Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MR414N-ID
GitHub Repository: MR414N-ID/botku2
Path: blob/master/node_modules/@adiwajshing/baileys/lib/LegacySocket/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 http_1 = require("http");
9
const util_1 = require("util");
10
const ws_1 = __importDefault(require("ws"));
11
const Defaults_1 = require("../Defaults");
12
const Types_1 = require("../Types");
13
const Utils_1 = require("../Utils");
14
const WABinary_1 = require("../WABinary");
15
/**
16
* Connects to WA servers and performs:
17
* - simple queries (no retry mechanism, wait for connection establishment)
18
* - listen to messages and emit events
19
* - query phone connection
20
*/
21
const makeSocket = ({ waWebSocketUrl, connectTimeoutMs, phoneResponseTimeMs, logger, agent, keepAliveIntervalMs, expectResponseTimeout, }) => {
22
// for generating tags
23
const referenceDateSeconds = (0, Utils_1.unixTimestampSeconds)(new Date());
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
let lastDateRecv;
39
let epoch = 0;
40
let authInfo;
41
let keepAliveReq;
42
let phoneCheckInterval;
43
let phoneCheckListeners = 0;
44
const phoneConnectionChanged = (value) => {
45
ws.emit('phone-connection', { value });
46
};
47
const sendPromise = (0, util_1.promisify)(ws.send);
48
/** generate message tag and increment epoch */
49
const generateMessageTag = (longTag = false) => {
50
const tag = `${longTag ? referenceDateSeconds : (referenceDateSeconds % 1000)}.--${epoch}`;
51
epoch += 1; // increment message count, it makes the 'epoch' field when sending binary messages
52
return tag;
53
};
54
const sendRawMessage = (data) => {
55
if (ws.readyState !== ws.OPEN) {
56
throw new boom_1.Boom('Connection Closed', { statusCode: Types_1.DisconnectReason.connectionClosed });
57
}
58
return sendPromise.call(ws, data);
59
};
60
/**
61
* Send a message to the WA servers
62
* @returns the tag attached in the message
63
* */
64
const sendNode = async ({ json, binaryTag, tag, longTag }) => {
65
tag = tag || generateMessageTag(longTag);
66
let data;
67
if (logger.level === 'trace') {
68
logger.trace({ tag, fromMe: true, json, binaryTag }, 'communication');
69
}
70
if (binaryTag) {
71
if (Array.isArray(json)) {
72
throw new boom_1.Boom('Expected BinaryNode with binary code', { statusCode: 400 });
73
}
74
if (!authInfo) {
75
throw new boom_1.Boom('No encryption/mac keys to encrypt node with', { statusCode: 400 });
76
}
77
const binary = (0, WABinary_1.encodeBinaryNodeLegacy)(json); // encode the JSON to the WhatsApp binary format
78
const buff = (0, Utils_1.aesEncrypt)(binary, authInfo.encKey); // encrypt it using AES and our encKey
79
const sign = (0, Utils_1.hmacSign)(buff, authInfo.macKey); // sign the message using HMAC and our macKey
80
data = Buffer.concat([
81
Buffer.from(tag + ','),
82
Buffer.from(binaryTag),
83
sign,
84
buff, // the actual encrypted buffer
85
]);
86
}
87
else {
88
data = `${tag},${JSON.stringify(json)}`;
89
}
90
await sendRawMessage(data);
91
return tag;
92
};
93
const end = (error) => {
94
logger.info({ error }, 'connection closed');
95
ws.removeAllListeners('close');
96
ws.removeAllListeners('error');
97
ws.removeAllListeners('open');
98
ws.removeAllListeners('message');
99
phoneCheckListeners = 0;
100
clearInterval(keepAliveReq);
101
clearPhoneCheckInterval();
102
if (ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
103
try {
104
ws.close();
105
}
106
catch (_a) { }
107
}
108
ws.emit('ws-close', error);
109
ws.removeAllListeners('ws-close');
110
};
111
const onMessageRecieved = (message) => {
112
var _a, _b, _c;
113
if (message[0] === '!' || message[0] === '!'.charCodeAt(0)) {
114
// when the first character in the message is an '!', the server is sending a pong frame
115
const timestamp = message.slice(1, message.length).toString();
116
lastDateRecv = new Date(parseInt(timestamp));
117
ws.emit('received-pong');
118
}
119
else {
120
let messageTag;
121
let json;
122
try {
123
const dec = (0, Utils_1.decodeWAMessage)(message, authInfo);
124
messageTag = dec[0];
125
json = dec[1];
126
if (!json) {
127
return;
128
}
129
}
130
catch (error) {
131
end(error);
132
return;
133
}
134
//if (this.shouldLogMessages) this.messageLog.push ({ tag: messageTag, json: JSON.stringify(json), fromMe: false })
135
if (logger.level === 'trace') {
136
logger.trace({ tag: messageTag, fromMe: false, json }, 'communication');
137
}
138
let anyTriggered = false;
139
/* Check if this is a response to a message we sent */
140
anyTriggered = ws.emit(`${Defaults_1.DEF_TAG_PREFIX}${messageTag}`, json);
141
/* Check if this is a response to a message we are expecting */
142
const l0 = json.tag || json[0] || '';
143
const l1 = (json === null || json === void 0 ? void 0 : json.attrs) || (json === null || json === void 0 ? void 0 : json[1]) || {};
144
const l2 = ((_b = (_a = json === null || json === void 0 ? void 0 : json.content) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.tag) || ((_c = json[2]) === null || _c === void 0 ? void 0 : _c[0]) || '';
145
Object.keys(l1).forEach(key => {
146
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, json) || anyTriggered;
147
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, json) || anyTriggered;
148
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},${key}`, json) || anyTriggered;
149
});
150
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0},,${l2}`, json) || anyTriggered;
151
anyTriggered = ws.emit(`${Defaults_1.DEF_CALLBACK_PREFIX}${l0}`, json) || anyTriggered;
152
if (!anyTriggered && logger.level === 'debug') {
153
logger.debug({ unhandled: true, tag: messageTag, fromMe: false, json }, 'communication recv');
154
}
155
}
156
};
157
/** Exits a query if the phone connection is active and no response is still found */
158
const exitQueryIfResponseNotExpected = (tag, cancel) => {
159
let timeout;
160
const listener = ([, connected]) => {
161
if (connected) {
162
timeout = setTimeout(() => {
163
logger.info({ tag }, 'cancelling wait for message as a response is no longer expected from the phone');
164
cancel(new boom_1.Boom('Not expecting a response', { statusCode: 422 }));
165
}, expectResponseTimeout);
166
ws.off(Defaults_1.PHONE_CONNECTION_CB, listener);
167
}
168
};
169
ws.on(Defaults_1.PHONE_CONNECTION_CB, listener);
170
return () => {
171
ws.off(Defaults_1.PHONE_CONNECTION_CB, listener);
172
timeout && clearTimeout(timeout);
173
};
174
};
175
/** interval is started when a query takes too long to respond */
176
const startPhoneCheckInterval = () => {
177
phoneCheckListeners += 1;
178
if (!phoneCheckInterval) {
179
// if its been a long time and we haven't heard back from WA, send a ping
180
phoneCheckInterval = setInterval(() => {
181
if (phoneCheckListeners <= 0) {
182
logger.warn('phone check called without listeners');
183
return;
184
}
185
logger.info('checking phone connection...');
186
sendAdminTest();
187
phoneConnectionChanged(false);
188
}, phoneResponseTimeMs);
189
}
190
};
191
const clearPhoneCheckInterval = () => {
192
phoneCheckListeners -= 1;
193
if (phoneCheckListeners <= 0) {
194
clearInterval(phoneCheckInterval);
195
phoneCheckInterval = undefined;
196
phoneCheckListeners = 0;
197
}
198
};
199
/** checks for phone connection */
200
const sendAdminTest = () => sendNode({ json: ['admin', 'test'] });
201
/**
202
* Wait for a message with a certain tag to be received
203
* @param tag the message tag to await
204
* @param json query that was sent
205
* @param timeoutMs timeout after which the promise will reject
206
*/
207
const waitForMessage = (tag, requiresPhoneConnection, timeoutMs) => {
208
if (ws.readyState !== ws.OPEN) {
209
throw new boom_1.Boom('Connection not open', { statusCode: Types_1.DisconnectReason.connectionClosed });
210
}
211
let cancelToken = () => { };
212
return {
213
promise: (async () => {
214
let onRecv;
215
let onErr;
216
let cancelPhoneChecker;
217
try {
218
const result = await (0, Utils_1.promiseTimeout)(timeoutMs, (resolve, reject) => {
219
onRecv = resolve;
220
onErr = err => {
221
reject(err || new boom_1.Boom('Intentional Close', { statusCode: Types_1.DisconnectReason.connectionClosed }));
222
};
223
cancelToken = () => onErr(new boom_1.Boom('Cancelled', { statusCode: 500 }));
224
if (requiresPhoneConnection) {
225
startPhoneCheckInterval();
226
cancelPhoneChecker = exitQueryIfResponseNotExpected(tag, onErr);
227
}
228
ws.on(`TAG:${tag}`, onRecv);
229
ws.on('ws-close', onErr); // if the socket closes, you'll never receive the message
230
});
231
return result;
232
}
233
finally {
234
requiresPhoneConnection && clearPhoneCheckInterval();
235
cancelPhoneChecker && cancelPhoneChecker();
236
ws.off(`TAG:${tag}`, onRecv);
237
ws.off('ws-close', onErr); // if the socket closes, you'll never receive the message
238
}
239
})(),
240
cancelToken: () => {
241
cancelToken();
242
}
243
};
244
};
245
/**
246
* Query something from the WhatsApp servers
247
* @param json the query itself
248
* @param binaryTags the tags to attach if the query is supposed to be sent encoded in binary
249
* @param timeoutMs timeout after which the query will be failed (set to null to disable a timeout)
250
* @param tag the tag to attach to the message
251
*/
252
const query = async ({ json, timeoutMs, expect200, tag, longTag, binaryTag, requiresPhoneConnection }) => {
253
tag = tag || generateMessageTag(longTag);
254
const { promise, cancelToken } = waitForMessage(tag, !!requiresPhoneConnection, timeoutMs);
255
try {
256
await sendNode({ json, tag, binaryTag });
257
}
258
catch (error) {
259
cancelToken();
260
// swallow error
261
await promise.catch(() => { });
262
// throw back the error
263
throw error;
264
}
265
const response = await promise;
266
const responseStatusCode = +(response.status ? response.status : 200); // default status
267
// read here: http://getstatuscode.com/599
268
if (responseStatusCode === 599) { // the connection has gone bad
269
end(new boom_1.Boom('WA server overloaded', { statusCode: 599, data: { query: json, response } }));
270
}
271
if (expect200 && Math.floor(responseStatusCode / 100) !== 2) {
272
const message = http_1.STATUS_CODES[responseStatusCode] || 'unknown';
273
throw new boom_1.Boom(`Unexpected status in '${Array.isArray(json) ? json[0] : ((json === null || json === void 0 ? void 0 : json.tag) || 'query')}': ${message}(${responseStatusCode})`, { data: { query: json, response }, statusCode: response.status });
274
}
275
return response;
276
};
277
const startKeepAliveRequest = () => (keepAliveReq = setInterval(() => {
278
if (!lastDateRecv) {
279
lastDateRecv = new Date();
280
}
281
const diff = Date.now() - lastDateRecv.getTime();
282
/*
283
check if it's been a suspicious amount of time since the server responded with our last seen
284
it could be that the network is down
285
*/
286
if (diff > keepAliveIntervalMs + 5000) {
287
end(new boom_1.Boom('Connection was lost', { statusCode: Types_1.DisconnectReason.connectionLost }));
288
}
289
else if (ws.readyState === ws.OPEN) {
290
sendRawMessage('?,,'); // if its all good, send a keep alive request
291
}
292
else {
293
logger.warn('keep alive called when WS not open');
294
}
295
}, keepAliveIntervalMs));
296
const waitForSocketOpen = async () => {
297
if (ws.readyState === ws.OPEN) {
298
return;
299
}
300
if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
301
throw new boom_1.Boom('Connection Already Closed', { statusCode: Types_1.DisconnectReason.connectionClosed });
302
}
303
let onOpen;
304
let onClose;
305
await new Promise((resolve, reject) => {
306
onOpen = () => resolve(undefined);
307
onClose = reject;
308
ws.on('open', onOpen);
309
ws.on('close', onClose);
310
ws.on('error', onClose);
311
})
312
.finally(() => {
313
ws.off('open', onOpen);
314
ws.off('close', onClose);
315
ws.off('error', onClose);
316
});
317
};
318
ws.on('message', onMessageRecieved);
319
ws.on('open', () => {
320
startKeepAliveRequest();
321
logger.info('Opened WS connection to WhatsApp Web');
322
});
323
ws.on('error', end);
324
ws.on('close', () => end(new boom_1.Boom('Connection Terminated', { statusCode: Types_1.DisconnectReason.connectionLost })));
325
ws.on(Defaults_1.PHONE_CONNECTION_CB, json => {
326
if (!json[1]) {
327
end(new boom_1.Boom('Connection terminated by phone', { statusCode: Types_1.DisconnectReason.connectionLost }));
328
logger.info('Connection terminated by phone, closing...');
329
}
330
else {
331
phoneConnectionChanged(true);
332
}
333
});
334
ws.on('CB:Cmd,type:disconnect', json => {
335
const { kind } = json[1];
336
let reason;
337
switch (kind) {
338
case 'replaced':
339
reason = Types_1.DisconnectReason.connectionReplaced;
340
break;
341
default:
342
reason = Types_1.DisconnectReason.connectionLost;
343
break;
344
}
345
end(new boom_1.Boom(`Connection terminated by server: "${kind || 'unknown'}"`, { statusCode: reason }));
346
});
347
return {
348
type: 'legacy',
349
ws,
350
sendAdminTest,
351
updateKeys: (info) => authInfo = info,
352
waitForSocketOpen,
353
sendNode,
354
generateMessageTag,
355
waitForMessage,
356
query,
357
/** Generic function for action, set queries */
358
setQuery: async (nodes, binaryTag = [Types_1.WAMetric.group, Types_1.WAFlag.ignore], tag) => {
359
const json = {
360
tag: 'action',
361
attrs: { epoch: epoch.toString(), type: 'set' },
362
content: nodes
363
};
364
return query({
365
json,
366
binaryTag,
367
tag,
368
expect200: true,
369
requiresPhoneConnection: true
370
});
371
},
372
currentEpoch: () => epoch,
373
end
374
};
375
};
376
exports.makeSocket = makeSocket;
377
378