fix(whatsapp): unify contact identity and reduce realtime latency
This commit is contained in:
@@ -125,7 +125,7 @@ async function saveWebhookDeliveries() {
|
||||
const sorted = [...webhookDeliveries.values()]
|
||||
.sort((a, b) => Date.parse(b.createdAt || '') - Date.parse(a.createdAt || ''))
|
||||
const active = sorted.filter((item) => !['delivered', 'dead'].includes(item.status))
|
||||
const terminal = sorted.filter((item) => ['delivered', 'dead'].includes(item.status)).slice(0, 10_000)
|
||||
const terminal = sorted.filter((item) => ['delivered', 'dead'].includes(item.status)).slice(0, 2_000)
|
||||
const values = [...active, ...terminal]
|
||||
const keep = new Set(values.map((item) => item.id))
|
||||
for (const id of webhookDeliveries.keys()) {
|
||||
@@ -134,11 +134,20 @@ async function saveWebhookDeliveries() {
|
||||
await atomicJson(webhookDeliveriesFile, values)
|
||||
}
|
||||
|
||||
let webhookDeliverySaveChain = Promise.resolve()
|
||||
function queueWebhookDeliveriesSave() {
|
||||
webhookDeliverySaveChain = webhookDeliverySaveChain
|
||||
.catch(() => undefined)
|
||||
.then(() => saveWebhookDeliveries())
|
||||
.catch((error) => log.error({ err: error }, 'webhook delivery persistence failed'))
|
||||
return webhookDeliverySaveChain
|
||||
}
|
||||
|
||||
async function saveOutboundJobs() {
|
||||
const sorted = [...outboundJobs.values()]
|
||||
.sort((a, b) => Date.parse(b.createdAt || '') - Date.parse(a.createdAt || ''))
|
||||
const active = sorted.filter((item) => !['sent', 'dead'].includes(item.status))
|
||||
const terminal = sorted.filter((item) => ['sent', 'dead'].includes(item.status)).slice(0, 10_000)
|
||||
const terminal = sorted.filter((item) => ['sent', 'dead'].includes(item.status)).slice(0, 2_000)
|
||||
const values = [...active, ...terminal]
|
||||
const keep = new Set(values.map((item) => item.id))
|
||||
for (const id of outboundJobs.keys()) {
|
||||
@@ -147,6 +156,15 @@ async function saveOutboundJobs() {
|
||||
await atomicJson(outboundJobsFile, values)
|
||||
}
|
||||
|
||||
let outboundJobsSaveChain = Promise.resolve()
|
||||
function queueOutboundJobsSave() {
|
||||
outboundJobsSaveChain = outboundJobsSaveChain
|
||||
.catch(() => undefined)
|
||||
.then(() => saveOutboundJobs())
|
||||
.catch((error) => log.error({ err: error }, 'outbound job persistence failed'))
|
||||
return outboundJobsSaveChain
|
||||
}
|
||||
|
||||
async function saveAccounts() {
|
||||
const values = [...accounts.values()].map(({ qrDataUrl, pairingCode, ...item }) => item)
|
||||
await atomicJson(accountsFile, values)
|
||||
@@ -484,7 +502,7 @@ async function processWebhookDelivery(deliveryId) {
|
||||
delivery.status = 'delivering'
|
||||
delivery.attempts = Number(delivery.attempts || 0) + 1
|
||||
delivery.updatedAt = new Date().toISOString()
|
||||
await saveWebhookDeliveries()
|
||||
void queueWebhookDeliveriesSave()
|
||||
try {
|
||||
const signature = crypto.createHmac('sha256', webhookSecret)
|
||||
.update(delivery.payload)
|
||||
@@ -523,13 +541,19 @@ async function processWebhookDelivery(deliveryId) {
|
||||
} finally {
|
||||
delivery.updatedAt = new Date().toISOString()
|
||||
webhookDeliveriesProcessing.delete(deliveryId)
|
||||
await saveWebhookDeliveries()
|
||||
await queueWebhookDeliveriesSave()
|
||||
}
|
||||
}
|
||||
|
||||
async function deliverWebhook(account, event, data) {
|
||||
if (!account.webhookUrl || !account.webhookActive || !accountWebhookSecret(account)) return
|
||||
const id = crypto.randomUUID()
|
||||
const messageId = ['message.received', 'message.sent', 'message.edited'].includes(event)
|
||||
? String(data?.message?.id || '')
|
||||
: ''
|
||||
const id = messageId
|
||||
? `wa-${crypto.createHash('sha256').update(`${account.id}:${event}:${messageId}`).digest('hex').slice(0, 32)}`
|
||||
: crypto.randomUUID()
|
||||
if (webhookDeliveries.has(id)) return
|
||||
const payload = JSON.stringify({
|
||||
id,
|
||||
event,
|
||||
@@ -547,8 +571,8 @@ async function deliverWebhook(account, event, data) {
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
await saveWebhookDeliveries()
|
||||
scheduleWebhookDelivery(id)
|
||||
await queueWebhookDeliveriesSave()
|
||||
}
|
||||
|
||||
function queuedOutboundMessage(job) {
|
||||
@@ -611,7 +635,7 @@ async function processOutboundJob(jobId) {
|
||||
job.status = 'queued'
|
||||
job.nextAttemptAt = new Date(Date.now() + 5_000).toISOString()
|
||||
job.updatedAt = new Date().toISOString()
|
||||
await saveOutboundJobs()
|
||||
await queueOutboundJobsSave()
|
||||
scheduleOutboundJob(job.id, 5_000)
|
||||
return queuedOutboundMessage(job)
|
||||
}
|
||||
@@ -621,7 +645,7 @@ async function processOutboundJob(jobId) {
|
||||
job.throttled = true
|
||||
job.nextAttemptAt = new Date(Date.now() + throttleDelay).toISOString()
|
||||
job.updatedAt = new Date().toISOString()
|
||||
await saveOutboundJobs()
|
||||
await queueOutboundJobsSave()
|
||||
scheduleOutboundJob(job.id, throttleDelay)
|
||||
return queuedOutboundMessage(job)
|
||||
}
|
||||
@@ -629,7 +653,7 @@ async function processOutboundJob(jobId) {
|
||||
job.status = 'sending'
|
||||
job.attempts = Number(job.attempts || 0) + 1
|
||||
job.updatedAt = new Date().toISOString()
|
||||
await saveOutboundJobs()
|
||||
void queueOutboundJobsSave()
|
||||
try {
|
||||
const store = await loadStore(job.accountId)
|
||||
const quoted = job.quotedMessageId
|
||||
@@ -713,7 +737,7 @@ async function processOutboundJob(jobId) {
|
||||
} finally {
|
||||
job.updatedAt = new Date().toISOString()
|
||||
outboundJobsProcessing.delete(job.id)
|
||||
await saveOutboundJobs()
|
||||
await queueOutboundJobsSave()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1415,7 +1439,7 @@ app.post('/accounts/:id/backup/restore', async (req, res) => {
|
||||
if (delivery.accountId === account.id) webhookDeliveries.delete(id)
|
||||
}
|
||||
for (const delivery of snapshot.webhookDeliveries || []) webhookDeliveries.set(delivery.id, delivery)
|
||||
await Promise.all([saveAccounts(), saveOutboundJobs(), saveWebhookDeliveries()])
|
||||
await Promise.all([saveAccounts(), queueOutboundJobsSave(), queueWebhookDeliveriesSave()])
|
||||
await connectAccount(account.id)
|
||||
res.json({ code: 0, data: { restored: true, account: accountPublic(account) } })
|
||||
})
|
||||
@@ -1575,12 +1599,16 @@ app.get('/accounts/:id/chats/:jid/identity', async (req, res) => {
|
||||
const group = jid.endsWith('@g.us')
|
||||
? await sock.groupMetadata(jid).catch(() => store.groups.get(jid))
|
||||
: null
|
||||
const avatarUrl = await sock.profilePictureUrl(jid, 'image')
|
||||
.catch(() => phoneJid !== jid ? sock.profilePictureUrl(phoneJid, 'image').catch(() => '') : '')
|
||||
const includeProfile = req.query.includeProfile !== 'false'
|
||||
const avatarUrl = includeProfile
|
||||
? await sock.profilePictureUrl(jid, 'image')
|
||||
.catch(() => phoneJid !== jid ? sock.profilePictureUrl(phoneJid, 'image').catch(() => '') : '')
|
||||
: ''
|
||||
res.json({
|
||||
code: 0,
|
||||
data: {
|
||||
id: jid,
|
||||
canonicalJid: phoneJid.endsWith('@s.whatsapp.net') ? phoneJid : jid,
|
||||
name: group?.subject || contact.name || contact.notify || contact.verifiedName || jid.split('@')[0],
|
||||
phone: phoneJid.endsWith('@s.whatsapp.net') ? phoneJid.split('@')[0] : '',
|
||||
avatarUrl,
|
||||
@@ -1663,7 +1691,7 @@ app.post('/accounts/:id/messages/send', async (req, res) => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
outboundJobs.set(job.id, job)
|
||||
await saveOutboundJobs()
|
||||
await queueOutboundJobsSave()
|
||||
const result = await processOutboundJob(job.id)
|
||||
res.json({ code: 0, data: result || queuedOutboundMessage(job) })
|
||||
})
|
||||
@@ -1776,7 +1804,7 @@ app.post('/accounts/:id/outbound-jobs/:jobId/retry', async (req, res) => {
|
||||
delete job.deadAt
|
||||
delete job.lastError
|
||||
delete job.nextAttemptAt
|
||||
await saveOutboundJobs()
|
||||
await queueOutboundJobsSave()
|
||||
const result = await processOutboundJob(job.id)
|
||||
res.json({ code: 0, data: result || queuedOutboundMessage(job) })
|
||||
})
|
||||
@@ -1835,7 +1863,7 @@ app.post('/accounts/:id/messages/media', upload.single('file'), async (req, res)
|
||||
job.filePath = path.join(outboundMediaDir, `${job.id}.bin`)
|
||||
await fs.writeFile(job.filePath, req.file.buffer, { mode: 0o600 })
|
||||
outboundJobs.set(job.id, job)
|
||||
await saveOutboundJobs()
|
||||
await queueOutboundJobsSave()
|
||||
const result = await processOutboundJob(job.id)
|
||||
res.json({ code: 0, data: result || queuedOutboundMessage(job) })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user