lint: stricter linting rules

This commit is contained in:
Adhiraj Singh
2022-03-01 16:32:14 +05:30
parent c00c3da313
commit de7d1002a9
39 changed files with 534 additions and 526 deletions

View File

@@ -52,10 +52,18 @@
"sharp": "^0.29.3" "sharp": "^0.29.3"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@adiwajshing/keyed-db": { "optional": true }, "@adiwajshing/keyed-db": {
"jimp": { "optional": true }, "optional": true
"qrcode-terminal": { "optional": true }, },
"sharp": { "optional": true } "jimp": {
"optional": true
},
"qrcode-terminal": {
"optional": true
},
"sharp": {
"optional": true
}
}, },
"files": [ "files": [
"lib/*", "lib/*",

View File

@@ -10,7 +10,7 @@ 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 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 NOISE_WA_HEADER = new Uint8Array([87, 65, 5, 2]) // last is "DICT_VERSION" export const NOISE_WA_HEADER = new Uint8Array([87, 65, 5, 2]) // last is "DICT_VERSION"

View File

@@ -49,7 +49,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
mapProductCreate(product) mapProductCreate(product)
] ]
}) })
return mapProduct(result.data.product) return mapProduct(result.data.product)
} }
@@ -80,7 +80,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
{ {
product_id: productId, product_id: productId,
...mapProductCreate( ...mapProductCreate(
{ ...update, originCountryCode: undefined }, { ...update, originCountryCode: undefined },
false false
) )
} }
@@ -89,7 +89,7 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
return mapProduct(result.data.product) return mapProduct(result.data.product)
} }
// maps product create to send to WA // maps product create to send to WA
const mapProductCreate = (product: ProductCreate, mapCompliance = true) => { const mapProductCreate = (product: ProductCreate, mapCompliance = true) => {
const result: any = { const result: any = {
@@ -107,10 +107,10 @@ const makeBusinessSocket = (config: LegacySocketConfig) => {
} }
if(mapCompliance) { if(mapCompliance) {
Object.assign(result, { Object.assign(result, {
compliance_category: product.originCountryCode compliance_category: product.originCountryCode
? undefined : ? undefined :
'COUNTRY_ORIGIN_EXEMPT', 'COUNTRY_ORIGIN_EXEMPT',
compliance_info: product.originCountryCode compliance_info: product.originCountryCode
? { country_code_origin: product.originCountryCode } ? { country_code_origin: product.originCountryCode }
: undefined : undefined
}) })

View File

@@ -6,12 +6,12 @@ import makeAuthSocket from './auth'
const makeChatsSocket = (config: LegacySocketConfig) => { const makeChatsSocket = (config: LegacySocketConfig) => {
const { logger } = config const { logger } = config
const sock = makeAuthSocket(config) const sock = makeAuthSocket(config)
const { const {
ev, ev,
ws: socketEvents, ws: socketEvents,
currentEpoch, currentEpoch,
setQuery, setQuery,
query, query,
sendNode, sendNode,
state state
} = sock } = sock
@@ -29,9 +29,9 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
) )
const profilePictureUrl = async(jid: string, timeoutMs?: number) => { const profilePictureUrl = async(jid: string, timeoutMs?: number) => {
const response = await query({ const response = await query({
json: ['query', 'ProfilePicThumb', jid], json: ['query', 'ProfilePicThumb', jid],
expect200: false, expect200: false,
requiresPhoneConnection: false, requiresPhoneConnection: false,
timeoutMs timeoutMs
}) })
@@ -71,11 +71,11 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
case 'unstar': case 'unstar':
const starred = updateType === 'star' const starred = updateType === 'star'
const updates: WAMessageUpdate[] = (node.content as BinaryNode[]).map( const updates: WAMessageUpdate[] = (node.content as BinaryNode[]).map(
({ attrs }) => ({ ({ attrs }) => ({
key: { key: {
remoteJid: jid, remoteJid: jid,
id: attrs.index, id: attrs.index,
fromMe: attrs.owner === 'true' fromMe: attrs.owner === 'true'
}, },
update: { starred } update: { starred }
}) })
@@ -93,13 +93,13 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
default: default:
logger.warn({ node }, 'received unrecognized chat update') logger.warn({ node }, 'received unrecognized chat update')
break break
} }
} }
const applyingPresenceUpdate = (update: BinaryNode['attrs']): BaileysEventMap<any>['presence.update'] => { const applyingPresenceUpdate = (update: BinaryNode['attrs']): BaileysEventMap<any>['presence.update'] => {
const id = jidNormalizedUser(update.id) const id = jidNormalizedUser(update.id)
const participant = jidNormalizedUser(update.participant || update.id) const participant = jidNormalizedUser(update.participant || update.id)
const presence: PresenceData = { const presence: PresenceData = {
lastSeen: update.t ? +update.t : undefined, lastSeen: update.t ? +update.t : undefined,
lastKnownPresence: update.type as WAPresence lastKnownPresence: update.type as WAPresence
@@ -110,21 +110,21 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
const chatRead = async(fromMessage: WAMessageKey, count: number) => { const chatRead = async(fromMessage: WAMessageKey, count: number) => {
await setQuery ( await setQuery (
[ [
{ {
tag: 'read', tag: 'read',
attrs: { attrs: {
jid: fromMessage.remoteJid, jid: fromMessage.remoteJid,
count: count.toString(), count: count.toString(),
index: fromMessage.id, index: fromMessage.id,
owner: fromMessage.fromMe ? 'true' : 'false' owner: fromMessage.fromMe ? 'true' : 'false'
} }
} }
], ],
[ WAMetric.read, WAFlag.ignore ] [ WAMetric.read, WAFlag.ignore ]
) )
if(config.emitOwnEvents) { if(config.emitOwnEvents) {
ev.emit('chats.update', [{ id: fromMessage.remoteJid, unreadCount: count < 0 ? -1 : 0 }]) ev.emit('chats.update', [{ id: fromMessage.remoteJid, unreadCount: count < 0 ? -1 : 0 }])
} }
} }
ev.on('connection.update', async({ connection }) => { ev.on('connection.update', async({ connection }) => {
@@ -143,7 +143,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
binaryTag: [ WAMetric.queryStatus, WAFlag.ignore ] binaryTag: [ WAMetric.queryStatus, WAFlag.ignore ]
}), }),
sendNode({ sendNode({
json: { tag: 'query', attrs: { type: 'quick_reply', epoch: '1' } }, json: { tag: 'query', attrs: { type: 'quick_reply', epoch: '1' } },
binaryTag: [ WAMetric.queryQuickReply, WAFlag.ignore ] binaryTag: [ WAMetric.queryQuickReply, WAFlag.ignore ]
}), }),
sendNode({ sendNode({
@@ -152,21 +152,21 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
}), }),
sendNode({ sendNode({
json: { tag: 'query', attrs: { type: 'emoji', epoch: '1' } }, json: { tag: 'query', attrs: { type: 'emoji', epoch: '1' } },
binaryTag: [ WAMetric.queryEmoji, WAFlag.ignore ] binaryTag: [ WAMetric.queryEmoji, WAFlag.ignore ]
}), }),
sendNode({ sendNode({
json: { json: {
tag: 'action', tag: 'action',
attrs: { type: 'set', epoch: '1' }, attrs: { type: 'set', epoch: '1' },
content: [ content: [
{ tag: 'presence', attrs: { type: 'available' } } { tag: 'presence', attrs: { type: 'available' } }
] ]
}, },
binaryTag: [ WAMetric.presence, WAFlag.available ] binaryTag: [ WAMetric.presence, WAFlag.available ]
}) })
]) ])
chatsDebounceTimeout.start() chatsDebounceTimeout.start()
logger.debug('sent init queries') logger.debug('sent init queries')
} catch(error) { } catch(error) {
logger.error(`error in sending init queries: ${error}`) logger.error(`error in sending init queries: ${error}`)
@@ -246,16 +246,16 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
ev.emit('chats.update', [update]) ev.emit('chats.update', [update])
} }
}) })
socketEvents.on('CB:Cmd,type:picture', async json => { socketEvents.on('CB:Cmd,type:picture', async json => {
json = json[1] json = json[1]
const id = jidNormalizedUser(json.jid) const id = jidNormalizedUser(json.jid)
const imgUrl = await profilePictureUrl(id).catch(() => '') const imgUrl = await profilePictureUrl(id).catch(() => '')
ev.emit('contacts.update', [ { id, imgUrl } ]) ev.emit('contacts.update', [ { id, imgUrl } ])
}) })
// chat archive, pin etc. // chat archive, pin etc.
socketEvents.on('CB:action,,chat', ({ content }: BinaryNode) => { socketEvents.on('CB:action,,chat', ({ content }: BinaryNode) => {
if(Array.isArray(content)) { if(Array.isArray(content)) {
@@ -269,7 +269,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
const user = json.content[0].attrs const user = json.content[0].attrs
if(user.id) { if(user.id) {
user.id = jidNormalizedUser(user.id) user.id = jidNormalizedUser(user.id)
//ev.emit('contacts.upsert', [user]) //ev.emit('contacts.upsert', [user])
} else { } else {
logger.warn({ json }, 'recv unknown action') logger.warn({ json }, 'recv unknown action')
@@ -303,7 +303,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
* Modify a given chat (archive, pin etc.) * Modify a given chat (archive, pin etc.)
* @param jid the ID of the person/group you are modifiying * @param jid the ID of the person/group you are modifiying
*/ */
chatModify: async(modification: ChatModification, jid: string, chatInfo: Pick<Chat, 'mute' | 'pin'>, timestampNow?: number) => { chatModify: async(modification: ChatModification, jid: string, chatInfo: Pick<Chat, 'mute' | 'pin'>, timestampNow?: number) => {
const chatAttrs: BinaryNode['attrs'] = { jid: jid } const chatAttrs: BinaryNode['attrs'] = { jid: jid }
let data: BinaryNode[] | undefined = undefined let data: BinaryNode[] | undefined = undefined
@@ -327,10 +327,10 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
} }
} else if('clear' in modification) { } else if('clear' in modification) {
chatAttrs.type = 'clear' chatAttrs.type = 'clear'
chatAttrs.modify_tag = Math.round(Math.random()*1000000).toString() chatAttrs.modify_tag = Math.round(Math.random() * 1000000).toString()
if(modification.clear !== 'all') { if(modification.clear !== 'all') {
data = modification.clear.messages.map(({ id, fromMe }) => ( data = modification.clear.messages.map(({ id, fromMe }) => (
{ {
tag: 'item', tag: 'item',
attrs: { owner: (!!fromMe).toString(), index: id } attrs: { owner: (!!fromMe).toString(), index: id }
} }
@@ -339,20 +339,20 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
} else if('star' in modification) { } else if('star' in modification) {
chatAttrs.type = modification.star.star ? 'star' : 'unstar' chatAttrs.type = modification.star.star ? 'star' : 'unstar'
data = modification.star.messages.map(({ id, fromMe }) => ( data = modification.star.messages.map(({ id, fromMe }) => (
{ {
tag: 'item', tag: 'item',
attrs: { owner: (!!fromMe).toString(), index: id } attrs: { owner: (!!fromMe).toString(), index: id }
} }
)) ))
} else if('markRead' in modification) { } else if('markRead' in modification) {
const indexKey = modification.lastMessages[modification.lastMessages.length-1].key const indexKey = modification.lastMessages[modification.lastMessages.length - 1].key
return chatRead(indexKey, modification.markRead ? 0 : -1) return chatRead(indexKey, modification.markRead ? 0 : -1)
} else if('delete' in modification) { } else if('delete' in modification) {
chatAttrs.type = 'delete' chatAttrs.type = 'delete'
} }
if('lastMessages' in modification) { if('lastMessages' in modification) {
const indexKey = modification.lastMessages[modification.lastMessages.length-1].key const indexKey = modification.lastMessages[modification.lastMessages.length - 1].key
if(indexKey) { if(indexKey) {
chatAttrs.index = indexKey.id chatAttrs.index = indexKey.id
chatAttrs.owner = indexKey.fromMe ? 'true' : 'false' chatAttrs.owner = indexKey.fromMe ? 'true' : 'false'
@@ -368,20 +368,20 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
return response return response
}, },
/** /**
* Query whether a given number is registered on WhatsApp * Query whether a given number is registered on WhatsApp
* @param str phone number/jid you want to check for * @param str phone number/jid you want to check for
* @returns undefined if the number doesn't exists, otherwise the correctly formatted jid * @returns undefined if the number doesn't exists, otherwise the correctly formatted jid
*/ */
onWhatsApp: async(str: string) => { onWhatsApp: async(str: string) => {
const { status, jid, biz } = await query({ const { status, jid, biz } = await query({
json: ['query', 'exist', str], json: ['query', 'exist', str],
requiresPhoneConnection: false requiresPhoneConnection: false
}) })
if(status === 200) { if(status === 200) {
return { return {
exists: true, exists: true,
jid: jidNormalizedUser(jid), jid: jidNormalizedUser(jid),
isBusiness: biz as boolean isBusiness: biz as boolean
} }
} }
@@ -394,20 +394,20 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
sendPresenceUpdate: (type: WAPresence, jid: string | undefined) => ( sendPresenceUpdate: (type: WAPresence, jid: string | undefined) => (
sendNode({ sendNode({
binaryTag: [WAMetric.presence, WAFlag[type]], // weird stuff WA does binaryTag: [WAMetric.presence, WAFlag[type]], // weird stuff WA does
json: { json: {
tag: 'action', tag: 'action',
attrs: { epoch: currentEpoch().toString(), type: 'set' }, attrs: { epoch: currentEpoch().toString(), type: 'set' },
content: [ content: [
{ {
tag: 'presence', tag: 'presence',
attrs: { type: type, to: jid } attrs: { type: type, to: jid }
} }
] ]
} }
}) })
), ),
/** /**
* Request updates on the presence of a user * Request updates on the presence of a user
* this returns nothing, you'll receive updates in chats.update event * this returns nothing, you'll receive updates in chats.update event
* */ * */
presenceSubscribe: async(jid: string) => ( presenceSubscribe: async(jid: string) => (
@@ -421,7 +421,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
setStatus: async(status: string) => { setStatus: async(status: string) => {
const response = await setQuery( const response = await setQuery(
[ [
{ {
tag: 'status', tag: 'status',
attrs: {}, attrs: {},
content: Buffer.from (status, 'utf-8') content: Buffer.from (status, 'utf-8')
@@ -444,7 +444,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
updateProfileName: async(name: string) => { updateProfileName: async(name: string) => {
const response = (await setQuery( const response = (await setQuery(
[ [
{ {
tag: 'profile', tag: 'profile',
attrs: { name } attrs: { name }
} }
@@ -463,14 +463,14 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
}, },
/** /**
* Update the profile picture * Update the profile picture
* @param jid * @param jid
* @param img * @param img
*/ */
async updateProfilePicture(jid: string, img: Buffer) { async updateProfilePicture(jid: string, img: Buffer) {
jid = jidNormalizedUser (jid) jid = jidNormalizedUser (jid)
const data = { img: Buffer.from([]), preview: Buffer.from([]) } //await generateProfilePicture(img) TODO const data = { img: Buffer.from([]), preview: Buffer.from([]) } //await generateProfilePicture(img) TODO
const tag = this.generateMessageTag () const tag = this.generateMessageTag ()
const query: BinaryNode = { const query: BinaryNode = {
tag: 'picture', tag: 'picture',
attrs: { jid: jid, id: tag, type: 'set' }, attrs: { jid: jid, id: tag, type: 'set' },
content: [ content: [
@@ -481,7 +481,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
const user = state.legacy?.user const user = state.legacy?.user
const { eurl } = await this.setQuery ([query], [WAMetric.picture, 136], tag) as { eurl: string, status: number } const { eurl } = await this.setQuery ([query], [WAMetric.picture, 136], tag) as { eurl: string, status: number }
if(config.emitOwnEvents) { if(config.emitOwnEvents) {
if(jid === user.id) { if(jid === user.id) {
user.imgUrl = eurl user.imgUrl = eurl
@@ -502,7 +502,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
* @param type type of operation * @param type type of operation
*/ */
blockUser: async(jid: string, type: 'add' | 'remove' = 'add') => { blockUser: async(jid: string, type: 'add' | 'remove' = 'add') => {
const json = { const json = {
tag: 'block', tag: 'block',
attrs: { type }, attrs: { type },
content: [ { tag: 'user', attrs: { jid } } ] content: [ { tag: 'user', attrs: { jid } } ]
@@ -522,12 +522,12 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
const { const {
profiles: [{ profiles: [{
profile, profile,
wid wid
}] }]
} = await query({ } = await query({
json: [ json: [
'query', 'businessProfile', 'query', 'businessProfile',
[ { 'wid': jid.replace('@s.whatsapp.net', '@c.us') } ], [ { 'wid': jid.replace('@s.whatsapp.net', '@c.us') } ],
84 84
], ],
expect200: true, expect200: true,

View File

@@ -6,8 +6,8 @@ import makeMessagesSocket from './messages'
const makeGroupsSocket = (config: LegacySocketConfig) => { const makeGroupsSocket = (config: LegacySocketConfig) => {
const { logger } = config const { logger } = config
const sock = makeMessagesSocket(config) const sock = makeMessagesSocket(config)
const { const {
ev, ev,
ws: socketEvents, ws: socketEvents,
query, query,
generateMessageTag, generateMessageTag,
@@ -29,10 +29,10 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
jid: jid, jid: jid,
subject: subject, subject: subject,
}, },
content: participants ? content: participants ?
participants.map(jid => ( participants.map(jid => (
{ tag: 'participant', attrs: { jid } } { tag: 'participant', attrs: { jid } }
)) : )) :
additionalNodes additionalNodes
} }
], [WAMetric.group, 136], tag) ], [WAMetric.group, 136], tag)
@@ -42,9 +42,9 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
/** Get the metadata of the group from WA */ /** Get the metadata of the group from WA */
const groupMetadataFull = async(jid: string) => { const groupMetadataFull = async(jid: string) => {
const metadata = await query({ const metadata = await query({
json: ['query', 'GroupMetadata', jid], json: ['query', 'GroupMetadata', jid],
expect200: true expect200: true
}) })
const meta: GroupMetadata = { const meta: GroupMetadata = {
id: metadata.id, id: metadata.id,
@@ -69,10 +69,10 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
const groupMetadataMinimal = async(jid: string) => { const groupMetadataMinimal = async(jid: string) => {
const { attrs, content }:BinaryNode = await query({ const { attrs, content }:BinaryNode = await query({
json: { json: {
tag: 'query', tag: 'query',
attrs: { type: 'group', jid: jid, epoch: currentEpoch().toString() } attrs: { type: 'group', jid: jid, epoch: currentEpoch().toString() }
}, },
binaryTag: [WAMetric.group, WAFlag.ignore], binaryTag: [WAMetric.group, WAFlag.ignore],
expect200: true expect200: true
}) })
const participants: GroupParticipant[] = [] const participants: GroupParticipant[] = []
@@ -102,7 +102,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
} }
return meta return meta
} }
socketEvents.on('CB:Chat,cmd:action', (json: BinaryNode) => { socketEvents.on('CB:Chat,cmd:action', (json: BinaryNode) => {
/*const data = json[1].data /*const data = json[1].data
if (data) { if (data) {
@@ -138,7 +138,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
result = await groupMetadataFull(jid) result = await groupMetadataFull(jid)
} }
return result return result
}, },
/** /**
* Create a group * Create a group
@@ -219,16 +219,16 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
return jids return jids
}, },
/** Query broadcast list info */ /** Query broadcast list info */
getBroadcastListInfo: async(jid: string) => { getBroadcastListInfo: async(jid: string) => {
interface WABroadcastListInfo { interface WABroadcastListInfo {
status: number status: number
name: string name: string
recipients?: {id: string}[] recipients?: {id: string}[]
} }
const result = await query({ const result = await query({
json: ['query', 'contact', jid], json: ['query', 'contact', jid],
expect200: true, expect200: true,
requiresPhoneConnection: true requiresPhoneConnection: true
}) as WABroadcastListInfo }) as WABroadcastListInfo
@@ -245,8 +245,8 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
}, },
groupInviteCode: async(jid: string) => { groupInviteCode: async(jid: string) => {
const response = await sock.query({ const response = await sock.query({
json: ['query', 'inviteCode', jid], json: ['query', 'inviteCode', jid],
expect200: true, expect200: true,
requiresPhoneConnection: false requiresPhoneConnection: false
}) })
return response.code as string return response.code as string

View File

@@ -15,8 +15,8 @@ const STATUS_MAP = {
const makeMessagesSocket = (config: LegacySocketConfig) => { const makeMessagesSocket = (config: LegacySocketConfig) => {
const { logger } = config const { logger } = config
const sock = makeChatsSocket(config) const sock = makeChatsSocket(config)
const { const {
ev, ev,
ws: socketEvents, ws: socketEvents,
query, query,
generateMessageTag, generateMessageTag,
@@ -28,10 +28,10 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
let mediaConn: Promise<MediaConnInfo> let mediaConn: Promise<MediaConnInfo>
const refreshMediaConn = async(forceGet = false) => { const refreshMediaConn = async(forceGet = false) => {
const media = await mediaConn const media = await mediaConn
if(!media || forceGet || (new Date().getTime()-media.fetchDate.getTime()) > media.ttl*1000) { if(!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) {
mediaConn = (async() => { mediaConn = (async() => {
const { media_conn } = await query({ const { media_conn } = await query({
json: ['query', 'mediaConn'], json: ['query', 'mediaConn'],
requiresPhoneConnection: false, requiresPhoneConnection: false,
expect200: true expect200: true
}) })
@@ -40,12 +40,12 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
})() })()
} }
return mediaConn return mediaConn
} }
const fetchMessagesFromWA = async( const fetchMessagesFromWA = async(
jid: string, jid: string,
count: number, count: number,
cursor?: WAMessageCursor cursor?: WAMessageCursor
) => { ) => {
let key: WAMessageKey let key: WAMessageKey
@@ -66,8 +66,8 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
owner: key?.fromMe === false ? 'false' : 'true', owner: key?.fromMe === false ? 'false' : 'true',
} }
}, },
binaryTag: [WAMetric.queryMessages, WAFlag.ignore], binaryTag: [WAMetric.queryMessages, WAFlag.ignore],
expect200: false, expect200: false,
requiresPhoneConnection: true requiresPhoneConnection: true
}) })
if(Array.isArray(content)) { if(Array.isArray(content)) {
@@ -78,27 +78,27 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
} }
const updateMediaMessage = async(message: WAMessage) => { const updateMediaMessage = async(message: WAMessage) => {
const content = message.message?.audioMessage || message.message?.videoMessage || message.message?.imageMessage || message.message?.stickerMessage || message.message?.documentMessage const content = message.message?.audioMessage || message.message?.videoMessage || message.message?.imageMessage || message.message?.stickerMessage || message.message?.documentMessage
if(!content) { if(!content) {
throw new Boom( throw new Boom(
`given message ${message.key.id} is not a media message`, `given message ${message.key.id} is not a media message`,
{ statusCode: 400, data: message } { statusCode: 400, data: message }
) )
} }
const response: BinaryNode = await query ({ const response: BinaryNode = await query ({
json: { json: {
tag: 'query', tag: 'query',
attrs: { attrs: {
type: 'media', type: 'media',
index: message.key.id, index: message.key.id,
owner: message.key.fromMe ? 'true' : 'false', owner: message.key.fromMe ? 'true' : 'false',
jid: message.key.remoteJid, jid: message.key.remoteJid,
epoch: currentEpoch().toString() epoch: currentEpoch().toString()
} }
}, },
binaryTag: [WAMetric.queryMedia, WAFlag.ignore], binaryTag: [WAMetric.queryMedia, WAFlag.ignore],
expect200: true, expect200: true,
requiresPhoneConnection: true requiresPhoneConnection: true
}) })
const attrs = response.attrs const attrs = response.attrs
@@ -112,14 +112,14 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
const onMessage = (message: WAMessage, type: MessageUpdateType) => { const onMessage = (message: WAMessage, type: MessageUpdateType) => {
const jid = message.key.remoteJid! const jid = message.key.remoteJid!
// store chat updates in this // store chat updates in this
const chatUpdate: Partial<Chat> = { const chatUpdate: Partial<Chat> = {
id: jid, id: jid,
} }
const emitGroupUpdate = (update: Partial<GroupMetadata>) => { const emitGroupUpdate = (update: Partial<GroupMetadata>) => {
ev.emit('groups.update', [ { id: jid, ...update } ]) ev.emit('groups.update', [ { id: jid, ...update } ])
} }
if(message.message) { if(message.message) {
chatUpdate.conversationTimestamp = +toNumber(message.messageTimestamp) chatUpdate.conversationTimestamp = +toNumber(message.messageTimestamp)
// add to count if the message isn't from me & there exists a message // add to count if the message isn't from me & there exists a message
@@ -128,7 +128,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
const participant = jidNormalizedUser(message.participant || jid) const participant = jidNormalizedUser(message.participant || jid)
ev.emit( ev.emit(
'presence.update', 'presence.update',
{ {
id: jid, id: jid,
presences: { [participant]: { lastKnownPresence: 'available' } } presences: { [participant]: { lastKnownPresence: 'available' } }
@@ -144,11 +144,11 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
case proto.ProtocolMessage.ProtocolMessageType.REVOKE: case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
const key = protocolMessage.key const key = protocolMessage.key
const messageStubType = WAMessageStubType.REVOKE const messageStubType = WAMessageStubType.REVOKE
ev.emit('messages.update', [ ev.emit('messages.update', [
{ {
// the key of the deleted message is updated // the key of the deleted message is updated
update: { message: null, key: message.key, messageStubType }, update: { message: null, key: message.key, messageStubType },
key key
} }
]) ])
return return
@@ -166,7 +166,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
} }
} }
// check if the message is an action // check if the message is an action
if(message.messageStubType) { if(message.messageStubType) {
const { user } = state.legacy! const { user } = state.legacy!
//let actor = jidNormalizedUser (message.participant) //let actor = jidNormalizedUser (message.participant)
@@ -234,14 +234,14 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
const response: BinaryNode = await query({ const response: BinaryNode = await query({
json: { json: {
tag: 'query', tag: 'query',
attrs: { attrs: {
type: 'url', type: 'url',
url: text, url: text,
epoch: currentEpoch().toString() epoch: currentEpoch().toString()
} }
}, },
binaryTag: [26, WAFlag.ignore], binaryTag: [26, WAFlag.ignore],
expect200: true, expect200: true,
requiresPhoneConnection: false requiresPhoneConnection: false
}) })
const urlInfo = { ...response.attrs } as any as WAUrlInfo const urlInfo = { ...response.attrs } as any as WAUrlInfo
@@ -258,9 +258,9 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
tag: 'action', tag: 'action',
attrs: { epoch: currentEpoch().toString(), type: 'relay' }, attrs: { epoch: currentEpoch().toString(), type: 'relay' },
content: [ content: [
{ {
tag: 'message', tag: 'message',
attrs: {}, attrs: {},
content: proto.WebMessageInfo.encode(message).finish() content: proto.WebMessageInfo.encode(message).finish()
} }
] ]
@@ -272,9 +272,9 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
message.status = WAMessageStatus.PENDING message.status = WAMessageStatus.PENDING
const promise = query({ const promise = query({
json, json,
binaryTag: [WAMetric.message, flag], binaryTag: [WAMetric.message, flag],
tag: mID, tag: mID,
expect200: true, expect200: true,
requiresPhoneConnection: true requiresPhoneConnection: true
}) })
@@ -308,7 +308,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
socketEvents.on('CB:action,add:last', json => messagesUpdate(json, true)) socketEvents.on('CB:action,add:last', json => messagesUpdate(json, true))
socketEvents.on('CB:action,add:unread', json => messagesUpdate(json, false)) socketEvents.on('CB:action,add:unread', json => messagesUpdate(json, false))
socketEvents.on('CB:action,add:before', json => messagesUpdate(json, false)) socketEvents.on('CB:action,add:before', json => messagesUpdate(json, false))
// new messages // new messages
socketEvents.on('CB:action,add:relay,message', (node: BinaryNode) => { socketEvents.on('CB:action,add:relay,message', (node: BinaryNode) => {
const msgs = getBinaryNodeMessages(node) const msgs = getBinaryNodeMessages(node)
@@ -316,7 +316,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
onMessage(msg, 'notify') onMessage(msg, 'notify')
} }
}) })
// If a message has been updated // If a message has been updated
// usually called when a video message gets its upload url, or live locations or ciphertext message gets fixed // usually called when a video message gets its upload url, or live locations or ciphertext message gets fixed
socketEvents.on ('CB:action,add:update,message', (node: BinaryNode) => { socketEvents.on ('CB:action,add:update,message', (node: BinaryNode) => {
const msgs = getBinaryNodeMessages(node) const msgs = getBinaryNodeMessages(node)
@@ -369,7 +369,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
return return
} }
const keyPartial = { const keyPartial = {
remoteJid: jidNormalizedUser(attributes.to), remoteJid: jidNormalizedUser(attributes.to),
fromMe: areJidsSameUser(attributes.from, state.legacy?.user?.id || ''), fromMe: areJidsSameUser(attributes.from, state.legacy?.user?.id || ''),
} }
@@ -413,13 +413,13 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
json: { json: {
tag: 'query', tag: 'query',
attrs: { attrs: {
type: 'message_info', type: 'message_info',
index: messageID, index: messageID,
jid: jid, jid: jid,
epoch: currentEpoch().toString() epoch: currentEpoch().toString()
} }
}, },
binaryTag: [WAMetric.queryRead, WAFlag.ignore], binaryTag: [WAMetric.queryRead, WAFlag.ignore],
expect200: true, expect200: true,
requiresPhoneConnection: true requiresPhoneConnection: true
}) })
@@ -427,7 +427,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
if(Array.isArray(content)) { if(Array.isArray(content)) {
for(const { tag, content: innerData } of content) { for(const { tag, content: innerData } of content) {
const [{ attrs }] = (innerData as BinaryNode[]) const [{ attrs }] = (innerData as BinaryNode[])
const jid = jidNormalizedUser(attrs.jid) const jid = jidNormalizedUser(attrs.jid)
const recp = info[jid] || { userJid: jid } const recp = info[jid] || { userJid: jid }
const date = +attrs.t const date = +attrs.t
@@ -465,14 +465,14 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
return stream return stream
} }
try { try {
const result = await downloadMediaMessage() const result = await downloadMediaMessage()
return result return result
} catch(error) { } catch(error) {
if(error.message.includes('404')) { // media needs to be updated if(error.message.includes('404')) { // media needs to be updated
logger.info (`updating media of message: ${message.key.id}`) logger.info (`updating media of message: ${message.key.id}`)
await updateMediaMessage(message) await updateMediaMessage(message)
const result = await downloadMediaMessage() const result = await downloadMediaMessage()
@@ -508,13 +508,13 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
page: page.toString(), page: page.toString(),
jid: inJid jid: inJid
} }
}, },
binaryTag: [24, WAFlag.ignore], binaryTag: [24, WAFlag.ignore],
expect200: true expect200: true
}) // encrypt and send off }) // encrypt and send off
return { return {
last: node.attrs?.last === 'true', last: node.attrs?.last === 'true',
messages: getBinaryNodeMessages(node) messages: getBinaryNodeMessages(node)
} }
}, },
@@ -531,7 +531,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
isJidGroup(jid) isJidGroup(jid)
) { ) {
const { disappearingMessagesInChat } = content const { disappearingMessagesInChat } = content
const value = typeof disappearingMessagesInChat === 'boolean' ? const value = typeof disappearingMessagesInChat === 'boolean' ?
(disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) : (disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
disappearingMessagesInChat disappearingMessagesInChat
const tag = generateMessageTag(true) const tag = generateMessageTag(true)
@@ -539,8 +539,8 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
{ {
tag: 'group', tag: 'group',
attrs: { id: tag, jid, type: 'prop', author: userJid }, attrs: { id: tag, jid, type: 'prop', author: userJid },
content: [ content: [
{ tag: 'ephemeral', attrs: { value: value.toString() } } { tag: 'ephemeral', attrs: { value: value.toString() } }
] ]
} }
]) ])
@@ -557,7 +557,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
...options, ...options,
} }
) )
await relayMessage(msg, { waitForAck: options.waitForAck }) await relayMessage(msg, { waitForAck: options.waitForAck })
return msg return msg
} }

View File

@@ -14,13 +14,13 @@ import { BinaryNode, encodeBinaryNodeLegacy } from '../WABinary'
* - query phone connection * - query phone connection
*/ */
export const makeSocket = ({ export const makeSocket = ({
waWebSocketUrl, waWebSocketUrl,
connectTimeoutMs, connectTimeoutMs,
phoneResponseTimeMs, phoneResponseTimeMs,
logger, logger,
agent, agent,
keepAliveIntervalMs, keepAliveIntervalMs,
expectResponseTimeout, expectResponseTimeout,
}: LegacySocketConfig) => { }: LegacySocketConfig) => {
// for generating tags // for generating tags
const referenceDateSeconds = unixTimestampSeconds(new Date()) const referenceDateSeconds = unixTimestampSeconds(new Date())
@@ -53,7 +53,7 @@ export const makeSocket = ({
const sendPromise = promisify(ws.send) const sendPromise = promisify(ws.send)
/** generate message tag and increment epoch */ /** generate message tag and increment epoch */
const generateMessageTag = (longTag: boolean = false) => { const generateMessageTag = (longTag: boolean = false) => {
const tag = `${longTag ? referenceDateSeconds : (referenceDateSeconds%1000)}.--${epoch}` const tag = `${longTag ? referenceDateSeconds : (referenceDateSeconds % 1000)}.--${epoch}`
epoch += 1 // increment message count, it makes the 'epoch' field when sending binary messages epoch += 1 // increment message count, it makes the 'epoch' field when sending binary messages
return tag return tag
} }
@@ -66,7 +66,7 @@ export const makeSocket = ({
return sendPromise.call(ws, data) as Promise<void> return sendPromise.call(ws, data) as Promise<void>
} }
/** /**
* Send a message to the WA servers * Send a message to the WA servers
* @returns the tag attached in the message * @returns the tag attached in the message
* */ * */
@@ -121,7 +121,7 @@ export const makeSocket = ({
if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) { if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
try { try {
ws.close() ws.close()
} catch{ } } catch{ }
} }
@@ -150,7 +150,7 @@ export const makeSocket = ({
return return
} }
//if (this.shouldLogMessages) this.messageLog.push ({ tag: messageTag, json: JSON.stringify(json), fromMe: false }) //if (this.shouldLogMessages) this.messageLog.push ({ tag: messageTag, json: JSON.stringify(json), fromMe: false })
if(logger.level === 'trace') { if(logger.level === 'trace') {
logger.trace({ tag: messageTag, fromMe: false, json }, 'communication') logger.trace({ tag: messageTag, fromMe: false, json }, 'communication')
} }
@@ -213,7 +213,7 @@ export const makeSocket = ({
phoneConnectionChanged(false) phoneConnectionChanged(false)
}, phoneResponseTimeMs) }, phoneResponseTimeMs)
} }
} }
const clearPhoneCheckInterval = () => { const clearPhoneCheckInterval = () => {
@@ -254,12 +254,12 @@ export const makeSocket = ({
} }
cancelToken = () => onErr(new Boom('Cancelled', { statusCode: 500 })) cancelToken = () => onErr(new Boom('Cancelled', { statusCode: 500 }))
if(requiresPhoneConnection) { if(requiresPhoneConnection) {
startPhoneCheckInterval() startPhoneCheckInterval()
cancelPhoneChecker = exitQueryIfResponseNotExpected(tag, onErr) cancelPhoneChecker = exitQueryIfResponseNotExpected(tag, onErr)
} }
ws.on(`TAG:${tag}`, onRecv) ws.on(`TAG:${tag}`, onRecv)
ws.on('ws-close', onErr) // if the socket closes, you'll never receive the message ws.on('ws-close', onErr) // if the socket closes, you'll never receive the message
}, },
@@ -268,13 +268,13 @@ export const makeSocket = ({
} finally { } finally {
requiresPhoneConnection && clearPhoneCheckInterval() requiresPhoneConnection && clearPhoneCheckInterval()
cancelPhoneChecker && cancelPhoneChecker() cancelPhoneChecker && cancelPhoneChecker()
ws.off(`TAG:${tag}`, onRecv) ws.off(`TAG:${tag}`, onRecv)
ws.off('ws-close', onErr) // if the socket closes, you'll never receive the message ws.off('ws-close', onErr) // if the socket closes, you'll never receive the message
} }
})(), })(),
cancelToken: () => { cancelToken: () => {
cancelToken() cancelToken()
} }
} }
} }
@@ -300,7 +300,7 @@ export const makeSocket = ({
// throw back the error // throw back the error
throw error throw error
} }
const response = await promise const response = await promise
const responseStatusCode = +(response.status ? response.status : 200) // default status const responseStatusCode = +(response.status ? response.status : 200) // default status
// read here: http://getstatuscode.com/599 // read here: http://getstatuscode.com/599
@@ -308,10 +308,10 @@ export const makeSocket = ({
end(new Boom('WA server overloaded', { statusCode: 599, data: { query: json, response } })) end(new Boom('WA server overloaded', { statusCode: 599, data: { query: json, response } }))
} }
if(expect200 && Math.floor(responseStatusCode/100) !== 2) { if(expect200 && Math.floor(responseStatusCode / 100) !== 2) {
const message = STATUS_CODES[responseStatusCode] || 'unknown' const message = STATUS_CODES[responseStatusCode] || 'unknown'
throw new Boom( throw new Boom(
`Unexpected status in '${Array.isArray(json) ? json[0] : (json?.tag || 'query')}': ${message}(${responseStatusCode})`, `Unexpected status in '${Array.isArray(json) ? json[0] : (json?.tag || 'query')}': ${message}(${responseStatusCode})`,
{ data: { query: json, response }, statusCode: response.status } { data: { query: json, response }, statusCode: response.status }
) )
} }
@@ -330,7 +330,7 @@ export const makeSocket = ({
check if it's been a suspicious amount of time since the server responded with our last seen 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 it could be that the network is down
*/ */
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.readyState === ws.OPEN) {
sendRawMessage('?,,') // if its all good, send a keep alive request sendRawMessage('?,,') // if its all good, send a keep alive request
@@ -394,7 +394,7 @@ export const makeSocket = ({
} }
end(new Boom( end(new Boom(
`Connection terminated by server: "${kind || 'unknown'}"`, `Connection terminated by server: "${kind || 'unknown'}"`,
{ statusCode: reason } { statusCode: reason }
)) ))
}) })
@@ -417,12 +417,12 @@ export const makeSocket = ({
content: nodes content: nodes
} }
return query({ return query({
json, json,
binaryTag, binaryTag,
tag, tag,
expect200: true, expect200: true,
requiresPhoneConnection: true requiresPhoneConnection: true
}) as Promise<{ status: number }> }) as Promise<{ status: number }>
}, },
currentEpoch: () => epoch, currentEpoch: () => epoch,

View File

@@ -11,7 +11,7 @@ const MAX_SYNC_ATTEMPTS = 5
export const makeChatsSocket = (config: SocketConfig) => { export const makeChatsSocket = (config: SocketConfig) => {
const { logger } = config const { logger } = config
const sock = makeMessagesSocket(config) const sock = makeMessagesSocket(config)
const { const {
ev, ev,
ws, ws,
authState, authState,
@@ -95,7 +95,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
const fetchStatus = async(jid: string) => { const fetchStatus = async(jid: string) => {
const [result] = await interactiveQuery( const [result] = await interactiveQuery(
[{ tag: 'user', attrs: { jid } }], [{ tag: 'user', attrs: { jid } }],
{ tag: 'status', attrs: { } } { tag: 'status', attrs: { } }
) )
if(result) { if(result) {
@@ -130,8 +130,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
const result = await query({ const result = await query({
tag: 'iq', tag: 'iq',
attrs: { attrs: {
xmlns: 'blocklist', xmlns: 'blocklist',
to: S_WHATSAPP_NET, to: S_WHATSAPP_NET,
type: 'get' type: 'get'
} }
}) })
@@ -223,7 +223,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
attrs: { attrs: {
type: 'account_sync', type: 'account_sync',
timestamp: fromTimestamp.toString(), timestamp: fromTimestamp.toString(),
} }
} }
] ]
}) })
@@ -238,7 +238,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
await authState.keys.transaction( await authState.keys.transaction(
async() => { async() => {
const collectionsToHandle = new Set<string>(collections) const collectionsToHandle = new Set<string>(collections)
// in case something goes wrong -- ensure we don't enter a loop that cannot be exited from // in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
const attemptsMap = { } as { [T in WAPatchName]: number | undefined } const attemptsMap = { } as { [T in WAPatchName]: number | undefined }
// keep executing till all collections are done // keep executing till all collections are done
// sometimes a single patch request will not return all the patches (God knows why) // sometimes a single patch request will not return all the patches (God knows why)
@@ -265,9 +265,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
nodes.push({ nodes.push({
tag: 'collection', tag: 'collection',
attrs: { attrs: {
name, name,
version: state.version.toString(), version: state.version.toString(),
// return snapshot if being synced from scratch // return snapshot if being synced from scratch
return_snapshot: (!state.version).toString() return_snapshot: (!state.version).toString()
} }
@@ -289,7 +289,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
} }
] ]
}) })
const decoded = await extractSyncdPatches(result) // extract from binary node const decoded = await extractSyncdPatches(result) // extract from binary node
for(const key in decoded) { for(const key in decoded) {
const name = key as WAPatchName const name = key as WAPatchName
@@ -298,7 +298,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
if(snapshot) { if(snapshot) {
const { state: newState, mutations } = await decodeSyncdSnapshot(name, snapshot, getAppStateSyncKey, initialVersionMap[name]) const { state: newState, mutations } = await decodeSyncdSnapshot(name, snapshot, getAppStateSyncKey, initialVersionMap[name])
states[name] = newState states[name] = newState
logger.info(`restored state of ${name} from snapshot to v${newState.version} with ${mutations.length} mutations`) logger.info(`restored state of ${name} from snapshot to v${newState.version} with ${mutations.length} mutations`)
await authState.keys.set({ 'app-state-sync-version': { [name]: newState } }) await authState.keys.set({ 'app-state-sync-version': { [name]: newState } })
@@ -309,14 +309,14 @@ export const makeChatsSocket = (config: SocketConfig) => {
// only process if there are syncd patches // only process if there are syncd patches
if(patches.length) { if(patches.length) {
const { newMutations, state: newState } = await decodePatches(name, patches, states[name], getAppStateSyncKey, initialVersionMap[name]) const { newMutations, state: newState } = await decodePatches(name, patches, states[name], getAppStateSyncKey, initialVersionMap[name])
await authState.keys.set({ 'app-state-sync-version': { [name]: newState } }) await authState.keys.set({ 'app-state-sync-version': { [name]: newState } })
logger.info(`synced ${name} to v${newState.version}`) logger.info(`synced ${name} to v${newState.version}`)
if(newMutations.length) { if(newMutations.length) {
logger.trace({ newMutations, name }, 'recv new mutations') logger.trace({ newMutations, name }, 'recv new mutations')
} }
appStateChunk.totalMutations.push(...newMutations) appStateChunk.totalMutations.push(...newMutations)
} }
@@ -431,17 +431,17 @@ export const makeChatsSocket = (config: SocketConfig) => {
} }
const resyncMainAppState = async() => { const resyncMainAppState = async() => {
logger.debug('resyncing main app state') logger.debug('resyncing main app state')
await ( await (
mutationMutex.mutex( mutationMutex.mutex(
() => resyncAppState([ () => resyncAppState([
'critical_block', 'critical_block',
'critical_unblock_low', 'critical_unblock_low',
'regular_high', 'regular_high',
'regular_low', 'regular_low',
'regular' 'regular'
]) ])
) )
.catch(err => ( .catch(err => (
@@ -458,7 +458,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
for(const { syncAction: { value: action }, index: [_, id, msgId, fromMe] } of actions) { for(const { syncAction: { value: action }, index: [_, id, msgId, fromMe] } of actions) {
const update: Partial<Chat> = { id } const update: Partial<Chat> = { id }
if(action?.muteAction) { if(action?.muteAction) {
update.mute = action.muteAction?.muted ? update.mute = action.muteAction?.muted ?
toNumber(action.muteAction!.muteEndTimestamp!) : toNumber(action.muteAction!.muteEndTimestamp!) :
undefined undefined
} else if(action?.archiveChatAction) { } else if(action?.archiveChatAction) {
@@ -546,7 +546,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
tag: 'collection', tag: 'collection',
attrs: { attrs: {
name, name,
version: (state.version-1).toString(), version: (state.version - 1).toString(),
return_snapshot: 'false' return_snapshot: 'false'
}, },
content: [ content: [
@@ -562,9 +562,9 @@ export const makeChatsSocket = (config: SocketConfig) => {
] ]
} }
await query(node) await query(node)
await authState.keys.set({ 'app-state-sync-version': { [name]: state } }) await authState.keys.set({ 'app-state-sync-version': { [name]: state } })
if(config.emitOwnEvents) { if(config.emitOwnEvents) {
const result = await decodePatches(name, [{ ...patch, version: { version: state.version }, }], initial, getAppStateSyncKey) const result = await decodePatches(name, [{ ...patch, version: { version: state.version }, }], initial, getAppStateSyncKey)
processSyncActions(result.newMutations) processSyncActions(result.newMutations)
@@ -589,7 +589,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
}) })
const propsNode = getBinaryNodeChild(abtNode, 'props') const propsNode = getBinaryNodeChild(abtNode, 'props')
let props: { [_: string]: string } = { } let props: { [_: string]: string } = { }
if(propsNode) { if(propsNode) {
props = reduceBinaryNodeToDictionary(propsNode, 'prop') props = reduceBinaryNodeToDictionary(propsNode, 'prop')
@@ -616,7 +616,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
}) })
const propsNode = getBinaryNodeChild(resultNode, 'props') const propsNode = getBinaryNodeChild(resultNode, 'props')
let props: { [_: string]: string } = { } let props: { [_: string]: string } = { }
if(propsNode) { if(propsNode) {
props = reduceBinaryNodeToDictionary(propsNode, 'prop') props = reduceBinaryNodeToDictionary(propsNode, 'prop')
@@ -651,7 +651,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
} }
lastAccountSyncTimestamp = +attrs.timestamp lastAccountSyncTimestamp = +attrs.timestamp
ev.emit('creds.update', { lastAccountSyncTimestamp }) ev.emit('creds.update', { lastAccountSyncTimestamp })
break break
default: default:
logger.info({ node }, 'received unknown sync') logger.info({ node }, 'received unknown sync')
@@ -683,7 +683,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
}) })
return { return {
...sock, ...sock,
appPatch, appPatch,
sendPresenceUpdate, sendPresenceUpdate,
presenceSubscribe, presenceSubscribe,

View File

@@ -21,15 +21,15 @@ export const makeGroupsSocket = (config: SocketConfig) => {
const groupMetadata = async(jid: string) => { const groupMetadata = async(jid: string) => {
const result = await groupQuery( const result = await groupQuery(
jid, jid,
'get', 'get',
[ { tag: 'query', attrs: { request: 'interactive' } } ] [ { tag: 'query', attrs: { request: 'interactive' } } ]
) )
return extractGroupMetadata(result) return extractGroupMetadata(result)
} }
return { return {
...sock, ...sock,
groupMetadata, groupMetadata,
groupCreate: async(subject: string, participants: string[]) => { groupCreate: async(subject: string, participants: string[]) => {
const key = generateMessageID() const key = generateMessageID()
@@ -86,7 +86,7 @@ export const makeGroupsSocket = (config: SocketConfig) => {
action: ParticipantAction action: ParticipantAction
) => { ) => {
const result = await groupQuery( const result = await groupQuery(
jid, jid,
'set', 'set',
participants.map( participants.map(
jid => ({ jid => ({
@@ -135,7 +135,7 @@ export const makeGroupsSocket = (config: SocketConfig) => {
return result.attrs.jid return result.attrs.jid
}, },
groupToggleEphemeral: async(jid: string, ephemeralExpiration: number) => { groupToggleEphemeral: async(jid: string, ephemeralExpiration: number) => {
const content: BinaryNode = ephemeralExpiration ? const content: BinaryNode = ephemeralExpiration ?
{ tag: 'ephemeral', attrs: { ephemeral: ephemeralExpiration.toString() } } : { tag: 'ephemeral', attrs: { ephemeral: ephemeralExpiration.toString() } } :
{ tag: 'not_ephemeral', attrs: { } } { tag: 'not_ephemeral', attrs: { } }
await groupQuery(jid, 'set', [content]) await groupQuery(jid, 'set', [content])

View File

@@ -19,14 +19,14 @@ const getStatusFromReceiptType = (type: string | undefined) => {
if(typeof type === 'undefined') { if(typeof type === 'undefined') {
return proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK return proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK
} }
return status return status
} }
export const makeMessagesRecvSocket = (config: SocketConfig) => { export const makeMessagesRecvSocket = (config: SocketConfig) => {
const { logger } = config const { logger } = config
const sock = makeChatsSocket(config) const sock = makeChatsSocket(config)
const { const {
ev, ev,
authState, authState,
ws, ws,
@@ -74,11 +74,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
return return
} }
msgRetryMap[msgId] = retryCount+1 msgRetryMap[msgId] = retryCount + 1
const isGroup = !!node.attrs.participant const isGroup = !!node.attrs.participant
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account).finish() const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account).finish()
await assertingPreKeys(1, async preKeys => { await assertingPreKeys(1, async preKeys => {
const [keyId] = Object.keys(preKeys) const [keyId] = Object.keys(preKeys)
@@ -93,14 +93,14 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
to: isGroup ? node.attrs.from : jidEncode(decFrom!.user, 's.whatsapp.net', decFrom!.device, 0) to: isGroup ? node.attrs.from : jidEncode(decFrom!.user, 's.whatsapp.net', decFrom!.device, 0)
}, },
content: [ content: [
{ {
tag: 'retry', tag: 'retry',
attrs: { attrs: {
count: retryCount.toString(), count: retryCount.toString(),
id: node.attrs.id, id: node.attrs.id,
t: node.attrs.t, t: node.attrs.t,
v: '1' v: '1'
} }
}, },
{ {
tag: 'registration', tag: 'registration',
@@ -145,10 +145,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
switch (protocolMsg.type) { switch (protocolMsg.type) {
case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION: case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION:
const histNotification = protocolMsg!.historySyncNotification const histNotification = protocolMsg!.historySyncNotification
logger.info({ histNotification, id: message.key.id }, 'got history notification') logger.info({ histNotification, id: message.key.id }, 'got history notification')
const { chats, contacts, messages, isLatest } = await downloadAndProcessHistorySyncNotification(histNotification, historyCache) const { chats, contacts, messages, isLatest } = await downloadAndProcessHistorySyncNotification(histNotification, historyCache)
const meJid = authState.creds.me!.id const meJid = authState.creds.me!.id
await sendNode({ await sendNode({
tag: 'receipt', tag: 'receipt',
@@ -178,15 +178,15 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
let newAppStateSyncKeyId = '' let newAppStateSyncKeyId = ''
for(const { keyData, keyId } of keys) { for(const { keyData, keyId } of keys) {
const strKeyId = Buffer.from(keyId.keyId!).toString('base64') const strKeyId = Buffer.from(keyId.keyId!).toString('base64')
logger.info({ strKeyId }, 'injecting new app state sync key') logger.info({ strKeyId }, 'injecting new app state sync key')
await authState.keys.set({ 'app-state-sync-key': { [strKeyId]: keyData } }) await authState.keys.set({ 'app-state-sync-key': { [strKeyId]: keyData } })
newAppStateSyncKeyId = strKeyId newAppStateSyncKeyId = strKeyId
} }
ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId }) ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId })
resyncMainAppState() resyncMainAppState()
} else { } else {
[ [
@@ -197,12 +197,12 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
break break
case proto.ProtocolMessage.ProtocolMessageType.REVOKE: case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
ev.emit('messages.update', [ ev.emit('messages.update', [
{ {
key: { key: {
...message.key, ...message.key,
id: protocolMsg.key!.id id: protocolMsg.key!.id
}, },
update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key } update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key }
} }
]) ])
break break
@@ -299,7 +299,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid) const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
if( if(
participants.length === 1 && participants.length === 1 &&
// if recv. "remove" message and sender removed themselves // if recv. "remove" message and sender removed themselves
// mark as left // mark as left
areJidsSameUser(participants[0], node.attrs.participant) && areJidsSameUser(participants[0], node.attrs.participant) &&
@@ -324,7 +324,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
result.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT result.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
result.messageStubParameters = [ (child.tag === 'locked') ? 'on' : 'off' ] result.messageStubParameters = [ (child.tag === 'locked') ? 'on' : 'off' ]
break break
} }
} else { } else {
switch (child.tag) { switch (child.tag) {
@@ -354,7 +354,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
// message failed to decrypt // message failed to decrypt
if(msg.messageStubType === proto.WebMessageInfo.WebMessageInfoStubType.CIPHERTEXT) { if(msg.messageStubType === proto.WebMessageInfo.WebMessageInfoStubType.CIPHERTEXT) {
logger.error( logger.error(
{ msgId: msg.key.id, params: msg.messageStubParameters }, { msgId: msg.key.id, params: msg.messageStubParameters },
'failure in decrypting message' 'failure in decrypting message'
) )
retryMutex.mutex( retryMutex.mutex(
@@ -366,7 +366,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
await sendReceipt(msg.key.remoteJid!, msg.key.participant, [msg.key.id!], undefined) await sendReceipt(msg.key.remoteJid!, msg.key.participant, [msg.key.id!], undefined)
logger.debug({ msg: msg.key }, 'sent delivery receipt') logger.debug({ msg: msg.key }, 'sent delivery receipt')
} }
msg.key.remoteJid = jidNormalizedUser(msg.key.remoteJid!) msg.key.remoteJid = jidNormalizedUser(msg.key.remoteJid!)
ev.emit('messages.upsert', { messages: [msg], type: stanza.attrs.offline ? 'append' : 'notify' }) ev.emit('messages.upsert', { messages: [msg], type: stanza.attrs.offline ? 'append' : 'notify' })
} }
@@ -470,7 +470,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
) )
} else { } else {
ev.emit( ev.emit(
'messages.update', 'messages.update',
ids.map(id => ({ ids.map(id => ({
key: { ...key, id }, key: { ...key, id },
update: { status } update: { status }
@@ -521,7 +521,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
...(msg.key || {}) ...(msg.key || {})
} }
msg.messageTimestamp = +node.attrs.t msg.messageTimestamp = +node.attrs.t
const fullMsg = proto.WebMessageInfo.fromObject(msg) const fullMsg = proto.WebMessageInfo.fromObject(msg)
ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' }) ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' })
} }
@@ -549,7 +549,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
'p-' + chat.id!, 'p-' + chat.id!,
() => processMessage(msg, chat) () => processMessage(msg, chat)
) )
if(!!msg.message && !msg.message!.protocolMessage) { if(!!msg.message && !msg.message!.protocolMessage) {
chat.conversationTimestamp = toNumber(msg.messageTimestamp) chat.conversationTimestamp = toNumber(msg.messageTimestamp)
if(!msg.key.fromMe) { if(!msg.key.fromMe) {

View File

@@ -10,7 +10,7 @@ import { makeGroupsSocket } from './groups'
export const makeMessagesSocket = (config: SocketConfig) => { export const makeMessagesSocket = (config: SocketConfig) => {
const { logger } = config const { logger } = config
const sock = makeGroupsSocket(config) const sock = makeGroupsSocket(config)
const { const {
ev, ev,
authState, authState,
query, query,
@@ -31,8 +31,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const { content } = await query({ const { content } = await query({
tag: 'iq', tag: 'iq',
attrs: { attrs: {
xmlns: 'privacy', xmlns: 'privacy',
to: S_WHATSAPP_NET, to: S_WHATSAPP_NET,
type: 'get' type: 'get'
}, },
content: [ content: [
@@ -48,7 +48,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
let mediaConn: Promise<MediaConnInfo> let mediaConn: Promise<MediaConnInfo>
const refreshMediaConn = async(forceGet = false) => { const refreshMediaConn = async(forceGet = false) => {
const media = await mediaConn const media = await mediaConn
if(!media || forceGet || (new Date().getTime()-media.fetchDate.getTime()) > media.ttl*1000) { if(!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) {
mediaConn = (async() => { mediaConn = (async() => {
const result = await query({ const result = await query({
tag: 'iq', tag: 'iq',
@@ -77,7 +77,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
} }
/** /**
* generic send receipt function * generic send receipt function
* used for receipts of phone call, read, delivery etc. * used for receipts of phone call, read, delivery etc.
* */ * */
const sendReceipt = async(jid: string, participant: string | undefined, messageIds: string[], type: 'read' | 'read-self' | undefined) => { const sendReceipt = async(jid: string, participant: string | undefined, messageIds: string[], type: 'read' | 'read-self' | undefined) => {
@@ -124,7 +124,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const getUSyncDevices = async(jids: string[], ignoreZeroDevices: boolean) => { const getUSyncDevices = async(jids: string[], ignoreZeroDevices: boolean) => {
const deviceResults: JidWithDevice[] = [] const deviceResults: JidWithDevice[] = []
const users: BinaryNode[] = [] const users: BinaryNode[] = []
jids = Array.from(new Set(jids)) jids = Array.from(new Set(jids))
for(let jid of jids) { for(let jid of jids) {
@@ -162,8 +162,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
tag: 'query', tag: 'query',
attrs: { }, attrs: { },
content: [ content: [
{ {
tag: 'devices', tag: 'devices',
attrs: { version: '2' } attrs: { version: '2' }
} }
] ]
@@ -194,7 +194,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const assertSessions = async(jids: string[], force: boolean) => { const assertSessions = async(jids: string[], force: boolean) => {
let jidsRequiringFetch: string[] = [] let jidsRequiringFetch: string[] = []
if(force) { if(force) {
jidsRequiringFetch = jids jidsRequiringFetch = jids
} else { } else {
const addrs = jids.map(jid => jidToSignalProtocolAddress(jid).toString()) const addrs = jids.map(jid => jidToSignalProtocolAddress(jid).toString())
const sessions = await authState.keys.get('session', addrs) const sessions = await authState.keys.get('session', addrs)
@@ -240,11 +240,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
if(authState.keys.isInTransaction()) { if(authState.keys.isInTransaction()) {
await authState.keys.prefetch( await authState.keys.prefetch(
'session', 'session',
jids.map(jid => jidToSignalProtocolAddress(jid).toString()) jids.map(jid => jidToSignalProtocolAddress(jid).toString())
) )
} }
const nodes = await Promise.all( const nodes = await Promise.all(
jids.map( jids.map(
async jid => { async jid => {
@@ -266,8 +266,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
} }
const relayMessage = async( const relayMessage = async(
jid: string, jid: string,
message: proto.IMessage, message: proto.IMessage,
{ messageId: msgId, participant, additionalAttributes, cachedGroupMetadata }: MessageRelayOptions { messageId: msgId, participant, additionalAttributes, cachedGroupMetadata }: MessageRelayOptions
) => { ) => {
const meId = authState.creds.me!.id const meId = authState.creds.me!.id
@@ -293,10 +293,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
async() => { async() => {
if(isGroup) { if(isGroup) {
const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, meId, authState) const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, meId, authState)
const [groupData, senderKeyMap] = await Promise.all([ const [groupData, senderKeyMap] = await Promise.all([
(async() => { (async() => {
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
if(!groupData) { if(!groupData) {
groupData = await groupMetadata(jid) groupData = await groupMetadata(jid)
} }
@@ -308,13 +308,13 @@ export const makeMessagesSocket = (config: SocketConfig) => {
return result[jid] || { } return result[jid] || { }
})() })()
]) ])
if(!participant) { if(!participant) {
const participantsList = groupData.participants.map(p => p.id) const participantsList = groupData.participants.map(p => p.id)
const additionalDevices = await getUSyncDevices(participantsList, false) const additionalDevices = await getUSyncDevices(participantsList, false)
devices.push(...additionalDevices) devices.push(...additionalDevices)
} }
const senderKeyJids: string[] = [] const senderKeyJids: string[] = []
// ensure a connection is established with every device // ensure a connection is established with every device
for(const { user, device } of devices) { for(const { user, device } of devices) {
@@ -330,44 +330,44 @@ export const makeMessagesSocket = (config: SocketConfig) => {
// if there are, we re-send the senderkey // if there are, we re-send the senderkey
if(senderKeyJids.length) { if(senderKeyJids.length) {
logger.debug({ senderKeyJids }, 'sending new sender key') logger.debug({ senderKeyJids }, 'sending new sender key')
const encSenderKeyMsg = encodeWAMessage({ const encSenderKeyMsg = encodeWAMessage({
senderKeyDistributionMessage: { senderKeyDistributionMessage: {
axolotlSenderKeyDistributionMessage: senderKeyDistributionMessageKey, axolotlSenderKeyDistributionMessage: senderKeyDistributionMessageKey,
groupId: destinationJid groupId: destinationJid
} }
}) })
participants.push( participants.push(
...(await createParticipantNodes(senderKeyJids, encSenderKeyMsg)) ...(await createParticipantNodes(senderKeyJids, encSenderKeyMsg))
) )
} }
binaryNodeContent.push({ binaryNodeContent.push({
tag: 'enc', tag: 'enc',
attrs: { v: '2', type: 'skmsg' }, attrs: { v: '2', type: 'skmsg' },
content: ciphertext content: ciphertext
}) })
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } }) await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
} else { } else {
const { user: meUser } = jidDecode(meId) const { user: meUser } = jidDecode(meId)
const encodedMeMsg = encodeWAMessage({ const encodedMeMsg = encodeWAMessage({
deviceSentMessage: { deviceSentMessage: {
destinationJid, destinationJid,
message message
} }
}) })
if(!participant) { if(!participant) {
devices.push({ user }) devices.push({ user })
devices.push({ user: meUser }) devices.push({ user: meUser })
const additionalDevices = await getUSyncDevices([ meId, jid ], true) const additionalDevices = await getUSyncDevices([ meId, jid ], true)
devices.push(...additionalDevices) devices.push(...additionalDevices)
} }
const meJids: string[] = [] const meJids: string[] = []
const otherJids: string[] = [] const otherJids: string[] = []
for(const { user, device } of devices) { for(const { user, device } of devices) {
@@ -379,7 +379,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
otherJids.push(jid) otherJids.push(jid)
} }
} }
const [meNodes, otherNodes] = await Promise.all([ const [meNodes, otherNodes] = await Promise.all([
createParticipantNodes(meJids, encodedMeMsg), createParticipantNodes(meJids, encodedMeMsg),
createParticipantNodes(otherJids, encodedMsg) createParticipantNodes(otherJids, encodedMsg)
@@ -387,7 +387,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
participants.push(...meNodes) participants.push(...meNodes)
participants.push(...otherNodes) participants.push(...otherNodes)
} }
if(participants.length) { if(participants.length) {
binaryNodeContent.push({ binaryNodeContent.push({
tag: 'participants', tag: 'participants',
@@ -395,7 +395,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
content: participants content: participants
}) })
} }
const stanza: BinaryNode = { const stanza: BinaryNode = {
tag: 'message', tag: 'message',
attrs: { attrs: {
@@ -406,32 +406,32 @@ export const makeMessagesSocket = (config: SocketConfig) => {
}, },
content: binaryNodeContent content: binaryNodeContent
} }
const shouldHaveIdentity = !!participants.find( const shouldHaveIdentity = !!participants.find(
participant => (participant.content! as BinaryNode[]).find(n => n.attrs.type === 'pkmsg') participant => (participant.content! as BinaryNode[]).find(n => n.attrs.type === 'pkmsg')
) )
if(shouldHaveIdentity) { if(shouldHaveIdentity) {
(stanza.content as BinaryNode[]).push({ (stanza.content as BinaryNode[]).push({
tag: 'device-identity', tag: 'device-identity',
attrs: { }, attrs: { },
content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish() content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish()
}) })
logger.debug({ jid }, 'adding device identity') logger.debug({ jid }, 'adding device identity')
} }
logger.debug({ msgId }, `sending message to ${participants.length} devices`) logger.debug({ msgId }, `sending message to ${participants.length} devices`)
await sendNode(stanza) await sendNode(stanza)
} }
) )
return msgId return msgId
} }
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn) const waUploadToServer = getWAUploadToServer(config, refreshMediaConn)
return { return {
...sock, ...sock,
assertSessions, assertSessions,
@@ -454,7 +454,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
isJidGroup(jid) isJidGroup(jid)
) { ) {
const { disappearingMessagesInChat } = content const { disappearingMessagesInChat } = content
const value = typeof disappearingMessagesInChat === 'boolean' ? const value = typeof disappearingMessagesInChat === 'boolean' ?
(disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) : (disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
disappearingMessagesInChat disappearingMessagesInChat
await groupToggleEphemeral(jid, value) await groupToggleEphemeral(jid, value)

View File

@@ -16,10 +16,10 @@ import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, getBinaryNodeChild,
* - query phone connection * - query phone connection
*/ */
export const makeSocket = ({ export const makeSocket = ({
waWebSocketUrl, waWebSocketUrl,
connectTimeoutMs, connectTimeoutMs,
logger, logger,
agent, agent,
keepAliveIntervalMs, keepAliveIntervalMs,
version, version,
browser, browser,
@@ -58,7 +58,7 @@ export const makeSocket = ({
} }
const { creds } = authState const { creds } = authState
let lastDateRecv: Date let lastDateRecv: Date
let epoch = 0 let epoch = 0
let keepAliveReq: NodeJS.Timeout let keepAliveReq: NodeJS.Timeout
@@ -129,7 +129,7 @@ export const makeSocket = ({
onErr = err => { onErr = err => {
reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })) reject(err || new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed }))
} }
ws.on(`TAG:${msgId}`, onRecv) ws.on(`TAG:${msgId}`, onRecv)
ws.on('close', onErr) // if the socket closes, you'll never receive the message ws.on('close', onErr) // if the socket closes, you'll never receive the message
ws.off('error', onErr) ws.off('error', onErr)
@@ -203,10 +203,10 @@ export const makeSocket = ({
/** get some pre-keys and do something with them */ /** get some pre-keys and do something with them */
const assertingPreKeys = async(range: number, execute: (keys: { [_: number]: any }) => Promise<void>) => { const assertingPreKeys = async(range: number, execute: (keys: { [_: number]: any }) => Promise<void>) => {
const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(authState.creds, range) const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(authState.creds, range)
const update: Partial<AuthenticationCreds> = { const update: Partial<AuthenticationCreds> = {
nextPreKeyId: Math.max(lastPreKeyId+1, creds.nextPreKeyId), nextPreKeyId: Math.max(lastPreKeyId + 1, creds.nextPreKeyId),
firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId+1) firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId + 1)
} }
if(!creds.serverHasPreKeys) { if(!creds.serverHasPreKeys) {
update.serverHasPreKeys = true update.serverHasPreKeys = true
@@ -255,7 +255,7 @@ export const makeSocket = ({
if(logger.level === 'trace') { if(logger.level === 'trace') {
logger.trace({ msgId, fromMe: false, frame }, 'communication') logger.trace({ msgId, fromMe: false, frame }, 'communication')
} }
let anyTriggered = false let anyTriggered = false
/* Check if this is a response to a message we sent */ /* Check if this is a response to a message we sent */
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${msgId}`, frame) anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${msgId}`, frame)
@@ -263,7 +263,7 @@ export const makeSocket = ({
const l0 = frame.tag const l0 = frame.tag
const l1 = frame.attrs || { } const l1 = frame.attrs || { }
const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : '' const l2 = Array.isArray(frame.content) ? frame.content[0]?.tag : ''
Object.keys(l1).forEach(key => { Object.keys(l1).forEach(key => {
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, frame) || anyTriggered
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, frame) || anyTriggered
@@ -272,7 +272,7 @@ export const makeSocket = ({
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},,${l2}`, frame) || anyTriggered
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0}`, frame) || anyTriggered
anyTriggered = ws.emit('frame', frame) || anyTriggered anyTriggered = ws.emit('frame', frame) || anyTriggered
if(!anyTriggered && logger.level === 'debug') { if(!anyTriggered && logger.level === 'debug') {
logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'communication recv') logger.debug({ unhandled: true, msgId, fromMe: false, frame }, 'communication recv')
} }
@@ -293,16 +293,16 @@ export const makeSocket = ({
if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) { if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
try { try {
ws.close() ws.close()
} catch{ } } catch{ }
} }
ev.emit('connection.update', { ev.emit('connection.update', {
connection: 'close', connection: 'close',
lastDisconnect: { lastDisconnect: {
error, error,
date: new Date() date: new Date()
} }
}) })
ev.removeAllListeners('connection.update') ev.removeAllListeners('connection.update')
} }
@@ -343,7 +343,7 @@ export const makeSocket = ({
check if it's been a suspicious amount of time since the server responded with our last seen 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 it could be that the network is down
*/ */
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.readyState === ws.OPEN) {
// if its all good, send a keep alive request // if its all good, send a keep alive request
@@ -422,8 +422,8 @@ export const makeSocket = ({
}) })
// QR gen // QR gen
ws.on('CB:iq,type:set,pair-device', async(stanza: BinaryNode) => { ws.on('CB:iq,type:set,pair-device', async(stanza: BinaryNode) => {
const iq: BinaryNode = { const iq: BinaryNode = {
tag: 'iq', tag: 'iq',
attrs: { attrs: {
to: S_WHATSAPP_NET, to: S_WHATSAPP_NET,
type: 'result', type: 'result',
@@ -446,7 +446,7 @@ export const makeSocket = ({
} }
const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',') const qr = [ref, noiseKeyB64, identityKeyB64, advB64].join(',')
ev.emit('connection.update', { qr }) ev.emit('connection.update', { qr })
qrTimer = setTimeout(genPairQR, qrMs) qrTimer = setTimeout(genPairQR, qrMs)
@@ -498,10 +498,10 @@ export const makeSocket = ({
ev.emit('connection.update', { connection: 'open' }) ev.emit('connection.update', { connection: 'open' })
}) })
ws.on('CB:ib,,offline', (node: BinaryNode) => { ws.on('CB:ib,,offline', (node: BinaryNode) => {
const child = getBinaryNodeChild(node, 'offline') const child = getBinaryNodeChild(node, 'offline')
const offlineCount = +child.attrs.count const offlineCount = +child.attrs.count
logger.info(`got ${offlineCount} offline messages/notifications`) logger.info(`got ${offlineCount} offline messages/notifications`)

View File

@@ -14,7 +14,7 @@ type LegacyWASocket = ReturnType<typeof makeLegacySocket>
type AnyWASocket = ReturnType<typeof makeMDSocket> type AnyWASocket = ReturnType<typeof makeMDSocket>
export const waChatKey = (pin: boolean) => ({ export const waChatKey = (pin: boolean) => ({
key: (c: Chat) => (pin ? (c.pin ? '1' : '0') : '') + (c.archive ? '0' : '1') + (c.conversationTimestamp ? c.conversationTimestamp.toString(16).padStart(8, '0') :'') + c.id, key: (c: Chat) => (pin ? (c.pin ? '1' : '0') : '') + (c.archive ? '0' : '1') + (c.conversationTimestamp ? c.conversationTimestamp.toString(16).padStart(8, '0') : '') + c.id,
compare: (k1: string, k2: string) => k2.localeCompare (k1) compare: (k1: string, k2: string) => k2.localeCompare (k1)
}) })
@@ -33,7 +33,7 @@ export default (
logger = logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' }) logger = logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' })
chatKey = chatKey || waChatKey(true) chatKey = chatKey || waChatKey(true)
const KeyedDB = require('@adiwajshing/keyed-db').default as new (...args: any[]) => KeyedDB<Chat, string> const KeyedDB = require('@adiwajshing/keyed-db').default as new (...args: any[]) => KeyedDB<Chat, string>
const chats = new KeyedDB(chatKey, c => c.id) const chats = new KeyedDB(chatKey, c => c.id)
const messages: { [_: string]: ReturnType<typeof makeMessagesDictionary> } = { } const messages: { [_: string]: ReturnType<typeof makeMessagesDictionary> } = { }
const contacts: { [_: string]: Contact } = { } const contacts: { [_: string]: Contact } = { }
@@ -54,7 +54,7 @@ export default (
for(const contact of newContacts) { for(const contact of newContacts) {
oldContacts.delete(contact.id) oldContacts.delete(contact.id)
contacts[contact.id] = Object.assign( contacts[contact.id] = Object.assign(
contacts[contact.id] || {}, contacts[contact.id] || {},
contact contact
) )
} }
@@ -63,7 +63,7 @@ export default (
} }
/** /**
* binds to a BaileysEventEmitter. * binds to a BaileysEventEmitter.
* It listens to all events and constructs a state that you can query accurate data from. * It listens to all events and constructs a state that you can query accurate data from.
* Eg. can use the store to fetch chats, contacts, messages etc. * Eg. can use the store to fetch chats, contacts, messages etc.
* @param ev typically the event emitter from the socket connection * @param ev typically the event emitter from the socket connection
@@ -76,7 +76,7 @@ export default (
if(isLatest) { if(isLatest) {
chats.clear() chats.clear()
} }
const chatsAdded = chats.insertIfAbsent(...newChats).length const chatsAdded = chats.insertIfAbsent(...newChats).length
logger.debug({ chatsAdded }, 'synced chats') logger.debug({ chatsAdded }, 'synced chats')
}) })
@@ -150,12 +150,12 @@ export default (
if(type === 'notify') { if(type === 'notify') {
if(!chats.get(jid)) { if(!chats.get(jid)) {
ev.emit('chats.upsert', [ ev.emit('chats.upsert', [
{ {
id: jid, id: jid,
conversationTimestamp: toNumber(msg.messageTimestamp), conversationTimestamp: toNumber(msg.messageTimestamp),
unreadCount: 1 unreadCount: 1
} }
]) ])
} }
} }
@@ -268,7 +268,7 @@ export default (
const mode = !cursor || 'before' in cursor ? 'before' : 'after' const mode = !cursor || 'before' in cursor ? 'before' : 'after'
const cursorKey = !!cursor ? ('before' in cursor ? cursor.before : cursor.after) : undefined const cursorKey = !!cursor ? ('before' in cursor ? cursor.before : cursor.after) : undefined
const cursorValue = cursorKey ? list.get(cursorKey.id) : undefined const cursorValue = cursorKey ? list.get(cursorKey.id) : undefined
let messages: WAMessage[] let messages: WAMessage[]
if(list && mode === 'before' && (!cursorKey || cursorValue)) { if(list && mode === 'before' && (!cursorKey || cursorValue)) {
if(cursorValue) { if(cursorValue) {
@@ -286,7 +286,7 @@ export default (
const cursor = { before: fMessage?.key || cursorKey } const cursor = { before: fMessage?.key || cursorKey }
const extra = await retrieve (diff, cursor) const extra = await retrieve (diff, cursor)
// add to DB // add to DB
for(let i = extra.length-1; i >= 0;i--) { for(let i = extra.length - 1; i >= 0;i--) {
list.upsert(extra[i], 'prepend') list.upsert(extra[i], 'prepend')
} }

View File

@@ -29,7 +29,7 @@ function makeOrderedDictionary<T>(idGetter: (item: T) => string) {
dict[id] = item dict[id] = item
} }
} }
const remove = (item: T) => { const remove = (item: T) => {
const id = idGetter(item) const id = idGetter(item)
const idx = array.findIndex(i => idGetter(i) === id) const idx = array.findIndex(i => idGetter(i) === id)
@@ -62,7 +62,7 @@ function makeOrderedDictionary<T>(idGetter: (item: T) => string) {
clear: () => { clear: () => {
array.splice(0, array.length) array.splice(0, array.length)
Object.keys(dict).forEach(key => { Object.keys(dict).forEach(key => {
delete dict[key] delete dict[key]
}) })
}, },
filter: (contain: (item: T) => boolean) => { filter: (contain: (item: T) => boolean) => {

View File

@@ -39,7 +39,7 @@ describe('Media Download Tests', () => {
it('should download a full encrypted media correctly', async() => { it('should download a full encrypted media correctly', async() => {
for(const { type, message, plaintext } of TEST_VECTORS) { for(const { type, message, plaintext } of TEST_VECTORS) {
const readPipe = await downloadContentFromMessage(message, type) const readPipe = await downloadContentFromMessage(message, type)
let buffer = Buffer.alloc(0) let buffer = Buffer.alloc(0)
for await (const read of readPipe) { for await (const read of readPipe) {
buffer = Buffer.concat([ buffer, read ]) buffer = Buffer.concat([ buffer, read ])
@@ -53,13 +53,13 @@ describe('Media Download Tests', () => {
for(const { type, message, plaintext } of TEST_VECTORS) { for(const { type, message, plaintext } of TEST_VECTORS) {
// check all edge cases // check all edge cases
const ranges = [ const ranges = [
{ startByte: 51, endByte: plaintext.length-100 }, // random numbers { startByte: 51, endByte: plaintext.length - 100 }, // random numbers
{ startByte: 1024, endByte: 2038 }, // larger random multiples of 16 { startByte: 1024, endByte: 2038 }, // larger random multiples of 16
{ startByte: 1, endByte: plaintext.length-1 } // borders { startByte: 1, endByte: plaintext.length - 1 } // borders
] ]
for(const range of ranges) { for(const range of ranges) {
const readPipe = await downloadContentFromMessage(message, type, range) const readPipe = await downloadContentFromMessage(message, type, range)
let buffer = Buffer.alloc(0) let buffer = Buffer.alloc(0)
for await (const read of readPipe) { for await (const read of readPipe) {
buffer = Buffer.concat([ buffer, read ]) buffer = Buffer.concat([ buffer, read ])

View File

@@ -13,10 +13,10 @@ export type SignalIdentity = {
identifierKey: Uint8Array identifierKey: Uint8Array
} }
export type LTHashState = { export type LTHashState = {
version: number version: number
hash: Buffer hash: Buffer
indexValueMap: { indexValueMap: {
[indexMacBase64: string]: { valueMac: Uint8Array | Buffer } [indexMacBase64: string]: { valueMac: Uint8Array | Buffer }
} }
} }
@@ -30,7 +30,7 @@ export type SignalCreds = {
export type AuthenticationCreds = SignalCreds & { export type AuthenticationCreds = SignalCreds & {
readonly noiseKey: KeyPair readonly noiseKey: KeyPair
readonly advSecretKey: string readonly advSecretKey: string
me?: Contact me?: Contact
account?: proto.IADVSignedDeviceIdentity account?: proto.IADVSignedDeviceIdentity
signalIdentities?: SignalIdentity[] signalIdentities?: SignalIdentity[]

View File

@@ -10,7 +10,7 @@ export interface PresenceData {
lastSeen?: number lastSeen?: number
} }
export type ChatMutation = { export type ChatMutation = {
syncAction: proto.ISyncActionData syncAction: proto.ISyncActionData
index: string[] index: string[]
} }
@@ -32,14 +32,14 @@ export type Chat = Omit<proto.IConversation, 'messages'> & {
pin?: number | null pin?: number | null
archive?: boolean archive?: boolean
} }
/** /**
* the last messages in a chat, sorted reverse-chronologically * the last messages in a chat, sorted reverse-chronologically
* for MD modifications, the last message in the array must be the last message recv in the chat * for MD modifications, the last message in the array must be the last message recv in the chat
* */ * */
export type LastMessageList = Pick<proto.IWebMessageInfo, 'key' | 'messageTimestamp'>[] export type LastMessageList = Pick<proto.IWebMessageInfo, 'key' | 'messageTimestamp'>[]
export type ChatModification = export type ChatModification =
{ {
archive: boolean archive: boolean
lastMessages: LastMessageList lastMessages: LastMessageList
} | } |
@@ -54,11 +54,11 @@ export type ChatModification =
clear: 'all' | { messages: {id: string, fromMe?: boolean}[] } clear: 'all' | { messages: {id: string, fromMe?: boolean}[] }
} | } |
{ {
star: { star: {
messages: { id: string, fromMe?: boolean }[], messages: { id: string, fromMe?: boolean }[],
star: boolean star: boolean
} }
} | } |
{ {
markRead: boolean markRead: boolean
lastMessages: LastMessageList lastMessages: LastMessageList

View File

@@ -24,15 +24,15 @@ export type BaileysEventMap<T> = {
/** delete chats with given ID */ /** delete chats with given ID */
'chats.delete': string[] 'chats.delete': string[]
/** presence of contact in a chat updated */ /** presence of contact in a chat updated */
'presence.update': { id: string, presences: { [participant: string]: PresenceData } } 'presence.update': { id: string, presences: { [participant: string]: PresenceData } }
'contacts.upsert': Contact[] 'contacts.upsert': Contact[]
'contacts.update': Partial<Contact>[] 'contacts.update': Partial<Contact>[]
'messages.delete': { keys: WAMessageKey[] } | { jid: string, all: true } 'messages.delete': { keys: WAMessageKey[] } | { jid: string, all: true }
'messages.update': WAMessageUpdate[] 'messages.update': WAMessageUpdate[]
/** /**
* add/update the given messages. If they were received while the connection was online, * add/update the given messages. If they were received while the connection was online,
* the update will have type: "notify" * the update will have type: "notify"
* */ * */
'messages.upsert': { messages: WAMessage[], type: MessageUpdateType } 'messages.upsert': { messages: WAMessage[], type: MessageUpdateType }

View File

@@ -25,7 +25,7 @@ export type MessageType = keyof proto.Message
export type DownloadableMessage = { mediaKey?: Uint8Array, directPath?: string, url?: string } export type DownloadableMessage = { mediaKey?: Uint8Array, directPath?: string, url?: string }
export type MediaConnInfo = { export type MediaConnInfo = {
auth: string auth: string
ttl: number ttl: number
hosts: { hostname: string, maxContentLengthBytes: number }[] hosts: { hostname: string, maxContentLengthBytes: number }[]
fetchDate: Date fetchDate: Date
@@ -77,7 +77,7 @@ export type AnyMediaMessageContent = (
image: WAMediaUpload image: WAMediaUpload
caption?: string caption?: string
jpegThumbnail?: string jpegThumbnail?: string
} & Mentionable & Buttonable & Templatable & WithDimensions) | } & Mentionable & Buttonable & Templatable & WithDimensions) |
({ ({
video: WAMediaUpload video: WAMediaUpload
caption?: string caption?: string
@@ -95,22 +95,22 @@ export type AnyMediaMessageContent = (
document: WAMediaUpload document: WAMediaUpload
mimetype: string mimetype: string
fileName?: string fileName?: string
} & Buttonable & Templatable)) & } & Buttonable & Templatable)) &
{ mimetype?: string } { mimetype?: string }
export type AnyRegularMessageContent = ( export type AnyRegularMessageContent = (
({ ({
text: string text: string
} }
& Mentionable & Buttonable & Templatable & Listable) | & Mentionable & Buttonable & Templatable & Listable) |
AnyMediaMessageContent | AnyMediaMessageContent |
{ {
contacts: { contacts: {
displayName?: string displayName?: string
contacts: proto.IContactMessage[] contacts: proto.IContactMessage[]
} }
} | } |
{ {
location: WALocationMessage location: WALocationMessage
} }
) & ViewOnce ) & ViewOnce
@@ -120,7 +120,7 @@ export type AnyMessageContent = AnyRegularMessageContent | {
force?: boolean force?: boolean
} | { } | {
delete: WAMessageKey delete: WAMessageKey
} | { } | {
disappearingMessagesInChat: boolean | number disappearingMessagesInChat: boolean | number
} }

View File

@@ -1,8 +1,8 @@
export type CatalogResult = { export type CatalogResult = {
data: { data: {
paging: { cursors: { before: string, after: string } }, paging: { cursors: { before: string, after: string } },
data: any[] data: any[]
} }
} }

View File

@@ -12,7 +12,7 @@ export type CommonSocketConfig<T> = {
/** provide an auth state object to maintain the auth state */ /** provide an auth state object to maintain the auth state */
auth?: T auth?: T
/** the WS url to connect to WA */ /** the WS url to connect to WA */
waWebSocketUrl: string | URL waWebSocketUrl: string | URL
/** Fails the connection if the socket times out in this interval */ /** Fails the connection if the socket times out in this interval */
connectTimeoutMs: number connectTimeoutMs: number
/** Default timeout for queries, undefined for no timeout */ /** Default timeout for queries, undefined for no timeout */

View File

@@ -21,5 +21,5 @@ export type ConnectionState = {
phoneConnected: boolean phoneConnected: boolean
user?: Contact user?: Contact
} }
} }

View File

@@ -19,8 +19,8 @@ export type SocketConfig = CommonSocketConfig<AuthenticationState> & {
userDevicesCache?: NodeCache userDevicesCache?: NodeCache
/** map to store the retry counts for failed messages */ /** map to store the retry counts for failed messages */
msgRetryCounterMap?: { [msgId: string]: number } msgRetryCounterMap?: { [msgId: string]: number }
/** /**
* fetch a message from your store * fetch a message from your store
* implement this so that messages failed to send (solves the "this message can take a while" issue) can be retried * implement this so that messages failed to send (solves the "this message can take a while" issue) can be retried
* */ * */
getMessage: (key: proto.IMessageKey) => Promise<proto.IMessage | undefined> getMessage: (key: proto.IMessageKey) => Promise<proto.IMessage | undefined>
@@ -53,15 +53,15 @@ export type WABusinessHoursConfig = {
export type WABusinessProfile = { export type WABusinessProfile = {
description: string description: string
email: string email: string
business_hours: { business_hours: {
timezone?: string timezone?: string
config?: WABusinessHoursConfig[] config?: WABusinessHoursConfig[]
business_config?: WABusinessHoursConfig[] business_config?: WABusinessHoursConfig[]
} }
website: string[] website: string[]
categories: { categories: {
id: string id: string
localized_display_name: string localized_display_name: string
}[] }[]
wid?: string wid?: string
} }

View File

@@ -128,10 +128,10 @@ export const useSingleFileAuthState = (filename: string, logger?: Logger): { sta
JSON.stringify({ creds, keys }, BufferJSON.replacer, 2) JSON.stringify({ creds, keys }, BufferJSON.replacer, 2)
) )
} }
if(existsSync(filename)) { if(existsSync(filename)) {
const result = JSON.parse( const result = JSON.parse(
readFileSync(filename, { encoding: 'utf-8' }), readFileSync(filename, { encoding: 'utf-8' }),
BufferJSON.reviver BufferJSON.reviver
) )
creds = result.creds creds = result.creds
@@ -141,8 +141,8 @@ export const useSingleFileAuthState = (filename: string, logger?: Logger): { sta
keys = { } keys = { }
} }
return { return {
state: { state: {
creds, creds,
keys: { keys: {
get: (type, ids) => { get: (type, ids) => {
@@ -172,7 +172,7 @@ export const useSingleFileAuthState = (filename: string, logger?: Logger): { sta
saveState() saveState()
} }
} }
}, },
saveState saveState
} }
} }

View File

@@ -5,7 +5,7 @@ import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren } from '../WABina
import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto' import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto'
import { toNumber } from './generics' import { toNumber } from './generics'
import { LT_HASH_ANTI_TAMPERING } from './lt-hash' import { LT_HASH_ANTI_TAMPERING } from './lt-hash'
import { downloadContentFromMessage, } from './messages-media' import { downloadContentFromMessage, } from './messages-media'
type FetchAppStateSyncKey = (keyId: string) => Promise<proto.IAppStateSyncKeyData> | proto.IAppStateSyncKeyData type FetchAppStateSyncKey = (keyId: string) => Promise<proto.IAppStateSyncKeyData> | proto.IAppStateSyncKeyData
@@ -39,7 +39,7 @@ const generateMac = (operation: proto.SyncdMutation.SyncdMutationSyncdOperation,
const keyData = getKeyData() const keyData = getKeyData()
const last = Buffer.alloc(8) // 8 bytes const last = Buffer.alloc(8) // 8 bytes
last.set([ keyData.length ], last.length-1) last.set([ keyData.length ], last.length - 1)
const total = Buffer.concat([ keyData, data, last ]) const total = Buffer.concat([ keyData, data, last ])
const hmac = hmacSign(total, key, 'sha512') const hmac = hmacSign(total, key, 'sha512')
@@ -180,7 +180,7 @@ export const encodeSyncdPatch = async(
} }
export const decodeSyncdMutations = async( export const decodeSyncdMutations = async(
msgMutations: (proto.ISyncdMutation | proto.ISyncdRecord)[], msgMutations: (proto.ISyncdMutation | proto.ISyncdRecord)[],
initialState: LTHashState, initialState: LTHashState,
getAppStateSyncKey: FetchAppStateSyncKey, getAppStateSyncKey: FetchAppStateSyncKey,
validateMacs: boolean validateMacs: boolean
@@ -214,7 +214,7 @@ export const decodeSyncdMutations = async(
// otherwise, if it's only a record -- it'll be a SET mutation // otherwise, if it's only a record -- it'll be a SET mutation
const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdMutationSyncdOperation.SET const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdMutationSyncdOperation.SET
const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation as proto.ISyncdRecord const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation as proto.ISyncdRecord
const key = await getKey(record.keyId!.id!) const key = await getKey(record.keyId!.id!)
const content = Buffer.from(record.value!.blob!) const content = Buffer.from(record.value!.blob!)
const encContent = content.slice(0, -32) const encContent = content.slice(0, -32)
@@ -236,12 +236,12 @@ export const decodeSyncdMutations = async(
} }
} }
const indexStr = Buffer.from(syncAction.index).toString() const indexStr = Buffer.from(syncAction.index).toString()
mutations.push({ mutations.push({
syncAction, syncAction,
index: JSON.parse(indexStr), index: JSON.parse(indexStr),
}) })
ltGenerator.mix({ ltGenerator.mix({
indexMac: record.index!.blob!, indexMac: record.index!.blob!,
valueMac: ogValueMac, valueMac: ogValueMac,
operation: operation operation: operation
@@ -252,7 +252,7 @@ export const decodeSyncdMutations = async(
} }
export const decodeSyncdPatch = async( export const decodeSyncdPatch = async(
msg: proto.ISyncdPatch, msg: proto.ISyncdPatch,
name: WAPatchName, name: WAPatchName,
initialState: LTHashState, initialState: LTHashState,
getAppStateSyncKey: FetchAppStateSyncKey, getAppStateSyncKey: FetchAppStateSyncKey,
@@ -263,11 +263,11 @@ export const decodeSyncdPatch = async(
const mainKeyObj = await getAppStateSyncKey(base64Key) const mainKeyObj = await getAppStateSyncKey(base64Key)
const mainKey = mutationKeys(mainKeyObj.keyData!) const mainKey = mutationKeys(mainKeyObj.keyData!)
const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32)) const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32))
const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, toNumber(msg.version!.version), name, mainKey.patchMacKey) const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, toNumber(msg.version!.version), name, mainKey.patchMacKey)
if(Buffer.compare(patchMac, msg.patchMac) !== 0) { if(Buffer.compare(patchMac, msg.patchMac) !== 0) {
throw new Boom('Invalid patch mac') throw new Boom('Invalid patch mac')
} }
} }
const result = await decodeSyncdMutations(msg!.mutations!, initialState, getAppStateSyncKey, validateMacs) const result = await decodeSyncdMutations(msg!.mutations!, initialState, getAppStateSyncKey, validateMacs)
@@ -277,7 +277,7 @@ export const decodeSyncdPatch = async(
export const extractSyncdPatches = async(result: BinaryNode) => { export const extractSyncdPatches = async(result: BinaryNode) => {
const syncNode = getBinaryNodeChild(result, 'sync') const syncNode = getBinaryNodeChild(result, 'sync')
const collectionNodes = getBinaryNodeChildren(syncNode, 'collection') const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
const final = { } as { [T in WAPatchName]: { patches: proto.ISyncdPatch[], hasMorePatches: boolean, snapshot?: proto.ISyncdSnapshot } } const final = { } as { [T in WAPatchName]: { patches: proto.ISyncdPatch[], hasMorePatches: boolean, snapshot?: proto.ISyncdSnapshot } }
await Promise.all( await Promise.all(
collectionNodes.map( collectionNodes.map(
@@ -291,7 +291,7 @@ export const extractSyncdPatches = async(result: BinaryNode) => {
const name = collectionNode.attrs.name as WAPatchName const name = collectionNode.attrs.name as WAPatchName
const hasMorePatches = collectionNode.attrs.has_more_patches === 'true' const hasMorePatches = collectionNode.attrs.has_more_patches === 'true'
let snapshot: proto.ISyncdSnapshot | undefined = undefined let snapshot: proto.ISyncdSnapshot | undefined = undefined
if(snapshotNode && !!snapshotNode.content) { if(snapshotNode && !!snapshotNode.content) {
if(!Buffer.isBuffer(snapshotNode)) { if(!Buffer.isBuffer(snapshotNode)) {
@@ -313,13 +313,13 @@ export const extractSyncdPatches = async(result: BinaryNode) => {
const syncd = proto.SyncdPatch.decode(content! as Uint8Array) const syncd = proto.SyncdPatch.decode(content! as Uint8Array)
if(!syncd.version) { if(!syncd.version) {
syncd.version = { version: +collectionNode.attrs.version+1 } syncd.version = { version: +collectionNode.attrs.version + 1 }
} }
syncds.push(syncd) syncds.push(syncd)
} }
} }
final[name] = { patches: syncds, hasMorePatches, snapshot } final[name] = { patches: syncds, hasMorePatches, snapshot }
} }
) )
@@ -354,7 +354,7 @@ export const decodeSyncdSnapshot = async(
) => { ) => {
const newState = newLTHashState() const newState = newLTHashState()
newState.version = toNumber(snapshot.version!.version!) newState.version = toNumber(snapshot.version!.version!)
const { hash, indexValueMap, mutations } = await decodeSyncdMutations(snapshot.records!, newState, getAppStateSyncKey, validateMacs) const { hash, indexValueMap, mutations } = await decodeSyncdMutations(snapshot.records!, newState, getAppStateSyncKey, validateMacs)
newState.hash = hash newState.hash = hash
newState.indexValueMap = indexValueMap newState.indexValueMap = indexValueMap
@@ -408,7 +408,7 @@ export const decodePatches = async(
} }
const patchVersion = toNumber(version.version!) const patchVersion = toNumber(version.version!)
newState.version = patchVersion newState.version = patchVersion
const decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, validateMacs) const decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, validateMacs)
@@ -418,7 +418,7 @@ export const decodePatches = async(
if(typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber) { if(typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber) {
successfulMutations.push(...decodeResult.mutations) successfulMutations.push(...decodeResult.mutations)
} }
if(validateMacs) { if(validateMacs) {
const base64Key = Buffer.from(keyId!.id!).toString('base64') const base64Key = Buffer.from(keyId!.id!).toString('base64')
const keyEnc = await getAppStateSyncKey(base64Key) const keyEnc = await getAppStateSyncKey(base64Key)
@@ -450,7 +450,7 @@ export const chatModificationToAppPatch = (
throw new Boom('Expected last message to be not from me', { statusCode: 400 }) throw new Boom('Expected last message to be not from me', { statusCode: 400 })
} }
const lastMsg = lastMessages[lastMessages.length-1] const lastMsg = lastMessages[lastMessages.length - 1]
if(lastMsg.key.fromMe) { if(lastMsg.key.fromMe) {
throw new Boom('Expected last message in array to be not from me', { statusCode: 400 }) throw new Boom('Expected last message in array to be not from me', { statusCode: 400 })
} }

View File

@@ -29,7 +29,7 @@ export const signedKeyPair = (keyPair: KeyPair, keyId: number) => {
pubKey.set(signKeys.public, 1) pubKey.set(signKeys.public, 1)
const signature = Curve.sign(keyPair.private, pubKey) const signature = Curve.sign(keyPair.private, pubKey)
return { keyPair: signKeys, signature, keyId } return { keyPair: signKeys, signature, keyId }
} }
@@ -78,10 +78,10 @@ export function hkdf(buffer: Uint8Array, expandedLength: number, { info, salt }:
let prev = Buffer.from([]) let prev = Buffer.from([])
const buffers = [] const buffers = []
const num_blocks = Math.ceil(expandedLength / hashLength) const num_blocks = Math.ceil(expandedLength / hashLength)
const infoBuff = Buffer.from(info || []) const infoBuff = Buffer.from(info || [])
for(var i=0; i<num_blocks; i++) { for(var i = 0; i < num_blocks; i++) {
const hmac = createHmac(hashAlg, prk) const hmac = createHmac(hashAlg, prk)
// XXX is there a more optimal way to build up buffers? // XXX is there a more optimal way to build up buffers?
const input = Buffer.concat([ const input = Buffer.concat([

View File

@@ -62,7 +62,7 @@ export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationStat
const fromMe = isMe(stanza.attrs.participant || stanza.attrs.from) const fromMe = isMe(stanza.attrs.participant || stanza.attrs.from)
const pushname = stanza.attrs.notify const pushname = stanza.attrs.notify
const key: WAMessageKey = { const key: WAMessageKey = {
remoteJid: chatId, remoteJid: chatId,
fromMe, fromMe,
@@ -83,21 +83,21 @@ export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationStat
return { return {
fullMessage, fullMessage,
decryptionTask: (async() => { decryptionTask: (async() => {
let decryptables = 0 let decryptables = 0
if(Array.isArray(stanza.content)) { if(Array.isArray(stanza.content)) {
for(const { tag, attrs, content } of stanza.content) { for(const { tag, attrs, content } of stanza.content) {
if(tag !== 'enc') { if(tag !== 'enc') {
continue continue
} }
if(!(content instanceof Uint8Array)) { if(!(content instanceof Uint8Array)) {
continue continue
} }
decryptables += 1 decryptables += 1
let msgBuffer: Buffer let msgBuffer: Buffer
try { try {
const e2eType = attrs.type const e2eType = attrs.type
switch (e2eType) { switch (e2eType) {
@@ -110,13 +110,13 @@ export const decodeMessageStanza = (stanza: BinaryNode, auth: AuthenticationStat
msgBuffer = await decryptSignalProto(user, e2eType, content as Buffer, auth) msgBuffer = await decryptSignalProto(user, e2eType, content as Buffer, auth)
break break
} }
let msg: proto.IMessage = proto.Message.decode(unpadRandomMax16(msgBuffer)) let msg: proto.IMessage = proto.Message.decode(unpadRandomMax16(msgBuffer))
msg = msg.deviceSentMessage?.message || msg msg = msg.deviceSentMessage?.message || msg
if(msg.senderKeyDistributionMessage) { if(msg.senderKeyDistributionMessage) {
await processSenderKeyMessage(author, msg.senderKeyDistributionMessage, auth) await processSenderKeyMessage(author, msg.senderKeyDistributionMessage, auth)
} }
if(fullMessage.message) { if(fullMessage.message) {
Object.assign(fullMessage.message, msg) Object.assign(fullMessage.message, msg)
} else { } else {

View File

@@ -38,7 +38,7 @@ export const BufferJSON = {
} }
return value return value
} }
} }
export const writeRandomPadMax16 = (e: Binary) => { export const writeRandomPadMax16 = (e: Binary) => {
@@ -47,7 +47,7 @@ export const writeRandomPadMax16 = (e: Binary) => {
e.writeUint8(t) e.writeUint8(t)
} }
} }
var t = randomBytes(1) var t = randomBytes(1)
r(e, 1 + (15 & t[0])) r(e, 1 + (15 & t[0]))
return e return e
@@ -58,12 +58,12 @@ export const unpadRandomMax16 = (e: Uint8Array | Buffer) => {
if(0 === t.length) { if(0 === t.length) {
throw new Error('unpadPkcs7 given empty bytes') throw new Error('unpadPkcs7 given empty bytes')
} }
var r = t[t.length - 1] var r = t[t.length - 1]
if(r > t.length) { if(r > t.length) {
throw new Error(`unpad given ${t.length} bytes, but pad is ${r}`) throw new Error(`unpad given ${t.length} bytes, but pad is ${r}`)
} }
return new Uint8Array(t.buffer, t.byteOffset, t.length - r) return new Uint8Array(t.buffer, t.byteOffset, t.length - r)
} }
@@ -88,7 +88,7 @@ export const encodeInt = (e: number, t: number) => {
return a return a
} }
export const encodeBigEndian = (e: number, t=4) => { export const encodeBigEndian = (e: number, t = 4) => {
let r = e let r = e
const a = new Uint8Array(t) const a = new Uint8Array(t)
for(let i = t - 1; i >= 0; i--) { for(let i = t - 1; i >= 0; i--) {
@@ -121,7 +121,7 @@ export function shallowChanges <T>(old: T, current: T, { lookForDeletedKeys }: {
} }
/** unix timestamp of a date in seconds */ /** unix timestamp of a date in seconds */
export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime()/1000) export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime() / 1000)
export type DebouncedTimeout = ReturnType<typeof debouncedTimeout> export type DebouncedTimeout = ReturnType<typeof debouncedTimeout>
@@ -175,7 +175,7 @@ export async function promiseTimeout<T>(ms: number, promise: (resolve: (v?: T)=>
const stack = new Error().stack const stack = new Error().stack
// Create a promise that rejects in <ms> milliseconds // Create a promise that rejects in <ms> milliseconds
const { delay, cancel } = delayCancellable (ms) const { delay, cancel } = delayCancellable (ms)
const p = new Promise ((resolve, reject) => { const p = new Promise ((resolve, reject) => {
delay delay
.then(() => reject( .then(() => reject(
@@ -186,8 +186,8 @@ export async function promiseTimeout<T>(ms: number, promise: (resolve: (v?: T)=>
} }
}) })
)) ))
.catch (err => reject(err)) .catch (err => reject(err))
promise (resolve, reject) promise (resolve, reject)
}) })
.finally (cancel) .finally (cancel)
@@ -202,7 +202,7 @@ export const bindWaitForConnectionUpdate = (ev: CommonBaileysEventEmitter<any>)
let listener: (item: Partial<ConnectionState>) => void let listener: (item: Partial<ConnectionState>) => void
await ( await (
promiseTimeout( promiseTimeout(
timeoutMs, timeoutMs,
(resolve, reject) => { (resolve, reject) => {
listener = (update) => { listener = (update) => {
if(check(update)) { if(check(update)) {
@@ -236,7 +236,7 @@ export const printQRIfNecessaryListener = (ev: CommonBaileysEventEmitter<any>, l
/** /**
* utility that fetches latest baileys version from the master branch. * utility that fetches latest baileys version from the master branch.
* Use to ensure your WA connection is always on the latest version * Use to ensure your WA connection is always on the latest version
*/ */
export const fetchLatestBaileysVersion = async() => { export const fetchLatestBaileysVersion = async() => {
const URL = 'https://raw.githubusercontent.com/adiwajshing/Baileys/master/src/Defaults/baileys-version.json' const URL = 'https://raw.githubusercontent.com/adiwajshing/Baileys/master/src/Defaults/baileys-version.json'

View File

@@ -10,21 +10,21 @@ export const newLegacyAuthCreds = () => ({
}) as LegacyAuthenticationCreds }) as LegacyAuthenticationCreds
export const decodeWAMessage = ( export const decodeWAMessage = (
message: Buffer | string, message: Buffer | string,
auth: { macKey: Buffer, encKey: Buffer }, auth: { macKey: Buffer, encKey: Buffer },
fromMe: boolean=false fromMe: boolean = false
) => { ) => {
let commaIndex = message.indexOf(',') // all whatsapp messages have a tag and a comma, followed by the actual message let commaIndex = message.indexOf(',') // all whatsapp messages have a tag and a comma, followed by the actual message
if(commaIndex < 0) { if(commaIndex < 0) {
throw new Boom('invalid message', { data: message }) throw new Boom('invalid message', { data: message })
} // if there was no comma, then this message must be not be valid } // if there was no comma, then this message must be not be valid
if(message[commaIndex+1] === ',') { if(message[commaIndex + 1] === ',') {
commaIndex += 1 commaIndex += 1
} }
let data = message.slice(commaIndex+1, message.length) let data = message.slice(commaIndex + 1, message.length)
// get the message tag. // get the message tag.
// If a query was done, the server will respond with the same message tag we sent the query with // If a query was done, the server will respond with the same message tag we sent the query with
const messageTag: string = message.slice(0, commaIndex).toString() const messageTag: string = message.slice(0, commaIndex).toString()
@@ -43,7 +43,7 @@ export const decodeWAMessage = (
throw new Boom('recieved encrypted buffer when auth creds unavailable', { data: message, statusCode: DisconnectReason.badSession }) throw new Boom('recieved encrypted buffer when auth creds unavailable', { data: message, statusCode: DisconnectReason.badSession })
} }
/* /*
If the data recieved was not a JSON, then it must be an encrypted message. If the data recieved was not a JSON, then it must be an encrypted message.
Such a message can only be decrypted if we're connected successfully to the servers & have encryption keys Such a message can only be decrypted if we're connected successfully to the servers & have encryption keys
*/ */
@@ -51,11 +51,11 @@ export const decodeWAMessage = (
tags = [data[0], data[1]] tags = [data[0], data[1]]
data = data.slice(2, data.length) data = data.slice(2, data.length)
} }
const checksum = data.slice(0, 32) // the first 32 bytes of the buffer are the HMAC sign of the message const checksum = data.slice(0, 32) // the first 32 bytes of the buffer are the HMAC sign of the message
data = data.slice(32, data.length) // the actual message data = data.slice(32, data.length) // the actual message
const computedChecksum = hmacSign(data, macKey) // compute the sign of the message we recieved using our macKey const computedChecksum = hmacSign(data, macKey) // compute the sign of the message we recieved using our macKey
if(checksum.equals(computedChecksum)) { if(checksum.equals(computedChecksum)) {
// the checksum the server sent, must match the one we computed for the message to be valid // the checksum the server sent, must match the one we computed for the message to be valid
const decrypted = aesDecrypt(data, encKey) // decrypt using AES const decrypted = aesDecrypt(data, encKey) // decrypt using AES
@@ -73,7 +73,7 @@ export const decodeWAMessage = (
}) })
} }
} }
} }
} }
return [messageTag, json, tags] as const return [messageTag, json, tags] as const
@@ -85,7 +85,7 @@ export const decodeWAMessage = (
* @param json * @param json
*/ */
export const validateNewConnection = ( export const validateNewConnection = (
json: { [_: string]: any }, json: { [_: string]: any },
auth: LegacyAuthenticationCreds, auth: LegacyAuthenticationCreds,
curveKeys: CurveKeyPair curveKeys: CurveKeyPair
) => { ) => {
@@ -164,7 +164,7 @@ export const useSingleFileLegacyAuthState = (file: string) => {
if(existsSync(file)) { if(existsSync(file)) {
state = JSON.parse( state = JSON.parse(
readFileSync(file, { encoding: 'utf-8' }), readFileSync(file, { encoding: 'utf-8' }),
BufferJSON.reviver BufferJSON.reviver
) )
if(typeof state.encKey === 'string') { if(typeof state.encKey === 'string') {

View File

@@ -2,7 +2,7 @@ import { hkdf } from './crypto'
/** /**
* LT Hash is a summation based hash algorithm that maintains the integrity of a piece of data * LT Hash is a summation based hash algorithm that maintains the integrity of a piece of data
* over a series of mutations. You can add/remove mutations and it'll return a hash equal to * over a series of mutations. You can add/remove mutations and it'll return a hash equal to
* if the same series of mutations was made sequentially. * if the same series of mutations was made sequentially.
*/ */

View File

@@ -54,7 +54,7 @@ export const hkdfInfoKey = (type: MediaType) => {
if(type === 'md-app-state') { if(type === 'md-app-state') {
str = 'App State' str = 'App State'
} }
const hkdfInfo = str[0].toUpperCase() + str.slice(1) const hkdfInfo = str[0].toUpperCase() + str.slice(1)
return `WhatsApp ${hkdfInfo} Keys` return `WhatsApp ${hkdfInfo} Keys`
} }
@@ -145,7 +145,7 @@ export const generateProfilePicture = async(mediaUpload: WAMediaUpload) => {
.resize(640, 640, RESIZE_BILINEAR) .resize(640, 640, RESIZE_BILINEAR)
.getBufferAsync(MIME_JPEG) .getBufferAsync(MIME_JPEG)
} }
return { return {
img: await img, img: await img,
} }
@@ -207,8 +207,8 @@ export const getStream = async(item: WAMediaUpload) => {
/** generates a thumbnail for a given media, if required */ /** generates a thumbnail for a given media, if required */
export async function generateThumbnail( export async function generateThumbnail(
file: string, file: string,
mediaType: 'video' | 'image', mediaType: 'video' | 'image',
options: { options: {
logger?: Logger logger?: Logger
} }
@@ -229,7 +229,7 @@ export async function generateThumbnail(
options.logger?.debug('could not generate video thumb: ' + err) options.logger?.debug('could not generate video thumb: ' + err)
} }
} }
return thumbnail return thumbnail
} }
@@ -238,9 +238,9 @@ export const getHttpStream = async(url: string | URL, options: AxiosRequestConfi
const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' }) const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' })
return fetched.data as Readable return fetched.data as Readable
} }
export const encryptedStream = async( export const encryptedStream = async(
media: WAMediaUpload, media: WAMediaUpload,
mediaType: MediaType, mediaType: MediaType,
saveOriginalFileIfRequired = true, saveOriginalFileIfRequired = true,
logger?: Logger logger?: Logger
@@ -266,7 +266,7 @@ export const encryptedStream = async(
writeStream = createWriteStream(bodyPath) writeStream = createWriteStream(bodyPath)
didSaveToTmpPath = true didSaveToTmpPath = true
} }
let fileLength = 0 let fileLength = 0
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv) const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
let hmac = Crypto.createHmac('sha256', macKey).update(iv) let hmac = Crypto.createHmac('sha256', macKey).update(iv)
@@ -278,7 +278,7 @@ export const encryptedStream = async(
hmac = hmac.update(buff) hmac = hmac.update(buff)
encWriteStream.push(buff) encWriteStream.push(buff)
} }
try { try {
for await (const data of stream) { for await (const data of stream) {
fileLength += data.length fileLength += data.length
@@ -293,21 +293,21 @@ export const encryptedStream = async(
} }
onChunk(aes.final()) onChunk(aes.final())
const mac = hmac.digest().slice(0, 10) const mac = hmac.digest().slice(0, 10)
sha256Enc = sha256Enc.update(mac) sha256Enc = sha256Enc.update(mac)
const fileSha256 = sha256Plain.digest() const fileSha256 = sha256Plain.digest()
const fileEncSha256 = sha256Enc.digest() const fileEncSha256 = sha256Enc.digest()
encWriteStream.push(mac) encWriteStream.push(mac)
encWriteStream.push(null) encWriteStream.push(null)
writeStream && writeStream.end() writeStream && writeStream.end()
stream.destroy() stream.destroy()
logger?.debug('encrypted data successfully') logger?.debug('encrypted data successfully')
return { return {
mediaKey, mediaKey,
encWriteStream, encWriteStream,
@@ -356,14 +356,14 @@ export const downloadContentFromMessage = async(
if(startByte) { if(startByte) {
const chunk = toSmallestChunkSize(startByte || 0) const chunk = toSmallestChunkSize(startByte || 0)
if(chunk) { if(chunk) {
startChunk = chunk-AES_CHUNK_SIZE startChunk = chunk - AES_CHUNK_SIZE
bytesFetched = chunk bytesFetched = chunk
firstBlockIsIV = true firstBlockIsIV = true
} }
} }
const endChunk = endByte ? toSmallestChunkSize(endByte || 0)+AES_CHUNK_SIZE : undefined const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
const headers: { [_: string]: string } = { const headers: { [_: string]: string } = {
Origin: DEFAULT_ORIGIN, Origin: DEFAULT_ORIGIN,
@@ -377,7 +377,7 @@ export const downloadContentFromMessage = async(
// download the message // download the message
const fetched = await getHttpStream( const fetched = await getHttpStream(
downloadUrl, downloadUrl,
{ {
headers, headers,
maxBodyLength: Infinity, maxBodyLength: Infinity,
@@ -392,11 +392,11 @@ export const downloadContentFromMessage = async(
const pushBytes = (bytes: Buffer, push: (bytes: Buffer) => void) => { const pushBytes = (bytes: Buffer, push: (bytes: Buffer) => void) => {
if(startByte || endByte) { if(startByte || endByte) {
const start = bytesFetched >= startByte ? undefined : Math.max(startByte-bytesFetched, 0) const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0)
const end = bytesFetched+bytes.length < endByte ? undefined : Math.max(endByte-bytesFetched, 0) const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0)
push(bytes.slice(start, end)) push(bytes.slice(start, end))
bytesFetched += bytes.length bytesFetched += bytes.length
} else { } else {
push(bytes) push(bytes)
@@ -406,7 +406,7 @@ export const downloadContentFromMessage = async(
const output = new Transform({ const output = new Transform({
transform(chunk, _, callback) { transform(chunk, _, callback) {
let data = Buffer.concat([remainingBytes, chunk]) let data = Buffer.concat([remainingBytes, chunk])
const decryptLength = toSmallestChunkSize(data.length) const decryptLength = toSmallestChunkSize(data.length)
remainingBytes = data.slice(decryptLength) remainingBytes = data.slice(decryptLength)
data = data.slice(0, decryptLength) data = data.slice(0, decryptLength)
@@ -424,7 +424,7 @@ export const downloadContentFromMessage = async(
if(endByte) { if(endByte) {
aes.setAutoPadding(false) aes.setAutoPadding(false)
} }
} }
try { try {
@@ -432,7 +432,7 @@ export const downloadContentFromMessage = async(
callback() callback()
} catch(error) { } catch(error) {
callback(error) callback(error)
} }
}, },
final(callback) { final(callback) {
try { try {
@@ -451,14 +451,14 @@ export const downloadContentFromMessage = async(
* @param message the media message you want to decode * @param message the media message you want to decode
*/ */
export async function decryptMediaMessageBuffer(message: WAMessageContent): Promise<Readable> { export async function decryptMediaMessageBuffer(message: WAMessageContent): Promise<Readable> {
/* /*
One can infer media type from the key in the message One can infer media type from the key in the message
it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc. it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc.
*/ */
const type = Object.keys(message)[0] as MessageType const type = Object.keys(message)[0] as MessageType
if( if(
!type || !type ||
type === 'conversation' || type === 'conversation' ||
type === 'extendedTextMessage' type === 'extendedTextMessage'
) { ) {
throw new Boom(`no media message for "${type}"`, { statusCode: 400 }) throw new Boom(`no media message for "${type}"`, { statusCode: 400 })
@@ -492,8 +492,8 @@ export function extensionForMediaMessage(message: WAMessageContent) {
const type = Object.keys(message)[0] as MessageType const type = Object.keys(message)[0] as MessageType
let extension: string let extension: string
if( if(
type === 'locationMessage' || type === 'locationMessage' ||
type === 'liveLocationMessage' || type === 'liveLocationMessage' ||
type === 'productMessage' type === 'productMessage'
) { ) {
extension = '.jpeg' extension = '.jpeg'
@@ -539,8 +539,8 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C
const body = await axios.post( const body = await axios.post(
url, url,
reqBody, reqBody,
{ {
headers: { headers: {
'Content-Type': 'application/octet-stream', 'Content-Type': 'application/octet-stream',
'Origin': DEFAULT_ORIGIN 'Origin': DEFAULT_ORIGIN
}, },
@@ -552,7 +552,7 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C
} }
) )
result = body.data result = body.data
if(result?.url || result?.directPath) { if(result?.url || result?.directPath) {
urls = { urls = {
mediaUrl: result.url, mediaUrl: result.url,
@@ -568,7 +568,7 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C
result = error.response?.data result = error.response?.data
} }
const isLast = hostname === hosts[uploadInfo.hosts.length-1]?.hostname const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`) logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`)
} }
} }

View File

@@ -2,22 +2,22 @@ import { Boom } from '@hapi/boom'
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
import { proto } from '../../WAProto' import { proto } from '../../WAProto'
import { MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults' import { MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults'
import { import {
AnyMediaMessageContent, AnyMediaMessageContent,
AnyMessageContent, AnyMessageContent,
MediaGenerationOptions, MediaGenerationOptions,
MediaType, MediaType,
MessageContentGenerationOptions, MessageContentGenerationOptions,
MessageGenerationOptions, MessageGenerationOptions,
MessageGenerationOptionsFromContent, MessageGenerationOptionsFromContent,
MessageType, MessageType,
MessageUserReceipt, MessageUserReceipt,
WAMediaUpload, WAMediaUpload,
WAMessage, WAMessage,
WAMessageContent, WAMessageContent,
WAMessageStatus, WAMessageStatus,
WAProto, WAProto,
WATextMessage WATextMessage
} from '../Types' } from '../Types'
import { generateMessageID, unixTimestampSeconds } from './generics' import { generateMessageID, unixTimestampSeconds } from './generics'
import { encryptedStream, generateThumbnail, getAudioDuration } from './messages-media' import { encryptedStream, generateThumbnail, getAudioDuration } from './messages-media'
@@ -54,7 +54,7 @@ const MessageTypeProto = {
const ButtonType = proto.ButtonsMessage.ButtonsMessageHeaderType const ButtonType = proto.ButtonsMessage.ButtonsMessageHeaderType
export const prepareWAMessageMedia = async( export const prepareWAMessageMedia = async(
message: AnyMediaMessageContent, message: AnyMediaMessageContent,
options: MediaGenerationOptions options: MediaGenerationOptions
) => { ) => {
const logger = options.logger const logger = options.logger
@@ -66,15 +66,15 @@ export const prepareWAMessageMedia = async(
} }
} }
const uploadData: MediaUploadData = { const uploadData: MediaUploadData = {
...message, ...message,
media: message[mediaType] media: message[mediaType]
} }
delete uploadData[mediaType] delete uploadData[mediaType]
// check if cacheable + generate cache key // check if cacheable + generate cache key
const cacheableKey = typeof uploadData.media === 'object' && const cacheableKey = typeof uploadData.media === 'object' &&
('url' in uploadData.media) && ('url' in uploadData.media) &&
!!uploadData.media.url && !!uploadData.media.url &&
!!options.mediaCache && ( !!options.mediaCache && (
// generate the key // generate the key
mediaType + ':' + uploadData.media.url!.toString() mediaType + ':' + uploadData.media.url!.toString()
@@ -93,7 +93,7 @@ export const prepareWAMessageMedia = async(
const mediaBuff: Buffer = options.mediaCache!.get(cacheableKey) const mediaBuff: Buffer = options.mediaCache!.get(cacheableKey)
if(mediaBuff) { if(mediaBuff) {
logger?.debug({ cacheableKey }, 'got media cache hit') logger?.debug({ cacheableKey }, 'got media cache hit')
const obj = WAProto.Message.decode(mediaBuff) const obj = WAProto.Message.decode(mediaBuff)
const key = `${mediaType}Message` const key = `${mediaType}Message`
@@ -105,7 +105,7 @@ export const prepareWAMessageMedia = async(
} }
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined' const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') &&
(typeof uploadData['jpegThumbnail'] === 'undefined') (typeof uploadData['jpegThumbnail'] === 'undefined')
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
const { const {
@@ -118,7 +118,7 @@ export const prepareWAMessageMedia = async(
didSaveToTmpPath didSaveToTmpPath
} = await encryptedStream(uploadData.media, mediaType, requiresOriginalForSomeProcessing) } = await encryptedStream(uploadData.media, mediaType, requiresOriginalForSomeProcessing)
// url safe Base64 encode the SHA256 hash of the body // url safe Base64 encode the SHA256 hash of the body
const fileEncSha256B64 = encodeURIComponent( const fileEncSha256B64 = encodeURIComponent(
fileEncSha256.toString('base64') fileEncSha256.toString('base64')
.replace(/\+/g, '-') .replace(/\+/g, '-')
.replace(/\//g, '_') .replace(/\//g, '_')
@@ -239,7 +239,7 @@ export const generateForwardMessageContent = (
} }
export const generateWAMessageContent = async( export const generateWAMessageContent = async(
message: AnyMessageContent, message: AnyMessageContent,
options: MessageContentGenerationOptions options: MessageContentGenerationOptions
) => { ) => {
let m: WAMessageContent = {} let m: WAMessageContent = {}
@@ -256,7 +256,7 @@ export const generateWAMessageContent = async(
extContent.previewType = 0 extContent.previewType = 0
} catch(error) { // ignore if fails } catch(error) { // ignore if fails
options.logger?.warn({ trace: error.stack }, 'url generation failed') options.logger?.warn({ trace: error.stack }, 'url generation failed')
} }
} }
m.extendedTextMessage = extContent m.extendedTextMessage = extContent
@@ -265,7 +265,7 @@ export const generateWAMessageContent = async(
if(!contactLen) { if(!contactLen) {
throw new Boom('require atleast 1 contact', { statusCode: 400 }) throw new Boom('require atleast 1 contact', { statusCode: 400 })
} }
if(contactLen === 1) { if(contactLen === 1) {
m.contactMessage = WAProto.ContactMessage.fromObject(message.contacts.contacts[0]) m.contactMessage = WAProto.ContactMessage.fromObject(message.contacts.contacts[0])
} else { } else {
@@ -284,7 +284,7 @@ export const generateWAMessageContent = async(
message.force message.force
) )
} else if('disappearingMessagesInChat' in message) { } else if('disappearingMessagesInChat' in message) {
const exp = typeof message.disappearingMessagesInChat === 'boolean' ? const exp = typeof message.disappearingMessagesInChat === 'boolean' ?
(message.disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) : (message.disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
message.disappearingMessagesInChat message.disappearingMessagesInChat
m = prepareDisappearingMessageSettingContent(exp) m = prepareDisappearingMessageSettingContent(exp)
@@ -309,7 +309,7 @@ export const generateWAMessageContent = async(
const type = Object.keys(m)[0].replace('Message', '').toUpperCase() const type = Object.keys(m)[0].replace('Message', '').toUpperCase()
buttonsMessage.headerType = ButtonType[type] buttonsMessage.headerType = ButtonType[type]
Object.assign(buttonsMessage, m) Object.assign(buttonsMessage, m)
} }
@@ -324,7 +324,7 @@ export const generateWAMessageContent = async(
hydratedButtons: message.templateButtons hydratedButtons: message.templateButtons
} }
} }
if('text' in message) { if('text' in message) {
templateMessage.hydratedTemplate.hydratedContentText = message.text templateMessage.hydratedTemplate.hydratedContentText = message.text
} else { } else {
@@ -332,7 +332,7 @@ export const generateWAMessageContent = async(
if('caption' in message) { if('caption' in message) {
templateMessage.hydratedTemplate.hydratedContentText = message.caption templateMessage.hydratedTemplate.hydratedContentText = message.caption
} }
Object.assign(templateMessage.hydratedTemplate, m) Object.assign(templateMessage.hydratedTemplate, m)
} }
@@ -342,7 +342,7 @@ export const generateWAMessageContent = async(
m = { templateMessage } m = { templateMessage }
} }
if('sections' in message && !!message.sections) { if('sections' in message && !!message.sections) {
const listMessage: proto.IListMessage = { const listMessage: proto.IListMessage = {
sections: message.sections, sections: message.sections,
@@ -370,8 +370,8 @@ export const generateWAMessageContent = async(
} }
export const generateWAMessageFromContent = ( export const generateWAMessageFromContent = (
jid: string, jid: string,
message: WAMessageContent, message: WAMessageContent,
options: MessageGenerationOptionsFromContent options: MessageGenerationOptionsFromContent
) => { ) => {
if(!options.timestamp) { if(!options.timestamp) {
@@ -389,7 +389,7 @@ export const generateWAMessageFromContent = (
message[key].contextInfo.participant = participant message[key].contextInfo.participant = participant
message[key].contextInfo.stanzaId = quoted.key.id message[key].contextInfo.stanzaId = quoted.key.id
message[key].contextInfo.quotedMessage = quoted.message message[key].contextInfo.quotedMessage = quoted.message
// if a participant is quoted, then it must be a group // if a participant is quoted, then it must be a group
// hence, remoteJid of group must also be entered // hence, remoteJid of group must also be entered
if(quoted.key.participant || quoted.participant) { if(quoted.key.participant || quoted.participant) {
@@ -403,7 +403,7 @@ export const generateWAMessageFromContent = (
// and it's not a protocol message -- delete, toggle disappear message // and it's not a protocol message -- delete, toggle disappear message
key !== 'protocolMessage' && key !== 'protocolMessage' &&
// already not converted to disappearing message // already not converted to disappearing message
key !== 'ephemeralMessage' key !== 'ephemeralMessage'
) { ) {
message[key].contextInfo = { message[key].contextInfo = {
...(message[key].contextInfo || {}), ...(message[key].contextInfo || {}),
@@ -478,10 +478,10 @@ export const extractMessageContent = (content: WAMessageContent | undefined | nu
return { conversation: 'contentText' in msg ? msg.contentText : ('hydratedContentText' in msg ? msg.hydratedContentText : '') } return { conversation: 'contentText' in msg ? msg.contentText : ('hydratedContentText' in msg ? msg.hydratedContentText : '') }
} }
} }
content = content?.ephemeralMessage?.message || content = content?.ephemeralMessage?.message ||
content?.viewOnceMessage?.message || content?.viewOnceMessage?.message ||
content || content ||
undefined undefined
if(content?.buttonsMessage) { if(content?.buttonsMessage) {

View File

@@ -30,7 +30,7 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
const result = Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()]) const result = Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()])
writeCounter += 1 writeCounter += 1
authenticate(result) authenticate(result)
return result return result
} }
@@ -42,8 +42,8 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
const cipher = createDecipheriv('aes-256-gcm', decKey, iv) const cipher = createDecipheriv('aes-256-gcm', decKey, iv)
// decrypt additional adata // decrypt additional adata
const tagLength = 128 >> 3 const tagLength = 128 >> 3
const enc = ciphertext.slice(0, ciphertext.length-tagLength) const enc = ciphertext.slice(0, ciphertext.length - tagLength)
const tag = ciphertext.slice(ciphertext.length-tagLength) const tag = ciphertext.slice(ciphertext.length - tagLength)
// set additional data // set additional data
cipher.setAAD(hash) cipher.setAAD(hash)
cipher.setAuthTag(tag) cipher.setAuthTag(tag)
@@ -55,7 +55,7 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
} else { } else {
writeCounter += 1 writeCounter += 1
} }
authenticate(ciphertext) authenticate(ciphertext)
return result return result
} }
@@ -83,7 +83,7 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
writeCounter = 0 writeCounter = 0
isFinished = true isFinished = true
} }
const data = Binary.build(NOISE_MODE).readBuffer() const data = Binary.build(NOISE_MODE).readBuffer()
let hash = Buffer.from(data.byteLength === 32 ? data : sha256(Buffer.from(data))) let hash = Buffer.from(data.byteLength === 32 ? data : sha256(Buffer.from(data)))
let salt = hash let salt = hash
@@ -109,19 +109,19 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => { processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => {
authenticate(serverHello!.ephemeral!) authenticate(serverHello!.ephemeral!)
mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral!)) mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral!))
const decStaticContent = decrypt(serverHello!.static!) const decStaticContent = decrypt(serverHello!.static!)
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent)) mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
const certDecoded = decrypt(serverHello!.payload!) const certDecoded = decrypt(serverHello!.payload!)
const { details: certDetails, signature: certSignature } = proto.NoiseCertificate.decode(certDecoded) const { details: certDetails, signature: certSignature } = proto.NoiseCertificate.decode(certDecoded)
const { key: certKey } = proto.NoiseCertificateDetails.decode(certDetails) const { key: certKey } = proto.NoiseCertificateDetails.decode(certDetails)
if(Buffer.compare(decStaticContent, certKey) !== 0) { if(Buffer.compare(decStaticContent, certKey) !== 0) {
throw new Boom('certification match failed', { statusCode: 400 }) throw new Boom('certification match failed', { statusCode: 400 })
} }
const keyEnc = encrypt(noiseKey.public) const keyEnc = encrypt(noiseKey.public)
mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello!.ephemeral!)) mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello!.ephemeral!))
@@ -135,16 +135,16 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
const introSize = sentIntro ? 0 : NOISE_WA_HEADER.length const introSize = sentIntro ? 0 : NOISE_WA_HEADER.length
outBinary.ensureAdditionalCapacity(introSize + 3 + data.byteLength) outBinary.ensureAdditionalCapacity(introSize + 3 + data.byteLength)
if(!sentIntro) { if(!sentIntro) {
outBinary.writeByteArray(NOISE_WA_HEADER) outBinary.writeByteArray(NOISE_WA_HEADER)
sentIntro = true sentIntro = true
} }
outBinary.writeUint8(data.byteLength >> 16) outBinary.writeUint8(data.byteLength >> 16)
outBinary.writeUint16(65535 & data.byteLength) outBinary.writeUint16(65535 & data.byteLength)
outBinary.write(data) outBinary.write(data)
const bytes = outBinary.readByteArray() const bytes = outBinary.readByteArray()
return bytes as Uint8Array return bytes as Uint8Array
}, },
@@ -175,5 +175,5 @@ export const makeNoiseHandler = ({ public: publicKey, private: privateKey }: Key
inBinary.peek(peekSize) inBinary.peek(peekSize)
} }
} }
} }

View File

@@ -27,7 +27,7 @@ export const createSignalIdentity = (
wid: string, wid: string,
accountSignatureKey: Uint8Array accountSignatureKey: Uint8Array
): SignalIdentity => { ): SignalIdentity => {
return { return {
identifier: { name: wid, deviceId: 0 }, identifier: { name: wid, deviceId: 0 },
identifierKey: generateSignalPubKey(accountSignatureKey) identifierKey: generateSignalPubKey(accountSignatureKey)
} }
@@ -145,7 +145,7 @@ export const decryptGroupSignalProto = (group: string, user: string, msg: Buffer
export const processSenderKeyMessage = async( export const processSenderKeyMessage = async(
authorJid: string, authorJid: string,
item: proto.ISenderKeyDistributionMessage, item: proto.ISenderKeyDistributionMessage,
auth: SignalAuthState auth: SignalAuthState
) => { ) => {
const builder = new GroupSessionBuilder(signalStorage(auth)) const builder = new GroupSessionBuilder(signalStorage(auth))
@@ -171,7 +171,7 @@ export const decryptSignalProto = async(user: string, type: 'pkmsg' | 'msg', msg
break break
case 'msg': case 'msg':
result = await session.decryptWhisperMessage(msg) result = await session.decryptWhisperMessage(msg)
break break
} }
return result return result
@@ -231,7 +231,7 @@ export const parseAndInjectE2ESessions = async(node: BinaryNode, auth: SignalAut
const identity = getBinaryNodeChildBuffer(node, 'identity') const identity = getBinaryNodeChildBuffer(node, 'identity')
const jid = node.attrs.jid const jid = node.attrs.jid
const registrationId = getBinaryNodeChildUInt(node, 'registration', 4) const registrationId = getBinaryNodeChildUInt(node, 'registration', 4)
const device = { const device = {
registrationId, registrationId,
identityKey: generateSignalPubKey(identity), identityKey: generateSignalPubKey(identity),

View File

@@ -130,8 +130,8 @@ export const configureSuccessfulPairing = (
id: msgId, id: msgId,
}, },
content: [ content: [
{ {
tag: 'pair-device-sign', tag: 'pair-device-sign',
attrs: { }, attrs: { },
content: [ content: [
{ tag: 'device-identity', attrs: { 'key-index': `${keyIndex}` }, content: accountEnc } { tag: 'device-identity', attrs: { 'key-index': `${keyIndex}` }, content: accountEnc }
@@ -141,9 +141,9 @@ export const configureSuccessfulPairing = (
} }
const authUpdate: Partial<AuthenticationCreds> = { const authUpdate: Partial<AuthenticationCreds> = {
account, account,
me: { id: jid, verifiedName }, me: { id: jid, verifiedName },
signalIdentities: [...(signalIdentities || []), identity] signalIdentities: [...(signalIdentities || []), identity]
} }
return { return {
creds: authUpdate, creds: authUpdate,

View File

@@ -14,7 +14,7 @@ export const isLegacyBinaryNode = (buffer: Buffer) => {
} }
function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode { function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
const checkEOS = (length: number) => { const checkEOS = (length: number) => {
if(indexRef.index + length > buffer.length) { if(indexRef.index + length > buffer.length) {
throw new Error('end of stream') throw new Error('end of stream')
@@ -201,7 +201,7 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
throw new Error('invalid node') throw new Error('invalid node')
} }
// read the attributes in // read the attributes in
const attributesLength = (listSize - 1) >> 1 const attributesLength = (listSize - 1) >> 1
for(let i = 0; i < attributesLength; i++) { for(let i = 0; i < attributesLength; i++) {
const key = readString(readByte()) const key = readString(readByte())
@@ -243,10 +243,10 @@ function decode(buffer: Buffer, indexRef: { index: number }): BinaryNode {
} }
const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => { const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
const pushByte = (value: number) => buffer.push(value & 0xff) const pushByte = (value: number) => buffer.push(value & 0xff)
const pushInt = (value: number, n: number, littleEndian=false) => { const pushInt = (value: number, n: number, littleEndian = false) => {
for(let i = 0; i < n; i++) { for(let i = 0; i < n; i++) {
const curShift = littleEndian ? i : n - 1 - i const curShift = littleEndian ? i : n - 1 - i
buffer.push((value >> (curShift * 8)) & 0xff) buffer.push((value >> (curShift * 8)) & 0xff)
@@ -263,7 +263,7 @@ const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
if(length >= 4294967296) { if(length >= 4294967296) {
throw new Error('string too large to encode: ' + length) throw new Error('string too large to encode: ' + length)
} }
if(length >= 1 << 20) { if(length >= 1 << 20) {
pushByte(Tags.BINARY_32) pushByte(Tags.BINARY_32)
pushInt(length, 4) // 32 bit integer pushInt(length, 4) // 32 bit integer
@@ -294,7 +294,7 @@ const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
if(token === 'c.us') { if(token === 'c.us') {
token = 's.whatsapp.net' token = 's.whatsapp.net'
} }
const tokenIndex = SingleByteTokens.indexOf(token) const tokenIndex = SingleByteTokens.indexOf(token)
if(!i && token === 's.whatsapp.net') { if(!i && token === 's.whatsapp.net') {
writeToken(tokenIndex) writeToken(tokenIndex)
@@ -341,14 +341,14 @@ const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
typeof attrs[k] !== 'undefined' && attrs[k] !== null typeof attrs[k] !== 'undefined' && attrs[k] !== null
)) ))
writeListStart(2*validAttributes.length + 1 + (typeof content !== 'undefined' && content !== null ? 1 : 0)) writeListStart(2 * validAttributes.length + 1 + (typeof content !== 'undefined' && content !== null ? 1 : 0))
writeString(tag) writeString(tag)
validAttributes.forEach((key) => { validAttributes.forEach((key) => {
if(typeof attrs[key] === 'string') { if(typeof attrs[key] === 'string') {
writeString(key) writeString(key)
writeString(attrs[key]) writeString(attrs[key])
} }
}) })
if(typeof content === 'string') { if(typeof content === 'string') {
@@ -364,7 +364,7 @@ const encode = ({ tag, attrs, content }: BinaryNode, buffer: number[] = []) => {
} }
} }
} else if(typeof content === 'undefined' || content === null) { } else if(typeof content === 'undefined' || content === null) {
} else { } else {
throw new Error(`invalid children for header "${tag}": ${content} (${typeof content})`) throw new Error(`invalid children for header "${tag}": ${content} (${typeof content})`)
} }

View File

@@ -21,7 +21,7 @@ export const jidDecode = (jid: string) => {
return undefined return undefined
} }
const server = jid.slice(sepIdx+1) const server = jid.slice(sepIdx + 1)
const userCombined = jid.slice(0, sepIdx) const userCombined = jid.slice(0, sepIdx)
const [userAgent, device] = userCombined.split(':') const [userAgent, device] = userCombined.split(':')

View File

@@ -1,8 +1,8 @@
/** /**
* the binary node WA uses internally for communication * the binary node WA uses internally for communication
* *
* this is manipulated soley as an object and it does not have any functions. * this is manipulated soley as an object and it does not have any functions.
* This is done for easy serialization, to prevent running into issues with prototypes & * This is done for easy serialization, to prevent running into issues with prototypes &
* to maintain functional code structure * to maintain functional code structure
* */ * */
export type BinaryNode = { export type BinaryNode = {

View File

@@ -4,7 +4,7 @@
"@adiwajshing/eslint-config@git+https://github.com/adiwajshing/eslint-config": "@adiwajshing/eslint-config@git+https://github.com/adiwajshing/eslint-config":
version "1.0.0" version "1.0.0"
resolved "git+https://github.com/adiwajshing/eslint-config#db16c7427bd6dcf8fba20e0aaa526724e46c83aa" resolved "git+https://github.com/adiwajshing/eslint-config#ee2b90dba10bc161d85745be59217efa10bc1eb3"
dependencies: dependencies:
"@typescript-eslint/eslint-plugin" "^4.33.0" "@typescript-eslint/eslint-plugin" "^4.33.0"
"@typescript-eslint/parser" "^4.33.0" "@typescript-eslint/parser" "^4.33.0"
@@ -1800,14 +1800,14 @@ 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"
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: debug@4, debug@^4.1.0, debug@^4.1.1:
version "4.3.2" version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
debug@^4.0.1: debug@^4.0.1, debug@^4.3.1:
version "4.3.3" version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
@@ -2033,21 +2033,21 @@ escodegen@^2.0.0:
source-map "~0.6.1" source-map "~0.6.1"
eslint-plugin-react@^7.26.1: eslint-plugin-react@^7.26.1:
version "7.28.0" version "7.29.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz#8f3ff450677571a659ce76efc6d80b6a525adbdf" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.2.tgz#2d4da69d30d0a736efd30890dc6826f3e91f3f7c"
integrity sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw== integrity sha512-ypEBTKOy5liFQXZWMchJ3LN0JX1uPI6n7MN7OPHKacqXAxq5gYC30TdO7wqGYQyxD1OrzpobdHC3hDmlRWDg9w==
dependencies: dependencies:
array-includes "^3.1.4" array-includes "^3.1.4"
array.prototype.flatmap "^1.2.5" array.prototype.flatmap "^1.2.5"
doctrine "^2.1.0" doctrine "^2.1.0"
estraverse "^5.3.0" estraverse "^5.3.0"
jsx-ast-utils "^2.4.1 || ^3.0.0" jsx-ast-utils "^2.4.1 || ^3.0.0"
minimatch "^3.0.4" minimatch "^3.1.2"
object.entries "^1.1.5" object.entries "^1.1.5"
object.fromentries "^2.0.5" object.fromentries "^2.0.5"
object.hasown "^1.1.0" object.hasown "^1.1.0"
object.values "^1.1.5" object.values "^1.1.5"
prop-types "^15.7.2" prop-types "^15.8.1"
resolve "^2.0.0-next.3" resolve "^2.0.0-next.3"
semver "^6.3.0" semver "^6.3.0"
string.prototype.matchall "^4.0.6" string.prototype.matchall "^4.0.6"
@@ -2180,16 +2180,11 @@ estraverse@^4.1.1:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
estraverse@^5.1.0, estraverse@^5.3.0: estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
version "5.3.0" version "5.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
estraverse@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
esutils@^2.0.2: esutils@^2.0.2:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -2680,9 +2675,9 @@ is-ci@^3.0.0:
ci-info "^3.1.1" ci-info "^3.1.1"
is-core-module@^2.2.0: is-core-module@^2.2.0:
version "2.5.0" version "2.8.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
@@ -3544,10 +3539,10 @@ min-document@^2.19.0:
dependencies: dependencies:
dom-walk "^0.1.0" dom-walk "^0.1.0"
minimatch@^3.0.4: minimatch@^3.0.4, minimatch@^3.1.2:
version "3.0.4" version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
@@ -3873,11 +3868,16 @@ phin@^2.9.1:
resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c" resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c"
integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA== integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==
picomatch@^2.0.4, picomatch@^2.2.3: picomatch@^2.0.4:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
picomatch@^2.2.3:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pino-abstract-transport@v0.5.0: pino-abstract-transport@v0.5.0:
version "0.5.0" version "0.5.0"
resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0" resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0"
@@ -4010,7 +4010,7 @@ prompts@^2.0.1:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@^15.7.2: prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==