fix: Re-added the option to use proxy agent forn non-mobile api (#148)

This commit is contained in:
Edgard Lorraine Messias
2023-06-15 21:46:43 -03:00
committed by GitHub
parent aa9872b039
commit 54f8215eef
9 changed files with 177 additions and 78 deletions

View File

@@ -53,7 +53,7 @@
"pino": "^7.0.0",
"protobufjs": "^6.11.3",
"uuid": "^9.0.0",
"ws": "^8.0.0"
"ws": "^8.13.0"
},
"devDependencies": {
"@adiwajshing/eslint-config": "https://github.com/adiwajshing/eslint-config.git",

View File

@@ -0,0 +1,19 @@
import { EventEmitter } from 'events'
import { URL } from 'url'
import { SocketConfig } from '../../Types'
export abstract class AbstractSocketClient extends EventEmitter {
abstract get isOpen(): boolean
abstract get isClosed(): boolean
abstract get isClosing(): boolean
abstract get isConnecting(): boolean
constructor(public url: URL, public config: SocketConfig) {
super()
this.setMaxListeners(0)
}
abstract connect(): Promise<void>
abstract close(): Promise<void>
abstract send(str: Uint8Array | string, cb?: (err?: Error) => void): boolean;
}

View File

@@ -0,0 +1,3 @@
export * from './abstract-socket-client'
export * from './mobile-socket-client'
export * from './web-socket-client'

View File

@@ -0,0 +1,66 @@
import { connect, Socket } from 'net'
import { AbstractSocketClient } from './abstract-socket-client'
export class MobileSocketClient extends AbstractSocketClient {
protected socket: Socket | null = null
get isOpen(): boolean {
return this.socket?.readyState === 'open'
}
get isClosed(): boolean {
return this.socket === null || this.socket?.readyState === 'closed'
}
get isClosing(): boolean {
return this.socket === null || this.socket?.readyState === 'closed'
}
get isConnecting(): boolean {
return this.socket?.readyState === 'opening'
}
async connect(): Promise<void> {
if(this.socket) {
return
}
if(this.config.agent) {
throw new Error('There are not support for proxy agent for mobile connection')
} else {
this.socket = connect({
host: this.url.hostname,
port: Number(this.url.port) || 443
})
}
this.socket.setMaxListeners(0)
const events = ['close', 'connect', 'data', 'drain', 'end', 'error', 'lookup', 'ready', 'timeout']
for(const event of events) {
this.socket?.on(event, (...args: any[]) => this.emit(event, ...args))
}
this.socket.on('data', (...args: any[]) => this.emit('message', ...args))
this.socket.on('ready', (...args: any[]) => this.emit('open', ...args))
}
async close(): Promise<void> {
if(!this.socket) {
return
}
return new Promise<void>(resolve => {
this.socket!.end(resolve)
this.socket = null
})
}
send(str: string | Uint8Array, cb?: (err?: Error) => void): boolean {
if(this.socket === null) {
return false
}
return this.socket.write(str, undefined, cb)
}
}

View File

@@ -0,0 +1,57 @@
import WebSocket from 'ws'
import { DEFAULT_ORIGIN } from '../../Defaults'
import { AbstractSocketClient } from './abstract-socket-client'
export class WebSocketClient extends AbstractSocketClient {
protected socket: WebSocket | null = null
get isOpen(): boolean {
return this.socket?.readyState === WebSocket.OPEN
}
get isClosed(): boolean {
return this.socket === null || this.socket?.readyState === WebSocket.CLOSED
}
get isClosing(): boolean {
return this.socket === null || this.socket?.readyState === WebSocket.CLOSING
}
get isConnecting(): boolean {
return this.socket?.readyState === WebSocket.CONNECTING
}
async connect(): Promise<void> {
if(this.socket) {
return
}
this.socket = new WebSocket(this.url, {
origin: DEFAULT_ORIGIN,
headers: this.config.options?.headers as {},
handshakeTimeout: this.config.connectTimeoutMs,
timeout: this.config.connectTimeoutMs,
agent: this.config.agent,
})
this.socket.setMaxListeners(0)
const events = ['close', 'error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response']
for(const event of events) {
this.socket?.on(event, (...args: any[]) => this.emit(event, ...args))
}
}
async close(): Promise<void> {
if(!this.socket) {
return
}
this.socket.close()
this.socket = null
}
send(str: string | Uint8Array, cb?: (err?: Error) => void): boolean {
this.socket?.send(str, cb)
return Boolean(this.socket)
}
}

View File

@@ -1,46 +0,0 @@
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()
this.on('data', (d) => {
this.emit('message', d)
})
}
override connect() {
return super.connect({
host: MOBILE_ENDPOINT,
port: MOBILE_PORT,
}, () => {
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, undefined, cb as ((err?: Error | undefined) => void))
}
}

View File

@@ -5,7 +5,6 @@ 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')
@@ -33,10 +32,6 @@ export const makeRegistrationSocket = (config: SocketConfig) => {
sock.authState.creds.registered = true
sock.ev.emit('creds.update', sock.authState.creds)
if(sock.ws instanceof MobileSocket) {
sock.ws.connect()
}
return result
}

