@bakit/gateway 2.1.9 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cluster.cjs +25 -0
- package/dist/cluster.js +23 -0
- package/dist/index.cjs +632 -0
- package/dist/index.d.cts +201 -0
- package/dist/index.d.ts +170 -124
- package/dist/index.js +522 -389
- package/package.json +12 -6
- package/dist/services/worker.d.ts +0 -2
- package/dist/services/worker.js +0 -6
package/dist/index.js
CHANGED
|
@@ -1,486 +1,619 @@
|
|
|
1
|
-
import
|
|
1
|
+
import EventEmitter from 'events';
|
|
2
2
|
import { createInflate, constants } from 'zlib';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
3
|
+
import { TextDecoder } from 'util';
|
|
4
|
+
import { randomInt, randomUUID } from 'crypto';
|
|
5
|
+
import WebSocket from 'ws';
|
|
6
|
+
import { GatewayOpcodes, GatewayDispatchEvents, GatewayCloseCodes } from 'discord-api-types/v10';
|
|
6
7
|
import { fork } from 'child_process';
|
|
7
|
-
import '
|
|
8
|
+
import { dirname, resolve } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { isCommonJS, Collection, Queue } from '@bakit/utils';
|
|
11
|
+
import { REST } from '@bakit/rest';
|
|
8
12
|
|
|
9
|
-
// src/lib/
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (state !== ShardState.Idle && state !== ShardState.Disconnected) {
|
|
54
|
-
self.emit("error", new Error("Shard is already connected or connecting."));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (strategy === ShardStrategy.Shutdown)
|
|
58
|
-
return;
|
|
59
|
-
state = ShardState.Connecting;
|
|
60
|
-
let { gateway } = resolvedOptions, baseURL = isResumable() && resumeGatewayURL ? resumeGatewayURL : gateway.baseURL, url = new URL(baseURL);
|
|
61
|
-
url.searchParams.set("v", gateway.version.toString()), url.searchParams.set("encoding", "json"), url.searchParams.set("compress", "zlib-stream"), ws = new WebSocket(url.toString(), {
|
|
62
|
-
perMessageDeflate: false
|
|
63
|
-
}), inflater = createInflate({
|
|
64
|
-
flush: constants.Z_SYNC_FLUSH
|
|
65
|
-
}), inflater.on("data", onInflate), inflater.on("error", (err) => {
|
|
66
|
-
self.emit("error", err), ws?.terminate();
|
|
67
|
-
}), ws.on("message", onMessage), ws.on("close", onClose), ws.on("error", (err) => self.emit("error", err));
|
|
68
|
-
}
|
|
69
|
-
function cleanup() {
|
|
70
|
-
heartbeatInterval && (clearInterval(heartbeatInterval), heartbeatInterval = void 0), heartbeatTimeout && (clearTimeout(heartbeatTimeout), heartbeatTimeout = void 0), reconnectTimeout && (clearTimeout(reconnectTimeout), reconnectTimeout = void 0), inflater && (inflater.destroy(), inflater = void 0), ws && (ws.readyState !== WebSocket.CLOSED && ws.terminate(), ws.removeAllListeners(), ws = void 0), decompressBuffer = [], missedHeartbeats = 0, lastHeartbeatSent = -1, lastHeartbeatAcknowledged = -1;
|
|
71
|
-
}
|
|
72
|
-
function connect() {
|
|
73
|
-
return new Promise((resolve) => {
|
|
74
|
-
state !== ShardState.Idle && state !== ShardState.Disconnected || (strategy = ShardStrategy.Unknown, self.once("ready", () => resolve()), init());
|
|
13
|
+
// src/lib/Shard.ts
|
|
14
|
+
var MIN_HEARTBEAT_INTERVAL = 1e3, MAX_HEARTBEAT_INTERVAL = 6e4, SAFE_HEARTBEAT_INTERVAL = 45e3, ShardState = /* @__PURE__ */ ((ShardState2) => (ShardState2[ShardState2.Idle = 0] = "Idle", ShardState2[ShardState2.Connecting = 1] = "Connecting", ShardState2[ShardState2.Ready = 2] = "Ready", ShardState2[ShardState2.Resuming = 3] = "Resuming", ShardState2[ShardState2.Disconnecting = 4] = "Disconnecting", ShardState2[ShardState2.Disconnected = 5] = "Disconnected", ShardState2))(ShardState || {}), ShardStrategy = /* @__PURE__ */ ((ShardStrategy2) => (ShardStrategy2[ShardStrategy2.Resume = 0] = "Resume", ShardStrategy2[ShardStrategy2.Reconnect = 1] = "Reconnect", ShardStrategy2[ShardStrategy2.Shutdown = 2] = "Shutdown", ShardStrategy2))(ShardStrategy || {}), Shard = class extends EventEmitter {
|
|
15
|
+
constructor(id, options) {
|
|
16
|
+
super();
|
|
17
|
+
this.id = id;
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
#state = 0 /* Idle */;
|
|
21
|
+
#ws;
|
|
22
|
+
#inflater;
|
|
23
|
+
#textDecoder = new TextDecoder();
|
|
24
|
+
#decompressBuffer = [];
|
|
25
|
+
#sessionId;
|
|
26
|
+
#lastSequence;
|
|
27
|
+
#resumeGatewayURL;
|
|
28
|
+
#lastHeartbeatSent = -1;
|
|
29
|
+
#lastHeartbeatAck = -1;
|
|
30
|
+
#missedHeartbeats = 0;
|
|
31
|
+
#heartbeatInterval;
|
|
32
|
+
#heartbeatTimeout;
|
|
33
|
+
#reconnectTimeout;
|
|
34
|
+
#strategy;
|
|
35
|
+
get state() {
|
|
36
|
+
return this.#state;
|
|
37
|
+
}
|
|
38
|
+
get latency() {
|
|
39
|
+
return this.#lastHeartbeatSent === -1 || this.#lastHeartbeatAck === -1 ? -1 : this.#lastHeartbeatAck - this.#lastHeartbeatSent;
|
|
40
|
+
}
|
|
41
|
+
get resumable() {
|
|
42
|
+
let hasSessionId = this.#sessionId !== void 0, hasSequence = this.#lastSequence !== void 0;
|
|
43
|
+
return this.#strategy === 0 /* Resume */ && hasSequence && hasSessionId;
|
|
44
|
+
}
|
|
45
|
+
async connect() {
|
|
46
|
+
if (this.#state !== 0 /* Idle */ && this.#state !== 5 /* Disconnected */)
|
|
47
|
+
throw new Error("Shard already connecting or connected");
|
|
48
|
+
return new Promise((resolve2, reject) => {
|
|
49
|
+
let cleanup = () => {
|
|
50
|
+
this.off("error", onError), this.off("ready", onReady);
|
|
51
|
+
}, onReady = () => {
|
|
52
|
+
cleanup(), resolve2();
|
|
53
|
+
}, onError = (err) => {
|
|
54
|
+
cleanup(), reject(err);
|
|
55
|
+
};
|
|
56
|
+
this.once("ready", onReady), this.once("error", onError), this.#init();
|
|
75
57
|
});
|
|
76
58
|
}
|
|
77
|
-
|
|
78
|
-
return new Promise((
|
|
79
|
-
if (strategy =
|
|
80
|
-
|
|
59
|
+
disconnect(code) {
|
|
60
|
+
return new Promise((resolve2) => {
|
|
61
|
+
if (this.#state = 4 /* Disconnecting */, this.#strategy = 2 /* Shutdown */, !this.#ws) {
|
|
62
|
+
resolve2();
|
|
81
63
|
return;
|
|
82
64
|
}
|
|
83
|
-
ws.once("close", () => {
|
|
84
|
-
|
|
85
|
-
}), ws.close(code);
|
|
65
|
+
this.#ws.once("close", () => {
|
|
66
|
+
resolve2();
|
|
67
|
+
}), this.#ws.close(code);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
resume() {
|
|
71
|
+
this.resumable && (this.#state = 3 /* Resuming */, this.send({
|
|
72
|
+
op: GatewayOpcodes.Resume,
|
|
73
|
+
d: {
|
|
74
|
+
token: this.options.token,
|
|
75
|
+
session_id: this.#sessionId,
|
|
76
|
+
seq: this.#lastSequence
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
identify() {
|
|
81
|
+
this.send({
|
|
82
|
+
op: GatewayOpcodes.Identify,
|
|
83
|
+
d: {
|
|
84
|
+
token: this.options.token,
|
|
85
|
+
intents: Number(this.options.intents),
|
|
86
|
+
properties: {
|
|
87
|
+
os: process.platform,
|
|
88
|
+
browser: "bakit",
|
|
89
|
+
device: "bakit"
|
|
90
|
+
},
|
|
91
|
+
shard: [this.id, this.options.total]
|
|
92
|
+
}
|
|
86
93
|
});
|
|
87
94
|
}
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
send(payload) {
|
|
96
|
+
this.#ws?.readyState === WebSocket.OPEN && this.#ws.send(JSON.stringify(payload));
|
|
97
|
+
}
|
|
98
|
+
sendHeartbeat() {
|
|
99
|
+
if (this.#lastHeartbeatSent !== -1 && this.#lastHeartbeatAck < this.#lastHeartbeatSent ? this.#missedHeartbeats++ : this.#missedHeartbeats = 0, this.#missedHeartbeats >= 2) {
|
|
100
|
+
this.emit("debug", "Missed 2 heartbeats, reconnecting"), this.#ws?.terminate();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.send({
|
|
104
|
+
op: GatewayOpcodes.Heartbeat,
|
|
105
|
+
d: this.#lastSequence ?? null
|
|
106
|
+
}), this.#lastHeartbeatSent = Date.now();
|
|
107
|
+
}
|
|
108
|
+
#init() {
|
|
109
|
+
this.#state = 1 /* Connecting */, this.#strategy ??= 1 /* Reconnect */;
|
|
110
|
+
let url = new URL(
|
|
111
|
+
this.#strategy === 0 /* Resume */ && this.#resumeGatewayURL ? this.#resumeGatewayURL : this.options.gateway.baseURL
|
|
112
|
+
);
|
|
113
|
+
url.searchParams.set("v", String(this.options.gateway.version)), url.searchParams.set("encoding", "json"), url.searchParams.set("compress", "zlib-stream"), this.#ws = new WebSocket(url, { perMessageDeflate: false }), this.#inflater = createInflate({ flush: constants.Z_SYNC_FLUSH }), this.#inflater.on("data", (chunk) => this.#onInflate(chunk)), this.#inflater.on("error", (err) => {
|
|
114
|
+
this.emit("error", err), this.#ws?.terminate();
|
|
115
|
+
}), this.#ws.on("message", (data) => this.#onMessage(data)), this.#ws.on("close", (code) => this.#onClose(code)), this.#ws.on("error", (err) => this.emit("error", err));
|
|
116
|
+
}
|
|
117
|
+
#onMessage(data) {
|
|
118
|
+
if (!this.#inflater) {
|
|
90
119
|
try {
|
|
91
120
|
let text = data.toString(), payload = JSON.parse(text);
|
|
92
|
-
handlePayload(payload);
|
|
121
|
+
this.#handlePayload(payload);
|
|
93
122
|
} catch (error) {
|
|
94
|
-
|
|
123
|
+
this.emit("error", error);
|
|
95
124
|
}
|
|
96
125
|
return;
|
|
97
126
|
}
|
|
98
127
|
let buffer;
|
|
99
128
|
Buffer.isBuffer(data) ? buffer = data : Array.isArray(data) ? buffer = Buffer.concat(data) : data instanceof ArrayBuffer ? buffer = Buffer.from(data) : buffer = Buffer.from(String(data));
|
|
100
129
|
let hasSyncFlush = buffer.length >= 4 && buffer[buffer.length - 4] === 0 && buffer[buffer.length - 3] === 0 && buffer[buffer.length - 2] === 255 && buffer[buffer.length - 1] === 255;
|
|
101
|
-
inflater.write(buffer, (writeError) => {
|
|
130
|
+
this.#inflater.write(buffer, (writeError) => {
|
|
102
131
|
if (writeError) {
|
|
103
|
-
|
|
132
|
+
this.emit("error", writeError);
|
|
104
133
|
return;
|
|
105
134
|
}
|
|
106
|
-
hasSyncFlush && inflater?.flush(constants.Z_SYNC_FLUSH);
|
|
135
|
+
hasSyncFlush && this.#inflater?.flush(constants.Z_SYNC_FLUSH);
|
|
107
136
|
});
|
|
108
137
|
}
|
|
109
|
-
|
|
110
|
-
decompressBuffer.push(chunk);
|
|
138
|
+
#onInflate(chunk) {
|
|
139
|
+
this.#decompressBuffer.push(chunk);
|
|
140
|
+
let fullBuffer = Buffer.concat(this.#decompressBuffer);
|
|
111
141
|
try {
|
|
112
|
-
let
|
|
113
|
-
handlePayload(payload), decompressBuffer = [];
|
|
142
|
+
let text = this.#textDecoder.decode(fullBuffer), payload = JSON.parse(text);
|
|
143
|
+
this.#handlePayload(payload), this.#decompressBuffer = [];
|
|
114
144
|
} catch (error) {
|
|
115
145
|
if (error instanceof SyntaxError) {
|
|
116
|
-
let
|
|
146
|
+
let text = this.#textDecoder.decode(fullBuffer);
|
|
117
147
|
if (text.includes("{") && !isValidJSON(text))
|
|
118
148
|
return;
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
function isValidJSON(str) {
|
|
124
|
-
try {
|
|
125
|
-
return JSON.parse(str), !0;
|
|
126
|
-
} catch {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function onClose(code) {
|
|
131
|
-
if (cleanup(), state = ShardState.Disconnected, self.emit("disconnect", code), strategy === ShardStrategy.Shutdown) {
|
|
132
|
-
switch (code) {
|
|
133
|
-
case GatewayCloseCodes.AuthenticationFailed:
|
|
134
|
-
self.emit("error", new Error("Invalid token provided"));
|
|
135
|
-
break;
|
|
136
|
-
case GatewayCloseCodes.InvalidIntents:
|
|
137
|
-
self.emit("error", new Error("Invalid intents provided"));
|
|
138
|
-
break;
|
|
139
|
-
case GatewayCloseCodes.DisallowedIntents:
|
|
140
|
-
self.emit("error", new Error("Disallowed intents provided"));
|
|
141
|
-
break;
|
|
149
|
+
this.emit("error", error), this.#decompressBuffer = [];
|
|
142
150
|
}
|
|
143
|
-
return;
|
|
144
151
|
}
|
|
145
|
-
strategy === ShardStrategy.Unknown && (strategy = getReconnectStrategy(code)), (strategy === ShardStrategy.Reconnect || strategy === ShardStrategy.Resume) && scheduleReconnect();
|
|
146
152
|
}
|
|
147
|
-
|
|
148
|
-
switch (
|
|
153
|
+
#handlePayload(payload) {
|
|
154
|
+
switch (this.emit("raw", payload), payload.op) {
|
|
149
155
|
case GatewayOpcodes.Dispatch: {
|
|
150
|
-
handleDispatch(payload);
|
|
156
|
+
this.#handleDispatch(payload);
|
|
151
157
|
break;
|
|
152
158
|
}
|
|
153
159
|
case GatewayOpcodes.Hello: {
|
|
154
|
-
startHeartbeat(payload.d.heartbeat_interval),
|
|
160
|
+
this.#startHeartbeat(payload.d.heartbeat_interval), this.resumable ? this.resume() : this.emit("needIdentify");
|
|
155
161
|
break;
|
|
156
162
|
}
|
|
157
163
|
case GatewayOpcodes.Heartbeat: {
|
|
158
|
-
sendHeartbeat();
|
|
164
|
+
this.sendHeartbeat();
|
|
159
165
|
break;
|
|
160
166
|
}
|
|
161
167
|
case GatewayOpcodes.HeartbeatAck: {
|
|
162
|
-
|
|
168
|
+
this.#lastHeartbeatAck = Date.now();
|
|
163
169
|
break;
|
|
164
170
|
}
|
|
165
171
|
case GatewayOpcodes.InvalidSession: {
|
|
166
|
-
payload.d ? strategy =
|
|
172
|
+
payload.d ? this.#strategy = 0 /* Resume */ : (this.#strategy = 1 /* Reconnect */, this.#sessionId = void 0, this.#lastSequence = void 0, this.#resumeGatewayURL = void 0), this.emit("debug", `Invalid session (resumable=${this.resumable})`), this.#ws?.terminate();
|
|
167
173
|
break;
|
|
168
174
|
}
|
|
169
175
|
case GatewayOpcodes.Reconnect: {
|
|
170
|
-
strategy =
|
|
176
|
+
this.#strategy = 0 /* Resume */, this.emit("debug", "Reconnecting to gateway"), this.#ws?.terminate();
|
|
171
177
|
break;
|
|
172
178
|
}
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
|
-
|
|
176
|
-
switch (lastSequence = payload.s,
|
|
181
|
+
#handleDispatch(payload) {
|
|
182
|
+
switch (this.#lastSequence = payload.s, this.emit("dispatch", payload), payload.t) {
|
|
177
183
|
case GatewayDispatchEvents.Ready: {
|
|
178
184
|
let { d: data } = payload;
|
|
179
|
-
state =
|
|
185
|
+
this.#state = 2 /* Ready */, this.#sessionId = data.session_id, this.#resumeGatewayURL = data.resume_gateway_url, this.emit("ready", data);
|
|
180
186
|
break;
|
|
181
187
|
}
|
|
182
188
|
case GatewayDispatchEvents.Resumed: {
|
|
183
|
-
state =
|
|
189
|
+
this.#state = 2 /* Ready */, this.#strategy = void 0, this.emit("resume");
|
|
184
190
|
break;
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
194
|
+
#onClose(code) {
|
|
195
|
+
if (this.#cleanup(), this.#state = 5 /* Disconnected */, this.emit("disconnect", code), this.#strategy === 2 /* Shutdown */) {
|
|
196
|
+
switch (code) {
|
|
197
|
+
case GatewayCloseCodes.AuthenticationFailed:
|
|
198
|
+
this.emit("error", new Error("Invalid token provided"));
|
|
199
|
+
break;
|
|
200
|
+
case GatewayCloseCodes.InvalidIntents:
|
|
201
|
+
this.emit("error", new Error("Invalid intents provided"));
|
|
202
|
+
break;
|
|
203
|
+
case GatewayCloseCodes.DisallowedIntents:
|
|
204
|
+
this.emit("error", new Error("Disallowed intents provided"));
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
} else this.#strategy || (this.#strategy = this.#getStrategy(code));
|
|
209
|
+
(this.#strategy === 1 /* Reconnect */ || this.#strategy === 0 /* Resume */) && this.#scheduleReconnect();
|
|
191
210
|
}
|
|
192
|
-
|
|
211
|
+
#getStrategy(code) {
|
|
193
212
|
switch (code) {
|
|
194
213
|
case GatewayCloseCodes.AuthenticationFailed:
|
|
195
214
|
case GatewayCloseCodes.InvalidIntents:
|
|
196
215
|
case GatewayCloseCodes.DisallowedIntents:
|
|
197
|
-
return
|
|
216
|
+
return 2 /* Shutdown */;
|
|
198
217
|
case GatewayCloseCodes.InvalidSeq:
|
|
199
218
|
case GatewayCloseCodes.SessionTimedOut:
|
|
200
|
-
return
|
|
219
|
+
return 1 /* Reconnect */;
|
|
201
220
|
default:
|
|
202
|
-
return
|
|
221
|
+
return 0 /* Resume */;
|
|
203
222
|
}
|
|
204
223
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
#scheduleReconnect(delay = 1e3) {
|
|
225
|
+
this.#reconnectTimeout || (this.#reconnectTimeout = setTimeout(() => {
|
|
226
|
+
this.#reconnectTimeout = void 0, this.#state = 0 /* Idle */, this.#init();
|
|
227
|
+
}, delay));
|
|
228
|
+
}
|
|
229
|
+
#startHeartbeat(interval) {
|
|
230
|
+
this.#heartbeatInterval && (clearInterval(this.#heartbeatInterval), this.#heartbeatInterval = void 0), (interval < MIN_HEARTBEAT_INTERVAL || interval > MAX_HEARTBEAT_INTERVAL) && (interval = SAFE_HEARTBEAT_INTERVAL);
|
|
231
|
+
let jitter = randomInt(0, 10) / 100, firstDelay = Math.floor(interval * jitter);
|
|
232
|
+
this.emit("debug", `Starting heartbeat (interval=${interval}ms, jitter=${firstDelay}ms)`), this.#heartbeatTimeout = setTimeout(() => {
|
|
233
|
+
this.sendHeartbeat(), this.#heartbeatInterval = setInterval(() => this.sendHeartbeat(), interval);
|
|
234
|
+
}, firstDelay);
|
|
235
|
+
}
|
|
236
|
+
#cleanup() {
|
|
237
|
+
clearTimeout(this.#reconnectTimeout), clearInterval(this.#heartbeatInterval), clearTimeout(this.#heartbeatTimeout), this.#inflater?.destroy(), this.#inflater = void 0, this.#ws?.removeAllListeners(), this.#ws = void 0, this.#decompressBuffer = [], this.#missedHeartbeats = 0;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
function isValidJSON(str) {
|
|
241
|
+
try {
|
|
242
|
+
return JSON.parse(str), !0;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
var EVAL_TIMEOUT = 3e4, __filename$1 = fileURLToPath(import.meta.url), __dirname$1 = dirname(__filename$1);
|
|
248
|
+
function isDispatchPayload(payload) {
|
|
249
|
+
return payload.op === "dispatch";
|
|
250
|
+
}
|
|
251
|
+
function isIdentifyPayload(payload) {
|
|
252
|
+
return payload.op === "identify";
|
|
253
|
+
}
|
|
254
|
+
function isSendPayload(payload) {
|
|
255
|
+
return payload.op === "send";
|
|
256
|
+
}
|
|
257
|
+
function isEvalRequestPayload(payload) {
|
|
258
|
+
return payload.op === "eval";
|
|
259
|
+
}
|
|
260
|
+
function isEvalResponsePayload(payload) {
|
|
261
|
+
return payload.op === "evalResponse";
|
|
262
|
+
}
|
|
263
|
+
var ClusterProcess = class _ClusterProcess extends EventEmitter {
|
|
264
|
+
constructor(manager, id, options = {}) {
|
|
265
|
+
super();
|
|
266
|
+
this.manager = manager;
|
|
267
|
+
this.id = id;
|
|
268
|
+
this.setMaxListeners(0);
|
|
269
|
+
let entry = resolve(__dirname$1, isCommonJS() ? "cluster.cjs" : "cluster.js");
|
|
270
|
+
this.process = fork(entry, {
|
|
271
|
+
env: options.env,
|
|
272
|
+
execArgv: options.execArgv,
|
|
273
|
+
stdio: ["inherit", "inherit", "inherit", "ipc"]
|
|
274
|
+
}), this.#bindProcessEvents();
|
|
275
|
+
}
|
|
276
|
+
process;
|
|
277
|
+
#pendingEvals = /* @__PURE__ */ new Map();
|
|
278
|
+
get killed() {
|
|
279
|
+
return this.process.killed || !this.process.connected;
|
|
280
|
+
}
|
|
281
|
+
kill(signal = "SIGTERM") {
|
|
282
|
+
if (!this.killed) {
|
|
283
|
+
for (let [nonce, pending] of this.#pendingEvals)
|
|
284
|
+
clearTimeout(pending.timeout), pending.reject(new Error(`Process killed before eval completed (nonce: ${nonce})`));
|
|
285
|
+
this.#pendingEvals.clear(), this.process.kill(signal);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async eval(fn, ctx) {
|
|
289
|
+
let nonce = randomUUID();
|
|
290
|
+
return new Promise((resolve2, reject) => {
|
|
291
|
+
let timeoutId = setTimeout(() => {
|
|
292
|
+
this.#pendingEvals.delete(nonce), reject(new Error(`Eval timed out after ${EVAL_TIMEOUT}ms`));
|
|
293
|
+
}, EVAL_TIMEOUT);
|
|
294
|
+
this.#pendingEvals.set(nonce, {
|
|
295
|
+
resolve: resolve2,
|
|
296
|
+
reject,
|
|
297
|
+
timeout: timeoutId
|
|
298
|
+
});
|
|
299
|
+
let context;
|
|
300
|
+
try {
|
|
301
|
+
context = JSON.stringify(ctx ?? null);
|
|
302
|
+
} catch {
|
|
303
|
+
reject(new Error("Eval context is not serializable"));
|
|
304
|
+
return;
|
|
217
305
|
}
|
|
306
|
+
let script = `(${fn.toString()})(cluster, ${context})`;
|
|
307
|
+
this.sendIPC({
|
|
308
|
+
op: "eval",
|
|
309
|
+
d: { nonce, script }
|
|
310
|
+
});
|
|
218
311
|
});
|
|
219
312
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
session_id: sessionId,
|
|
226
|
-
seq: lastSequence
|
|
227
|
-
}
|
|
313
|
+
send(idOrPayload, payload) {
|
|
314
|
+
let hasShardId = typeof idOrPayload == "number" && payload !== void 0, shardId = hasShardId ? idOrPayload : void 0, data = hasShardId ? payload : idOrPayload;
|
|
315
|
+
this.sendIPC({
|
|
316
|
+
op: "send",
|
|
317
|
+
d: { shardId, data }
|
|
228
318
|
});
|
|
229
319
|
}
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
function startHeartbeat(interval) {
|
|
241
|
-
heartbeatInterval && (clearInterval(heartbeatInterval), heartbeatInterval = void 0);
|
|
242
|
-
let jitter = Math.random(), firstDelay = Math.floor(interval * jitter);
|
|
243
|
-
self.emit("debug", `Starting heartbeat (interval=${interval}ms, jitter=${firstDelay}ms)`), heartbeatTimeout = setTimeout(() => {
|
|
244
|
-
sendHeartbeat(), heartbeatInterval = setInterval(sendHeartbeat, interval);
|
|
245
|
-
}, firstDelay);
|
|
320
|
+
sendIPC(message) {
|
|
321
|
+
if (!(!this.process.connected || this.process.killed))
|
|
322
|
+
try {
|
|
323
|
+
this.process.send(message, void 0, void 0, (err) => {
|
|
324
|
+
err && this.emit("error", err);
|
|
325
|
+
});
|
|
326
|
+
} catch (err) {
|
|
327
|
+
this.emit("error", err);
|
|
328
|
+
}
|
|
246
329
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
330
|
+
identifyShard(id) {
|
|
331
|
+
this.sendIPC({
|
|
332
|
+
op: "identify",
|
|
333
|
+
d: id
|
|
334
|
+
});
|
|
251
335
|
}
|
|
252
|
-
|
|
253
|
-
|
|
336
|
+
#bindProcessEvents() {
|
|
337
|
+
this.process.on("message", (message) => this.#handleIPC(message)), this.process.on("error", (err) => this.emit("error", err)), this.process.on("disconnect", () => this.emit("debug", "Process disconnected")), this.process.on("exit", (code) => {
|
|
338
|
+
for (let [nonce, pending] of this.#pendingEvals)
|
|
339
|
+
clearTimeout(pending.timeout), pending.reject(new Error(`Process exited (code: ${code}) before eval completed (nonce: ${nonce})`));
|
|
340
|
+
this.#pendingEvals.clear();
|
|
341
|
+
});
|
|
254
342
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
function createWorker(options) {
|
|
265
|
-
let shards = new Collection(), state = GatewayWorkerState.Idle, readyShards = /* @__PURE__ */ new Set(), self = attachEventBus({
|
|
266
|
-
get id() {
|
|
267
|
-
return options.id;
|
|
268
|
-
},
|
|
269
|
-
get shards() {
|
|
270
|
-
return shards;
|
|
271
|
-
},
|
|
272
|
-
get shardIds() {
|
|
273
|
-
return [...options.shards];
|
|
274
|
-
},
|
|
275
|
-
get latency() {
|
|
276
|
-
let count = 0, sumLatency = shards.reduce((acc, shard) => shard.latency === -1 ? acc : (count++, acc + shard.latency), 0);
|
|
277
|
-
return count === 0 ? -1 : sumLatency / count;
|
|
278
|
-
},
|
|
279
|
-
get state() {
|
|
280
|
-
return state;
|
|
281
|
-
},
|
|
282
|
-
start,
|
|
283
|
-
stop,
|
|
284
|
-
broadcast
|
|
285
|
-
});
|
|
286
|
-
async function start() {
|
|
287
|
-
if (!(state !== GatewayWorkerState.Idle && state !== GatewayWorkerState.Stopped)) {
|
|
288
|
-
state = GatewayWorkerState.Starting;
|
|
289
|
-
for (let id of options.shards) {
|
|
290
|
-
let shard = createShard({
|
|
291
|
-
id,
|
|
292
|
-
token: options.token,
|
|
293
|
-
intents: options.intents,
|
|
294
|
-
total: options.total,
|
|
295
|
-
gateway: {
|
|
296
|
-
baseURL: options.gatewayURL,
|
|
297
|
-
version: 10
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
shards.set(id, shard), shard.on("ready", (data) => {
|
|
301
|
-
if (readyShards.add(id), self.emit("shardReady", id, data), state !== GatewayWorkerState.Ready && readyShards.size === options.shards.length) {
|
|
302
|
-
let wasDegraded = state === GatewayWorkerState.Degraded;
|
|
303
|
-
state = GatewayWorkerState.Ready, self.emit(wasDegraded ? "resume" : "ready");
|
|
304
|
-
}
|
|
305
|
-
}), shard.on("disconnect", (code) => {
|
|
306
|
-
readyShards.delete(id), self.emit("shardDisconnect", id, code), state !== GatewayWorkerState.Starting && state !== GatewayWorkerState.Degraded && readyShards.size < options.shards.length && (state = GatewayWorkerState.Degraded, self.emit("degrade", readyShards.size, options.shards.length));
|
|
307
|
-
}), shard.on("raw", (payload) => self.emit("shardRaw", id, payload)), shard.on("dispatch", (payload) => self.emit("shardDispatch", id, payload)), shard.on("error", (err) => self.emit("error", err)), shard.on("debug", (msg) => self.emit("debug", `[Shard ${id}] ${msg}`)), shard.on("requestIdentify", () => self.emit("shardRequestIdentify", id)), shard.connect();
|
|
343
|
+
#handleIPC(message) {
|
|
344
|
+
if (this.#isValidPayload(message)) {
|
|
345
|
+
if (isDispatchPayload(message)) {
|
|
346
|
+
this.emit(message.t, ...message.d);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (isEvalResponsePayload(message)) {
|
|
350
|
+
this.#handleEvalResponse(message);
|
|
351
|
+
return;
|
|
308
352
|
}
|
|
309
353
|
}
|
|
310
354
|
}
|
|
311
|
-
|
|
312
|
-
|
|
355
|
+
#handleEvalResponse(payload) {
|
|
356
|
+
let pending = this.#pendingEvals.get(payload.d.nonce);
|
|
357
|
+
if (!pending) {
|
|
358
|
+
this.emit("debug", `Received eval response for unknown nonce: ${payload.d.nonce}`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (pending.timeout && clearTimeout(pending.timeout), this.#pendingEvals.delete(payload.d.nonce), payload.d.success)
|
|
362
|
+
pending.resolve({
|
|
363
|
+
success: true,
|
|
364
|
+
data: payload.d.result,
|
|
365
|
+
cluster: this
|
|
366
|
+
});
|
|
367
|
+
else {
|
|
368
|
+
let error = new Error(payload.d.error ?? "Unknown eval error");
|
|
369
|
+
pending.resolve({
|
|
370
|
+
success: false,
|
|
371
|
+
error,
|
|
372
|
+
cluster: this
|
|
373
|
+
});
|
|
374
|
+
}
|
|
313
375
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
376
|
+
#isValidPayload(message) {
|
|
377
|
+
if (typeof message != "object" || message === null)
|
|
378
|
+
return false;
|
|
379
|
+
let payload = message;
|
|
380
|
+
return payload.op === "dispatch" || payload.op === "identify" || payload.op === "send" || payload.op === "eval" || payload.op === "evalResponse";
|
|
317
381
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
break;
|
|
382
|
+
static bindProcess(cluster) {
|
|
383
|
+
let superEmit = cluster.emit.bind(cluster), safeSend = (message) => {
|
|
384
|
+
process.connected && process.send?.(message, void 0, void 0, (err) => {
|
|
385
|
+
err && cluster.emit("error", err);
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
cluster.emit = function(eventName, ...args) {
|
|
389
|
+
let result = superEmit(eventName, ...args);
|
|
390
|
+
return safeSend({
|
|
391
|
+
op: "dispatch",
|
|
392
|
+
t: eventName,
|
|
393
|
+
d: args
|
|
394
|
+
}), result;
|
|
395
|
+
};
|
|
396
|
+
let messageHandler = async (message) => {
|
|
397
|
+
if (isIdentifyPayload(message)) {
|
|
398
|
+
cluster.shards.get(message.d)?.identify();
|
|
399
|
+
return;
|
|
337
400
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
401
|
+
if (isSendPayload(message)) {
|
|
402
|
+
message.d.shardId !== void 0 ? cluster.send(message.d.shardId, message.d.data) : cluster.send(message.d.data);
|
|
403
|
+
return;
|
|
341
404
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
405
|
+
if (isEvalRequestPayload(message)) {
|
|
406
|
+
await _ClusterProcess.#handleEvalRequest(cluster, message, safeSend);
|
|
407
|
+
return;
|
|
345
408
|
}
|
|
409
|
+
};
|
|
410
|
+
process.on("message", messageHandler);
|
|
411
|
+
}
|
|
412
|
+
static async #handleEvalRequest(cluster, payload, safeSend) {
|
|
413
|
+
let { nonce, script } = payload.d, executeEval = async () => await new Function("cluster", `return ${script}`)(cluster), timeoutId;
|
|
414
|
+
try {
|
|
415
|
+
let evalPromise = executeEval(), timeoutPromise = new Promise((_, reject) => {
|
|
416
|
+
timeoutId = setTimeout(() => {
|
|
417
|
+
reject(new Error(`Eval execution timed out after ${EVAL_TIMEOUT}ms`));
|
|
418
|
+
}, EVAL_TIMEOUT);
|
|
419
|
+
}), result = await Promise.race([evalPromise, timeoutPromise]);
|
|
420
|
+
timeoutId && clearTimeout(timeoutId), safeSend({
|
|
421
|
+
op: "evalResponse",
|
|
422
|
+
d: {
|
|
423
|
+
nonce,
|
|
424
|
+
success: !0,
|
|
425
|
+
result
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
} catch (err) {
|
|
429
|
+
timeoutId && clearTimeout(timeoutId), safeSend({
|
|
430
|
+
op: "evalResponse",
|
|
431
|
+
d: {
|
|
432
|
+
nonce,
|
|
433
|
+
success: false,
|
|
434
|
+
result: void 0,
|
|
435
|
+
error: err instanceof Error ? err.message : String(err)
|
|
436
|
+
}
|
|
437
|
+
});
|
|
346
438
|
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
function getWorkerOptions() {
|
|
350
|
-
let { WORKER_DATA } = process.env;
|
|
351
|
-
if (!WORKER_DATA)
|
|
352
|
-
throw new Error("WORKER_DATA is not set");
|
|
353
|
-
return JSON.parse(WORKER_DATA);
|
|
354
|
-
}
|
|
355
|
-
var DEFAULT_WORKER_PATH = fileURLToPath(new URL("./services/worker.js", import.meta.url)), DEFAULT_GATEWAY_MANAGER_OPTIONS = {
|
|
356
|
-
gatewayURL: "wss://gateway.discord.gg",
|
|
357
|
-
totalShards: "auto",
|
|
358
|
-
shardsPerWorker: 5
|
|
439
|
+
}
|
|
359
440
|
};
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
`Resets in: ${Math.ceil(limit.reset_after / 1e3)}s`
|
|
382
|
-
].join(" ")
|
|
383
|
-
);
|
|
384
|
-
self.emit("error", error);
|
|
385
|
-
return;
|
|
441
|
+
var Cluster = class extends EventEmitter {
|
|
442
|
+
constructor(id, options) {
|
|
443
|
+
super();
|
|
444
|
+
this.id = id;
|
|
445
|
+
this.options = options;
|
|
446
|
+
}
|
|
447
|
+
shards = new Collection();
|
|
448
|
+
#readyCount = 0;
|
|
449
|
+
#starting = false;
|
|
450
|
+
get size() {
|
|
451
|
+
return this.shards.size;
|
|
452
|
+
}
|
|
453
|
+
get ready() {
|
|
454
|
+
return this.#readyCount === this.options.shards.length;
|
|
455
|
+
}
|
|
456
|
+
async spawn() {
|
|
457
|
+
if (!this.#starting) {
|
|
458
|
+
this.#starting = true, this.emit("debug", `Spawning ${this.options.shards.length} shards...`);
|
|
459
|
+
for (let i of this.options.shards)
|
|
460
|
+
await this.#spawnShard(i);
|
|
461
|
+
this.emit("debug", "All shards spawned");
|
|
386
462
|
}
|
|
387
|
-
|
|
463
|
+
}
|
|
464
|
+
async shutdown(code = 1e3) {
|
|
465
|
+
this.emit("debug", "Shutting down cluster...");
|
|
466
|
+
let tasks = [];
|
|
467
|
+
for (let shard of this.shards.values())
|
|
468
|
+
tasks.push(shard.disconnect(code));
|
|
469
|
+
await Promise.allSettled(tasks), this.emit("debug", "Cluster shutdown complete");
|
|
470
|
+
}
|
|
471
|
+
broadcast(fn) {
|
|
472
|
+
for (let shard of this.shards.values())
|
|
473
|
+
fn(shard);
|
|
474
|
+
}
|
|
475
|
+
send(idOrPayload, payload) {
|
|
476
|
+
let hasId = typeof idOrPayload == "number" && payload !== void 0, shardId = hasId ? idOrPayload : void 0, data = hasId ? payload : idOrPayload;
|
|
477
|
+
shardId !== void 0 ? this.shards.get(shardId)?.send(data) : this.broadcast((shard) => shard.send(data));
|
|
478
|
+
}
|
|
479
|
+
async #spawnShard(id) {
|
|
480
|
+
let shard = new Shard(id, {
|
|
481
|
+
token: this.options.token,
|
|
482
|
+
intents: this.options.intents,
|
|
483
|
+
total: this.options.total,
|
|
484
|
+
gateway: this.options.gateway
|
|
485
|
+
});
|
|
486
|
+
this.#bindShardEvents(shard), this.shards.set(id, shard), this.emit("shardAdd", id), await shard.connect();
|
|
487
|
+
}
|
|
488
|
+
#bindShardEvents(shard) {
|
|
489
|
+
let id = shard.id;
|
|
490
|
+
shard.on("ready", () => {
|
|
491
|
+
this.#readyCount++, this.emit("debug", `Shard ${id} ready`), this.emit("shardReady", id), this.ready && this.emit("ready");
|
|
492
|
+
}), shard.on("resume", () => {
|
|
493
|
+
this.emit("debug", `Shard ${id} resumed`), this.emit("shardResume", id);
|
|
494
|
+
}), shard.on("disconnect", (code) => {
|
|
495
|
+
this.emit("debug", `Shard ${id} disconnected (${code})`), this.emit("shardDisconnect", id, code);
|
|
496
|
+
}), shard.on("error", (err) => {
|
|
497
|
+
this.emit("debug", `Shard ${id} error: ${err.message}`), this.emit("shardError", id, err);
|
|
498
|
+
}), shard.on("raw", (payload) => {
|
|
499
|
+
this.emit("raw", id, payload);
|
|
500
|
+
}), shard.on("dispatch", (payload) => {
|
|
501
|
+
this.emit("dispatch", id, payload);
|
|
502
|
+
}), shard.on("needIdentify", () => {
|
|
503
|
+
this.emit("needIdentify", id);
|
|
504
|
+
}), shard.on("debug", (msg) => {
|
|
505
|
+
this.emit("debug", `[Shard ${id}] ${msg}`);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
var ShardingManager = class extends EventEmitter {
|
|
510
|
+
clusters = new Collection();
|
|
511
|
+
options;
|
|
512
|
+
rest;
|
|
513
|
+
#gatewayInfo;
|
|
514
|
+
#totalShards = 0;
|
|
515
|
+
#readyCount = 0;
|
|
516
|
+
#identifyQueue;
|
|
517
|
+
constructor(options, rest) {
|
|
518
|
+
super(), this.setMaxListeners(0), this.options = {
|
|
519
|
+
shardsPerCluster: 5,
|
|
520
|
+
totalShards: options.totalShards ?? "auto",
|
|
521
|
+
...options
|
|
522
|
+
}, rest || (rest = new REST({ token: this.options.token })), this.rest = rest;
|
|
523
|
+
}
|
|
524
|
+
get totalClusters() {
|
|
525
|
+
let { shardsPerCluster } = this.options;
|
|
526
|
+
return shardsPerCluster <= 0 ? 0 : Math.ceil(this.totalShards / shardsPerCluster);
|
|
527
|
+
}
|
|
528
|
+
get totalShards() {
|
|
529
|
+
return this.#totalShards;
|
|
530
|
+
}
|
|
531
|
+
get ready() {
|
|
532
|
+
return this.#readyCount === this.totalClusters;
|
|
533
|
+
}
|
|
534
|
+
async spawn() {
|
|
535
|
+
this.#gatewayInfo = await this.rest.get("/gateway/bot");
|
|
536
|
+
let { session_start_limit: limit } = this.#gatewayInfo;
|
|
537
|
+
this.#totalShards = typeof this.options.totalShards == "number" ? this.options.totalShards : this.#gatewayInfo.shards, this.#identifyQueue = new Queue({
|
|
388
538
|
concurrency: limit.max_concurrency,
|
|
389
539
|
intervalCap: limit.max_concurrency,
|
|
390
540
|
interval: 5e3
|
|
391
541
|
});
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
shards,
|
|
399
|
-
gatewayURL: opts.gatewayURL ?? "wss://gateway.discord.gg"
|
|
400
|
-
};
|
|
401
|
-
workers.set(i, spawnWorker(workerOptions));
|
|
402
|
-
}
|
|
542
|
+
let { totalShards, totalClusters } = this;
|
|
543
|
+
this.emit("debug", `Spawning ${totalClusters} clusters (${totalShards} total shards)...`);
|
|
544
|
+
let promises = [];
|
|
545
|
+
for (let i = 0; i < totalClusters; i++)
|
|
546
|
+
promises.push(this.#spawnCluster(i));
|
|
547
|
+
await Promise.all(promises), this.emit("debug", "All clusters spawned");
|
|
403
548
|
}
|
|
404
|
-
async
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
env: {
|
|
415
|
-
WORKER_DATA: JSON.stringify(payload)
|
|
416
|
-
},
|
|
417
|
-
stdio: "inherit"
|
|
418
|
-
});
|
|
419
|
-
return child.on("message", (msg) => {
|
|
420
|
-
switch (msg.type) {
|
|
421
|
-
case "shardRaw": {
|
|
422
|
-
self.emit("shardRaw", payload.id, msg.shardId, msg.payload);
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
case "shardDispatch": {
|
|
426
|
-
self.emit("shardDispatch", payload.id, msg.shardId, msg.payload);
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
case "shardReady": {
|
|
430
|
-
self.emit("shardReady", payload.id, msg.shardId, msg.payload);
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
case "shardDisconnect": {
|
|
434
|
-
self.emit("shardDisconnect", payload.id, msg.shardId, msg.code);
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
case "shardRequestIdentify": {
|
|
438
|
-
identifyQueue?.add(async () => {
|
|
439
|
-
child.connected && child.send({
|
|
440
|
-
type: "identifyShard",
|
|
441
|
-
shardId: msg.shardId
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
break;
|
|
445
|
-
}
|
|
446
|
-
case "ready": {
|
|
447
|
-
self.emit("workerReady", payload.id);
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
case "stop": {
|
|
451
|
-
self.emit("workerStop", payload.id);
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
case "workerError": {
|
|
455
|
-
self.emit("error", new Error(`[worker ${payload.id}] ${msg.error.message}`));
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}), child;
|
|
549
|
+
async kill(signal = "SIGTERM") {
|
|
550
|
+
this.emit("debug", "Shutting down all clusters...");
|
|
551
|
+
let tasks = [];
|
|
552
|
+
for (let cluster of this.clusters.values())
|
|
553
|
+
tasks.push(
|
|
554
|
+
new Promise((resolve2) => {
|
|
555
|
+
cluster.process.once("exit", () => resolve2()), cluster.kill(signal);
|
|
556
|
+
})
|
|
557
|
+
);
|
|
558
|
+
await Promise.all(tasks), this.emit("debug", "All clusters shut down");
|
|
460
559
|
}
|
|
461
|
-
|
|
462
|
-
for (let
|
|
463
|
-
|
|
464
|
-
type: "broadcast",
|
|
465
|
-
payload
|
|
466
|
-
});
|
|
560
|
+
broadcast(payload) {
|
|
561
|
+
for (let cluster of this.clusters.values())
|
|
562
|
+
cluster.send(payload);
|
|
467
563
|
}
|
|
468
|
-
|
|
469
|
-
let
|
|
470
|
-
return
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
564
|
+
async broadcastEval(fn) {
|
|
565
|
+
let promises = this.clusters.map((cluster) => cluster.eval(fn));
|
|
566
|
+
return Promise.all(promises);
|
|
567
|
+
}
|
|
568
|
+
requestIdentify(cluster, shardId) {
|
|
569
|
+
this.#identifyQueue?.add(() => cluster.identifyShard(shardId));
|
|
570
|
+
}
|
|
571
|
+
#getShardIdsForCluster(clusterId) {
|
|
572
|
+
let start = clusterId * this.options.shardsPerCluster, end = Math.min(start + this.options.shardsPerCluster, this.totalShards);
|
|
573
|
+
return Array.from({ length: end - start }, (_, i) => start + i);
|
|
574
|
+
}
|
|
575
|
+
async #spawnCluster(id) {
|
|
576
|
+
let shardIds = this.#getShardIdsForCluster(id), firstShardId = shardIds[0], lastShardId = shardIds[shardIds.length - 1];
|
|
577
|
+
this.emit("debug", `Spawning cluster ${id} (shards ${firstShardId}-${lastShardId})`);
|
|
578
|
+
let env = {
|
|
579
|
+
...process.env,
|
|
580
|
+
BAKIT_CLUSTER_ID: String(id),
|
|
581
|
+
BAKIT_CLUSTER_SHARD_TOTAL: String(this.totalShards),
|
|
582
|
+
BAKIT_CLUSTER_SHARD_LIST: JSON.stringify(shardIds),
|
|
583
|
+
BAKIT_DISCORD_TOKEN: this.options.token,
|
|
584
|
+
BAKIT_DISCORD_INTENTS: String(this.options.intents),
|
|
585
|
+
BAKIT_DISCORD_GATEWAY_URL: this.#gatewayInfo?.url,
|
|
586
|
+
BAKIT_DISCORD_GATEWAY_VERSION: "10"
|
|
587
|
+
}, cluster = new ClusterProcess(this, id, { env });
|
|
588
|
+
this.#bindClusterEvents(cluster, id), this.clusters.set(id, cluster), this.emit("clusterCreate", cluster);
|
|
589
|
+
}
|
|
590
|
+
#bindClusterEvents(cluster, id) {
|
|
591
|
+
cluster.on("ready", () => {
|
|
592
|
+
this.#readyCount++, this.emit("clusterReady", cluster), this.ready && this.emit("ready");
|
|
593
|
+
}), cluster.process.on("exit", (code) => {
|
|
594
|
+
this.emit("clusterExit", cluster, code), this.clusters.delete(id), this.#readyCount = Math.max(0, this.#readyCount - 1);
|
|
595
|
+
}), cluster.on("error", (err) => {
|
|
596
|
+
this.emit("clusterError", cluster, err);
|
|
597
|
+
}), cluster.on("debug", (msg) => {
|
|
598
|
+
this.emit("debug", `[Cluster ${id}] ${msg}`);
|
|
599
|
+
}), cluster.on("dispatch", (shardId, payload) => {
|
|
600
|
+
this.emit("dispatch", cluster, shardId, payload);
|
|
601
|
+
}), cluster.on("raw", (shardId, payload) => {
|
|
602
|
+
this.emit("raw", cluster, shardId, payload);
|
|
603
|
+
}), cluster.on("shardAdd", (shardId) => {
|
|
604
|
+
this.emit("shardAdd", cluster, shardId);
|
|
605
|
+
}), cluster.on("shardReady", (shardId) => {
|
|
606
|
+
this.emit("shardReady", cluster, shardId);
|
|
607
|
+
}), cluster.on("shardDisconnect", (shardId, code) => {
|
|
608
|
+
this.emit("shardDisconnect", cluster, shardId, code);
|
|
609
|
+
}), cluster.on("shardResume", (shardId) => {
|
|
610
|
+
this.emit("shardResume", cluster, shardId);
|
|
611
|
+
}), cluster.on("shardError", (shardId, error) => {
|
|
612
|
+
this.emit("shardError", cluster, shardId, error);
|
|
613
|
+
}), cluster.on("needIdentify", (shardId) => {
|
|
614
|
+
this.requestIdentify(cluster, shardId);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
};
|
|
485
618
|
|
|
486
|
-
export {
|
|
619
|
+
export { Cluster, ClusterProcess, Shard, ShardState, ShardStrategy, ShardingManager };
|