UNPKG

flamebase-database-node

Version:
496 lines (447 loc) 17.9 kB
var JsonDB = require('node-json-db'); var FCM = require('fcm-push'); var diff = require('rus-diff').diff; var log4js = require('log4js'); var SN = require('sync-node'); var sha1 = require('sha1'); const TAG = "Flamebase Database"; var logger = log4js.getLogger(TAG); logger.level = 'all'; JSON.stringifyAligned = require('json-align'); var ACTION_SIMPLE_UPDATE = "simple_update"; var ACTION_SLICE_UPDATE = "slice_update"; var ACTION_NO_UPDATE = "no_update"; function FlamebaseDatabase(database, path) { // object reference var object = this; // debug this.debugVal = false; // os this.OS = {}; this.OS.ANDROID = "android"; this.OS.IOS = "ios"; var lengthMargin = 400; // supposed length of additional info to send this.lengthLimit = {}; this.lengthLimit.ANDROID = (4096 - lengthMargin); this.lengthLimit.IOS = (2048 - lengthMargin); this.queue = SN.createQueue(); // database this.db = new JsonDB(database, true, true); // db reference this.ref = {}; // last db string reference this.lastStringReference = JSON.stringify({}); this.pushConfig = null; this.fcm = null; /** * sync from database */ this.syncFromDatabase = function() { try { object.ref = object.db.getData(path); this.lastStringReference = JSON.stringify(object.ref); if (this.debugVal) { logger.debug("ref: " + path); logger.debug("len: " + this.lastStringReference.length); } } catch(e) { console.log("####################### error: " + e); console.log("####################### generating tree "); this.prepareUnknownPath(); this.lastStringReference = JSON.stringify(object.ref); if (this.debugVal) { logger.debug("ref: " + path); logger.debug("len: " + this.lastStringReference.length); } } }; this.prepareUnknownPath = function() { var paths = path.split("/"); var currentObject = object.ref; for (p in paths) { var pCheck = paths[p]; currentObject[pCheck] = {}; currentObject = currentObject[pCheck]; } object.ref = currentObject; object.db.push(path, object.ref); }; /** * sync to database */ this.syncToDatabase = function(restart, callback) { if (restart !== undefined && restart) { this.lastStringReference = JSON.stringify({}); if (this.debugVal) { logger.debug("cleaning last reference on " + path); } } object.db.push(path, object.ref); object.syncNotifications(callback); }; /** * config for real time synchronization * @param config */ this.setSyncConfig = function(config) { this.pushConfig = config; // push notifications if (this.fcm === null) { this.fcm = (this.pushConfig.APIKey() === null || this.pushConfig.APIKey() === 0 ? null : new FCM(this.pushConfig.APIKey())); } this.lastStringReference = JSON.stringify({}); }; /** * debug logs * @param callback */ this.debug = function(value) { this.debugVal = value; }; /** * fired if fcm is defined and DB changes or reloads */ this.syncNotifications = function(callback) { if (this.pushConfig !== null) { try { this.sendDetailPushMessage(callback); } catch (e) { logger.error("error: " + e); } } }; /** * */ this.sendDetailPushMessage = function(callback) { if (this.fcm === null) { logger.error("# no fcm detected, set an API key") return; } var ios_tokens = []; var android_tokens = []; var id = this.pushConfig.referenceId(); var notification = this.pushConfig.notification(); var devices = this.pushConfig.devices(); for (var t = 0; t < devices.length; t++) { var device = devices[t]; if (device.os.indexOf(this.OS.IOS) !== -1) { ios_tokens.push(device.token); } else { android_tokens.push(device.token); } } this.lastStringReference = JSON.stringify(this.ref); if (android_tokens.length > 0) { var data_android = this.getPartsFor(this.OS.ANDROID, JSON.parse(this.lastStringReference), this.ref); if (object.debugVal) { logger.debug("android_tokens_size: " + android_tokens.length); logger.debug("data_android_size: " + data_android.parts.length); } if (data_android.parts.length === 1) { var data = {}; data.id = id; data.tag = this.pushConfig.tag(); data.reference = data_android.parts[0]; data.action = ACTION_SIMPLE_UPDATE; data.size = data_android.parts.length; data.index = 0; var send = {}; send.data = data; send.tokens = android_tokens; send.notification = notification; if (ios_tokens.length === 0) { this.sendPushMessage(send, callback); } else { this.sendPushMessage(send); } } else if (data_android.parts.length > 1) { for (var i = 0; i < data_android.parts.length; i++) { var dat = {}; dat.id = id; dat.tag = this.pushConfig.tag(); dat.reference = data_android.parts[i]; dat.action = ACTION_SLICE_UPDATE; dat.index = i; dat.size = data_android.parts.length; var sen = {}; sen.data = dat; sen.tokens = android_tokens; sen.notification = notification; if (ios_tokens.length === 0 && i === data_android.parts.length - 1) { this.sendPushMessage(sen, callback); } else { this.sendPushMessage(sen); } } } else { var data = {}; data.id = id; data.tag = this.pushConfig.tag(); data.action = ACTION_NO_UPDATE; var send = {}; send.data = data; send.tokens = android_tokens; send.notification = notification; if (ios_tokens.length === 0) { this.sendPushMessage(send, callback); } else { this.sendPushMessage(send); } } } if (ios_tokens.length > 0) { var data_ios = this.getPartsFor(this.OS.IOS, JSON.parse(this.lastStringReference), this.ref); if (object.debugVal) { logger.debug("ios_tokens_size: " + ios_tokens.length); logger.debug("data_ios_size: " + data_ios.parts.length); } if (data_ios.parts.length === 1) { var da = {}; da.id = id; da.tag = this.pushConfig.tag(); da.reference = data_ios.parts[0]; da.action = ACTION_SIMPLE_UPDATE; da.size = data_ios.parts.length; da.index = 0; var se = {}; se.data = da; se.tokens = ios_tokens; se.notification = notification; this.sendPushMessage(se, callback); } else if (data_ios.parts.length > 1) { for (var i = 0; i < data_ios.parts.length; i++) { var d = {}; d.id = id; d.tag = this.pushConfig.tag(); d.reference = data_ios.parts[i]; d.action = ACTION_SLICE_UPDATE; d.index = i; d.size = data_ios.parts.length; var s = {}; s.data = d; s.tokens = ios_tokens; s.notification = notification; if (i === data_ios.parts.length - 1) { this.sendPushMessage(s, callback); } else { this.sendPushMessage(s); } } } else { var data = {}; data.id = id; data.tag = this.pushConfig.tag(); data.action = ACTION_NO_UPDATE; var s = {}; s.data = data; s.tokens = ios_tokens; s.notification = notification; this.sendPushMessage(s, callback); } } }; this.sendDifferencesForClient = function(before, device, callback) { if (this.fcm === null) { logger.error("# no fcm detected, set an API key") return; } var ios_tokens = []; var android_tokens = []; var id = this.pushConfig.referenceId(); var notification = this.pushConfig.notification(); if (device.os.indexOf(this.OS.IOS) !== -1) { ios_tokens.push(device.token); } else { android_tokens.push(device.token); } if (android_tokens.length > 0) { var data_android = this.getPartsFor(this.OS.ANDROID, JSON.parse(before), this.ref); if (object.debugVal) { logger.debug("android_tokens_size: " + android_tokens.length); logger.debug("data_android_size: " + data_android.parts.length); } if (data_android.parts.length === 1) { var data = {}; data.id = id; data.tag = this.pushConfig.tag(); data.reference = data_android.parts[0]; data.action = ACTION_SIMPLE_UPDATE; data.size = data_android.parts.length; data.index = 0; var send = {}; send.data = data; send.tokens = android_tokens; send.notification = notification; if (ios_tokens.length === 0) { this.sendPushMessage(send, callback); } else { this.sendPushMessage(send); } } else if (data_android.parts.length > 1) { for (var i = 0; i < data_android.parts.length; i++) { var dat = {}; dat.id = id; dat.tag = this.pushConfig.tag(); dat.reference = data_android.parts[i]; dat.action = ACTION_SLICE_UPDATE; dat.index = i; dat.size = data_android.parts.length; var sen = {}; sen.data = dat; sen.tokens = android_tokens; sen.notification = notification; if (ios_tokens.length === 0 && i === data_android.parts.length - 1) { this.sendPushMessage(sen, callback); } else { this.sendPushMessage(sen); } } } else { var data = {}; data.id = id; data.tag = this.pushConfig.tag(); data.action = ACTION_NO_UPDATE; var send = {}; send.data = data; send.tokens = android_tokens; send.notification = notification; if (ios_tokens.length === 0) { this.sendPushMessage(send, callback); } else { this.sendPushMessage(send); } } } if (ios_tokens.length > 0) { var data_ios = this.getPartsFor(this.OS.IOS, JSON.parse(before), this.ref); if (object.debugVal) { logger.debug("ios_tokens_size: " + ios_tokens.length); logger.debug("data_ios_size: " + data_ios.parts.length); } if (data_ios.parts.length === 1) { var da = {}; da.id = id; da.tag = this.pushConfig.tag(); da.reference = data_ios.parts[0]; da.action = ACTION_SIMPLE_UPDATE; da.size = data_ios.parts.length; da.index = 0; var se = {}; se.data = da; se.tokens = ios_tokens; se.notification = notification; this.sendPushMessage(se, callback); } else if (data_ios.parts.length > 1) { for (var i = 0; i < data_ios.parts.length; i++) { var d = {}; d.id = id; d.tag = this.pushConfig.tag(); d.reference = data_ios.parts[i]; d.action = ACTION_SLICE_UPDATE; d.index = i; d.size = data_ios.parts.length; var s = {}; s.data = d; s.tokens = ios_tokens; s.notification = notification; if (i === data_ios.parts.length - 1) { this.sendPushMessage(s, callback); } else { this.sendPushMessage(s); } } } else { var data = {}; data.id = id; data.tag = this.pushConfig.tag(); data.action = ACTION_NO_UPDATE; var send = {}; send.data = data; send.tokens = ios_tokens; send.notification = notification; this.sendPushMessage(send, callback); } } this.lastStringReference = JSON.stringify(this.ref); }; this.sendPushMessage = function(send, callback) { this.queue.pushJob(function() { return new Promise(function (resolve, reject) { var message = { to: send.tokens, // required fill with device token or topics data: send.data, notification: send.notification }; if (object.debugVal) { logger.debug("Sending to: " + JSON.stringifyAligned(message.registration_ids)); } object.fcm.send(message) .then(function (response) { if (object.debugVal) { logger.debug("Successfully sent with response: " + JSON.stringifyAligned(JSON.parse(response))); } if (callback != undefined) { callback(); } resolve(); }) .catch(function (err) { logger.error("api key: " + object.pushConfig.APIKey()); logger.error("message: " + JSON.stringifyAligned(message)); logger.error("error: " + JSON.stringifyAligned(err)); resolve(); }) }); }); }; this.getPartsFor = function(os, before, after) { var notification = this.pushConfig.notification(); var notificationLength = JSON.stringify(notification).length; //var differences = JSON.stringify(diff(JSON.parse(this.lastStringReference), this.ref)); var differences = JSON.stringify(diff(before, after)); var partsToSend = []; if (this.debugVal) { logger.debug("diff: " + differences); } if (differences === "false") { var currentStringAfter = JSON.stringify(after); var currentStringBefore = JSON.stringify(before); if (currentStringBefore.length !== currentStringAfter.length) { logger.error("something went wrong; sha1 diff: " + currentStringBefore.length + " - " + currentStringAfter.length); } if (this.debugVal) { logger.debug("no differences"); } } else { differences = this.string2Hex(differences); var limit = os.indexOf(this.OS.IOS) !== -1 ? this.lengthLimit.IOS - notificationLength : this.lengthLimit.ANDROID - notificationLength; if (differences.length > limit) { var index = -1; var pendingChars = differences.length; while (pendingChars > 0) { index++; var part = differences.slice(index * limit, (pendingChars < limit ? index * limit + pendingChars : (index + 1) * limit)); pendingChars = pendingChars - part.length; partsToSend.push(part); } } else { partsToSend.push(differences); } } var result = {}; result.parts = partsToSend; return result; }; this.exist = function() { return !(this.ref === null || this.ref === undefined) }; this.string2Hex = function (tmp) { var str = ''; for (var i = 0; i < tmp.length; i++) { str += tmp[i].charCodeAt(0).toString(16); } return str; }; } module.exports = FlamebaseDatabase;