View File

@@ -1,13 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Boom } from '@hapi/boom'
import { URL } from 'url'
import { promisify } from 'util'
import { proto } from '../../WAProto'
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MOBILE_NOISE_HEADER, NOISE_WA_HEADER } from '../Defaults'
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MOBILE_ENDPOINT, MOBILE_NOISE_HEADER, MOBILE_PORT, NOISE_WA_HEADER } from '../Defaults'
import { DisconnectReason, SocketConfig } from '../Types'
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, binaryNodeToString, encodeBinaryNode, getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } from '../WABinary'
import { MobileSocket } from './mobile-socket'
import { MobileSocketClient, WebSocketClient } from './Client'
/**
* Connects to WA servers and performs:
@@ -18,6 +19,7 @@ import { MobileSocket } from './mobile-socket'
export const makeSocket = (config: SocketConfig) => {
const {
waWebSocketUrl,
connectTimeoutMs,
logger,
keepAliveIntervalMs,
@@ -30,15 +32,18 @@ export const makeSocket = (config: SocketConfig) => {
makeSignalRepository,
} = config
config.mobile = config.mobile || config.auth.creds.registered
const ws = new MobileSocket(config)
ws.setMaxListeners?.(0)
let url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
// if not mobile or already registered -> auto connect
if(!config.mobile || config.auth.creds.registered) {
ws.connect()
config.mobile = config.mobile || config.auth?.creds?.registered || url.protocol === 'tcp:'
if(config.mobile && url.protocol !== 'tcp:') {
url = new URL(`tcp://${MOBILE_ENDPOINT}:${MOBILE_PORT}`)
}
const ws = config.mobile ? new MobileSocketClient(url, config) : new WebSocketClient(url, config)
ws.connect()
const ev = makeEventBuffer(logger)
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
const ephemeralKeyPair = Curve.generateKeyPair()
@@ -64,7 +69,7 @@ export const makeSocket = (config: SocketConfig) => {
const uqTagId = generateMdTagPrefix()
const generateMessageTag = () => `${uqTagId}${epoch++}`
const sendPromise = promisify<void>(ws.send)
const sendPromise = promisify(ws.send)
/** send a raw buffer */
const sendRawMessage = async(data: Uint8Array | Buffer) => {
if(!ws.isOpen) {
@@ -135,12 +140,12 @@ export const makeSocket = (config: SocketConfig) => {
}
/**
* Wait for a message with a certain tag to be received
* @param tag the message tag to await
* @param json query that was sent
* @param timeoutMs timeout after which the promise will reject
*/
const waitForMessage = async<T>(msgId: string, timeoutMs = defaultQueryTimeoutMs) => {
* Wait for a message with a certain tag to be received
* @param tag the message tag to await
* @param json query that was sent
* @param timeoutMs timeout after which the promise will reject
*/
const waitForMessage = async<T>(msgId: string, timeoutMs = defaultQueryTimeoutMs) => {
let onRecv: (json) => void
let onErr: (err) => void
try {
@@ -237,7 +242,7 @@ export const makeSocket = (config: SocketConfig) => {
to: S_WHATSAPP_NET
},
content: [
{ tag: 'count', attrs: { } }
{ tag: 'count', attrs: {} }
]
})
const countChild = getBinaryNodeChild(result, 'count')
@@ -287,7 +292,7 @@ export const makeSocket = (config: SocketConfig) => {
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${msgId}`, frame) || anyTriggered
/* Check if this is a response to a message we are expecting */
const l0 = frame.tag
const l1 = frame.attrs || { }
const l1 = frame.attrs || {}
const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : ''
Object.keys(l1).forEach(key => {
@@ -374,9 +379,9 @@ export const makeSocket = (config: SocketConfig) => {
const diff = Date.now() - lastDateRecv.getTime()
/*
check if it's been a suspicious amount of time since the server responded with our last seen
it could be that the network is down
*/
check if it's been a suspicious amount of time since the server responded with our last seen
it could be that the network is down
*/
if(diff > keepAliveIntervalMs + 5000) {
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
} else if(ws.isOpen) {
@@ -390,7 +395,7 @@ export const makeSocket = (config: SocketConfig) => {
type: 'get',
xmlns: 'w:p',
},
content: [{ tag: 'ping', attrs: { } }]
content: [{ tag: 'ping', attrs: {} }]
}
)
.catch(err => {
@@ -411,7 +416,7 @@ export const makeSocket = (config: SocketConfig) => {
type: 'set',
},
content: [
{ tag, attrs: { } }
{ tag, attrs: {} }
]
})
)

View File

@@ -7501,10 +7501,10 @@ ws@^7.4.6:
resolved "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz"
integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==
ws@^8.0.0:
version "8.7.0"
resolved "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz"
integrity sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==
ws@^8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
xdg-basedir@^5.0.1, xdg-basedir@^5.1.0:
version "5.1.0"