▌Introduction
FCM provides 2 ways for sending
message to multiple devices:
The difference for the 2 methods is
as following (based on the official document on Dec, 2018)
Topic messaging
|
Device group
messaging
|
|
Description
|
Based on the publish/subscribe model, FCM topic messaging
allows you to send a message to multiple devices that have been subscribed to
a particular topic.
It’s Optimized for throughput rather than latency
|
All devices in a group share a common notification key, which
is the token that FCM uses to fan out messages to all devices in the group.
|
Scenario
|
Broadcast discount or gaming scores to users
|
l
Sending messages to multiple devices per
user
l
Sending a message to a specific circle of
friends.
|
Limits
|
l
One app instance can be subscribed to no
more than 2000 topics
l
Unlimited
subscriptions for each topic
|
In other words, no more than 20 devices in a single device
group
|
We will use Topic
messaging to broadcast a Confirmed Order from one user to others. (Something
like the popup message on Booking.com to notify other’s reservation on the
hotel you are watching)
▋Related articles
▌Environment
▋Vue.js 2.5.11
▋Vue CLI 3.2.1
▋Firebase Javascript SDK 5.5.8
▋VueFire 1.4.5
▌Google Instance ID API
The first
step that we have to know how to subscribe/unsubscribe a topic thru Google Instance
ID API.
▋Subscribe topic
▋Subscribe one device to a topic
Http method
|
POST
|
|
Url
|
https://iid.googleapis.com/iid/v1/<registration
token>/rel/topics/<topic name>
|
|
Header
|
Authorization
|
key=<Server key>
|
▋Subscribe devices to a topic
Http method
|
POST
|
|
Url
|
https://iid.googleapis.com/iid/v1:batchAdd
|
|
Header
|
Authorization
|
key=<Server key>
|
Content-Type
|
application/json
|
|
Body
|
{
"to": "/topics/<topic name>",
"registration_tokens": [
"<registration token 1>",
"<registration
token 2>",
…
]
}
|
▋Unsubbscribe topic
Http method
|
POST
|
|
Url
|
https://iid.googleapis.com/iid/v1:batchRemove
|
|
Header
|
Authorization
|
key=<Server key>
|
Content-Type
|
application/json
|
|
Body
|
{
"to": "/topics/<topic name>",
"registration_tokens": [
"<registration token 1>",
"<registration
token 2>",
…
]
}
|
▋Get information
To see
what topics had been subscribed, we can use the Get-information-about-app-instances
API.
Http method
|
GET
|
|
Url
|
https://iid.googleapis.com/iid/info/<registration
token>?details=true
|
|
Header
|
Authorization
|
key=<Server key>
|
Which
will response the JSON indicates what topics the device subscribed.
{
"connectDate": "2018-12-10",
"application": "com.chrome.windows",
"subtype": "wp:http://localhost:8081/#123CD065-4567-4138-9735-B83EB0D55-V2",
"scope": "*",
"authorizedEntity": "1234567890",
"rel": {
"topics": {
"orders": {
"addDate": "2018-12-10"
}
}
},
"connectionType": "WIFI",
"platform": "BROWSER"
}
▌Implement: Cloud Functions with Google Instance ID API
Now we can implement the Firebase Cloud Functions to
support subscription/un-subscription with Google
Instance ID API.
First, We must install request(Simplified HTTP client),
$ npm install request
and create firebase.config.js
to store the Server key.
var firebaseConfig = {
serverKey: "<Your
project's Cloud Messaging Server key>"
};
module.exports = firebaseConfig;
▋Subscription
const functions = require("firebase-functions")
const cors = require("cors")({ origin: true });
const httpClient = require("request");
const firebaseConfig = require("./firebase.config")
exports.subscribeTopic =
functions.https.onRequest((request, response) => {
cors(request,
response, () => {
const userToken =
request.get("token");
const topic =
request.query.topic;
if (userToken)
{ //Subscribe ONE token to a topic
httpClient({
url: `https://iid.googleapis.com/iid/v1/${userToken}/rel/topics/${topic}`,
method: 'POST',
headers: {
Authorization: "key=" +
firebaseConfig.serverKey
}
}, function (err, res,
body) {
if (err) {
console.log(err);
response.send("Failed to subscribe topic: " + topic);
} else {
console.log(res.statusCode, body);
response.send("Successfully subscribed to topic:
" + topic);
}
});
}
else { //Subscribe MULTIPLE tokens to a topic
let userTokens
= request.body.tokens; //tokens should be Array
// 1. Google
API
let data = '{
"to": "/topics/' + topic + '",
"registration_tokens": ' + JSON.stringify(userTokens) + '}';
httpClient({
url: "https://iid.googleapis.com/iid/v1:batchAdd",
method: 'POST',
headers: {
Authorization: "key=" +
firebaseConfig.serverKey
},
body: data
}, function (err, res,
body) {
if (err) {
console.log(err);
response.send("Failed to subscribe topic: " + topic);
} else {
console.log(res.statusCode, body);
response.send("Successfully subscribed to topic:
" + topic);
}
});
}
});
})
▋Demo: single-device subscription
Http method
|
POST
|
|
Url
|
https://xxxx.cloudfunctions.net/subscribeTopic?topic=<topic
name>
|
|
Header
|
token
|
<registration token>
|
▋Demo: multiple-devices subscription
Http method
|
POST
|
|
Url
|
https://xxxx.cloudfunctions.net/subscribeTopic?topic=orders
|
|
Content-Type
|
application/json
|
|
Body
|
{
"tokens": [
"<registration token 1>",
"<registration
token 2>",
…
]
}
|
▋Unsubscription
const functions = require("firebase-functions")
const cors = require("cors")({ origin: true });
const httpClient = require("request");
const firebaseConfig = require("./firebase.config")
exports.unsubscribeTopic =
functions.https.onRequest((request, response) => {
const userTokens
= request.body.tokens; //tokens should be Array
const topic =
request.query.topic;
cors(request, response, () => {
cors(request, response, () => {
//1. By
Google API
let data = '{
"to": "/topics/' + topic + '",
"registration_tokens": ' + JSON.stringify(userTokens) + '}';
httpClient({
url: "https://iid.googleapis.com/iid/v1:batchRemove",
method:
'POST',
headers: {
Authorization: "key=" +
firebaseConfig.serverKey
},
body:
data
}, function (err, res,
body) {
if (err) {
console.log(err);
response.send("Failed to unsubscribe topic: " + topic);
} else {
console.log(res.statusCode, body);
response.send("Successfully unsubscribed to topic:
" + topic);
}
});
});
})
▋Demo: multiple-devices unsubscription
Http method
|
POST
|
|
Url
|
https://xxxx.cloudfunctions.net/unsubscribeTopic?topic=orders
|
|
Content-Type
|
application/json
|
|
Body
|
{
"tokens": [
"<registration token 1>",
"<registration
token 2>",
…
]
}
|
▌Implement: Cloud Functions with Firebase Admin SDK
However,
we can use the Firebase Admin SDK (interface: admin.messaging.Messaging)
to do the subscription/un-subscription as well.
▋Subscription
const functions = require("firebase-functions")
const admin = require("firebase-admin")
const cors = require("cors")({ origin: true });
const firebaseConfig = require("./firebase.config")
exports.subscribeTopic =
functions.https.onRequest((request, response) => {
cors(request,
response, () => {
const topic =
request.query.topic;
const userTokens
= request.body.tokens; //tokens should be Array
response.end();
return
admin.messaging().subscribeToTopic(userTokens, topic)
.then(function (response) {
console.log('Successfully subscribed to topic:', response);
})
.catch(function (err) {
console.error("Failed to subscribe topic: " + topic,
err);
});
});
})
▋Unsubscription
const functions = require("firebase-functions")
const admin = require("firebase-admin")
const cors = require("cors")({ origin: true });
const firebaseConfig = require("./firebase.config")
exports.unsubscribeTopic =
functions.https.onRequest((request, response) => {
const userTokens
= request.body.tokens; //tokens should be Array
const topic =
request.query.topic;
cors(request, response, () => {
cors(request, response, () => {
response.end();
return admin.messaging().unsubscribeFromTopic(userTokens,
topic)
.then(function (response)
{
console.log('Successfully subscribed to topic:', response);
})
.catch(function (err) {
console.error("Failed to unsubscribe topic: " + topic,
err);
});
});
})
So far we had implemented the Cloud Functions to
subscribe/unsubscribe topic.
You can try implement the client side like doing
subscription on certain topics when a device first get or refresh it’s token.
For example, call the following method when the
registration token is known.
subscribeTopic(token, topic) {
console.log("Start
subscribe topic with token: " + token);
this.axios
.get(
"https://us-central1-shopcart-vue.cloudfunctions.net/subscribeTopic?topic=" + topic,
{
headers: {
token: token
}
}
)
.then(result => {
console.log(result);
});
}
Next step, we will create a cloud function which
will send message to the topic: “orders”.
▌Implement: Send message to topic
We had used the method: admin.messaging().send(…)
for send message to specified
registration token(s) on previous article, [Vue]
Shopcart example with Firebase Cloud Messaging and Functions (2)
We will use the same method but replace the token(s)
to the topic.
exports.sendOrdersMsg =
functions.https.onRequest((request, response) => {
cors(request,
response, () => {
const userName =
request.get("user-name")
const itemsCnt =
request.query.itemscnt;
response.end();
let message = {
webpush: {
notification: {
title: "Orders",
body: `${userName} just bought
${itemsCnt} awesome
product(s), don't miss the best discount!`,
click_action: "https://shopcart-vue.firebaseapp.com",
icon: "dist/firebase-logo.png"
}
},
topic: "orders"
};
return
admin.messaging().send(message)
.then((response) => {
// Response
is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
});
})
This cloud function should be requested whenever a
user have successfully confirmed (saved) the order in the shopping cart page.
For example, add the following function to trigger
the cloud function in Vue.js app.
export default {
name: "shopcart",
//…skip
methods: {
pushOrdersMsg(user, itemsCnt) {
console.log(`Start push
msg with user: ${user}, items: ${itemsCnt}`);
this.axios
.get(
"https://xxxx.cloudfunctions.net/sendOrdersMsg" + "?itemscnt=" + itemsCnt,
{
headers: {
"user-name": user,
}
}
)
.then(result => {
console.log(result);
});
}
};
▋Optional: Use RTDB triggers or Firestore triggers
If we do not want to update the code of client side.
We can use RTDB triggers
or Firestore
triggers to trigger the cloud function instead.
I will take RTDB triggers
for example since my shopcart app uses RTDB :P
Modify the original cloud function: sendOrdersMsg, as following,
exports.sendOrdersMsg = functions.database.ref('/Demo/orders/{pushId}')
.onCreate((snapshot,
context) => {
// Grab the
current value of what was written to the Realtime Database.
const newOrder =
snapshot.val();
console.info("Push id",
context.params.pushId); //You can the param: pushId, by context.params.pushId
const user =
newOrder.customer;
const itemsCnt =
newOrder.items.length;
let message = {
webpush: {
notification: {
title: "Orders",
body: `${user} just bought
${itemsCnt} awesome
product(s), don't miss the best discount!`,
click_action: "https://shopcart-vue.firebaseapp.com",
icon: "dist/firebase-logo.png"
}
},
topic: "orders"
};
return
admin.messaging().send(message)
.then((response) => {
// Response
is a message ID string.
console.log('Successfully
sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
});
Then this cloud functions will be triggered when a
new data is written to /Demo/orders/
▋Demo
I also received the FCM message
from my mobile phone which was logged in as another Google account.
▌Reference
沒有留言:
張貼留言