UNPKG

detritus-client

Version:

A Typescript NodeJS library to interact with Discord's API, both Rest and Gateway.

309 lines (308 loc) 13.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClusterProcess = void 0; const child_process_1 = require("child_process"); const detritus_utils_1 = require("detritus-utils"); const basecollection_1 = require("../collections/basecollection"); const constants_1 = require("../constants"); class ClusterProcess extends detritus_utils_1.EventSpewer { constructor(manager, options) { super(); this._evalsWaiting = new basecollection_1.BaseCollection(); this._shardsWaiting = new basecollection_1.BaseCollection(); this.clusterId = -1; this.env = {}; this.process = null; this.manager = manager; this.clusterId = options.clusterId; Object.assign(this.env, process.env, options.env, { CLUSTER_COUNT: String(this.manager.clusterCount), CLUSTER_ID: String(this.clusterId), CLUSTER_SHARD_COUNT: String(options.shardCount), CLUSTER_SHARD_END: String(options.shardEnd), CLUSTER_SHARD_START: String(options.shardStart), }); Object.defineProperties(this, { clusterId: { writable: false }, manager: { enumerable: false, writable: false }, }); } get file() { return this.manager.file; } async onMessage(message) { // our child has sent us something if (message && typeof (message) === 'object') { try { switch (message.op) { case constants_1.ClusterIPCOpCodes.CLOSE: { const data = message.data; this.emit('shardClose', data); } ; return; case constants_1.ClusterIPCOpCodes.EVAL: { if (message.request) { const data = message.data; try { const results = await this.manager.broadcastEvalRaw(data.code, data.nonce); await this.sendIPC(constants_1.ClusterIPCOpCodes.EVAL, { ...data, results, }); } catch (error) { await this.sendIPC(constants_1.ClusterIPCOpCodes.EVAL, { ...data, error: { message: error.message, name: error.name, stack: error.stack, }, }); } } } ; return; case constants_1.ClusterIPCOpCodes.FILL_INTERACTION_COMMANDS: { await this.manager.broadcast(message); } ; return; case constants_1.ClusterIPCOpCodes.IDENTIFY_REQUEST: { const { shardId } = message.data; const ratelimitKey = this.manager.getRatelimitKey(shardId); const bucket = this.manager.buckets.get(ratelimitKey); if (bucket) { bucket.add(() => { return new Promise(async (resolve, reject) => { await this.sendIPC(constants_1.ClusterIPCOpCodes.IDENTIFY_REQUEST, { shardId }); const waiting = this._shardsWaiting.get(shardId); if (waiting) { const error = new Error('Received new Identify Request with same shard id, unknown why'); waiting.reject(error); this.emit('warn', { error }); } this._shardsWaiting.set(shardId, { resolve, reject }); }); }); } } ; return; case constants_1.ClusterIPCOpCodes.READY: { this.emit('ready'); } ; return; case constants_1.ClusterIPCOpCodes.RESPAWN_ALL: { } ; return; case constants_1.ClusterIPCOpCodes.REST_REQUEST: { const data = message.data; try { if (data.name in this.manager.rest && typeof (this.manager.rest[data.name]) === 'function') { let payload; if (this.manager.restCache.has(data.hash)) { payload = this.manager.restCache.get(data.hash); if (payload.promise) { payload.result = await payload.promise; payload.promise = undefined; } } else { payload = { promise: this.manager.rest[data.name](...(data.args || [])), }; this.manager.restCache.set(data.hash, payload); payload.result = await payload.promise; payload.promise = undefined; } this.manager.restCache.delete(data.hash); await this.sendIPC(constants_1.ClusterIPCOpCodes.REST_REQUEST, { result: payload.result, hash: data.hash, name: data.name, }, false, message.shardId, message.clusterId); } else { throw Error('Invalid rest function name'); } } catch (error) { await this.sendIPC(constants_1.ClusterIPCOpCodes.REST_REQUEST, { ...data, error: { message: error.message, name: error.name, stack: error.stack, }, }, false, message.shardId, message.clusterId); } } ; return; case constants_1.ClusterIPCOpCodes.SHARD_STATE: { const data = message.data; switch (data.state) { case constants_1.SocketStates.READY: { const waiting = this._shardsWaiting.get(data.shardId); if (waiting) { waiting.resolve(); } this._shardsWaiting.delete(data.shardId); } ; break; } this.emit('shardState', data); } ; return; } } catch (error) { this.emit('warn', { error }); } } this.emit('message', message); } async onExit(code, signal) { this.emit('close', { code, signal }); Object.defineProperty(this, 'ran', { value: false }); this.process = null; const error = new Error(`Process has closed with '${code}' code and '${signal}' signal.`); for (let [nonce, item] of this._evalsWaiting) { item.resolve([error, true]); this._evalsWaiting.delete(nonce); } for (let [shardId, item] of this._shardsWaiting) { item.reject(error); this._shardsWaiting.delete(shardId); } if (this.manager.respawn) { try { await this.run(); } catch (error) { this.emit('warn', { error }); } } } async eval(code, nonce) { if (this.process === null) { throw new Error('Cannot eval without a child!'); } if (this._evalsWaiting.has(nonce)) { return this._evalsWaiting.get(nonce).promise; } // incase the process dies const child = this.process; return new Promise((resolve, reject) => { const promise = new Promise(async (res, rej) => { const listener = (message) => { if (message && typeof (message) === 'object') { switch (message.op) { case constants_1.ClusterIPCOpCodes.EVAL: { const data = message.data; if (data.nonce === nonce) { child.removeListener('message', listener); this._evalsWaiting.delete(nonce); if (data.ignored) { res(null); } else { if (data.error) { res([data.error, true]); } else { res([data.result, false]); } } } } ; break; } } }; child.addListener('message', listener); if (typeof (code) === 'function') { code = `(${String(code)})(this)`; } try { await this.sendIPC(constants_1.ClusterIPCOpCodes.EVAL, { code, nonce }, true); } catch (error) { child.removeListener('message', listener); rej(error); } }); this._evalsWaiting.set(nonce, { promise, resolve, reject }); promise.then(resolve).catch(reject); }); } async send(message) { if (this.process != null) { const child = this.process; await new Promise((resolve, reject) => { child.send(message, (error) => { if (error) { reject(error); } else { resolve(); } }); }); } } async sendIPC(op, data = null, request = false, shard, clusterId) { return this.send({ op, data, request, clusterId, shard }); } async run(options = {}) { if (this.process) { return this.process; } const process = child_process_1.fork(this.file, [], { env: this.env }); this.process = process; this.process.on('message', this.onMessage.bind(this)); this.process.on('exit', this.onExit.bind(this)); if (options.wait || options.wait === undefined) { return new Promise((resolve, reject) => { const timeout = new detritus_utils_1.Timers.Timeout(); if (options.timeout) { timeout.start(options.timeout, () => { if (this.process === process) { this.process.kill(); this.process = null; } reject(new Error('Cluster Child took too long to ready up')); }); } this.once('ready', () => { timeout.stop(); resolve(process); }); }); } return process; } on(event, listener) { super.on(event, listener); return this; } } exports.ClusterProcess = ClusterProcess;