built.io
Version:
SDK for Built.io Backend
1,536 lines (1,379 loc) • 67.3 kB
JavaScript
/*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