You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:24:55