UNPKG

gypsum

Version:

Simple and easy lightweight typescript server side framework on Node.js.

598 lines 25.1 kB
"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