diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index fba67e1a..89c46805 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -153,14 +153,15 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { // ======= tctoken index tracking for cross-session pruning ======= const TC_TOKEN_INDEX_KEY = '__index' + const TC_TOKEN_PRUNE_TS_KEY = '__prune_ts' const tcTokenKnownJids = new Set() let tcTokenIndexSaveTimer: ReturnType | undefined let lastTcTokenPruneTs = 0 - // Load persisted JID index on startup + // Load persisted JID index and last prune timestamp on startup const tcTokenIndexLoaded = (async () => { try { - const data = await authState.keys.get('tctoken', [TC_TOKEN_INDEX_KEY]) + const data = await authState.keys.get('tctoken', [TC_TOKEN_INDEX_KEY, TC_TOKEN_PRUNE_TS_KEY]) const entry = data[TC_TOKEN_INDEX_KEY] if(entry?.token) { const stored = JSON.parse(Buffer.from(entry.token).toString('utf8')) @@ -168,6 +169,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { for(const jid of stored) tcTokenKnownJids.add(jid) } } + + const pruneEntry = data[TC_TOKEN_PRUNE_TS_KEY] + if(pruneEntry?.timestamp) { + lastTcTokenPruneTs = Number(pruneEntry.timestamp) + } } catch { /* first run or corrupt index — start fresh */ } })() @@ -197,19 +203,21 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const pruneSet: Record = {} const survivingJids: string[] = [] - for(const jid of tcTokenKnownJids) { - if(jid === TC_TOKEN_INDEX_KEY) continue - try { - const data = await authState.keys.get('tctoken', [jid]) - const entry = data[jid] + const jidsToCheck = Array.from(tcTokenKnownJids).filter(j => j !== TC_TOKEN_INDEX_KEY) + if(!jidsToCheck.length) return + + try { + const allData = await authState.keys.get('tctoken', jidsToCheck) + for(const jid of jidsToCheck) { + const entry = allData[jid] if(!entry?.token || isTcTokenExpired(entry.timestamp)) { pruneSet[jid] = null } else { survivingJids.push(jid) } - } catch { - pruneSet[jid] = null } + } catch { + return // batch read failed — skip this pruning cycle } const pruneCount = Object.keys(pruneSet).length @@ -2050,6 +2058,15 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { const ONE_DAY_MS = 86400000 if(now - lastTcTokenPruneTs > ONE_DAY_MS) { lastTcTokenPruneTs = now + // Persist prune timestamp so it survives restarts + Promise.resolve(authState.keys.set({ + tctoken: { + [TC_TOKEN_PRUNE_TS_KEY]: { + token: Buffer.alloc(0), + timestamp: now.toString() + } + } + })).catch(() => { /* non-critical */ }) pruneExpiredTcTokens().catch(err => { logger.debug({ err: err?.message }, 'tctoken pruning failed') }) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index fe731f1d..edf340b1 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -1519,7 +1519,31 @@ export const makeMessagesSocket = (config: SocketConfig) => { result: fetchResult, fallbackJid: destinationJid, keys: authState.keys, - getLIDForPN: signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping) + getLIDForPN: signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping), + onNewJidStored: (storedJid) => { + // Fire-and-forget: persist JID into tctoken index for pruning + (async () => { + try { + const TC_IDX = '__index' + const idxData = await authState.keys.get('tctoken', [TC_IDX]) + const idxEntry = idxData[TC_IDX] + const existing: string[] = idxEntry?.token + ? JSON.parse(Buffer.from(idxEntry.token).toString('utf8')) + : [] + if(!existing.includes(storedJid)) { + existing.push(storedJid) + await authState.keys.set({ + tctoken: { + [TC_IDX]: { + token: Buffer.from(JSON.stringify(existing), 'utf8'), + timestamp: unixTimestampSeconds().toString() + } + } + }) + } + } catch { /* non-critical — index rebuilt on next pruning cycle */ } + })() + } }) // Re-read from key store — the notification handler or inline diff --git a/src/Utils/tc-token-utils.ts b/src/Utils/tc-token-utils.ts index 372d5b3a..8d3fc956 100644 --- a/src/Utils/tc-token-utils.ts +++ b/src/Utils/tc-token-utils.ts @@ -19,7 +19,7 @@ const TC_TOKEN_NUM_BUCKETS = 4 */ export function isTcTokenExpired(timestamp: number | string | null | undefined): boolean { if(timestamp === null || timestamp === undefined) return true - const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp + const ts = typeof timestamp === 'string' ? Number(timestamp) : timestamp if(isNaN(ts)) return true const now = Math.floor(Date.now() / 1000) const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION) @@ -57,9 +57,10 @@ export async function resolveTcTokenJid( jid: string, getLIDForPN: (pn: string) => Promise ): Promise { - if(isLidUser(jid)) return jid - const lid = await getLIDForPN(jid) - return lid ?? jid + const normalized = jidNormalizedUser(jid) + if(isLidUser(normalized)) return normalized + const lid = await getLIDForPN(normalized) + return lid ?? normalized } type TcTokenParams = {