diff --git a/src/errors/Messages.js b/src/errors/Messages.js index f864d71eec9c..c2c7d9c92dc3 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -23,6 +23,7 @@ const Messages = { DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.', SHARDING_NO_SHARDS: 'No shards have been spawned.', SHARDING_IN_PROCESS: 'Shards are still being spawned.', + SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function', SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`, SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`, SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`, diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index d98aaa2f0136..6f840c714a33 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -348,7 +348,7 @@ class Shard extends EventEmitter { // Shard is requesting an eval broadcast if (message._sEval) { const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard }; - this.manager.broadcastEval(message._sEval, message._sEvalShard).then( + this.manager._performOnShards('eval', [message._sEval], message._sEvalShard).then( results => this.send({ ...resp, _result: results }), err => this.send({ ...resp, _error: Util.makePlainError(err) }), ); diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js index 044d8ab35818..b93142376817 100644 --- a/src/sharding/ShardClientUtil.js +++ b/src/sharding/ShardClientUtil.js @@ -127,29 +127,33 @@ class ShardClientUtil { /** * Evaluates a script or function on all shards, or a given shard, in the context of the {@link Client}s. - * @param {string|Function} script JavaScript to run on each shard - * @param {number} [shard] Shard to run script on, all if undefined + * @param {Function} script JavaScript to run on each shard + * @param {BroadcastEvalOptions} [options={}] The options for the broadcast * @returns {Promise<*>|Promise>} Results of the script execution * @example - * client.shard.broadcastEval('this.guilds.cache.size') + * client.shard.broadcastEval(client => client.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, shard) { + broadcastEval(script, options = {}) { return new Promise((resolve, reject) => { const parent = this.parentPort || process; - script = typeof script === 'function' ? `(${script})(this)` : script; + if (typeof script !== 'function') { + reject(new TypeError('SHARDING_INVALID_EVAL_BROADCAST')); + return; + } + script = `(${script})(this, ${JSON.stringify(options.context)})`; const listener = message => { - if (!message || message._sEval !== script || message._sEvalShard !== shard) return; + if (!message || message._sEval !== script || message._sEvalShard !== options.shard) 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, _sEvalShard: shard }).catch(err => { + this.send({ _sEval: script, _sEvalShard: options.shard }).catch(err => { parent.removeListener('message', listener); reject(err); }); diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index bd8452c1e2c0..cf43553ac551 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -226,14 +226,22 @@ class ShardingManager extends EventEmitter { return Promise.all(promises); } + /** + * Options for {@link ShardingManager#broadcastEval} and {@link ShardClientUtil#broadcastEval}. + * @typedef {Object} BroadcastEvalOptions + * @property {number} [shard] Shard to run script on, all if undefined + * @property {*} [context] The JSON-serializable values to call the script with + */ + /** * Evaluates a script on all shards, or a given shard, in the context of the {@link Client}s. - * @param {string} script JavaScript to run on each shard - * @param {number} [shard] Shard to run on, all if undefined + * @param {Function} script JavaScript to run on each shard + * @param {BroadcastEvalOptions} [options={}] The options for the broadcast * @returns {Promise<*>|Promise>} Results of the script execution */ - broadcastEval(script, shard) { - return this._performOnShards('eval', [script], shard); + broadcastEval(script, options = {}) { + if (typeof script !== 'function') return Promise.reject(new TypeError('SHARDING_INVALID_EVAL_BROADCAST')); + return this._performOnShards('eval', [`(${script})(this, ${JSON.stringify(options.context)})`], options.shard); } /** diff --git a/typings/index.d.ts b/typings/index.d.ts index 0a11c2b64ed2..5c9e3d5de217 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1695,10 +1695,8 @@ declare module 'discord.js' { public readonly ids: number[]; public mode: ShardingManagerMode; public parentPort: any | null; - public broadcastEval(script: string): Promise; - public broadcastEval(script: string, shard: number): Promise; - public broadcastEval(fn: (client: Client) => T): Promise; - public broadcastEval(fn: (client: Client) => T, shard: number): Promise; + public broadcastEval(fn: (client: Client, context: P) => T, { shard: undefined, context: P }?: BroadcastEvalOptions): Promise; + public broadcastEval(fn: (client: Client, context: P) => T, { shard: number, context: P }: BroadcastEvalOptions): Promise; public fetchClientValues(prop: string): Promise; public fetchClientValues(prop: string, shard: number): Promise; public respawnAll(options?: { shardDelay?: number; respawnDelay?: number; timeout?: number }): Promise; @@ -1721,8 +1719,8 @@ declare module 'discord.js' { public totalShards: number | 'auto'; public shardList: number[] | 'auto'; public broadcast(message: any): Promise; - public broadcastEval(script: string): Promise; - public broadcastEval(script: string, shard: number): Promise; + public broadcastEval(fn: (client: Client, context: P) => T, { shard: undefined, context: P }?: BroadcastEvalOptions): Promise; + public broadcastEval(fn: (client: Client, context: P) => T, { shard: number, context: P }: BroadcastEvalOptions): Promise; public createShard(id: number): Shard; public fetchClientValues(prop: string): Promise; public fetchClientValues(prop: string, shard: number): Promise; @@ -2729,6 +2727,11 @@ declare module 'discord.js' { | N | Readonly>; + interface BroadcastEvalOptions { + shard?: number; + context?: T; + } + type BufferResolvable = Buffer | string; interface ChannelCreationOverwrites {