From d514764686dd6edda58cf5fa982cf424e38ba641 Mon Sep 17 00:00:00 2001 From: Matheus Filype <67132916+Santosl2@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:51:47 -0300 Subject: [PATCH] feat: send unified session (#2294) * fix: improve message resend logic by adding checks for message IDs * Revert "fix: improve message resend logic by adding checks for message IDs" This reverts commit c03f9d8e6fc6cbfbb9d1f8f67c169700e704213d. * feat: add unified session handling and time constants * refactor: improve socket variable destructuring and presence update logic * fix: remove unnecessary semicolons in socket and time constants definitions * fix: handle invalid server time offset parsing in makeSocket function --- src/Defaults/index.ts | 7 ++++++ src/Socket/chats.ts | 21 +++++++++++++--- src/Socket/socket.ts | 57 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index 10d140fc..be3750d9 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -137,3 +137,10 @@ export const DEFAULT_CACHE_TTLS = { CALL_OFFER: 5 * 60, // 5 minutes USER_DEVICES: 5 * 60 // 5 minutes } + +export const TimeMs = { + Minute: 60 * 1000, + Hour: 60 * 60 * 1000, + Day: 24 * 60 * 60 * 1000, + Week: 7 * 24 * 60 * 60 * 1000 +} diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index 8e2de602..efb38838 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -68,7 +68,17 @@ export const makeChatsSocket = (config: SocketConfig) => { getMessage } = config const sock = makeSocket(config) - const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock + const { + ev, + ws, + authState, + generateMessageTag, + sendNode, + query, + signalRepository, + onUnexpectedError, + sendUnifiedSession + } = sock let privacySettings: { [_: string]: string } | undefined @@ -659,13 +669,18 @@ export const makeChatsSocket = (config: SocketConfig) => { const sendPresenceUpdate = async (type: WAPresence, toJid?: string) => { const me = authState.creds.me! - if (type === 'available' || type === 'unavailable') { + const isAvailableType = type === 'available' + if (isAvailableType || type === 'unavailable') { if (!me.name) { logger.warn('no name present, ignoring presence update request...') return } - ev.emit('connection.update', { isOnline: type === 'available' }) + ev.emit('connection.update', { isOnline: isAvailableType }) + + if (isAvailableType) { + void sendUnifiedSession() + } await sendNode({ tag: 'presence', diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index b258fc40..87c65a5a 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -11,6 +11,7 @@ import { MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, + TimeMs, UPLOAD_TIMEOUT } from '../Defaults' import type { LIDMapping, SocketConfig } from '../Types' @@ -77,6 +78,8 @@ export const makeSocket = (config: SocketConfig) => { const publicWAMBuffer = new BinaryInfo() + let serverTimeOffsetMs = 0 + const uqTagId = generateMdTagPrefix() const generateMessageTag = () => `${uqTagId}${epoch++}` @@ -895,6 +898,7 @@ export const makeSocket = (config: SocketConfig) => { ws.on('CB:iq,,pair-success', async (stanza: BinaryNode) => { logger.debug('pair success recv') try { + updateServerTimeOffset(stanza) const { reply, creds: updatedCreds } = configureSuccessfulPairing(stanza, creds) logger.info( @@ -906,6 +910,7 @@ export const makeSocket = (config: SocketConfig) => { ev.emit('connection.update', { isNewLogin: true, qr: undefined }) await sendNode(reply) + void sendUnifiedSession() } catch (error: any) { logger.info({ trace: error.stack }, 'error in pairing') void end(error) @@ -914,6 +919,7 @@ export const makeSocket = (config: SocketConfig) => { // login complete ws.on('CB:success', async (node: BinaryNode) => { try { + updateServerTimeOffset(node) await uploadPreKeysToServerIfRequired() await sendPassiveIq('active') @@ -933,6 +939,7 @@ export const makeSocket = (config: SocketConfig) => { ev.emit('creds.update', { me: { ...authState.creds.me!, lid: node.attrs.lid } }) ev.emit('connection.update', { connection: 'open' }) + void sendUnifiedSession() if (node.attrs.lid && authState.creds.me?.id) { const myLID = node.attrs.lid @@ -1041,6 +1048,54 @@ export const makeSocket = (config: SocketConfig) => { Object.assign(creds, update) }) + const updateServerTimeOffset = ({ attrs }: BinaryNode) => { + const tValue = attrs?.t + if (!tValue) { + return + } + + const parsed = Number(tValue) + if (Number.isNaN(parsed) || parsed <= 0) { + return + } + + const localMs = Date.now() + serverTimeOffsetMs = parsed * 1000 - localMs + logger.debug({ offset: serverTimeOffsetMs }, 'calculated server time offset') + } + + const getUnifiedSessionId = () => { + const offsetMs = 3 * TimeMs.Day + const now = Date.now() + serverTimeOffsetMs + const id = (now + offsetMs) % TimeMs.Week + return id.toString() + } + + const sendUnifiedSession = async () => { + if (!ws.isOpen) { + return + } + + const node = { + tag: 'ib', + attrs: {}, + content: [ + { + tag: 'unified_session', + attrs: { + id: getUnifiedSessionId() + } + } + ] + } + + try { + await sendNode(node) + } catch (error) { + logger.debug({ error }, 'failed to send unified_session telemetry') + } + } + return { type: 'md' as 'md', ws, @@ -1064,6 +1119,8 @@ export const makeSocket = (config: SocketConfig) => { digestKeyBundle, rotateSignedPreKey, requestPairingCode, + updateServerTimeOffset, + sendUnifiedSession, wamBuffer: publicWAMBuffer, /** Waits for the connection to WA to reach a state */ waitForConnectionUpdate: bindWaitForConnectionUpdate(ev),