@kangwifi-pro/waliwa 1.0.0
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/LICENSE +21 -0
- package/README.md +600 -0
- package/dist/advanced/features.d.ts +211 -0
- package/dist/advanced/features.d.ts.map +1 -0
- package/dist/advanced/features.js +671 -0
- package/dist/advanced/features.js.map +1 -0
- package/dist/auth/auth-state.d.ts +89 -0
- package/dist/auth/auth-state.d.ts.map +1 -0
- package/dist/auth/auth-state.js +330 -0
- package/dist/auth/auth-state.js.map +1 -0
- package/dist/auth/pairing-code.d.ts +78 -0
- package/dist/auth/pairing-code.d.ts.map +1 -0
- package/dist/auth/pairing-code.js +191 -0
- package/dist/auth/pairing-code.js.map +1 -0
- package/dist/auth/qr-code.d.ts +60 -0
- package/dist/auth/qr-code.d.ts.map +1 -0
- package/dist/auth/qr-code.js +151 -0
- package/dist/auth/qr-code.js.map +1 -0
- package/dist/calls/handler.d.ts +61 -0
- package/dist/calls/handler.d.ts.map +1 -0
- package/dist/calls/handler.js +117 -0
- package/dist/calls/handler.js.map +1 -0
- package/dist/core/binary.d.ts +74 -0
- package/dist/core/binary.d.ts.map +1 -0
- package/dist/core/binary.js +448 -0
- package/dist/core/binary.js.map +1 -0
- package/dist/core/crypto.d.ts +60 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +170 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/noise.d.ts +106 -0
- package/dist/core/noise.d.ts.map +1 -0
- package/dist/core/noise.js +307 -0
- package/dist/core/noise.js.map +1 -0
- package/dist/events/emitter.d.ts +44 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +79 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/features/index.d.ts +342 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +755 -0
- package/dist/features/index.js.map +1 -0
- package/dist/fixes/index.d.ts +333 -0
- package/dist/fixes/index.d.ts.map +1 -0
- package/dist/fixes/index.js +762 -0
- package/dist/fixes/index.js.map +1 -0
- package/dist/groups/management.d.ts +86 -0
- package/dist/groups/management.d.ts.map +1 -0
- package/dist/groups/management.js +443 -0
- package/dist/groups/management.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/media.d.ts +93 -0
- package/dist/messages/media.d.ts.map +1 -0
- package/dist/messages/media.js +252 -0
- package/dist/messages/media.js.map +1 -0
- package/dist/messages/message-encoder.d.ts +70 -0
- package/dist/messages/message-encoder.d.ts.map +1 -0
- package/dist/messages/message-encoder.js +453 -0
- package/dist/messages/message-encoder.js.map +1 -0
- package/dist/messages/send.d.ts +101 -0
- package/dist/messages/send.d.ts.map +1 -0
- package/dist/messages/send.js +409 -0
- package/dist/messages/send.js.map +1 -0
- package/dist/recovery/index.d.ts +125 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +584 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/skdm/index.d.ts +220 -0
- package/dist/skdm/index.d.ts.map +1 -0
- package/dist/skdm/index.js +600 -0
- package/dist/skdm/index.js.map +1 -0
- package/dist/socket/hybrid.d.ts +118 -0
- package/dist/socket/hybrid.d.ts.map +1 -0
- package/dist/socket/hybrid.js +352 -0
- package/dist/socket/hybrid.js.map +1 -0
- package/dist/socket/wa-socket.d.ts +300 -0
- package/dist/socket/wa-socket.d.ts.map +1 -0
- package/dist/socket/wa-socket.js +1094 -0
- package/dist/socket/wa-socket.js.map +1 -0
- package/dist/socket/ws-socket.d.ts +96 -0
- package/dist/socket/ws-socket.d.ts.map +1 -0
- package/dist/socket/ws-socket.js +302 -0
- package/dist/socket/ws-socket.js.map +1 -0
- package/dist/types/index.d.ts +444 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/jid.d.ts +46 -0
- package/dist/utils/jid.d.ts.map +1 -0
- package/dist/utils/jid.js +152 -0
- package/dist/utils/jid.js.map +1 -0
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +43 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +175 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/API.md +745 -0
- package/docs/ARCHITECTURE.md +307 -0
- package/docs/BAILEYS_FIXES.md +360 -0
- package/docs/FEATURES.md +532 -0
- package/docs/PROTOCOL.md +489 -0
- package/docs/RECOVERY.md +409 -0
- package/docs/SKDM.md +233 -0
- package/examples/ai-bot/index.ts +479 -0
- package/examples/ai-bot/package.json +17 -0
- package/examples/echo-bot/index.ts +171 -0
- package/examples/echo-bot/package.json +18 -0
- package/examples/group-bot/index.ts +557 -0
- package/examples/group-bot/package.json +17 -0
- package/examples/rest-gateway/index.ts +499 -0
- package/examples/rest-gateway/package.json +19 -0
- package/package.json +75 -0
- package/src/advanced/features.ts +817 -0
- package/src/auth/auth-state.ts +342 -0
- package/src/auth/pairing-code.ts +246 -0
- package/src/auth/qr-code.ts +191 -0
- package/src/calls/handler.ts +153 -0
- package/src/core/binary.ts +464 -0
- package/src/core/crypto.ts +189 -0
- package/src/core/noise.ts +406 -0
- package/src/events/emitter.ts +88 -0
- package/src/features/index.ts +921 -0
- package/src/fixes/index.ts +882 -0
- package/src/groups/management.ts +497 -0
- package/src/index.ts +274 -0
- package/src/messages/media.ts +372 -0
- package/src/messages/message-encoder.ts +520 -0
- package/src/messages/send.ts +521 -0
- package/src/recovery/index.ts +704 -0
- package/src/skdm/index.ts +693 -0
- package/src/socket/hybrid.ts +414 -0
- package/src/socket/wa-socket.ts +1347 -0
- package/src/socket/ws-socket.ts +355 -0
- package/src/types/index.ts +457 -0
- package/src/utils/jid.ts +156 -0
- package/src/utils/logger.ts +84 -0
- package/src/utils/retry.ts +205 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waliwa - WebSocket layer untuk WhatsApp Web Multi-Device
|
|
3
|
+
*
|
|
4
|
+
* Connect ke wss://web.whatsapp.com/ws/chat dengan Noise protocol encrypted frames.
|
|
5
|
+
* RAM optimization: gunakan buffer pooling, hindari Buffer.concat berlebihan,
|
|
6
|
+
* drain queue ASAP.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import WebSocket from 'ws';
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import {
|
|
12
|
+
NoiseHandshake,
|
|
13
|
+
TrafficCipher
|
|
14
|
+
} from '../core/noise.js';
|
|
15
|
+
import { concatBytes, getRandomBytes } from '../core/crypto.js';
|
|
16
|
+
import type { WALogger } from '../types/index.js';
|
|
17
|
+
import { SilentLogger } from '../utils/logger.js';
|
|
18
|
+
|
|
19
|
+
export type WAWebSocketEvent =
|
|
20
|
+
| 'open'
|
|
21
|
+
| 'message'
|
|
22
|
+
| 'close'
|
|
23
|
+
| 'error'
|
|
24
|
+
| 'handshake:done'
|
|
25
|
+
| 'handshake:error';
|
|
26
|
+
|
|
27
|
+
export interface WAWebSocketOptions {
|
|
28
|
+
url: string;
|
|
29
|
+
logger?: WALogger;
|
|
30
|
+
/** Browser identification for client payload */
|
|
31
|
+
browser?: [string, string, string];
|
|
32
|
+
/** Connect timeout ms */
|
|
33
|
+
connectTimeoutMs?: number;
|
|
34
|
+
/** Keep alive interval ms */
|
|
35
|
+
keepAliveIntervalMs?: number;
|
|
36
|
+
/** Static key pair untuk Noise (persistent across sessions) */
|
|
37
|
+
noiseKey?: { private: Uint8Array; public: Uint8Array };
|
|
38
|
+
/** RAM optimization level (1-3) */
|
|
39
|
+
ramLevel?: 1 | 2 | 3;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_WA_URL = 'wss://web.whatsapp.com/ws/chat';
|
|
43
|
+
const DEFAULT_BROWSER: [string, string, string] = ['Waliwa', 'WA', '1.0.0'];
|
|
44
|
+
|
|
45
|
+
// ============== Frame Buffer Pool (RAM optimization) ==============
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Buffer pool untuk reuse buffer daripada alokasi ulang.
|
|
49
|
+
* RAM-friendly pattern untuk high-throughput message processing.
|
|
50
|
+
*/
|
|
51
|
+
class BufferPool {
|
|
52
|
+
private pool: Uint8Array[] = [];
|
|
53
|
+
private readonly maxSize: number;
|
|
54
|
+
private readonly bufferSize: number;
|
|
55
|
+
|
|
56
|
+
constructor(bufferSize: number = 4096, maxPoolSize: number = 16) {
|
|
57
|
+
this.bufferSize = bufferSize;
|
|
58
|
+
this.maxSize = maxPoolSize;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
acquire(): Uint8Array {
|
|
62
|
+
const buf = this.pool.pop();
|
|
63
|
+
return buf ?? new Uint8Array(this.bufferSize);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
release(buf: Uint8Array): void {
|
|
67
|
+
if (this.pool.length < this.maxSize) {
|
|
68
|
+
this.pool.push(buf);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ============== WA WebSocket ==============
|
|
74
|
+
|
|
75
|
+
export class WAWebSocket extends EventEmitter {
|
|
76
|
+
private ws: WebSocket | null = null;
|
|
77
|
+
private url: string;
|
|
78
|
+
private logger: WALogger;
|
|
79
|
+
private browser: [string, string, string];
|
|
80
|
+
private connectTimeoutMs: number;
|
|
81
|
+
private keepAliveIntervalMs: number;
|
|
82
|
+
private noiseKey?: { private: Uint8Array; public: Uint8Array };
|
|
83
|
+
private ramLevel: 1 | 2 | 3;
|
|
84
|
+
|
|
85
|
+
private trafficCipher: TrafficCipher | null = null;
|
|
86
|
+
private handshake: NoiseHandshake | null = null;
|
|
87
|
+
private keepAliveTimer: NodeJS.Timeout | null = null;
|
|
88
|
+
private connectTimer: NodeJS.Timeout | null = null;
|
|
89
|
+
private isOpen: boolean = false;
|
|
90
|
+
private isHandshakeDone: boolean = false;
|
|
91
|
+
private recvBuffer: number[] = [];
|
|
92
|
+
private bufferPool: BufferPool;
|
|
93
|
+
|
|
94
|
+
constructor(opts: WAWebSocketOptions) {
|
|
95
|
+
super();
|
|
96
|
+
this.url = opts.url || DEFAULT_WA_URL;
|
|
97
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
98
|
+
this.browser = opts.browser ?? DEFAULT_BROWSER;
|
|
99
|
+
this.connectTimeoutMs = opts.connectTimeoutMs ?? 20000;
|
|
100
|
+
this.keepAliveIntervalMs = opts.keepAliveIntervalMs ?? 20000;
|
|
101
|
+
this.noiseKey = opts.noiseKey;
|
|
102
|
+
this.ramLevel = opts.ramLevel ?? 2;
|
|
103
|
+
this.bufferPool = new BufferPool(8192, this.ramLevel === 3 ? 32 : 16);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Connect ke WhatsApp WebSocket server
|
|
108
|
+
*/
|
|
109
|
+
async connect(): Promise<void> {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
this.logger.info(`Connecting to ${this.url}`);
|
|
112
|
+
this.ws = new WebSocket(this.url, {
|
|
113
|
+
origin: 'https://web.whatsapp.com',
|
|
114
|
+
headers: {
|
|
115
|
+
'Origin': 'https://web.whatsapp.com',
|
|
116
|
+
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
117
|
+
},
|
|
118
|
+
perMessageDeflate: false, // Hindari CPU overhead untuk RAM efficiency
|
|
119
|
+
maxPayload: 16 * 1024 * 1024 // 16MB max payload
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Connect timeout
|
|
123
|
+
this.connectTimer = setTimeout(() => {
|
|
124
|
+
if (!this.isOpen) {
|
|
125
|
+
this.ws?.terminate();
|
|
126
|
+
reject(new Error('WebSocket connect timeout'));
|
|
127
|
+
}
|
|
128
|
+
}, this.connectTimeoutMs);
|
|
129
|
+
|
|
130
|
+
this.ws.on('open', () => {
|
|
131
|
+
this.logger.info('WebSocket connected, performing Noise handshake');
|
|
132
|
+
this.isOpen = true;
|
|
133
|
+
if (this.connectTimer) {
|
|
134
|
+
clearTimeout(this.connectTimer);
|
|
135
|
+
this.connectTimer = null;
|
|
136
|
+
}
|
|
137
|
+
this.startHandshake().then(() => {
|
|
138
|
+
this.isHandshakeDone = true;
|
|
139
|
+
this.emit('handshake:done');
|
|
140
|
+
this.startKeepAlive();
|
|
141
|
+
resolve();
|
|
142
|
+
}).catch(err => {
|
|
143
|
+
this.logger.error(`Handshake failed: ${err.message}`);
|
|
144
|
+
this.emit('handshake:error', err);
|
|
145
|
+
reject(err);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.ws.on('message', (data: Buffer) => this.handleMessage(data));
|
|
150
|
+
this.ws.on('close', (code: number, reason: Buffer) => {
|
|
151
|
+
this.logger.warn(`WebSocket closed: ${code} ${reason.toString()}`);
|
|
152
|
+
this.isOpen = false;
|
|
153
|
+
this.isHandshakeDone = false;
|
|
154
|
+
this.stopKeepAlive();
|
|
155
|
+
this.emit('close', code, reason.toString());
|
|
156
|
+
});
|
|
157
|
+
this.ws.on('error', (err: Error) => {
|
|
158
|
+
this.logger.error(`WebSocket error: ${err.message}`);
|
|
159
|
+
this.emit('error', err);
|
|
160
|
+
if (!this.isOpen) {
|
|
161
|
+
if (this.connectTimer) {
|
|
162
|
+
clearTimeout(this.connectTimer);
|
|
163
|
+
this.connectTimer = null;
|
|
164
|
+
}
|
|
165
|
+
reject(err);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Start Noise Protocol XX handshake
|
|
173
|
+
*/
|
|
174
|
+
private async startHandshake(): Promise<void> {
|
|
175
|
+
this.handshake = new NoiseHandshake(this.noiseKey, true);
|
|
176
|
+
|
|
177
|
+
// Build client hello: ephemeral + encrypted static + encrypted payload
|
|
178
|
+
const clientPayload = this.buildClientPayload();
|
|
179
|
+
const clientHello = this.handshake.buildClientHello(clientPayload);
|
|
180
|
+
|
|
181
|
+
// Wrap dengan WA frame header
|
|
182
|
+
const frame = NoiseHandshake.encodeFrame(clientHello);
|
|
183
|
+
this.ws!.send(Buffer.from(frame));
|
|
184
|
+
|
|
185
|
+
// Wait for server response
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
const timeout = setTimeout(() => {
|
|
188
|
+
reject(new Error('Handshake response timeout'));
|
|
189
|
+
}, 15000);
|
|
190
|
+
|
|
191
|
+
const onceHandler = (data: Buffer) => {
|
|
192
|
+
clearTimeout(timeout);
|
|
193
|
+
this.ws!.off('message', onceHandler);
|
|
194
|
+
try {
|
|
195
|
+
this.processHandshakeResponse(data);
|
|
196
|
+
resolve();
|
|
197
|
+
} catch (err) {
|
|
198
|
+
reject(err);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
this.ws!.once('message', onceHandler);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Process handshake response dari server
|
|
207
|
+
*/
|
|
208
|
+
private processHandshakeResponse(data: Buffer): void {
|
|
209
|
+
if (!this.handshake) throw new Error('No handshake state');
|
|
210
|
+
|
|
211
|
+
// Strip WA header (7 bytes: WA + version + length)
|
|
212
|
+
const payload = data.subarray(7);
|
|
213
|
+
this.handshake.readResponderHandshake(payload);
|
|
214
|
+
|
|
215
|
+
// Finalize - dapatkan traffic keys
|
|
216
|
+
const { sendKey, recvKey } = this.handshake.finalize();
|
|
217
|
+
this.trafficCipher = new TrafficCipher(sendKey, recvKey);
|
|
218
|
+
this.logger.debug('Noise handshake completed, traffic keys derived');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Build client payload - browser info + pairing static key
|
|
223
|
+
*/
|
|
224
|
+
private buildClientPayload(): Uint8Array {
|
|
225
|
+
const enc = new TextEncoder();
|
|
226
|
+
const parts: Uint8Array[] = [];
|
|
227
|
+
|
|
228
|
+
parts.push(enc.encode(this.browser[0]));
|
|
229
|
+
parts.push(enc.encode(this.browser[1]));
|
|
230
|
+
parts.push(enc.encode(this.browser[2]));
|
|
231
|
+
|
|
232
|
+
// Generate pairing static key (atau reuse dari creds)
|
|
233
|
+
const pairingKey = this.noiseKey ?? this.generatePairingKey();
|
|
234
|
+
parts.push(pairingKey.public);
|
|
235
|
+
|
|
236
|
+
// Encode as length-prefixed
|
|
237
|
+
const result: number[] = [];
|
|
238
|
+
for (const p of parts) {
|
|
239
|
+
result.push(p.length);
|
|
240
|
+
for (let i = 0; i < p.length; i++) result.push(p[i]);
|
|
241
|
+
}
|
|
242
|
+
return new Uint8Array(result);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private generatePairingKey(): { private: Uint8Array; public: Uint8Array } {
|
|
246
|
+
const { generateCurveKeyPair } = require('../core/crypto.js');
|
|
247
|
+
return generateCurveKeyPair();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Handle incoming message - decrypt jika handshake done
|
|
252
|
+
*/
|
|
253
|
+
private handleMessage(data: Buffer): void {
|
|
254
|
+
if (!this.isHandshakeDone) {
|
|
255
|
+
// Should be handled by handshake
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
// Strip WA header (7 bytes) dan decrypt
|
|
260
|
+
const payload = data.subarray(7);
|
|
261
|
+
const plaintext = this.trafficCipher!.decrypt(payload);
|
|
262
|
+
this.emit('message', plaintext);
|
|
263
|
+
} catch (err: any) {
|
|
264
|
+
this.logger.error(`Message decrypt failed: ${err.message}`);
|
|
265
|
+
this.emit('error', err);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Send encrypted frame ke server
|
|
271
|
+
*/
|
|
272
|
+
send(plaintext: Uint8Array): void {
|
|
273
|
+
if (!this.isHandshakeDone || !this.trafficCipher) {
|
|
274
|
+
throw new Error('Cannot send before handshake complete');
|
|
275
|
+
}
|
|
276
|
+
const ciphertext = this.trafficCipher.encrypt(plaintext);
|
|
277
|
+
const frame = NoiseHandshake.encodeFrame(ciphertext);
|
|
278
|
+
this.ws!.send(Buffer.from(frame));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Start keep-alive ping
|
|
283
|
+
*/
|
|
284
|
+
private startKeepAlive(): void {
|
|
285
|
+
this.stopKeepAlive();
|
|
286
|
+
this.keepAliveTimer = setInterval(() => {
|
|
287
|
+
if (this.isOpen && this.isHandshakeDone) {
|
|
288
|
+
// Send ping sebagai binary node "iq" type "get" query "ping"
|
|
289
|
+
const pingFrame = this.buildPingFrame();
|
|
290
|
+
try {
|
|
291
|
+
this.send(pingFrame);
|
|
292
|
+
} catch (err: any) {
|
|
293
|
+
this.logger.warn(`Keep-alive ping failed: ${err.message}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}, this.keepAliveIntervalMs);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private stopKeepAlive(): void {
|
|
300
|
+
if (this.keepAliveTimer) {
|
|
301
|
+
clearInterval(this.keepAliveTimer);
|
|
302
|
+
this.keepAliveTimer = null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Build ping frame: <iq type="get"><ping/></iq>
|
|
308
|
+
*/
|
|
309
|
+
private buildPingFrame(): Uint8Array {
|
|
310
|
+
// Simple binary ping: tag=iq, attrs={type:get}, content=ping node
|
|
311
|
+
const { encodeBinaryNode } = require('../core/binary.js');
|
|
312
|
+
const node = {
|
|
313
|
+
tag: 'iq',
|
|
314
|
+
attrs: { type: 'get', id: this.generateId() },
|
|
315
|
+
content: [{ tag: 'ping', attrs: {} }]
|
|
316
|
+
};
|
|
317
|
+
return encodeBinaryNode(node);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private generateId(): string {
|
|
321
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Close WebSocket connection
|
|
326
|
+
*/
|
|
327
|
+
close(code: number = 1000, reason: string = 'normal'): void {
|
|
328
|
+
this.stopKeepAlive();
|
|
329
|
+
if (this.connectTimer) {
|
|
330
|
+
clearTimeout(this.connectTimer);
|
|
331
|
+
this.connectTimer = null;
|
|
332
|
+
}
|
|
333
|
+
this.isOpen = false;
|
|
334
|
+
this.isHandshakeDone = false;
|
|
335
|
+
if (this.ws) {
|
|
336
|
+
this.ws.close(code, reason);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get connection state
|
|
342
|
+
*/
|
|
343
|
+
isConnected(): boolean {
|
|
344
|
+
return this.isOpen && this.isHandshakeDone;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get traffic cipher (untuk debug)
|
|
349
|
+
*/
|
|
350
|
+
getTrafficCipher(): TrafficCipher | null {
|
|
351
|
+
return this.trafficCipher;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export default WAWebSocket;
|