使用 FCM 和 APNs 发送通知

编辑页面

了解如何使用 FCM 和 APNs 发送通知。


For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

你可能需要对通知进行更细粒度的控制,在这种情况下,直接与 FCM 和 APNs 通信可能是必要的。Expo 平台不会强制你使用 Expo Application Services,而 expo-notifications API 与推送服务无关。

注意:本指南并不旨在成为通过 FCM 或 APNs 发送通知的完整资源。我们建议你阅读官方文档,以确保你遵循的是最新说明。

获取 FCM 或 APNs 的设备令牌

使用 Expo 通知服务时,你会使用通过 getExpoPushTokenAsync 获取的 ExpoPushToken

如果你改为希望通过 FCM 或 APNs 发送通知,则需要使用 getDevicePushTokenAsync 获取原生设备令牌。

App.js
11import * as Notifications from 'expo-notifications';
22// ...
3const token = (await Notifications.getExpoPushTokenAsync()).data;
3const token = (await Notifications.getDevicePushTokenAsync()).data;
44// 将令牌发送到你的服务器

FCMv1 服务器

本指南基于 Firebase 官方文档

与 FCM 通信是通过发送 POST 请求完成的。不过,在发送或接收任何通知之前,你需要先按照步骤配置 FCM 并获取你的 FCM-SERVER-KEY

获取认证令牌

FCM 需要一个 Oauth 2.0 访问令牌,该令牌必须通过 "Update authorization of send requests" 中描述的方法之一获得。

出于测试目的,你可以使用 Google Auth Library 和上面获得的私钥文件,为单条通知获取一个短期令牌,如下面这个根据 Firebase 文档改编的 Node 示例所示:

import { JWT } from 'google-auth-library'; function getAccessTokenAsync( key: string // 你的 FCM 私钥文件内容 ) { return new Promise(function (resolve, reject) { const jwtClient = new JWT( key.client_email, null, key.private_key, ['https://www.googleapis.com/auth/cloud-platform'], null ); jwtClient.authorize(function (err, tokens) { if (err) { reject(err); return; } resolve(tokens.access_token); }); }); }

发送通知

下面的示例代码调用上面的 getAccessTokenAsync() 来获取 Oauth 2.0 令牌,然后构造并发送通知 POST 请求。请注意,与 FCM 旧版协议不同,请求的端点包含你的 Firebase 项目名称。

// FCM_SERVER_KEY: 指向你的 FCM 私钥文件路径的环境变量 // FCM_PROJECT_NAME: 你的 Firebase 项目名称 // FCM_DEVICE_TOKEN: 客户端的设备令牌(见本文上方) async function sendFCMv1Notification() { const key = require(process.env.FCM_SERVER_KEY); const firebaseAccessToken = await getAccessTokenAsync(key); const fcmToken = process.env.FCM_DEVICE_TOKEN; const messageBody = { message: { token: fcmToken, data: { channelId: 'default', message: 'Testing', title: `This is an FCM notification message`, body: JSON.stringify({ title: 'bodyTitle', body: 'bodyBody' }), scopeKey: '@yourExpoUsername/yourProjectSlug', experienceId: '@yourExpoUsername/yourProjectSlug', }, }, }; const response = await fetch( `https://fcm.googleapis.com/v1/projects/${process.env.FCM_PROJECT_NAME}/messages:send`, { method: 'POST', headers: { Authorization: `Bearer ${firebaseAccessToken}`, Accept: 'application/json', 'Accept-encoding': 'gzip, deflate', 'Content-Type': 'application/json', }, body: JSON.stringify(messageBody), } ); const readResponse = (response: Response) => response.json(); const json = await readResponse(response); console.log(`Response JSON: ${JSON.stringify(json, null, 2)}`); }

experienceIdscopeKey 字段仅在使用 Expo Go 时适用(从 SDK 53 开始,Expo Go 中已移除推送通知支持)。否则,你的通知将不会送达你的应用。FCM 在 通知负载 中提供了一份受支持字段列表,你也可以通过查看 FirebaseRemoteMessage 来了解 expo-notifications 在 Android 上支持哪些字段。

FCM 还提供了一些可用于替代原始 fetch 请求的 多种语言的服务端库

如何查找 FCM 服务器密钥

你可以通过确保已遵循配置步骤来找到你的 FCM 服务器密钥;并且不是将你的 FCM 密钥上传到 Expo,而是直接在你的服务器中使用该密钥(作为前一个示例中的 FCM-SERVER-KEY)。

APNs 服务器

信息 本文档基于 Apple 的文档,本节将介绍帮助你入门的基础内容。

与 APNs 通信比与 FCM 通信要复杂一些。一些库会将所有这些功能封装到一两个函数调用中,例如 node-apn。不过,在下面的示例中,仅使用了一组最少的库。

客户端 APNs 权限

只有当你的 iOS 应用具有 APNs 权限时,接收推送通知才有效。对于使用 CNG 的应用, Expo 配置需要通过以下两种方式之一进行修改:

  • 推荐:将 expo-notifications 库添加到你的应用中,并确保其插件出现在你的 app configplugins 数组中:
app.json
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "plugins": [ %%placeholder-start%%... %%placeholder-end%% "expo-notifications" ] } }
app.json
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "ios": { %%placeholder-start%%... %%placeholder-end%% "entitlements": { "aps-environment": "development" } } } }

