feat: integrate tctoken lifecycle with expiration, pruning and re-issuance (PR #2339)

Surgical integration of WhiskeySockets/Baileys PR #2339 preserving all
InfiniteAPI customizations (biz nodes, DSM skip, carousel, Prometheus,
circuit breaker, LID mapping, CTWA recovery, identity debounce).

Changes:
- tc-token-utils.ts: rolling bucket expiration (28d/4 buckets), LID
  resolution, monotonicity guard, storeTcTokensFromIqResult parser
- messages-send.ts: proactive fetch if missing/expired, fire-and-forget
  re-issuance on bucket boundary, getPrivacyTokens timestamp param
- messages-recv.ts: persistent JID index for cross-session pruning,
  pruneExpiredTcTokens on connect (max 1x/24h), session_refreshed
  re-issuance, error 463/479 handling in handleBadAck
- chats.ts: self-detection in profilePictureUrl, LID resolver in
  presenceSubscribe
- socket.ts: granular stream error logging (device_removed, xml, ack)
- Auth.ts: senderTimestamp field on tctoken type
- Types/index.ts: sessionInvalidated = 516 disconnect reason
- decode-wa-message.ts: SERVER_ERROR_CODES constant (463, 479, 421, 475)
- generics.ts: enhanced getErrorCodeFromStreamError with device_removed
- baileys-logger.ts: logTcToken function following [BAILEYS] prefix pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Renato Alcara
2026-02-15 20:53:27 -03:00
parent 938c6aaa49
commit 08793904e2
10 changed files with 506 additions and 55 deletions
+27 -4
View File
@@ -50,6 +50,8 @@ import {
type BinaryNode,
getBinaryNodeChild,
getBinaryNodeChildren,
isLidUser,
isPnUser,
jidDecode,
jidNormalizedUser,
reduceBinaryNodeToDictionary,
@@ -695,9 +697,26 @@ export const makeChatsSocket = (config: SocketConfig) => {
const profilePictureUrl = async (jid: string, type: 'preview' | 'image' = 'preview', timeoutMs?: number) => {
const baseContent: BinaryNode[] = [{ tag: 'picture', attrs: { type, query: 'url' } }]
const tcTokenContent = await buildTcTokenFromJid({ authState, jid, baseContent })
// WA Web only includes tctoken for user JIDs (not groups/newsletters)
// and never for own profile pic (Chat model for self has no tcToken).
// Including tctoken for own JID causes the server to never respond.
const normalizedJid = jidNormalizedUser(jid)
const isUserJid = isPnUser(normalizedJid) || isLidUser(normalizedJid)
const me = authState.creds.me
const isSelf =
me && (normalizedJid === jidNormalizedUser(me.id) || (me.lid && normalizedJid === jidNormalizedUser(me.lid)))
let content: BinaryNode[] | undefined = baseContent
jid = jidNormalizedUser(jid)
if(isUserJid && !isSelf) {
content = await buildTcTokenFromJid({
authState,
jid: normalizedJid,
baseContent,
getLIDForPN: signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
})
}
jid = normalizedJid
const result = await query(
{
tag: 'iq',
@@ -707,7 +726,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
type: 'get',
xmlns: 'w:profile:picture'
},
content: tcTokenContent
content
},
timeoutMs
)
@@ -799,7 +818,11 @@ export const makeChatsSocket = (config: SocketConfig) => {
* @param tcToken token for subscription, use if present
*/
const presenceSubscribe = async (toJid: string) => {
const tcTokenContent = await buildTcTokenFromJid({ authState, jid: toJid })
const tcTokenContent = await buildTcTokenFromJid({
authState,
jid: toJid,
getLIDForPN: signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
})
return sendNode({
tag: 'presence',