Firebase云函数触发Cloud Messaging推送通知失败排查
我需要在Firestore中allUsers/{userId}/notifications/{notificationId}路径的文档被创建时,通过Cloud Messaging发送推送通知。用户的token以arrayUnion形式存在messagingTokens字段中,且仅当用户的isPushEnabled字段为true时才发送通知。我编写了云函数并成功部署,但触发时出现TypeError。此前已通过setGlobalOptions({maxInstances: 10})和firebase.json配置解决了maxScale错误。
云函数代码:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); const { onDocumentCreated } = require("firebase-functions/v2/firestore"); const { setGlobalOptions } = require("firebase-functions/v2"); admin.initializeApp(); // Set the maximum instances to 10 for all functions setGlobalOptions({ maxInstances: 10 }); const notificationPath = "allUsers/{userId}/notifications/{notificationId}"; exports.sendNotification = onDocumentCreated(notificationPath, async (event) => { functions.logger.log("New notificatin document was created"); const data = event.data.after.data(); const senderUid = data.senderUid; const receiverUid = data.receiverUid; const notificationOption = data.option; functions.logger.log("Retrieved notification fields"); if (notificationOption === "receivedFriendRequest") { functions.logger.log("Is option: receivedFriendRequest"); await onSentFriendRequest(senderUid, receiverUid); } }); async function onSentFriendRequest(ownerId, userId) { // Get the owners details const owner = await admin.firestore().collection("allUsers").doc(ownerId).get(); // Get the users details const user = await admin.firestore().collection("allUsers").doc(userId).get(); const userHasNotificationsEnabled = user.data().isPushEnabled; functions.logger.log("If this does not print, then your function is not being called"); if (!userHasNotificationsEnabled) { functions.logger.log("User does not have push enabled"); return; } functions.logger.log("User has push enabled"); // Listing all tokens as an array. tokens = user.data().messagingTokens; // Send message to all tokens const response = await admin.messaging().sendEachForMulticast({ tokens: tokens, notification: { title: "Neue Freundschaftsanfrage", body: `${owner.data().username} möchte mit dir befreundet sein.`, }, data: { ownerId: ownerId, userId: userId, notificationOption: "receivedFriendRequest", }, }); // For each message check if there was an error. const tokensToRemove = []; response.results.forEach((result, index) => { const error = result.error; if (error) { functions.logger.error( 'Failure sending notification to', tokens[index], error ); // Cleanup the tokens who are not registered anymore. if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove()); } } }); return Promise.all(tokensToRemove); }
firebase.json配置:
"frameworksBackend": { "region": "us-central1", "maxInstances": 10 }
触发时的TypeError,大概率由以下几个问题导致:
1. 未定义的tokensSnapshot变量
在清理无效token的代码块中,你使用了tokensSnapshot.ref.child(tokens[index]).remove(),但tokensSnapshot从未被声明或赋值,这会直接抛出TypeError。同时child()是Realtime Database的语法,不适用于Firestore。
修复方式:使用用户文档引用,通过arrayRemove移除无效token:
// 替换原清理token的代码 if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { tokensToRemove.push( admin.firestore().collection("allUsers").doc(userId).update({ messagingTokens: admin.firestore.FieldValue.arrayRemove(tokens[index]) }) ); }
2. 未声明tokens变量
tokens = user.data().messagingTokens;未使用let/const声明,会导致tokens成为全局变量,严格模式下会报错,还可能引发意外行为。
修复方式:添加const声明:
const tokens = user.data().messagingTokens;
3. 未处理文档不存在的情况
如果ownerId或userId对应的Firestore文档不存在,user.data()或owner.data()会返回null,后续访问user.data().isPushEnabled或owner.data().username会抛出TypeError。
修复方式:增加文档存在性检查:
async function onSentFriendRequest(ownerId, userId) { const ownerDoc = await admin.firestore().collection("allUsers").doc(ownerId).get(); if (!ownerDoc.exists) { functions.logger.error("Sender document does not exist:", ownerId); return; } const userDoc = await admin.firestore().collection("allUsers").doc(userId).get(); if (!userDoc.exists) { functions.logger.error("Receiver document does not exist:", userId); return; } const userData = userDoc.data(); const userHasNotificationsEnabled = userData.isPushEnabled; if (!userHasNotificationsEnabled) { functions.logger.log("User does not have push enabled"); return; } const tokens = userData.messagingTokens; // 确保tokens是有效数组,避免sendEachForMulticast报错 if (!Array.isArray(tokens) || tokens.length === 0) { functions.logger.log("No valid tokens to send notification"); return; } const ownerData = ownerDoc.data(); const response = await admin.messaging().sendEachForMulticast({ tokens: tokens, notification: { title: "Neue Freundschaftsanfrage", body: `${ownerData.username} möchte mit dir befreundet sein.`, }, data: { ownerId: ownerId, userId: userId, notificationOption: "receivedFriendRequest", }, }); const tokensToRemove = []; response.results.forEach((result, index) => { const error = result.error; if (error) { functions.logger.error( 'Failure sending notification to', tokens[index], error ); if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { tokensToRemove.push( admin.firestore().collection("allUsers").doc(userId).update({ messagingTokens: admin.firestore.FieldValue.arrayRemove(tokens[index]) }) ); } } }); return Promise.all(tokensToRemove); }
4. 未检查通知文档的字段是否存在
主函数中直接访问data.senderUid、data.receiverUid等字段,如果这些字段缺失,会抛出TypeError。
修复方式:增加字段存在性检查:
exports.sendNotification = onDocumentCreated(notificationPath, async (event) => { functions.logger.log("New notification document was created"); const data = event.data?.after?.data(); if (!data) { functions.logger.error("Notification document has no data"); return; } const senderUid = data.senderUid; const receiverUid = data.receiverUid; const notificationOption = data.option; if (!senderUid || !receiverUid || !notificationOption) { functions.logger.error("Missing required fields in notification document"); return; } functions.logger.log("Retrieved notification fields"); if (notificationOption === "receivedFriendRequest") { functions.logger.log("Is option: receivedFriendRequest"); await onSentFriendRequest(senderUid, receiverUid); } });
核心TypeError来源是未定义的tokensSnapshot变量,以及未处理文档不存在、字段缺失的边界情况。按照上述方式修复代码后,即可解决触发时的报错问题。
内容的提问来源于stack exchange,提问作者Chris

