mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: native-mobile-api
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import parsePhoneNumber from 'libphonenumber-js'
|
||||
import NodeCache from 'node-cache'
|
||||
import makeWASocket, { AnyMessageContent, delay, DisconnectReason, fetchLatestBaileysVersion, getAggregateVotesInPollMessage, makeCacheableSignalKeyStore, makeInMemoryStore, proto, useMultiFileAuthState, WAMessageContent, WAMessageKey } from '../src'
|
||||
import MAIN_LOGGER from '../src/Utils/logger'
|
||||
import P from 'pino'
|
||||
import readline from 'readline'
|
||||
import makeWASocket, { AnyMessageContent, delay, DisconnectReason, fetchLatestBaileysVersion, getAggregateVotesInPollMessage, makeCacheableSignalKeyStore, makeInMemoryStore, PHONENUMBER_MCC, proto, useMultiFileAuthState, WAMessageContent, WAMessageKey } from '../src'
|
||||
|
||||
const logger = MAIN_LOGGER.child({ })
|
||||
logger.level = 'trace'
|
||||
const logger = P({
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
},
|
||||
level: 'trace'
|
||||
})
|
||||
|
||||
const useStore = !process.argv.includes('--no-store')
|
||||
const doReplies = !process.argv.includes('--no-reply')
|
||||
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
|
||||
@@ -33,6 +40,7 @@ const startSock = async() => {
|
||||
version,
|
||||
logger,
|
||||
printQRInTerminal: true,
|
||||
mobile: useMobile,
|
||||
auth: {
|
||||
creds: state.creds,
|
||||
/** caching makes the store faster to send/recv messages */
|
||||
@@ -49,6 +57,69 @@ const startSock = async() => {
|
||||
|
||||
store?.bind(sock.ev)
|
||||
|
||||
// If mobile was chosen, ask for the code
|
||||
if(useMobile && !sock.authState.creds.registered) {
|
||||
const question = (text: string) => new Promise<string>((resolve) => rl.question(text, resolve))
|
||||
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
||||
const { registration } = sock.authState.creds || { registration: {} }
|
||||
|
||||
if(!registration.phoneNumber) {
|
||||
registration.phoneNumber = await question('Please enter your mobile phone number:\n')
|
||||
} else {
|
||||
console.log('Your mobile phone number is not registered.')
|
||||
}
|
||||
|
||||
const phoneNumber = 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 askForOTP() {
|
||||
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)
|
||||
await askForOTP()
|
||||
}
|
||||
}
|
||||
|
||||
askForOTP()
|
||||
}
|
||||
|
||||
const sendMessageWTyping = async(msg: AnyMessageContent, jid: string) => {
|
||||
await sock.presenceSubscribe(jid)
|
||||
await delay(500)
|
||||
|
||||
@@ -36,11 +36,13 @@
|
||||
"@hapi/boom": "^9.1.3",
|
||||
"axios": "^1.3.3",
|
||||
"futoin-hkdf": "^1.5.1",
|
||||
"libphonenumber-js": "^1.10.20",
|
||||
"libsignal": "git+https://github.com/adiwajshing/libsignal-node",
|
||||
"music-metadata": "^7.12.3",
|
||||
"node-cache": "^5.1.2",
|
||||
"pino": "^7.0.0",
|
||||
"protobufjs": "^6.11.3",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -79,11 +81,13 @@
|
||||
"@types/jest": "^27.5.1",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/sharp": "^0.29.4",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/ws": "^8.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"jest": "^27.0.6",
|
||||
"jimp": "^0.16.1",
|
||||
"link-preview-js": "^3.0.0",
|
||||
"pino-pretty": "^9.4.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"sharp": "^0.30.5",
|
||||
"ts-jest": "^27.0.3",
|
||||
|
||||
@@ -4,22 +4,36 @@ import type { AuthenticationState, MediaType, SocketConfig, WAVersion } from '..
|
||||
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
|
||||
|
||||
export const MOBILE_TOKEN = Buffer.from('0a1mLfGUIBVrMKF1RdvLI5lkRBvof6vn0fD2QRSM4174c0243f5277a5d7720ce842cc4ae6')
|
||||
export const MOBILE_REGISTRATION_ENDPOINT = 'https://v.whatsapp.net/v2'
|
||||
export const MOBILE_USERAGENT = 'WhatsApp/2.22.24.81 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])
|
||||
export const NOISE_WA_HEADER = Buffer.from(
|
||||
[ 87, 65, 6, DICT_VERSION ]
|
||||
) // last is "DICT_VERSION"
|
||||
export const PROTOCOL_VERSION = [5, 2]
|
||||
export const MOBILE_NOISE_HEADER = Buffer.concat([Buffer.from('WA'), Buffer.from(PROTOCOL_VERSION)])
|
||||
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
|
||||
export const URL_REGEX = /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/
|
||||
export const URL_EXCLUDE_REGEX = /.*@.*/
|
||||
|
||||
223
src/Defaults/phonenumber-mcc.json
Normal file
223
src/Defaults/phonenumber-mcc.json
Normal file
@@ -0,0 +1,223 @@
|
||||
{
|
||||
"93": 412,
|
||||
"355": 276,
|
||||
"213": 603,
|
||||
"1-684": 544,
|
||||
"376": 213,
|
||||
"244": 631,
|
||||
"1-264": 365,
|
||||
"1-268": 344,
|
||||
"54": 722,
|
||||
"374": 283,
|
||||
"297": 363,
|
||||
"61": 505,
|
||||
"43": 232,
|
||||
"994": 400,
|
||||
"1-242": 364,
|
||||
"973": 426,
|
||||
"880": 470,
|
||||
"1-246": 342,
|
||||
"375": 257,
|
||||
"32": 206,
|
||||
"501": 702,
|
||||
"229": 616,
|
||||
"1-441": 350,
|
||||
"975": 402,
|
||||
"591": 736,
|
||||
"387": 218,
|
||||
"267": 652,
|
||||
"55": 724,
|
||||
"1-284": 348,
|
||||
"673": 528,
|
||||
"359": 284,
|
||||
"226": 613,
|
||||
"257": 642,
|
||||
"855": 456,
|
||||
"237": 624,
|
||||
"238": 625,
|
||||
"1-345": 346,
|
||||
"236": 623,
|
||||
"235": 622,
|
||||
"56": 730,
|
||||
"86": 454,
|
||||
"57": 732,
|
||||
"269": 654,
|
||||
"682": 548,
|
||||
"506": 712,
|
||||
"385": 219,
|
||||
"53": 368,
|
||||
"357": 280,
|
||||
"420": 230,
|
||||
"243": 630,
|
||||
"45": 238,
|
||||
"253": 638,
|
||||
"1-767": 366,
|
||||
"1-809": 370,
|
||||
"1-849": 370,
|
||||
"1-829": 370,
|
||||
"593": 740,
|
||||
"20": 602,
|
||||
"503": 706,
|
||||
"240": 627,
|
||||
"291": 657,
|
||||
"372": 248,
|
||||
"251": 636,
|
||||
"500": 750,
|
||||
"298": 288,
|
||||
"679": 542,
|
||||
"358": 244,
|
||||
"33": 208,
|
||||
"689": 547,
|
||||
"241": 628,
|
||||
"220": 607,
|
||||
"995": 282,
|
||||
"49": 262,
|
||||
"233": 620,
|
||||
"350": 266,
|
||||
"30": 202,
|
||||
"299": 290,
|
||||
"1-473": 352,
|
||||
"1-671": 535,
|
||||
"502": 704,
|
||||
"224": 537,
|
||||
"592": 738,
|
||||
"509": 372,
|
||||
"504": 708,
|
||||
"852": 454,
|
||||
"36": 216,
|
||||
"354": 274,
|
||||
"91": 404,
|
||||
"62": 510,
|
||||
"98": 432,
|
||||
"964": 418,
|
||||
"353": 234,
|
||||
"972": 425,
|
||||
"39": 222,
|
||||
"225": 612,
|
||||
"1-876": 338,
|
||||
"81": 440,
|
||||
"962": 416,
|
||||
"254": 639,
|
||||
"686": 545,
|
||||
"383": 221,
|
||||
"965": 419,
|
||||
"371": 247,
|
||||
"961": 415,
|
||||
"266": 651,
|
||||
"231": 618,
|
||||
"218": 606,
|
||||
"423": 295,
|
||||
"370": 246,
|
||||
"352": 270,
|
||||
"389": 294,
|
||||
"261": 646,
|
||||
"265": 650,
|
||||
"60": 502,
|
||||
"960": 472,
|
||||
"223": 610,
|
||||
"356": 278,
|
||||
"692": 551,
|
||||
"222": 609,
|
||||
"230": 617,
|
||||
"52": 334,
|
||||
"691": 550,
|
||||
"373": 259,
|
||||
"377": 212,
|
||||
"976": 428,
|
||||
"382": 297,
|
||||
"1-664": 354,
|
||||
"212": 604,
|
||||
"258": 643,
|
||||
"95": 414,
|
||||
"264": 649,
|
||||
"674": 536,
|
||||
"977": 429,
|
||||
"31": 204,
|
||||
"687": 546,
|
||||
"64": 530,
|
||||
"505": 710,
|
||||
"227": 614,
|
||||
"234": 621,
|
||||
"683": 555,
|
||||
"1-670": 534,
|
||||
"47": 242,
|
||||
"968": 226,
|
||||
"92": 410,
|
||||
"680": 552,
|
||||
"970": 423,
|
||||
"507": 714,
|
||||
"675": 537,
|
||||
"595": 744,
|
||||
"51": 716,
|
||||
"63": 515,
|
||||
"48": 260,
|
||||
"351": 268,
|
||||
"1-787, 1-939": 330,
|
||||
"974": 427,
|
||||
"242": 630,
|
||||
"40": 226,
|
||||
"7": 250,
|
||||
"250": 635,
|
||||
"290": 658,
|
||||
"1-869": 356,
|
||||
"1-758": 358,
|
||||
"508": 308,
|
||||
"1-784": 360,
|
||||
"685": 544,
|
||||
"378": 292,
|
||||
"239": 626,
|
||||
"966": 420,
|
||||
"221": 608,
|
||||
"381": 220,
|
||||
"248": 633,
|
||||
"232": 619,
|
||||
"65": 525,
|
||||
"386": 293,
|
||||
"677": 540,
|
||||
"27": 655,
|
||||
"211": 659,
|
||||
"34": 214,
|
||||
"94": 413,
|
||||
"249": 634,
|
||||
"597": 746,
|
||||
"268": 653,
|
||||
"46": 240,
|
||||
"41": 228,
|
||||
"963": 417,
|
||||
"886": 466,
|
||||
"992": 436,
|
||||
"255": 640,
|
||||
"66": 520,
|
||||
"228": 615,
|
||||
"690": 554,
|
||||
"676": 539,
|
||||
"1-868": 374,
|
||||
"216": 605,
|
||||
"90": 286,
|
||||
"993": 438,
|
||||
"1-649": 376,
|
||||
"688": 553,
|
||||
"1-340": 332,
|
||||
"256": 641,
|
||||
"380": 255,
|
||||
"971": 424,
|
||||
"44": 234,
|
||||
"1": 310,
|
||||
"598": 748,
|
||||
"998": 434,
|
||||
"678": 541,
|
||||
"379": 225,
|
||||
"58": 734,
|
||||
"681": 543,
|
||||
"967": 421,
|
||||
"260": 645,
|
||||
"263": 648,
|
||||
"670": 514,
|
||||
"245": 632,
|
||||
"856": 457,
|
||||
"599": 362,
|
||||
"850": 467,
|
||||
"262": 647,
|
||||
"82": 450,
|
||||
"84": 452
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults'
|
||||
import { UserFacingSocketConfig } from '../Types'
|
||||
import { makeBusinessSocket as _makeSocket } from './business'
|
||||
import { makeRegistrationSocket as _makeSocket } from './registration'
|
||||
|
||||
// export the last socket layer
|
||||
const makeWASocket = (config: UserFacingSocketConfig) => (
|
||||
|
||||
47
src/Socket/mobile-socket.ts
Normal file
47
src/Socket/mobile-socket.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Socket } from 'net'
|
||||
import { MOBILE_ENDPOINT, MOBILE_PORT } from '../Defaults'
|
||||
import { SocketConfig } from '../Types'
|
||||
|
||||
export class MobileSocket extends Socket {
|
||||
constructor(public config: SocketConfig) {
|
||||
super()
|
||||
|
||||
if(config.auth.creds.registered) {
|
||||
this.connect()
|
||||
}
|
||||
|
||||
this.on('data', (d) => {
|
||||
this.emit('message', d)
|
||||
})
|
||||
}
|
||||
|
||||
override connect() {
|
||||
return super.connect(MOBILE_PORT, MOBILE_ENDPOINT, () => {
|
||||
this.emit('open')
|
||||
})
|
||||
}
|
||||
|
||||
get isOpen(): boolean {
|
||||
return this.readyState === 'open'
|
||||
}
|
||||
|
||||
get isClosed(): boolean {
|
||||
return this.readyState === 'closed'
|
||||
}
|
||||
|
||||
get isClosing(): boolean {
|
||||
return this.isClosed
|
||||
}
|
||||
|
||||
get isConnecting(): boolean {
|
||||
return this.readyState === 'opening'
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.end()
|
||||
}
|
||||
|
||||
send(data: unknown, cb?: ((err?: Error | undefined) => void) | undefined) {
|
||||
return super.write(data as Uint8Array | string, cb as ((err?: Error | undefined) => void))
|
||||
}
|
||||
}
|
||||
251
src/Socket/registration.ts
Normal file
251
src/Socket/registration.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
/* eslint-disable camelcase */
|
||||
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'
|
||||
import { MobileSocket } from './mobile-socket'
|
||||
|
||||
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 })
|
||||
|
||||
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)
|
||||
|
||||
if(sock.ws instanceof MobileSocket) {
|
||||
sock.ws.connect()
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
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'),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a registration code for the given phone number.
|
||||
*/
|
||||
export function mobileRegisterCode(params: RegistrationParams) {
|
||||
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'
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function mobileRegisterExists(params: RegistrationParams) {
|
||||
return mobileRegisterFetch('/exist', {
|
||||
params: registrationParams(params)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the phone number on whatsapp with the received OTP code.
|
||||
*/
|
||||
export async function mobileRegister(params: RegistrationParams & { code: string }) {
|
||||
//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('-', '') },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: { params?: Record<string, string>, headers?: Record<string, string> } = {}) {
|
||||
let url = `${MOBILE_REGISTRATION_ENDPOINT}${path}`
|
||||
|
||||
if(opts.params) {
|
||||
const parameter = [] as string[]
|
||||
|
||||
for(const param in opts.params) {
|
||||
parameter.push(param + '=' + urlencode(opts.params[param]))
|
||||
}
|
||||
|
||||
console.log('parameter', opts.params, parameter)
|
||||
|
||||
// const params = urlencode(mobileRegisterEncrypt(parameter.join('&')))
|
||||
// url += `?ENC=${params}`
|
||||
url += `?${parameter.join('&')}`
|
||||
}
|
||||
|
||||
if(!opts.headers) {
|
||||
opts.headers = {}
|
||||
}
|
||||
|
||||
opts.headers['User-Agent'] = MOBILE_USERAGENT
|
||||
|
||||
const response = await fetch(url, opts)
|
||||
|
||||
const text = await response.text()
|
||||
|
||||
try {
|
||||
var json = JSON.parse(text)
|
||||
} catch(error) {
|
||||
throw text
|
||||
}
|
||||
|
||||
if(!response.ok || json.reason) {
|
||||
throw json
|
||||
}
|
||||
|
||||
if(json.status && !['ok', 'sent'].includes(json.status)) {
|
||||
throw json
|
||||
}
|
||||
|
||||
return json as ExistsResponse
|
||||
}
|
||||
|
||||
|
||||
export interface ExistsResponse {
|
||||
status: 'fail'
|
||||
voice_length?: number
|
||||
voice_wait?: number
|
||||
sms_length?: number
|
||||
sms_wait?: number
|
||||
reason?: 'incorrect' | 'missing_param'
|
||||
login?: string
|
||||
flash_type?: number
|
||||
ab_hash?: string
|
||||
ab_key?: string
|
||||
exp_cfg?: string
|
||||
lid?: string
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { promisify } from 'util'
|
||||
import WebSocket from 'ws'
|
||||
import { proto } from '../../WAProto'
|
||||
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, DEFAULT_ORIGIN, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT } from '../Defaults'
|
||||
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MOBILE_NOISE_HEADER, NOISE_WA_HEADER } from '../Defaults'
|
||||
import { DisconnectReason, SocketConfig } from '../Types'
|
||||
import { addTransactionCapability, bindWaitForConnectionUpdate, configureSuccessfulPairing, Curve, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, makeNoiseHandler, printQRIfNecessaryListener, promiseTimeout } from '../Utils'
|
||||
import { addTransactionCapability, bindWaitForConnectionUpdate, configureSuccessfulPairing, Curve, generateLoginNode, generateMdTagPrefix, generateMobileNode, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, makeNoiseHandler, printQRIfNecessaryListener, promiseTimeout } from '../Utils'
|
||||
import { makeEventBuffer } from '../Utils/event-buffer'
|
||||
import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { assertNodeErrorFree, BinaryNode, binaryNodeToString, encodeBinaryNode, getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { MobileSocket } from './mobile-socket'
|
||||
import { WebSocket } from './web-socket'
|
||||
|
||||
/**
|
||||
* Connects to WA servers and performs:
|
||||
@@ -14,37 +16,35 @@ import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, getBinaryNodeChild,
|
||||
* - listen to messages and emit events
|
||||
* - query phone connection
|
||||
*/
|
||||
export const makeSocket = ({
|
||||
waWebSocketUrl,
|
||||
connectTimeoutMs,
|
||||
logger,
|
||||
agent,
|
||||
keepAliveIntervalMs,
|
||||
version,
|
||||
browser,
|
||||
auth: authState,
|
||||
printQRInTerminal,
|
||||
defaultQueryTimeoutMs,
|
||||
syncFullHistory,
|
||||
transactionOpts,
|
||||
qrTimeout,
|
||||
options,
|
||||
makeSignalRepository
|
||||
}: SocketConfig) => {
|
||||
const ws = new WebSocket(waWebSocketUrl, undefined, {
|
||||
origin: DEFAULT_ORIGIN,
|
||||
headers: options.headers as {},
|
||||
handshakeTimeout: connectTimeoutMs,
|
||||
timeout: connectTimeoutMs,
|
||||
agent
|
||||
})
|
||||
|
||||
export const makeSocket = (config: SocketConfig) => {
|
||||
const {
|
||||
connectTimeoutMs,
|
||||
logger,
|
||||
keepAliveIntervalMs,
|
||||
browser,
|
||||
auth: authState,
|
||||
printQRInTerminal,
|
||||
defaultQueryTimeoutMs,
|
||||
transactionOpts,
|
||||
qrTimeout,
|
||||
makeSignalRepository,
|
||||
} = config
|
||||
|
||||
config.mobile = config.mobile || config.auth.creds.registered
|
||||
const ws = config.mobile ? new MobileSocket(config) : new WebSocket(config)
|
||||
ws.setMaxListeners(0)
|
||||
|
||||
const ev = makeEventBuffer(logger)
|
||||
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
|
||||
const ephemeralKeyPair = Curve.generateKeyPair()
|
||||
/** WA noise protocol wrapper */
|
||||
const noise = makeNoiseHandler(ephemeralKeyPair, logger)
|
||||
const noise = makeNoiseHandler({
|
||||
keyPair: ephemeralKeyPair,
|
||||
NOISE_HEADER: config.mobile ? MOBILE_NOISE_HEADER : NOISE_WA_HEADER,
|
||||
mobile: config.mobile,
|
||||
logger
|
||||
})
|
||||
|
||||
const { creds } = authState
|
||||
// add transaction capability
|
||||
@@ -63,7 +63,7 @@ export const makeSocket = ({
|
||||
const sendPromise = promisify<void>(ws.send)
|
||||
/** send a raw buffer */
|
||||
const sendRawMessage = async(data: Uint8Array | Buffer) => {
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
if(!ws.isOpen) {
|
||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export const makeSocket = ({
|
||||
/** send a binary node */
|
||||
const sendNode = (frame: BinaryNode) => {
|
||||
if(logger.level === 'trace') {
|
||||
logger.trace({ msgId: frame.attrs.id, fromMe: true, frame }, 'communication')
|
||||
logger.trace(binaryNodeToString(frame), 'xml send')
|
||||
}
|
||||
|
||||
const buff = encodeBinaryNode(frame)
|
||||
@@ -101,7 +101,7 @@ export const makeSocket = ({
|
||||
|
||||
/** await the next incoming message */
|
||||
const awaitNextMessage = async<T>(sendMsg?: Uint8Array) => {
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
if(!ws.isOpen) {
|
||||
throw new Boom('Connection Closed', {
|
||||
statusCode: DisconnectReason.connectionClosed
|
||||
})
|
||||
@@ -186,21 +186,21 @@ export const makeSocket = ({
|
||||
}
|
||||
helloMsg = proto.HandshakeMessage.fromObject(helloMsg)
|
||||
|
||||
logger.info({ browser, helloMsg }, 'connected to WA Web')
|
||||
logger.info({ browser, helloMsg }, 'connected to WA')
|
||||
|
||||
const init = proto.HandshakeMessage.encode(helloMsg).finish()
|
||||
|
||||
const result = await awaitNextMessage<Uint8Array>(init)
|
||||
const handshake = proto.HandshakeMessage.decode(result)
|
||||
|
||||
logger.trace({ handshake }, 'handshake recv from WA Web')
|
||||
logger.trace({ handshake }, 'handshake recv from WA')
|
||||
|
||||
const keyEnc = noise.processHandshake(handshake, creds.noiseKey)
|
||||
|
||||
const config = { version, browser, syncFullHistory }
|
||||
|
||||
let node: proto.IClientPayload
|
||||
if(!creds.me) {
|
||||
if(config.mobile) {
|
||||
node = generateMobileNode(config)
|
||||
} else if(!creds.me) {
|
||||
node = generateRegistrationNode(creds, config)
|
||||
logger.info({ node }, 'not logged in, attempting registration...')
|
||||
} else {
|
||||
@@ -276,7 +276,7 @@ export const makeSocket = ({
|
||||
const msgId = frame.attrs.id
|
||||
|
||||
if(logger.level === 'trace') {
|
||||
logger.trace({ msgId, fromMe: false, frame }, 'communication')
|
||||
logger.trace(binaryNodeToString(frame), 'recv xml')
|
||||
}
|
||||
|
||||
/* Check if this is a response to a message we sent */
|
||||
@@ -321,7 +321,7 @@ export const makeSocket = ({
|
||||
ws.removeAllListeners('open')
|
||||
ws.removeAllListeners('message')
|
||||
|
||||
if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
|
||||
if(!ws.isClosed && !ws.isClosing) {
|
||||
try {
|
||||
ws.close()
|
||||
} catch{ }
|
||||
@@ -338,11 +338,11 @@ export const makeSocket = ({
|
||||
}
|
||||
|
||||
const waitForSocketOpen = async() => {
|
||||
if(ws.readyState === ws.OPEN) {
|
||||
if(ws.isOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
if(ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
|
||||
if(ws.isClosed || ws.isClosing) {
|
||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ export const makeSocket = ({
|
||||
*/
|
||||
if(diff > keepAliveIntervalMs + 5000) {
|
||||
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
||||
} else if(ws.readyState === ws.OPEN) {
|
||||
} else if(ws.isOpen) {
|
||||
// if its all good, send a keep alive request
|
||||
query(
|
||||
{
|
||||
@@ -472,7 +472,7 @@ export const makeSocket = ({
|
||||
|
||||
let qrMs = qrTimeout || 60_000 // time to let a QR live
|
||||
const genPairQR = () => {
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
if(!ws.isOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
31
src/Socket/web-socket.ts
Normal file
31
src/Socket/web-socket.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { WebSocket as WS } from 'ws'
|
||||
import { DEFAULT_ORIGIN } from '../Defaults'
|
||||
import { SocketConfig } from '../Types'
|
||||
|
||||
export class WebSocket extends WS {
|
||||
constructor(public config: SocketConfig) {
|
||||
super(config.waWebSocketUrl, undefined, {
|
||||
origin: DEFAULT_ORIGIN,
|
||||
headers: config.options.headers as Record<string, string>,
|
||||
handshakeTimeout: config.connectTimeoutMs,
|
||||
timeout: config.connectTimeoutMs,
|
||||
agent: config.agent,
|
||||
})
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
return this.readyState === WS.OPEN
|
||||
}
|
||||
|
||||
get isClosed() {
|
||||
return this.readyState === WS.CLOSED
|
||||
}
|
||||
|
||||
get isClosing() {
|
||||
return this.readyState === WS.CLOSING
|
||||
}
|
||||
|
||||
get isConnecting() {
|
||||
return this.readyState === WS.CONNECTING
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { proto } from '../../WAProto'
|
||||
import { RegistrationOptions } from '../Socket/registration'
|
||||
import type { Contact } from './Contact'
|
||||
import type { MinimalMessage } from './Message'
|
||||
|
||||
@@ -58,6 +59,13 @@ 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
|
||||
}
|
||||
|
||||
export type SignalDataTypeMap = {
|
||||
|
||||
@@ -31,6 +31,8 @@ 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 */
|
||||
mobile?: boolean
|
||||
/** proxy agent */
|
||||
agent?: Agent
|
||||
/** pino logger */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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'
|
||||
@@ -202,6 +203,13 @@ export const initAuthCreds = (): AuthenticationCreds => {
|
||||
accountSyncCounter: 0,
|
||||
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 RegistrationOptions
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,10 @@ export function sha256(buffer: Buffer) {
|
||||
return createHash('sha256').update(buffer).digest()
|
||||
}
|
||||
|
||||
export function md5(buffer: Buffer) {
|
||||
return createHash('md5').update(buffer).digest()
|
||||
}
|
||||
|
||||
// HKDF key expansion
|
||||
export function hkdf(buffer: Uint8Array | Buffer, expandedLength: number, info: { salt?: Buffer, info?: string }) {
|
||||
return HKDF(!Buffer.isBuffer(buffer) ? Buffer.from(buffer) : buffer, expandedLength, info)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { Logger } from 'pino'
|
||||
import { proto } from '../../WAProto'
|
||||
import { NOISE_MODE, NOISE_WA_HEADER, WA_CERT_DETAILS } from '../Defaults'
|
||||
import { NOISE_MODE, WA_CERT_DETAILS } from '../Defaults'
|
||||
import { KeyPair } from '../Types'
|
||||
import { BinaryNode, decodeBinaryNode } from '../WABinary'
|
||||
import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto'
|
||||
@@ -13,10 +13,17 @@ const generateIV = (counter: number) => {
|
||||
return new Uint8Array(iv)
|
||||
}
|
||||
|
||||
export const makeNoiseHandler = (
|
||||
{ public: publicKey, private: privateKey }: KeyPair,
|
||||
export const makeNoiseHandler = ({
|
||||
keyPair: { private: privateKey, public: publicKey },
|
||||
NOISE_HEADER,
|
||||
mobile,
|
||||
logger,
|
||||
}: {
|
||||
keyPair: KeyPair
|
||||
NOISE_HEADER: Uint8Array
|
||||
mobile: boolean
|
||||
logger: Logger
|
||||
) => {
|
||||
}) => {
|
||||
logger = logger.child({ class: 'ns' })
|
||||
|
||||
const authenticate = (data: Uint8Array) => {
|
||||
@@ -86,7 +93,7 @@ export const makeNoiseHandler = (
|
||||
|
||||
let inBytes = Buffer.alloc(0)
|
||||
|
||||
authenticate(NOISE_WA_HEADER)
|
||||
authenticate(NOISE_HEADER)
|
||||
authenticate(publicKey)
|
||||
|
||||
return {
|
||||
@@ -103,12 +110,18 @@ export const makeNoiseHandler = (
|
||||
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
|
||||
|
||||
const certDecoded = decrypt(serverHello!.payload!)
|
||||
const { intermediate: certIntermediate } = proto.CertChain.decode(certDecoded)
|
||||
|
||||
const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate!.details!)
|
||||
if(mobile) {
|
||||
const cert = proto.CertChain.NoiseCertificate.decode(certDecoded)
|
||||
logger.debug(cert)
|
||||
} else {
|
||||
const { intermediate: certIntermediate } = proto.CertChain.decode(certDecoded)
|
||||
|
||||
if(issuerSerial !== WA_CERT_DETAILS.SERIAL) {
|
||||
throw new Boom('certification match failed', { statusCode: 400 })
|
||||
const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate!.details!)
|
||||
|
||||
if(issuerSerial !== WA_CERT_DETAILS.SERIAL) {
|
||||
throw new Boom('certification match failed', { statusCode: 400 })
|
||||
}
|
||||
}
|
||||
|
||||
const keyEnc = encrypt(noiseKey.public)
|
||||
@@ -121,11 +134,11 @@ export const makeNoiseHandler = (
|
||||
data = encrypt(data)
|
||||
}
|
||||
|
||||
const introSize = sentIntro ? 0 : NOISE_WA_HEADER.length
|
||||
const introSize = sentIntro ? 0 : NOISE_HEADER.length
|
||||
const frame = Buffer.alloc(introSize + 3 + data.byteLength)
|
||||
|
||||
if(!sentIntro) {
|
||||
frame.set(NOISE_WA_HEADER)
|
||||
frame.set(NOISE_HEADER)
|
||||
sentIntro = true
|
||||
}
|
||||
|
||||
|
||||
@@ -8,26 +8,31 @@ import { Curve, hmacSign } from './crypto'
|
||||
import { encodeBigEndian } from './generics'
|
||||
import { createSignalIdentity } from './signal'
|
||||
|
||||
type ClientPayloadConfig = Pick<SocketConfig, 'version' | 'browser' | 'syncFullHistory'>
|
||||
const getUserAgent = (config: SocketConfig): proto.ClientPayload.IUserAgent => {
|
||||
const osVersion = config.mobile ? '15.3.1' : '0.1'
|
||||
const version = config.mobile ? [2, 22, 24] : 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 } : {}
|
||||
|
||||
const getUserAgent = ({ version }: ClientPayloadConfig): proto.ClientPayload.IUserAgent => {
|
||||
const osVersion = '0.1'
|
||||
return {
|
||||
appVersion: {
|
||||
primary: version[0],
|
||||
secondary: version[1],
|
||||
tertiary: version[2],
|
||||
},
|
||||
platform: proto.ClientPayload.UserAgent.Platform.WEB,
|
||||
platform,
|
||||
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
||||
mcc: '000',
|
||||
mnc: '000',
|
||||
mcc: config.auth.creds.registration?.phoneNumberMobileCountryCode || '000',
|
||||
mnc: config.auth.creds.registration?.phoneNumberMobileNetworkCode || '000',
|
||||
osVersion: osVersion,
|
||||
manufacturer: '',
|
||||
device: 'Desktop',
|
||||
manufacturer,
|
||||
device,
|
||||
osBuildNumber: osVersion,
|
||||
localeLanguageIso6391: 'en',
|
||||
localeCountryIso31661Alpha2: 'US',
|
||||
...phoneId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +41,7 @@ const PLATFORM_MAP = {
|
||||
'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
|
||||
}
|
||||
|
||||
const getWebInfo = (config: ClientPayloadConfig): proto.ClientPayload.IWebInfo => {
|
||||
const getWebInfo = (config: SocketConfig): proto.ClientPayload.IWebInfo => {
|
||||
let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER
|
||||
if(config.syncFullHistory && PLATFORM_MAP[config.browser[0]]) {
|
||||
webSubPlatform = PLATFORM_MAP[config.browser[0]]
|
||||
@@ -45,16 +50,43 @@ const getWebInfo = (config: ClientPayloadConfig): proto.ClientPayload.IWebInfo =
|
||||
return { webSubPlatform }
|
||||
}
|
||||
|
||||
const getClientPayload = (config: ClientPayloadConfig): proto.IClientPayload => {
|
||||
return {
|
||||
const getClientPayload = (config: SocketConfig) => {
|
||||
const payload: proto.IClientPayload = {
|
||||
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
||||
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
||||
userAgent: getUserAgent(config),
|
||||
webInfo: getWebInfo(config),
|
||||
}
|
||||
|
||||
if(!config.mobile) {
|
||||
payload.webInfo = getWebInfo(config)
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
export const generateLoginNode = (userJid: string, config: ClientPayloadConfig): proto.IClientPayload => {
|
||||
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)!
|
||||
const payload: proto.IClientPayload = {
|
||||
...getClientPayload(config),
|
||||
@@ -67,7 +99,7 @@ export const generateLoginNode = (userJid: string, config: ClientPayloadConfig):
|
||||
|
||||
export const generateRegistrationNode = (
|
||||
{ registrationId, signedPreKey, signedIdentityKey }: SignalCreds,
|
||||
config: ClientPayloadConfig
|
||||
config: SocketConfig
|
||||
) => {
|
||||
// the app version needs to be md5 hashed
|
||||
// and passed in
|
||||
|
||||
@@ -87,4 +87,35 @@ function bufferToUInt(e: Uint8Array | Buffer, t: number) {
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
const tabs = (n: number) => '\t'.repeat(n)
|
||||
|
||||
export function binaryNodeToString(node: BinaryNode | BinaryNode['content'], i = 0) {
|
||||
if(!node) {
|
||||
return node
|
||||
}
|
||||
|
||||
if(typeof node === 'string') {
|
||||
return tabs(i) + node
|
||||
}
|
||||
|
||||
if(node instanceof Uint8Array) {
|
||||
return tabs(i) + Buffer.from(node).toString('hex')
|
||||
}
|
||||
|
||||
if(Array.isArray(node)) {
|
||||
return node.map((x) => tabs(i + 1) + binaryNodeToString(x, i + 1)).join('\n')
|
||||
}
|
||||
|
||||
const children = binaryNodeToString(node.content, i + 1)
|
||||
|
||||
const tag = `<${node.tag} ${Object.entries(node.attrs || {})
|
||||
.filter(([, v]) => v !== undefined)
|
||||
.map(([k, v]) => `${k}='${v}'`)
|
||||
.join(' ')}`
|
||||
|
||||
const content: string = children ? `>\n${children}\n${tabs(i)}</${node.tag}>` : '/>'
|
||||
|
||||
return tag + content
|
||||
}
|
||||
127
yarn.lock
127
yarn.lock
@@ -1127,6 +1127,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz"
|
||||
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
|
||||
|
||||
"@types/uuid@^9.0.0":
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.1.tgz#98586dc36aee8dacc98cc396dbca8d0429647aa6"
|
||||
integrity sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==
|
||||
|
||||
"@types/ws@^8.0.0":
|
||||
version "8.5.3"
|
||||
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz"
|
||||
@@ -1576,6 +1581,14 @@ buffer@^5.2.0, buffer@^5.5.0:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz"
|
||||
@@ -1736,6 +1749,11 @@ color@^4.2.3:
|
||||
color-convert "^2.0.1"
|
||||
color-string "^1.9.0"
|
||||
|
||||
colorette@^2.0.7:
|
||||
version "2.0.20"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
|
||||
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
||||
@@ -1838,6 +1856,11 @@ data-urls@^2.0.0:
|
||||
whatwg-mimetype "^2.3.0"
|
||||
whatwg-url "^8.0.0"
|
||||
|
||||
dateformat@^4.6.3:
|
||||
version "4.6.3"
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5"
|
||||
integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
@@ -2246,6 +2269,11 @@ event-target-shim@^5.0.0:
|
||||
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
|
||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||
|
||||
events@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
||||
|
||||
execa@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz"
|
||||
@@ -2286,6 +2314,11 @@ expect@^27.5.1:
|
||||
jest-matcher-utils "^27.5.1"
|
||||
jest-message-util "^27.5.1"
|
||||
|
||||
fast-copy@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.1.tgz#9e89ef498b8c04c1cd76b33b8e14271658a732aa"
|
||||
integrity sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==
|
||||
|
||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||
@@ -2317,6 +2350,11 @@ fast-redact@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.1.tgz"
|
||||
integrity sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A==
|
||||
|
||||
fast-safe-stringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
|
||||
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz"
|
||||
@@ -2547,6 +2585,17 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^8.0.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
|
||||
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^5.0.1"
|
||||
once "^1.3.0"
|
||||
|
||||
global@~4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz"
|
||||
@@ -2635,6 +2684,14 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
help-me@^4.0.1:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/help-me/-/help-me-4.2.0.tgz#50712bfd799ff1854ae1d312c36eafcea85b0563"
|
||||
integrity sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==
|
||||
dependencies:
|
||||
glob "^8.0.0"
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
html-encoding-sniffer@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz"
|
||||
@@ -3372,6 +3429,11 @@ jimp@^0.16.1:
|
||||
"@jimp/types" "^0.16.1"
|
||||
regenerator-runtime "^0.13.3"
|
||||
|
||||
joycon@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
|
||||
integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==
|
||||
|
||||
jpeg-js@0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz"
|
||||
@@ -3494,6 +3556,11 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
libphonenumber-js@^1.10.20:
|
||||
version "1.10.28"
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.28.tgz#cae7e929cad96cee5ecc9449027192ecba39ee72"
|
||||
integrity sha512-1eAgjLrZA0+2Wgw4hs+4Q/kEBycxQo8ZLYnmOvZ3AlM8ImAVAJgDPlZtISLEzD1vunc2q8s2Pn7XwB7I8U3Kzw==
|
||||
|
||||
"libsignal@git+https://github.com/adiwajshing/libsignal-node":
|
||||
version "2.0.1"
|
||||
resolved "git+ssh://git@github.com/adiwajshing/libsignal-node.git#11dbd962ea108187c79a7c46fe4d6f790e23da97"
|
||||
@@ -3866,6 +3933,11 @@ on-exit-leak-free@^0.2.0:
|
||||
resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz"
|
||||
integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==
|
||||
|
||||
on-exit-leak-free@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4"
|
||||
integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==
|
||||
|
||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||
@@ -4033,6 +4105,14 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
pino-abstract-transport@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3"
|
||||
integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==
|
||||
dependencies:
|
||||
readable-stream "^4.0.0"
|
||||
split2 "^4.0.0"
|
||||
|
||||
pino-abstract-transport@v0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz"
|
||||
@@ -4041,6 +4121,26 @@ pino-abstract-transport@v0.5.0:
|
||||
duplexify "^4.1.2"
|
||||
split2 "^4.0.0"
|
||||
|
||||
pino-pretty@^9.4.0:
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-9.4.0.tgz#fc4026e83c87272cbdfb7afed121770e6000940c"
|
||||
integrity sha512-NIudkNLxnl7MGj1XkvsqVyRgo6meFP82ECXF2PlOI+9ghmbGuBUUqKJ7IZPIxpJw4vhhSva0IuiDSAuGh6TV9g==
|
||||
dependencies:
|
||||
colorette "^2.0.7"
|
||||
dateformat "^4.6.3"
|
||||
fast-copy "^3.0.0"
|
||||
fast-safe-stringify "^2.1.1"
|
||||
help-me "^4.0.1"
|
||||
joycon "^3.1.1"
|
||||
minimist "^1.2.6"
|
||||
on-exit-leak-free "^2.1.0"
|
||||
pino-abstract-transport "^1.0.0"
|
||||
pump "^3.0.0"
|
||||
readable-stream "^4.0.0"
|
||||
secure-json-parse "^2.4.0"
|
||||
sonic-boom "^3.0.0"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
pino-std-serializers@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz"
|
||||
@@ -4285,6 +4385,16 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^4.0.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.3.0.tgz#0914d0c72db03b316c9733bb3461d64a3cc50cba"
|
||||
integrity sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==
|
||||
dependencies:
|
||||
abort-controller "^3.0.0"
|
||||
buffer "^6.0.3"
|
||||
events "^3.3.0"
|
||||
process "^0.11.10"
|
||||
|
||||
readable-web-to-node-stream@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz"
|
||||
@@ -4412,6 +4522,11 @@ saxes@^5.0.1:
|
||||
dependencies:
|
||||
xmlchars "^2.2.0"
|
||||
|
||||
secure-json-parse@^2.4.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
|
||||
integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==
|
||||
|
||||
semver@7.x, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz"
|
||||
@@ -4516,6 +4631,13 @@ sonic-boom@^2.2.1:
|
||||
dependencies:
|
||||
atomic-sleep "^1.0.0"
|
||||
|
||||
sonic-boom@^3.0.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c"
|
||||
integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==
|
||||
dependencies:
|
||||
atomic-sleep "^1.0.0"
|
||||
|
||||
source-map-support@^0.5.6:
|
||||
version "0.5.21"
|
||||
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz"
|
||||
@@ -4971,6 +5093,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
uuid@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
|
||||
|
||||
Reference in New Issue
Block a user