gypsum
Version:
Simple and easy lightweight typescript server side framework on Node.js.
598 lines • 25.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const unique_1 = require("tools-box/unique");
const string_1 = require("tools-box/string");
const object_1 = require("tools-box/object");
const state_1 = require("./state");
const logger_1 = require("./misc/logger");
const types_1 = require("./types");
class Context {
constructor(type, data, init = true) {
this._locals = {};
this._mainHandler = false;
this._resolve = null;
this._reject = null;
this._stack = [];
this.response = {};
this.user = null;
this._socket = data.socket || undefined;
this._req = data.req || undefined;
this._res = data.res || undefined;
this._cookies = data.cookies;
this._rid = data.rid;
this._domain = data.domain;
this._appName = data.appName;
this._namespace = data.namespace || data.appName;
this.room = data.room || '';
this.apiType = type;
this.headers = data.headers;
this.query = data.query;
this.body = data.body;
this.params = data.params;
this.model = data.model;
this.service = data.service;
this.logger = new logger_1.Logger(`${this._appName}.${this.model.name}.${this.service.name}`);
if (init)
this._mInit();
}
static Publish(model, serviceName, data) {
return new Promise((resolve, reject) => {
if (!state_1.State.currentContext)
return reject({
message: 'could not access current context!'
});
if (state_1.State.currentContext.apiType === types_1.API_TYPES.REST || !state_1.State.currentContext._socket) {
return reject({
message: 'could not publish during rest request!'
});
}
let serviceData = model.$getService(serviceName);
console.log('serviceData');
console.log(JSON.stringify(serviceData, null, 2));
if (!serviceData) {
return reject({
message: `could not publish: service not found: ${serviceName}!`
});
}
let service = mimicService(serviceName, {
crud: serviceData.crud,
domain: serviceData.domain,
after: serviceData.after.slice(0)
});
let context = new Context(types_1.API_TYPES.SOCKET, {
rid: model.name === state_1.State.currentContext.model.name ? state_1.State.currentContext._rid : unique_1.Unique.Get(),
headers: state_1.State.currentContext.headers,
socket: state_1.State.currentContext._socket,
query: state_1.State.currentContext.query,
body: state_1.State.currentContext.body,
params: state_1.State.currentContext.params,
cookies: state_1.State.currentContext.cookies,
domain: serviceData.domain,
req: state_1.State.currentContext._req,
res: state_1.State.currentContext._res,
appName: state_1.State.currentContext._appName,
namespace: state_1.State.currentContext._namespace,
model: model,
service: service
}, false);
context._resolve = resolve;
context._reject = reject;
context._mPushStack(service.after);
context.ok(new types_1.Response(data));
});
}
static Rest(appName, model, service) {
return function (req, res, next) {
new Context(types_1.API_TYPES.REST, {
rid: null,
headers: req.headers,
query: req.query,
body: req.body,
params: req.params,
cookies: req.cookies,
req: req,
res: res,
appName: appName,
model: model,
service: service
});
};
}
static Socket(appName, namespace, socket, model, service) {
return function (data) {
logger_1.Logger.Info(`${appName} - socket event catched: '${service.event}'`);
new Context(types_1.API_TYPES.SOCKET, {
headers: socket.handshake ? socket.handshake.query : {},
rid: data.rid || null,
query: data.query,
body: data.body,
params: data.params,
socket: socket,
domain: data.domain,
room: data.room,
model: model,
service: service,
appName: appName || 'default',
namespace: namespace || appName
});
};
}
_mInit(hooks = 'both', extraHooks, extraHooksPos = 0) {
let stackDetails = {
secure: { should: 0, actual: 0 },
authorize: { should: 0, actual: 0 },
validate: { should: 0, actual: 0 },
before: { should: 0, actual: 0 },
service: { should: 0, actual: 0 },
after: { should: 0, actual: 0 },
extra: { should: 0, actual: 0 }
};
let total = 0;
this.logger.debug(`checking authentication layer with options: ${this.service.secure}`);
if (this.service.secure) {
stackDetails.secure.should = 1;
let Authentication = state_1.State.getModel('auth.users');
if (Authentication)
this._stack.push({ handler: Authentication.Authenticate.bind(Authentication), args: [] });
}
stackDetails.secure.actual = total = this._stack.length;
this.logger.debug(`checking authorization layer with options: ${this.service.authorize}`);
if (this.service.authorize) {
stackDetails.authorize.should = 1;
let Authorization = state_1.State.getModel('auth.authorization');
if (Authorization)
this._stack.push({ handler: Authorization.Authorize.bind(Authorization), args: [this.service.authorize] });
}
stackDetails.authorize.actual = this._stack.length - total;
total = this._stack.length;
this.logger.debug(`checking validate hook with options: ${this.service.validate}`);
if (this.service.validate) {
stackDetails.validate.should = 1;
this._stack.push({ handler: state_1.State.getHook('validate'), args: [this.service.validate] });
}
stackDetails.validate.actual = this._stack.length - total;
total = this._stack.length;
this.logger.debug(`checking before hooks with options: ${this.service.before}`);
if ((hooks === 'both' || hooks === 'before') && this.service.before && this.service.before.length) {
stackDetails.before.should = this.service.before.length;
this._mPushStack(this.service.before);
}
stackDetails.before.actual = this._stack.length - total;
total = this._stack.length;
this.logger.debug('adding main service');
stackDetails.service.should = 1;
this._stack.push({ handler: this.model[this.service.__name].bind(this.model), args: [], mainHandler: true });
stackDetails.service.actual = this._stack.length - total;
total = this._stack.length;
this.logger.debug('checking extra hooks pre after hooks');
if (extraHooks && extraHooks.length && extraHooksPos === -1) {
stackDetails.extra.should = extraHooks.length;
this._stack.push(...extraHooks);
}
stackDetails.extra.actual = this._stack.length - total;
total = this._stack.length;
this.logger.debug(`checking after hooks with options: ${this.service.after}`);
if ((hooks === 'both' || hooks === 'after') && this.service.after && this.service.after.length) {
stackDetails.after.should = this.service.after.length;
this._mPushStack(this.service.after);
}
stackDetails.after.actual = this._stack.length - total;
total = this._stack.length;
this.logger.debug('checking extra hooks post after hooks');
if (extraHooks && extraHooks.length && extraHooksPos === 1) {
stackDetails.extra.should = extraHooks.length;
this._stack.push(...extraHooks);
}
stackDetails.extra.actual = this._stack.length - total;
total = this._stack.length;
this.logger.debug('stack details:', JSON.stringify(stackDetails, null, 2));
state_1.State.currentContext = this;
this.logger.debug('running the stack');
this.next();
}
_mRespond() {
this.response.apiType = this.apiType;
this.response.code = this.response.code || types_1.RESPONSE_CODES.UNKNOWN_ERROR;
this.response.domain = this.response.domain || this.service.domain || types_1.RESPONSE_DOMAINS.SELF;
this.response.service = this.response.service || this.service.__name;
this.response.rid = this._rid;
this.logger.debug('sending response');
if (this.apiType === types_1.API_TYPES.REST && this._res) {
state_1.State.currentContext = null;
if (this._resolve && this.response.success) {
this._resolve(this.response);
}
else if (this._reject && !this.response.success) {
this._reject(this.response);
}
else {
this._res.status(this.response.code).json(this.response);
}
}
else if (this._socket) {
let event = `${this.model.name} ${this.service.crud}`;
this.response.crud = this.service.crud;
this.logger.debug(`dispatching event: '${event}' with domain: ${this.response.domain || this._domain || this.service.domain}, room: ${this.response.room}`);
if (this.response.code < 200 || this.response.code >= 300) {
this._socket.emit(event, this.response);
}
else {
let domain = this.response.domain || this._domain || this.service.domain;
let ns = state_1.State.ioNamespaces[this._namespace];
this.response.room = this.room || this.response.room;
if (this.response.domain === types_1.RESPONSE_DOMAINS.ROOM && this.response.room) {
if (process && process.send)
process.send({ data: this.response, target: 'others', action: 'response', namespace: this._namespace });
if (ns)
ns.to(this.response.room).emit(event, this.response);
else
this._socket.emit(event, this.response);
}
else if (this.response.domain === types_1.RESPONSE_DOMAINS.ALL) {
if (process && process.send)
process.send({ data: this.response, target: 'others', action: 'response', namespace: this._namespace });
let io = state_1.State.ioNamespaces[this._namespace];
if (ns)
ns.emit(event, this.response);
else
this._socket.emit(event, this.response);
}
else {
this._socket.emit(event, this.response);
}
}
if (this._resolve && this.response.success) {
this._resolve(this.response);
}
else if (this._reject && !this.response.success) {
this._reject(this.response);
}
}
if (this === state_1.State.currentContext)
state_1.State.currentContext = null;
}
_mPushStack(hooksList) {
if (hooksList && hooksList.length)
for (let hook of getHooks(this, hooksList))
if (hook && hook.handler)
this._stack.push(hook);
}
switchService(model, serviceName, hooks = 'both', useOwnHooks = 0) {
this.model = model;
this.service = this.model[string_1.capitalizeFirst(serviceName)];
let extraHooks;
if (useOwnHooks !== 0)
extraHooks = this._stack;
this._stack = [];
this._mInit(hooks, extraHooks, useOwnHooks);
}
runService(model, serviceName, data = {}, user) {
return new Promise((resolve, reject) => {
let service = model[string_1.capitalizeFirst(serviceName)];
let context = new Context(this.apiType, {
rid: model.name === this.model.name ? this._rid : null,
headers: this.headers,
query: data.query || this.query,
body: data.body || this.body,
params: data.params || this.params,
cookies: data.cookies || this.cookies,
req: this._req,
res: this._res,
appName: model.app.name,
model: model,
service: service
}, false);
context.user = user || this.user;
context._resolve = resolve;
context._reject = reject;
context._mInit();
})
.then(response => {
state_1.State.currentContext = this;
return response;
});
}
useServiceHooks(service, clearOwnHooks = false) {
if (service) {
if (clearOwnHooks)
this._stack = [];
this._mPushStack(service.after);
}
else {
this.logger.warn('cannot user undifined service hooks!');
}
}
get domain() { return this._domain; }
set domain(value) { this._domain = value; }
get isMainHandler() {
if (this._mainHandler) {
this._mainHandler = false;
return true;
}
return false;
}
getHeader(name) {
return this.headers[name];
}
get(name) {
return this._locals[name];
}
set(name, value) {
if (name)
this._locals[name] = value;
return this;
}
remove(name) {
if (name)
delete this._locals[name];
return this;
}
cookie(name, value, options = { httpOnly: true }) {
if (this.apiType === types_1.API_TYPES.REST && name && value)
this._res.cookie(name, value, options);
return this;
}
cookies(name) {
return this._cookies[name];
}
clearCookie(name) {
if (this.apiType === types_1.API_TYPES.REST)
this._res.cookie(name, "", { maxAge: 0 });
return this;
}
toggleRoom(action, rooms, users) {
return new Promise((resolve, reject) => {
if (!rooms) {
rooms = [this.room];
}
else if (!Array.isArray(rooms)) {
rooms = [rooms];
}
this.logger.info(`${action}ing room`);
try {
rooms.map((room) => typeof room === 'string' ? room : room.toString());
}
catch (err) {
reject({
message: `error ${action}ing room`,
original: err,
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
}
if (!users) {
if (this.apiType === types_1.API_TYPES.SOCKET && this._socket) {
for (let room of rooms) {
this._socket[action](room || this.room);
this.logger.info(`socket: ${this._socket.id} ${action}ed room: ${room || this.room}`);
}
return resolve(true);
}
reject({
message: `invalid ${action} room options`,
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
}
else {
users = typeof users === "string" ? [users] : users;
let usersModel = state_1.State.getModel('auth.users');
usersModel.getSockets(users)
.then(sockets => {
let ns = state_1.State.ioNamespaces[this._namespace];
if (ns) {
let nsSockets = ns.sockets;
for (let i = 0; i < sockets.length; i++) {
let nsSockets = ns.sockets;
if (nsSockets[sockets[i]]) {
for (let room of rooms)
if (room) {
nsSockets[sockets[i]][action](room);
this.logger.info(`socket: ${sockets[i]} ${action}ed room: ${room}`);
}
sockets.splice(i--, 1);
}
}
}
if (sockets.length)
if (process && process.send)
process.send({ data: { room: rooms, socketIds: sockets }, target: 'others', action: `${action} room`, namespace: this._namespace });
resolve(true);
})
.catch(err => {
reject({
message: 'error getting users sockets',
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
});
}
});
}
joinRoom(rooms, users) {
return this.toggleRoom('join', rooms, users);
}
leaveRoom(rooms, users) {
return this.toggleRoom('leave', rooms, users);
}
getRealArgsValues(args, hookName) {
if (args && args.length) {
for (let j = 0; j < args.length; j++) {
if (typeof args[j] === 'string') {
if (args[j].indexOf(',') > -1) {
args[j] = args[j].split(',');
for (let x = 0; x < args[j].length; x++)
args[j][x] = getReference(this, args[j][x], hookName);
}
else {
args[j] = getReference(this, args[j], hookName);
}
}
}
}
return args;
}
next(err) {
if (err) {
console.trace(err);
this.response = new types_1.Response({ error: new types_1.ResponseError(err), code: err.code });
this._mRespond();
}
else {
let current = this._stack.splice(0, 1)[0];
if (current) {
this.logger.debug(`running stack handler: ${current.handler.__name, current.handler.name}`);
this._mainHandler = !!current.mainHandler;
current.handler(this, ...this.getRealArgsValues(current.args, current.handler.name));
}
else {
this.logger.debug('end of stack, preparing for responding...');
this._mRespond();
}
}
}
nextHook(hook, args) {
if (!hook)
return this;
if (typeof hook === 'function') {
this._stack.unshift({ handler: hook, args: args || [] });
return this;
}
let handler = state_1.State.getHook(hook);
if (handler)
this._stack.unshift({ handler: handler, args: args || [] });
else
this.logger.warn(`${hook} was not found`);
return this;
}
ok(res) {
if (res.type === 'html')
return this._mSendHtml(res.data, res.code);
else if (res.type === 'file')
return this._mSendFile(res.data, res.code);
this.response = res;
this.next();
}
_mSendHtml(html, code = 200) {
if (this._res) {
this._res.type('html')
.status(code)
.send(html);
}
else {
this.next({
message: 'cannot send html content on socket connection',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
}
}
_mSendFile(filePath, code = 200) {
if (this._res)
this._res.status(code).sendFile(filePath);
else
this.next({
message: 'cannot send file on socket connection',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
}
}
exports.Context = Context;
function* getHooks(context, list) {
for (let hook of list) {
let args = typeof hook === 'string' ? hook.split(':') : (hook.args ? (Array.isArray(hook.args) ? hook.args : [hook.args]) : []);
let hookName = typeof hook === 'string' ? args.shift().toLowerCase() : hook.name.toLowerCase();
let handler = null;
if (hookName) {
if (hookName.indexOf('.') > -1) {
let appName, modelName, modelHookName;
[appName, modelName, modelHookName] = hookName.split('.');
if (modelHookName) {
let model = state_1.State.getModel(`${appName}.${modelName}`);
if (model) {
let modelHook = model.$getHook(modelHookName);
if (modelHook) {
if (modelHook.private && context.model !== model) {
logger_1.Logger.Warn(`getHooks: hook '${hookName}' is a private hook`);
yield null;
}
handler = model[modelHook.__name].bind(model);
}
else {
yield null;
}
}
else {
yield null;
}
}
else {
let appHookName = modelName;
let app = state_1.State.getApp(appName);
if (appName) {
let appHook = app.$getHook(appHookName);
if (appHook) {
if (appHook.private && context._appName.toLowerCase() !== app.name.toLowerCase()) {
logger_1.Logger.Warn(`getHooks: hook '${hookName}' is a private hook`);
yield null;
}
handler = app[appHook.__name].bind(app);
}
else {
yield null;
}
}
else {
yield null;
}
}
}
else {
(handler = state_1.State.getHook(hookName)) || (yield null);
}
}
else {
yield null;
}
yield { handler: handler, args: args || [] };
}
}
function getReference(ctx, name, hookName) {
if (name.charAt(0) === '@') {
name = name.slice(1);
let appName, modelName;
[appName, modelName] = name.split('.');
let model = state_1.State.getModel(name);
if (!model) {
ctx.logger.warn(`${hookName} hook: model '${modelName}' is not found`);
return undefined;
}
let isPrivate = model.$get('private');
if (!isPrivate) {
ctx.logger.warn(`'${modelName}' is a private model`);
return undefined;
}
return model;
}
else if (name.charAt(0) === '$') {
let parts = name.split('.');
let targetName = parts[0].slice(1);
if (targetName === 'locals')
return ctx.get(parts[1]);
else if (targetName === 'cookies')
return ctx.cookies(parts[1]);
else if (['headers', 'query', 'body', 'params', 'response'].indexOf(targetName) > -1) {
parts.shift();
return object_1.getValue(ctx[targetName], parts.join('.'));
}
else
return name;
}
return name;
}
function mimicService(name, options) {
let service = {
__name: name,
name
};
object_1.extend(service, options);
return service;
}
//# sourceMappingURL=context.js.map