fix: address PR review comments for tctoken lifecycle

- Wire onNewJidStored callback in messages-send.ts so proactively
  fetched tokens are registered in the pruning index
- Batch keystore reads in pruneExpiredTcTokens (1 query instead of N)
- Persist lastTcTokenPruneTs in keystore so 24h throttle survives restarts
- Replace parseInt() with Number() for safer timestamp parsing
- Normalize JIDs via jidNormalizedUser() before LID lookup in
  resolveTcTokenJid to prevent device-suffix mismatches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Renato Alcara
2026-02-15 21:35:45 -03:00
parent 08793904e2
commit 39de1f0582
3 changed files with 56 additions and 14 deletions
+26 -9
View File
@@ -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<string>()
let tcTokenIndexSaveTimer: ReturnType<typeof setTimeout> | 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<string, null> = {}
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')
})