fix: eliminate 40s message delivery delay caused by libsignal console dumps

fix: eliminate 40s message delivery delay caused by libsignal console dumps
This commit is contained in:
Renato Alcara
2026-02-27 08:43:54 -03:00
committed by GitHub
parent efc927728b
commit d233a7856f
18 changed files with 359 additions and 277 deletions
@@ -25,7 +25,7 @@ interface MockMessageRetryManager {
/** Mirrors jidNormalizedUser: strips device suffix from JID user part */
function jidNormalizedUser(jid: string): string {
const atIdx = jid.indexOf('@')
if(atIdx < 0) return jid
if (atIdx < 0) return jid
const user = jid.slice(0, atIdx)
const server = jid.slice(atIdx + 1)
const normalizedUser = user.includes(':') ? user.split(':')[0] : user
@@ -46,9 +46,9 @@ async function handleBadAck463(
const jid = jidNormalizedUser(attrs.from)
const key: MockKey = { remoteJid: attrs.from, fromMe: true, id: msgId }
if(attrs.error === '463') {
if (attrs.error === '463') {
const retryKey = `${jid}:${msgId}`
if(msgId && jid && !tcTokenRetriedMsgIds.has(retryKey)) {
if (msgId && jid && !tcTokenRetriedMsgIds.has(retryKey)) {
tcTokenRetriedMsgIds.add(retryKey)
// Each entry auto-expires after 60s — naturally bounded under normal use
setTimeout(() => tcTokenRetriedMsgIds.delete(retryKey), 60_000)
@@ -57,7 +57,7 @@ async function handleBadAck463(
(await getMessage(key)) ??
// Fallback: ack can arrive <30ms after send, before store persists
messageRetryManager?.getRecentMessage(jid, msgId)?.message
if(msg) {
if (msg) {
try {
await delayFn(1500)
await relayMessage(jid, msg, {
@@ -190,7 +190,7 @@ describe('handleBadAck error 463 retry', () => {
})
it('should not retry for non-463 errors', async () => {
for(const errorCode of ['479', '421']) {
for (const errorCode of ['479', '421']) {
mockGetMessage.mockClear()
mockRelayMessage.mockClear()
mockEmit.mockClear()
@@ -274,7 +274,9 @@ describe('handleBadAck error 463 retry', () => {
it('should fall back to messageRetryManager when getMessage returns undefined', async () => {
const cachedMsg = { conversation: 'cached' }
const mockRetryManager: MockMessageRetryManager = {
getRecentMessage: jest.fn<(jid: string, msgId: string) => { message: any } | undefined>().mockReturnValue({ message: cachedMsg })
getRecentMessage: jest
.fn<(jid: string, msgId: string) => { message: any } | undefined>()
.mockReturnValue({ message: cachedMsg })
}
mockGetMessage.mockResolvedValue(undefined)
mockRelayMessage.mockResolvedValue(undefined)
@@ -36,15 +36,11 @@ function makeState(): OfflineBufferState {
* Mirrors the process.nextTick block that arms the offline-buffer timer.
* Only called when creds.me?.id is set (reconnection path).
*/
function startBuffer(
state: OfflineBufferState,
flush: () => void,
warn: () => void
): void {
function startBuffer(state: OfflineBufferState, flush: () => void, warn: () => void): void {
state.didStartBuffer = true
state.offlineBufferTimeout = setTimeout(() => {
state.offlineBufferTimeout = undefined
if(state.didStartBuffer) {
if (state.didStartBuffer) {
warn()
flush()
state.didStartBuffer = false
@@ -57,12 +53,12 @@ function startBuffer(
* delivers all offline notifications before the safety timer fires.
*/
function onOffline(state: OfflineBufferState, flush: () => void): void {
if(state.offlineBufferTimeout) {
if (state.offlineBufferTimeout) {
clearTimeout(state.offlineBufferTimeout)
state.offlineBufferTimeout = undefined
}
if(state.didStartBuffer) {
if (state.didStartBuffer) {
flush()
state.didStartBuffer = false
}
@@ -73,7 +69,7 @@ function onOffline(state: OfflineBufferState, flush: () => void): void {
* flag so a closing socket cannot emit stale events after the fact.
*/
function onClose(state: OfflineBufferState): void {
if(state.offlineBufferTimeout) {
if (state.offlineBufferTimeout) {
clearTimeout(state.offlineBufferTimeout)
state.offlineBufferTimeout = undefined
}
@@ -97,7 +93,7 @@ describe('offline-buffer safety timer (socket.ts)', () => {
afterEach(() => {
// Clean up any remaining timer to avoid cross-test interference
if(state.offlineBufferTimeout) {
if (state.offlineBufferTimeout) {
clearTimeout(state.offlineBufferTimeout)
}