转到控制台

向多台设备发送消息

Firebase 云消息传递提供了以下两种将消息发送至多台设备的方法:

本教程主要介绍如何使用 HTTP 或 XMPP 协议,从应用服务器为 FCM 发送主题消息,并在 Web 应用中接收和处理此类消息。内容涵盖后台应用和前台应用的消息处理方式。

设置 SDK

如果您已针对 FCM 设置 JavaScript 客户端应用,或已经完成接收消息所需执行的步骤,则本部分可能包含了您已经完成的步骤。

将 Firebase 添加到您的 JavaScript 项目

如果您尚未将 Firebase 添加到您的 JavaScript 项目,请先添加。

检索消息对象

// Retrieve Firebase Messaging object.
const messaging = firebase.messaging();

请求接收通知的权限

Web Notifications API 中的 Notification.requestPermission() 方法会显示一个征询用户同意的对话框,让用户向您的应用授予权限,以便在浏览器中接收通知。如果用户拒绝授予权限,FCM 注册令牌请求会导致错误。

Notification.requestPermission().then((permission) => {
  if (permission === 'granted') {
    console.log('Notification permission granted.');
    // TODO(developer): Retrieve an Instance ID token for use with FCM.
    // ...
  } else {
    console.log('Unable to get permission to notify.');
  }
});

获取注册令牌

本部分介绍如何检索应用实例的注册令牌以及如何监控令牌刷新事件。因为令牌可能会在初次启用后发生轮替,所以您应该监控令牌刷新并始终检索最近更新的注册令牌。

注册令牌可能会在发生下列情况时更改:

  • Web 应用删除注册令牌时。
  • 用户清除浏览器数据时。在这种情况下,调用 getToken 可检索新令牌。

检索当前注册令牌

如果需要检索当前令牌,请调用 getToken。当尚未获得授权时,此方法会返回 null。在其他情况下,它会返回一个令牌,或由于出现错误而拒绝该 Promise。

消息传递服务需要一个 firebase-messaging-sw.js 文件。除非您已经有 firebase-messaging-sw.js 文件,否则请创建一个使用该名称的空文件,并在检索令牌之前将该文件放在您网域的根目录中。您可以在后面的客户端设置流程中向该文件添加有意义的内容。

如需检索当前令牌,请参考以下代码:

// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
messaging.getToken().then((currentToken) => {
  if (currentToken) {
    sendTokenToServer(currentToken);
    updateUIForPushEnabled(currentToken);
  } else {
    // Show permission request.
    console.log('No Instance ID token available. Request permission to generate one.');
    // Show permission UI.
    updateUIForPushPermissionRequired();
    setTokenSentToServer(false);
  }
}).catch((err) => {
  console.log('An error occurred while retrieving token. ', err);
  showToken('Error retrieving Instance ID token. ', err);
  setTokenSentToServer(false);
});

监控令牌刷新

每次生成新的令牌时,都会触发 onTokenRefresh 回调,因此,在上下文中调用 getToken 可以确保您访问的是当前可用的注册令牌。

// Callback fired if Instance ID token is updated.
messaging.onTokenRefresh(() => {
  messaging.getToken().then((refreshedToken) => {
    console.log('Token refreshed.');
    // Indicate that the new Instance ID token has not yet been sent to the
    // app server.
    setTokenSentToServer(false);
    // Send Instance ID token to app server.
    sendTokenToServer(refreshedToken);
    // ...
  }).catch((err) => {
    console.log('Unable to retrieve refreshed token ', err);
    showToken('Unable to retrieve refreshed token ', err);
  });
});

获取令牌后,将其发送到您的应用服务器,并使用您首选的方法进行存储。您可以使用 Instance ID server API 来获取有关订阅的信息

为客户端应用订阅主题

有了注册令牌和主题名称,您就可以使用 Google Instance ID server API 向该主题添加该令牌。请在以下端点调用 Instance ID API,并提供应用实例的注册令牌和主题名称:

 https://iid.googleapis.com/iid/v1/<REGISTRATION_TOKEN>/rel/topics/<TOPIC_NAME>

例如,如需为某个应用实例订阅名为“movies”的主题,请从您的服务器将以下 POST 请求发送到该端点,按如下所示在授权标头中添加服务器 API 密钥:

https://iid.googleapis.com/iid/v1/nKctODamlM4:CKrh_PC8kIb7O...clJONHoA/rel/topics/movies
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

鉴于服务器密钥的敏感性,切勿从客户端发送这种类型的请求。

请求成功则返回 HTTP 200 OK。如需详细了解错误响应以及如何发送批量请求,请参阅为应用实例创建关系映射

您可以将一个注册令牌列表传递给 Firebase Admin SDK 订阅方法,以便为相应的多台设备订阅某个主题:

Node.js

// These registration tokens come from the client FCM SDKs.
var registrationTokens = [
  'YOUR_REGISTRATION_TOKEN_1',
  // ...
  'YOUR_REGISTRATION_TOKEN_n'
];

