UNPKG

built.io

Version:

SDK for Built.io Backend

1,536 lines (1,379 loc) 67.3 kB
/*TO DO == IMP == Move socket.once from attachlisteners to enableRealtime function and uncomment onConnect in reconnect callback When two apps are made with the same api key, realtime stops working. Off method does not support broadcast. For built_io_application_user, socket.on is invoked twice from the server side. When a listner is attached on Class.Object we should cache objects so that we don't have to fetch object from Built.io Backend getPresence method should accept a fetch args, which will get presence form server. Else, it would return _presence, if available. */ var R = require('ramda'); var io = require('socket.io-client'); var utility = require('./utilities/utility'); var instanceMethodBuilder = require('./utilities/instanceMethodBuilder')(); var Diff = require('./utilities/diff'); var when = require('when'); var Built = require('./built'); var constants = require('./constants'); var prefix = 'notifications' // generics var generiClasscRegex = /^notifications\.class$/ var generiObjectcRegex = /^notifications\.object$/ var generiUploadcRegex = /^notifications\.upload$/ var genericExtensionRegex = /^notifications\.extensions$/ var genericObjectRegex = /^notifications\.(.*)\.object$/ // specifics var uploadRegex = /^notifications\.upload\.(.*)$/ var classRegex = /^notifications\.(.*)$/ var objectRegex = /^notifications\.(.*)\.(.*)$/ var extensionsRegex = /^notifications\.extensions\.(.*)$/ /* The self invoking method given below would register real time plugin. */ pluginsHelper.registerPlugin("realtime",{ onAppInstance: function(app) { // Hold the actual Authtoken function defination var actualSetAccessTokenFn = app.setAccessToken app.onRealtimeConnect = onRealtimeConnect; app.enableRealtime = enableRealtime; app.disableRealtime = disableRealtime; app.isRealtimeEnabled = isRealtimeEnabled; app.onRealtimeError = onRealtimeError; app.authorizeSocket = authorization; app.on = setupAppListeners; app.off = removeAppListeners; app.setAccessToken = function(access_token){ var currentApp = this // Authorizes the socket connection var authorizedApp = actualSetAccessTokenFn(access_token, currentApp) if (currentApp.isRealtimeEnabled && currentApp.isRealtimeEnabled()) { if(currentApp.isSocketConnected()) authorization(authorizedApp) // Socket isnt yet connected so we register a authorization call against it. Which will get executed once the socket is connected authorizedApp.getMetaMap()['authorization'] = authorization; } return authorizedApp } app.addMetaMapp = function(){ return addSocketDataInAppOptions('metaMap',{},this); }; app.addAdhocOpterations = function(){ return addSocketDataInAppOptions('adhocOperations',[],this); }; app.setAuthStarted = function(value){ return addSocketDataInAppOptions('authStarted',value,this); }; app.setSocket = function(value){ return addSocketDataInAppOptions('socket',value,this); }; app.getSocket = function(value) { if(this.getOptions().socketData){ return this.options.socketData.socket }else{ return null; } } app.isSocketConnected = function() { // 2 time '.socket' as socket object itself has a socket property if(this.isRealtimeEnabled && this.isRealtimeEnabled()){ return this.getOptions().socketData.socket.connected //changed }else{ return null; } } app.getMetaMap = function(){ if(this.isRealtimeEnabled && this.isRealtimeEnabled()){ return this.getOptions().socketData.metaMap; }else{ return null; } }; app.getAdhocOperations = function(){ if(this.isRealtimeEnabled && this.isRealtimeEnabled()){ return this.getOptions().socketData.adhocOperations; }else{ return null; } } app.isSocketAuthStarted = function(){ if(this.isRealtimeEnabled && this.isRealtimeEnabled()){ return this.getOptions().socketData.authStarted; }else{ return null; } } app.getURLForRealtime = function(){ return this.getOption('rtProtocol') + '://' + this.getOption('rtHost') + ':' + this.getOption('rtPort') + '/' + this.getApiKey(); } }, onObjectInstance: function(object) { object.on = setupInstListener('object'); object.off = utility.wrapper(removeInstListener('object'),[null,null]); object.broadcast = broadcastInstMessage('object'); }, onObjectCons : function(cls){ var channel = "notifications."+cls.uid+".object" cls.Object.on = setupConsListener(cls.app,channel); cls.Object.off = utility.wrapper(removeConsListener(cls.app,channel),[null,null]); cls.Object.broadcast = broadcastMessageForCons(cls.app,channel); }, onClassCons : function(app){ var channel = "notifications.class"; app.Class.on = setupConsListener(app,channel); app.Class.off = utility.wrapper(removeConsListener(app,channel),[null,null]); app.Class.broadcast = broadcastMessageForCons(app,channel); }, onClassInstance: function(classInst){ classInst.on = setupInstListener('class'); classInst.off = utility.wrapper(removeInstListener('class'),[null,null]); classInst.broadcast = broadcastInstMessage('class'); }, onUploadCons : function(app){ var channel = "notifications.upload"; app.Upload.on = setupConsListener(app,channel); app.Upload.off = utility.wrapper(removeConsListener(app,channel),[null,null]); app.Upload.broadcast = broadcastMessageForCons(app,channel); }, onUploadInstance: function(upload){ upload.on = setupInstListener('upload'); upload.off = utility.wrapper(removeInstListener('upload'),[null,null]); upload.broadcast = broadcastInstMessage('upload'); }, onExtensionsCons : function(app){ var channel = "notifications.extensions"; app.Extension.on = setupConsListener(app, channel); app.Extension.off = utility.wrapper(removeConsListener(app,channel),[null,null]); }, onExtensionsInstance: function(extension){ extension.on = setupInstListener('extensions'); extension.off = utility.wrapper(removeInstListener('extensions'),[null,null]); }, onUserCons: function(app){ var loginFn = app.User.login; var loginWithGoogleFn = app.User.loginWithGoogle; var loginWithFacebookFn = app.User.loginWithFacebook; var loginWithTwitterFn = app.User.loginWithTwitter; var logoutFn = app.User.logout; var channel = "notifications." + constants.APP_USER_CLS +".object"; app.User.on = setupConsListener(app,channel); app.User.off = utility.wrapper(removeConsListener(app,channel),[null,null]); app.User.broadcast = broadcastMessageForCons(app,channel); app.User.getPresence = app.User.getCurrentUserPresence = function(){ return socketEmit('getPresence', { }, app); }; app.User.login = function(email, password, user){ return loginHelper(loginFn, email, password, user); }; app.User.logout = function(authtoken, user){ return logoutHelper(logoutFn, authtoken, user); }; app.User.loginWithGoogle = function(access_token,user){ return loginWithGoogleHelper(loginWithGoogleFn, access_token, user); }; app.User.loginWithFacebook = function(access_token,user){ return loginWithFacebookHelper(loginWithFacebookFn, access_token, user); }; app.User.loginWithTwitter = function(token, token_secret, consumer_key, consumer_secret, user){ return loginWithTwitterHelper(loginWithTwitterFn ,token, token_secret, consumer_key, consumer_secret, user) } }, onUserInstance: function(user) { var loginFn = user.login; var loginWithGoogleFn = user.loginWithGoogle; var loginWithFacebookFn = user.loginWithFacebook; var loginWithTwitterFn = user.loginWithTwitterT; var logoutFn = user.logout; user.getPresence = function(forceFetch) { var self = this; if(!!forceFetch){ return when.resolve(Built.Presence(user.app, self.get('_presence'))); }else{ if (!this.getUid()) throw new Error("Uid not found"); return socketEmit('getPresence', { application_user: this.getUid() }, user.app); } }; user.login = function(email, password) { var userObj = this; return loginHelper(loginFn, email, password, userObj); }; user.logout = function(authtoken){ var userObj = this; return logoutHelper(logoutFn, authtoken, userObj); }; user.loginWithGoogle = function(access_token){ return loginWithGoogleHelper(loginWithGoogleFn, access_token, this); }; user.loginWithFacebook = function(access_token){ return loginWithFacebookHelper(loginWithFacebookFn, access_token, this); }; user.loginWithTwitter = function(token, token_secret, consumer_key, consumer_secret){ return loginWithTwitterHelper(loginWithTwitterFn, token, token_secret, consumer_key, consumer_secret, this); } }, onRoleCons: function(app){ var channel = "notifications." + constants.APP_ROLE_CLS +".object"; app.Role.on = setupConsListener(app,channel); app.Role.off = utility.wrapper(removeConsListener(app,channel),[null,null]); app.Role.broadcast = broadcastMessageForCons(app,channel); }, onInstallationCons: function(app){ var channel = "notifications." + constants.APP_INSTALLATION_CLS +".object"; app.Installation.on = setupConsListener(app,channel); app.Installation.off = utility.wrapper(removeConsListener(app,channel),[null,null]); app.Installation.broadcast = broadcastMessageForCons(app,channel); } }); function authorizeSocketAfterLogin(user) { var app = user.app if (app.isRealtimeEnabled && app.isRealtimeEnabled()) { if (app.isSocketConnected()) return authorization(app).yield(user) // Socket isnt yet connected so we register a authorization call against it. // Which will get executed once the socket is connected app.getMetaMap()['authorization'] = authorization return user } else return user } function loginHelper(loginFn, email, password, userObj) { return loginFn(email, password, userObj) .then(function(user) { return authorizeSocketAfterLogin(user); }); } function loginWithGoogleHelper(actualFn, access_token,user){ return actualFn(access_token, user) .then(function(user){ return authorizeSocketAfterLogin(user); }); } function loginWithFacebookHelper(actualFn, access_token,user){ return actualFn(access_token, user) .then(function(user){ return authorizeSocketAfterLogin(user); }); } function loginWithTwitterHelper(actualFn, token, token_secret, consumer_key, consumer_secret, user){ return actualFn(token, token_secret, consumer_key, consumer_secret, user) .then(function(user){ return authorizeSocketAfterLogin(user); }); } function logoutHelper(logoutFn, authtoken, userObj) { return logoutFn(authtoken, userObj) .then(function(user) { if(userObj.app.isRealtimeEnabled()) return authorization(userObj.app).yield(user); else return user; }); } function setupEventListener(app, eventName, callback){ if(app.getMetaMap()) app.getMetaMap()[eventName] = callback } function setupAppListeners(eventName, callback){ switch(eventName){ case 'connect': setupEventListener(this, 'connect', callback) break; case 'reconnect': setupEventListener(this, 'reconnect', callback) break; case 'auth': setupEventListener(this, 'auth', callback) break; case 'subscription': setupEventListener(this, 'subscription', callback) break case 'disconnect': setupEventListener(this, 'disconnect', callback) break; case 'error': setupEventListener(this, 'errorHandler', callback) break; } } function removeAppListeners(eventName, callback){ if(!utility.isString(eventName)){ throw new Error('Event name is invalid') } if(!callback){ throw new Error('Callback to be removed is missing') } if(this.getMetaMap()){ delete this.getMetaMap()[eventName] } } var broadcastInstMessage = R.curry(function(type,message){ return broadcastMessageForCons.bind(this)(this.app, generateChannelForInst(type, this), message); }); var broadcastMessageForCons = R.curry(function(app, channel, message) { var self = this; var socket = app.getSocket(); var subsriberStr = 'subscription.' + channel; var curriendBroadcastEmit = broadcastEmit(channel,message,app); // If the master key is present in app headers, subscription is not required so we can directly make a broadcast on that channel if(app.getMasterKey()){ if(app.isSocketConnected()){ return curriendBroadcastEmit(socket) }else{ return app.getAdhocOperations().push(curriendBroadcastEmit); } } if (!app.getMetaMap()[subsriberStr]) { //Checks if we already have a subscriber for this channel storeSubscriberInMap(app, channel, subsriberStr); } if (app.isSocketConnected()) { app.getMetaMap()[subsriberStr](socket) // Installs the subscriber .then(function(){ curriendBroadcastEmit(socket); }); }else{ app.getAdhocOperations().push(curriendBroadcastEmit); } return self; }); var broadcastEmit = R.curry(function(channel,message,app,socket){ socket.emit('broadcast', { channel : channel, data : message }, function(error) { if (!error) console.log('broadcast done'); if(error) callEventCallBack(app, 'errorHandler', error) }) }); function addSocketDataInAppOptions(parameter, value, app) { var options = app.getOptions(); var newSocketData = R.mixin({}, options.socketData) newSocketData[parameter] = value; options['socketData'] = newSocketData; return app.setOptions(options, app); } var socketEmit = R.curry(function(string, payload, app) { var deferred = when.defer(); function emitFn(socket) { socket.emit(string, payload, function(err, data) { if (err) { deferred.reject(err); } if (!err) { if(data){ deferred.resolve(Built.Presence(app, data.presence)) }else{ deferred.resolve(); } } }); } if (app.isSocketConnected()) { emitFn(app.getSocket(),deferred); } else { app.getAdhocOperations().push(emitFn); } return deferred.promise; }); var setupInstListener = R.curry(function(type,parameter,callback){ return setupConsListener.bind(this)(this.app,generateChannelForInst(type,this),parameter,callback); }); var setupConsListener = R.curry(function(app, channel, parameter, callback) { var self = this; switch (parameter) { case 'presence': storeCallbackInMap(app, parameter, callback); break; case 'broadcast': registerConsBroadcast(app, channel, parameter, callback); // no break statement so subscriber is called default: registerConsSubscriber(app, channel, parameter, callback); break; } return self; }); function registerConsBroadcast(app, channel, parameter, callback) { var broadcastStr = 'message.' + channel + "." + parameter; storeCallbackInMap(app, broadcastStr, callback); } function registerConsSubscriber(app,channel,parameter,callback){ var socket = app.getSocket(); var channelStr = 'channel.' + channel + '.' + parameter; var subsriberStr = 'subscription.' + channel; var subscriptionStatusStr = 'isDone.'+ channel; storeCallbackInMap(app, channelStr, callback); storeSubscriberInMap(app, channel, subsriberStr); storeSubscriptionStatus(app, subscriptionStatusStr, false); // Determines whether emit call for subscription is already performed if (app.isSocketConnected()) { app.getMetaMap()[subsriberStr](socket); } } function storeCallbackInMap(app, channelStr, callback) { var array = app.getMetaMap()[channelStr] || []; array.push(callback); app.getMetaMap()[channelStr] = array; } function storeSubscriberInMap(app, channel, subsriberStr) { var dataStr = 'cache.' + channel; app.getMetaMap()[subsriberStr] = function(socket) { //e.g., subscription.person.blt123.update var subscriptionStatusStr = 'isDone.' + channel; var status = app.getMetaMap()[subscriptionStatusStr]; // Already subscribed if(status) return when.resolve(); //Set subscription status to true in order to avoid multiple subscriptions. app.getMetaMap()[subscriptionStatusStr] = true; var deferred = when.defer(); socket.emit('subscribe', { channel: channel }, function(error, data) { if (!error && data && data.resource) { app.getMetaMap()[dataStr] = data.resource; } if(error) callEventCallBack(app, 'errorHandler', error) // if there is an error in subscription (no permission to read on the entity), 'error' will contain details deferred.resolve(); }); return deferred.promise; } } function storeSubscriptionStatus(app, subscriptionStatusStr, value){ app.getMetaMap()[subscriptionStatusStr] = value; } var removeInstListener = R.curry(function(type, parameter, callback) { return removeConsListener.bind(this)(this.app, generateChannelForInst(type,this), parameter, callback); }); var removeConsListener = R.curry(function(app, channelName, parameter, callback) { var self = this; validateArguments(parameter,callback); if (callback === null && parameter === null) { removeAllCallbacks(channelName, app); } else if (callback === null) { removeAllCallbacksOnEventName(channelName, parameter, app); } else { if (parameter === 'broadcast') { removeBroadcastListener(app, channelName, parameter, callback); } else { unRegisterSubscriber(app, channelName, parameter, callback); } } var subsriberStr = 'subscription.' + channelName; removeSubscriber(app, channelName, subsriberStr); return self; }); function validateArguments(parameter,callback) { if (utility.isFunction(parameter)) throw new Error('Event name is missing'); } function removeAllCallbacks(channelName, app) { var channelSubStr = 'channel.' + channelName; var broadcastSubStr = 'message.' + channelName; Object .keys(app.getMetaMap()) .map(function(key) { if (key.indexOf(channelSubStr) > -1 || key.indexOf(broadcastSubStr) > -1) { delete app.getMetaMap()[key]; } }); } function removeAllCallbacksOnEventName(channelName, parameter, app) { var eventString = 'channel.' + channelName + '.' + parameter; // If parameter is broadcast the meta string is of the form broadcast.channelName.parameter if(parameter === 'broadcast') eventString = 'message.' + channelName + '.' + parameter; var callback = app.getMetaMap()[eventString]; if(callback) // ignore if callback was not present delete app.getMetaMap()[eventString]; } function removeBroadcastListener(app, channelName, parameter, callback) { var broadcastStr = 'message.' + channelName + "." + parameter; removeSpecificCallback(broadcastStr, app, parameter, channelName, callback) } function unRegisterSubscriber(app, channelName, parameter, callback) { var channelStr = 'channel.' + channelName + "." + parameter; removeSpecificCallback(channelStr, app, parameter, channelName, callback); } function removeSpecificCallback(channelStr, app, parameter, channelName, callback) { var array = app.getMetaMap()[channelStr]; if (array) { var index = array.indexOf(callback); array.splice(index, 1); if (array.length === 0) { delete app.getMetaMap()[channelStr]; // Removes the channel entry from MetaMaps } } } /* Entry for subscriber should be removed when all channels related to it are removed i.e., 'channel.notifications.person.blt660deb3539a50407.update' was the only channel and then was removed so'subscription.notifications.person.blt660deb3539a50407' should be removed as well */ function removeSubscriber(app, channelName, subsriberStr) { var matchedObj = (R.pickBy(function(key) { // if (key.indexOf('channel.' + channelName) > -1 || key.indexOf('message.'+channelName) > -1) { return true } return false; }, Object.keys(app.getMetaMap()))); if (Object.keys(matchedObj).length === 0) { delete app.getMetaMap()[subsriberStr]; } } function generateChannelForInst(type,entity) { if (!entity.isNew()) throw new Error("Uid not found"); switch (type) { case 'object': return "notifications." + entity.cls.uid + "." + entity.getUid(); break; case 'class': return "notifications." + entity.uid; break; case 'upload': return "notifications.upload." + entity.getUid(); case 'extensions' : return "notifications.extensions." + entity.getExtensionKey(); break; } } /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> Enables realtime connection. All requests to the server would be made through a socket. @memberof App @function enableRealtime @instance @example var app = Built .App('blt5d4sample2633b') .enableRealtime(); @return {App} */ function enableRealtime(persistSameAppObj){ // PersistSameObject : When socket is reconnected we do not create a new app object, // so that we can preserve previous subscriptions var newApp = this var socketObj var socketObj = connect(newApp); // Creates a new app object if(!persistSameAppObj){ newApp = resetSocketMetaInfo(socketObj, this) } //Creates a new socket connection newApp = newApp.setSocket(socketObj) attachListeners(newApp); newApp.getSocket() .once('connect', function(){ callEventCallBack(newApp, 'connect') onConnect(newApp); }) if(newApp.isAuthenticated() || newApp.getHeaders().master_key){ newApp.getMetaMap()['authorization'] = authorization; } return newApp; } function connect(app) { var random = Math.ceil(Math.random()*10000000) var url = app.getURLForRealtime() var socket = io(url, { query: 'api_key=' + app.getApiKey() + "&conn_id=" + random, forceNew: true }); return socket; } function resetSocketMetaInfo(socketObj, app) { var newApp = app .addAdhocOpterations() //Meta properties that hold information required for realtime functionality .addMetaMapp() // .setAdaptor(Built.Adaptor.SOCKET); // Sets a socket property in app and changes adaptor to socketAdaptor return newApp; } function setupRouters(newApp){ setupRouter('create' , newApp); setupRouter('update' , newApp); setupRouter('delete' , newApp); setupRouter('broadcast', newApp); setupRouter('presence' , newApp); } function setupRouter(parameter, newApp) { var socket = newApp.getSocket(); switch (parameter) { case 'broadcast': socket.on(parameter,routerCallbackForBroadcast(parameter, newApp)); break; case 'presence': socket.on(parameter, routerCallbackForPresence(parameter, newApp)); break; default : socket.on(parameter, routerCallbackForCUD(parameter, newApp)); break; } } var routerCallbackForPresence = R.curry(function(parameter, newApp, data) { if (newApp.getMetaMap()[parameter]) { newApp.getMetaMap()[parameter] .map(function(callback) { // Server returns an object consisting of presence property which is not required in SDK var rawPresence = R.mixin({}, data.presence); rawPresence.application_user = data.application_user; callback(Built.Presence(newApp, rawPresence)); }); } }); var routerCallbackForBroadcast = R.curry(function(parameter, newApp, data) { var broadcastStr = 'message.' + data.channel + "." + parameter; if (newApp.getMetaMap()[broadcastStr]) { newApp.getMetaMap()[broadcastStr] .map(function(callback) { callback(data.resource); }); } }); var routerCallbackForCUD = R.curry(function(parameter, newApp, data) { /*var resource = data.resource if(resource.uid === 'bltf68651a9985dc6f2'){ resource.message.forEach(function(msg){ console.log(resource.uid, msg) if(typeof msg === 'string'){ console.log(resource.uid, msg) } }) }*/ var channelStr = "channel." + data.channel + "." + parameter; // Listener for event on object is registered in metaMap as follow notifications.person.blt01D.update if (newApp.getMetaMap()[channelStr]) { // Merge the updated data with cache data mergeData(parameter, data, newApp) .then(function(mergedObj){ newApp.getMetaMap()[channelStr] .map(function(callback) { callback(mergedObj) }); }) } }); function mergeData(parameter, data, newApp) { switch (getType(data.channel)) { case 'objectInst': return objectWrapper(true, parameter, data, newApp); break; case 'object': return objectWrapper(false, parameter, data, newApp); break; case 'uploadInst': return uploadWrapper(parameter, data, newApp); break; case 'upload' : return uploadWrapper(parameter, data, newApp); break; case 'classInst': return classWrapper(parameter, data, newApp) break; case 'class' : return classWrapper(parameter, data, newApp) break; default: return when.resolve(data.resource); break; } } // This is from version 4 // function getType(channel) { // if (channel.match(generiUploadcRegex)) { // return "upload" // } else if (channel.match(generiClasscRegex)) { // return "class"; // } else if (channel.match(genericObjectRegex)) { // return "object"; // } else if (channel.match(objectRegex)) { // return 'objectInst'; // } else if (channel.match(classRegex)) { // return 'classInst' // } else if (channel.match(uploadRegex)) { // return "uploadInst"; // } // } function getType(channel) { // console.log(channel) if (channel.match(generiUploadcRegex)) { return "upload" } else if (channel.match(generiClasscRegex)) { return "class" } else if (channel.match(genericExtensionRegex)){ return "extensions" } else if (channel.match(genericObjectRegex)) { return "object" } else if (channel.match(objectRegex)) { return 'objectInst' } else if (channel.match(classRegex)) { return 'classInst' } else if (channel.match(uploadRegex)) { return "uploadInst" } else if (channel.match(extensionsRegex)) { return 'extensionsInst' } } function classWrapper(parameter, data, newApp){ if (parameter === "delete") { return when.resolve(data.resource); } else { return when.resolve(newApp.ClassCons(data.resource, data.resource.uid)); } } function uploadWrapper(parameter, data, newApp) { if (parameter === "delete") { return when.resolve(data.resource); } else { return when.resolve(newApp.UploadCons({}, data.resource, data.resource.uid)); } } function objectWrapper(isInstanceChannel, parameter, data, newApp) { var deferred = when.defer(); /* *Important*: On object level the channel string is notification.{classname}.object In cache we use channel name as a hash key. So to search and update the cache we require a specific channel string i.e notification.{classname}.{objectuid} so we generate specific channel string using the received object's data */ if(!isInstanceChannel) var dataStr = "cache." + prefix + '.' + data.classuid + '.' + data.objectuid; else var dataStr = "cache." + data.channel; if (parameter === "update") { if (newApp.getMetaMap()[dataStr] && (newApp.getMetaMap()[dataStr]._version + 1 === data.version)) { var updateObject = newApp.Class(data.classuid).ObjectCons({}, {}, Diff.applyPatch(newApp.getMetaMap()[dataStr], data.resource), data.objectuid) // Update our local cache newApp.getMetaMap()[dataStr] = updateObject.toJSON(); deferred.resolve(updateObject); } else { newApp .Class(data.classuid) .Object(data.objectuid) .includeOwner() .fetch() .then(function(object) { // We update our cache with the new merged object newApp.getMetaMap()[dataStr] = object.toJSON(); deferred.resolve(object); }); } }else{ // To create and delete deferred.resolve(newApp.Class(data.classuid).ObjectCons({}, {}, data.resource, data.resource.uid)); } return deferred.promise; } function attemptReconnection(app){ // Removes all previous socket data app.disableRealtime() // Iteratively checks whether connection was successfull var reconnectAttempt = setInterval(function(){ app = app.enableRealtime(true) app.on('connect', function() { resetSubscriptionStatuses(app) callEventCallBack(app, 'reconnect') clearInterval(reconnectAttempt) }) }, 1000) } function attachListeners(newApp) { var socket = newApp.getSocket(); var socketGlobalNS = socket.io.socket('/'); socket.on('reconnect', function() { console.log("reconnecting socket"); callEventCallBack(newApp, 'reconnect') resetSubscriptionStatuses(newApp); onConnect(newApp); }); socket.on('disconnect', function(){ console.log('Socket disconnected'); callEventCallBack(newApp, 'disconnect') // attemptReconnection(newApp) }); socket.on('error', function(error) { console.log('Socket error:', error) callEventCallBack(newApp, 'errorHandler', error) callEventCallBack(newApp, 'error', error) }); socketGlobalNS.on('error', function(error){ console.log('Socket namespace error', error); callEventCallBack(newApp, 'errorHandler', error) callEventCallBack(newApp, 'error', error) }) } // As socket is reconnecting, we need to set each subscriber's status to false function resetSubscriptionStatuses(app){ var statusKeys = Object .keys(app.getMetaMap()) .filter(extractSubscriberStatus); //Iterates the overall key in MetaMap, which starts with 'isDone' and sets them to false statusKeys.map(function(key){ app.getMetaMap()[key] = false; }); } function extractSubscriberStatus(str) { if (str.indexOf('isDone') === 0) return true; return false; } function onConnect(newApp){ console.log("Socket connected"); var socket = newApp.getSocket(); socket.removeAllListeners(); attachListeners(newApp); // Sets up listener for CUD and broadcast setupRouters(newApp); //Registers the subscriber with the server var authPromise = when.resolve() // Trys to authenticate the socket connection if(newApp.getMetaMap().authorization){ authPromise = newApp .getMetaMap() .authorization(newApp); } //On completion of subscription, flushes all adhoc operations authPromise .then(function() { return setupAllSubscribersPromise(newApp) }) .then(function(){ callEventCallBack(newApp, 'subscription') return }) .then(function() { if (!newApp.isSocketAuthStarted()) { // Authentication process is not going to happen now, so tries to flush the operation flushAdhocOperation(newApp); } }); } function authorization(app){ var deferred = when.defer(); //Authentication started, flag enabled app.setAuthStarted(true); // It may happen that before server could start listening on auth call. The client has already sent a request // due to which the auth call never receives a response var interval = setInterval(function(){ emitAuthCall(app) .then(function() { clearInterval(interval) deferred.resolve() }) .catch(function(error) { clearInterval(interval) deferred.reject(error) }) }, 2000) emitAuthCall(app) .then(function(){ clearInterval(interval) deferred.resolve() }) .catch(function(error){ clearInterval(interval) deferred.reject(error) }) return deferred.promise; } function emitAuthCall(app) { var deferred = when.defer(); var socket = app.getSocket() var emitObj = {} if(app.getAccessToken()) emitObj['access_token'] = app.getAccessToken(); if(app.getMasterKey()) emitObj['master_key'] = app.getMasterKey(); if(app.getDeviceID()) emitObj['device_id'] = app.getDeviceID(); socket.emit('auth', emitObj, function(error) { if (!error) { console.log("socket authorized"); callEventCallBack(app, 'auth') deferred.resolve(); } if(error){ callEventCallBack(app, 'errorHandler', error) deferred.reject(error) } app.setAuthStarted(false); // Flushing of operations is done }); return deferred.promise; } function setupAllSubscribersPromise(newApp) { var array = Object .keys(newApp.getMetaMap()) .filter(extractSubscribers); return when.map(array, function(str) { return when.resolve(newApp.getMetaMap()[str](newApp.getSocket())); // Invokes the registered subscriber }); } function flushAdhocOperation(newApp){ newApp .getAdhocOperations() .map(function(fn){ fn(newApp.getSocket()); }); newApp.options.socketData.adhocOperations = []; } function extractSubscribers(str) { if (str.indexOf('subscription') === 0) return true; return false; } /** * <p class="text-danger"> * [Only avaliable with realtime plugin] * </p> * * Disables realtime connection. All requests to the server would be made using HTTP. * @memberof App * @function disableRealtime * @instance * @example * // 'blt5d4sample2633b' is a dummy Application API key * // Returns an app in which realtime is enabled * var app = Built * .App('blt5d4sample2633b') * .enableRealtime(); * * // Disables realtime * app = app * .disalbeRealtime(); * @return {App} */ function disableRealtime(){ var app = this; var socket = app.getSocket(); socket.disconnect(); socket.destroy(); socket.removeAllListeners(); var newApp = emptySocketData(app); /*newApp = newApp .setAdaptor(Built.Adaptor.HTTP);*/ return newApp; } function emptySocketData(app) { var options = app.getOptions(); var newOptions = R.mixin({}, options); var newSocketData = R.mixin({}, options.socketData) newSocketData = null; newOptions['socketData'] = null; return app.setOptions(newOptions); } /** * <p class="text-danger"> * [Only avaliable with realtime plugin] * </p> * * Checks whether realtime is enabled on this app instance * @memberof App * @function isRealtimeEnabled * @instance * @example * // 'blt5d4sample2633b' is a dummy Application API key * var app = Built * .App('blt5d4sample2633b') * .enableRealtime(); // Returns an app in which realtime is enabled * var bool = app * .isRealtimeEnabled(); * @return {Boolean} */ function isRealtimeEnabled(){ return !! this.getSocket(); } /** * <p class="text-danger"> * [Only avaliable with realtime plugin] * </p> * * The callback function provided to this method would be invoked whenever an error occurs in realtime. * @memberof App * @function onRealtimeError * @instance * @example * // 'blt5d4sample2633b' is a dummy Application API key * var app = Built * .App('blt5d4sample2633b') * .enableRealtime() * .onRealtimeError(function(error){ * // e.g invalid application_key. * }); */ function onRealtimeError(callback){ if(this.getMetaMap()) this.getMetaMap()['errorHandler'] = callback; } function onRealtimeConnect(callback){ if(this.getMetaMap()) this.getMetaMap()['connect'] = callback } function callEventCallBack(app, eventName, entity){ entity = entity || app if(app.getMetaMap()[eventName]) app.getMetaMap()[eventName](entity); } /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on Object constructor. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Create</b></li > <p> The 'create' event is triggered when a new object is created in the class with which the Object constructor is associated. Registered callback is invoked with the created object.</p> <li><b>Update</b></li > <p> Update event is triggered when any object in the class with which the Object constructor is associated is modified. Registered callback is invoked with the updated object.</p> <li><b>Delete</b></li> <p> Delete event is triggered when any object in the class with which the Object constructor is associated is deleted. Registered callback is invoked with the deleted object.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcast for all objects belonging to a particular class. Registered callback is invoked with the broadcasted message.</p> </ul > @memberof Object @function on @param {String} event The event on which the listener should be registered @param {Function} callback The callback function to be invoked on event trigger @static @example * // 'blt5d4sample2633b' is a dummy Application API key * // app.Class('class_name').Object() returns a 'Object' constructor * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var Person = app.Class('person').Object; * * Person.on('create', function(person) { * // created person object * }); * Person.on('update', function(person) { * // updated person object * }); * Person.on('delete', function(person) { * // deleted person object * }); * Person.on('broadcast', function(message) { * // The message is broadcasted to all objects of person class * }); * @return {Object} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on Object instance. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Update</b></li > <p> The 'update' event is triggered when the content and/or the ACL of this object is modified. Registered callback is invoked with the updated object.</p> <li><b>Delete</b></li> <p> Delete event is triggered when an object is deleted from Built.io Backend. Registered callback is invoked with the deleted object.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcasted over this object. Registered callback is invoked with the broadcast message.</p> </ul > @memberof Object @function on @param {String} event The event on which listener should be registered @param {Function} callback The callback function to be invoked on event trigger @instance @throws new Error("Uid not found"); @example * // 'blt5d4sample2633b' is a dummy Application API key * // app.Clas('class_name').Object() returns a 'Object' instance * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var person = app.Class('person').Object('blt5dsamplef62a633b'); * person.on('update', function(person) { * // updated person object * }); * person.on('delete', function(person) { * // deleted person object * }); * person.on('broadcast', function(message) { * // The message broadcasted on this object * }); @return {Object} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on Class constructor. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Create</b></li > <p> The 'create' event is triggered when a new class is created in this application. Registered callback is invoked with the created class data.</p> <li><b>Update</b></li > <p> Update event is triggered when any class in this application is modified. Registered callback is invoked with the updated class data.</p> <li><b>Delete</b></li> <p> Delete event is triggered when any class in this application is deleted. Registered callback is invoked with the deleted class data.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcast for all classes belonging to this application. Registered callback is invoked with the broadcast message.</p> </ul > @memberof Class @function on @param {String} event The event on which the listener should be registered @param {Function} callback The callback function to be invoked on event trigger @static @example * // 'blt5d4sample2633b' is a dummy Application API key * // app.Class returns a 'Class' constructor * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var ClsCons = app.Class; * * ClsCons.on('create', function(classObj) { * // newly created class * console.log(classObj.toJSON()) * }); * ClsCons.on('update', function(classObj) { * // updated class * console.log(classObj.toJSON()) * }); * ClsCons.on('delete', function(data) { * // deleted class data * console.log(data) * }); * ClsCons.on('broadcast', function(message) { * // The message broadcasted for all classes of this application * }); @return {Class} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on Class instance </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Update</b></li > <p> Update event is triggered when the schema and/or the ACL of this class is modified. Registered callback is invoked with the updated class data.</p> <li><b>Delete</b></li> <p> Delete event is triggered when this class is deleted. Registered callback is invoked with the deleted class data.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcasted over the class instance. Registered callback is invoked with the broadcast message.</p> </ul > @memberof Class @function on @param {String} event The event on which the listener needs to be registered @param {Function} callback The callback function to be invoked on event trigger @instance @throws new Error("Uid not found"); @example * // 'blt5d4sample2633b' is a dummy Application API key * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var Person = app.Class('Person'); * * Person.on('update', function(classObj) { * // updated class * console.log(classObj.toJSON()) * }); * Person.on('delete', function(data) { * // deleted class * console.log(data) * }); * Person.on('broadcast', function(message) { * // The message broadcasted over this class * }); @return {Class} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on Upload constructor. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Create</b></li > <p> Create event is triggered when a new file is uploaded in this application. Registered callback is invoked with the uploaded data.</p> <li><b>Update</b></li > <p> Update event is triggered when the content and/or the ACL of any upload on this application is modified. Registered callback is invoked with the updated upload data.</p> <li><b>Delete</b></li> <p> Delete event is triggered when any upload on this application is deleted. Registered callback is invoked with the deleted upload data.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcast across all uploads. Registered callback is invoked with the broadcast message.</p> </ul > @memberof Upload @function on @param {String} event The event on which listener needs to be registered @param {Function} callback The callback function to be invoked on event trigger @static @example * // 'blt5d4sample2633b' is a dummy Application API key * // app.Upload returns a 'Upload' constructor * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var Upload = app.Upload; * * Upload.on('create', function(jsonObject) { * // newly created upload * }); * Upload.on('update', function(jsonObject) { * // updated upload data * }); * Upload.on('delete', function(jsonObject) { * // deleted upload data * }); * Upload.on('broadcast', function(message) { * // The message broadcasted across all the uploads in this application. * }); @return {Upload} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on Upload instance. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Update</b></li > <p> Update event is triggered when the content and/or the ACL of an upload on this application is modified. Registered callback is invoked with the updated upload data.</p> <li><b>Delete</b></li> <p> Delete event is triggered when an upload on this application is deleted. Registered callback is invoked with the deleted upload data.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcast over this upload instance. Registered callback is invoked with the broadcast message.</p> </ul > @memberof Upload @function on @param {String} event The event on which listener should be registered @param {Function} callback The callback function to be invoked on event trigger @instance @example * // 'blt5d4sample2633b' is a dummy Application API key * // 'blt5dsamplef62a633b' is uid of an upload on Built.io Backend * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var upload = app.Upload('blt5dsamplef62a633b'); * * upload.on('update', function(jsonObject) { * // updated upload data * }); * upload.on('delete', function(jsonObject) { * // deleted upload data * }); * upload.on('broadcast', function(message) { * // The message broadcasted over this upload instance * }); @return {Upload} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on User constructor. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Create</b></li > <p> Create event is triggered when a new application user is created on this application. Registered callback is invoked with the created user's details.</p> <li><b>Update</b></li > <p> Update event is triggered whe the content or the ACL of any user object on this application is modified. Registered callback is invoked with the updated user's details.</p> <li><b>Delete</b></li> <p> Delete event is triggered when any user object on this application is deleted. Registered callback is invoked with the deleted user's details.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcast across all user objects. Registered callback is invoked with the broadcast message.</p> <li><b>{@link Presence}</b></li > <p> Presence event is triggered when a user's state is changed, i.e. when the user logs in or logs out </p> </ul > @memberof User @function on @param {String} event The event on which listener should be registered @param {Function} callback The callback function to be invoked on event trigger @static @example * // 'blt5d4sample2633b' is a dummy Application API key * // app.User returns a 'User' constructor * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var User = app.User; * * User.on('create', function(jsonObject) { * // newly created user's details * }); * User.on('update', function(jsonObject) { * // updated user's details * }); * User.on('delete', function(jsonObject) { * // deleted user's details * }); * User.on('broadcast', function(message) { * // The message broadcasted across all user objects * }); * User.on('presence', function(presence){ * console.log(presence.toJSON()); // Currently logged-in users presence details * }); @return {User} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on User instance. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Update</b></li > <p> Update event is triggered when the content and/or the ACL of a user object on this application is modified. Registered callback is invoked with the updated user's details.</p> <li><b>Delete</b></li> <p> Delete event is triggered when a user object on this application is deleted. Registered callback is invoked with the deleted user's details.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcast over this user object. Registered callback is invoked with the broadcast message.</p> </ul > @memberof User @function on @param {String} event The event on which listener should be registered @param {Function} callback The callback function to be invoked on event trigger @instance @example * // 'blt5d4sample2633b' is a dummy Application API key * // app.User() returns an instance of application user class * // 'blt111sample2211r' is uid of an object on Built.io Backend * var app = Built.App('blt5d4sample2633b').enableRealtime(); * var user = app.User('blt111sample2211r'); * * user.on('update', function(jsonObject) { * // updated user's details * }); * user.on('delete', function(jsonObject) { * // deleted user's details * }); * user.on('broadcast', function(message) { * // The message broadcasted across this user object * }); @return {User} */ /** <p class="text-danger"> [Only avaliable with realtime plugin] </p> This function registers an event listener on Installation constructor. </br> </br> <p><u>Given below is the list of supported events.</u></p> <ul> <li><b>Create</b></li > <p> Create event is triggered when a new installation is created on this application. Registered callback is invoked with the created installation's details.</p> <li><b>Update</b></li > <p> Update event is triggered when the content and/or the ACL of any installation object on this application is modified. Registered callback is invoked with updated installation's details.</p> <li><b>Delete</b></li> <p> Delete event is triggered when any installation object on this application is deleted. Registered callback is invoked with the deleted installation's details.</p> <li><b>Broadcast</b></li> <p> Broadcast event is triggered when a message is broadcasted across all installation objects. Registered callback is invoked with the broadcasted message.</p> </ul > @memberof Installation @function on @param {String} event The event on which the listener should be registered @param {Function} callback The callback function to