#!/usr/bin/env node const firebase_app=require("firebase-admin/app") const firebase_messaging=require("firebase-admin/messaging") const util=require("util"); // Potential TODO's: // - Use collapseKey to collect notifications for particular rooms // - But note that you cannot have more than 4 collapseKeys at a time; // correctly managing this requires synchronisation between app and server // of exactly what keys have disappeared in the meantime firebase_app.initializeApp({ credential:firebase_app.cert(require("./firebaseServiceAccountKey.json")), databaseURL:"https://tomsg-83196.firebaseio.com", }); const fieldConverters=new Map([ ["token",(b)=>b.toString()], ["user",(b)=>b.toString()], ["to_user",(b)=>b.toString()], ["room",(b)=>b.toString()], ["message",(b)=>b.toString()], ["timestamp",(b)=>b.toString()], // Nanosecond timestamps are too close to 52-bit range ["online",(b)=>parseInt(b.toString(),10)], ]); function typeConvertFields(fields){ for(const key of fields.keys()){ const conv=fieldConverters.get(key); if(conv!=undefined)fields.set(key,conv(fields.get(key))); } } function readFields(buffer){ const fields=new Map(); if(buffer.length==0)return fields; let cursor=0; while(true){ let idx=buffer.indexOf(32,cursor); if(idx==-1){ console.error("ERR: Incomplete field in input line"); return null; } const key=String(buffer.slice(cursor,idx)); cursor=idx+1; idx=buffer.indexOf(32,cursor); if(idx==-1){ console.error("ERR: Incomplete field in input line"); return null; } const arglen=parseInt(String(buffer.slice(cursor,idx)),10); if(arglen<0||arglen==null||isNaN(arglen)){ console.error("ERR: Invalid length in input line"); return null; } cursor=idx+1; if(cursor+arglen>buffer.length){ console.error("ERR: Length larger than remaining line in input line"); return null; } const arg=buffer.slice(cursor,cursor+arglen); cursor+=arglen; fields.set(key,arg); if(cursor==buffer.length)break; if(buffer[cursor]!=32){ console.error("ERR: No space after argument in input line"); return null; } cursor++; } return fields; } function processMessage(type,fields){ switch(type){ case "message": const user=fields.get("user"); const to_user=fields.get("to_user"); const room=fields.get("room"); const message=fields.get("message"); const token=fields.get("token"); const payload={ notification: { title: room + " (" + user + ")", body: message, }, data: { user: user, room: room, message: message, } }; const options={ collapseKey: "tomsg-collapseKey", }; firebase_messaging.getMessaging().sendToDevice(token,payload,options) .then((response)=>{ const result=response.results[0]; const realToken=result.canonicalRegistrationToken; if(result.error){ console.error("JS: Send error:",result.error.errorInfo); console.log("delete_token "+to_user+" "+token); } else if(realToken&&realToken!=token){ console.log("delete_token "+to_user+" "+token); console.log("add_token "+to_user+" "+realToken); } }) .catch((err)=>{ console.error("JS: Early send error:",err); }); break; default: console.error("JS: Unknown type '"+type+"'"); } } function handleInputLine(buffer){ let idx=buffer.indexOf(32); if(idx==-1){ console.error("ERR: No space in input line"); return; } const type=String(buffer.slice(0,idx)); const fields=readFields(buffer.slice(idx+1)); if(fields==null)return; typeConvertFields(fields); processMessage(type,fields); } { let buffer=null; process.stdin.on("data",(data)=>{ const prevlen=buffer==null?0:buffer.length; if(buffer)buffer=Buffer.concat([buffer,data]); else buffer=data; let cursor=0; let lfidx=buffer.indexOf(10,prevlen); while(lfidx!=-1){ handleInputLine(buffer.slice(cursor,lfidx)); cursor=lfidx+1; lfidx=buffer.indexOf(10,cursor); } if(cursor>=buffer.length)buffer=null; else buffer=Buffer.from(buffer.slice(cursor)); }); } console.error("Firebase js plugin loaded!");