Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"amqplib": "^2.0.1",
"audio-decode": "^2.2.3",
"axios": "^1.17.0",
"baileys": "7.0.0-rc13",
"baileys": "7.0.0-rc14",
"class-validator": "^0.15.1",
"compression": "^1.7.5",
"cors": "^2.8.6",
Expand Down
220 changes: 220 additions & 0 deletions patches/baileys+7.0.0-rc14.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
diff --git a/node_modules/baileys/lib/Socket/messages-recv.js b/node_modules/baileys/lib/Socket/messages-recv.js
index f2003ac..1f72102 100644
--- a/node_modules/baileys/lib/Socket/messages-recv.js
+++ b/node_modules/baileys/lib/Socket/messages-recv.js
@@ -880,6 +880,7 @@ export const makeMessagesRecvSocket = (config) => {
break;
case 'link_code_companion_reg':
const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg');
+ if (!getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub')) { break; }
const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'));
const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'));
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
diff --git a/node_modules/baileys/lib/Socket/socket.js b/node_modules/baileys/lib/Socket/socket.js
index ae34a8f..944188d 100644
--- a/node_modules/baileys/lib/Socket/socket.js
+++ b/node_modules/baileys/lib/Socket/socket.js
@@ -6,7 +6,7 @@ import { proto } from '../../WAProto/index.js';
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
import { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js';
import { DisconnectReason, XWAPaths } from '../Types/index.js';
-import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, buildPairingQRData, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
+import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, buildPairingQRData, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId, getErrorCodeFromStreamError, getNextPreKeysNode, handleCompanionRegRefresh, makeEventBuffer, makeNoiseHandler, makePairingQRRenderer, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
import { BinaryInfo } from '../WAM/BinaryInfo.js';
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
@@ -690,6 +690,9 @@ export const makeSocket = (config) => {
ws.on('close', () => void end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionClosed })));
// the server terminated the connection
ws.on('CB:xmlstreamend', () => void end(new Boom('Connection Terminated by Server', { statusCode: DisconnectReason.connectionClosed })));
+ // Re-render the QR currently on screen. Set while a pairing QR flow is
+ // live on this connection, undefined otherwise.
+ let refreshPairingQR;
// QR gen
ws.on('CB:iq,type:set,pair-device', async (stanza) => {
const iq = {
@@ -705,25 +708,40 @@ export const makeSocket = (config) => {
const refNodes = getBinaryNodeChildren(pairDeviceNode, 'ref');
const noiseKeyB64 = Buffer.from(creds.noiseKey.public).toString('base64');
const identityKeyB64 = Buffer.from(creds.signedIdentityKey.public).toString('base64');
- const advB64 = creds.advSecretKey;
+ const renderer = makePairingQRRenderer(refNodes.map(refNode => refNode.content.toString('utf-8')),
+ // creds.advSecretKey is read per render rather than captured once:
+ // a companion_reg_refresh rotates it mid-flow.
+ ref => ev.emit('connection.update', {
+ qr: buildPairingQRData(ref, noiseKeyB64, identityKeyB64, creds.advSecretKey, browser)
+ }));
+ refreshPairingQR = () => void renderer.refresh();
let qrMs = qrTimeout || 60000; // time to let a QR live
const genPairQR = () => {
if (!ws.isOpen) {
return;
}
- const refNode = refNodes.shift();
- if (!refNode) {
+ if (!renderer.next()) {
void end(new Boom('QR refs attempts ended', { statusCode: DisconnectReason.timedOut }));
return;
}
- const ref = refNode.content.toString('utf-8');
- const qr = buildPairingQRData(ref, noiseKeyB64, identityKeyB64, advB64, browser);
- ev.emit('connection.update', { qr });
qrTimer = setTimeout(genPairQR, qrMs);
qrMs = qrTimeout || 20000; // shorter subsequent qrs
};
genPairQR();
});
+ // the server retiring an unpaired companion's registration material
+ ws.on('CB:notification,type:companion_reg_refresh', (node) => {
+ handleCompanionRegRefresh(node, {
+ creds,
+ emitCredsUpdate: update => ev.emit('creds.update', update),
+ // Deliberately re-renders the ref already on screen and leaves
+ // qrTimer alone: that ref has not expired, only the secret it
+ // advertises changed. Spending a ref here would drain the pool the
+ // server allotted and end the flow with 'QR refs attempts ended'.
+ refreshQR: () => refreshPairingQR?.(),
+ logger
+ });
+ });
// device paired for the first time
// if device pairs successfully, the server asks to restart the connection
ws.on('CB:iq,,pair-success', async (stanza) => {
diff --git a/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts b/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts
index a79a52c..3bc161c 100644
--- a/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts
+++ b/node_modules/baileys/lib/Utils/companion-reg-client-utils.d.ts
@@ -1,4 +1,6 @@
-import type { WABrowserDescription } from '../Types/index.js';
+import type { AuthenticationCreds, WABrowserDescription } from '../Types/index.js';
+import type { BinaryNode } from '../WABinary/index.js';
+import type { ILogger } from './logger.js';
export declare enum CompanionWebClientType {
UNKNOWN = 0,
CHROME = 1,
@@ -14,4 +16,40 @@ export declare enum CompanionWebClientType {
export declare const getCompanionWebClientType: ([os, browserName]: WABrowserDescription) => CompanionWebClientType;
export declare const getCompanionPlatformId: (browser: WABrowserDescription) => string;
export declare const buildPairingQRData: (ref: string, noiseKeyB64: string, identityKeyB64: string, advB64: string, browser: WABrowserDescription) => string;
+export type PairingQRRenderer = {
+ /** Render the next ref's QR. False once the server's allotment is spent. */
+ next(): boolean;
+ /** Re-render the QR on screen. Consumes no ref; false if none is shown yet. */
+ refresh(): boolean;
+};
+/**
+ * Holds the ref currently on screen so it can be re-rendered.
+ *
+ * `render` is called with the ref rather than a finished payload so the caller
+ * can read the adv secret at render time: a `companion_reg_refresh` rotates it
+ * mid-flow, and every QR emitted afterwards has to advertise the new value.
+ */
+export declare const makePairingQRRenderer: (refs: string[], render: (ref: string) => void) => PairingQRRenderer;
+export type CompanionRegRefreshContext = {
+ creds: AuthenticationCreds;
+ emitCredsUpdate: (update: Partial<AuthenticationCreds>) => void;
+ refreshQR: () => void;
+ logger: ILogger;
+};
+export type CompanionRegRefreshOutcome = 'rotated' | 'ignored_malformed' | 'ignored_registered';
+/**
+ * `<notification type="companion_reg_refresh">` - the server retiring an
+ * unpaired companion's registration material.
+ *
+ * WA Web accepts the stanza with either a `companion_reg_refresh` or a
+ * `pair-device-rotate-qr` child, rejects it when neither is present, and
+ * answers by regenerating the adv secret key. That key is a quarter of what
+ * the pairing QR advertises, so a client that only acks keeps offering a QR
+ * built on a secret the server has already retired: the phone scans it,
+ * reports a failed link, and no pair-success ever arrives.
+ *
+ * The ack itself is unchanged - the generic notification path already sends
+ * it - so this only adds the rotation and the re-render.
+ */
+export declare const handleCompanionRegRefresh: (node: BinaryNode, { creds, emitCredsUpdate, refreshQR, logger }: CompanionRegRefreshContext) => CompanionRegRefreshOutcome;
//# sourceMappingURL=companion-reg-client-utils.d.ts.map
\ No newline at end of file
diff --git a/node_modules/baileys/lib/Utils/companion-reg-client-utils.js b/node_modules/baileys/lib/Utils/companion-reg-client-utils.js
index f5edebd..5021c8f 100644
--- a/node_modules/baileys/lib/Utils/companion-reg-client-utils.js
+++ b/node_modules/baileys/lib/Utils/companion-reg-client-utils.js
@@ -1,3 +1,5 @@
+import { randomBytes } from 'crypto';
+import { getBinaryNodeChild } from '../WABinary/index.js';
export var CompanionWebClientType;
(function (CompanionWebClientType) {
CompanionWebClientType[CompanionWebClientType["UNKNOWN"] = 0] = "UNKNOWN";
@@ -32,4 +34,72 @@ export const buildPairingQRData = (ref, noiseKeyB64, identityKeyB64, advB64, bro
return ('https://wa.me/settings/linked_devices#' +
[ref, noiseKeyB64, identityKeyB64, advB64, getCompanionPlatformId(browser)].join(','));
};
+/**
+ * Holds the ref currently on screen so it can be re-rendered.
+ *
+ * `render` is called with the ref rather than a finished payload so the caller
+ * can read the adv secret at render time: a `companion_reg_refresh` rotates it
+ * mid-flow, and every QR emitted afterwards has to advertise the new value.
+ */
+export const makePairingQRRenderer = (refs, render) => {
+ let index = 0;
+ let current;
+ return {
+ next() {
+ const ref = refs[index];
+ if (ref === undefined) {
+ return false;
+ }
+ index += 1;
+ current = ref;
+ render(ref);
+ return true;
+ },
+ refresh() {
+ if (current === undefined) {
+ return false;
+ }
+ render(current);
+ return true;
+ }
+ };
+};
+/** The two children WA Web's parser accepts on this notification. */
+const COMPANION_REG_REFRESH_CHILDREN = ['companion_reg_refresh', 'pair-device-rotate-qr'];
+/**
+ * `<notification type="companion_reg_refresh">` - the server retiring an
+ * unpaired companion's registration material.
+ *
+ * WA Web accepts the stanza with either a `companion_reg_refresh` or a
+ * `pair-device-rotate-qr` child, rejects it when neither is present, and
+ * answers by regenerating the adv secret key. That key is a quarter of what
+ * the pairing QR advertises, so a client that only acks keeps offering a QR
+ * built on a secret the server has already retired: the phone scans it,
+ * reports a failed link, and no pair-success ever arrives.
+ *
+ * The ack itself is unchanged - the generic notification path already sends
+ * it - so this only adds the rotation and the re-render.
+ */
+export const handleCompanionRegRefresh = (node, { creds, emitCredsUpdate, refreshQR, logger }) => {
+ if (!COMPANION_REG_REFRESH_CHILDREN.some(tag => getBinaryNodeChild(node, tag))) {
+ logger.warn({ node }, 'companion_reg_refresh carries neither expected child; ignoring');
+ return 'ignored_malformed';
+ }
+ // WA Web rotates unconditionally; a registered session is the one case
+ // where that is wrong here. `creds.me` is set by pair-success and by
+ // requestPairingCode, and in both cases the adv secret is what a completed
+ // or pending pairing is verified against - re-minting it would break the
+ // session rather than refresh a pending registration.
+ if (creds.me) {
+ logger.debug({ id: node.attrs.id }, 'companion_reg_refresh on a registered session; keeping the adv secret');
+ return 'ignored_registered';
+ }
+ // Same construction as initAuthCreds and as WA Web's generateADVSecretKey:
+ // 32 CSPRNG bytes, base64.
+ creds.advSecretKey = randomBytes(32).toString('base64');
+ emitCredsUpdate({ advSecretKey: creds.advSecretKey });
+ logger.info({ id: node.attrs.id }, 'rotated the adv secret the server asked to retire; re-rendering the pairing QR');
+ refreshQR();
+ return 'rotated';
+};
//# sourceMappingURL=companion-reg-client-utils.js.map
\ No newline at end of file