perf: eliminates a delay in message delivery between phone→app + cache device-list

perf: eliminates a delay in message delivery between phone→app + cache device-list
This commit is contained in:
Renato Alcara
2026-02-24 07:40:18 -03:00
committed by GitHub
parent d34d3d8791
commit f906eca124
4 changed files with 52 additions and 7 deletions
+2 -2
View File
@@ -62,10 +62,10 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
browser: Browsers.macOS('Chrome'),
waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat',
connectTimeoutMs: 20_000,
keepAliveIntervalMs: 30_000,
keepAliveIntervalMs: 15_000,
logger: logger.child({ class: 'baileys' }),
emitOwnEvents: true,
defaultQueryTimeoutMs: 60_000,
defaultQueryTimeoutMs: 30_000,
customUploadHosts: [],
retryRequestDelayMs: 250,
maxMsgRetryCount: 5,
+25 -2
View File
@@ -286,6 +286,17 @@ export function makeLibSignalRepository(
updateAgeOnGet: true
})
// Cache for device-list DB reads in migrateSession.
// The device list changes rarely (new linked device), so a 5-minute TTL avoids
// a DB round-trip on every incoming LID message without risking stale state:
// new devices are picked up at most 5 minutes late (still migrated via their own
// decrypt transaction when the session is first used).
const deviceListCache = new LRUCache<string, string[]>({
max: 500,
ttl: 5 * 60 * 1000, // 5 minutes
ttlAutopurge: true
})
const repository: SignalRepositoryWithLIDStore = {
decryptGroupMessage({ group, authorJid, msg }) {
const senderName = jidToSignalSenderKeyName(group, authorJid)
@@ -493,12 +504,24 @@ export function makeLibSignalRepository(
logger.debug({ fromJid }, 'bulk device migration - loading all user devices')
// Get user's device list from storage
const { [user]: userDevices } = await parsedKeys.get('device-list', [user])
// Get user's device list — use in-memory cache to avoid a DB round-trip on
// every incoming LID message. Cache is invalidated after 5 minutes.
let userDevices: string[] | undefined = deviceListCache.get(user)
if (!userDevices) {
const result = await parsedKeys.get('device-list', [user])
userDevices = result[user]
if (userDevices) {
deviceListCache.set(user, userDevices)
}
}
if (!userDevices) {
return { migrated: 0, skipped: 0, total: 0 }
}
// Work on a copy so we don't mutate the cached array
userDevices = [...userDevices]
const { device: fromDevice } = decoded1
const fromDeviceStr = fromDevice?.toString() || '0'
if (!userDevices.includes(fromDeviceStr)) {
+22
View File
@@ -1330,6 +1330,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
if (syncState === SyncState.Syncing) {
// All collections will be synced, so clear any blocked ones
blockedCollections.clear()
logger.info('Doing app state sync')
await resyncAppState(ALL_WA_PATCH_NAMES, true)
@@ -1430,6 +1431,27 @@ export const makeChatsSocket = (config: SocketConfig) => {
logger.info('Connection is now AwaitingInitialSync, buffering events')
ev.buffer()
// On reconnections (accountSyncCounter > 0), app state was already synced in a previous
// session. Skip the 20s AwaitingInitialSync wait and go directly to Online so that
// live incoming messages are not held in the buffer for up to 20-60 seconds.
// History messages that arrive later are still processed via processMessage regardless
// of the state machine phase (see the Syncing → Online path below).
const isReconnection = (authState.creds.accountSyncCounter ?? 0) > 0
if (isReconnection) {
logger.info('Reconnection detected (accountSyncCounter > 0), skipping AwaitingInitialSync wait. Transitioning to Online immediately.')
blockedCollections.clear()
syncState = SyncState.Online
const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1
ev.emit('creds.update', { accountSyncCounter })
// Fire-and-forget: pick up patches missed during downtime (mute/archive/pin/read state).
// Runs in background so live incoming messages are not blocked.
resyncAppState(ALL_WA_PATCH_NAMES, true).catch(err =>
logger.warn({ err }, 'Background app state resync failed (non-critical on reconnection)')
)
setTimeout(() => ev.flush(), 0)
return
}
const willSyncHistory = shouldSyncHistoryMessage(
proto.Message.HistorySyncNotification.create({
syncType: proto.HistorySync.HistorySyncType.RECENT
+3 -3
View File
@@ -54,9 +54,9 @@ export interface BufferConfig {
*/
export function loadBufferConfig(): BufferConfig {
return {
bufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_TIMEOUT_MS || '30000', 10),
minBufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_MIN_TIMEOUT_MS || '5000', 10),
maxBufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_MAX_TIMEOUT_MS || '60000', 10),
bufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_TIMEOUT_MS || '15000', 10),
minBufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_MIN_TIMEOUT_MS || '3000', 10),
maxBufferTimeoutMs: parseInt(process.env.BAILEYS_BUFFER_MAX_TIMEOUT_MS || '20000', 10),
maxHistoryCacheSize: parseInt(process.env.BAILEYS_BUFFER_MAX_HISTORY_CACHE || '10000', 10),
maxBufferSize: parseInt(process.env.BAILEYS_BUFFER_MAX_SIZE || '5000', 10),
flushDebounceMs: parseInt(process.env.BAILEYS_BUFFER_FLUSH_DEBOUNCE_MS || '100', 10),