Edit File: fcm.js
var https = require('https'); var HttpsProxyAgent = require('https-proxy-agent'); var retry = require('retry'); var firebaseadmin = require("firebase-admin"); const TopicRequest = require('../lib/topic_request'); const TopicOptions = require('../lib/topic_options'); const TopicData = require('../lib/topic_data'); function FCM(accountKey, proxy_url=null, name=null) { var admin = null if(!proxy_url) { proxy_url = process.env.http_proxy || null; } if(!accountKey) { throw Error('You must provide the APIKEY for your firebase application.'); } else if(typeof accountKey == 'string') { //API KEY PASSED string, legacy use this.serverKey = accountKey; this.fcmOptions = { host: 'fcm.googleapis.com', port: 443, path: '/fcm/send', method: 'POST', headers: {} }; this.send = function (payload, CB) { var self = this; if (!CB) { throw Error('you must provide a callback function(err,result)'); //just in case } else { var operation = retry.operation(); var mpayload = JSON.stringify(payload); var mFcmOptions = Object.assign({}, self.fcmOptions); //copying the fcmOptions object to avoid problems in parallel calls if(proxy_url) { // HTTP/HTTPS proxy to connect to var proxy = proxy_url; var agent = new HttpsProxyAgent(proxy); mFcmOptions.agent = agent; } operation.attempt(function (currentAttempt) { var headers = { 'Host': mFcmOptions.host, 'Authorization': 'key=' + self.serverKey, 'Content-Type': 'application/json' //'Content-Length': mpayload.length //removed this line for chunk-encoded transfer compatibility (UTF-8 and all non-ANSI codification) }; mFcmOptions.headers = headers; if (self.keepAlive) headers.Connection = 'keep-alive'; var request = https.request(mFcmOptions, function (res) { var data = ''; if (res.statusCode == 503) { // If the server is temporary unavailable, the FCM spec requires that we implement exponential backoff // and respect any Retry-After header if (res.headers['retry-after']) { var retrySeconds = res.headers['retry-after'] * 1; // force number if (isNaN(retrySeconds)) { // The Retry-After header is a HTTP-date, try to parse it retrySeconds = new Date(res.headers['retry-after']).getTime() - new Date().getTime(); } if (!isNaN(retrySeconds) && retrySeconds > 0) { operation._timeouts['minTimeout'] = retrySeconds; } } if (!operation.retry('TemporaryUnavailable')) { CB(operation.mainError(), null); } // Ignore all subsequent events for this request return; } function respond() { var error = null, id = null; //Handle the various responses if (data.indexOf('\"multicast_id\":') > -1)//multicast_id success { var anyFail = ((JSON.parse(data)).failure > 0); if (anyFail) { error = data.substring(0).trim(); } var anySuccess = ((JSON.parse(data)).success > 0); if (anySuccess) { id = data.substring(0).trim(); } } else if (data.indexOf('\"message_id\":') > -1) { //topic messages success id = data; } else if (data.indexOf('\"error\":') > -1) { //topic messages error error = data; } else if (data.indexOf('TopicsMessageRateExceeded') > -1) { error = 'TopicsMessageRateExceededError' } else if (data.indexOf('Unauthorized') > -1) { error = 'NotAuthorizedError' } else { error = 'InvalidServerResponse'; } // Only retry if error is QuotaExceeded or DeviceQuotaExceeded if (operation.retry(currentAttempt <= 3 && ['QuotaExceeded', 'DeviceQuotaExceeded', 'InvalidServerResponse'].indexOf(error) >= 0 ? error : null)) { return; } // Success, return message id (without id=) CB(error, id); } res.on('data', function (chunk) { data += chunk; }); res.on('end', respond); }); request.on('error', function (error) { CB(error, null); }); request.end(mpayload); }); } } // Subscribe devices to topic // If topic does not exist, a new one is created this.subscribeToTopic = (deviceTokens, topicName, CB) => { const options = TopicOptions('iid.googleapis.com', '/iid/v1:batchAdd', 'POST', this.serverKey.slice(0)); const subscriptionData = TopicData(topicName, deviceTokens); TopicRequest(options, subscriptionData, (err, res) => { CB(err, res); }); } // Unsubscribe device to topic this.unsubscribeToTopic = (deviceTokens, topicName, CB) => { const options = TopicOptions('iid.googleapis.com', '/iid/v1:batchRemove', 'POST', this.serverKey.slice(0)); const unsubscriptionData = TopicData(topicName, deviceTokens); TopicRequest(options, unsubscriptionData, (err, res) => { CB(err, res); }); } } else{ //accountkey object passed, new SDK 'de-promisefy' use const config = 'private_key' in accountKey || 'privateKey' in accountKey ? {credential: firebaseadmin.credential.cert(accountKey)} : accountKey; if(name) { admin = firebaseadmin.initializeApp(config, name); } else { admin = firebaseadmin.initializeApp(config); } this.send = function(payload, _callback){ if (!_callback) { throw Error('You must provide a callback function(err,result)') } else{ if(!payload) _callback(new Error('You must provide a payload object')) else{ if(payload.to) { if (typeof payload.to == 'string') { var to = payload.to delete payload.to if (to.startsWith('/topics/')) { var topic = to.slice(8)//anything after '/topics/' admin.messaging().sendToTopic(topic, payload) .then(function(response){_callback(null, response)}) .catch(function (err) {_callback(err)}) } else{ admin.messaging().sendToDevice(to,payload) .then(function (response) {_callback(null,response)}) .catch(function (error) {_callback(error)}) } } else{ var err = new Error('Invalid "to" field in payload'); _callback(err) } } else if(payload.registration_ids){ var regIds = payload.registration_ids; delete payload.registration_ids; if(regIds instanceof Array && typeof regIds[0] == 'string') { admin.messaging().sendToDevice(regIds, payload) .then(function (response) {_callback(null,response)}) .catch(function (error) {_callback(error)}) } else{ var err = new Error('Invalid "registration_ids" field in payload'); _callback(err) } } else{ var err = new Error('Invalid payload object'); _callback(err) } } } } } } module.exports = FCM;
Back to File Manager