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:
Rajeh Taher
2025-09-08 10:03:28 +03:00
committed by GitHub
parent ca22ae5f9c
commit 20693a59d0
27 changed files with 630 additions and 405 deletions
+23 -4
View File
@@ -2,12 +2,31 @@
import type { Config } from 'jest'
const config: Config = {
roots: ['<rootDir>/src'],
testMatch: ['**.test.ts'],
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/WAProto'],
testMatch: ['<rootDir>/src/**/*.test.ts'],
extensionsToTreatAsEsm: ['.ts'],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', { useESM: true }]
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
tsconfig: {
module: 'esnext',
verbatimModuleSyntax: false,
allowImportingTsExtensions: false,
},
},
],
'^.+\\.js$': ['ts-jest', { useESM: true }],
},
transformIgnorePatterns: [
'node_modules/(?!(protobufjs|long|@protobufjs|@types/long)/)',
],
}
export default config
+3 -3
View File
@@ -57,12 +57,12 @@
"@types/jest": "^30.0.0",
"@types/node": "^20.9.0",
"@types/ws": "^8.0.0",
"@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0",
"@typescript-eslint/eslint-plugin": "^8",
"@typescript-eslint/parser": "^8",
"@whiskeysockets/eslint-config": "^1.0.0",
"conventional-changelog-cli": "^2.2.2",
"esbuild-register": "^3.6.0",
"eslint": "^9.31.0",
"eslint": "^9",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.4.0",
"jest": "^30.0.5",
-3
View File
@@ -1,3 +0,0 @@
{
"version": [2, 3000, 1023223821]
}
+1 -2
View File
@@ -3,9 +3,8 @@ import { makeLibSignalRepository } from '../Signal/libsignal'
import type { AuthenticationState, SocketConfig, WAVersion } from '../Types'
import { Browsers } from '../Utils'
import logger from '../Utils/logger'
import defaultVersion from './baileys-version.json' with { type: 'json' }
const { version } = defaultVersion
const version = [2, 3000, 1023223821]
export const UNAUTHORIZED_CODES = [401, 403, 419]
+12 -2
View File
@@ -12,8 +12,18 @@ import { SenderKeyRecord } from './Group/sender-key-record'
import { GroupCipher, GroupSessionBuilder, SenderKeyDistributionMessage } from './Group'
import { LIDMappingStore } from './lid-mapping'
export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository {
const lidMapping = new LIDMappingStore(auth.keys as SignalKeyStoreWithTransaction)
export function makeLibSignalRepository(
auth: SignalAuthState,
onWhatsAppFunc?: (...jids: string[]) => Promise<
| {
jid: string
exists: boolean
lid: string
}[]
| undefined
>
): SignalRepository {
const lidMapping = new LIDMappingStore(auth.keys as SignalKeyStoreWithTransaction, onWhatsAppFunc)
const storage = signalStorage(auth, lidMapping)
const parsedKeys = auth.keys as SignalKeyStoreWithTransaction
+46 -10
View File
@@ -1,22 +1,51 @@
import type { SignalKeyStoreWithTransaction } from '../Types'
import logger from '../Utils/logger'
import { isJidUser, isLidUser, jidDecode } from '../WABinary'
import { isLidUser, isPnUser, jidDecode } from '../WABinary'
//TODO: Caching
export class LIDMappingStore {
private readonly keys: SignalKeyStoreWithTransaction
private onWhatsAppFunc?: (...jids: string[]) => Promise<
| {
jid: string
exists: boolean
lid: string
}[]
| undefined
>
constructor(keys: SignalKeyStoreWithTransaction) {
constructor(
keys: SignalKeyStoreWithTransaction,
onWhatsAppFunc?: (...jids: string[]) => Promise<
| {
jid: string
exists: boolean
lid: string
}[]
| undefined
>
) {
this.keys = keys
this.onWhatsAppFunc = onWhatsAppFunc // needed to get LID from PN if not found
}
/**
* Store LID-PN mapping - USER LEVEL
*/
async storeLIDPNMapping(lid: string, pn: string): Promise<void> {
return this.storeLIDPNMappings([{ lid, pn }])
}
/**
* Store LID-PN mapping - USER LEVEL
*/
async storeLIDPNMappings(pairs: { lid: string; pn: string }[]): Promise<void> {
// Validate inputs
if (!((isLidUser(lid) && isJidUser(pn)) || (isJidUser(lid) && isLidUser(pn)))) {
const pairMap: { [_: string]: string } = {}
for (const { lid, pn } of pairs) {
if (!((isLidUser(lid) && isPnUser(pn)) || (isPnUser(lid) && isLidUser(pn)))) {
logger.warn(`Invalid LID-PN mapping: ${lid}, ${pn}`)
return
continue
}
const [lidJid, pnJid] = isLidUser(lid) ? [lid, pn] : [pn, lid]
@@ -28,26 +57,28 @@ export class LIDMappingStore {
const pnUser = pnDecoded.user
const lidUser = lidDecoded.user
pairMap[pnUser] = lidUser
}
logger.trace(`Storing USER LID mapping: PN ${pnUser} → LID ${lidUser}`)
logger.trace({ pairMap }, `Storing ${Object.keys(pairMap).length} pn mappings`)
await this.keys.transaction(async () => {
for (const [pnUser, lidUser] of Object.entries(pairMap)) {
await this.keys.set({
'lid-mapping': {
[pnUser]: lidUser, // "554396160286" -> "102765716062358"
[`${lidUser}_reverse`]: pnUser // "102765716062358_reverse" -> "554396160286"
}
})
}
}, 'lid-mapping')
logger.trace(`USER LID mapping stored: PN ${pnUser} → LID ${lidUser}`)
}
/**
* Get LID for PN - Returns device-specific LID based on user mapping
*/
async getLIDForPN(pn: string): Promise<string | null> {
if (!isJidUser(pn)) return null
if (!isPnUser(pn)) return null
const decoded = jidDecode(pn)
if (!decoded) return null
@@ -55,12 +86,17 @@ export class LIDMappingStore {
// Look up user-level mapping (whatsmeow approach)
const pnUser = decoded.user
const stored = await this.keys.get('lid-mapping', [pnUser])
const lidUser = stored[pnUser]
let lidUser = stored[pnUser]
if (!lidUser) {
logger.trace(`No LID mapping found for PN user ${pnUser}`)
logger.trace(`No LID mapping found for PN user ${pnUser}; getting from USync`)
const { exists, lid } = (await this.onWhatsAppFunc?.(pn))?.[0]!
if (exists) {
lidUser = jidDecode(lid)?.user
} else {
return null
}
}
if (typeof lidUser !== 'string') return null
+4 -19
View File
@@ -53,7 +53,7 @@ import {
S_WHATSAPP_NET
} from '../WABinary'
import { USyncQuery, USyncUser } from '../WAUSync'
import { makeUSyncSocket } from './usync'
import { makeSocket } from './socket.js'
const MAX_SYNC_ATTEMPTS = 2
export const makeChatsSocket = (config: SocketConfig) => {
@@ -65,8 +65,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
shouldIgnoreJid,
shouldSyncHistoryMessage
} = config
const sock = makeUSyncSocket(config)
const { ev, ws, authState, generateMessageTag, sendNode, query, onUnexpectedError } = sock
const sock = makeSocket(config)
const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock
let privacySettings: { [_: string]: string } | undefined
@@ -221,21 +221,6 @@ export const makeChatsSocket = (config: SocketConfig) => {
return botList
}
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 sock.executeUSyncQuery(usyncQuery)
if (results) {
return results.list.filter(a => !!a.contact).map(({ contact, id, lid }) => ({ jid: id, exists: contact, lid }))
}
}
const fetchStatus = async (...jids: string[]) => {
const usyncQuery = new USyncQuery().withStatusProtocol()
@@ -1092,6 +1077,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
}
})(),
processMessage(msg, {
signalRepository,
shouldProcessHistoryMsg,
placeholderResendCache,
ev,
@@ -1195,7 +1181,6 @@ export const makeChatsSocket = (config: SocketConfig) => {
sendPresenceUpdate,
presenceSubscribe,
profilePictureUrl,
onWhatsApp,
fetchBlocklist,
fetchStatus,
fetchDisappearingDuration,
+11 -10
View File
@@ -1,14 +1,14 @@
import { proto } from '../../WAProto/index.js'
import type { GroupMetadata, GroupParticipant, ParticipantAction, SocketConfig, WAMessageKey } from '../Types'
import { WAMessageStubType } from '../Types'
import { WAMessageAddressingMode, WAMessageStubType } from '../Types'
import { generateMessageIDV2, unixTimestampSeconds } from '../Utils'
import {
type BinaryNode,
getBinaryNodeChild,
getBinaryNodeChildren,
getBinaryNodeChildString,
isJidUser,
isLidUser,
isPnUser,
jidEncode,
jidNormalizedUser
} from '../WABinary'
@@ -305,12 +305,12 @@ export const extractGroupMetadata = (result: BinaryNode) => {
let desc: string | undefined
let descId: string | undefined
let descOwner: string | undefined
let descOwnerJid: string | undefined
let descOwnerPn: string | undefined
let descTime: number | undefined
if (descChild) {
desc = getBinaryNodeChildString(descChild, 'body')
descOwner = descChild.attrs.participant ? jidNormalizedUser(descChild.attrs.participant) : undefined
descOwnerJid = descChild.attrs.participant_pn ? jidNormalizedUser(descChild.attrs.participant_pn) : undefined
descOwnerPn = descChild.attrs.participant_pn ? jidNormalizedUser(descChild.attrs.participant_pn) : undefined
descTime = +descChild.attrs.t!
descId = descChild.attrs.id
}
@@ -320,20 +320,21 @@ export const extractGroupMetadata = (result: BinaryNode) => {
const memberAddMode = getBinaryNodeChildString(group, 'member_add_mode') === 'all_member_add'
const metadata: GroupMetadata = {
id: groupId!,
addressingMode: group.attrs.addressing_mode as 'pn' | 'lid',
notify: group.attrs.notify,
addressingMode: group.attrs.addressing_mode === 'lid' ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN,
subject: group.attrs.subject!,
subjectOwner: group.attrs.s_o,
subjectOwnerJid: group.attrs.s_o_pn,
subjectOwnerPn: group.attrs.s_o_pn,
subjectTime: +group.attrs.s_t!,
size: group.attrs.size ? +group.attrs.size : getBinaryNodeChildren(group, 'participant').length,
creation: +group.attrs.creation!,
owner: group.attrs.creator ? jidNormalizedUser(group.attrs.creator) : undefined,
ownerJid: group.attrs.creator_pn ? jidNormalizedUser(group.attrs.creator_pn) : undefined,
ownerPn: group.attrs.creator_pn ? jidNormalizedUser(group.attrs.creator_pn) : undefined,
owner_country_code: group.attrs.creator_country_code,
desc,
descId,
descOwner,
descOwnerJid,
descOwnerPn,
descTime,
linkedParent: getBinaryNodeChild(group, 'linked_parent')?.attrs.jid || undefined,
restrict: !!getBinaryNodeChild(group, 'locked'),
@@ -345,8 +346,8 @@ export const extractGroupMetadata = (result: BinaryNode) => {
participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => {
return {
id: attrs.jid!,
jid: isJidUser(attrs.jid) ? attrs.jid : jidNormalizedUser(attrs.phone_number),
lid: isLidUser(attrs.jid) ? attrs.jid : attrs.lid,
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phoneNumber) ? attrs.phoneNumber : undefined,
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
admin: (attrs.type || null) as GroupParticipant['admin']
}
}),
+31 -46
View File
@@ -50,7 +50,6 @@ import {
getBinaryNodeChildString,
isJidGroup,
isJidStatusBroadcast,
isJidUser,
isLidUser,
jidDecode,
jidNormalizedUser,
@@ -549,6 +548,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const handleGroupNotification = (participant: string, child: BinaryNode, msg: Partial<proto.IWebMessageInfo>) => {
const participantJid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant
// TODO: Add participant LID
switch (child?.tag) {
case 'create':
const metadata = extractGroupMetadata(child)
@@ -697,11 +697,16 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
break
case 'devices':
const devices = getBinaryNodeChildren(child, 'device')
if (areJidsSameUser(child!.attrs.jid, authState.creds.me!.id)) {
const deviceJids = devices.map(d => d.attrs.jid)
logger.info({ deviceJids }, 'got my own devices')
if (
areJidsSameUser(child!.attrs.jid, authState.creds.me!.id) ||
areJidsSameUser(child!.attrs.lid, authState.creds.me!.lid)
) {
const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }))
logger.info({ deviceData }, 'my own devices changed')
}
//TODO: drop a new event, add hashes
break
case 'server_sync':
const update = getBinaryNodeChild(node, 'collection')
@@ -1034,7 +1039,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
// correctly set who is asking for the retry
key.participant = key.participant || attrs.from
const retryNode = getBinaryNodeChild(node, 'retry')
if (ids[0] && key.participant && willSendMessageAgain(ids[0], key.participant!)) {
if (ids[0] && key.participant && willSendMessageAgain(ids[0], key.participant)) {
if (key.fromMe) {
try {
updateSendMessageAgainCount(ids[0], key.participant)
@@ -1142,7 +1147,25 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER &&
node.attrs.sender_pn
) {
ev.emit('chats.phoneNumberShare', { lid: node.attrs.from!, jid: node.attrs.sender_pn })
const lid = jidNormalizedUser(node.attrs.from),
pn = jidNormalizedUser(node.attrs.sender_pn)
ev.emit('lid-mapping.update', { lid, pn })
await signalRepository.storeLIDPNMapping(lid, pn)
}
const alt = msg.key.participantAlt || msg.key.remoteJidAlt
// store new mappings we didn't have before
if (!!alt) {
const altServer = jidDecode(alt)?.server
const lidMapping = signalRepository.getLIDMappingStore()
if (altServer === 'lid') {
if (!(await lidMapping.getPNForLID(alt))) {
await lidMapping.storeLIDPNMapping(alt, msg.key.participant || msg.key.remoteJid!)
}
} else {
if (!(await lidMapping.getLIDForPN(alt))) {
await lidMapping.storeLIDPNMapping(msg.key.participant || msg.key.remoteJid!, alt)
}
}
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
@@ -1154,44 +1177,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
},
'Added message to recent cache for retry receipts'
)
if (msg.message?.protocolMessage?.lidMigrationMappingSyncMessage?.encodedMappingPayload) {
try {
const payload = msg.message.protocolMessage.lidMigrationMappingSyncMessage.encodedMappingPayload
const decoded = proto.LIDMigrationMappingSyncPayload.decode(payload)
logger.debug(
{
mappingCount: decoded.pnToLidMappings?.length || 0,
timestamp: decoded.chatDbMigrationTimestamp
},
'Received LID migration sync message from server'
)
const lidMapping = signalRepository.getLIDMappingStore()
if (decoded.pnToLidMappings && decoded.pnToLidMappings.length > 0) {
for (const mapping of decoded.pnToLidMappings) {
const pn = `${mapping.pn}@s.whatsapp.net`
// Use latestLid if available, otherwise assignedLid (proper LID refresh)
const lidValue = mapping.latestLid || mapping.assignedLid
const lid = `${lidValue}@lid`
await lidMapping.storeLIDPNMapping(lid, pn)
logger.debug(
{
pn,
lid,
assignedLid: mapping.assignedLid,
latestLid: mapping.latestLid,
usedLatest: !!mapping.latestLid
},
'Stored server-provided PN-LID mapping'
)
}
}
} catch (error) {
logger.error({ error }, 'Failed to process LID migration sync message')
}
}
try {
@@ -1263,8 +1248,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
// message was sent by us from a different device
type = 'sender'
// need to specially handle this case
if (isJidUser(msg.key.remoteJid!)) {
participant = author
if (isLidUser(msg.key.remoteJid!) || isLidUser(msg.key.remoteJidAlt)) {
participant = author // TODO: investigate sending receipts to LIDs and not PNs
}
} else if (!sendActiveReceipts) {
type = 'inactive'
+9 -12
View File
@@ -11,6 +11,7 @@ import type {
SocketConfig,
WAMessageKey
} from '../Types'
import { WAMessageAddressingMode } from '../Types'
import {
aggregateMessageKeysNotFromMe,
assertMediaContent,
@@ -40,16 +41,15 @@ import {
getBinaryNodeChild,
getBinaryNodeChildren,
isJidGroup,
isJidUser,
isPnUser,
jidDecode,
jidEncode,
jidNormalizedUser,
type JidWithDevice,
S_WHATSAPP_NET
S_WHATSAPP_NET,
transferDevice
} from '../WABinary'
import { USyncQuery, USyncUser } from '../WAUSync'
import { makeGroupsSocket } from './groups'
import type { NewsletterSocket } from './newsletter'
import { makeNewsletterSocket } from './newsletter'
export const makeMessagesSocket = (config: SocketConfig) => {
@@ -63,7 +63,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
enableRecentMessageCache,
maxMsgRetryCount
} = config
const sock: NewsletterSocket = makeNewsletterSocket(makeGroupsSocket(config))
const sock = makeNewsletterSocket(config)
const {
ev,
authState,
@@ -147,7 +147,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
node.attrs.t = unixTimestampSeconds().toString()
}
if (type === 'sender' && isJidUser(jid)) {
if (type === 'sender' && isPnUser(jid)) {
node.attrs.recipient = jid
node.attrs.to = participant!
} else {
@@ -445,11 +445,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
if (!jid.includes('@s.whatsapp.net')) return
try {
const jidDecoded = jidDecode(jid)
const deviceId = jidDecoded?.device || 0
const lidDecoded = jidDecode(lidForPN)
const lidWithDevice = jidEncode(lidDecoded?.user!, 'lid', deviceId)
const lidWithDevice = transferDevice(jid, lidForPN)
await signalRepository.migrateSession(jid, lidWithDevice)
logger.debug({ fromJid: jid, toJid: lidWithDevice }, 'Migrated device session to LID')
@@ -904,7 +900,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
}
if (!isStatus) {
const groupAddressingMode = groupData?.addressingMode || (isLid ? 'lid' : 'pn')
const groupAddressingMode =
groupData?.addressingMode || (isLid ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN)
additionalAttributes = {
...additionalAttributes,
addressing_mode: groupAddressingMode
+4 -3
View File
@@ -1,9 +1,9 @@
import type { NewsletterCreateResponse, WAMediaUpload } from '../Types'
import type { NewsletterCreateResponse, SocketConfig, WAMediaUpload } from '../Types'
import type { NewsletterMetadata, NewsletterUpdate } from '../Types'
import { QueryIds, XWAPaths } from '../Types'
import { generateProfilePicture } from '../Utils/messages-media'
import { getBinaryNodeChild } from '../WABinary'
import type { GroupsSocket } from './groups'
import { makeGroupsSocket } from './groups'
import { executeWMexQuery as genericExecuteWMexQuery } from './mex'
const parseNewsletterCreateResponse = (response: NewsletterCreateResponse): NewsletterMetadata => {
@@ -41,7 +41,8 @@ const parseNewsletterMetadata = (result: unknown): NewsletterMetadata | null =>
return null
}
export const makeNewsletterSocket = (sock: GroupsSocket) => {
export const makeNewsletterSocket = (config: SocketConfig) => {
const sock = makeGroupsSocket(config)
const { query, generateMessageTag } = sock
const executeWMexQuery = <T>(variables: Record<string, unknown>, queryId: string, dataPath: string): Promise<T> => {
+170 -93
View File
@@ -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
}
}
-73
View File
@@ -1,73 +0,0 @@
import { Boom } from '@hapi/boom'
import type { SocketConfig } from '../Types'
import { type BinaryNode, S_WHATSAPP_NET } from '../WABinary'
import { USyncQuery } from '../WAUSync'
import { makeSocket } from './socket'
export const makeUSyncSocket = (config: SocketConfig) => {
const sock = makeSocket(config)
const { generateMessageTag, query } = sock
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)
}
return {
...sock,
executeUSyncQuery
}
}
+4 -4
View File
@@ -1,10 +1,10 @@
export interface Contact {
/** ID either in lid or jid format **/
/** ID either in lid or jid format (preferred) **/
id: string
/** ID in Lid (anonymous) format (@lid) **/
/** ID in LID format (@lid) **/
lid?: string
/** ID in Phone Number format (@s.whatsapp.net) **/
jid?: string
/** ID in PN format (@s.whatsapp.net) **/
phoneNumber?: string
/** name of the contact, you have saved on your WA */
name?: string
/** name of the contact, the contact has set on their own on WA */
+1 -1
View File
@@ -30,7 +30,7 @@ export type BaileysEventMap = {
'chats.upsert': Chat[]
/** update the given chats */
'chats.update': ChatUpdate[]
'chats.phoneNumberShare': { lid: string; jid: string }
'lid-mapping.update': { lid: string; pn: string }
/** delete chats with given ID */
'chats.delete': string[]
/** presence of contact in a chat updated */
+6 -4
View File
@@ -1,4 +1,5 @@
import type { Contact } from './Contact'
import type { WAMessageAddressingMode } from './Message'
export type GroupParticipant = Contact & {
isAdmin?: boolean
@@ -14,21 +15,22 @@ export type RequestJoinMethod = 'invite_link' | 'linked_group_join' | 'non_admin
export interface GroupMetadata {
id: string
notify?: string
/** group uses 'lid' or 'pn' to send messages */
addressingMode: 'pn' | 'lid'
addressingMode?: WAMessageAddressingMode
owner: string | undefined
ownerJid?: string | undefined
ownerPn?: string | undefined
owner_country_code?: string | undefined
subject: string
/** group subject owner */
subjectOwner?: string
subjectOwnerJid?: string
subjectOwnerPn?: string
/** group subject modification date */
subjectTime?: number
creation?: number
desc?: string
descOwner?: string
descOwnerJid?: string
descOwnerPn?: string
descId?: string
descTime?: number
/** if this group is part of a community, it returns the jid of the community to which it belongs */
+8 -5
View File
@@ -14,12 +14,10 @@ export type WAMessageContent = proto.IMessage
export type WAContactMessage = proto.Message.IContactMessage
export type WAContactsArrayMessage = proto.Message.IContactsArrayMessage
export type WAMessageKey = proto.IMessageKey & {
senderLid?: string
remoteJidAlt?: string
participantAlt?: string
server_id?: string
senderPn?: string
participantLid?: string
participantPn?: string
isViewOnce?: boolean
isViewOnce?: boolean // TODO: remove out of the message key, place in WebMessageInfo
}
export type WATextMessage = proto.Message.IExtendedTextMessage
export type WAContextInfo = proto.IContextInfo
@@ -39,6 +37,11 @@ export type WAMediaUpload = Buffer | WAMediaPayloadStream | WAMediaPayloadURL
/** Set of message types that are supported by the library */
export type MessageType = keyof proto.Message
export enum WAMessageAddressingMode {
PN = 'pn',
LID = 'lid'
}
export type MessageWithContextInfo =
| 'imageMessage'
| 'contactMessage'
+11 -1
View File
@@ -137,5 +137,15 @@ export type SocketConfig = {
/** cached group metadata, use to prevent redundant requests to WA & speed up msg sending */
cachedGroupMetadata: (jid: string) => Promise<GroupMetadata | undefined>
makeSignalRepository: (auth: SignalAuthState) => SignalRepository
makeSignalRepository: (
auth: SignalAuthState,
onWhatsAppFunc?: (...jids: string[]) => Promise<
| {
jid: string
exists: boolean
lid: string
}[]
| undefined
>
) => SignalRepository
}
+2 -9
View File
@@ -19,14 +19,7 @@ import {
LabelAssociationType,
type MessageLabelAssociation
} from '../Types/LabelAssociation'
import {
type BinaryNode,
getBinaryNodeChild,
getBinaryNodeChildren,
isJidGroup,
isJidUser,
jidNormalizedUser
} from '../WABinary'
import { type BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser } from '../WABinary'
import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto'
import { toNumber } from './generics'
import type { ILogger } from './logger'
@@ -845,7 +838,7 @@ export const processSyncAction = (
id: id!,
name: action.contactAction.fullName!,
lid: action.contactAction.lidJid || undefined,
jid: isJidUser(id) ? id : undefined
phoneNumber: action.contactAction.pnJid || undefined
}
])
} else if (action?.pushNameSetting) {
+29 -31
View File
@@ -6,14 +6,12 @@ import {
type BinaryNode,
isJidBroadcast,
isJidGroup,
isJidMetaIa,
isJidMetaAI,
isJidNewsletter,
isJidStatusBroadcast,
isJidUser,
isLidUser,
jidDecode,
jidEncode,
jidNormalizedUser
isPnUser,
transferDevice
} from '../WABinary'
import { unpadRandomMax16 } from './generics'
import type { ILogger } from './logger'
@@ -23,17 +21,7 @@ const getDecryptionJid = async (sender: string, repository: SignalRepository): P
return sender
}
const lidMapping = repository.getLIDMappingStore()
const normalizedSender = jidNormalizedUser(sender)
const lidForPN = await lidMapping.getLIDForPN(normalizedSender)
if (lidForPN?.includes('@lid')) {
const senderDecoded = jidDecode(sender)
const deviceId = senderDecoded?.device || 0
return jidEncode(jidDecode(lidForPN)!.user, 'lid', deviceId)
}
return sender
return (await repository.getLIDMappingStore().getLIDForPN(sender))!
}
const storeMappingFromEnvelope = async (
@@ -45,7 +33,7 @@ const storeMappingFromEnvelope = async (
): Promise<void> => {
const { senderAlt } = extractAddressingContext(stanza)
if (senderAlt && isLidUser(senderAlt) && isJidUser(sender) && decryptionJid === sender) {
if (senderAlt && isLidUser(senderAlt) && isPnUser(sender) && decryptionJid === sender) {
try {
await repository.storeLIDPNMapping(senderAlt, sender)
logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope')
@@ -95,14 +83,23 @@ export const extractAddressingContext = (stanza: BinaryNode) => {
let senderAlt: string | undefined
let recipientAlt: string | undefined
const sender = stanza.attrs.participant || stanza.attrs.from
if (addressingMode === 'lid') {
// Message is LID-addressed: sender is LID, extract corresponding PN
senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn
// without device data
senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn
recipientAlt = stanza.attrs.recipient_pn
// with device data
if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
} else {
// Message is PN-addressed: sender is PN, extract corresponding LID
senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid
// without device data
senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid
recipientAlt = stanza.attrs.recipient_lid
//with device data
if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
}
return {
@@ -126,11 +123,13 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin
const participant: string | undefined = stanza.attrs.participant
const recipient: string | undefined = stanza.attrs.recipient
const addressingContext = extractAddressingContext(stanza)
const isMe = (jid: string) => areJidsSameUser(jid, meId)
const isMeLid = (jid: string) => areJidsSameUser(jid, meLid)
if (isJidUser(from) || isLidUser(from)) {
if (recipient && !isJidMetaIa(recipient)) {
if (isPnUser(from) || isLidUser(from)) {
if (recipient && !isJidMetaAI(recipient)) {
if (!isMe(from!) && !isMeLid(from!)) {
throw new Boom('receipient present, but msg not from me', { data: stanza })
}
@@ -177,13 +176,11 @@ export function decodeMessageNode(stanza: BinaryNode, meId: string, meLid: strin
const key: WAMessageKey = {
remoteJid: chatId,
remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
fromMe,
id: msgId,
senderLid: stanza?.attrs?.sender_lid,
senderPn: stanza?.attrs?.sender_pn || stanza?.attrs?.peer_recipient_pn,
participant,
participantPn: stanza?.attrs?.participant_pn,
participantLid: stanza?.attrs?.participant_lid,
participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
}
@@ -228,7 +225,7 @@ export const decryptMessageNode = (
}
if (tag === 'unavailable' && attrs.type === 'view_once') {
fullMessage.key.isViewOnce = true
fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE
}
if (tag !== 'enc' && tag !== 'plaintext') {
@@ -243,6 +240,12 @@ export const decryptMessageNode = (
let msgBuffer: Uint8Array
const user = isPnUser(sender) ? sender : author // TODO: flaky logic
const decryptionJid = await getDecryptionJid(user, repository)
if (tag !== 'plaintext') {
await storeMappingFromEnvelope(stanza, user, decryptionJid, repository, logger)
}
try {
const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type
@@ -256,16 +259,11 @@ export const decryptMessageNode = (
break
case 'pkmsg':
case 'msg':
const user = isJidUser(sender) ? sender : author
const decryptionJid = await getDecryptionJid(user, repository)
msgBuffer = await repository.decryptMessage({
jid: decryptionJid,
type: e2eType,
ciphertext: content
})
await storeMappingFromEnvelope(stanza, user, decryptionJid, repository, logger)
break
case 'plaintext':
msgBuffer = content
+1 -2
View File
@@ -3,8 +3,7 @@ import axios, { type AxiosRequestConfig } from 'axios'
import { createHash, randomBytes } from 'crypto'
import { platform, release } from 'os'
import { proto } from '../../WAProto/index.js'
import version from '../Defaults/baileys-version.json' with { type: 'json' }
const baileysVersion = version.version
const baileysVersion = [2, 3000, 1023223821]
import type {
BaileysEventEmitter,
BaileysEventMap,
+1 -2
View File
@@ -4,7 +4,6 @@ import { inflate } from 'zlib'
import { proto } from '../../WAProto/index.js'
import type { Chat, Contact } from '../Types'
import { WAMessageStubType } from '../Types'
import { isJidUser } from '../WABinary'
import { toNumber } from './generics'
import { normalizeMessageContent } from './messages'
import { downloadContentFromMessage } from './messages-media'
@@ -42,7 +41,7 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
id: chat.id,
name: chat.name || undefined,
lid: chat.lidJid || undefined,
jid: isJidUser(chat.id) ? chat.id : undefined
phoneNumber: chat.pnJid || undefined
})
const msgs = chat.messages || []
+1 -1
View File
@@ -1,5 +1,5 @@
import { LRUCache } from 'lru-cache'
import { proto } from '../../WAProto'
import type { proto } from '../../WAProto/index.js'
import type { ILogger } from './logger'
/** Number of sent messages to cache in memory for handling retry receipts */
+26 -2
View File
@@ -9,7 +9,8 @@ import type {
ParticipantAction,
RequestJoinAction,
RequestJoinMethod,
SignalKeyStoreWithTransaction
SignalKeyStoreWithTransaction,
SignalRepository
} from '../Types'
import { WAMessageStubType } from '../Types'
import { getContentType, normalizeMessageContent } from '../Utils/messages'
@@ -27,6 +28,7 @@ type ProcessMessageContext = {
ev: BaileysEventEmitter
logger?: ILogger
options: AxiosRequestConfig<{}>
signalRepository: SignalRepository
}
const REAL_MSG_STUB_TYPES = new Set([
@@ -145,7 +147,16 @@ export function decryptPollVote(
const processMessage = async (
message: proto.IWebMessageInfo,
{ shouldProcessHistoryMsg, placeholderResendCache, ev, creds, keyStore, logger, options }: ProcessMessageContext
{
shouldProcessHistoryMsg,
placeholderResendCache,
ev,
creds,
signalRepository,
keyStore,
logger,
options
}: ProcessMessageContext
) => {
const meId = creds.me!.id
const { accountSettings } = creds
@@ -292,6 +303,19 @@ const processMessage = async (
}
])
break
case proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC:
const lidMappingStore = signalRepository.getLIDMappingStore()
const encodedPayload = protocolMsg.lidMigrationMappingSyncMessage?.encodedMappingPayload!
const { pnToLidMappings, chatDbMigrationTimestamp } =
proto.LIDMigrationMappingSyncPayload.decode(encodedPayload)
logger?.debug({ pnToLidMappings, chatDbMigrationTimestamp }, 'got lid mappings and chat db migration timestamp')
const pairs = []
for (const { pn, latestLid, assignedLid } of pnToLidMappings) {
const lid = latestLid || assignedLid
pairs.push({ lid: `${lid}@lid`, pn: `${pn}@s.whatsapp.net` })
}
await lidMappingStore.storeLIDPNMappings(pairs)
}
} else if (content?.reactionMessage) {
const reaction: proto.IReaction = {
+3 -2
View File
@@ -133,6 +133,7 @@ export const configureSuccessfulPairing = (
const bizName = businessNode?.attrs.name
const jid = deviceNode.attrs.jid
const lid = deviceNode.attrs.lid
const { details, hmac, accountType } = proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityNode.content as Buffer)
const isHostedAccount = accountType !== undefined && accountType === proto.ADVEncryptionType.HOSTED
@@ -154,7 +155,7 @@ export const configureSuccessfulPairing = (
const deviceMsg = Buffer.concat([devicePrefix, deviceDetails, signedIdentityKey.public, accountSignatureKey])
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg)
const identity = createSignalIdentity(jid!, accountSignatureKey)
const identity = createSignalIdentity(lid!, accountSignatureKey)
const accountEnc = encodeSignedDeviceIdentity(account, false)
const deviceIdentity = proto.ADVDeviceIdentity.decode(account.details)
@@ -183,7 +184,7 @@ export const configureSuccessfulPairing = (
const authUpdate: Partial<AuthenticationCreds> = {
account,
me: { id: jid!, name: bizName },
me: { id: jid!, name: bizName, lid },
signalIdentities: [...(signalIdentities || []), identity],
platform: platformNode?.attrs.name
}
+12 -5
View File
@@ -44,11 +44,11 @@ export const jidDecode = (jid: string | undefined): FullJid | undefined => {
/** is the jid a user */
export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefined) =>
jidDecode(jid1)?.user === jidDecode(jid2)?.user
/** is the jid Meta IA */
export const isJidMetaIa = (jid: string | undefined) => jid?.endsWith('@bot')
/** is the jid a user */
export const isJidUser = (jid: string | undefined) => jid?.endsWith('@s.whatsapp.net')
/** is the jid a group */
/** is the jid Meta AI */
export const isJidMetaAI = (jid: string | undefined) => jid?.endsWith('@bot')
/** is the jid a PN user */
export const isPnUser = (jid: string | undefined) => jid?.endsWith('@s.whatsapp.net')
/** is the jid a LID */
export const isLidUser = (jid: string | undefined) => jid?.endsWith('@lid')
/** is the jid a broadcast */
export const isJidBroadcast = (jid: string | undefined) => jid?.endsWith('@broadcast')
@@ -72,3 +72,10 @@ export const jidNormalizedUser = (jid: string | undefined) => {
const { user, server } = result
return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : (server as JidServer))
}
export const transferDevice = (fromJid: string, toJid: string) => {
const fromDecoded = jidDecode(fromJid)
const deviceId = fromDecoded?.device || 0
const { server, user } = jidDecode(toJid)!
return jidEncode(user, server, deviceId)
}
+184 -29
View File
@@ -622,7 +622,7 @@ __metadata:
languageName: node
linkType: hard
"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0":
"@eslint-community/eslint-utils@npm:^4.7.0":
version: 4.7.0
resolution: "@eslint-community/eslint-utils@npm:4.7.0"
dependencies:
@@ -633,6 +633,17 @@ __metadata:
languageName: node
linkType: hard
"@eslint-community/eslint-utils@npm:^4.8.0":
version: 4.8.0
resolution: "@eslint-community/eslint-utils@npm:4.8.0"
dependencies:
eslint-visitor-keys: "npm:^3.4.3"
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
checksum: 10c0/33b93d2a4e9d5fe4c11d02d0fc5ed69e12fcb1e7ca031ded0d6adb24e768c36df77288ed79eecc784f9db34219816247db27688dfe869fb7fbf096840a097d7a
languageName: node
linkType: hard
"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1":
version: 4.12.1
resolution: "@eslint-community/regexpp@npm:4.12.1"
@@ -651,19 +662,19 @@ __metadata:
languageName: node
linkType: hard
"@eslint/config-helpers@npm:^0.3.0":
version: 0.3.0
resolution: "@eslint/config-helpers@npm:0.3.0"
checksum: 10c0/013ae7b189eeae8b30cc2ee87bc5c9c091a9cd615579003290eb28bebad5d78806a478e74ba10b3fe08ed66975b52af7d2cd4b4b43990376412b14e5664878c8
"@eslint/config-helpers@npm:^0.3.1":
version: 0.3.1
resolution: "@eslint/config-helpers@npm:0.3.1"
checksum: 10c0/f6c5b3a0b76a0d7d84cc93e310c259e6c3e0792ddd0a62c5fc0027796ffae44183432cb74b2c2b1162801ee1b1b34a6beb5d90a151632b4df7349f994146a856
languageName: node
linkType: hard
"@eslint/core@npm:^0.15.0, @eslint/core@npm:^0.15.1":
version: 0.15.1
resolution: "@eslint/core@npm:0.15.1"
"@eslint/core@npm:^0.15.2":
version: 0.15.2
resolution: "@eslint/core@npm:0.15.2"
dependencies:
"@types/json-schema": "npm:^7.0.15"
checksum: 10c0/abaf641940776638b8c15a38d99ce0dac551a8939310ec81b9acd15836a574cf362588eaab03ab11919bc2a0f9648b19ea8dee33bf12675eb5b6fd38bda6f25e
checksum: 10c0/c17a6dc4f5a6006ecb60165cc38bcd21fefb4a10c7a2578a0cfe5813bbd442531a87ed741da5adab5eb678e8e693fda2e2b14555b035355537e32bcec367ea17
languageName: node
linkType: hard
@@ -684,7 +695,14 @@ __metadata:
languageName: node
linkType: hard
"@eslint/js@npm:9.31.0, @eslint/js@npm:^9.31.0":
"@eslint/js@npm:9.35.0":
version: 9.35.0
resolution: "@eslint/js@npm:9.35.0"
checksum: 10c0/d40fe38724bc76c085c0b753cdf937fa35c0d6807ae76b2632e3f5f66c3040c91adcf1aff2ce70b4f45752e60629fadc415eeec9af3be3c274bae1cac54b9840
languageName: node
linkType: hard
"@eslint/js@npm:^9.31.0":
version: 9.31.0
resolution: "@eslint/js@npm:9.31.0"
checksum: 10c0/f9d4c73d0fafe70679a418cbb25ab7ebcc8f1dba6c32456d6f8ba5a137d583ecff233cfe10f61f41d7d4d2220e94cff1f39fc7ed1fa3819d1888dee1cad678ea
@@ -698,13 +716,13 @@ __metadata:
languageName: node
linkType: hard
"@eslint/plugin-kit@npm:^0.3.1":
version: 0.3.3
resolution: "@eslint/plugin-kit@npm:0.3.3"
"@eslint/plugin-kit@npm:^0.3.5":
version: 0.3.5
resolution: "@eslint/plugin-kit@npm:0.3.5"
dependencies:
"@eslint/core": "npm:^0.15.1"
"@eslint/core": "npm:^0.15.2"
levn: "npm:^0.4.1"
checksum: 10c0/c61888eb8757abc0d25a53c1832f85521c2f347126c475eb32d3596be3505e8619e0ceddee7346d195089a2eb1633b61e6127a5772b8965a85eb9f55b8b1cebe
checksum: 10c0/c178c1b58c574200c0fd125af3e4bc775daba7ce434ba6d1eeaf9bcb64b2e9fea75efabffb3ed3ab28858e55a016a5efa95f509994ee4341b341199ca630b89e
languageName: node
linkType: hard
@@ -2162,7 +2180,28 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^8.32.0, @typescript-eslint/eslint-plugin@npm:^8.37.0":
"@typescript-eslint/eslint-plugin@npm:^8":
version: 8.42.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.42.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.42.0"
"@typescript-eslint/type-utils": "npm:8.42.0"
"@typescript-eslint/utils": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
"@typescript-eslint/parser": ^8.42.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/835fd7497f0e4eaef55dc3d94079acc0ad1dc74735916915f160419b1e7f44d04fbce683b4871148d1af33046bd5ae3fed59103d4c49460776b560c42173bbff
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.37.0"
dependencies:
@@ -2183,7 +2222,23 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^8.32.0, @typescript-eslint/parser@npm:^8.37.0":
"@typescript-eslint/parser@npm:^8":
version: 8.42.0
resolution: "@typescript-eslint/parser@npm:8.42.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.42.0"
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/f071154bce7f874449236919a7367d977317959fe6d454fe5369ca54dee7d057fe3b8b250c5990ea4205a9c52fd59702da63d1721895c72d745168aa31532112
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/parser@npm:8.37.0"
dependencies:
@@ -2212,6 +2267,19 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/project-service@npm:8.42.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.42.0"
"@typescript-eslint/types": "npm:^8.42.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/788b0bc52683be376cd768a4fed3202cdaccc86f231ec94a0f6bbb1389fdfd0e14c505f03015cefb73869de63c8089b78a169ed957048a1e5ee1b6250ec19604
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/scope-manager@npm:8.37.0"
@@ -2222,6 +2290,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/scope-manager@npm:8.42.0"
dependencies:
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
checksum: 10c0/caca15f2124909c588ed3e48fe0769ad8baa296a0b229f724ec94f5f746e486e08dd49eeddd66d01f09e2ddaed03f9e18d7b535a44196d413f283e22f929f623
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.37.0, @typescript-eslint/tsconfig-utils@npm:^8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.37.0"
@@ -2231,6 +2309,15 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.42.0, @typescript-eslint/tsconfig-utils@npm:^8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.42.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/03882eeee279fafa2cb4ee3154742417fd29395b3bfe3f867d9d4cb9cb68d1200c885c35b96dd558a1aff8561ac3700cff8ca7680a5cf34e5e0e136a6ee3c30c
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/type-utils@npm:8.37.0"
@@ -2247,6 +2334,22 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/type-utils@npm:8.42.0"
dependencies:
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0"
"@typescript-eslint/utils": "npm:8.42.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/47e5f7276cafd7719d3e2f2e456fa988927e658d15c2c188a692d9c639f9d76f582a6c133cb1bf01eba9027e1022eb6b79b57861a96302460e5e847c2b536afa
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.37.0, @typescript-eslint/types@npm:^8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/types@npm:8.37.0"
@@ -2254,6 +2357,13 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.42.0, @typescript-eslint/types@npm:^8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/types@npm:8.42.0"
checksum: 10c0/d585dff5005328282cc59f9402e886a3db64727906ad3e68b49d7ef73bc07bef3ed569287ba826ebaa07b69be42a72232a38529951d64c28cebd83db0892cd33
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/typescript-estree@npm:8.37.0"
@@ -2274,6 +2384,26 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/typescript-estree@npm:8.42.0"
dependencies:
"@typescript-eslint/project-service": "npm:8.42.0"
"@typescript-eslint/tsconfig-utils": "npm:8.42.0"
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
minimatch: "npm:^9.0.4"
semver: "npm:^7.6.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/2d3354d780421cfa90f812048984c43cd47aabecef7a5c0f56ad0b91331cb369d1c8366da90bf9a8f6df47df3741f9e16897e998f16270ac55376f519b775c23
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/utils@npm:8.37.0"
@@ -2289,6 +2419,21 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/utils@npm:8.42.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.42.0"
"@typescript-eslint/types": "npm:8.42.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/acf30019023669ddae00c02cabfa74fc12defccd4703e552ab5115edbeceaaf1688c1586873bf66aefeb3f03eb1ed456905403303913c724db38bf030e40a700
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.37.0":
version: 8.37.0
resolution: "@typescript-eslint/visitor-keys@npm:8.37.0"
@@ -2299,6 +2444,16 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.42.0":
version: 8.42.0
resolution: "@typescript-eslint/visitor-keys@npm:8.42.0"
dependencies:
"@typescript-eslint/types": "npm:8.42.0"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10c0/22c942f2a100d71c08f952b976446e824ddf227d4ac02b7016e12d4a33804ab06072d570695baed3565d0a08a1d3fa6ff3ccf97a122d63e65780f871d597f1b1
languageName: node
linkType: hard
"@ungap/structured-clone@npm:^1.3.0":
version: 1.3.0
resolution: "@ungap/structured-clone@npm:1.3.0"
@@ -2849,14 +3004,14 @@ __metadata:
"@types/jest": "npm:^30.0.0"
"@types/node": "npm:^20.9.0"
"@types/ws": "npm:^8.0.0"
"@typescript-eslint/eslint-plugin": "npm:^8.32.0"
"@typescript-eslint/parser": "npm:^8.32.0"
"@typescript-eslint/eslint-plugin": "npm:^8"
"@typescript-eslint/parser": "npm:^8"
"@whiskeysockets/eslint-config": "npm:^1.0.0"
async-mutex: "npm:^0.5.0"
axios: "npm:^1.6.0"
conventional-changelog-cli: "npm:^2.2.2"
esbuild-register: "npm:^3.6.0"
eslint: "npm:^9.31.0"
eslint: "npm:^9"
eslint-config-prettier: "npm:^10.1.2"
eslint-plugin-prettier: "npm:^5.4.0"
jest: "npm:^30.0.5"
@@ -4513,18 +4668,18 @@ __metadata:
languageName: node
linkType: hard
"eslint@npm:^9.31.0":
version: 9.31.0
resolution: "eslint@npm:9.31.0"
"eslint@npm:^9":
version: 9.35.0
resolution: "eslint@npm:9.35.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.2.0"
"@eslint-community/eslint-utils": "npm:^4.8.0"
"@eslint-community/regexpp": "npm:^4.12.1"
"@eslint/config-array": "npm:^0.21.0"
"@eslint/config-helpers": "npm:^0.3.0"
"@eslint/core": "npm:^0.15.0"
"@eslint/config-helpers": "npm:^0.3.1"
"@eslint/core": "npm:^0.15.2"
"@eslint/eslintrc": "npm:^3.3.1"
"@eslint/js": "npm:9.31.0"
"@eslint/plugin-kit": "npm:^0.3.1"
"@eslint/js": "npm:9.35.0"
"@eslint/plugin-kit": "npm:^0.3.5"
"@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
"@humanwhocodes/retry": "npm:^0.4.2"
@@ -4559,7 +4714,7 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
checksum: 10c0/3fd1cd5b38b907ecb3f5e7537ab91204efb38bc1ad0ca6e46fc4112f13b594272ff56e641b41580049bc333fbcb5b1b99ca9a542e8406e7da5e951068cbaec77
checksum: 10c0/798c527520ccf62106f8cd210bd1db1f8eb1b0e7a56feb0a8b322bf3a1e6a0bc6dc3a414542c22b1b393d58d5e3cd0252c44c023049de9067b836450503a2f03
languageName: node
linkType: hard