Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MR414N-ID
GitHub Repository: MR414N-ID/botku2
Path: blob/master/node_modules/@adiwajshing/baileys/lib/Socket/socket.js
1129 views
1
"use strict";
2
var __importDefault = (this && this.__importDefault) || function (mod) {
3
return (mod && mod.__esModule) ? mod : { "default": mod };
4
};
5
Object.defineProperty(exports, "__esModule", { value: true });
6
exports.makeSocket = void 0;
7
const boom_1 = require("@hapi/boom");
8
const util_1 = require("util");
9
const ws_1 = __importDefault(require("ws"));
10
const WAProto_1 = require("../../WAProto");
11
const Defaults_1 = require("../Defaults");
12
const Types_1 = require("../Types");
13
const Utils_1 = require("../Utils");
14
const event_buffer_1 = require("../Utils/event-buffer");
15
const WABinary_1 = require("../WABinary");
16
/**
17
* Connects to WA servers and performs:
18
* - simple queries (no retry mechanism, wait for connection establishment)
19
* - listen to messages and emit events
20
* - query phone connection
21
*/
22
const makeSocket = ({ waWebSocketUrl, connectTimeoutMs, logger, agent, keepAliveIntervalMs, version, browser, auth: authState, printQRInTerminal, defaultQueryTimeoutMs, syncFullHistory, transactionOpts, qrTimeout }) => {
23
const ws = new ws_1.default(waWebSocketUrl, undefined, {
24
origin: Defaults_1.DEFAULT_ORIGIN,
25
handshakeTimeout: connectTimeoutMs,
26
timeout: connectTimeoutMs,
27
agent
28
});
29
ws.setMaxListeners(0);
30
const ev = (0, event_buffer_1.makeEventBuffer)(logger);
31
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
32
const ephemeralKeyPair = Utils_1.Curve.generateKeyPair();
33
/** WA noise protocol wrapper */
34
const noise = (0, Utils_1.makeNoiseHandler)(ephemeralKeyPair, logger);
35
const { creds } = authState;
36
// add transaction capability
37
const keys = (0, Utils_1.addTransactionCapability)(authState.keys, logger, transactionOpts);
38
let lastDateRecv;
39
let epoch = 1;
40
let keepAliveReq;
41
let qrTimer;
42
let closed = false;
43
const uqTagId = (0, Utils_1.generateMdTagPrefix)();
44
const generateMessageTag = () => `${uqTagId}${epoch++}`;
45
const sendPromise = (0, util_1.promisify)(ws.send);
46
/** send a raw buffer */
47
const sendRawMessage = async (data) => {
48
if (ws.readyState !== ws.OPEN) {
49
throw new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed });
50
}
51
const bytes = noise.encodeFrame(data);
52
await sendPromise.call(ws, bytes);
53
};
54
/** send a binary node */
55
const sendNode = (frame) => {
56
if (logger.level === 'trace') {
57
logger.trace({ msgId: frame.attrs.id, fromMe: true, frame }, 'communication');
58
}
59
const buff = (0, WABinary_1.encodeBinaryNode)(frame);
60
return sendRawMessage(buff);
61
};
62
/** log & process any unexpected errors */
63
const onUnexpectedError = (error, msg) => {
64
logger.error({ trace: error.stack, output: error.output }, `unexpected error in '${msg}'`);
65
};
66
/** await the next incoming message */
67
const awaitNextMessage = async (sendMsg) => {
68
if (ws.readyState !== ws.OPEN) {
69
throw new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed });
70
}
71
let onOpen;
72
let onClose;
73
const result = (0, Utils_1.promiseTimeout)(connectTimeoutMs, (resolve, reject) => {
74
onOpen = (data) => resolve(data);
75
onClose = reject;
76
ws.on('frame', onOpen);
77
ws.on('close', onClose);
78
ws.on('error', onClose);
79
})
80
.finally(() => {
81
ws.off('frame', onOpen);
82
ws.off('close', onClose);
83
ws.off('error', onClose);
84
});
85
if (sendMsg) {
86
sendRawMessage(sendMsg).catch(onClose);
87
}
88
return result;
89
};
90
/**
91
* Wait for a message with a certain tag to be received
92
* @param tag the message tag to await
93
* @param json query that was sent
94
* @param timeoutMs timeout after which the promise will reject
95
*/
96
const waitForMessage = async (msgId, timeoutMs = defaultQueryTimeoutMs) => {
97
let onRecv;
98
let onErr;
99
try {
100
const result = await (0, Utils_1.promiseTimeout)(timeoutMs, (resolve, reject) => {
101
onRecv = resolve;
102
onErr = err => {
103
reject(err || new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed }));
104
};
105
ws.on(`TAG:${msgId}`, onRecv);
106
ws.on('close', onErr); // if the socket closes, you'll never receive the message
107
ws.off('error', onErr);
108
});
109
return result;
110
}
111
finally {
112
ws.off(`TAG:${msgId}`, onRecv);
113
ws.off('close', onErr); // if the socket closes, you'll never receive the message
114
ws.off('error', onErr);
115
}
116
};
117
/** send a query, and wait for its response. auto-generates message ID if not provided */
118
const query = async (node, timeoutMs) => {
119
if (!node.attrs.id) {
120
node.attrs.id = generateMessageTag();
121
}
122
const msgId = node.attrs.id;
123
const wait = waitForMessage(msgId, timeoutMs);
124
await sendNode(node);
125
const result = await wait;
126
if ('tag' in result) {
127
(0, WABinary_1.assertNodeErrorFree)(result);
128
}
129
return result;
130
};
131
/** connection handshake */
132
const validateConnection = async () => {
133
let helloMsg = {
134
clientHello: { ephemeral: ephemeralKeyPair.public }
135
};
136
helloMsg = WAProto_1.proto.HandshakeMessage.fromObject(helloMsg);
137
logger.info({ browser, helloMsg }, 'connected to WA Web');
138
const init = WAProto_1.proto.HandshakeMessage.encode(helloMsg).finish();
139
const result = await awaitNextMessage(init);
140
const handshake = WAProto_1.proto.HandshakeMessage.decode(result);
141
logger.trace({ handshake }, 'handshake recv from WA Web');
142
const keyEnc = noise.processHandshake(handshake, creds.noiseKey);
143
const config = { version, browser, syncFullHistory };
144
let node;
145
if (!creds.me) {
146
node = (0, Utils_1.generateRegistrationNode)(creds, config);
147
logger.info({ node }, 'not logged in, attempting registration...');
148
}
149
else {
150
node = (0, Utils_1.generateLoginNode)(creds.me.id, config);
151
logger.info({ node }, 'logging in...');
152
}
153
const payloadEnc = noise.encrypt(WAProto_1.proto.ClientPayload.encode(node).finish());
154
await sendRawMessage(WAProto_1.proto.HandshakeMessage.encode({
155
clientFinish: {
156
static: keyEnc,
157
payload: payloadEnc,
158
},
159
}).finish());
160
noise.finishInit();
161
startKeepAliveRequest();
162
};
163
const getAvailablePreKeysOnServer = async () => {
164
const result = await query({
165
tag: 'iq',
166
attrs: {
167
id: generateMessageTag(),
168
xmlns: 'encrypt',
169
type: 'get',
170
to: WABinary_1.S_WHATSAPP_NET
171
},
172
content: [
173
{ tag: 'count', attrs: {} }
174
]
175
});
176
const countChild = (0, WABinary_1.getBinaryNodeChild)(result, 'count');
177
return +countChild.attrs.value;
178
};
179
/** generates and uploads a set of pre-keys to the server */
180
const uploadPreKeys = async (count = Defaults_1.INITIAL_PREKEY_COUNT) => {
181
await keys.transaction(async () => {
182
logger.info({ count }, 'uploading pre-keys');
183
const { update, node } = await (0, Utils_1.getNextPreKeysNode)({ creds, keys }, count);
184
await query(node);
185
ev.emit('creds.update', update);
186
logger.info({ count }, 'uploaded pre-keys');
187
});
188
};
189
const uploadPreKeysToServerIfRequired = async () => {
190
const preKeyCount = await getAvailablePreKeysOnServer();
191
logger.info(`${preKeyCount} pre-keys found on server`);
192
if (preKeyCount <= Defaults_1.MIN_PREKEY_COUNT) {
193
await uploadPreKeys();
194
}
195
};
196
const onMessageRecieved = (data) => {
197
noise.decodeFrame(data, frame => {
198
var _a;
199
// reset ping timeout
200
lastDateRecv = new Date();
201
let anyTriggered = false;
202
anyTriggered = ws.emit('frame', frame);
203
// if it's a binary node
204
if (!(frame instanceof Uint8Array)) {
205
const msgId = frame.attrs.id;
206
if (logger.level === 'trace') {
207
logger.trace({ msgId, fromMe: false, frame }, 'communication');
208
}
209
/* Check if this is a response to a message we sent */
210
anyTriggered = ws.emit(`${Defaults_1.DEF_TAG_PREFIX}${msgId}`, frame) || anyTriggered;
211
/* Check if this is a response to a message we are expecting */
212
const l0 = frame.tag;
213
const l1 = frame.attrs || {};
214
const l2 = Array.isArray(frame.content) ? (_a = frame.content[0]) === null || _a === void 0 ? void 0 : _a.tag : '';
215
Object.keys(l1).forEach(key => {
216
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered;
217
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered;
218
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}`, frame) || anyTriggered;
219
});
220
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered;
221
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered;
222
if (!anyTriggered && logger.level === 'debug') {
223
logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'communication recv');
224
}
225
}
226
});
227
};
228
const end = (error) => {
229
if (closed) {
230
logger.trace({ trace: error === null || error === void 0 ? void 0 : error.stack }, 'connection already closed');
231
return;
232
}
233
closed = true;
234
logger.info({ trace: error === null || error === void 0 ? void 0 : error.stack }, error ? 'connection errored' : 'connection closed');
235
clearInterval(keepAliveReq);
236
clearTimeout(qrTimer);
237
ws.removeAllListeners('close');
238
ws.removeAllListeners('error');
239
ws.removeAllListeners('open');
240
ws.removeAllListeners('message');
241
if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
242
try {
243
ws.close();
244
}
245
catch (_a) { }
246
}
247
ev.emit('connection.update', {
248
connection: 'close',
249
lastDisconnect: {
250
error,
251
date: new Date()
252
}
253
});
254
ev.removeAllListeners('connection.update');
255
};
256
const waitForSocketOpen = async () => {
257
if (ws.readyState === ws.OPEN) {
258
return;
259
}
260
if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
261
throw new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed });
262
}
263
let onOpen;
264
let onClose;
265
await new Promise((resolve, reject) => {
266
onOpen = () => resolve(undefined);
267
onClose = reject;
268
ws.on('open', onOpen);
269
ws.on('close', onClose);
270
ws.on('error', onClose);
271
})
272
.finally(() => {
273
ws.off('open', onOpen);
274
ws.off('close', onClose);
275
ws.off('error', onClose);
276
});
277
};
278
const startKeepAliveRequest = () => (keepAliveReq = setInterval(() => {
279
if (!lastDateRecv) {
280
lastDateRecv = new Date();
281
}
282
const diff = Date.now() - lastDateRecv.getTime();
283
/*
284
check if it's been a suspicious amount of time since the server responded with our last seen
285
it could be that the network is down
286
*/
287
if (diff > keepAliveIntervalMs + 5000) {
288
end(new boom_1.Boom('Connection was lost', { statusCode: Types_1.DisconnectReason.connectionLost }));
289
}
290
else if (ws.readyState === ws.OPEN) {
291
// if its all good, send a keep alive request
292
query({
293
tag: 'iq',
294
attrs: {
295
id: generateMessageTag(),
296
to: WABinary_1.S_WHATSAPP_NET,
297
type: 'get',
298
xmlns: 'w:p',
299
},
300
content: [{ tag: 'ping', attrs: {} }]
301
})
302
.catch(err => {
303
logger.error({ trace: err.stack }, 'error in sending keep alive');
304
});
305
}
306
else {
307
logger.warn('keep alive called when WS not open');
308
}
309
}, keepAliveIntervalMs));
310
/** i have no idea why this exists. pls enlighten me */
311
const sendPassiveIq = (tag) => (query({
312
tag: 'iq',
313
attrs: {
314
to: WABinary_1.S_WHATSAPP_NET,
315
xmlns: 'passive',
316
type: 'set',
317
},
318
content: [
319
{ tag, attrs: {} }
320
]
321
}));
322
/** logout & invalidate connection */
323
const logout = async () => {
324
var _a;
325
const jid = (_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id;
326
if (jid) {
327
await sendNode({
328
tag: 'iq',
329
attrs: {
330
to: WABinary_1.S_WHATSAPP_NET,
331
type: 'set',
332
id: generateMessageTag(),
333
xmlns: 'md'
334
},
335
content: [
336
{
337
tag: 'remove-companion-device',
338
attrs: {
339
jid,
340
reason: 'user_initiated'
341
}
342
}
343
]
344
});
345
}
346
end(new boom_1.Boom('Intentional Logout', { statusCode: Types_1.DisconnectReason.loggedOut }));
347
};
348
ws.on('message', onMessageRecieved);
349
ws.on('open', validateConnection);
350
ws.on('error', error => end(new boom_1.Boom(`WebSocket Error (${error.message})`, { statusCode: (0, Utils_1.getCodeFromWSError)(error), data: error })));
351
ws.on('close', () => end(new boom_1.Boom('Connection Terminated', { statusCode: Types_1.DisconnectReason.connectionClosed })));
352
// the server terminated the connection
353
ws.on('CB:xmlstreamend', () => end(new boom_1.Boom('Connection Terminated by Server', { statusCode: Types_1.DisconnectReason.connectionClosed })));
354
// QR gen
355
ws.on('CB:iq,type:set,pair-device', async (stanza) => {
356
const iq = {
357
tag: 'iq',
358
attrs: {
359
to: WABinary_1.S_WHATSAPP_NET,
360
type: 'result',
361
id: stanza.attrs.id,
362
}
363
};
364
await sendNode(iq);
365
const pairDeviceNode = (0, WABinary_1.getBinaryNodeChild)(stanza, 'pair-device');
366
const refNodes = (0, WABinary_1.getBinaryNodeChildren)(pairDeviceNode, 'ref');
367
const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64');
368
const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64');
369
const advB64 = creds.advSecretKey;
370
let qrMs = qrTimeout || 60000; // time to let a QR live
371
const genPairQR = () => {
372
if (ws.readyState !== ws.OPEN) {
373
return;
374
}
375
const refNode = refNodes.shift();
376
if (!refNode) {
377
end(new boom_1.Boom('QR refs attempts ended', { statusCode: Types_1.DisconnectReason.timedOut }));
378
return;
379
}
380
const ref = refNode.content.toString('utf-8');
381
const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',');
382
ev.emit('connection.update', { qr });
383
qrTimer = setTimeout(genPairQR, qrMs);
384
qrMs = qrTimeout || 20000; // shorter subsequent qrs
385
};
386
genPairQR();
387
});
388
// device paired for the first time
389
// if device pairs successfully, the server asks to restart the connection
390
ws.on('CB:iq,,pair-success', async (stanza) => {
391
logger.debug('pair success recv');
392
try {
393
const { reply, creds: updatedCreds } = (0, Utils_1.configureSuccessfulPairing)(stanza, creds);
394
logger.info({ me: updatedCreds.me, platform: updatedCreds.platform }, 'pairing configured successfully, expect to restart the connection...');
395
ev.emit('creds.update', updatedCreds);
396
ev.emit('connection.update', { isNewLogin: true, qr: undefined });
397
await sendNode(reply);
398
}
399
catch (error) {
400
logger.info({ trace: error.stack }, 'error in pairing');
401
end(error);
402
}
403
});
404
// login complete
405
ws.on('CB:success', async () => {
406
await uploadPreKeysToServerIfRequired();
407
await sendPassiveIq('active');
408
logger.info('opened connection to WA');
409
clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
410
ev.emit('connection.update', { connection: 'open' });
411
});
412
ws.on('CB:stream:error', (node) => {
413
logger.error({ node }, 'stream errored out');
414
const { reason, statusCode } = (0, Utils_1.getErrorCodeFromStreamError)(node);
415
end(new boom_1.Boom(`Stream Errored (${reason})`, { statusCode, data: node }));
416
});
417
// stream fail, possible logout
418
ws.on('CB:failure', (node) => {
419
const reason = +(node.attrs.reason || 500);
420
end(new boom_1.Boom('Connection Failure', { statusCode: reason, data: node.attrs }));
421
});
422
ws.on('CB:ib,,downgrade_webclient', () => {
423
end(new boom_1.Boom('Multi-device beta not joined', { statusCode: Types_1.DisconnectReason.multideviceMismatch }));
424
});
425
process.nextTick(() => {
426
// start buffering important events
427
ev.buffer();
428
ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined });
429
});
430
// update credentials when required
431
ev.on('creds.update', update => {
432
var _a, _b;
433
const name = (_a = update.me) === null || _a === void 0 ? void 0 : _a.name;
434
// if name has just been received
435
if (((_b = creds.me) === null || _b === void 0 ? void 0 : _b.name) !== name) {
436
logger.info({ name }, 'updated pushName');
437
sendNode({
438
tag: 'presence',
439
attrs: { name: name }
440
})
441
.catch(err => {
442
logger.warn({ trace: err.stack }, 'error in sending presence update on name change');
443
});
444
}
445
Object.assign(creds, update);
446
});
447
if (printQRInTerminal) {
448
(0, Utils_1.printQRIfNecessaryListener)(ev, logger);
449
}
450
return {
451
type: 'md',
452
ws,
453
ev,
454
authState: { creds, keys },
455
get user() {
456
return authState.creds.me;
457
},
458
generateMessageTag,
459
query,
460
waitForMessage,
461
waitForSocketOpen,
462
sendRawMessage,
463
sendNode,
464
logout,
465
end,
466
onUnexpectedError,
467
uploadPreKeys,
468
/** Waits for the connection to WA to reach a state */
469
waitForConnectionUpdate: (0, Utils_1.bindWaitForConnectionUpdate)(ev),
470
};
471
};
472
exports.makeSocket = makeSocket;
473
474