@hieuxyz/rpc 1.0.7 → 1.0.8
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/README.md
CHANGED
|
@@ -43,7 +43,7 @@ DISCORD_USER_TOKEN="YOUR_DISCORD_USER_TOKEN_HERE"
|
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
45
|
import * as path from 'path';
|
|
46
|
-
import { Client,
|
|
46
|
+
import { Client, LocalImage, logger } from '@hieuxyz/rpc';
|
|
47
47
|
|
|
48
48
|
async function start() {
|
|
49
49
|
const token = process.env.DISCORD_USER_TOKEN;
|
|
@@ -54,7 +54,10 @@ async function start() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Initialize client with token
|
|
57
|
-
const client = new Client({
|
|
57
|
+
const client = new Client({
|
|
58
|
+
token,
|
|
59
|
+
alwaysReconnect: true,
|
|
60
|
+
});
|
|
58
61
|
|
|
59
62
|
await client.run();
|
|
60
63
|
|
|
@@ -66,11 +69,8 @@ async function start() {
|
|
|
66
69
|
.setType(0) // 0: Playing
|
|
67
70
|
.setTimestamps(Date.now())
|
|
68
71
|
.setParty(1, 5)
|
|
69
|
-
.setLargeImage(
|
|
70
|
-
.setSmallImage(new LocalImage(path.join(__dirname, 'vscode.png')), "VS Code")
|
|
71
|
-
.setButtons([
|
|
72
|
-
{ label: "View on GitHub", url: "https://github.com/hieuxyz00/hieuxyz_rpc" }
|
|
73
|
-
]);
|
|
72
|
+
.setLargeImage("https://i.ibb.co/MDP0hfTM/typescript.png", "TypeScript")
|
|
73
|
+
.setSmallImage(new LocalImage(path.join(__dirname, 'vscode.png')), "VS Code");
|
|
74
74
|
|
|
75
75
|
await client.rpc.build();
|
|
76
76
|
|
|
@@ -139,8 +139,11 @@ This is the main starting point.
|
|
|
139
139
|
- `new Client(options)`: Create a new instance.
|
|
140
140
|
- `options.token` (required): Your Discord user token.
|
|
141
141
|
- `options.apiBaseUrl` (optional): Override the default image proxy service URL.
|
|
142
|
+
- `options.alwaysReconnect` (optional): If `true`, the client will attempt to reconnect even after a normal close (e.g., from `client.close()` or a Discord-initiated close). Defaults to `false`.
|
|
142
143
|
- `client.run()`: Start connecting to Discord Gateway.
|
|
143
144
|
- `client.rpc`: Access the instance of `HieuxyzRPC` to build the state.
|
|
145
|
+
- `client.close(force?: boolean)`: Closes the connection to the Discord Gateway.
|
|
146
|
+
- `force` (optional, boolean): If set to `true`, the client will close permanently and will not attempt to reconnect, overriding the `alwaysReconnect` option. Defaults to `false`.
|
|
144
147
|
|
|
145
148
|
### Class `HieuxyzRPC`
|
|
146
149
|
|
package/dist/hieuxyz/Client.d.ts
CHANGED
|
@@ -48,8 +48,8 @@ export declare class Client {
|
|
|
48
48
|
run(): Promise<void>;
|
|
49
49
|
/**
|
|
50
50
|
* Close the connection to Discord Gateway.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* @param {boolean} [force=false] - If true, the client will close permanently and will not attempt to reconnect,
|
|
52
|
+
* even if `alwaysReconnect` is enabled. Defaults to false.
|
|
53
53
|
*/
|
|
54
|
-
close(): void;
|
|
54
|
+
close(force?: boolean): void;
|
|
55
55
|
}
|
package/dist/hieuxyz/Client.js
CHANGED
|
@@ -55,12 +55,12 @@ class Client {
|
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Close the connection to Discord Gateway.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
58
|
+
* @param {boolean} [force=false] - If true, the client will close permanently and will not attempt to reconnect,
|
|
59
|
+
* even if `alwaysReconnect` is enabled. Defaults to false.
|
|
60
60
|
*/
|
|
61
|
-
close() {
|
|
61
|
+
close(force = false) {
|
|
62
62
|
this.rpc.stopBackgroundRenewal();
|
|
63
|
-
this.websocket.close();
|
|
63
|
+
this.websocket.close(force);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
exports.Client = Client;
|
|
@@ -11,9 +11,12 @@ export declare class DiscordWebSocket {
|
|
|
11
11
|
private ws;
|
|
12
12
|
private sequence;
|
|
13
13
|
private heartbeatInterval;
|
|
14
|
+
private heartbeatIntervalValue;
|
|
14
15
|
private sessionId;
|
|
15
16
|
private resumeGatewayUrl;
|
|
16
17
|
private options;
|
|
18
|
+
private isReconnecting;
|
|
19
|
+
private permanentClose;
|
|
17
20
|
private resolveReady;
|
|
18
21
|
/**
|
|
19
22
|
* A promise will be resolved when the Gateway connection is ready.
|
|
@@ -34,9 +37,9 @@ export declare class DiscordWebSocket {
|
|
|
34
37
|
* If there was a previous session, it will try to resume.
|
|
35
38
|
*/
|
|
36
39
|
connect(): void;
|
|
37
|
-
private reconnect;
|
|
38
40
|
private onMessage;
|
|
39
41
|
private startHeartbeating;
|
|
42
|
+
private sendHeartbeat;
|
|
40
43
|
private identify;
|
|
41
44
|
private resume;
|
|
42
45
|
/**
|
|
@@ -46,9 +49,10 @@ export declare class DiscordWebSocket {
|
|
|
46
49
|
sendActivity(presence: PresenceUpdatePayload): void;
|
|
47
50
|
private sendJson;
|
|
48
51
|
/**
|
|
49
|
-
*
|
|
52
|
+
* Closes the WebSocket connection.
|
|
53
|
+
* @param force If true, prevents any automatic reconnection attempts.
|
|
50
54
|
*/
|
|
51
|
-
close(): void;
|
|
55
|
+
close(force?: boolean): void;
|
|
52
56
|
private cleanupHeartbeat;
|
|
53
57
|
private shouldReconnect;
|
|
54
58
|
}
|
|
@@ -17,9 +17,12 @@ class DiscordWebSocket {
|
|
|
17
17
|
ws = null;
|
|
18
18
|
sequence = null;
|
|
19
19
|
heartbeatInterval = null;
|
|
20
|
+
heartbeatIntervalValue = 0;
|
|
20
21
|
sessionId = null;
|
|
21
22
|
resumeGatewayUrl = null;
|
|
22
23
|
options;
|
|
24
|
+
isReconnecting = false;
|
|
25
|
+
permanentClose = false;
|
|
23
26
|
resolveReady = () => { };
|
|
24
27
|
/**
|
|
25
28
|
* A promise will be resolved when the Gateway connection is ready.
|
|
@@ -51,18 +54,40 @@ class DiscordWebSocket {
|
|
|
51
54
|
* If there was a previous session, it will try to resume.
|
|
52
55
|
*/
|
|
53
56
|
connect() {
|
|
57
|
+
if (this.isReconnecting) {
|
|
58
|
+
logger_1.logger.info("Connection attempt aborted: reconnection already in progress.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.permanentClose = false;
|
|
62
|
+
this.isReconnecting = true;
|
|
54
63
|
this.resetReadyPromise();
|
|
55
64
|
const url = this.resumeGatewayUrl || "wss://gateway.discord.gg/?v=10&encoding=json";
|
|
56
65
|
logger_1.logger.info(`Attempting to connect to ${url}...`);
|
|
57
66
|
this.ws = new ws_1.default(url);
|
|
58
|
-
this.ws.on('open', () =>
|
|
67
|
+
this.ws.on('open', () => {
|
|
68
|
+
logger_1.logger.info(`Successfully connected to Discord Gateway at ${url}.`);
|
|
69
|
+
this.isReconnecting = false;
|
|
70
|
+
});
|
|
59
71
|
this.ws.on('message', this.onMessage.bind(this));
|
|
60
72
|
this.ws.on('close', (code, reason) => {
|
|
61
|
-
logger_1.logger.warn(`Connection closed: ${code} - ${reason.toString()}`);
|
|
73
|
+
logger_1.logger.warn(`Connection closed: ${code} - ${reason.toString('utf-8')}`);
|
|
62
74
|
this.cleanupHeartbeat();
|
|
75
|
+
if (this.permanentClose) {
|
|
76
|
+
logger_1.logger.info("Connection permanently closed by client. Not reconnecting.");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (this.isReconnecting)
|
|
80
|
+
return;
|
|
63
81
|
if (this.shouldReconnect(code)) {
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
const canResume = code !== 4004 && !!this.sessionId;
|
|
84
|
+
if (!canResume) {
|
|
85
|
+
this.sessionId = null;
|
|
86
|
+
this.sequence = null;
|
|
87
|
+
this.resumeGatewayUrl = null;
|
|
88
|
+
}
|
|
89
|
+
this.connect();
|
|
90
|
+
}, 500);
|
|
66
91
|
}
|
|
67
92
|
else {
|
|
68
93
|
logger_1.logger.info("Not attempting to reconnect based on close code and client options.");
|
|
@@ -72,15 +97,6 @@ class DiscordWebSocket {
|
|
|
72
97
|
logger_1.logger.error(`WebSocket Error: ${err.message}`);
|
|
73
98
|
});
|
|
74
99
|
}
|
|
75
|
-
reconnect(forceNewSession = false) {
|
|
76
|
-
this.ws?.terminate();
|
|
77
|
-
if (forceNewSession) {
|
|
78
|
-
logger_1.logger.warn("Forcing a new session. Clearing previous session data.");
|
|
79
|
-
this.sessionId = null;
|
|
80
|
-
this.resumeGatewayUrl = null;
|
|
81
|
-
}
|
|
82
|
-
setTimeout(() => this.connect(), 3000);
|
|
83
|
-
}
|
|
84
100
|
onMessage(data) {
|
|
85
101
|
const payload = JSON.parse(data.toString());
|
|
86
102
|
if (payload.s) {
|
|
@@ -88,8 +104,10 @@ class DiscordWebSocket {
|
|
|
88
104
|
}
|
|
89
105
|
switch (payload.op) {
|
|
90
106
|
case OpCode_1.OpCode.HELLO:
|
|
91
|
-
this.
|
|
92
|
-
|
|
107
|
+
this.heartbeatIntervalValue = payload.d.heartbeat_interval;
|
|
108
|
+
logger_1.logger.info(`Received HELLO. Setting heartbeat interval to ${this.heartbeatIntervalValue}ms.`);
|
|
109
|
+
this.startHeartbeating();
|
|
110
|
+
if (this.sessionId && this.sequence) {
|
|
93
111
|
this.resume();
|
|
94
112
|
}
|
|
95
113
|
else {
|
|
@@ -99,8 +117,8 @@ class DiscordWebSocket {
|
|
|
99
117
|
case OpCode_1.OpCode.DISPATCH:
|
|
100
118
|
if (payload.t === 'READY') {
|
|
101
119
|
this.sessionId = payload.d.session_id;
|
|
102
|
-
this.resumeGatewayUrl = payload.d.resume_gateway_url;
|
|
103
|
-
logger_1.logger.info(`Session
|
|
120
|
+
this.resumeGatewayUrl = payload.d.resume_gateway_url + "/?v=10&encoding=json";
|
|
121
|
+
logger_1.logger.info(`Session READY. Session ID: ${this.sessionId}. Resume URL set.`);
|
|
104
122
|
this.resolveReady();
|
|
105
123
|
}
|
|
106
124
|
else if (payload.t === 'RESUMED') {
|
|
@@ -112,28 +130,27 @@ class DiscordWebSocket {
|
|
|
112
130
|
logger_1.logger.info("Heartbeat acknowledged.");
|
|
113
131
|
break;
|
|
114
132
|
case OpCode_1.OpCode.INVALID_SESSION:
|
|
115
|
-
logger_1.logger.warn(`
|
|
116
|
-
if (payload.d
|
|
117
|
-
this.ws?.close(
|
|
133
|
+
logger_1.logger.warn(`Received INVALID_SESSION. Resumable: ${payload.d}`);
|
|
134
|
+
if (payload.d) {
|
|
135
|
+
this.ws?.close(4000, "Invalid session, attempting to resume.");
|
|
118
136
|
}
|
|
119
137
|
else {
|
|
120
|
-
this.ws?.close(
|
|
138
|
+
this.ws?.close(4004, "Invalid session, starting a new session.");
|
|
121
139
|
}
|
|
122
140
|
break;
|
|
123
141
|
case OpCode_1.OpCode.RECONNECT:
|
|
124
|
-
logger_1.logger.info("Gateway requested
|
|
125
|
-
this.ws?.close(4000, "
|
|
142
|
+
logger_1.logger.info("Gateway requested RECONNECT. Closing to reconnect and resume.");
|
|
143
|
+
this.ws?.close(4000, "Gateway requested reconnect.");
|
|
126
144
|
break;
|
|
127
145
|
default:
|
|
128
146
|
break;
|
|
129
147
|
}
|
|
130
148
|
}
|
|
131
|
-
startHeartbeating(
|
|
149
|
+
startHeartbeating() {
|
|
132
150
|
this.cleanupHeartbeat();
|
|
133
151
|
setTimeout(() => {
|
|
134
152
|
if (this.ws?.readyState === ws_1.default.OPEN) {
|
|
135
|
-
this.
|
|
136
|
-
logger_1.logger.info(`Initial heartbeat sent with sequence ${this.sequence}.`);
|
|
153
|
+
this.sendHeartbeat();
|
|
137
154
|
}
|
|
138
155
|
this.heartbeatInterval = setInterval(() => {
|
|
139
156
|
if (this.ws?.readyState !== ws_1.default.OPEN) {
|
|
@@ -141,10 +158,15 @@ class DiscordWebSocket {
|
|
|
141
158
|
this.cleanupHeartbeat();
|
|
142
159
|
return;
|
|
143
160
|
}
|
|
144
|
-
this.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
this.sendHeartbeat();
|
|
162
|
+
}, this.heartbeatIntervalValue);
|
|
163
|
+
}, this.heartbeatIntervalValue * Math.random());
|
|
164
|
+
}
|
|
165
|
+
sendHeartbeat() {
|
|
166
|
+
if (this.ws?.readyState !== ws_1.default.OPEN)
|
|
167
|
+
return;
|
|
168
|
+
this.sendJson({ op: OpCode_1.OpCode.HEARTBEAT, d: this.sequence });
|
|
169
|
+
logger_1.logger.info(`Heartbeat sent with sequence ${this.sequence}.`);
|
|
148
170
|
}
|
|
149
171
|
identify() {
|
|
150
172
|
const identifyPayload = (0, identify_1.getIdentifyPayload)(this.token);
|
|
@@ -182,10 +204,21 @@ class DiscordWebSocket {
|
|
|
182
204
|
}
|
|
183
205
|
}
|
|
184
206
|
/**
|
|
185
|
-
*
|
|
207
|
+
* Closes the WebSocket connection.
|
|
208
|
+
* @param force If true, prevents any automatic reconnection attempts.
|
|
186
209
|
*/
|
|
187
|
-
close() {
|
|
188
|
-
|
|
210
|
+
close(force = false) {
|
|
211
|
+
if (force) {
|
|
212
|
+
logger_1.logger.info("Forcing permanent closure. Reconnects will be disabled.");
|
|
213
|
+
this.permanentClose = true;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
logger_1.logger.info("Closing connection manually...");
|
|
217
|
+
}
|
|
218
|
+
this.isReconnecting = false;
|
|
219
|
+
if (this.ws) {
|
|
220
|
+
this.ws.close(1000, "Client initiated closure");
|
|
221
|
+
}
|
|
189
222
|
}
|
|
190
223
|
cleanupHeartbeat() {
|
|
191
224
|
if (this.heartbeatInterval) {
|