从 2024 年 6 月 20 日之后就无法使用旧版的 FCM API 了,必须迁移到 V1 版本才能使用,今天介绍国内如何迁移到 V1 版本。
1. 生成服务账号
进入你的 Firebase Messaging 项目 > 项目设置 > 服务账号 > 生成新的私钥
2. 获取 accessToken
我是 node 项目,所以使用 google-auth-library 库
npm install google-auth-library
const { GoogleAuth } = require("google-auth-library");
const SERVICE_ACCOUNT_FILE = process.env.FIREBASE_ADMIN_SDK; // 服务账号JSON路径
const serviceAccount = requrie(SERVICE_ACCOUNT_FILE);
const auth = new GoogleAuth({
credentials: serviceAccount,
scopes: ["https://www.googleapis.com/auth/cloud-platform"], // 直接使用示例授权范围即可
});
async function getAccessToken() {
const accessToken = await auth.getAccessToken();
return accessToken;
}
getAccessToken().then(accessToken => console.log(accessToken));
本地开发的话,国内需要使用代理,google-auth-library 并不提供 proxy 配置,但支持 HTTPS_PROXY 环境代理,Windows 使用以下命令
set HTTPS_PROXY=http://xxx.xxx.xxx.xxx:xxx # 配置代理
set HTTPS_PROXY= # 取消代理
3. 发送通知
与之前的接口相比,URL、授权、请求参数都发生了些许变化
URL 变化
原 URL
https://fcm.googleapis.com/fcm/send // 原URL
V1 新 URL
https://fcm.googleapis.com/v1/projects/{project-name}/messages:send // projects-name为firebase项目名
授权变化
原来使用服务器密钥
Authorization: key=AIzaSyZ-1u...0GBYzPu7Udno5aA
现在使用 accessToken
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
请求参数
原请求参数
{
// 旧请求参数
"to": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", // or "/topics/news"
"registration_ids": ["bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1..."],
"notification": {
"title": "Breaking News",
"body": "New news story available."
},
"data": {
"story_id": "story_12345"
}
}
- 单设备或者主题通知使用 to 参数
- 多设备使用 registration_ids 参数
V1 新请求参数
{ // 新请求参数
"message": {
"token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1..."
"topic": "news",
"notification": {
"title": "Breaking News",
"body": "New news story available."
},
"data": {
"story_id": "story_12345"
}
}
}
- 使用 message 包裹
- 单设备使用 token
- 主题通知使用 topic,但不需要/topics/,只用主题名
- 删除了多设备通知,只能使用主题实现
返回结果:projects/{project_id}/messages/{message_id}格式字符串
{
"name": "projects/myproject-b5ae1/messages/0:1500415314455276%31bd1c9631bd1c96"
}
4. 订阅和退订主题
之前多设备都是用的 registration_ids 参数,但移除了不得不使用主题通知
订阅通知
URL: https://iid.googleapis.com/iid/v1:batchAdd
{
"to": "/topics/{your_topic}",
"registration_tokens": [
"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"..."
]
}
- 授权方式和发送通知一样用 accessToken
- to:订阅主题,/topics/ + 你的主题名
- registration_tokens: 订阅的设备令牌数组,支持多个设备一起订阅
返回结果
{ "results": [{}] }
- results 包含每个令牌的订阅结果
- 如果结果中有 error 字段表示该令牌订阅失败
退订通知
URL: https://iid.googleapis.com/iid/v1:batchRemove
{
"to": "/topics/{your_topic}",
"registration_tokens": [
"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"..."
]
}
- 授权方式和发送通知一样用 accessToken
- to:退订主题,/topics/ + 你的主题名
- registration_tokens: 订阅的设备令牌数组,支持多个设备一起退订
返回结果
{ "results": [{}] }
- results 包含每个令牌的退订结果
- 如果结果中有 error 字段表示该令牌退订失败
5. 其他参数调整
- notification 参数中不支持 icon 和 click_action 参数,需要在相关平台参数中配置,如 webpush 的 notificaition,android 的 notificaition
- webpush 不支持 click_action,改为 fcm_options.link
详细配置参考:https://firebase.google.com/docs/cloud-messaging/migrate-v1?hl=zh-cn
6. 请求封装
我在 Node 项目中使用,所以使用 axios 封装,可参考
// axiosInstance.js
const axios = require("axios");
const { GoogleAuth } = require("google-auth-library");
const { HttpsProxyAgent } = require("https-proxy-agent");
const fs = require("fs");
const dotenv = require("dotenv");
const envFile = process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : ".env";
dotenv.config({ path: envFile });
const httpsAgent = process.env.NODE_ENV
? new HttpsProxyAgent("http://xxx.xxx.xxx.xxx:xxxx")
: null;
const SERVICE_ACCOUNT_FILE = process.env.FIREBASE_ADMIN_SDK;
const serviceAccount = require(SERVICE_ACCOUNT_FILE);
const auth = new GoogleAuth({
credentials: serviceAccount,
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
});
// 获取访问令牌
async function getAccessToken() {
const accessToken = await auth.getAccessToken();
return accessToken;
}
let accessToken = "";
const axiosInstance = axios.create({
timeout: 10000,
headers: {
"Content-Type": "application/json",
access_token_auth: true,
},
httpsAgent,
proxy: false,
});
axiosInstance.interceptors.request.use(async config => {
if (!accessToken) {
accessToken = await getAccessToken();
}
config.headers["Authorization"] = `Bearer ${accessToken}`;
return config;
});
axiosInstance.interceptors.response.use(
async response => {
return [null, response.data];
},
async error => {
const { status = 0, statusText = "" } = error.response;
if (status === 400) {
const { message } = error.response.data.error;
return [{ status, statusText: message }, null];
}
if (status === 401) {
const originConfig = error.config;
accessToken = await getAccessToken();
originConfig.headers["Authorization"] = `Bearer ${accessToken}`;
const result = await axios.request(originConfig);
return [null, result.data];
}
return [{ status, statusText }, null];
}
);
module.exports = axiosInstance;
封装接口调用
const axiosInstance = require("./axiosInstance");
const sendNotification = (data, config) =>
axiosInstance.post(
"https://fcm.googleapis.com/v1/projects/xxxxxxx/messages:send",
data,
config
);
const batchAdd = (data, config) =>
axiosInstance.post(
"https://iid.googleapis.com/iid/v1:batchAdd",
data,
config
);
const batchRemove = (data, config) =>
axiosInstance.post(
"https://iid.googleapis.com/iid/v1:batchRemove",
data,
config
);
module.exports = {
sendNotification,
batchAdd,
batchRemove,
};
接口使用
const { sendNotification, batchAdd, batchRemove } = require("../xx/index");
// 发送通知
const params = {
message: {
token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
notification: {
title,
body: content,
},
data: {
messageId: String(newMessage.id),
},
},
};
const [err, result] = await sendNotification(params);
if (err) {
// do something
}
// 设备订阅
let batchData = {
to: `/topics/{your_topic}`,
registration_tokens: ["xxxxxx"],
};
const [err, result] = await batchAdd(batchData);
if (err) {
// do something
}
// 设备退订
let batchData = {
to: `/topics/{your_topic}`,
registration_tokens: ["xxxxxx"],
};
const [err, result] = await batchRemove(batchData);
if (err) {
// do something
}
注意事项:
- 因为我线上服务部署在香港,所以不需要使用代理,只有本地开发需要,所以 httpsAgent 自己调整
- 如果你是使用 axios,并且需要使用代理,请将你的 proxy 配置设置为 false,否则在你配置 HTTPS_PROXY 后请求 https 时会受到影响
- 如果你需要使用订阅和退订,需要在 headers 中使用 access_token_auth: true 配置