使用 FCM 和 APNs 发送通知
编辑
学习如何使用 FCM 和 APNs 发送通知。
您可能需要对通知进行更精细的控制,在这种情况下,直接与 FCM 和 APNs 通信可能是必要的。Expo 平台并不强制您使用 Expo 应用服务,并且 expo-notifications API 是与推送服务无关的。
注意:本指南并不旨在成为通过 FCM 或 APNs 发送通知的全面资源。我们建议您阅读官方文档,以确保您遵循最新的说明。
获取 FCM 或 APNs 的设备令牌
使用 Expo 通知服务时,您使用通过 getExpoPushTokenAsync 获得的 ExpoPushToken。
如果您想通过 FCM 或 APNs 发送通知,您需要通过 getDevicePushTokenAsync 获取原生设备令牌。
import * as Notifications from 'expo-notifications'; ... - const token = (await Notifications.getExpoPushTokenAsync()).data; + const token = (await Notifications.getDevicePushTokenAsync()).data; // send token to your server
FCMv1 服务器
本指南基于 Firebase 官方文档。
与 FCM 的通信是通过发送 POST 请求来完成的。然而,在发送或接收任何通知之前,您需要按照步骤 配置 FCM 并获取您的 FCM-SERVER-KEY。
获取身份验证令牌
FCM 需要一个 Oauth 2.0 访问令牌,必须通过其中一种方法生成,“更新发送请求的授权”。
出于测试目的,您可以使用 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 deviceToken = process.env.FCM_DEVICE_TOKEN; const messageBody = { message: { token: deviceToken, 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)}`); }
experienceId 和 scopeKey 字段仅在使用 Expo Go 时适用(从 SDK 53 开始,推送通知支持已从 Expo Go 中移除)。否则,您的通知将无法发送到您的应用。FCM 在 通知有效负载 中提供了支持字段的列表,您可以通过查看 FirebaseRemoteMessage 来了解 Android 上 expo-notifications 支持哪些字段。
FCM 还提供了一些 多种语言的服务器端库,您可以使用这些库代替原始的 fetch 请求。
如何找到 FCM 服务器密钥
您的 FCM 服务器密钥可以通过确保您已经遵循了 配置步骤,而不是将您的 FCM 密钥上传到 Expo,您可以直接在您的服务器中使用该密钥(在上一个示例中的 FCM-SERVER-KEY)。
APNs 服务器
本文档基于 Apple 的文档,本节涵盖了让您入门的基础知识。
与 APNs 的通信比与 FCM 更复杂。一些库将所有这些功能包装成一到两个函数调用,例如 node-apn。然而,在下面的示例中,仅使用了一小部分库。
客户端 APNs 权限
只有当您的 iOS 应用具有 APNs 权限时,接收推送通知才有效。对于使用 CNG 的应用,Expo 配置需要以两种方式之一进行修改:
- 推荐:将
expo-notifications库添加到您的应用中,并确保其插件出现在您的 应用配置 的plugins数组中:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "plugins": [ %%placeholder-start%%... %%placeholder-end%% "expo-notifications" ] } }
- 如果您不打算使用
expo-notifications库,则应 手动将aps-environment权限添加到 Expo 配置中,如下示例所示:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "ios": { %%placeholder-start%%... %%placeholder-end%% "entitlements": { "aps-environment": "development" } } } }
如果您没有使用 CNG,那么您应该 在 Xcode 中添加推送通知权限。
注意:如果您正在将 Expo 应用程序从 SDK 51 及更早版本升级,您应该参考 此 FYI 文档。
授权
最初,在向 APNS 发送请求之前,您需要获得向您的应用发送通知的权限。这是通过使用 iOS 开发者凭证生成的 JSON Web 令牌授予的:
- 与您的应用相关的 APN 密钥(
.p8文件) - 上述
.p8文件的密钥 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 令牌 }); request.setEncoding('utf8'); request.write( JSON.stringify({ aps: { alert: { title: "📧 You've got mail!", body: 'Hello world! 🌐', }, }, experienceId: '@yourExpoUsername/yourProjectSlug', // 测试时需要在 Expo Go 应用中 scopeKey: '@yourExpoUsername/yourProjectSlug', // 测试时需要在 Expo Go 应用中 }) ); request.end();
此示例是最小的,并且不包含错误处理和连接池功能。出于测试目的,您可以参考
sendNotificationToAPNS示例代码。
APNs 在 通知有效负载 中提供了其支持字段的完整列表。