Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions src/Utils/process-history-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { proto } from '../WAProto/runtime.ts'
import type { Chat, Contact, LIDMapping, WAMessage } from '../Types/index.ts'
import { WAProto } from '../Types/index.ts'
import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser } from '../WABinary/jid-utils.ts'
import { Boom } from './boom.ts'
import { toNumber } from './generics.ts'
import type { ILogger } from './logger.ts'
import { downloadContentFromMessage, normalizeMessageContent } from './messages.ts'
Expand Down Expand Up @@ -234,12 +233,17 @@ export const downloadAndProcessHistorySyncNotification = async (
return processHistoryMessage(historyMsg, logger)
}

/** Extract a history-sync notification through the same wrapper normalization as upstream. */
export const getHistoryMsg = (message: proto.IMessage): proto.Message.IHistorySyncNotification => {
/**
* Extract a history-sync notification through the same wrapper normalization as
* upstream.
*
* Returns `undefined` when the message carries none. It used to throw a Boom
* 400, which breaks the shape every caller writes against a drop-in API —
* `const h = getHistoryMsg(msg); if (!h) return` crashed instead of returning.
* "Absent" is the ordinary case here, not an error: any message that is not a
* history sync takes this path.
*/
export const getHistoryMsg = (message: proto.IMessage): proto.Message.IHistorySyncNotification | undefined => {
const normalizedContent = message ? normalizeMessageContent(message) : undefined
const historySyncNotification = normalizedContent?.protocolMessage?.historySyncNotification
if (!historySyncNotification) {
throw new Boom('Message does not contain a history sync notification', { statusCode: 400 })
}
return historySyncNotification
return normalizedContent?.protocolMessage?.historySyncNotification ?? undefined
}
19 changes: 0 additions & 19 deletions src/__fuzz__/harness/divergence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,25 +959,6 @@ export const KNOWN_DIVERGENCES: readonly KnownDivergence[] = [
'The two decoders disagree about which malformed `<message>` payloads are readable, in both directions. Upstream throws "illegal buffer" where baileyrs returns an empty message object; and for a truncated length prefix (`2a 16` with no body) baileyrs throws RangeError "premature EOF" where upstream returns `[{ participant: "" }]`. Whichever way round, a corrupt stanza becomes an empty message on one side and an exception on the other, so a caller cannot write one handler that works against both. Needs a maintainer call on which contract the stanza handlers should rely on.',
review: '2026-11-01'
},
{
id: 'get-history-msg-throws-instead-of-undefined',
target: 'pure:getHistoryMsg',
status: 'open',
// The generator now emits valid history-sync notifications, so the entry has
// to say which half of that it covers: only the *missing* one. Read
// structurally rather than by searching the serialised input — a
// notification nested under `deviceSentMessage` appears in the text but is
// not where either helper looks, and both correctly ignore it.
when: divergence => {
if (!isThrow(divergence.local) || divergence.upstream !== undefined) return false
const message = Array.isArray(divergence.input) ? divergence.input[0] : undefined
const protocol = (message as { protocolMessage?: Record<string, unknown> } | undefined)?.protocolMessage
return protocol?.historySyncNotification === undefined
},
reason:
'Upstream returns `undefined` when the message carries no history-sync notification; baileyrs throws a Boom 400. Drop-in consumer code written as `const h = getHistoryMsg(msg); if (!h) return` therefore crashes against baileyrs. The fix is a signature change on a published API, so it belongs in its own commit rather than in the change that found it.',
review: '2026-11-01'
},
{
id: 'clean-message-empty-jid-normalisation',
target: /^pure:cleanMessage/u,
Expand Down
6 changes: 5 additions & 1 deletion src/__tests__/public-helpers-compatibility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,11 @@ describe('public helper compatibility', () => {
it('extracts wrapped history notifications', () => {
const notification = { syncType: proto.HistorySync.HistorySyncType.RECENT }
assert.deepEqual(getHistoryMsg({ protocolMessage: { historySyncNotification: notification } }), notification)
assert.throws(() => getHistoryMsg({ conversation: 'not a history sync' }), /history sync notification/)
// Upstream returns undefined here. Throwing broke `const h = getHistoryMsg(m); if (!h) return`,
// which is the shape a drop-in caller writes.
assert.equal(getHistoryMsg({ conversation: 'not a history sync' }), undefined)
assert.equal(getHistoryMsg({}), undefined)
assert.equal(getHistoryMsg(undefined as never), undefined)
})

it('matches message-device inference and bulk receipt grouping', () => {
Expand Down
Loading