首页指南参考教程

使用 FCMv1 和 APN 发送通知

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


你可能需要对通知进行更细粒度的控制,因此可能需要直接与 FCM 和 APN 进行通信。Expo 平台不会限制你使用 Expo 的应用服务,并且 expo-notifications API 与推送服务无关。

¥You may need finer-grained control over your notifications, so communicating directly with FCM and APNs may be necessary. The Expo platform does not lock you into using Expo's application services, and the expo-notifications API is push-service agnostic.

有关与旧版 FCM 服务通信的文档,请参阅 使用 FCM 旧服务器发送通知
在直接通过 FCMv1 或 APNs 发送通知之前,你需要 打开问题

FCMv1 服务器

¥FCMv1 server

本指南基于 Firebase 官方文档

¥This guide is based on Firebase official documentation.

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

¥Communicating with FCM is done by sending a POST request. However, before sending or receiving any notifications, you'll need to follow the steps to configure FCMv1 and get your FCM-SERVER-KEY.

获取身份验证令牌

¥Getting an authentication token

FCMv1 需要 Oauth 2.0 访问令牌,必须通过 "更新发送请求的授权" 中描述的方法之一获取。

¥FCMv1 requires an Oauth 2.0 access token, which must be obtained via one of the methods described in "Update authorization of send requests".

出于测试目的,你可以使用 Google Auth 库和上面获得的私钥文件来获取单个通知的短期令牌,如从 Firebase 文档改编的此 Node 示例所示:

¥For testing purposes, you can use the Google Auth Library and your private key file obtained above, to obtain a short lived token for a single notification, as in this Node example adapted from Firebase documentation:

import { JWT } from 'google-auth-library';

function getAccessTokenAsync(
  key: string // Contents of your FCM private key file
) {
  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);
    });
  });
}

发送通知

¥Sending the notification

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

¥The example code below calls getAccessTokenAsync() above to get the Oauth 2.0 token, then constructs and sends the notification POST request. Note that unlike FCM legacy protocol, the endpoint for the request includes the name of your Firebase project.

