Skip to content

Commit 6f7e5d0

Browse files
committed
fix(whatsapp): patch Baileys QR auth handshake for sendUnifiedSession
WhatsApp servers now require sendUnifiedSession telemetry after pairing. Without it, QR scan shows 'Logging in...' then silently fails with no connection.update event. Patch ports the fix from upstream Baileys PR #2294: - bump bundled WA version 1027934701 � 1035194821 - add serverTimeOffsetMs state + updateServerTimeOffset() on pair success - emit sendUnifiedSession() after pair success and available presence - preserve existing media upload race fix and dispatcher guard Closes #46518
1 parent ea9f172 commit 6f7e5d0

2 files changed

Lines changed: 153 additions & 11 deletions

File tree

patches/@[email protected]

Lines changed: 150 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,154 @@
1+
diff --git a/lib/Defaults/index.js b/lib/Defaults/index.js
2+
index 7651b275c8..6b3e7816cf 100644
3+
--- a/lib/Defaults/index.js
4+
+++ b/lib/Defaults/index.js
5+
@@ -2,7 +2,7 @@ import { proto } from '../../WAProto/index.js';
6+
import { makeLibSignalRepository } from '../Signal/libsignal.js';
7+
import { Browsers } from '../Utils/browser-utils.js';
8+
import logger from '../Utils/logger.js';
9+
-const version = [2, 3000, 1027934701];
10+
+const version = [2, 3000, 1035194821];
11+
export const UNAUTHORIZED_CODES = [401, 403, 419];
12+
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
13+
export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
14+
@@ -112,4 +112,10 @@ export const DEFAULT_CACHE_TTLS = {
15+
CALL_OFFER: 5 * 60, // 5 minutes
16+
USER_DEVICES: 5 * 60 // 5 minutes
17+
};
18+
+export const TimeMs = {
19+
+ Minute: 60 * 1000,
20+
+ Hour: 60 * 60 * 1000,
21+
+ Day: 24 * 60 * 60 * 1000,
22+
+ Week: 7 * 24 * 60 * 60 * 1000
23+
+};
24+
//# sourceMappingURL=index.js.map
25+
\ No newline at end of file
26+
diff --git a/lib/Socket/chats.js b/lib/Socket/chats.js
27+
index 82021743f2..e76d9c2c2f 100644
28+
--- a/lib/Socket/chats.js
29+
+++ b/lib/Socket/chats.js
30+
@@ -14,7 +14,7 @@ const MAX_SYNC_ATTEMPTS = 2;
31+
export const makeChatsSocket = (config) => {
32+
const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config;
33+
const sock = makeSocket(config);
34+
- const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock;
35+
+ const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession } = sock;
36+
let privacySettings;
37+
let syncState = SyncState.Connecting;
38+
/** this mutex ensures that the notifications (receipts, messages etc.) are processed in order */
39+
@@ -497,6 +497,9 @@ export const makeChatsSocket = (config) => {
40+
return;
41+
}
42+
ev.emit('connection.update', { isOnline: type === 'available' });
43+
+ if (type === 'available') {
44+
+ void sendUnifiedSession();
45+
+ }
46+
await sendNode({
47+
tag: 'presence',
48+
attrs: {
49+
diff --git a/lib/Socket/socket.js b/lib/Socket/socket.js
50+
index fe1aa7f2f3..9a49a17740 100644
51+
--- a/lib/Socket/socket.js
52+
+++ b/lib/Socket/socket.js
53+
@@ -3,7 +3,7 @@ import { randomBytes } from 'crypto';
54+
import { URL } from 'url';
55+
import { promisify } from 'util';
56+
import { proto } from '../../WAProto/index.js';
57+
-import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, UPLOAD_TIMEOUT } from '../Defaults/index.js';
58+
+import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
59+
import { DisconnectReason } from '../Types/index.js';
60+
import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
61+
import { getPlatformId } from '../Utils/browser-utils.js';
62+
@@ -20,6 +20,7 @@ import { WebSocketClient } from './Client/index.js';
63+
export const makeSocket = (config) => {
64+
const { waWebSocketUrl, connectTimeoutMs, logger, keepAliveIntervalMs, browser, auth: authState, printQRInTerminal, defaultQueryTimeoutMs, transactionOpts, qrTimeout, makeSignalRepository } = config;
65+
const publicWAMBuffer = new BinaryInfo();
66+
+ let serverTimeOffsetMs = 0;
67+
const uqTagId = generateMdTagPrefix();
68+
const generateMessageTag = () => `${uqTagId}${epoch++}`;
69+
if (printQRInTerminal) {
70+
@@ -718,11 +719,13 @@ export const makeSocket = (config) => {
71+
ws.on('CB:iq,,pair-success', async (stanza) => {
72+
logger.debug('pair success recv');
73+
try {
74+
+ updateServerTimeOffset(stanza);
75+
const { reply, creds: updatedCreds } = configureSuccessfulPairing(stanza, creds);
76+
logger.info({ me: updatedCreds.me, platform: updatedCreds.platform }, 'pairing configured successfully, expect to restart the connection...');
77+
ev.emit('creds.update', updatedCreds);
78+
ev.emit('connection.update', { isNewLogin: true, qr: undefined });
79+
await sendNode(reply);
80+
+ void sendUnifiedSession();
81+
}
82+
catch (error) {
83+
logger.info({ trace: error.stack }, 'error in pairing');
84+
@@ -732,6 +735,7 @@ export const makeSocket = (config) => {
85+
// login complete
86+
ws.on('CB:success', async (node) => {
87+
try {
88+
+ updateServerTimeOffset(node);
89+
await uploadPreKeysToServerIfRequired();
90+
await sendPassiveIq('active');
91+
// After successful login, validate our key-bundle against server
92+
@@ -749,6 +753,7 @@ export const makeSocket = (config) => {
93+
clearTimeout(qrTimer); // will never happen in all likelyhood -- but just in case WA sends success on first try
94+
ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } });
95+
ev.emit('connection.update', { connection: 'open' });
96+
+ void sendUnifiedSession();
97+
if (node.attrs.lid && authState.creds.me?.id) {
98+
const myLID = node.attrs.lid;
99+
process.nextTick(async () => {
100+
@@ -839,6 +844,37 @@ export const makeSocket = (config) => {
101+
}
102+
Object.assign(creds, update);
103+
});
104+
+ const updateServerTimeOffset = ({ attrs }) => {
105+
+ const tValue = attrs?.t;
106+
+ if (!tValue) {
107+
+ return;
108+
+ }
109+
+ const parsed = Number(tValue);
110+
+ if (Number.isNaN(parsed) || parsed <= 0) {
111+
+ return;
112+
+ }
113+
+ const localMs = Date.now();
114+
+ serverTimeOffsetMs = parsed * 1000 - localMs;
115+
+ logger.debug({ offset: serverTimeOffsetMs }, 'calculated server time offset');
116+
+ };
117+
+ const getUnifiedSessionId = () => {
118+
+ const offsetMs = 3 * TimeMs.Day;
119+
+ const now = Date.now() + serverTimeOffsetMs;
120+
+ const id = (now + offsetMs) % TimeMs.Week;
121+
+ return id.toString();
122+
+ };
123+
+ const sendUnifiedSession = async () => {
124+
+ if (!ws.isOpen) {
125+
+ return;
126+
+ }
127+
+ const node = { tag: 'ib', attrs: {}, content: [{ tag: 'unified_session', attrs: { id: getUnifiedSessionId() } }] };
128+
+ try {
129+
+ await sendNode(node);
130+
+ }
131+
+ catch (error) {
132+
+ logger.debug({ error }, 'failed to send unified_session telemetry');
133+
+ }
134+
+ };
135+
return {
136+
type: 'md',
137+
ws,
138+
@@ -862,6 +898,8 @@ export const makeSocket = (config) => {
139+
digestKeyBundle,
140+
rotateSignedPreKey,
141+
requestPairingCode,
142+
+ updateServerTimeOffset,
143+
+ sendUnifiedSession,
144+
wamBuffer: publicWAMBuffer,
145+
/** Waits for the connection to WA to reach a state */
146+
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
1147
diff --git a/lib/Utils/messages-media.js b/lib/Utils/messages-media.js
2-
index 0d32dfb4882dfe029ba8804772d7d89404b08e76..73809fcd1d52362aef0c35cb7416c29d86482df0 100644
148+
index 0d32dfb488..59cac3ef37 100644
3149
--- a/lib/Utils/messages-media.js
4150
+++ b/lib/Utils/messages-media.js
5-
@@ -353,9 +353,17 @@
151+
@@ -353,9 +353,17 @@ export const encryptedStream = async (media, mediaType, { logger, saveOriginalFi
6152
const fileSha256 = sha256Plain.digest();
7153
const fileEncSha256 = sha256Enc.digest();
8154
encFileWriteStream.write(mac);
@@ -20,19 +166,15 @@ index 0d32dfb4882dfe029ba8804772d7d89404b08e76..73809fcd1d52362aef0c35cb7416c29d
20166
logger?.debug('encrypted data successfully');
21167
return {
22168
mediaKey,
23-
@@ -520,11 +528,10 @@
24-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25-
let result;
169+
@@ -522,7 +530,6 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
26170
try {
27171
const stream = createReadStream(filePath);
28172
const response = await fetch(url, {
29173
- dispatcher: fetchAgent,
30174
method: 'POST',
31175
body: stream,
32176
headers: {
33-
...(() => {
34-
const hdrs = options?.headers;
35-
@@ -535,6 +542,11 @@
177+
@@ -535,6 +542,11 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
36178
'Content-Type': 'application/octet-stream',
37179
Origin: DEFAULT_ORIGIN
38180
},

pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)