occaecatidicta
Version:
384 lines (352 loc) • 13.3 kB
text/typescript
/**
* backend session service for backend session
*/
import * as utils from '../../util/utils';
import {Application} from '../../application';
import {IComponent} from '../../interfaces/IComponent';
import {SID, FRONTENDID, ServerInfo} from '../../util/constants';
import {ISession} from './sessionService';
let 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
*/
export class BackendSessionService implements IComponent {
app: Application;
name: string;
constructor(app: Application) {
this.app = app;
}
create(opts: any) {
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
*/
get(frontendId: string, sid: number, cb: (err: Error | null, result ?: BackendSession) => void) {
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
*/
getByUid(frontendId: string, uid: string, cb: (err: Error | null, result ?: BackendSession[]) => void) {
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
*/
kickBySid(frontendId: string, sid: number, cb: (err: Error | null, result ?: void) => void): void;
kickBySid(frontendId: string, sid: number, reason: (err: Error | null, result ?: void) => void | string, cb ?: (err: Error | null, result ?: void) => void) {
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
*/
kickByUid(frontendId: string, uid: string, reason: string, cb: (err: Error | null, result ?: void) => void) {
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
*/
bind(frontendId: string, sid: number, uid: string, cb: (err: Error | null, result ?: void) => void) {
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
*/
unbind(frontendId: string, sid: number, uid: string, cb: (err: Error | null, result ?: void) => void) {
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
*/
push(frontendId: string, sid: number, key: string, value: string, cb: (err: Error | null, result ?: void) => void) {
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
*/
pushAll(frontendId: string, sid: number, settings: Object, cb: (err: Error | null, result ?: void) => void) {
let namespace = 'sys';
let service = 'sessionRemote';
let method = 'pushAll';
let args = [sid, settings];
rpcInvoke(this.app, frontendId, namespace, service, method, args, cb);
}
aget = utils.promisify(this.get.bind(this));
agetByUid = utils.promisify(this.getByUid.bind(this));
akickBySid = utils.promisify(this.kickBySid.bind(this));
akickByUid = utils.promisify(this.kickByUid.bind(this));
abind = utils.promisify(this.bind.bind(this));
aunbind = utils.promisify(this.unbind.bind(this));
apush = utils.promisify(this.push.bind(this));
apushAll = utils.promisify(this.pushAll.bind(this));
}
let rpcInvoke = function (app: Application, sid: FRONTENDID, namespace: string, service: string, method: string, args: any, cb: Function) {
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
*/
export class BackendSession implements ISession {
id: number;
uid: string;
frontendId: string;
settings: { [key: string]: any };
__sessionService__: BackendSessionService;
constructor(opts: any, service: BackendSessionService) {
for (let f in opts) {
(this as any)[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
*/
bind(uid: string, cb: (err: Error | null, result ?: void) => void) {
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
*/
unbind(uid: string, cb: (err: Error | null, result ?: void) => void) {
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
*/
set(key: string, value: any) {
this.settings[key] = value;
}
/**
* Get the value from backend session by key.
*
* @param {String} key key
* @return {Object} value
*/
get(key: string) {
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
*/
push(key: string, cb: (err: Error | null, result ?: void) => void) {
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
*/
pushAll(cb: (err: Error | null, result ?: void) => void) {
this.__sessionService__.pushAll(this.frontendId, this.id, this.settings, cb);
}
abind(uid: string, ) {
return new Promise((resolve, reject) => this.bind(uid, (err, ret) => err ? reject(err) : resolve(ret as any)));
}
aunbind(uid: string, ) {
return new Promise((resolve, reject) => this.unbind(uid, (err, ret) => err ? reject(err) : resolve(ret as any)));
}
apush(key: string) {
return new Promise((resolve, reject) => this.push(key, (err, ret) => err ? reject(err) : resolve(ret as any)));
}
apushAll() {
return new Promise((resolve, reject) => this.pushAll((err, ret) => err ? reject(err) : resolve(ret as any)));
}
// abind = utils.promisify(this.bind.bind(this));
// aunbind = utils.promisify(this.unbind.bind(this));
// apush = utils.promisify(this.push.bind(this));
// apushAll = utils.promisify(this.pushAll.bind(this));
/**
* Export the key/values for serialization.
*
* @api private
*/
export() {
let res: any = {};
EXPORTED_FIELDS.forEach((field) => {
res[field] = (this as any)[field];
});
return res;
}
}
let BackendSessionCB = function (service: BackendSessionService, cb: Function, err: Error, sinfo: ServerInfo) {
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]));
}
utils.invokeCallback(cb, null, sessions);
}
else {
// #get
let session = service.create(sinfo);
utils.invokeCallback(cb, null, session);
}
};