// Subscribe the devices corresponding to the registration tokens to the
// topic.
admin.messaging().subscribeToTopic(registrationTokens, topic)
  .then(function(response) {
    // See the MessagingTopicManagementResponse reference documentation
    // for the contents of response.
    console.log('Successfully subscribed to topic:', response);
  })
  .catch(function(error) {
    console.log('Error subscribing to topic:', error);
  });

Java

// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n"
);

// Subscribe the devices corresponding to the registration tokens to the
// topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().subscribeToTopic(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
System.out.println(response.getSuccessCount() + " tokens were subscribed successfully");

Python

# These registration tokens come from the client FCM SDKs.
registration_tokens = [
    'YOUR_REGISTRATION_TOKEN_1',
    # ...
    'YOUR_REGISTRATION_TOKEN_n',
]

# Subscribe the devices corresponding to the registration tokens to the
# topic.
response = messaging.subscribe_to_topic(registration_tokens, topic)
# See the TopicManagementResponse reference documentation
# for the contents of response.
print(response.success_count, 'tokens were subscribed successfully')

Go

// These registration tokens come from the client FCM SDKs.
registrationTokens := []string{
	"YOUR_REGISTRATION_TOKEN_1",
	// ...
	"YOUR_REGISTRATION_TOKEN_n",
}

// Subscribe the devices corresponding to the registration tokens to the
// topic.
response, err := client.SubscribeToTopic(ctx, registrationTokens, topic)
if err != nil {
	log.Fatalln(err)
}
// See the TopicManagementResponse reference documentation
// for the contents of response.
fmt.Println(response.SuccessCount, "tokens were subscribed successfully")

C#

// These registration tokens come from the client FCM SDKs.
var registrationTokens = new List<string>()
{
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n",
};

// Subscribe the devices corresponding to the registration tokens to the
// topic
var response = await FirebaseMessaging.DefaultInstance.SubscribeToTopicAsync(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
Console.WriteLine($"{response.SuccessCount} tokens were subscribed successfully");

利用 Admin FCM API,您还可以通过将注册令牌传递给相应的方法来为设备退订主题:

Node.js

// These registration tokens come from the client FCM SDKs.
var registrationTokens = [
  'YOUR_REGISTRATION_TOKEN_1',
  // ...
  'YOUR_REGISTRATION_TOKEN_n'
];

// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
admin.messaging().unsubscribeFromTopic(registrationTokens, topic)
  .then(function(response) {
    // See the MessagingTopicManagementResponse reference documentation
    // for the contents of response.
    console.log('Successfully unsubscribed from topic:', response);
  })
  .catch(function(error) {
    console.log('Error unsubscribing from topic:', error);
  });

Java

// These registration tokens come from the client FCM SDKs.
List<String> registrationTokens = Arrays.asList(
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n"
);

// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
TopicManagementResponse response = FirebaseMessaging.getInstance().unsubscribeFromTopic(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
System.out.println(response.getSuccessCount() + " tokens were unsubscribed successfully");

Python

# These registration tokens come from the client FCM SDKs.
registration_tokens = [
    'YOUR_REGISTRATION_TOKEN_1',
    # ...
    'YOUR_REGISTRATION_TOKEN_n',
]

# Unubscribe the devices corresponding to the registration tokens from the
# topic.
response = messaging.unsubscribe_from_topic(registration_tokens, topic)
# See the TopicManagementResponse reference documentation
# for the contents of response.
print(response.success_count, 'tokens were unsubscribed successfully')

Go

// These registration tokens come from the client FCM SDKs.
registrationTokens := []string{
	"YOUR_REGISTRATION_TOKEN_1",
	// ...
	"YOUR_REGISTRATION_TOKEN_n",
}

// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
response, err := client.UnsubscribeFromTopic(ctx, registrationTokens, topic)
if err != nil {
	log.Fatalln(err)
}
// See the TopicManagementResponse reference documentation
// for the contents of response.
fmt.Println(response.SuccessCount, "tokens were unsubscribed successfully")

C#

// These registration tokens come from the client FCM SDKs.
var registrationTokens = new List<string>()
{
    "YOUR_REGISTRATION_TOKEN_1",
    // ...
    "YOUR_REGISTRATION_TOKEN_n",
};

// Unsubscribe the devices corresponding to the registration tokens from the
// topic
var response = await FirebaseMessaging.DefaultInstance.UnsubscribeFromTopicAsync(
    registrationTokens, topic);
// See the TopicManagementResponse reference documentation
// for the contents of response.
Console.WriteLine($"{response.SuccessCount} tokens were unsubscribed successfully");

subscribeToTopic()unsubscribeFromTopic() 方法会返回一个包含 FCM 响应的对象。无论请求中指定的注册令牌的数量为何,返回类型的格式都是相同的。

如果出现错误(身份验证失败、令牌或主题无效等),这些方法会返回错误。如需错误代码的完整列表(包括说明和解决步骤),请参阅 Admin FCM API 错误

接收和处理主题消息

消息的行为取决于相应页面的状态:页面位于前台(获得注意)、位于后台、隐藏在其他标签页后,还是完全关闭。无论哪种情况,页面都必须处理 onMessage 回调;但当页面位于后台时,您可能还需要处理 setBackgroundMessageHandler 或配置显示通知,以便用户将您的 Web 应用调到前台。

应用状态 通知 数据 两者
前台 onMessage onMessage onMessage
后台 (Service Worker) 由 SDK 显示通知 setBackgroundMessageHandler 由 SDK 显示通知

在 Web 应用位于前台时处理消息

为接收 onMessage 事件,您的应用必须在 firebase-messaging-sw.js 中定义 Firebase 消息传递 Service Worker。或者,您也可以使用 useServiceWorker 指定一个现有的 Service Worker。

// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/6.3.4/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/6.3.4/firebase-messaging.js');

// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
  'messagingSenderId': 'YOUR-SENDER-ID'
});

// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();

当您的应用位于前台时(用户当前正在查看您的网页),您可以直接在页面中接收数据和通知载荷。

// Handle incoming messages. Called when:
// - a message is received while the app has focus
// - the user clicks on an app notification created by a service worker
//   `messaging.setBackgroundMessageHandler` handler.
messaging.onMessage((payload) => {
  console.log('Message received. ', payload);
  // ...
});

在 Web 应用位于后台时处理消息

当应用位于后台时,收到的所有消息都会触发在浏览器中显示通知。您可以在从应用服务器发送请求时指定用于此通知的选项(如标题或点击操作),也可以在客户端中使用 Service Worker 逻辑进行指定。

在发送请求中设置通知选项

对于从应用服务器发送的通知消息,FCM JavaScript API 支持 fcm_options.link 键。通常情况下,此键会被设置为 Web 应用中的某一页面:

https://fcm.googleapis.com//v1/projects/<YOUR-PROJECT-ID>/messages:send
Content-Type: application/json
Authorization: bearer <YOUR-ACCESS-TOKEN>

{
  "message": {
    "topic": "matchday"
    "notification": {
      "title": "Background Message Title",
      "body": "Background message body"
    },
    "webpush": {
      "fcm_options": {
        "link": "https://dummypage.com"
      }
    }
  }
}

如果链接值指向已在某个浏览器标签页中打开的页面,那么当用户点击通知时,系统会将该标签页放到前台。 如果相应页面尚未打开,那么当用户点击通知时,系统会在一个新标签页中打开该页面。

由于数据消息不支持 fcm_options.link,因此我们建议您在所有数据消息中添加通知载荷。或者,您也可以使用 Service Worker 处理通知。

如需详细了解通知消息与数据消息之间的区别,请参阅消息类型

在 Service Worker 中设置通知选项

对于数据消息,您可以在 Service Worker 中设置通知选项。 首先,在 Service Worker 中初始化您的应用:

// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/6.3.4/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/6.3.4/firebase-messaging.js');

// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
  'messagingSenderId': 'YOUR-SENDER-ID'
});

// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();

要设置选项,请在 firebase-messaging-sw.js 中调用 setBackgroundMessageHandler。在本示例中,我们将创建一个包含标题、正文和图标字段的通知。

messaging.setBackgroundMessageHandler(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // Customize notification here
  const notificationTitle = 'Background Message Title';
  const notificationOptions = {
    body: 'Background Message body.',
    icon: '/firebase-logo.png'
  };

  return self.registration.showNotification(notificationTitle,
    notificationOptions);
});

构建发送请求

从服务器端向 Firebase 云消息传递主题发送消息,与将消息发送到单台设备或用户组非常相似。应用服务器会将 to 键设为类似 /topics/yourTopic 这样的值。开发者可以选择符合以下正则表达式的任何主题名称:"/topics/[a-zA-Z0-9-_.~%]+"

主题 HTTP POST 请求

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
  "message":{
    "topic" : "foo-bar",
    "notification" : {
      "body" : "This is a Firebase Cloud Messaging Topic Message!",
      "title" : "FCM Message",
      }
   }
}

用 cURL 发送:

curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "FCM Message",
    "body": "This is a Firebase Cloud Messaging Topic Message!",
  },
  "topic" : "foo-bar"
}' "https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1"

主题 HTTP 响应

{
    "name": "projects/myproject-b5ae1/messages/5735743068807585451"
}

主题 XMPP 消息

发送至一个主题:

<message id="">
  <gcm xmlns="google:mobile:data">
{
  "to" : /topics/foo-bar",
  "priority" : "high",
  "notification" : {
    "body" : "This is a Firebase Cloud Messaging Topic Message!",
    "title" : "FCM Message",
  }
}
  </gcm>
</message>

主题 XMPP 响应

//Success example:
{
  "message_id": "1023456"
}

//failure example:
{
  "error": "TopicsMessageRateExceeded"
}