"use strict";
/**
* @file backend session service for backend session
*/
const utils = require('../../util/utils');
const EXPORTED_FIELDS = ['id', 'frontendId', 'uid', 'settings'];
/**
* Service that maintains backend sessions and the communication with frontend
* servers.
*
* BackendSessionService would be created in each server process and maintains
* backend sessions for current process and communicates with the relative
* frontend servers.
*
* BackendSessionService instance could be accessed by
* `app.get('backendSessionService')` or app.backendSessionService.
*
* @class
* @constructor
*/
let BackendSessionService = function(app) {
this.app = app;
};
module.exports = BackendSessionService;
BackendSessionService.prototype.create = function(opts) {
if(!opts) {
throw new Error('opts should not be empty.');
}
return new BackendSession(opts, this);
};
/**
* Get backend session by frontend server id and session id.
*
* @param {String} frontendId frontend server id that session attached
* @param {String} sid session id
* @param {Function} cb callback function. args: cb(err, BackendSession)
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.get = function(frontendId, sid, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'getBackendSessionBySid';
let args = [sid];
rpcInvoke(this.app, frontendId, namespace, service, method,
args, BackendSessionCB.bind(null, this, cb));
};
/**
* Get backend sessions by frontend server id and user id.
*
* @param {String} frontendId frontend server id that session attached
* @param {String} uid user id binded with the session
* @param {Function} cb callback function. args: cb(err, BackendSessions)
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.getByUid = function(frontendId, uid, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'getBackendSessionsByUid';
let args = [uid];
rpcInvoke(this.app, frontendId, namespace, service, method,
args, BackendSessionCB.bind(null, this, cb));
};
/**
* Kick a session by session id.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.kickBySid = function(frontendId, sid, reason, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'kickBySid';
let args = [sid];
if(typeof reason === 'function') {
cb = reason;
}else{
args.push(reason);
}
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Kick sessions by user id.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number|String} uid user id
* @param {String} reason kick reason
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
*/
BackendSessionService.prototype.kickByUid = function(frontendId, uid, reason, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'kickByUid';
let args = [uid];
if(typeof reason === 'function') {
cb = reason;
}else{
args.push(reason);
}
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Bind the session with the specified user id. It would finally invoke the
* the sessionService.bind in the cooperating frontend server.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.bind = function(frontendId, sid, uid, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'bind';
let args = [sid, uid];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Unbind the session with the specified user id. It would finally invoke the
* the sessionService.unbind in the cooperating frontend server.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.unbind = function(frontendId, sid, uid, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'unbind';
let args = [sid, uid];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Push the specified customized change to the frontend internal session.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {String} key key in session that should be push
* @param {Object} value value in session, primitive js object
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.push = function(frontendId, sid, key, value, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'push';
let args = [sid, key, value];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
/**
* Push all the customized changes to the frontend internal session.
*
* @param {String} frontendId cooperating frontend server id
* @param {Number} sid session id
* @param {Object} settings key/values in session that should be push
* @param {Function} cb callback function
*
* @memberOf BackendSessionService
* @api private
*/
BackendSessionService.prototype.pushAll = function(frontendId, sid, settings, cb) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'pushAll';
let args = [sid, settings];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
};
let rpcInvoke = function(app, sid, namespace, service, method, args, cb) {
app.rpcInvoke(sid, {namespace: namespace, service: service, method: method, args: args}, cb);
};
/**
* BackendSession is the proxy for the frontend internal session passed to handlers and
* it helps to keep the key/value pairs for the server locally.
* Internal session locates in frontend server and should not be accessed directly.
*
* The mainly operation on backend session should be read and any changes happen in backend
* session is local and would be discarded in next request. You have to push the
* changes to the frontend manually if necessary. Any push would overwrite the last push
* of the same key silently and the changes would be saw in next request.
* And you have to make sure the transaction outside if you would push the session
* concurrently in different processes.
*
* See the api below for more details.
*
* @class
* @constructor
*/
let BackendSession = function(opts, service) {
for(let f in opts) {
this[f] = opts[f];
}
this.__sessionService__ = service;
};
/**
* Bind current session with the user id. It would push the uid to frontend
* server and bind uid to the frontend internal session.
*
* @param {Number|String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSession
*/
BackendSession.prototype.bind = function(uid, cb) {
let self = this;
this.__sessionService__.bind(this.frontendId, this.id, uid, function(err) {
if(!err) {
self.uid = uid;
}
utils.invokeCallback(cb, err);
});
};
/**
* Unbind current session with the user id. It would push the uid to frontend
* server and unbind uid from the frontend internal session.
*
* @param {Number|String} uid user id
* @param {Function} cb callback function
*
* @memberOf BackendSession
*/
BackendSession.prototype.unbind = function(uid, cb) {
let self = this;
this.__sessionService__.unbind(this.frontendId, this.id, uid, function(err) {
if(!err) {
self.uid = null;
}
utils.invokeCallback(cb, err);
});
};
/**
* Set the key/value into backend session.
*
* @param {String} key key
* @param {Object} value value
*/
BackendSession.prototype.set = function(key, value) {
this.settings[key] = value;
};
/**
* Get the value from backend session by key.
*
* @param {String} key key
* @return {Object} value
*/
BackendSession.prototype.get = function(key) {
return this.settings[key];
};
/**
* Push the key/value in backend session to the front internal session.
*
* @param {String} key key
* @param {Function} cb callback function
*/
BackendSession.prototype.push = function(key, cb) {
this.__sessionService__.push(this.frontendId, this.id, key, this.get(key), cb);
};
/**
* Push all the key/values in backend session to the frontend internal session.
*
* @param {Function} cb callback function
*/
BackendSession.prototype.pushAll = function(cb) {
this.__sessionService__.pushAll(this.frontendId, this.id, this.settings, cb);
};
/**
* Export the key/values for serialization.
*
* @api private
*/
BackendSession.prototype.export = function() {
let res = {};
EXPORTED_FIELDS.forEach(function(field) {
res[field] = this[field];
});
return res;
};
let BackendSessionCB = function(service, cb, err, sinfo) {
if(err) {
utils.invokeCallback(cb, err);
return;
}
if(!sinfo) {
utils.invokeCallback(cb);
return;
}
let sessions = [];
if(Array.isArray(sinfo)){
// #getByUid
for(let i = 0,k = sinfo.length;i<k;i++){
sessions.push(service.create(sinfo[i]));
}
}
else{
// #get
sessions = service.create(sinfo);
}
utils.invokeCallback(cb, null, sessions);
};