detritus-client
Version:
A Typescript NodeJS library to interact with Discord's API, both Rest and Gateway.
151 lines (150 loc) • 6.11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClusterManager = void 0;
const path = require("path");
const detritus_client_rest_1 = require("detritus-client-rest");
const detritus_utils_1 = require("detritus-utils");
const bucket_1 = require("./bucket");
const process_1 = require("./cluster/process");
const basecollection_1 = require("./collections/basecollection");
const constants_1 = require("./constants");
const errors_1 = require("./errors");
const utils_1 = require("./utils");
class ClusterManager extends detritus_utils_1.EventSpewer {
constructor(file, token, options = {}) {
super();
this.buckets = new basecollection_1.BaseCollection();
this.maxConcurrency = 1;
this.processes = new basecollection_1.BaseCollection();
this.ran = false;
this.respawn = true;
this.restCache = new basecollection_1.BaseCollection({ expire: 5 * 60 * 1000 });
this.shardCount = 0;
this.shardEnd = -1;
this.shardStart = 0;
this.shardsPerCluster = 4;
this.file = file;
if (!options.isAbsolute) {
if (require.main) {
this.file = path.join(path.dirname(require.main.filename), this.file);
}
}
if (!token) {
throw new Error('Token is required for this library to work.');
}
this.token = token;
this.maxConcurrency = options.maxConcurrency || this.maxConcurrency;
this.respawn = (options.respawn || options.respawn === undefined);
this.rest = new detritus_client_rest_1.Client(token, { authType: constants_1.AuthTypes.BOT });
this.shardCount = +(options.shardCount || this.shardCount);
if (Array.isArray(options.shards)) {
if (options.shards.length !== 2) {
throw new Error('Shards need to be in the format of [shardStart, shardEnd]');
}
const [shardStart, shardEnd] = options.shards;
this.shardEnd = +shardEnd;
this.shardStart = +shardStart;
}
this.shardsPerCluster = +(options.shardsPerCluster || this.shardsPerCluster);
Object.defineProperties(this, {
ran: { configurable: true, writable: false },
rest: { enumerable: false, writable: false },
token: { enumerable: false, writable: false },
});
process.env.CLUSTER_MANAGER = String(true);
process.env.CLUSTER_TOKEN = this.token;
}
get clusterCount() {
return Math.ceil(this.shardCount / this.shardsPerCluster);
}
async run(options = {}) {
if (this.ran) {
return this;
}
options = Object.assign({
delay: constants_1.DEFAULT_SHARD_LAUNCH_DELAY,
}, options);
const delay = +options.delay;
let maxConcurrency = +(options.maxConcurrency || this.maxConcurrency);
let shardCount = +(options.shardCount || this.shardCount || 0);
let url = options.url || '';
if (!url || !shardCount || !maxConcurrency) {
const data = await this.rest.fetchGatewayBot();
maxConcurrency = data.session_start_limit.max_concurrency;
shardCount = shardCount || data.shards;
url = url || data.url;
}
if (!shardCount) {
throw new Error('Shard Count cannot be 0, pass in one via the options or the constructor.');
}
this.maxConcurrency = maxConcurrency;
this.shardCount = shardCount;
if (this.shardEnd === -1) {
this.shardEnd = shardCount - 1;
}
this.buckets.clear();
for (let ratelimitKey = 0; ratelimitKey < maxConcurrency; ratelimitKey++) {
const bucket = new bucket_1.Bucket(1, delay, true);
this.buckets.set(ratelimitKey, bucket);
}
// now use these buckets whenever we identify
let clusterId = 0;
for (let shardStart = this.shardStart; shardStart <= this.shardEnd; shardStart += this.shardsPerCluster) {
shardStart = Math.min(shardStart, this.shardEnd);
const shardEnd = Math.min(shardStart + this.shardsPerCluster - 1, this.shardEnd);
const clusterProcess = new process_1.ClusterProcess(this, {
clusterId,
env: {
GATEWAY_URL: url,
MAX_CONCURRENCY: String(maxConcurrency),
},
shardCount,
shardEnd,
shardStart,
});
this.processes.set(clusterId, clusterProcess);
this.emit(constants_1.ClientEvents.CLUSTER_PROCESS, { clusterProcess });
await clusterProcess.run();
clusterId++;
}
Object.defineProperty(this, 'ran', { value: true });
return this;
}
async broadcast(message) {
const promises = this.processes.map((clusterProcess) => {
return clusterProcess.send(message);
});
return Promise.all(promises);
}
async broadcastEvalRaw(code, nonce = utils_1.Snowflake.generate().id) {
const promises = this.processes.map((clusterProcess) => {
return clusterProcess.eval(code, nonce);
});
const results = await Promise.all(promises);
return results.filter((item) => item);
}
async broadcastEval(code, nonce = utils_1.Snowflake.generate().id) {
const results = await this.broadcastEvalRaw(code, nonce);
return results.map(([result, isError]) => {
if (isError) {
return new errors_1.ClusterIPCError(result);
}
return result;
});
}
getRatelimitKey(shardId) {
return shardId % this.maxConcurrency;
}
on(event, listener) {
super.on(event, listener);
return this;
}
once(event, listener) {
super.once(event, listener);
return this;
}
subscribe(event, listener) {
return super.subscribe(event, listener);
}
}
exports.ClusterManager = ClusterManager;