首页 > 解决方案 > 如何收集用户 ID + 检索相应的令牌 + 通过 Firebase Cloud Function (JS) 发送推送通知

问题描述

问题:

我一直无法使用 Firebase (Google) Cloud Functions 来收集和利用设备令牌来实现云消息传递功能。

语境:

我是一名自学成才的 android-Java 开发人员,没有 JavaScript 经验。尽管如此,我相信我的代码应该可以工作,但不确定问题出在哪里。据我了解,这可能是以下三件事之一:

  1. 不知何故,我的 Firebase 实时数据库引用被错误地调用,我没有按预期检索数据。

  2. 在继续之前,我可能需要使用 Promises 来等待所有调用,但是我真的不明白如何将它合并到我拥有的代码中。

  3. 我可能错误地使用了多个返回语句(我也很模糊)。

我在 Firebase 实时数据库控制台上的错误消息如下:

@firebase/database:FIREBASE 警告:用户回调抛出异常。错误:提供给 sendToDevice() 的注册令牌必须是非空字符串或非空数组。 在 FirebaseMessagingError.FirebaseError [作为构造函数] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28) 在 FirebaseMessagingError.PrefixedFirebaseError [作为构造函数] (/srv/node_modules/firebase-admin/lib/ utils/error.js:88:28) 在新的 FirebaseMessagingError (/srv/node_modules/firebase-admin/lib/utils/error.js:254:16) 在 Messaging.validateRegistrationTokensType (/srv/node_modules/firebase-admin/lib /messaging/messaging.js:729:19) 在 Messaging.sendToDevice (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:328:14) 在 admin.database.ref.once.snapshot (/srv /index.js:84:12) 一次回调 (/srv/node_modules/@firebase/database/dist/index.node.cjs.js:4933:51) at /srv/node_modules/@firebase/database/dist/index .node.cjs.js:4549:22 在 EventList 的 exceptionGuard (/srv/node_modules/@firebase/database/dist/index.node.cjs.js:698:9)。提高(/srv/node_modules/@firebase/database/dist/index.node.cjs.js:9684:17)

以上表明我根本没有检索数据,也没有在调用 return 时检索数据。我的 JavaScript 函数代码是:

'use strict'; 

const admin = require('firebase-admin');
admin.initializeApp();

exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushID}').onCreate((snapshot, context) => {

    const valueObject = snapshot.after.val(); 
   
        return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
            
                var index = 0;    
                  var totalkeys = statusSnapshot.numChildren();
                var msgIDs = [];
        
                statusSnapshot.forEach(msg=>{

                    msgIDs.push(msg.key.toString());
                      
                        if(index === totalkeys - 1){
          
                            const payload = {
                                notification : {
                                        title: valueObject.userName,
                                              body: valueObject.message,
                                              sound: "default"
                                }
                              }
                    
                              sendNotificationPayload(valueObject.uid, payload);
                        }
        
        
                    index++;
        
        
                  });

        });

});

function sendNotificationPayload(uid, payload){

    admin.database()
    .ref(`/User Token Data/${uid}`)
    .once('value', snapshot=> {
        
        var tokens = [];

        //if(!snapshot.exists())return;

        snapshot.forEach(item =>{
            tokens.push(item.val())
        });


        admin.messaging()
        .sendToDevice(tokens, payload)
        .then(res => {
            return console.log('Notification sent')
        })
        .catch(err => {
            return console.log('Error in sending notification = '+err)
        });

    });

}

这段代码的灵感主要来自于此处的另一个 Stack Overflow 问题中一个工作示例。我已经通过手动将设备令牌复制到我的函数中成功测试了向单个设备发送通知,因此该函数确实运行完成。我的 Java 代码似乎与问题无关,所以我没有添加它(如果您希望添加它以获取更多上下文,请在评论中询问)。

我试过的:

我已经尝试在我的代码中实现承诺,但我认为我没有正确地做到这一点。我对此的主要参考是here。我还查看了与该主题相关的所有内容的文档,但是我对 JS 的了解不足以将准系统示例真正应用到我的代码中。

我的 Firebase 实时数据库节点:

#1:循环聊天成员以收集用户 ID:

"Chat Basics" : {
"1607801501690_TQY41wIfArhHDxEisyupZxwyHya2" : {
  "Chat Users" : {
    "JXrclZuu1aOwEpCe6KW8vSDea9h2" : true,
    "TQY41wIfArhHDxEisyupZxwyHya2" : true
  },

#2:从收集的 ID 中收集用户令牌(忽略令牌匹配):

"User Token Data" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi",
"TQY41wIfArhHDxEisyupZxwyHya2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi"

}

结论:

具体的例子将不胜感激,特别是因为我现在正在努力。感谢您的时间和帮助!

更新:

经过更多测试,看起来问题肯定是由于我对两个方面的承诺缺乏了解。首先,在调用最终返回之前只收集一个用户。其次,在第二个 forEach() 循环可以将快照数据存储到数组之前调用最终返回。

那么对于此代码,我该如何修改(或重建)它,以便在继续从所有键中检索令牌数据之前收集所有键 - 最终在返回通知之前?

标签: javascriptnode.jsfirebase-realtime-databasegoogle-cloud-functionsfirebase-cloud-messaging

解决方案


就像我发布的每个问题一样,我设法在几个小时后(暂时)弄清楚如何去做。下面是一个完整示例,说明如何根据发送到给定聊天的消息(尽管它尚未排除发件人)向聊天用户发送通知。操作顺序如下:

  1. 保存用户消息并触发事件。消息包含的相关数据是:

    • 用户名、聊天密钥、消息

    这些被检索,分别以(用户名+消息)作为通知的(标题+正文),聊天键用于用户ID参考。

  2. 循环聊天用户键 + 收集。

  3. 遍历聊天用户键数组以收集设备令牌数组。

  4. 完成后发送通知。

编码:

//Use firebase functions:log to see log
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushId}').onWrite((change, context) => {

    const valueObject = change.after.val(); 

        return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
                
        var index = 0;
        var totalkeys = statusSnapshot.numChildren();
                var msgIDs = [];
        
                statusSnapshot.forEach(msg=>{

                    msgIDs.push(msg.key.toString());
                      
                        if(index === totalkeys - 1){
          
                                const payload = {
                                    notification : {
                                            title: valueObject.userName,
                                body: valueObject.message,
                                sound: "default"
                                    }
                                }

                
                let promises = [];  
                var tokens = [];    
            
                for(let i=0; i < msgIDs.length; i++){
                
                    let userId = msgIDs[i];
                    
                                    let promise = admin.database().ref(`/User Token Data/${userId}`).once('value', snapshot=> {

                        tokens.push(snapshot.val());

                        })
                    promises.push(promise);
                }

                return Promise.all(promises).then(() => {
                    return admin.messaging().sendToDevice(tokens, payload);
                });

                        }
        
                        index++;

            return false;

            });

        });

});


推荐阅读