diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index cd39f13a..a44c6c60 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -36,7 +36,7 @@ import { signedKeyPair, xmppSignedPreKey } from '../Utils' -import { getPlatformId } from '../Utils/browser-utils' +import { getPlatformId, isAndroidBrowser } from '../Utils/browser-utils' import { assertNodeErrorFree, type BinaryNode, @@ -758,6 +758,15 @@ export const makeSocket = (config: SocketConfig) => { id: jidEncode(phoneNumber, 's.whatsapp.net'), name: '~' } + // Pair code companion_platform_id must be Chrome (1) when using Android + // browser preset. ANDROID_PHONE (16) causes silent timeout, UWP (21) + // causes rejection. Only Chrome (1) works for pair code via web protocol. + // The device still appears as "Android" in linked devices because + // DeviceProps.platformType=ANDROID_PHONE is set in the registration node. + const isAndroid = isAndroidBrowser(browser) + const pairPlatformId = isAndroid ? getPlatformId('Chrome') : getPlatformId(browser[1]) + const pairPlatformDisplay = isAndroid ? 'Chrome (Mac OS)' : `${browser[1]} (${browser[0]})` + ev.emit('creds.update', authState.creds) await sendNode({ tag: 'iq', @@ -790,12 +799,12 @@ export const makeSocket = (config: SocketConfig) => { { tag: 'companion_platform_id', attrs: {}, - content: getPlatformId(browser[1]) + content: pairPlatformId }, { tag: 'companion_platform_display', attrs: {}, - content: `${browser[1]} (${browser[0]})` + content: pairPlatformDisplay }, { tag: 'link_code_pairing_nonce', diff --git a/src/Types/index.ts b/src/Types/index.ts index 0fec86b2..bf8e1549 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -22,6 +22,7 @@ export type BrowsersMap = { baileys(browser: string): [string, string, string] windows(browser: string): [string, string, string] appropriate(browser: string): [string, string, string] + android(apiLevel: string): [string, string, string] } export enum DisconnectReason { diff --git a/src/Utils/browser-utils.ts b/src/Utils/browser-utils.ts index ee2e42dc..d8972085 100644 --- a/src/Utils/browser-utils.ts +++ b/src/Utils/browser-utils.ts @@ -22,10 +22,33 @@ export const Browsers: BrowsersMap = { baileys: browser => ['Baileys', browser, '6.5.0'], windows: browser => ['Windows', browser, '10.0.22631'], /** The appropriate browser based on your OS & release */ - appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()] + appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()], + /** Android companion device. apiLevel is the Android API level (e.g. '14') */ + android: (apiLevel: string) => [apiLevel, 'Android', ''] +} + +/** + * Checks if the browser tuple represents an Android companion device. + * @param browser - Browser tuple [os, platform, version] + * @returns True if platform is 'Android' (case-insensitive) + */ +export const isAndroidBrowser = (browser: [string, string, string]): boolean => { + return browser[1]?.toUpperCase() === 'ANDROID' } export const getPlatformId = (browser: string) => { const platformType = proto.DeviceProps.PlatformType[browser.toUpperCase() as any] - return platformType ? platformType.toString() : '1' //chrome + if (platformType !== undefined) { + return platformType.toString() + } + + // 'ANDROID' is not in the PlatformType enum — map to ANDROID_PHONE + if (browser.toUpperCase() === 'ANDROID') { + const androidPhone = proto.DeviceProps.PlatformType['ANDROID_PHONE' as any] + if (androidPhone !== undefined) { + return androidPhone.toString() + } + } + + return '1' // Chrome } diff --git a/src/Utils/validate-connection.ts b/src/Utils/validate-connection.ts index ba130da3..ce75918b 100644 --- a/src/Utils/validate-connection.ts +++ b/src/Utils/validate-connection.ts @@ -14,13 +14,17 @@ import { encodeBigEndian } from './generics' import { createSignalIdentity } from './signal' const getUserAgent = (config: SocketConfig): proto.ClientPayload.IUserAgent => { + // Always use MACOS platform for UserAgent — we connect via web protocol + // (WA\x06\x03) so the server expects a web-compatible identity. + // Using WEB causes 405 errors; using SMB_ANDROID breaks pair code. + // Android identity is only set in DeviceProps (registration node). return { appVersion: { primary: config.version[0], secondary: config.version[1], tertiary: config.version[2] }, - platform: proto.ClientPayload.UserAgent.Platform.WEB, + platform: proto.ClientPayload.UserAgent.Platform.MACOS, releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE, osVersion: '0.1', device: 'Desktop', @@ -79,6 +83,11 @@ export const generateLoginNode = (userJid: string, config: SocketConfig): proto. const getPlatformType = (platform: string): proto.DeviceProps.PlatformType => { const platformType = platform.toUpperCase() + // 'ANDROID' is not in PlatformType enum — map to ANDROID_PHONE + if (platformType === 'ANDROID') { + return proto.DeviceProps.PlatformType.ANDROID_PHONE + } + return ( proto.DeviceProps.PlatformType[platformType as keyof typeof proto.DeviceProps.PlatformType] || proto.DeviceProps.PlatformType.CHROME