推送通知

配置通知权限、本地收件箱、Expo 推送 token 和可选服务端发送。

Expo 模板已经包含客户端通知体验:权限预热、Android 通知频道、Expo push token 获取、前台通知处理、本地收件箱,以及点击通知后打开深度链接。它还没有内置完整的推送发送后端。

真实远程推送测试需要 native development、preview 或 production build。Expo 文档说明,从 SDK 53 开始,Expo Go 的 Android 端不再支持 expo-notifications 远程推送;本模板使用 Expo SDK 54。

提供什么

Surface状态说明
NotificationsProviderIntegrated懒加载 expo-notifications,在 web 或缺少 native module 的 build 中也不会崩溃,并暴露 useNotifications()
app/permissions.tsxIntegrated在一个页面中预热通知、照片、相机和 tracking 权限
app/notifications.tsxIntegrated显示权限状态、当前 Expo push token、缓存的通知收件箱和清空动作
Android channelsIntegrated注册 defaultalerts 两个通知频道
通知点击路由Integrated从 notification response 的 data.url 读取 URL,并交给 openDeepLink()
服务端发送未接好客户端不持久化 token;可选的 send-test-push 函数依赖当前不存在的 device_tokens

lib/notifications.tsx 用 lazy require 包住 native module。如果 expo-notifications 无法加载,isNativePushAvailable 会是 false,请求动作会返回友好错误,而不是让应用崩溃。

客户端流程

注册 handler

Provider 挂载后会安装前台通知 handler:显示 alert,不播放声音,不设置 badge。它也会订阅收到通知和点击通知的事件。

创建 Android 通知频道

Android 端会创建:

default -> General, default importance
alerts  -> Alerts, high importance

Android 13+ 需要通知权限,系统弹窗也依赖在请求 push token 前先创建频道。

请求权限和 token

requestPermission() 调用 requestPermissionsAsync()。权限通过后,它调用 getExpoPushTokenAsync(),并把返回的 token 存在当前 React state 中。

保存本地收件箱

前台通知会被标准化为 { id, title, body, url, createdAt },插入收件箱顶部,最多保留 50 条,并通过 lib/cache.ts 持久化在 cache.notifications.inbox.v1

点击通知打开路由

如果 notification response 带有 data.url,Provider 会调用:

openDeepLink(url, "/notifications");

只有 lib/deep-links.ts 允许的路由会被打开,否则回退到通知页面。

尚未接好的部分

客户端会获取 Expo push token,但不会写入 Supabase。随附的 Supabase send-test-push 函数只是一个可选 APNs 调试 artifact,来自 native stack。它查询:

public.device_tokens

当前没有 migration 创建这张表,Expo 客户端也没有注册 native APNs token。请把它当作起点,而不是一个可直接使用的 Expo 推送后端。

如果继续使用 APNs 函数,也要更新它的 fallback deep link: send-test-push 当前回退到 soar:///notifications,而本模板默认 scheme 是 soar-starter-expo-dev://soar-starter-expo-preview://soar-starter-expo://

真正发送推送

如果走 Expo push service:

添加 token 表

创建按用户隔离的 Expo push token 表:

create table public.device_push_tokens (
  id uuid primary key default gen_random_uuid(),
  user_id uuid not null references auth.users(id) on delete cascade,
  platform text not null,
  expo_push_token text not null,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (user_id, expo_push_token)
);

alter table public.device_push_tokens enable row level security;

再添加基于 auth.uid() = user_id 的 insert/select/delete policies。

权限通过后持久化 token

getExpoPushTokenAsync() 成功后,为已登录用户 upsert token。如果你不希望共享设备继续收到账号相关消息,可以在退出登录时删除旧 token。

通过 Expo 发送

创建 Supabase Edge Function 或自己的 API route,读取 device_push_tokens,调用 Expo push API,并清理无效 receipts。服务端认证和 provider secret 都要放在 app bundle 外。

如果你更想直接使用 APNs/FCM,请扩展现有 send-test-push artifact:增加真正的 device_tokens migration,采集 native device token 而不是 Expo push token,并在生产前补上 Android FCM 路径。

验证

  • 在真机上安装 native development build。
  • 打开 Permissions,请求 Notifications 权限。
  • 打开 Notifications,确认状态变成 granted
  • 确认 Expo push token 出现。如果一直为空,检查 native 凭据和运行时日志。
  • 发送一条 data.url 指向 /notifications/refer 的测试推送,确认点击后能打开对应页面。

相关页面

On this page