// FCM_SERVER_KEY: Environment variable with the path to your FCM private key file
// FCM_PROJECT_NAME: Your Firebase project name
// FCM_DEVICE_TOKEN: The client's device token (see above in this document)

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)}`);
}

experienceIdscopeKey 字段为必填字段。否则,你的通知将不会发送到你的应用。FCM 在 通知负载 中有一个支持的字段列表,你可以通过查看 FirebaseRemoteMessage 来了解 Android 上的 expo-notifications 支持哪些字段。

¥The experienceId and scopeKey fields are required. Otherwise, your notifications will not go through to your app. FCM has a list of supported fields in the notification payload, and you can see which ones are supported by expo-notifications on Android by looking at the FirebaseRemoteMessage.

FCM 还提供了一些你可以使用的 几种不同语言的服务器端库,而不是原始 fetch 请求。

¥FCM also provides some server-side libraries in a few different languages you can use instead of raw fetch requests.

如何查找 FCM 服务器密钥

¥How to find FCM server key

你可以通过确保遵循 配置步骤 来找到你的 FCM 服务器密钥,并且无需将 FCM 密钥上传到 Expo,而是直接在服务器中使用该密钥(如上例中的 FCM-SERVER-KEY)。

¥Your FCM server key can be found by making sure you've followed the configuration steps, and instead of uploading your FCM key to Expo, you would use that key directly in your server (as the FCM-SERVER-KEY in the previous example).

APNs 服务器

¥APNs server

本文档基于 苹果的文档,本节介绍了帮助你入门的基础知识。

与 APN 的通信比与 FCM 的通信稍微复杂一些。有些库将所有这些功能封装到一两个函数调用中,例如 node-apn。但是,在下面的示例中,使用了最少的库集。

¥Communicating with APNs is a little more complicated than with FCM. Some libraries wrap all of this functionality into one or two function calls such as node-apn. However, in the examples below, a minimum set of libraries are used.

授权

¥Authorization

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

¥Initially, before sending requests to APNS, you need permission to send notifications to your app. This is granted via a JSON web token which is generated using iOS developer credentials:

  • 与你的应用关联的 APN 密钥(.p8 文件)

    ¥APN key (.p8 file) associated with your app

  • 上述 .p8 文件的密钥 ID

    ¥Key ID of the above .p8 file

  • 你的 Apple 团队 ID

    ¥Your 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 连接

¥HTTP/2 connection

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

¥After getting the authorizationToken, you can open up an HTTP/2 connection to Apple's servers. In development, send requests to api.sandbox.push.apple.com. In production, send requests to api.push.apple.com.

以下是构建请求的方法:

¥Here's how to construct the request:

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, // This is the native device token you grabbed client-side
  authorization: `bearer ${authorizationToken}`, // This is the JSON web token generated in the "Authorization" step
});
request.setEncoding('utf8');

request.write(
  JSON.stringify({
    aps: {
      alert: {
        title: "📧 You've got mail!",
        body: 'Hello world! 🌐',
      },
    },
    experienceId: '@yourExpoUsername/yourProjectSlug', // Required when testing in the Expo Go app
    scopeKey: '@yourExpoUsername/yourProjectSlug', // Required when testing in the Expo Go app
  })
);
request.end();

此示例是最小的,不包含错误处理和连接池。出于测试目的,你可以参考 sentNotificationToAPNS 示例代码。

¥This example is minimal and includes no error handling and connection pooling. For testing purposes, you can refer to sentNotificationToAPNS example code.

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

¥APNs provide their full list of supported fields in the notification payload.

有效负载格式

¥Payload formats

上一节中的示例提供了最低限度的通知请求。你可能需要发送类别标识符、自定义声音、图标、自定义键值对等。expo-notifications 文件 它支持的所有字段,下面是 Expo 在其通知服务中发送的示例有效负载:

¥The examples in the previous section provide the bare minimum notification requests. You may have to send category identifiers, custom sounds, icons, custom key-value pairs, and so on. expo-notifications documents all the fields it supports, and below are the example payloads Expo sends in its notifications service:

安卓

¥Android

{
  "token": native device token string,
  "collapse_key": string that identifies notification as collapsible,
  "priority": "normal" || "high",
  "data": {
    "experienceId": "@yourExpoUsername/yourProjectSlug",
    "scopeKey": "@yourExpoUsername/yourProjectSlug",
    "title": title of your message,
    "message": body of your message,
    "channelId": the android channel ID associated with this notification,
    "categoryId": the category associated with this notification,
    "icon": the icon to show with this notification,
    "link": the link this notification should open,
    "sound": boolean or the custom sound file you'd like to play,
    "vibrate": "true" | "false" | number[],
    "priority": AndroidNotificationPriority, // https://expo.nodejs.cn/versions/latest/sdk/notifications/#androidnotificationpriority
    "badge": the number to set the icon badge to,
    "body": { object of key-value pairs }
  }
}

iOS 系统

¥iOS

{
  "aps": {
    "alert": {
      "title": title of your message,
      "subtitle": subtitle of your message (shown below title, above body),
      "body": body of your message,
      "launch-image": the name of the launch image file to display,
    },
    "category": the category associated with this notification,
    "badge": number to set badge count to upon notification's arrival,
    "sound": the sound to play when the notification is received,
    "thread-id": app-specific identifier for grouping related notifications
  },
  "body": { object of key-value pairs },
  "experienceId": "@yourExpoUsername/yourProjectSlug",
  "scopeKey": "@yourExpoUsername/yourProjectSlug",
}

Firebase 通知类型

¥Firebase notification types

Firebase Cloud Messaging 消息有两种类型:通知和数据消息

¥There are two types of Firebase Cloud Messaging messages: notification and data messages.

  1. 通知消息仅由 Firebase 库处理(和显示)。它们不一定会唤醒应用,并且 expo-notifications 不会知道你的应用已收到任何通知。

    ¥Notification messages are only handled (and displayed) by the Firebase library. They don't necessarily wake the app, and expo-notifications will not be made aware that your app has received any notification.

  2. Firebase 库不处理数据消息。它们会立即移交给你的应用进行处理。这就是 expo-notifications 解释数据有效负载并根据该数据采取行动的地方。在几乎所有情况下,这都是你必须发送的通知类型。

    ¥Data messages are not handled by the Firebase library. They are immediately handed off to your app for processing. That's where expo-notifications interprets the data payload and takes action based on that data. In almost all cases, this is the type of notification you have to send.

如果你直接通过 Firebase 发送通知类型的消息而不是数据,你将不知道用户是否与通知进行了交互(没有可用的 onNotificationResponse 事件),并且你将无法解析通知负载中的任何数据 你的通知事件相关的监听器。

¥If you send a message of type notification instead of data directly through Firebase, you won't know if a user interacted with the notification (no onNotificationResponse event available), and you won't be able to parse the notification payload for any data in your notification event-related listeners.

当你需要 expo-notifications 尚未公开的配置选项时,使用通知类型消息会很有用。通常,它可能导致比使用数据类型消息更难以预测的情况。但是,你可能需要将遇到的任何问题直接报告给 Google。

¥Using notification-type messages can be beneficial when you need a configuration option that is not yet exposed by expo-notifications. Generally, it may lead to less predictable situations than using data-type messages. However, you may need to report any issue you encounter directly to Google.

以下是使用 Node.js Firebase Admin SDK 发送数据类型消息而不是通知类型的每种类型的示例:

¥Below is an example of each type using Node.js Firebase Admin SDK to send data-type messages instead of notification-type:

const devicePushToken = /* ... */;
const options = /* ... */;

// ❌ The following payload has a root-level notification object and
// it will not trigger expo-notifications and may not work as expected.
admin.messaging().sendToDevice(
  devicePushToken,
  {
    notification: {
      title: "This is a notification-type message",
      body: "`expo-notifications` will never see this 😢",
    },
    data: {
      photoId: 42,
    },
  },
  options
);

// ✅ There is no "notification" key in the root level of the payload
// so the message is a "data" message, thus triggering expo-notifications.
admin.messaging().sendToDevice(
  devicePushToken,
  {
    data: {
      title: "This is a data-type message",
      message: "`expo-notifications` events will be triggered 🤗",
      // ⚠️ Notice the schema of this payload is different
      // than that of Firebase SDK. What is there called "body"
      // here is a "message". For more info see:
      // https://expo.nodejs.cn/versions/latest/sdk/notifications/#android-push-notification-payload-specification

      body:                              // As per Android payload format specified above, the
        JSON.stringify({ photoId: 42 }), // additional "data" should be placed under "body" key.
    },
  },
  options
);
Expo 中文网 - 粤ICP备13048890号