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