lid, wip: Support LIDs in Baileys (#1747)
* lid-mapping: get missing lid from usync * lid-mapping, jid-utils: change to isPnUser and store multiple mappings * process-message: parse protocolMsg mapping, and store from new msgs * types: lid-mapping event, addressing enum, alt, contact, group types * validate, decode: use lid for identity, better logic * lid: final commit * linting * linting * linting * linting * misc: fix testing and also remove version json * lint: IDE fucking up lint * lid-mapping: fix build error on NPM * message-retry: fix proto import
This commit is contained in:
+170
-93
@@ -43,6 +43,7 @@ import {
|
||||
jidEncode,
|
||||
S_WHATSAPP_NET
|
||||
} from '../WABinary'
|
||||
import { USyncQuery, USyncUser } from '../WAUSync/'
|
||||
import { WebSocketClient } from './Client'
|
||||
|
||||
/**
|
||||
@@ -67,6 +68,9 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
makeSignalRepository
|
||||
} = config
|
||||
|
||||
const uqTagId = generateMdTagPrefix()
|
||||
const generateMessageTag = () => `${uqTagId}${epoch++}`
|
||||
|
||||
if (printQRInTerminal) {
|
||||
console.warn(
|
||||
'⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.'
|
||||
@@ -83,98 +87,6 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
url.searchParams.append('ED', authState.creds.routingInfo.toString('base64url'))
|
||||
}
|
||||
|
||||
const ws = 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()
|
||||
/** WA noise protocol wrapper */
|
||||
const noise = makeNoiseHandler({
|
||||
keyPair: ephemeralKeyPair,
|
||||
NOISE_HEADER: NOISE_WA_HEADER,
|
||||
logger,
|
||||
routingInfo: authState?.creds?.routingInfo
|
||||
})
|
||||
|
||||
const { creds } = authState
|
||||
// add transaction capability
|
||||
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
|
||||
const signalRepository = makeSignalRepository({ creds, keys })
|
||||
|
||||
let lastDateRecv: Date
|
||||
let epoch = 1
|
||||
let keepAliveReq: NodeJS.Timeout
|
||||
let qrTimer: NodeJS.Timeout
|
||||
let closed = false
|
||||
|
||||
const uqTagId = generateMdTagPrefix()
|
||||
const generateMessageTag = () => `${uqTagId}${epoch++}`
|
||||
|
||||
const sendPromise = promisify(ws.send)
|
||||
/** send a raw buffer */
|
||||
const sendRawMessage = async (data: Uint8Array | Buffer) => {
|
||||
if (!ws.isOpen) {
|
||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
|
||||
const bytes = noise.encodeFrame(data)
|
||||
await promiseTimeout<void>(connectTimeoutMs, async (resolve, reject) => {
|
||||
try {
|
||||
await sendPromise.call(ws, bytes)
|
||||
resolve()
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** send a binary node */
|
||||
const sendNode = (frame: BinaryNode) => {
|
||||
if (logger.level === 'trace') {
|
||||
logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' })
|
||||
}
|
||||
|
||||
const buff = encodeBinaryNode(frame)
|
||||
return sendRawMessage(buff)
|
||||
}
|
||||
|
||||
/** log & process any unexpected errors */
|
||||
const onUnexpectedError = (err: Error | Boom, msg: string) => {
|
||||
logger.error({ err }, `unexpected error in '${msg}'`)
|
||||
}
|
||||
|
||||
/** await the next incoming message */
|
||||
const awaitNextMessage = async <T>(sendMsg?: Uint8Array) => {
|
||||
if (!ws.isOpen) {
|
||||
throw new Boom('Connection Closed', {
|
||||
statusCode: DisconnectReason.connectionClosed
|
||||
})
|
||||
}
|
||||
|
||||
let onOpen: (data: T) => void
|
||||
let onClose: (err: Error) => void
|
||||
|
||||
const result = promiseTimeout<T>(connectTimeoutMs, (resolve, reject) => {
|
||||
onOpen = resolve
|
||||
onClose = mapWebSocketError(reject)
|
||||
ws.on('frame', onOpen)
|
||||
ws.on('close', onClose)
|
||||
ws.on('error', onClose)
|
||||
}).finally(() => {
|
||||
ws.off('frame', onOpen)
|
||||
ws.off('close', onClose)
|
||||
ws.off('error', onClose)
|
||||
})
|
||||
|
||||
if (sendMsg) {
|
||||
sendRawMessage(sendMsg).catch(onClose!)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a message with a certain tag to be received
|
||||
* @param msgId the message tag to await
|
||||
@@ -244,6 +156,169 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
return result
|
||||
}
|
||||
|
||||
const executeUSyncQuery = async (usyncQuery: USyncQuery) => {
|
||||
if (usyncQuery.protocols.length === 0) {
|
||||
throw new Boom('USyncQuery must have at least one protocol')
|
||||
}
|
||||
|
||||
// todo: validate users, throw WARNING on no valid users
|
||||
// variable below has only validated users
|
||||
const validUsers = usyncQuery.users
|
||||
|
||||
const userNodes = validUsers.map(user => {
|
||||
return {
|
||||
tag: 'user',
|
||||
attrs: {
|
||||
jid: !user.phone ? user.id : undefined
|
||||
},
|
||||
content: usyncQuery.protocols.map(a => a.getUserElement(user)).filter(a => a !== null)
|
||||
} as BinaryNode
|
||||
})
|
||||
|
||||
const listNode: BinaryNode = {
|
||||
tag: 'list',
|
||||
attrs: {},
|
||||
content: userNodes
|
||||
}
|
||||
|
||||
const queryNode: BinaryNode = {
|
||||
tag: 'query',
|
||||
attrs: {},
|
||||
content: usyncQuery.protocols.map(a => a.getQueryElement())
|
||||
}
|
||||
const iq = {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'get',
|
||||
xmlns: 'usync'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'usync',
|
||||
attrs: {
|
||||
context: usyncQuery.context,
|
||||
mode: usyncQuery.mode,
|
||||
sid: generateMessageTag(),
|
||||
last: 'true',
|
||||
index: '0'
|
||||
},
|
||||
content: [queryNode, listNode]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = await query(iq)
|
||||
|
||||
return usyncQuery.parseUSyncQueryResult(result)
|
||||
}
|
||||
|
||||
const onWhatsApp = async (...jids: string[]) => {
|
||||
const usyncQuery = new USyncQuery().withContactProtocol().withLIDProtocol()
|
||||
|
||||
for (const jid of jids) {
|
||||
const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`
|
||||
usyncQuery.withUser(new USyncUser().withPhone(phone))
|
||||
}
|
||||
|
||||
const results = await executeUSyncQuery(usyncQuery)
|
||||
|
||||
if (results) {
|
||||
return results.list
|
||||
.filter(a => !!a.contact)
|
||||
.map(({ contact, id, lid }) => ({ jid: id, exists: contact as boolean, lid: lid as string }))
|
||||
}
|
||||
}
|
||||
|
||||
const ws = 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()
|
||||
/** WA noise protocol wrapper */
|
||||
const noise = makeNoiseHandler({
|
||||
keyPair: ephemeralKeyPair,
|
||||
NOISE_HEADER: NOISE_WA_HEADER,
|
||||
logger,
|
||||
routingInfo: authState?.creds?.routingInfo
|
||||
})
|
||||
|
||||
const { creds } = authState
|
||||
// add transaction capability
|
||||
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
|
||||
const signalRepository = makeSignalRepository({ creds, keys }, onWhatsApp)
|
||||
|
||||
let lastDateRecv: Date
|
||||
let epoch = 1
|
||||
let keepAliveReq: NodeJS.Timeout
|
||||
let qrTimer: NodeJS.Timeout
|
||||
let closed = false
|
||||
|
||||
const sendPromise = promisify(ws.send)
|
||||
/** send a raw buffer */
|
||||
const sendRawMessage = async (data: Uint8Array | Buffer) => {
|
||||
if (!ws.isOpen) {
|
||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
|
||||
const bytes = noise.encodeFrame(data)
|
||||
await promiseTimeout<void>(connectTimeoutMs, async (resolve, reject) => {
|
||||
try {
|
||||
await sendPromise.call(ws, bytes)
|
||||
resolve()
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** send a binary node */
|
||||
const sendNode = (frame: BinaryNode) => {
|
||||
if (logger.level === 'trace') {
|
||||
logger.trace({ xml: binaryNodeToString(frame), msg: 'xml send' })
|
||||
}
|
||||
|
||||
const buff = encodeBinaryNode(frame)
|
||||
return sendRawMessage(buff)
|
||||
}
|
||||
|
||||
/** log & process any unexpected errors */
|
||||
const onUnexpectedError = (err: Error | Boom, msg: string) => {
|
||||
logger.error({ err }, `unexpected error in '${msg}'`)
|
||||
}
|
||||
|
||||
/** await the next incoming message */
|
||||
const awaitNextMessage = async <T>(sendMsg?: Uint8Array) => {
|
||||
if (!ws.isOpen) {
|
||||
throw new Boom('Connection Closed', {
|
||||
statusCode: DisconnectReason.connectionClosed
|
||||
})
|
||||
}
|
||||
|
||||
let onOpen: (data: T) => void
|
||||
let onClose: (err: Error) => void
|
||||
|
||||
const result = promiseTimeout<T>(connectTimeoutMs, (resolve, reject) => {
|
||||
onOpen = resolve
|
||||
onClose = mapWebSocketError(reject)
|
||||
ws.on('frame', onOpen)
|
||||
ws.on('close', onClose)
|
||||
ws.on('error', onClose)
|
||||
}).finally(() => {
|
||||
ws.off('frame', onOpen)
|
||||
ws.off('close', onClose)
|
||||
ws.off('error', onClose)
|
||||
})
|
||||
|
||||
if (sendMsg) {
|
||||
sendRawMessage(sendMsg).catch(onClose!)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/** connection handshake */
|
||||
const validateConnection = async () => {
|
||||
let helloMsg: proto.IHandshakeMessage = {
|
||||
@@ -878,7 +953,9 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
requestPairingCode,
|
||||
/** Waits for the connection to WA to reach a state */
|
||||
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),
|
||||
sendWAMBuffer
|
||||
sendWAMBuffer,
|
||||
executeUSyncQuery,
|
||||
onWhatsApp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user