如果你没有使用 CNG,那么你应该 在 Xcode 中添加推送通知 entitlement

注意:如果你正在将一个 Expo 应用从 SDK 51 及更早版本升级,那么你应该参考 这份 FYI 文档

授权

最初,在向 APNS 发送请求之前,你需要获得向你的应用发送通知的权限。这是通过一个 JSON web token 授予的,该 token 使用 iOS 开发者凭据生成:

  • 与你的应用关联的 APN key(.p8 文件)
  • 上述 .p8 文件的 Key ID
  • 你的 Apple Team ID
const jwt = require("jsonwebtoken"); const authorizationToken = jwt.sign( { iss: "YOUR-APPLE-TEAM-ID" iat: Math.round(new Date().getTime() / 1000), }, fs.readFileSync("./path/to/appName_apns_key.p8", "utf8"), { header: { alg: "ES256", kid: "YOUR-P8-KEY-ID", }, } );

HTTP/2 连接

在获得 authorizationToken 之后,你可以与 Apple 的服务器建立 HTTP/2 连接。在开发环境中,请向 api.sandbox.push.apple.com 发送请求。在生产环境中,请向 api.push.apple.com 发送请求。

下面是构造请求的方法:

const http2 = require('http2'); const client = http2.connect( IS_PRODUCTION ? 'https://api.push.apple.com' : 'https://api.sandbox.push.apple.com' ); const request = client.request({ ':method': 'POST', ':scheme': 'https', 'apns-topic': 'YOUR-BUNDLE-IDENTIFIER', ':path': '/3/device/' + nativeDeviceToken, // 这是你在客户端获取到的原生设备令牌 authorization: `bearer ${authorizationToken}`, // 这是在“授权”步骤中生成的 JSON web token }); request.setEncoding('utf8'); request.write( JSON.stringify({ aps: { alert: { title: "\uD83D\uDCE7 你有新邮件!", body: '你好,世界! \uD83C\uDF10', }, }, experienceId: '@yourExpoUsername/yourProjectSlug', // 仅在使用旧版 Expo Go 进行测试时需要(SDK 52 及更早版本) scopeKey: '@yourExpoUsername/yourProjectSlug', // 仅在使用旧版 Expo Go 进行测试时需要(SDK 52 及更早版本) }) ); request.end();

此示例为最小示例,不包含错误处理和连接池。若用于测试,你可以参考 sendNotificationToAPNS 示例代码。

APNs 在 通知负载 中提供了其支持字段的完整列表。