eslint: upgrade to version 9 + tests: remove tests
This commit is contained in:
@@ -9,7 +9,7 @@ const _gcLimit = 10000
|
||||
|
||||
async function _asyncQueueExecutor(queue: Array<QueueJob<any>>, cleanup: () => void): Promise<void> {
|
||||
let offt = 0
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
|
||||
while (true) {
|
||||
const limit = Math.min(queue.length, _gcLimit)
|
||||
for (let i = offt; i < limit; i++) {
|
||||
|
||||
+10
-10
@@ -1095,9 +1095,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
|
||||
// Handles newsletter notifications
|
||||
async function handleNewsletterNotification(node: BinaryNode) {
|
||||
const from = node.attrs.from
|
||||
const [child] = getAllBinaryNodeChildren(node)
|
||||
const author = node.attrs.participant
|
||||
const from = node.attrs.from!
|
||||
const child = getAllBinaryNodeChildren(node)[0]!
|
||||
const author = node.attrs.participant!
|
||||
|
||||
logger.info({ from, child }, 'got newsletter notification')
|
||||
|
||||
@@ -1105,7 +1105,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
case 'reaction':
|
||||
const reactionUpdate = {
|
||||
id: from,
|
||||
server_id: child.attrs.message_id,
|
||||
server_id: child.attrs.message_id!,
|
||||
reaction: {
|
||||
code: getBinaryNodeChildString(child, 'reaction'),
|
||||
count: 1
|
||||
@@ -1117,7 +1117,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
case 'view':
|
||||
const viewUpdate = {
|
||||
id: from,
|
||||
server_id: child.attrs.message_id,
|
||||
server_id: child.attrs.message_id!,
|
||||
count: parseInt(child.content?.toString() || '0', 10)
|
||||
}
|
||||
ev.emit('newsletter.view', viewUpdate)
|
||||
@@ -1127,9 +1127,9 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const participantUpdate = {
|
||||
id: from,
|
||||
author,
|
||||
user: child.attrs.jid,
|
||||
action: child.attrs.action,
|
||||
new_role: child.attrs.role
|
||||
user: child.attrs.jid!,
|
||||
action: child.attrs.action!,
|
||||
new_role: child.attrs.role!
|
||||
}
|
||||
ev.emit('newsletter-participants.update', participantUpdate)
|
||||
break
|
||||
@@ -1168,7 +1168,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
fromMe: false
|
||||
},
|
||||
message: messageProto,
|
||||
messageTimestamp: +child.attrs.t
|
||||
messageTimestamp: +child.attrs.t!
|
||||
})
|
||||
await upsertMessage(fullMessage, 'append')
|
||||
logger.info('Processed plaintext newsletter message')
|
||||
@@ -1229,7 +1229,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
if (update.jid && update.user) {
|
||||
ev.emit('newsletter-participants.update', {
|
||||
id: update.jid,
|
||||
author: node.attrs.from,
|
||||
author: node.attrs.from!,
|
||||
user: update.user,
|
||||
new_role: 'ADMIN',
|
||||
action: 'promote'
|
||||
|
||||
@@ -47,7 +47,8 @@ import {
|
||||
} from '../WABinary'
|
||||
import { USyncQuery, USyncUser } from '../WAUSync'
|
||||
import { makeGroupsSocket } from './groups'
|
||||
import { makeNewsletterSocket, NewsletterSocket } from './newsletter'
|
||||
import type { NewsletterSocket } from './newsletter'
|
||||
import { makeNewsletterSocket } from './newsletter'
|
||||
|
||||
export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const {
|
||||
@@ -307,7 +308,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const msgId = await relayMessage(meJid, protocolMessage, {
|
||||
additionalAttributes: {
|
||||
category: 'peer',
|
||||
// eslint-disable-next-line camelcase
|
||||
|
||||
push_priority: 'high_force'
|
||||
}
|
||||
})
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { BinaryNode } from '../WABinary'
|
||||
import type { BinaryNode } from '../WABinary'
|
||||
import { getBinaryNodeChild, S_WHATSAPP_NET } from '../WABinary'
|
||||
|
||||
const wMexQuery = (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { NewsletterCreateResponse, WAMediaUpload } from '../Types'
|
||||
import { NewsletterMetadata, NewsletterUpdate, QueryIds, XWAPaths } from '../Types'
|
||||
import type { NewsletterMetadata, NewsletterUpdate } from '../Types'
|
||||
import { QueryIds, XWAPaths } from '../Types'
|
||||
import { generateProfilePicture } from '../Utils/messages-media'
|
||||
import { getBinaryNodeChild } from '../WABinary'
|
||||
import { GroupsSocket } from './groups'
|
||||
import type { GroupsSocket } from './groups'
|
||||
import { executeWMexQuery as genericExecuteWMexQuery } from './mex'
|
||||
|
||||
const parseNewsletterCreateResponse = (response: NewsletterCreateResponse): NewsletterMetadata => {
|
||||
|
||||
@@ -487,7 +487,7 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
attrs: {
|
||||
jid: authState.creds.me.id,
|
||||
stage: 'companion_hello',
|
||||
// eslint-disable-next-line camelcase
|
||||
|
||||
should_show_push_notification: 'true'
|
||||
},
|
||||
content: [
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
import { AccountSettings, ChatMutation, Contact, InitialAppStateSyncOptions } from '../Types'
|
||||
import { unixTimestampSeconds } from '../Utils'
|
||||
import { processSyncAction } from '../Utils/chat-utils'
|
||||
import logger from '../Utils/logger'
|
||||
|
||||
describe('App State Sync Tests', () => {
|
||||
const me: Contact = { id: randomJid() }
|
||||
// case when initial sync is off
|
||||
it('should return archive=false event', () => {
|
||||
const jid = randomJid()
|
||||
const index = ['archive', jid]
|
||||
|
||||
const CASES: ChatMutation[][] = [
|
||||
[
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: false,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: unixTimestampSeconds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: true,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: unixTimestampSeconds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: false,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: unixTimestampSeconds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
for (const mutations of CASES) {
|
||||
const events = processSyncAction(mutations, me, undefined, logger)
|
||||
expect(events['chats.update']).toHaveLength(1)
|
||||
const event = events['chats.update']?.[0]
|
||||
expect(event.archive).toEqual(false)
|
||||
}
|
||||
})
|
||||
// case when initial sync is on
|
||||
// and unarchiveChats = true
|
||||
it('should not fire any archive event', () => {
|
||||
const jid = randomJid()
|
||||
const index = ['archive', jid]
|
||||
const now = unixTimestampSeconds()
|
||||
|
||||
const CASES: ChatMutation[][] = [
|
||||
[
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: true,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: now - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: false,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: now + 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: true,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: now + 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: false,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: now + 11
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
const ctx: InitialAppStateSyncOptions = {
|
||||
recvChats: {
|
||||
[jid]: { lastMsgRecvTimestamp: now }
|
||||
},
|
||||
accountSettings: { unarchiveChats: true }
|
||||
}
|
||||
|
||||
for (const mutations of CASES) {
|
||||
const events = processSyncActions(mutations, me, ctx, logger)
|
||||
expect(events['chats.update']?.length).toBeFalsy()
|
||||
}
|
||||
})
|
||||
|
||||
// case when initial sync is on
|
||||
// with unarchiveChats = true & unarchiveChats = false
|
||||
it('should fire archive=true events', () => {
|
||||
const jid = randomJid()
|
||||
const index = ['archive', jid]
|
||||
const now = unixTimestampSeconds()
|
||||
|
||||
const CASES: { settings: AccountSettings; mutations: ChatMutation[] }[] = [
|
||||
{
|
||||
settings: { unarchiveChats: true },
|
||||
mutations: [
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: true,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
settings: { unarchiveChats: false },
|
||||
mutations: [
|
||||
{
|
||||
index,
|
||||
syncAction: {
|
||||
value: {
|
||||
archiveChatAction: {
|
||||
archived: true,
|
||||
messageRange: {
|
||||
lastMessageTimestamp: now - 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
for (const { mutations, settings } of CASES) {
|
||||
const ctx: InitialAppStateSyncOptions = {
|
||||
recvChats: {
|
||||
[jid]: { lastMsgRecvTimestamp: now }
|
||||
},
|
||||
accountSettings: settings
|
||||
}
|
||||
const events = processSyncActions(mutations, me, ctx, logger)
|
||||
expect(events['chats.update']).toHaveLength(1)
|
||||
const event = events['chats.update']?.[0]
|
||||
expect(event.archive).toEqual(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,330 +0,0 @@
|
||||
import { proto } from '../../WAProto'
|
||||
import { Chat, WAMessageKey, WAMessageStatus, WAMessageStubType, WAMessageUpdate } from '../Types'
|
||||
import { delay, generateMessageID, makeEventBuffer, toNumber, unixTimestampSeconds } from '../Utils'
|
||||
import logger from '../Utils/logger'
|
||||
import { randomJid } from './utils'
|
||||
|
||||
describe('Event Buffer Tests', () => {
|
||||
let ev: ReturnType<typeof makeEventBuffer>
|
||||
beforeEach(() => {
|
||||
const _logger = logger.child({})
|
||||
_logger.level = 'trace'
|
||||
ev = makeEventBuffer(_logger)
|
||||
})
|
||||
|
||||
it('should buffer a chat upsert & update event', async () => {
|
||||
const chatId = randomJid()
|
||||
|
||||
const chats: Chat[] = []
|
||||
|
||||
ev.on('chats.upsert', c => chats.push(...c))
|
||||
ev.on('chats.update', () => fail('should not emit update event'))
|
||||
|
||||
ev.buffer()
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
ev.buffer()
|
||||
await delay(100)
|
||||
ev.emit('chats.upsert', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }])
|
||||
const flushed = ev.flush()
|
||||
expect(flushed).toBeFalsy()
|
||||
})(),
|
||||
(async () => {
|
||||
ev.buffer()
|
||||
await delay(200)
|
||||
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 124, unreadCount: 1 }])
|
||||
const flushed = ev.flush()
|
||||
expect(flushed).toBeFalsy()
|
||||
})()
|
||||
])
|
||||
|
||||
const flushed = ev.flush()
|
||||
expect(flushed).toBeTruthy()
|
||||
|
||||
expect(chats).toHaveLength(1)
|
||||
expect(chats[0].conversationTimestamp).toEqual(124)
|
||||
expect(chats[0].unreadCount).toEqual(2)
|
||||
})
|
||||
|
||||
it('should overwrite a chats.delete event', async () => {
|
||||
const chatId = randomJid()
|
||||
const chats: Partial<Chat>[] = []
|
||||
|
||||
ev.on('chats.update', c => chats.push(...c))
|
||||
ev.on('chats.delete', () => fail('not should have emitted'))
|
||||
|
||||
ev.buffer()
|
||||
|
||||
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }])
|
||||
ev.emit('chats.delete', [chatId])
|
||||
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 124, unreadCount: 1 }])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(chats).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should overwrite a chats.update event', async () => {
|
||||
const chatId = randomJid()
|
||||
const chatsDeleted: string[] = []
|
||||
|
||||
ev.on('chats.delete', c => chatsDeleted.push(...c))
|
||||
ev.on('chats.update', () => fail('not should have emitted'))
|
||||
|
||||
ev.buffer()
|
||||
|
||||
ev.emit('chats.update', [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }])
|
||||
ev.emit('chats.delete', [chatId])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(chatsDeleted).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should release a conditional update at the right time', async () => {
|
||||
const chatId = randomJid()
|
||||
const chatId2 = randomJid()
|
||||
const chatsUpserted: Chat[] = []
|
||||
const chatsSynced: Chat[] = []
|
||||
|
||||
ev.on('chats.upsert', c => chatsUpserted.push(...c))
|
||||
ev.on('messaging-history.set', c => chatsSynced.push(...c.chats))
|
||||
ev.on('chats.update', () => fail('not should have emitted'))
|
||||
|
||||
ev.buffer()
|
||||
ev.emit('chats.update', [
|
||||
{
|
||||
id: chatId,
|
||||
archived: true,
|
||||
conditional(buff) {
|
||||
if (buff.chatUpserts[chatId]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
ev.emit('chats.update', [
|
||||
{
|
||||
id: chatId2,
|
||||
archived: true,
|
||||
conditional(buff) {
|
||||
if (buff.historySets.chats[chatId2]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
ev.flush()
|
||||
|
||||
ev.buffer()
|
||||
ev.emit('chats.upsert', [
|
||||
{
|
||||
id: chatId,
|
||||
conversationTimestamp: 123,
|
||||
unreadCount: 1,
|
||||
muteEndTime: 123
|
||||
}
|
||||
])
|
||||
ev.emit('messaging-history.set', {
|
||||
chats: [
|
||||
{
|
||||
id: chatId2,
|
||||
conversationTimestamp: 123,
|
||||
unreadCount: 1,
|
||||
muteEndTime: 123
|
||||
}
|
||||
],
|
||||
contacts: [],
|
||||
messages: [],
|
||||
isLatest: false
|
||||
})
|
||||
ev.flush()
|
||||
|
||||
expect(chatsUpserted).toHaveLength(1)
|
||||
expect(chatsUpserted[0].id).toEqual(chatId)
|
||||
expect(chatsUpserted[0].archived).toEqual(true)
|
||||
expect(chatsUpserted[0].muteEndTime).toEqual(123)
|
||||
|
||||
expect(chatsSynced).toHaveLength(1)
|
||||
expect(chatsSynced[0].id).toEqual(chatId2)
|
||||
expect(chatsSynced[0].archived).toEqual(true)
|
||||
})
|
||||
|
||||
it('should discard a conditional update', async () => {
|
||||
const chatId = randomJid()
|
||||
const chatsUpserted: Chat[] = []
|
||||
|
||||
ev.on('chats.upsert', c => chatsUpserted.push(...c))
|
||||
ev.on('chats.update', () => fail('not should have emitted'))
|
||||
|
||||
ev.buffer()
|
||||
ev.emit('chats.update', [
|
||||
{
|
||||
id: chatId,
|
||||
archived: true,
|
||||
conditional(buff) {
|
||||
if (buff.chatUpserts[chatId]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
ev.emit('chats.upsert', [
|
||||
{
|
||||
id: chatId,
|
||||
conversationTimestamp: 123,
|
||||
unreadCount: 1,
|
||||
muteEndTime: 123
|
||||
}
|
||||
])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(chatsUpserted).toHaveLength(1)
|
||||
expect(chatsUpserted[0].archived).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should overwrite a chats.update event with a history event', async () => {
|
||||
const chatId = randomJid()
|
||||
let chatRecv: Chat | undefined
|
||||
|
||||
ev.on('messaging-history.set', ({ chats }) => {
|
||||
chatRecv = chats[0]
|
||||
})
|
||||
ev.on('chats.update', () => fail('not should have emitted'))
|
||||
|
||||
ev.buffer()
|
||||
|
||||
ev.emit('messaging-history.set', {
|
||||
chats: [{ id: chatId, conversationTimestamp: 123, unreadCount: 1 }],
|
||||
messages: [],
|
||||
contacts: [],
|
||||
isLatest: true
|
||||
})
|
||||
ev.emit('chats.update', [{ id: chatId, archived: true }])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(chatRecv).toBeDefined()
|
||||
expect(chatRecv?.archived).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should buffer message upsert events', async () => {
|
||||
const messageTimestamp = unixTimestampSeconds()
|
||||
const msg: proto.IWebMessageInfo = {
|
||||
key: {
|
||||
remoteJid: randomJid(),
|
||||
id: generateMessageID(),
|
||||
fromMe: false
|
||||
},
|
||||
messageStubType: WAMessageStubType.CIPHERTEXT,
|
||||
messageTimestamp
|
||||
}
|
||||
|
||||
const msgs: proto.IWebMessageInfo[] = []
|
||||
|
||||
ev.on('messages.upsert', c => {
|
||||
msgs.push(...c.messages)
|
||||
expect(c.type).toEqual('notify')
|
||||
})
|
||||
|
||||
ev.buffer()
|
||||
ev.emit('messages.upsert', { messages: [proto.WebMessageInfo.fromObject(msg)], type: 'notify' })
|
||||
|
||||
msg.messageTimestamp = unixTimestampSeconds() + 1
|
||||
msg.messageStubType = undefined
|
||||
msg.message = { conversation: 'Test' }
|
||||
ev.emit('messages.upsert', { messages: [proto.WebMessageInfo.fromObject(msg)], type: 'notify' })
|
||||
ev.emit('messages.update', [{ key: msg.key, update: { status: WAMessageStatus.READ } }])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(msgs).toHaveLength(1)
|
||||
expect(msgs[0].message).toBeTruthy()
|
||||
expect(toNumber(msgs[0].messageTimestamp!)).toEqual(messageTimestamp)
|
||||
expect(msgs[0].status).toEqual(WAMessageStatus.READ)
|
||||
})
|
||||
|
||||
it('should buffer a message receipt update', async () => {
|
||||
const msg: proto.IWebMessageInfo = {
|
||||
key: {
|
||||
remoteJid: randomJid(),
|
||||
id: generateMessageID(),
|
||||
fromMe: false
|
||||
},
|
||||
messageStubType: WAMessageStubType.CIPHERTEXT,
|
||||
messageTimestamp: unixTimestampSeconds()
|
||||
}
|
||||
|
||||
const msgs: proto.IWebMessageInfo[] = []
|
||||
|
||||
ev.on('messages.upsert', c => msgs.push(...c.messages))
|
||||
ev.on('message-receipt.update', () => fail('should not emit'))
|
||||
|
||||
ev.buffer()
|
||||
ev.emit('messages.upsert', { messages: [proto.WebMessageInfo.fromObject(msg)], type: 'notify' })
|
||||
ev.emit('message-receipt.update', [
|
||||
{
|
||||
key: msg.key,
|
||||
receipt: {
|
||||
userJid: randomJid(),
|
||||
readTimestamp: unixTimestampSeconds()
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(msgs).toHaveLength(1)
|
||||
expect(msgs[0].userReceipt).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should buffer multiple status updates', async () => {
|
||||
const key: WAMessageKey = {
|
||||
remoteJid: randomJid(),
|
||||
id: generateMessageID(),
|
||||
fromMe: false
|
||||
}
|
||||
|
||||
const msgs: WAMessageUpdate[] = []
|
||||
|
||||
ev.on('messages.update', c => msgs.push(...c))
|
||||
|
||||
ev.buffer()
|
||||
ev.emit('messages.update', [{ key, update: { status: WAMessageStatus.DELIVERY_ACK } }])
|
||||
ev.emit('messages.update', [{ key, update: { status: WAMessageStatus.READ } }])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(msgs).toHaveLength(1)
|
||||
expect(msgs[0].update.status).toEqual(WAMessageStatus.READ)
|
||||
})
|
||||
|
||||
it('should remove chat unread counter', async () => {
|
||||
const msg: proto.IWebMessageInfo = {
|
||||
key: {
|
||||
remoteJid: '12345@s.whatsapp.net',
|
||||
id: generateMessageID(),
|
||||
fromMe: false
|
||||
},
|
||||
message: {
|
||||
conversation: 'abcd'
|
||||
},
|
||||
messageTimestamp: unixTimestampSeconds()
|
||||
}
|
||||
|
||||
const chats: Partial<Chat>[] = []
|
||||
|
||||
ev.on('chats.update', c => chats.push(...c))
|
||||
|
||||
ev.buffer()
|
||||
ev.emit('messages.upsert', { messages: [proto.WebMessageInfo.fromObject(msg)], type: 'notify' })
|
||||
ev.emit('chats.update', [{ id: msg.key.remoteJid!, unreadCount: 1, conversationTimestamp: msg.messageTimestamp }])
|
||||
ev.emit('messages.update', [{ key: msg.key, update: { status: WAMessageStatus.READ } }])
|
||||
|
||||
ev.flush()
|
||||
|
||||
expect(chats[0].unreadCount).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -1,77 +0,0 @@
|
||||
import { addTransactionCapability, delay } from '../Utils'
|
||||
import logger from '../Utils/logger'
|
||||
import { makeMockSignalKeyStore } from './utils'
|
||||
|
||||
logger.level = 'trace'
|
||||
|
||||
describe('Key Store w Transaction Tests', () => {
|
||||
const rawStore = makeMockSignalKeyStore()
|
||||
const store = addTransactionCapability(rawStore, logger, {
|
||||
maxCommitRetries: 1,
|
||||
delayBetweenTriesMs: 10
|
||||
})
|
||||
|
||||
it('should use transaction cache when mutated', async () => {
|
||||
const key = '123'
|
||||
const value = new Uint8Array(1)
|
||||
const ogGet = rawStore.get
|
||||
await store.transaction(async () => {
|
||||
await store.set({ session: { [key]: value } })
|
||||
|
||||
rawStore.get = () => {
|
||||
throw new Error('should not have been called')
|
||||
}
|
||||
|
||||
const { [key]: stored } = await store.get('session', [key])
|
||||
expect(stored).toEqual(new Uint8Array(1))
|
||||
})
|
||||
|
||||
rawStore.get = ogGet
|
||||
})
|
||||
|
||||
it('should not commit a failed transaction', async () => {
|
||||
const key = 'abcd'
|
||||
await expect(
|
||||
store.transaction(async () => {
|
||||
await store.set({ session: { [key]: new Uint8Array(1) } })
|
||||
throw new Error('fail')
|
||||
})
|
||||
).rejects.toThrowError('fail')
|
||||
|
||||
const { [key]: stored } = await store.get('session', [key])
|
||||
expect(stored).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle overlapping transactions', async () => {
|
||||
// promise to let transaction 2
|
||||
// know that transaction 1 has started
|
||||
let promiseResolve: () => void
|
||||
const promise = new Promise<void>(resolve => {
|
||||
promiseResolve = resolve
|
||||
})
|
||||
|
||||
store.transaction(async () => {
|
||||
await store.set({
|
||||
session: {
|
||||
'1': new Uint8Array(1)
|
||||
}
|
||||
})
|
||||
// wait for the other transaction to start
|
||||
await delay(5)
|
||||
// reolve the promise to let the other transaction continue
|
||||
promiseResolve()
|
||||
})
|
||||
|
||||
await store.transaction(async () => {
|
||||
await promise
|
||||
await delay(5)
|
||||
|
||||
expect(store.isInTransaction()).toBe(true)
|
||||
})
|
||||
|
||||
expect(store.isInTransaction()).toBe(false)
|
||||
// ensure that the transaction were committed
|
||||
const { ['1']: stored } = await store.get('session', ['1'])
|
||||
expect(stored).toEqual(new Uint8Array(1))
|
||||
})
|
||||
})
|
||||
@@ -1,163 +0,0 @@
|
||||
import { makeLibSignalRepository } from '../Signal/libsignal'
|
||||
import { SignalAuthState, SignalDataTypeMap } from '../Types'
|
||||
import { Curve, generateRegistrationId, generateSignalPubKey, signedKeyPair } from '../Utils'
|
||||
|
||||
// TODO: should move to libsignal
|
||||
|
||||
describe('Signal Tests', () => {
|
||||
it('should correctly encrypt/decrypt 1 message', async () => {
|
||||
const user1 = makeUser()
|
||||
const user2 = makeUser()
|
||||
|
||||
const msg = Buffer.from('hello there!')
|
||||
|
||||
await prepareForSendingMessage(user1, user2)
|
||||
|
||||
const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg })
|
||||
|
||||
const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result })
|
||||
|
||||
expect(dec).toEqual(msg)
|
||||
})
|
||||
|
||||
it('should correctly override a session', async () => {
|
||||
const user1 = makeUser()
|
||||
const user2 = makeUser()
|
||||
|
||||
const msg = Buffer.from('hello there!')
|
||||
|
||||
for (let preKeyId = 2; preKeyId <= 3; preKeyId++) {
|
||||
await prepareForSendingMessage(user1, user2, preKeyId)
|
||||
|
||||
const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg })
|
||||
|
||||
const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result })
|
||||
|
||||
expect(dec).toEqual(msg)
|
||||
}
|
||||
})
|
||||
|
||||
it('should correctly encrypt/decrypt multiple messages', async () => {
|
||||
const user1 = makeUser()
|
||||
const user2 = makeUser()
|
||||
|
||||
const msg = Buffer.from('hello there!')
|
||||
|
||||
await prepareForSendingMessage(user1, user2)
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const result = await user1.repository.encryptMessage({ jid: user2.jid, data: msg })
|
||||
|
||||
const dec = await user2.repository.decryptMessage({ jid: user1.jid, ...result })
|
||||
|
||||
expect(dec).toEqual(msg)
|
||||
}
|
||||
})
|
||||
|
||||
it('should encrypt/decrypt messages from group', async () => {
|
||||
const groupId = '123456@g.us'
|
||||
const participants = [...Array(5)].map(makeUser)
|
||||
|
||||
const msg = Buffer.from('hello there!')
|
||||
|
||||
const sender = participants[0]
|
||||
const enc = await sender.repository.encryptGroupMessage({
|
||||
group: groupId,
|
||||
meId: sender.jid,
|
||||
data: msg
|
||||
})
|
||||
|
||||
for (const participant of participants) {
|
||||
if (participant === sender) {
|
||||
continue
|
||||
}
|
||||
|
||||
await participant.repository.processSenderKeyDistributionMessage({
|
||||
item: {
|
||||
groupId,
|
||||
axolotlSenderKeyDistributionMessage: enc.senderKeyDistributionMessage
|
||||
},
|
||||
authorJid: sender.jid
|
||||
})
|
||||
|
||||
const dec = await participant.repository.decryptGroupMessage({
|
||||
group: groupId,
|
||||
authorJid: sender.jid,
|
||||
msg: enc.ciphertext
|
||||
})
|
||||
expect(dec).toEqual(msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
type User = ReturnType<typeof makeUser>
|
||||
|
||||
function makeUser() {
|
||||
const store = makeTestAuthState()
|
||||
const jid = `${Math.random().toString().replace('.', '')}@s.whatsapp.net`
|
||||
const repository = makeLibSignalRepository(store)
|
||||
return { store, jid, repository }
|
||||
}
|
||||
|
||||
async function prepareForSendingMessage(sender: User, receiver: User, preKeyId = 2) {
|
||||
const preKey = Curve.generateKeyPair()
|
||||
await sender.repository.injectE2ESession({
|
||||
jid: receiver.jid,
|
||||
session: {
|
||||
registrationId: receiver.store.creds.registrationId,
|
||||
identityKey: generateSignalPubKey(receiver.store.creds.signedIdentityKey.public),
|
||||
signedPreKey: {
|
||||
keyId: receiver.store.creds.signedPreKey.keyId,
|
||||
publicKey: generateSignalPubKey(receiver.store.creds.signedPreKey.keyPair.public),
|
||||
signature: receiver.store.creds.signedPreKey.signature
|
||||
},
|
||||
preKey: {
|
||||
keyId: preKeyId,
|
||||
publicKey: generateSignalPubKey(preKey.public)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await receiver.store.keys.set({
|
||||
'pre-key': {
|
||||
[preKeyId]: preKey
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeTestAuthState(): SignalAuthState {
|
||||
const identityKey = Curve.generateKeyPair()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const store: { [_: string]: any } = {}
|
||||
return {
|
||||
creds: {
|
||||
signedIdentityKey: identityKey,
|
||||
registrationId: generateRegistrationId(),
|
||||
signedPreKey: signedKeyPair(identityKey, 1)
|
||||
},
|
||||
keys: {
|
||||
get(type, ids) {
|
||||
const data: { [_: string]: SignalDataTypeMap[typeof type] } = {}
|
||||
for (const id of ids) {
|
||||
const item = store[getUniqueId(type, id)]
|
||||
if (typeof item !== 'undefined') {
|
||||
data[id] = item
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
},
|
||||
set(data) {
|
||||
for (const type in data) {
|
||||
for (const id in data[type]) {
|
||||
store[getUniqueId(type, id)] = data[type][id]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUniqueId(type: string, id: string) {
|
||||
return `${type}.${id}`
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { readFileSync } from 'fs'
|
||||
import { proto } from '../../WAProto'
|
||||
import { DownloadableMessage, MediaType } from '../Types'
|
||||
import { downloadContentFromMessage } from '../Utils'
|
||||
|
||||
jest.setTimeout(20_000)
|
||||
|
||||
type TestVector = {
|
||||
type: MediaType
|
||||
message: DownloadableMessage
|
||||
plaintext: Buffer
|
||||
}
|
||||
|
||||
const TEST_VECTORS: TestVector[] = [
|
||||
{
|
||||
type: 'image',
|
||||
message: proto.Message.ImageMessage.decode(
|
||||
Buffer.from(
|
||||
'Ck1odHRwczovL21tZy53aGF0c2FwcC5uZXQvZC9mL0FwaHR4WG9fWXZZcDZlUVNSa0tjOHE5d2ozVUpleWdoY3poM3ExX3I0ektnLmVuYxIKaW1hZ2UvanBlZyIgKTuVFyxDc6mTm4GXPlO3Z911Wd8RBeTrPLSWAEdqW8MomcUBQiB7wH5a4nXMKyLOT0A2nFgnnM/DUH8YjQf8QtkCIekaSkogTB+BXKCWDFrmNzozY0DCPn0L4VKd7yG1ZbZwbgRhzVc=',
|
||||
'base64'
|
||||
)
|
||||
),
|
||||
plaintext: readFileSync('./Media/cat.jpeg')
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
message: proto.Message.ImageMessage.decode(
|
||||
Buffer.from(
|
||||
'Ck1odHRwczovL21tZy53aGF0c2FwcC5uZXQvZC9mL0Ftb2tnWkphNWF6QWZxa3dVRzc0eUNUdTlGeWpjMmd5akpqcXNmMUFpZEU5LmVuYxIKaW1hZ2UvanBlZyIg8IS5TQzdzcuvcR7F8HMhWnXmlsV+GOo9JE1/t2k+o9Yoz6o6QiA7kDk8j5KOEQC0kDFE1qW7lBBDYhm5z06N3SirfUj3CUog/CjYF8e670D5wUJwWv2B2mKzDEo8IJLStDv76YmtPfs=',
|
||||
'base64'
|
||||
)
|
||||
),
|
||||
plaintext: readFileSync('./Media/icon.png')
|
||||
}
|
||||
]
|
||||
|
||||
describe('Media Download Tests', () => {
|
||||
it('should download a full encrypted media correctly', async () => {
|
||||
for (const { type, message, plaintext } of TEST_VECTORS) {
|
||||
const readPipe = await downloadContentFromMessage(message, type)
|
||||
|
||||
let buffer = Buffer.alloc(0)
|
||||
for await (const read of readPipe) {
|
||||
buffer = Buffer.concat([buffer, read])
|
||||
}
|
||||
|
||||
expect(buffer).toEqual(plaintext)
|
||||
}
|
||||
})
|
||||
|
||||
it('should download an encrypted media correctly piece', async () => {
|
||||
for (const { type, message, plaintext } of TEST_VECTORS) {
|
||||
// check all edge cases
|
||||
const ranges = [
|
||||
{ startByte: 51, endByte: plaintext.length - 100 }, // random numbers
|
||||
{ startByte: 1024, endByte: 2038 }, // larger random multiples of 16
|
||||
{ startByte: 1, endByte: plaintext.length - 1 } // borders
|
||||
]
|
||||
for (const range of ranges) {
|
||||
const readPipe = await downloadContentFromMessage(message, type, range)
|
||||
|
||||
let buffer = Buffer.alloc(0)
|
||||
for await (const read of readPipe) {
|
||||
buffer = Buffer.concat([buffer, read])
|
||||
}
|
||||
|
||||
const hex = buffer.toString('hex')
|
||||
const expectedHex = plaintext.slice(range.startByte || 0, range.endByte || undefined).toString('hex')
|
||||
expect(hex).toBe(expectedHex)
|
||||
|
||||
console.log('success on ', range)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
import { WAMessageContent } from '../Types'
|
||||
import { normalizeMessageContent } from '../Utils'
|
||||
|
||||
describe('Messages Tests', () => {
|
||||
it('should correctly unwrap messages', () => {
|
||||
const CONTENT = { imageMessage: {} }
|
||||
expectRightContent(CONTENT)
|
||||
expectRightContent({
|
||||
ephemeralMessage: { message: CONTENT }
|
||||
})
|
||||
expectRightContent({
|
||||
viewOnceMessage: {
|
||||
message: {
|
||||
ephemeralMessage: { message: CONTENT }
|
||||
}
|
||||
}
|
||||
})
|
||||
expectRightContent({
|
||||
viewOnceMessage: {
|
||||
message: {
|
||||
viewOnceMessageV2: {
|
||||
message: {
|
||||
ephemeralMessage: { message: CONTENT }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function expectRightContent(content: WAMessageContent) {
|
||||
expect(normalizeMessageContent(content)).toHaveProperty('imageMessage')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
import { jidEncode } from '../WABinary'
|
||||
|
||||
export function randomJid() {
|
||||
return jidEncode(Math.floor(Math.random() * 1000000), Math.random() < 0.5 ? 's.whatsapp.net' : 'g.us')
|
||||
}
|
||||
|
||||
export function makeMockSignalKeyStore(): SignalKeyStore {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const store: { [_: string]: any } = {}
|
||||
|
||||
return {
|
||||
get(type, ids) {
|
||||
const data: { [_: string]: SignalDataTypeMap[typeof type] } = {}
|
||||
for (const id of ids) {
|
||||
const item = store[getUniqueId(type, id)]
|
||||
if (typeof item !== 'undefined') {
|
||||
data[id] = item
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
},
|
||||
set(data) {
|
||||
for (const type in data) {
|
||||
for (const id in data[type]) {
|
||||
store[getUniqueId(type, id)] = data[type][id]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUniqueId(type: string, id: string) {
|
||||
return `${type}.${id}`
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export interface GroupMetadata {
|
||||
addressingMode: 'pn' | 'lid'
|
||||
owner: string | undefined
|
||||
ownerJid?: string | undefined
|
||||
owner_country_code: string
|
||||
owner_country_code?: string | undefined
|
||||
subject: string
|
||||
/** group subject owner */
|
||||
subjectOwner?: string
|
||||
|
||||
@@ -32,6 +32,7 @@ import type { ILogger } from './logger'
|
||||
const getTmpFilesDirectory = () => tmpdir()
|
||||
|
||||
const getImageProcessingLibrary = async () => {
|
||||
//@ts-ignore
|
||||
const [jimp, sharp] = await Promise.all([import('jimp').catch(() => {}), import('sharp').catch(() => {})])
|
||||
|
||||
if (sharp) {
|
||||
@@ -152,7 +153,7 @@ export const extractImageThumb = async (bufferOrFilePath: Readable | Buffer | st
|
||||
}
|
||||
}
|
||||
} else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
|
||||
const jimp = await lib.jimp.default.Jimp.read(bufferOrFilePath)
|
||||
const jimp = await (lib.jimp.Jimp as any).read(bufferOrFilePath)
|
||||
const dimensions = {
|
||||
width: jimp.width,
|
||||
height: jimp.height
|
||||
@@ -200,7 +201,7 @@ export const generateProfilePicture = async (
|
||||
})
|
||||
.toBuffer()
|
||||
} else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
|
||||
const jimp = await lib.jimp.default.Jimp.read(buffer)
|
||||
const jimp = await (lib.jimp.Jimp as any).read(buffer)
|
||||
const min = Math.min(jimp.width, jimp.height)
|
||||
const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min })
|
||||
|
||||
@@ -242,7 +243,8 @@ export async function getAudioDuration(buffer: Buffer | string | Readable) {
|
||||
*/
|
||||
export async function getAudioWaveform(buffer: Buffer | string | Readable, logger?: ILogger) {
|
||||
try {
|
||||
const { default: decoder } = await eval("import('audio-decode')")
|
||||
// @ts-ignore
|
||||
const { default: decoder } = await import('audio-decode')
|
||||
let audioData: Buffer
|
||||
if (Buffer.isBuffer(buffer)) {
|
||||
audioData = buffer
|
||||
@@ -311,7 +313,7 @@ export const getStream = async (item: WAMediaUpload, opts?: AxiosRequestConfig)
|
||||
const urlStr = item.url.toString()
|
||||
|
||||
if (urlStr.startsWith('data:')) {
|
||||
const buffer = Buffer.from(urlStr.split(',')[1], 'base64')
|
||||
const buffer = Buffer.from(urlStr.split(',')[1]!, 'base64')
|
||||
return { stream: toReadable(buffer), type: 'buffer' } as const
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
generateThumbnail,
|
||||
getAudioDuration,
|
||||
getAudioWaveform,
|
||||
getRawMediaUploadData,
|
||||
getRawMediaUploadData,
|
||||
type MediaDownloadOptions
|
||||
} from './messages-media'
|
||||
|
||||
@@ -177,7 +177,8 @@ export const prepareWAMessageMedia = async (
|
||||
await fs.unlink(filePath)
|
||||
|
||||
const obj = WAProto.Message.fromObject({
|
||||
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
|
||||
// todo: add more support here
|
||||
[`${mediaType}Message`]: (MessageTypeProto as any)[mediaType].fromObject({
|
||||
url: mediaUrl,
|
||||
directPath,
|
||||
fileSha256,
|
||||
|
||||
Reference in New Issue
Block a user