diff --git a/.gitignore b/.gitignore index f8fd010..79e1d9d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ Proxy messages*.json test.ts TestData -wa-logs.txt \ No newline at end of file +wa-logs.txt +.claude \ No newline at end of file diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index b6c5fe3..a5c8302 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -87,6 +87,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { let sendActiveReceipts = false; const sendMessageAck = async ({ tag, attrs, content }: BinaryNode) => { + if (ws.readyState !== ws.OPEN) { + logger.debug({ tag, id: attrs.id }, "connection closed, skipping ack"); + return; + } + const stanza: BinaryNode = { tag: "ack", attrs: { @@ -407,20 +412,54 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { node, "link_code_companion_reg" ); - const ref = toRequiredBuffer( - getBinaryNodeChildBuffer( - linkCodeCompanionReg, - "link_code_pairing_ref" - ) + if (!linkCodeCompanionReg) { + logger.warn( + "Received link_code_companion_reg notification without node content" + ); + break; + } + if ( + !authState.creds.pairingCode || + !authState.creds.pairingEphemeralKeyPair + ) { + logger.warn( + "Received link_code_companion_reg but pairing credentials are missing" + ); + break; + } + const refBuffer = getBinaryNodeChildBuffer( + linkCodeCompanionReg, + "link_code_pairing_ref" + ); + const primaryIdentityPubBuffer = getBinaryNodeChildBuffer( + linkCodeCompanionReg, + "primary_identity_pub" ); + const primaryEphemeralPubWrappedBuffer = getBinaryNodeChildBuffer( + linkCodeCompanionReg, + "link_code_pairing_wrapped_primary_ephemeral_pub" + ); + if ( + !refBuffer || + !primaryIdentityPubBuffer || + !primaryEphemeralPubWrappedBuffer + ) { + logger.warn( + { + hasRef: !!refBuffer, + hasPrimaryIdentityPub: !!primaryIdentityPubBuffer, + hasPrimaryEphemeralPubWrapped: !!primaryEphemeralPubWrappedBuffer + }, + "Received incomplete link_code_companion_reg notification" + ); + break; + } + const ref = toRequiredBuffer(refBuffer); const primaryIdentityPublicKey = toRequiredBuffer( - getBinaryNodeChildBuffer(linkCodeCompanionReg, "primary_identity_pub") + primaryIdentityPubBuffer ); const primaryEphemeralPublicKeyWrapped = toRequiredBuffer( - getBinaryNodeChildBuffer( - linkCodeCompanionReg, - "link_code_pairing_wrapped_primary_ephemeral_pub" - ) + primaryEphemeralPubWrappedBuffer ); const codePairingPublicKey = await decipherLinkPublicKey( primaryEphemeralPublicKeyWrapped diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index d309ce0..05a4f4a 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -9,7 +9,7 @@ import { INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT } from "../Defaults"; -import { DisconnectReason, SocketConfig } from "../Types"; +import { DisconnectReason, KeyPair, SocketConfig } from "../Types"; import { addTransactionCapability, aesEncryptCTR, @@ -40,6 +40,16 @@ import { } from "../WABinary"; import { randomBytes } from "crypto"; +// Map custom platform names to valid OS names accepted by WhatsApp +const VALID_OS_MAP: { [key: string]: string } = { + Baileys: "Windows", + baileys: "Windows" +}; + +const getPlatformOS = (platform: string): string => { + return VALID_OS_MAP[platform] || platform; +}; + /** * Connects to WA servers and performs: * - simple queries (no retry mechanism, wait for connection establishment) @@ -500,22 +510,33 @@ export const makeSocket = ({ const requestPairingCode = async ( phoneNumber: string, - customPairingCode?: string + customPairingCode?: string, + showPushNotification: boolean = true ): Promise => { - const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5)); + // Gerar novo ephemeral key pair para este pairing (igual ao Go) + const pairingEphemeralKeyPair = Curve.generateKeyPair(); - if (customPairingCode && customPairingCode?.length !== 8) { - throw new Boom("Custom pairing code must be exactly 8 chars"); + // Gerar o linking code (5 bytes -> crockford base32 = 8 chars) + if (customPairingCode && customPairingCode.length !== 8) { + throw new Boom("Custom pairing code must be exactly 8 characters"); } + const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5)); + + const jid = jidEncode(phoneNumber, "s.whatsapp.net"); + // Armazenar no estado para uso posterior no pair-success authState.creds.pairingCode = pairingCode; - authState.creds.me = { - id: jidEncode(phoneNumber, "s.whatsapp.net"), - name: "~" - }; + authState.creds.pairingEphemeralKeyPair = pairingEphemeralKeyPair; + authState.creds.me = { id: jid, name: "~" }; ev.emit("creds.update", authState.creds); - await sendNode({ + // Gerar a chave empacotada (salt + iv + encrypted pubkey) + const ephemeralKey = await generatePairingKey( + pairingCode, + pairingEphemeralKeyPair + ); + + const resp = await query({ tag: "iq", attrs: { to: S_WHATSAPP_NET, @@ -527,16 +548,16 @@ export const makeSocket = ({ { tag: "link_code_companion_reg", attrs: { - jid: authState.creds.me.id, + jid, stage: "companion_hello", // eslint-disable-next-line camelcase - should_show_push_notification: "true" + should_show_push_notification: showPushNotification.toString() }, content: [ { tag: "link_code_pairing_wrapped_companion_ephemeral_pub", attrs: {}, - content: await generatePairingKey() + content: ephemeralKey }, { tag: "companion_server_auth_key_pub", @@ -551,31 +572,45 @@ export const makeSocket = ({ { tag: "companion_platform_display", attrs: {}, - content: `${browser[1]} (${browser[0]})` + content: `${browser[1]} (${getPlatformOS(browser[0])})` }, { tag: "link_code_pairing_nonce", attrs: {}, - content: "0" + content: new Uint8Array([0]) } ] } ] }); - return authState.creds.pairingCode; + // Ler o pairing_ref da resposta (igual ao Go) + const regNode = getBinaryNodeChild(resp, "link_code_companion_reg"); + const pairingRefNode = getBinaryNodeChild(regNode, "link_code_pairing_ref"); + + if (!pairingRefNode) { + throw new Boom("link_code_pairing_ref not found in response"); + } + + const pairingRef = (pairingRefNode.content as Buffer).toString("utf-8"); + + // Armazenar para uso posterior no pair-success + authState.creds.pairingRef = pairingRef; + ev.emit("creds.update", authState.creds); + + // Formato "XXXX-XXXX" igual ao Go + return pairingCode.slice(0, 4) + "-" + pairingCode.slice(4); }; - async function generatePairingKey() { + async function generatePairingKey( + pairingCode: string, + ephemeralKeyPair: KeyPair + ) { const salt = randomBytes(32); - const randomIv = randomBytes(16); - const key = await derivePairingCodeKey(authState.creds.pairingCode!, salt); - const ciphered = aesEncryptCTR( - authState.creds.pairingEphemeralKeyPair.public, - key, - randomIv - ); - return Buffer.concat([salt, randomIv, ciphered]); + const iv = randomBytes(16); + const key = await derivePairingCodeKey(pairingCode, salt); + const encryptedPubkey = aesEncryptCTR(ephemeralKeyPair.public, key, iv); + return Buffer.concat([salt, iv, encryptedPubkey]); } ws.on("message", onMessageRecieved); diff --git a/src/Types/Auth.ts b/src/Types/Auth.ts index 0a60b8c..a2035a4 100644 --- a/src/Types/Auth.ts +++ b/src/Types/Auth.ts @@ -61,19 +61,26 @@ export type AuthenticationCreds = SignalCreds & { /** number of times history & app state has been synced */ accountSyncCounter: number; accountSettings: AccountSettings; - pairingCode: string | undefined; registered: boolean; + pairingCode: string | undefined; + pairingRef: string | undefined; lastPropHash: string | undefined; + routingInfo: Buffer | undefined; + additionalData?: any | undefined; }; export type SignalDataTypeMap = { "pre-key": KeyPair; - session: any; - "sender-key": any; + session: Uint8Array; + "sender-key": Uint8Array; "sender-key-memory": { [jid: string]: boolean }; "contacts-tc-token": { token: Buffer; timestamp?: string }; "app-state-sync-key": proto.Message.IAppStateSyncKeyData; "app-state-sync-version": LTHashState; + "lid-mapping": string; + "device-list": string[]; + tctoken: { token: Buffer; timestamp?: string }; + "identity-key": Uint8Array; }; export type SignalDataSet = { diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts index 33d64b8..c0a279c 100644 --- a/src/Utils/auth-utils.ts +++ b/src/Utils/auth-utils.ts @@ -218,6 +218,7 @@ export const initAuthCreds = (): AuthenticationCreds => { unarchiveChats: false }, pairingCode: undefined, + pairingRef: undefined, registered: false, lastPropHash: undefined };