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 { Boom } from '@hapi/boom'
|
||||||
|
import parsePhoneNumber from 'libphonenumber-js'
|
||||||
import NodeCache from 'node-cache'
|
import NodeCache from 'node-cache'
|
||||||
import makeWASocket, { AnyMessageContent, delay, DisconnectReason, fetchLatestBaileysVersion, getAggregateVotesInPollMessage, makeCacheableSignalKeyStore, makeInMemoryStore, proto, useMultiFileAuthState, WAMessageContent, WAMessageKey } from '../src'
|
import P from 'pino'
|
||||||
import MAIN_LOGGER from '../src/Utils/logger'
|
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({ })
|
const logger = P({
|
||||||
logger.level = 'trace'
|
transport: {
|
||||||
|
target: 'pino-pretty'
|
||||||
|
},
|
||||||
|
level: 'trace'
|
||||||
|
})
|
||||||
|
|
||||||
const useStore = !process.argv.includes('--no-store')
|
const useStore = !process.argv.includes('--no-store')
|
||||||
const doReplies = !process.argv.includes('--no-reply')
|
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
|
// 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
|
// 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,
|
version,
|
||||||
logger,
|
logger,
|
||||||
printQRInTerminal: true,
|
printQRInTerminal: true,
|
||||||
|
mobile: useMobile,
|
||||||
auth: {
|
auth: {
|
||||||
creds: state.creds,
|
creds: state.creds,
|
||||||
/** caching makes the store faster to send/recv messages */
|
/** caching makes the store faster to send/recv messages */
|
||||||
@@ -49,6 +57,69 @@ const startSock = async() => {
|
|||||||
|
|
||||||
store?.bind(sock.ev)
|
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) => {
|
const sendMessageWTyping = async(msg: AnyMessageContent, jid: string) => {
|
||||||
await sock.presenceSubscribe(jid)
|
await sock.presenceSubscribe(jid)
|
||||||
await delay(500)
|
await delay(500)
|
||||||
|
|||||||
@@ -36,11 +36,13 @@
|
|||||||
"@hapi/boom": "^9.1.3",
|
"@hapi/boom": "^9.1.3",
|
||||||
"axios": "^1.3.3",
|
"axios": "^1.3.3",
|
||||||
"futoin-hkdf": "^1.5.1",
|
"futoin-hkdf": "^1.5.1",
|
||||||
|
"libphonenumber-js": "^1.10.20",
|
||||||
"libsignal": "git+https://github.com/adiwajshing/libsignal-node",
|
"libsignal": "git+https://github.com/adiwajshing/libsignal-node",
|
||||||
"music-metadata": "^7.12.3",
|
"music-metadata": "^7.12.3",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"pino": "^7.0.0",
|
"pino": "^7.0.0",
|
||||||
"protobufjs": "^6.11.3",
|
"protobufjs": "^6.11.3",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"ws": "^8.0.0"
|
"ws": "^8.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -79,11 +81,13 @@
|
|||||||
"@types/jest": "^27.5.1",
|
"@types/jest": "^27.5.1",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^16.0.0",
|
||||||
"@types/sharp": "^0.29.4",
|
"@types/sharp": "^0.29.4",
|
||||||
|
"@types/uuid": "^9.0.0",
|
||||||
"@types/ws": "^8.0.0",
|
"@types/ws": "^8.0.0",
|
||||||
"eslint": "^8.0.0",
|
"eslint": "^8.0.0",
|
||||||
"jest": "^27.0.6",
|
"jest": "^27.0.6",
|
||||||
"jimp": "^0.16.1",
|
"jimp": "^0.16.1",
|
||||||
"link-preview-js": "^3.0.0",
|
"link-preview-js": "^3.0.0",
|
||||||
|
"pino-pretty": "^9.4.0",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"sharp": "^0.30.5",
|
"sharp": "^0.30.5",
|
||||||
"ts-jest": "^27.0.3",
|
"ts-jest": "^27.0.3",
|
||||||
|
|||||||
@@ -4,22 +4,36 @@ import type { AuthenticationState, MediaType, SocketConfig, WAVersion } from '..
|
|||||||
import { Browsers } from '../Utils'
|
import { Browsers } from '../Utils'
|
||||||
import logger from '../Utils/logger'
|
import logger from '../Utils/logger'
|
||||||
import { version } from './baileys-version.json'
|
import { version } from './baileys-version.json'
|
||||||
|
import phoneNumberMCC from './phonenumber-mcc.json'
|
||||||
|
|
||||||
export const UNAUTHORIZED_CODES = [401, 403, 419]
|
export const UNAUTHORIZED_CODES = [401, 403, 419]
|
||||||
|
|
||||||
|
export const PHONENUMBER_MCC = phoneNumberMCC
|
||||||
|
|
||||||
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com'
|
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_CALLBACK_PREFIX = 'CB:'
|
||||||
export const DEF_TAG_PREFIX = 'TAG:'
|
export const DEF_TAG_PREFIX = 'TAG:'
|
||||||
export const PHONE_CONNECTION_CB = 'CB:Pong'
|
export const PHONE_CONNECTION_CB = 'CB:Pong'
|
||||||
|
|
||||||
export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60
|
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 NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0'
|
||||||
export const DICT_VERSION = 2
|
export const DICT_VERSION = 2
|
||||||
export const KEY_BUNDLE_TYPE = Buffer.from([5])
|
export const KEY_BUNDLE_TYPE = Buffer.from([5])
|
||||||
export const NOISE_WA_HEADER = Buffer.from(
|
export const NOISE_WA_HEADER = Buffer.from(
|
||||||
[ 87, 65, 6, DICT_VERSION ]
|
[ 87, 65, 6, DICT_VERSION ]
|
||||||
) // last is "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 */
|
/** 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_REGEX = /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/
|
||||||
export const URL_EXCLUDE_REGEX = /.*@.*/
|
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 { DEFAULT_CONNECTION_CONFIG } from '../Defaults'
|
||||||
import { UserFacingSocketConfig } from '../Types'
|
import { UserFacingSocketConfig } from '../Types'
|
||||||
import { makeBusinessSocket as _makeSocket } from './business'
|
import { makeRegistrationSocket as _makeSocket } from './registration'
|
||||||
|
|
||||||
// export the last socket layer
|
// export the last socket layer
|
||||||
const makeWASocket = (config: UserFacingSocketConfig) => (
|
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 { Boom } from '@hapi/boom'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import WebSocket from 'ws'
|
|
||||||
import { proto } from '../../WAProto'
|
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 { 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 { 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:
|
* Connects to WA servers and performs:
|
||||||
@@ -14,37 +16,35 @@ import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, getBinaryNodeChild,
|
|||||||
* - listen to messages and emit events
|
* - listen to messages and emit events
|
||||||
* - query phone connection
|
* - query phone connection
|
||||||
*/
|
*/
|
||||||
export const makeSocket = ({
|
|
||||||
waWebSocketUrl,
|
export const makeSocket = (config: SocketConfig) => {
|
||||||
connectTimeoutMs,
|
const {
|
||||||
logger,
|
connectTimeoutMs,
|
||||||
agent,
|
logger,
|
||||||
keepAliveIntervalMs,
|
keepAliveIntervalMs,
|
||||||
version,
|
browser,
|
||||||
browser,
|
auth: authState,
|
||||||
auth: authState,
|
printQRInTerminal,
|
||||||
printQRInTerminal,
|
defaultQueryTimeoutMs,
|
||||||
defaultQueryTimeoutMs,
|
transactionOpts,
|
||||||
syncFullHistory,
|
qrTimeout,
|
||||||
transactionOpts,
|
makeSignalRepository,
|
||||||
qrTimeout,
|
} = config
|
||||||
options,
|
|
||||||
makeSignalRepository
|
config.mobile = config.mobile || config.auth.creds.registered
|
||||||
}: SocketConfig) => {
|
const ws = config.mobile ? new MobileSocket(config) : new WebSocket(config)
|
||||||
const ws = new WebSocket(waWebSocketUrl, undefined, {
|
|
||||||
origin: DEFAULT_ORIGIN,
|
|
||||||
headers: options.headers as {},
|
|
||||||
handshakeTimeout: connectTimeoutMs,
|
|
||||||
timeout: connectTimeoutMs,
|
|
||||||
agent
|
|
||||||
})
|
|
||||||
ws.setMaxListeners(0)
|
ws.setMaxListeners(0)
|
||||||
|
|
||||||
const ev = makeEventBuffer(logger)
|
const ev = makeEventBuffer(logger)
|
||||||
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
|
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
|
||||||
const ephemeralKeyPair = Curve.generateKeyPair()
|
const ephemeralKeyPair = Curve.generateKeyPair()
|
||||||
/** WA noise protocol wrapper */
|
/** 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
|
const { creds } = authState
|
||||||
// add transaction capability
|
// add transaction capability
|
||||||
@@ -63,7 +63,7 @@ export const makeSocket = ({
|
|||||||
const sendPromise = promisify<void>(ws.send)
|
const sendPromise = promisify<void>(ws.send)
|
||||||
/** send a raw buffer */
|
/** send a raw buffer */
|
||||||
const sendRawMessage = async(data: Uint8Array | Buffer) => {
|
const sendRawMessage = async(data: Uint8Array | Buffer) => {
|
||||||
if(ws.readyState !== ws.OPEN) {
|
if(!ws.isOpen) {
|
||||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ export const makeSocket = ({
|
|||||||
/** send a binary node */
|
/** send a binary node */
|
||||||
const sendNode = (frame: BinaryNode) => {
|
const sendNode = (frame: BinaryNode) => {
|
||||||
if(logger.level === 'trace') {
|
if(logger.level === 'trace') {
|
||||||
logger.trace({ msgId: frame.attrs.id, fromMe: true, frame }, 'communication')
|
logger.trace(binaryNodeToString(frame), 'xml send')
|
||||||
}
|
}
|
||||||
|
|
||||||
const buff = encodeBinaryNode(frame)
|
const buff = encodeBinaryNode(frame)
|
||||||
@@ -101,7 +101,7 @@ export const makeSocket = ({
|
|||||||
|
|
||||||
/** await the next incoming message */
|
/** await the next incoming message */
|
||||||
const awaitNextMessage = async<T>(sendMsg?: Uint8Array) => {
|
const awaitNextMessage = async<T>(sendMsg?: Uint8Array) => {
|
||||||
if(ws.readyState !== ws.OPEN) {
|
if(!ws.isOpen) {
|
||||||
throw new Boom('Connection Closed', {
|
throw new Boom('Connection Closed', {
|
||||||
statusCode: DisconnectReason.connectionClosed
|
statusCode: DisconnectReason.connectionClosed
|
||||||
})
|
})
|
||||||
@@ -186,21 +186,21 @@ export const makeSocket = ({
|
|||||||
}
|
}
|
||||||
helloMsg = proto.HandshakeMessage.fromObject(helloMsg)
|
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 init = proto.HandshakeMessage.encode(helloMsg).finish()
|
||||||
|
|
||||||
const result = await awaitNextMessage<Uint8Array>(init)
|
const result = await awaitNextMessage<Uint8Array>(init)
|
||||||
const handshake = proto.HandshakeMessage.decode(result)
|
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 keyEnc = noise.processHandshake(handshake, creds.noiseKey)
|
||||||
|
|
||||||
const config = { version, browser, syncFullHistory }
|
|
||||||
|
|
||||||
let node: proto.IClientPayload
|
let node: proto.IClientPayload
|
||||||
if(!creds.me) {
|
if(config.mobile) {
|
||||||
|
node = generateMobileNode(config)
|
||||||
|
} else if(!creds.me) {
|
||||||
node = generateRegistrationNode(creds, config)
|
node = generateRegistrationNode(creds, config)
|
||||||
logger.info({ node }, 'not logged in, attempting registration...')
|
logger.info({ node }, 'not logged in, attempting registration...')
|
||||||
} else {
|
} else {
|
||||||
@@ -276,7 +276,7 @@ export const makeSocket = ({
|
|||||||
const msgId = frame.attrs.id
|
const msgId = frame.attrs.id
|
||||||
|
|
||||||
if(logger.level === 'trace') {
|
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 */
|
/* Check if this is a response to a message we sent */
|
||||||
@@ -321,7 +321,7 @@ export const makeSocket = ({
|
|||||||
ws.removeAllListeners('open')
|
ws.removeAllListeners('open')
|
||||||
ws.removeAllListeners('message')
|
ws.removeAllListeners('message')
|
||||||
|
|
||||||
if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
|
if(!ws.isClosed && !ws.isClosing) {
|
||||||
try {
|
try {
|
||||||
ws.close()
|
ws.close()
|
||||||
} catch{ }
|
} catch{ }
|
||||||
@@ -338,11 +338,11 @@ export const makeSocket = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const waitForSocketOpen = async() => {
|
const waitForSocketOpen = async() => {
|
||||||
if(ws.readyState === ws.OPEN) {
|
if(ws.isOpen) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
|
if(ws.isClosed || ws.isClosing) {
|
||||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ export const makeSocket = ({
|
|||||||
*/
|
*/
|
||||||
if(diff > keepAliveIntervalMs + 5000) {
|
if(diff > keepAliveIntervalMs + 5000) {
|
||||||
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
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
|
// if its all good, send a keep alive request
|
||||||
query(
|
query(
|
||||||
{
|
{
|
||||||
@@ -472,7 +472,7 @@ export const makeSocket = ({
|
|||||||
|
|
||||||
let qrMs = qrTimeout || 60_000 // time to let a QR live
|
let qrMs = qrTimeout || 60_000 // time to let a QR live
|
||||||
const genPairQR = () => {
|
const genPairQR = () => {
|
||||||
if(ws.readyState !== ws.OPEN) {
|
if(!ws.isOpen) {
|
||||||
return
|
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 type { proto } from '../../WAProto'
|
||||||
|
import { RegistrationOptions } from '../Socket/registration'
|
||||||
import type { Contact } from './Contact'
|
import type { Contact } from './Contact'
|
||||||
import type { MinimalMessage } from './Message'
|
import type { MinimalMessage } from './Message'
|
||||||
|
|
||||||
@@ -58,6 +59,13 @@ export type AuthenticationCreds = SignalCreds & {
|
|||||||
/** number of times history & app state has been synced */
|
/** number of times history & app state has been synced */
|
||||||
accountSyncCounter: number
|
accountSyncCounter: number
|
||||||
accountSettings: AccountSettings
|
accountSettings: AccountSettings
|
||||||
|
// mobile creds
|
||||||
|
deviceId: string
|
||||||
|
phoneId: string
|
||||||
|
identityId: Buffer
|
||||||
|
registered: boolean
|
||||||
|
backupToken: Buffer
|
||||||
|
registration: RegistrationOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SignalDataTypeMap = {
|
export type SignalDataTypeMap = {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export type SocketConfig = {
|
|||||||
defaultQueryTimeoutMs: number | undefined
|
defaultQueryTimeoutMs: number | undefined
|
||||||
/** ping-pong interval for WS connection */
|
/** ping-pong interval for WS connection */
|
||||||
keepAliveIntervalMs: number
|
keepAliveIntervalMs: number
|
||||||
|
/** should baileys use the mobile api instead of the multi device api */
|
||||||
|
mobile?: boolean
|
||||||
/** proxy agent */
|
/** proxy agent */
|
||||||
agent?: Agent
|
agent?: Agent
|
||||||
/** pino logger */
|
/** pino logger */
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import NodeCache from 'node-cache'
|
import NodeCache from 'node-cache'
|
||||||
import type { Logger } from 'pino'
|
import type { Logger } from 'pino'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { DEFAULT_CACHE_TTLS } from '../Defaults'
|
import { DEFAULT_CACHE_TTLS } from '../Defaults'
|
||||||
import type { AuthenticationCreds, CacheStore, SignalDataSet, SignalDataTypeMap, SignalKeyStore, SignalKeyStoreWithTransaction, TransactionCapabilityOptions } from '../Types'
|
import type { AuthenticationCreds, CacheStore, SignalDataSet, SignalDataTypeMap, SignalKeyStore, SignalKeyStoreWithTransaction, TransactionCapabilityOptions } from '../Types'
|
||||||
import { Curve, signedKeyPair } from './crypto'
|
import { Curve, signedKeyPair } from './crypto'
|
||||||
@@ -202,6 +203,13 @@ export const initAuthCreds = (): AuthenticationCreds => {
|
|||||||
accountSyncCounter: 0,
|
accountSyncCounter: 0,
|
||||||
accountSettings: {
|
accountSettings: {
|
||||||
unarchiveChats: false
|
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()
|
return createHash('sha256').update(buffer).digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function md5(buffer: Buffer) {
|
||||||
|
return createHash('md5').update(buffer).digest()
|
||||||
|
}
|
||||||
|
|
||||||
// HKDF key expansion
|
// HKDF key expansion
|
||||||
export function hkdf(buffer: Uint8Array | Buffer, expandedLength: number, info: { salt?: Buffer, info?: string }) {
|
export function hkdf(buffer: Uint8Array | Buffer, expandedLength: number, info: { salt?: Buffer, info?: string }) {
|
||||||
return HKDF(!Buffer.isBuffer(buffer) ? Buffer.from(buffer) : buffer, expandedLength, info)
|
return HKDF(!Buffer.isBuffer(buffer) ? Buffer.from(buffer) : buffer, expandedLength, info)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { Logger } from 'pino'
|
import { Logger } from 'pino'
|
||||||
import { proto } from '../../WAProto'
|
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 { KeyPair } from '../Types'
|
||||||
import { BinaryNode, decodeBinaryNode } from '../WABinary'
|
import { BinaryNode, decodeBinaryNode } from '../WABinary'
|
||||||
import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto'
|
import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto'
|
||||||
@@ -13,10 +13,17 @@ const generateIV = (counter: number) => {
|
|||||||
return new Uint8Array(iv)
|
return new Uint8Array(iv)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeNoiseHandler = (
|
export const makeNoiseHandler = ({
|
||||||
{ public: publicKey, private: privateKey }: KeyPair,
|
keyPair: { private: privateKey, public: publicKey },
|
||||||
|
NOISE_HEADER,
|
||||||
|
mobile,
|
||||||
|
logger,
|
||||||
|
}: {
|
||||||
|
keyPair: KeyPair
|
||||||
|
NOISE_HEADER: Uint8Array
|
||||||
|
mobile: boolean
|
||||||
logger: Logger
|
logger: Logger
|
||||||
) => {
|
}) => {
|
||||||
logger = logger.child({ class: 'ns' })
|
logger = logger.child({ class: 'ns' })
|
||||||
|
|
||||||
const authenticate = (data: Uint8Array) => {
|
const authenticate = (data: Uint8Array) => {
|
||||||
@@ -86,7 +93,7 @@ export const makeNoiseHandler = (
|
|||||||
|
|
||||||
let inBytes = Buffer.alloc(0)
|
let inBytes = Buffer.alloc(0)
|
||||||
|
|
||||||
authenticate(NOISE_WA_HEADER)
|
authenticate(NOISE_HEADER)
|
||||||
authenticate(publicKey)
|
authenticate(publicKey)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -103,12 +110,18 @@ export const makeNoiseHandler = (
|
|||||||
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
|
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
|
||||||
|
|
||||||
const certDecoded = decrypt(serverHello!.payload!)
|
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) {
|
const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate!.details!)
|
||||||
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)
|
const keyEnc = encrypt(noiseKey.public)
|
||||||
@@ -121,11 +134,11 @@ export const makeNoiseHandler = (
|
|||||||
data = encrypt(data)
|
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)
|
const frame = Buffer.alloc(introSize + 3 + data.byteLength)
|
||||||
|
|
||||||
if(!sentIntro) {
|
if(!sentIntro) {
|
||||||
frame.set(NOISE_WA_HEADER)
|
frame.set(NOISE_HEADER)
|
||||||
sentIntro = true
|
sentIntro = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,26 +8,31 @@ import { Curve, hmacSign } from './crypto'
|
|||||||
import { encodeBigEndian } from './generics'
|
import { encodeBigEndian } from './generics'
|
||||||
import { createSignalIdentity } from './signal'
|
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 {
|
return {
|
||||||
appVersion: {
|
appVersion: {
|
||||||
primary: version[0],
|
primary: version[0],
|
||||||
secondary: version[1],
|
secondary: version[1],
|
||||||
tertiary: version[2],
|
tertiary: version[2],
|
||||||
},
|
},
|
||||||
platform: proto.ClientPayload.UserAgent.Platform.WEB,
|
platform,
|
||||||
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
||||||
mcc: '000',
|
mcc: config.auth.creds.registration?.phoneNumberMobileCountryCode || '000',
|
||||||
mnc: '000',
|
mnc: config.auth.creds.registration?.phoneNumberMobileNetworkCode || '000',
|
||||||
osVersion: osVersion,
|
osVersion: osVersion,
|
||||||
manufacturer: '',
|
manufacturer,
|
||||||
device: 'Desktop',
|
device,
|
||||||
osBuildNumber: osVersion,
|
osBuildNumber: osVersion,
|
||||||
localeLanguageIso6391: 'en',
|
localeLanguageIso6391: 'en',
|
||||||
localeCountryIso31661Alpha2: 'US',
|
localeCountryIso31661Alpha2: 'US',
|
||||||
|
...phoneId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +41,7 @@ const PLATFORM_MAP = {
|
|||||||
'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
|
'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
|
let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER
|
||||||
if(config.syncFullHistory && PLATFORM_MAP[config.browser[0]]) {
|
if(config.syncFullHistory && PLATFORM_MAP[config.browser[0]]) {
|
||||||
webSubPlatform = PLATFORM_MAP[config.browser[0]]
|
webSubPlatform = PLATFORM_MAP[config.browser[0]]
|
||||||
@@ -45,16 +50,43 @@ const getWebInfo = (config: ClientPayloadConfig): proto.ClientPayload.IWebInfo =
|
|||||||
return { webSubPlatform }
|
return { webSubPlatform }
|
||||||
}
|
}
|
||||||
|
|
||||||
const getClientPayload = (config: ClientPayloadConfig): proto.IClientPayload => {
|
const getClientPayload = (config: SocketConfig) => {
|
||||||
return {
|
const payload: proto.IClientPayload = {
|
||||||
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
connectType: proto.ClientPayload.ConnectType.WIFI_UNKNOWN,
|
||||||
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
connectReason: proto.ClientPayload.ConnectReason.USER_ACTIVATED,
|
||||||
userAgent: getUserAgent(config),
|
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 { user, device } = jidDecode(userJid)!
|
||||||
const payload: proto.IClientPayload = {
|
const payload: proto.IClientPayload = {
|
||||||
...getClientPayload(config),
|
...getClientPayload(config),
|
||||||
@@ -67,7 +99,7 @@ export const generateLoginNode = (userJid: string, config: ClientPayloadConfig):
|
|||||||
|
|
||||||
export const generateRegistrationNode = (
|
export const generateRegistrationNode = (
|
||||||
{ registrationId, signedPreKey, signedIdentityKey }: SignalCreds,
|
{ registrationId, signedPreKey, signedIdentityKey }: SignalCreds,
|
||||||
config: ClientPayloadConfig
|
config: SocketConfig
|
||||||
) => {
|
) => {
|
||||||
// the app version needs to be md5 hashed
|
// the app version needs to be md5 hashed
|
||||||
// and passed in
|
// and passed in
|
||||||
|
|||||||
@@ -87,4 +87,35 @@ function bufferToUInt(e: Uint8Array | Buffer, t: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return a
|
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"
|
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz"
|
||||||
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
|
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":
|
"@types/ws@^8.0.0":
|
||||||
version "8.5.3"
|
version "8.5.3"
|
||||||
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz"
|
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"
|
base64-js "^1.3.1"
|
||||||
ieee754 "^1.1.13"
|
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:
|
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz"
|
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-convert "^2.0.1"
|
||||||
color-string "^1.9.0"
|
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:
|
combined-stream@^1.0.6, combined-stream@^1.0.8:
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
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-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.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:
|
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
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"
|
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
|
||||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
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:
|
execa@^5.0.0:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz"
|
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-matcher-utils "^27.5.1"
|
||||||
jest-message-util "^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:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
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"
|
resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.1.tgz"
|
||||||
integrity sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A==
|
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:
|
fastq@^1.6.0:
|
||||||
version "1.15.0"
|
version "1.15.0"
|
||||||
resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz"
|
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"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.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:
|
global@~4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz"
|
resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz"
|
||||||
@@ -2635,6 +2684,14 @@ has@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
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:
|
html-encoding-sniffer@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz"
|
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"
|
"@jimp/types" "^0.16.1"
|
||||||
regenerator-runtime "^0.13.3"
|
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:
|
jpeg-js@0.4.2:
|
||||||
version "0.4.2"
|
version "0.4.2"
|
||||||
resolved "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz"
|
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"
|
prelude-ls "~1.1.2"
|
||||||
type-check "~0.3.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":
|
"libsignal@git+https://github.com/adiwajshing/libsignal-node":
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "git+ssh://git@github.com/adiwajshing/libsignal-node.git#11dbd962ea108187c79a7c46fe4d6f790e23da97"
|
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"
|
resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz"
|
||||||
integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==
|
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:
|
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
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"
|
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
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:
|
pino-abstract-transport@v0.5.0:
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz"
|
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"
|
duplexify "^4.1.2"
|
||||||
split2 "^4.0.0"
|
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:
|
pino-std-serializers@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz"
|
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"
|
string_decoder "^1.1.1"
|
||||||
util-deprecate "^1.0.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:
|
readable-web-to-node-stream@^3.0.0:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz"
|
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:
|
dependencies:
|
||||||
xmlchars "^2.2.0"
|
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:
|
semver@7.x, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7:
|
||||||
version "7.3.7"
|
version "7.3.7"
|
||||||
resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz"
|
resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz"
|
||||||
@@ -4516,6 +4631,13 @@ sonic-boom@^2.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
atomic-sleep "^1.0.0"
|
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:
|
source-map-support@^0.5.6:
|
||||||
version "0.5.21"
|
version "0.5.21"
|
||||||
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz"
|
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"
|
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
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:
|
v8-compile-cache-lib@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
|
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user