ircgrampp
Version:
IRCGram++ is a complexly simple Telegram <-> IRC Gateway
308 lines (239 loc) • 6.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.asyncHookedMethod = exports.syncHookedMethod = exports.plainParams = exports.resolveParams = exports.subscribeTo = exports.declareHook = exports.Hook = exports.HookFlow = void 0;
var _debug = _interopRequireDefault(require("debug"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const debug = (0, _debug.default)('hooks');
var Promise = require("bluebird");
let hooks = {};
class HookFlow {
constructor() {
this._preventDefault = false;
this._preventAfter = false;
this._stopPropagation = false;
}
preventDefault() {
this._preventDefault = true;
}
preventAfter() {
this._preventAfter = true;
}
stopPropagation() {
this._stopPropagation = true;
}
get isPreventDefault() {
return this._preventDefault;
}
get isPreventAfter() {
return this._preventAfter;
}
get isStopPropagation() {
return this._stopPropagation;
}
}
exports.HookFlow = HookFlow;
class Hook {
constructor(name) {
Hook.validateHookName(name);
this._name = name;
this._actions = {
before: [],
after: []
};
hooks[name] = this;
debug(`New hook defined: ${this._name}`);
}
subscribe(type, action) {
if (typeof action !== 'function') {
throw new Error('action must be a function');
}
this._actions[type].push(action);
return this;
}
resolveAsync(type, data, context = {}) {
let actions = this._actions[type];
if (!actions.length) {
return Promise.resolve({
flow: {},
data
});
}
let flow = new HookFlow();
debug(`[sync] ${this._name}:${type}`);
return Promise.reduce(actions, (ndata, action) => {
if (flow.isStopPropagation) {
return ndata;
}
let rdata = action(context, ndata, flow);
if (typeof rdata === 'undefined') {
return ndata;
} else {
return rdata;
}
}, data).then(finalData => {
return {
flow,
data: finalData
};
});
}
resolveSync(type, data, context = {}) {
let actions = this._actions[type];
if (!actions.length) {
return {
flow: {},
data
};
}
let flow = new HookFlow();
let ndata = data;
debug(`[async] ${this._name}:${type}`);
actions.forEach(action => {
if (flow.isStopPropagation) {
return;
}
let result = action(context, ndata, flow);
if (result instanceof Promise) {
throw new Error(`${this._name}:${type} is a sync hook`);
}
if (typeof result !== 'undefined') {
ndata = result;
}
});
return {
flow,
data: ndata
};
}
before(data, context = {}) {
return this.resolveAsync('before', data, context);
}
after(data, context = {}) {
return this.resolveAsync('after', data, context);
}
beforeSync(data, context = {}) {
return this.resolveSync('before', data, context);
}
afterSync(data, context = {}) {
return this.resolveSync('after', data, context);
}
get name() {
return this._name;
}
static validateHookName(name) {
if (hooks.hasOwnProperty(name)) {
throw new Error(`Hook ${name} already exists`);
}
if (!name.match(/^[a-z]+:[a-z]+(\.[a-z]+)*$/i)) {
throw new Error(`Invalid hook name ${name}`);
}
}
}
exports.Hook = Hook;
const declareHook = function (name) {
return new Hook(name);
};
exports.declareHook = declareHook;
const subscribeTo = function (name, type, action) {
if (!hooks.hasOwnProperty(name)) {
throw new Error(`Hook ${name} does not exists`);
}
debug(`Subscribe callback to ${type}:${name}`);
return hooks[name].subscribe(type, action);
};
exports.subscribeTo = subscribeTo;
const resolveParams = function (names, params) {
let restParam = null,
result = {};
if (!names.length) {
throw new Error('Name is empty');
}
if (names[names.length - 1].match(/^\.{3}/)) {
restParam = names.pop().replace(/^\.{3}/, '');
}
for (let i = 0; i < names.length; i++) {
result[names[i]] = params.length ? params.shift() : undefined;
}
if (params.length && restParam) {
result[restParam] = params;
} else if (params.length && !restParam) {
throw new Error('Invalid numer of params');
} else if (restParam) {
result[restParam] = [];
}
return result;
};
exports.resolveParams = resolveParams;
const plainParams = function (names, params) {
return names.map(name => {
return params[name];
});
};
exports.plainParams = plainParams;
const syncHookedMethod = function (name, ...params) {
if (!params.length) {
params = false;
} else if (params.length === 1) {
if (typeof params[0] !== 'string') {
params = !!params[0];
}
}
return function decorator(cls, methodName, target) {
let of = target.value;
let hook = declareHook(name);
if (typeof target.value !== 'function') {
throw new Error('Target method must to be a function');
}
target.value = function (...fargs) {
let paramsObject = params ? resolveParams(params, fargs) : fargs[0];
let result;
let bresult = hook.beforeSync(paramsObject, this);
if (bresult.flow.isPreventDefault) {
result = bresult.data;
} else {
result = of.apply(this, params ? plainParams(params, bresult.data) : [bresult.data]);
}
if (bresult.flow.isPreventAfter) {
return result;
}
return hook.afterSync(result, this).data;
};
};
};
exports.syncHookedMethod = syncHookedMethod;
const asyncHookedMethod = function (name, ...params) {
if (!params.length) {
params = false;
} else if (params.length === 1) {
if (typeof params[0] !== 'string') {
params = !!params[0];
}
}
return function decorator(cls, methodName, target) {
let of = target.value;
let hook = declareHook(name);
if (typeof target.value !== 'function') {
throw new Error('Target method must to be a function');
}
target.value = function (...fargs) {
let paramsObject = params ? resolveParams(params, fargs) : fargs[0];
let flow;
return hook.before(paramsObject, this).then(result => {
flow = result.flow;
let pargs = params ? plainParams(params, result.data) : [result.data];
if (flow.isPreventDefault) {
return pargs;
}
return of.apply(this, pargs);
}).then(result => {
if (flow.isPreventAfter) {
return result;
}
return hook.after(result, this);
});
};
};
};
exports.asyncHookedMethod = asyncHookedMethod;