UNPKG

discord.js-self

Version:

A fork of discord.js with support of user accounts

229 lines (209 loc) 7.2 kB
'use strict'; const { Events } = require('../util/Constants'); const Util = require('../util/Util'); /** * Helper class for sharded clients spawned as a child process/worker, such as from a {@link ShardingManager}. * Utilises IPC to send and receive data to/from the master process and other shards. */ class ShardClientUtil { /** * @param {Client} client Client of the current shard * @param {ShardingManagerMode} mode Mode the shard was spawned with */ constructor(client, mode) { /** * Client for the shard * @type {Client} */ this.client = client; /** * Mode the shard was spawned with * @type {ShardingManagerMode} */ this.mode = mode; /** * Message port for the master process (only when {@link ShardClientUtil#mode} is `worker`) * @type {?MessagePort} */ this.parentPort = null; if (mode === 'process') { process.on('message', this._handleMessage.bind(this)); client.on('ready', () => { process.send({ _ready: true }); }); client.on('disconnect', () => { process.send({ _disconnect: true }); }); client.on('reconnecting', () => { process.send({ _reconnecting: true }); }); } else if (mode === 'worker') { this.parentPort = require('worker_threads').parentPort; this.parentPort.on('message', this._handleMessage.bind(this)); client.on('ready', () => { this.parentPort.postMessage({ _ready: true }); }); client.on('disconnect', () => { this.parentPort.postMessage({ _disconnect: true }); }); client.on('reconnecting', () => { this.parentPort.postMessage({ _reconnecting: true }); }); } } /** * Array of shard IDs of this client * @type {number[]} * @readonly */ get ids() { return this.client.options.shards; } /** * Total number of shards * @type {number} * @readonly */ get count() { return this.client.options.shardCount; } /** * Sends a message to the master process. * @param {*} message Message to send * @returns {Promise<void>} */ send(message) { return new Promise((resolve, reject) => { if (this.mode === 'process') { process.send(message, err => { if (err) reject(err); else resolve(); }); } else if (this.mode === 'worker') { this.parentPort.postMessage(message); resolve(); } }); } /** * Fetches a client property value of each shard. * @param {string} prop Name of the client property to get, using periods for nesting * @returns {Promise<Array<*>>} * @example * client.shard.fetchClientValues('guilds.cache.size') * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) * .catch(console.error); * @see {@link ShardingManager#fetchClientValues} */ fetchClientValues(prop) { return new Promise((resolve, reject) => { const parent = this.parentPort || process; const listener = message => { if (!message || message._sFetchProp !== prop) return; parent.removeListener('message', listener); if (!message._error) resolve(message._result); else reject(Util.makeError(message._error)); }; parent.on('message', listener); this.send({ _sFetchProp: prop }).catch(err => { parent.removeListener('message', listener); reject(err); }); }); } /** * Evaluates a script or function on all shards, in the context of the {@link Clients}. * @param {string|Function} script JavaScript to run on each shard * @returns {Promise<Array<*>>} Results of the script execution * @example * client.shard.broadcastEval('this.guilds.cache.size') * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) * .catch(console.error); * @see {@link ShardingManager#broadcastEval} */ broadcastEval(script) { return new Promise((resolve, reject) => { const parent = this.parentPort || process; script = typeof script === 'function' ? `(${script})(this)` : script; const listener = message => { if (!message || message._sEval !== script) return; parent.removeListener('message', listener); if (!message._error) resolve(message._result); else reject(Util.makeError(message._error)); }; parent.on('message', listener); this.send({ _sEval: script }).catch(err => { parent.removeListener('message', listener); reject(err); }); }); } /** * Requests a respawn of all shards. * @param {number} [shardDelay=5000] How long to wait between shards (in milliseconds) * @param {number} [respawnDelay=500] How long to wait between killing a shard's process/worker and restarting it * (in milliseconds) * @param {number} [spawnTimeout=30000] The amount in milliseconds to wait for a shard to become ready before * continuing to another. (-1 or Infinity for no wait) * @returns {Promise<void>} Resolves upon the message being sent * @see {@link ShardingManager#respawnAll} */ respawnAll(shardDelay = 5000, respawnDelay = 500, spawnTimeout = 30000) { return this.send({ _sRespawnAll: { shardDelay, respawnDelay, spawnTimeout } }); } /** * Handles an IPC message. * @param {*} message Message received * @private */ async _handleMessage(message) { if (!message) return; if (message._fetchProp) { const props = message._fetchProp.split('.'); let value = this.client; for (const prop of props) value = value[prop]; this._respond('fetchProp', { _fetchProp: message._fetchProp, _result: value }); } else if (message._eval) { try { this._respond('eval', { _eval: message._eval, _result: await this.client._eval(message._eval) }); } catch (err) { this._respond('eval', { _eval: message._eval, _error: Util.makePlainError(err) }); } } } /** * Sends a message to the master process, emitting an error from the client upon failure. * @param {string} type Type of response to send * @param {*} message Message to send * @private */ _respond(type, message) { this.send(message).catch(err => { err.message = `Error when sending ${type} response to master process: ${err.message}`; /** * Emitted when the client encounters an error. * @event Client#error * @param {Error} error The error encountered */ this.client.emit(Events.ERROR, err); }); } /** * Creates/gets the singleton of this class. * @param {Client} client The client to use * @param {ShardingManagerMode} mode Mode the shard was spawned with * @returns {ShardClientUtil} */ static singleton(client, mode) { if (!this._singleton) { this._singleton = new this(client, mode); } else { client.emit( Events.WARN, 'Multiple clients created in child process/worker; only the first will handle sharding helpers.', ); } return this._singleton; } } module.exports = ShardClientUtil;