From 61a0ff31783e615520f72251db7651cdd655f4c6 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Mon, 14 Oct 2024 03:39:46 +0300 Subject: [PATCH] mobile: deprecation. --- Example/example.ts | 90 +------ README.md | 10 - package.json | 1 - src/Defaults/index.ts | 16 -- src/Socket/Client/index.ts | 5 +- src/Socket/Client/mobile-socket-client.ts | 66 ----- .../{abstract-socket-client.ts => types.ts} | 0 .../{web-socket-client.ts => websocket.ts} | 2 +- src/Socket/chats.ts | 10 +- src/Socket/index.ts | 4 +- src/Socket/messages-send.ts | 12 +- src/Socket/registration.ts | 250 ------------------ src/Socket/socket.ts | 24 +- src/Types/Auth.ts | 9 +- src/Types/Socket.ts | 4 +- src/Utils/auth-utils.ts | 8 - src/Utils/noise-handler.ts | 18 +- src/Utils/validate-connection.ts | 51 +--- 18 files changed, 47 insertions(+), 533 deletions(-) delete mode 100644 src/Socket/Client/mobile-socket-client.ts rename src/Socket/Client/{abstract-socket-client.ts => types.ts} (100%) rename src/Socket/Client/{web-socket-client.ts => websocket.ts} (95%) delete mode 100644 src/Socket/registration.ts diff --git a/Example/example.ts b/Example/example.ts index 66ab3023944..63fae25487a 100644 --- a/Example/example.ts +++ b/Example/example.ts @@ -1,7 +1,7 @@ import { Boom } from '@hapi/boom' import NodeCache from 'node-cache' import readline from 'readline' -import makeWASocket, { AnyMessageContent, BinaryInfo, delay, DisconnectReason, downloadAndProcessHistorySyncNotification, encodeWAM, fetchLatestBaileysVersion, getAggregateVotesInPollMessage, getHistoryMsg, isJidNewsletter, makeCacheableSignalKeyStore, makeInMemoryStore, PHONENUMBER_MCC, proto, useMultiFileAuthState, WAMessageContent, WAMessageKey } from '../src' +import makeWASocket, { AnyMessageContent, BinaryInfo, delay, DisconnectReason, downloadAndProcessHistorySyncNotification, encodeWAM, fetchLatestBaileysVersion, getAggregateVotesInPollMessage, getHistoryMsg, isJidNewsletter, makeCacheableSignalKeyStore, makeInMemoryStore, proto, useMultiFileAuthState, WAMessageContent, WAMessageKey } from '../src' //import MAIN_LOGGER from '../src/Utils/logger' import open from 'open' import fs from 'fs' @@ -13,7 +13,6 @@ logger.level = 'trace' const useStore = !process.argv.includes('--no-store') const doReplies = process.argv.includes('--do-reply') const usePairingCode = process.argv.includes('--use-pairing-code') -const useMobile = process.argv.includes('--mobile') // external map to store retry counts of messages when decryption/encryption fails // keep this out of the socket itself, so as to prevent a message decryption/encryption loop across socket restarts @@ -45,7 +44,6 @@ const startSock = async() => { version, logger, printQRInTerminal: !usePairingCode, - mobile: useMobile, auth: { creds: state.creds, /** caching makes the store faster to send/recv messages */ @@ -63,93 +61,13 @@ const startSock = async() => { store?.bind(sock.ev) // Pairing code for Web clients - if(usePairingCode && !sock.authState.creds.registered) { - if(useMobile) { - throw new Error('Cannot use pairing code with mobile api') - } - - const phoneNumber = await question('Please enter your mobile phone number:\n') + if (usePairingCode && !sock.authState.creds.registered) { + // todo move to QR event + const phoneNumber = await question('Please enter your phone number:\n') const code = await sock.requestPairingCode(phoneNumber) console.log(`Pairing code: ${code}`) } - // If mobile was chosen, ask for the code - if(useMobile && !sock.authState.creds.registered) { - const { registration } = sock.authState.creds || { registration: {} } - - if(!registration.phoneNumber) { - registration.phoneNumber = await question('Please enter your mobile phone number:\n') - } - - const libPhonenumber = await import("libphonenumber-js") - const phoneNumber = libPhonenumber.parsePhoneNumber(registration!.phoneNumber) - if(!phoneNumber?.isValid()) { - throw new Error('Invalid phone number: ' + registration!.phoneNumber) - } - - registration.phoneNumber = phoneNumber.format('E.164') - registration.phoneNumberCountryCode = phoneNumber.countryCallingCode - registration.phoneNumberNationalNumber = phoneNumber.nationalNumber - const mcc = PHONENUMBER_MCC[phoneNumber.countryCallingCode] - if(!mcc) { - throw new Error('Could not find MCC for phone number: ' + registration!.phoneNumber + '\nPlease specify the MCC manually.') - } - - registration.phoneNumberMobileCountryCode = mcc - - async function enterCode() { - try { - const code = await question('Please enter the one time code:\n') - const response = await sock.register(code.replace(/["']/g, '').trim().toLowerCase()) - console.log('Successfully registered your phone number.') - console.log(response) - rl.close() - } catch(error) { - console.error('Failed to register your phone number. Please try again.\n', error) - await askForOTP() - } - } - - async function enterCaptcha() { - const response = await sock.requestRegistrationCode({ ...registration, method: 'captcha' }) - const path = __dirname + '/captcha.png' - fs.writeFileSync(path, Buffer.from(response.image_blob!, 'base64')) - - open(path) - const code = await question('Please enter the captcha code:\n') - fs.unlinkSync(path) - registration.captcha = code.replace(/["']/g, '').trim().toLowerCase() - } - - async function askForOTP() { - if (!registration.method) { - await delay(2000) - let code = await question('How would you like to receive the one time code for registration? "sms" or "voice"\n') - code = code.replace(/["']/g, '').trim().toLowerCase() - if(code !== 'sms' && code !== 'voice') { - return await askForOTP() - } - - registration.method = code - } - - try { - await sock.requestRegistrationCode(registration) - await enterCode() - } catch(error) { - console.error('Failed to request registration code. Please try again.\n', error) - - if(error?.reason === 'code_checkpoint') { - await enterCaptcha() - } - - await askForOTP() - } - } - - askForOTP() - } - const sendMessageWTyping = async(msg: AnyMessageContent, jid: string) => { await sock.presenceSubscribe(jid) await delay(500) diff --git a/README.md b/README.md index 8fa7739ff40..bbcfdb4cd44 100644 --- a/README.md +++ b/README.md @@ -89,16 +89,6 @@ connectToWhatsApp() If the connection is successful, you will see a QR code printed on your terminal screen, scan it with WhatsApp on your phone and you'll be logged in! -**Note:** install `qrcode-terminal` using `yarn add qrcode-terminal` to auto-print the QR to the terminal. - -**Note:** the code to support the legacy version of WA Web (pre multi-device) has been removed in v5. Only the standard multi-device connection is now supported. This is done as WA seems to have completely dropped support for the legacy version. - -## Connecting native mobile api - -Baileys also supports the native mobile API, which allows users to authenticate as a standalone WhatsApp client using their phone number. - -Run the [example](Example/example.ts) file with ``--mobile`` cli flag to use the native mobile API. - ## Configuring the Connection You can configure the connection by passing a `SocketConfig` object. diff --git a/package.json b/package.json index ef13dcf3139..baf87aeefcb 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "changelog:preview": "conventional-changelog -p angular -u", "changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", "example": "node --inspect -r ts-node/register Example/example.ts", - "example:mobile": "node --inspect -r ts-node/register Example/example.ts --mobile", "gen:protobuf": "sh WAProto/GenerateStatics.sh", "lint": "eslint src --ext .js,.ts,.jsx,.tsx", "lint:fix": "eslint src --fix --ext .js,.ts,.jsx,.tsx", diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index d9c4bdf070d..ba6bf2cfda4 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -1,35 +1,19 @@ -import { createHash } from 'crypto' import { proto } from '../../WAProto' import { makeLibSignalRepository } from '../Signal/libsignal' import type { AuthenticationState, MediaType, SocketConfig, WAVersion } from '../Types' import { Browsers } from '../Utils' import logger from '../Utils/logger' import { version } from './baileys-version.json' -import phoneNumberMCC from './phonenumber-mcc.json' export const UNAUTHORIZED_CODES = [401, 403, 419] -export const PHONENUMBER_MCC = phoneNumberMCC - export const DEFAULT_ORIGIN = 'https://web.whatsapp.com' -export const MOBILE_ENDPOINT = 'g.whatsapp.net' -export const MOBILE_PORT = 443 export const DEF_CALLBACK_PREFIX = 'CB:' export const DEF_TAG_PREFIX = 'TAG:' export const PHONE_CONNECTION_CB = 'CB:Pong' export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60 -const WA_VERSION = '2.24.6.77' - -const WA_VERSION_HASH = createHash('md5').update(WA_VERSION).digest('hex') -export const MOBILE_TOKEN = Buffer.from('0a1mLfGUIBVrMKF1RdvLI5lkRBvof6vn0fD2QRSM' + WA_VERSION_HASH) -export const MOBILE_REGISTRATION_ENDPOINT = 'https://v.whatsapp.net/v2' -export const MOBILE_USERAGENT = `WhatsApp/${WA_VERSION} iOS/15.3.1 Device/Apple-iPhone_7` -export const REGISTRATION_PUBLIC_KEY = Buffer.from([ - 5, 142, 140, 15, 116, 195, 235, 197, 215, 166, 134, 92, 108, 60, 132, 56, 86, 176, 97, 33, 204, 232, 234, 119, 77, - 34, 251, 111, 18, 37, 18, 48, 45, -]) export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0' export const DICT_VERSION = 2 export const KEY_BUNDLE_TYPE = Buffer.from([5]) diff --git a/src/Socket/Client/index.ts b/src/Socket/Client/index.ts index 8843b0a8941..d5e782c7ddc 100644 --- a/src/Socket/Client/index.ts +++ b/src/Socket/Client/index.ts @@ -1,3 +1,2 @@ -export * from './abstract-socket-client' -export * from './mobile-socket-client' -export * from './web-socket-client' \ No newline at end of file +export * from './types' +export * from './websocket' \ No newline at end of file diff --git a/src/Socket/Client/mobile-socket-client.ts b/src/Socket/Client/mobile-socket-client.ts deleted file mode 100644 index 90cc63cab78..00000000000 --- a/src/Socket/Client/mobile-socket-client.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { connect, Socket } from 'net' -import { AbstractSocketClient } from './abstract-socket-client' - -export class MobileSocketClient extends AbstractSocketClient { - protected socket: Socket | null = null - - get isOpen(): boolean { - return this.socket?.readyState === 'open' - } - get isClosed(): boolean { - return this.socket === null || this.socket?.readyState === 'closed' - } - get isClosing(): boolean { - return this.socket === null || this.socket?.readyState === 'closed' - } - get isConnecting(): boolean { - return this.socket?.readyState === 'opening' - } - - async connect(): Promise { - if(this.socket) { - return - } - - if(this.config.agent) { - - throw new Error('There are not support for proxy agent for mobile connection') - } else { - this.socket = connect({ - host: this.url.hostname, - port: Number(this.url.port) || 443 - }) - } - - this.socket.setMaxListeners(0) - - const events = ['close', 'connect', 'data', 'drain', 'end', 'error', 'lookup', 'ready', 'timeout'] - - for(const event of events) { - this.socket?.on(event, (...args: any[]) => this.emit(event, ...args)) - } - - this.socket.on('data', (...args: any[]) => this.emit('message', ...args)) - this.socket.on('ready', (...args: any[]) => this.emit('open', ...args)) - } - - async close(): Promise { - if(!this.socket) { - return - } - - return new Promise(resolve => { - this.socket!.end(resolve) - this.socket = null - }) - } - - send(str: string | Uint8Array, cb?: (err?: Error) => void): boolean { - if(this.socket === null) { - return false - } - - return this.socket.write(str, undefined, cb) - } - -} diff --git a/src/Socket/Client/abstract-socket-client.ts b/src/Socket/Client/types.ts similarity index 100% rename from src/Socket/Client/abstract-socket-client.ts rename to src/Socket/Client/types.ts diff --git a/src/Socket/Client/web-socket-client.ts b/src/Socket/Client/websocket.ts similarity index 95% rename from src/Socket/Client/web-socket-client.ts rename to src/Socket/Client/websocket.ts index 987d0afbba0..ea8ddfd38e5 100644 --- a/src/Socket/Client/web-socket-client.ts +++ b/src/Socket/Client/websocket.ts @@ -1,6 +1,6 @@ import WebSocket from 'ws' import { DEFAULT_ORIGIN } from '../../Defaults' -import { AbstractSocketClient } from './abstract-socket-client' +import { AbstractSocketClient } from './types' export class WebSocketClient extends AbstractSocketClient { diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index bddd21061c8..ff8b9654008 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -3,12 +3,12 @@ import NodeCache from 'node-cache' import { proto } from '../../WAProto' import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES } from '../Defaults' import { ALL_WA_PATCH_NAMES, ChatModification, ChatMutation, LTHashState, MessageUpsertType, PresenceData, SocketConfig, WABusinessHoursConfig, WABusinessProfile, WAMediaUpload, WAMessage, WAPatchCreate, WAPatchName, WAPresence, WAPrivacyCallValue, WAPrivacyGroupAddValue, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '../Types' +import { LabelActionBody } from '../Types/Label' import { chatModificationToAppPatch, ChatMutationMap, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, extractSyncdPatches, generateProfilePicture, getHistoryMsg, newLTHashState, processSyncAction } from '../Utils' import { makeMutex } from '../Utils/make-mutex' import processMessage from '../Utils/process-message' import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary' import { makeSocket } from './socket' -import { Label, LabelActionBody } from '../Types/Label' const MAX_SYNC_ATTEMPTS = 2 @@ -221,7 +221,7 @@ export const makeChatsSocket = (config: SocketConfig) => { /** update the profile picture for yourself or a group */ const updateProfilePicture = async(jid: string, content: WAMediaUpload) => { - let targetJid; + let targetJid if(!jid) { throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update') } @@ -251,7 +251,7 @@ export const makeChatsSocket = (config: SocketConfig) => { /** remove the profile picture for yourself or a group */ const removeProfilePicture = async(jid: string) => { - let targetJid; + let targetJid if(!jid) { throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update') } @@ -777,6 +777,7 @@ export const makeChatsSocket = (config: SocketConfig) => { authState.creds.lastPropHash = propsNode?.attrs?.hash ev.emit('creds.update', authState.creds) } + props = reduceBinaryNodeToDictionary(propsNode, 'prop') } @@ -1002,7 +1003,8 @@ export const makeChatsSocket = (config: SocketConfig) => { // if we don't have the app state key // we keep buffering events until we finally have // the key and can sync the messages - if(!authState.creds?.myAppStateKeyId && !config.mobile) { + // todo scrutinize + if(!authState.creds?.myAppStateKeyId) { ev.buffer() needToFlushWithAppStateSync = true } diff --git a/src/Socket/index.ts b/src/Socket/index.ts index 7338c9cefd2..30d8871d8c6 100644 --- a/src/Socket/index.ts +++ b/src/Socket/index.ts @@ -1,10 +1,10 @@ import { DEFAULT_CONNECTION_CONFIG } from '../Defaults' import { UserFacingSocketConfig } from '../Types' -import { makeRegistrationSocket as _makeSocket } from './registration' +import { makeBusinessSocket } from './business' // export the last socket layer const makeWASocket = (config: UserFacingSocketConfig) => ( - _makeSocket({ + makeBusinessSocket({ ...DEFAULT_CONNECTION_CONFIG, ...config }) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 32e88ddbf2e..215a0957f8c 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -469,17 +469,15 @@ export const makeMessagesSocket = (config: SocketConfig) => { await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) } else { - const { user: meUser, device: meDevice } = jidDecode(meId)! + const { user: meUser } = jidDecode(meId)! if(!participant) { devices.push({ user }) - // do not send message to self if the device is 0 (mobile) - - if(!(additionalAttributes?.['category'] === 'peer' && user === meUser)) { - if(meDevice !== undefined && meDevice !== 0) { - devices.push({ user: meUser }) - } + if(user !== meUser) { + devices.push({ user: meUser }) + } + if(additionalAttributes?.['category'] !== 'peer') { const additionalDevices = await getUSyncDevices([ meId, jid ], !!useUserDevicesCache, true) devices.push(...additionalDevices) } diff --git a/src/Socket/registration.ts b/src/Socket/registration.ts deleted file mode 100644 index 6562855b9d3..00000000000 --- a/src/Socket/registration.ts +++ /dev/null @@ -1,250 +0,0 @@ -/* eslint-disable camelcase */ -import axios, { AxiosRequestConfig } from 'axios' -import { MOBILE_REGISTRATION_ENDPOINT, MOBILE_TOKEN, MOBILE_USERAGENT, REGISTRATION_PUBLIC_KEY } from '../Defaults' -import { KeyPair, SignedKeyPair, SocketConfig } from '../Types' -import { aesEncryptGCM, Curve, md5 } from '../Utils/crypto' -import { jidEncode } from '../WABinary' -import { makeBusinessSocket } from './business' - -function urlencode(str: string) { - return str.replace(/-/g, '%2d').replace(/_/g, '%5f').replace(/~/g, '%7e') -} - -const validRegistrationOptions = (config: RegistrationOptions) => config?.phoneNumberCountryCode && - config.phoneNumberNationalNumber && - config.phoneNumberMobileCountryCode - -export const makeRegistrationSocket = (config: SocketConfig) => { - const sock = makeBusinessSocket(config) - - const register = async(code: string) => { - if(!validRegistrationOptions(config.auth.creds.registration)) { - throw new Error('please specify the registration options') - } - - const result = await mobileRegister({ ...sock.authState.creds, ...sock.authState.creds.registration as RegistrationOptions, code }, config.options) - - sock.authState.creds.me = { - id: jidEncode(result.login!, 's.whatsapp.net'), - name: '~' - } - - sock.authState.creds.registered = true - sock.ev.emit('creds.update', sock.authState.creds) - - return result - } - - const requestRegistrationCode = async(registrationOptions?: RegistrationOptions) => { - registrationOptions = registrationOptions || config.auth.creds.registration - if(!validRegistrationOptions(registrationOptions)) { - throw new Error('Invalid registration options') - } - - sock.authState.creds.registration = registrationOptions - - sock.ev.emit('creds.update', sock.authState.creds) - - return mobileRegisterCode({ ...config.auth.creds, ...registrationOptions }, config.options) - } - - return { - ...sock, - register, - requestRegistrationCode, - } -} - -// Backup_token: Base64.getEncoder().encodeToString(Arrays.copyOfRange(Base64.getDecoder().decode(UUID.randomUUID().toString().replace('-','')),0,15)) - -export interface RegistrationData { - registrationId: number - signedPreKey: SignedKeyPair - noiseKey: KeyPair - signedIdentityKey: KeyPair - identityId: Buffer - phoneId: string - deviceId: string - backupToken: Buffer -} - -export interface RegistrationOptions { - /** your phone number */ - phoneNumber?: string - /** the country code of your phone number */ - phoneNumberCountryCode: string - /** your phone number without country code */ - phoneNumberNationalNumber: string - /** the country code of your mobile network - * @see {@link https://de.wikipedia.org/wiki/Mobile_Country_Code} - */ - phoneNumberMobileCountryCode: string - /** the network code of your mobile network - * @see {@link https://de.wikipedia.org/wiki/Mobile_Network_Code} - */ - phoneNumberMobileNetworkCode: string - /** - * How to send the one time code - */ - method?: 'sms' | 'voice' | 'captcha' - /** - * The captcha code if it was requested - */ - captcha?: string -} - -export type RegistrationParams = RegistrationData & RegistrationOptions - -function convertBufferToUrlHex(buffer: Buffer) { - var id = '' - - buffer.forEach((x) => { - // encode random identity_id buffer as percentage url encoding - id += `%${x.toString(16).padStart(2, '0').toLowerCase()}` - }) - - return id -} - -export function registrationParams(params: RegistrationParams) { - const e_regid = Buffer.alloc(4) - e_regid.writeInt32BE(params.registrationId) - - const e_skey_id = Buffer.alloc(3) - e_skey_id.writeInt16BE(params.signedPreKey.keyId) - - params.phoneNumberCountryCode = params.phoneNumberCountryCode.replace('+', '').trim() - params.phoneNumberNationalNumber = params.phoneNumberNationalNumber.replace(/[/-\s)(]/g, '').trim() - - return { - cc: params.phoneNumberCountryCode, - in: params.phoneNumberNationalNumber, - Rc: '0', - lg: 'en', - lc: 'GB', - mistyped: '6', - authkey: Buffer.from(params.noiseKey.public).toString('base64url'), - e_regid: e_regid.toString('base64url'), - e_keytype: 'BQ', - e_ident: Buffer.from(params.signedIdentityKey.public).toString('base64url'), - // e_skey_id: e_skey_id.toString('base64url'), - e_skey_id: 'AAAA', - e_skey_val: Buffer.from(params.signedPreKey.keyPair.public).toString('base64url'), - e_skey_sig: Buffer.from(params.signedPreKey.signature).toString('base64url'), - fdid: params.phoneId, - network_ratio_type: '1', - expid: params.deviceId, - simnum: '1', - hasinrc: '1', - pid: Math.floor(Math.random() * 1000).toString(), - id: convertBufferToUrlHex(params.identityId), - backup_token: convertBufferToUrlHex(params.backupToken), - token: md5(Buffer.concat([MOBILE_TOKEN, Buffer.from(params.phoneNumberNationalNumber)])).toString('hex'), - fraud_checkpoint_code: params.captcha, - } -} - -/** - * Requests a registration code for the given phone number. - */ -export function mobileRegisterCode(params: RegistrationParams, fetchOptions?: AxiosRequestConfig) { - return mobileRegisterFetch('/code', { - params: { - ...registrationParams(params), - mcc: `${params.phoneNumberMobileCountryCode}`.padStart(3, '0'), - mnc: `${params.phoneNumberMobileNetworkCode || '001'}`.padStart(3, '0'), - sim_mcc: '000', - sim_mnc: '000', - method: params?.method || 'sms', - reason: '', - hasav: '1' - }, - ...fetchOptions, - }) -} - -export function mobileRegisterExists(params: RegistrationParams, fetchOptions?: AxiosRequestConfig) { - return mobileRegisterFetch('/exist', { - params: registrationParams(params), - ...fetchOptions - }) -} - -/** - * Registers the phone number on whatsapp with the received OTP code. - */ -export async function mobileRegister(params: RegistrationParams & { code: string }, fetchOptions?: AxiosRequestConfig) { - //const result = await mobileRegisterFetch(`/reg_onboard_abprop?cc=${params.phoneNumberCountryCode}&in=${params.phoneNumberNationalNumber}&rc=0`) - - return mobileRegisterFetch('/register', { - params: { ...registrationParams(params), code: params.code.replace('-', '') }, - ...fetchOptions, - }) -} - -/** - * Encrypts the given string as AEAD aes-256-gcm with the public whatsapp key and a random keypair. - */ -export function mobileRegisterEncrypt(data: string) { - const keypair = Curve.generateKeyPair() - const key = Curve.sharedKey(keypair.private, REGISTRATION_PUBLIC_KEY) - - const buffer = aesEncryptGCM(Buffer.from(data), new Uint8Array(key), Buffer.alloc(12), Buffer.alloc(0)) - - return Buffer.concat([Buffer.from(keypair.public), buffer]).toString('base64url') -} - -export async function mobileRegisterFetch(path: string, opts: AxiosRequestConfig = {}) { - let url = `${MOBILE_REGISTRATION_ENDPOINT}${path}` - - if(opts.params) { - const parameter = [] as string[] - - for(const param in opts.params) { - if(opts.params[param] !== null && opts.params[param] !== undefined) { - parameter.push(param + '=' + urlencode(opts.params[param])) - } - } - - url += `?${parameter.join('&')}` - delete opts.params - } - - if(!opts.headers) { - opts.headers = {} - } - - opts.headers['User-Agent'] = MOBILE_USERAGENT - - const response = await axios(url, opts) - - var json = response.data - - if(response.status > 300 || json.reason) { - throw json - } - - if(json.status && !['ok', 'sent'].includes(json.status)) { - throw json - } - - return json as ExistsResponse -} - - -export interface ExistsResponse { - status: 'fail' | 'sent' - voice_length?: number - voice_wait?: number - sms_length?: number - sms_wait?: number - reason?: 'incorrect' | 'missing_param' | 'code_checkpoint' - login?: string - flash_type?: number - ab_hash?: string - ab_key?: string - exp_cfg?: string - lid?: string - image_blob?: string - audio_blob?: string -} diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index bca4e19f23a..d0d03b2626f 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -8,9 +8,6 @@ import { DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, - MOBILE_ENDPOINT, - MOBILE_NOISE_HEADER, - MOBILE_PORT, NOISE_WA_HEADER } from '../Defaults' import { DisconnectReason, SocketConfig } from '../Types' @@ -24,7 +21,6 @@ import { derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, - generateMobileNode, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, @@ -45,7 +41,7 @@ import { jidEncode, S_WHATSAPP_NET } from '../WABinary' -import { MobileSocketClient, WebSocketClient } from './Client' +import { WebSocketClient } from './Client' /** * Connects to WA servers and performs: @@ -69,19 +65,18 @@ export const makeSocket = (config: SocketConfig) => { makeSignalRepository, } = config - let url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl + const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl - config.mobile = config.mobile || url.protocol === 'tcp:' - if(config.mobile && url.protocol !== 'tcp:') { - url = new URL(`tcp://${MOBILE_ENDPOINT}:${MOBILE_PORT}`) + if(config.mobile || url.protocol === 'tcp:') { + throw new Boom('Mobile API is not supported anymore', { statusCode: DisconnectReason.loggedOut }) } - if(!config.mobile && url.protocol === 'wss' && authState?.creds?.routingInfo) { + if(url.protocol === 'wss' && authState?.creds?.routingInfo) { url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url')) } - const ws = config.socket ? config.socket : config.mobile ? new MobileSocketClient(url, config) : new WebSocketClient(url, config) + const ws = config.socket ? config.socket : new WebSocketClient(url, config) ws.connect() @@ -91,8 +86,7 @@ export const makeSocket = (config: SocketConfig) => { /** WA noise protocol wrapper */ const noise = makeNoiseHandler({ keyPair: ephemeralKeyPair, - NOISE_HEADER: config.mobile ? MOBILE_NOISE_HEADER : NOISE_WA_HEADER, - mobile: config.mobile, + NOISE_HEADER: NOISE_WA_HEADER, logger, routingInfo: authState?.creds?.routingInfo }) @@ -247,9 +241,7 @@ export const makeSocket = (config: SocketConfig) => { const keyEnc = noise.processHandshake(handshake, creds.noiseKey) let node: proto.IClientPayload - if(config.mobile) { - node = generateMobileNode(config) - } else if(!creds.me) { + if(!creds.me) { node = generateRegistrationNode(creds, config) logger.info({ node }, 'not logged in, attempting registration...') } else { diff --git a/src/Types/Auth.ts b/src/Types/Auth.ts index 4338286f2e5..c0c7ece6ade 100644 --- a/src/Types/Auth.ts +++ b/src/Types/Auth.ts @@ -1,5 +1,4 @@ import type { proto } from '../../WAProto' -import { RegistrationOptions } from '../Socket/registration' import type { Contact } from './Contact' import type { MinimalMessage } from './Message' @@ -60,13 +59,7 @@ export type AuthenticationCreds = SignalCreds & { /** number of times history & app state has been synced */ accountSyncCounter: number accountSettings: AccountSettings - // mobile creds - deviceId: string - phoneId: string - identityId: Buffer - registered: boolean - backupToken: Buffer - registration: RegistrationOptions + registered: boolean pairingCode: string | undefined lastPropHash: string | undefined routingInfo: Buffer | undefined diff --git a/src/Types/Socket.ts b/src/Types/Socket.ts index fa60cca26a8..7e532f448a3 100644 --- a/src/Types/Socket.ts +++ b/src/Types/Socket.ts @@ -32,7 +32,9 @@ export type SocketConfig = { defaultQueryTimeoutMs: number | undefined /** ping-pong interval for WS connection */ keepAliveIntervalMs: number - /** should baileys use the mobile api instead of the multi device api */ + /** should baileys use the mobile api instead of the multi device api + * @deprecated This feature has been removed + */ mobile?: boolean /** proxy agent */ agent?: Agent diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts index b299e445b21..ce42ceeedb6 100644 --- a/src/Utils/auth-utils.ts +++ b/src/Utils/auth-utils.ts @@ -1,7 +1,6 @@ import { randomBytes } from 'crypto' import NodeCache from 'node-cache' import type { Logger } from 'pino' -import { v4 as uuidv4 } from 'uuid' import { DEFAULT_CACHE_TTLS } from '../Defaults' import type { AuthenticationCreds, CacheStore, SignalDataSet, SignalDataTypeMap, SignalKeyStore, SignalKeyStoreWithTransaction, TransactionCapabilityOptions } from '../Types' import { Curve, signedKeyPair } from './crypto' @@ -208,13 +207,6 @@ export const initAuthCreds = (): AuthenticationCreds => { accountSettings: { unarchiveChats: false }, - // mobile creds - deviceId: Buffer.from(uuidv4().replace(/-/g, ''), 'hex').toString('base64url'), - phoneId: uuidv4(), - identityId: randomBytes(20), - registered: false, - backupToken: randomBytes(20), - registration: {} as never, pairingCode: undefined, lastPropHash: undefined, routingInfo: undefined, diff --git a/src/Utils/noise-handler.ts b/src/Utils/noise-handler.ts index 84cbe736624..9ea89ec30d7 100644 --- a/src/Utils/noise-handler.ts +++ b/src/Utils/noise-handler.ts @@ -16,13 +16,11 @@ const generateIV = (counter: number) => { export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, - mobile, logger, routingInfo }: { keyPair: KeyPair NOISE_HEADER: Uint8Array - mobile: boolean logger: Logger routingInfo?: Buffer | undefined }) => { @@ -113,16 +111,12 @@ export const makeNoiseHandler = ({ const certDecoded = decrypt(serverHello!.payload!) - if(mobile) { - proto.CertChain.NoiseCertificate.decode(certDecoded) - } else { - const { intermediate: certIntermediate } = proto.CertChain.decode(certDecoded) + const { intermediate: certIntermediate } = proto.CertChain.decode(certDecoded) - const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate!.details!) + const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate!.details!) - if(issuerSerial !== WA_CERT_DETAILS.SERIAL) { - throw new Boom('certification match failed', { statusCode: 400 }) - } + if(issuerSerial !== WA_CERT_DETAILS.SERIAL) { + throw new Boom('certification match failed', { statusCode: 400 }) } const keyEnc = encrypt(noiseKey.public) @@ -183,11 +177,11 @@ export const makeNoiseHandler = ({ inBytes = inBytes.slice(size + 3) if(isFinished) { - const result = decrypt(frame as Uint8Array) + const result = decrypt(frame) frame = await decodeBinaryNode(result) } - logger.trace({ msg: (frame as any)?.attrs?.id }, 'recv frame') + logger.trace({ msg: (frame as BinaryNode)?.attrs?.id }, 'recv frame') onFrame(frame) size = getBytesSize() diff --git a/src/Utils/validate-connection.ts b/src/Utils/validate-connection.ts index 6057d5dc236..36a15acd6f3 100644 --- a/src/Utils/validate-connection.ts +++ b/src/Utils/validate-connection.ts @@ -9,30 +9,20 @@ import { encodeBigEndian } from './generics' import { createSignalIdentity } from './signal' const getUserAgent = (config: SocketConfig): proto.ClientPayload.IUserAgent => { - const osVersion = config.mobile ? '15.3.1' : '0.1' - const version = config.mobile ? [2, 24, 6] : config.version - const device = config.mobile ? 'iPhone_7' : 'Desktop' - const manufacturer = config.mobile ? 'Apple' : '' - const platform = config.mobile ? proto.ClientPayload.UserAgent.Platform.IOS : proto.ClientPayload.UserAgent.Platform.WEB - const phoneId = config.mobile ? { phoneId: config.auth.creds.phoneId } : {} return { appVersion: { - primary: version[0], - secondary: version[1], - tertiary: version[2], + primary: config.version[0], + secondary: config.version[1], + tertiary: config.version[2], }, - platform, + platform: proto.ClientPayload.UserAgent.Platform.WEB, releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE, - mcc: config.auth.creds.registration?.phoneNumberMobileCountryCode || '000', - mnc: config.auth.creds.registration?.phoneNumberMobileNetworkCode || '000', - osVersion: osVersion, - manufacturer, - device, - osBuildNumber: osVersion, + osVersion: '0.1', + device: 'Desktop', + osBuildNumber: '0.1', localeLanguageIso6391: 'en', - localeCountryIso31661Alpha2: 'US', - ...phoneId + localeCountryIso31661Alpha2: 'US' } } @@ -58,34 +48,11 @@ const getClientPayload = (config: SocketConfig) => { userAgent: getUserAgent(config), } - if(!config.mobile) { - payload.webInfo = getWebInfo(config) - } + payload.webInfo = getWebInfo(config) return payload } -export const generateMobileNode = (config: SocketConfig): proto.IClientPayload => { - if(!config.auth.creds) { - throw new Boom('No registration data found', { data: config }) - } - - const payload: proto.IClientPayload = { - ...getClientPayload(config), - sessionId: Math.floor(Math.random() * 999999999 + 1), - shortConnect: true, - connectAttemptCount: 0, - device: 0, - dnsSource: { - appCached: false, - dnsMethod: proto.ClientPayload.DNSSource.DNSResolutionMethod.SYSTEM, - }, - passive: false, // XMPP heartbeat setting (false: server actively pings) (true: client actively pings) - pushName: 'test', - username: Number(`${config.auth.creds.registration.phoneNumberCountryCode}${config.auth.creds.registration.phoneNumberNationalNumber}`), - } - return proto.ClientPayload.fromObject(payload) -} export const generateLoginNode = (userJid: string, config: SocketConfig): proto.IClientPayload => { const { user, device } = jidDecode(userJid)!