feat(telemetry): add unified_session telemetry to reduce detection
Implements WhatsApp's unified_session telemetry feature to reduce detection of unofficial clients. This is an enterprise-grade implementation inspired by whatsmeow PR #1057 and Baileys PR #2294. Features: - UnifiedSessionManager class with circuit breaker protection - Server time synchronization for accurate session IDs - Rate limiting to prevent spam (1 minute between sends) - Prometheus metrics integration (unified_session_sent, errors) - Structured logging for debugging - Configurable via SocketConfig.enableUnifiedSession - Environment variable support (BAILEYS_UNIFIED_SESSION_ENABLED) Trigger points (matching official WhatsApp Web): - After successful login (CB:success) - After successful pairing (CB:iq,,pair-success) - When sending 'available' presence Implementation details: - Session ID algorithm: (now + serverOffset + 3days) % 7days - Time constants exported from Defaults/index.ts - Full test coverage (31 tests) References: - https://github.com/tulir/whatsmeow/pull/1057 - https://github.com/WhiskeySockets/Baileys/pull/2294 - https://github.com/tulir/whatsmeow/issues/810
This commit is contained in:
+71
-1
@@ -60,6 +60,12 @@ import {
|
||||
incrementActiveConnections,
|
||||
decrementActiveConnections
|
||||
} from '../Utils/prometheus-metrics'
|
||||
import {
|
||||
createUnifiedSessionManager,
|
||||
extractServerTime,
|
||||
shouldEnableUnifiedSession,
|
||||
type UnifiedSessionManager
|
||||
} from '../Utils/unified-session'
|
||||
import { BinaryInfo } from '../WAM/BinaryInfo.js'
|
||||
import { USyncQuery, USyncUser } from '../WAUSync/'
|
||||
import { WebSocketClient } from './Client'
|
||||
@@ -87,7 +93,8 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
enableCircuitBreaker = true,
|
||||
queryCircuitBreaker: queryCircuitBreakerConfig,
|
||||
connectionCircuitBreaker: connectionCircuitBreakerConfig,
|
||||
preKeyCircuitBreaker: preKeyCircuitBreakerConfig
|
||||
preKeyCircuitBreaker: preKeyCircuitBreakerConfig,
|
||||
enableUnifiedSession = shouldEnableUnifiedSession()
|
||||
} = config
|
||||
|
||||
// Initialize circuit breakers if enabled
|
||||
@@ -141,6 +148,19 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
logger.info('Circuit breakers initialized for socket operations')
|
||||
}
|
||||
|
||||
// Initialize Unified Session Manager for telemetry
|
||||
// Note: sendNode will be set after it's defined below
|
||||
let unifiedSessionManager: UnifiedSessionManager | undefined
|
||||
if (enableUnifiedSession) {
|
||||
unifiedSessionManager = createUnifiedSessionManager({
|
||||
enabled: true,
|
||||
logger,
|
||||
enableCircuitBreaker
|
||||
// sendNode will be configured after sendNode function is defined
|
||||
})
|
||||
logger.info('Unified session manager initialized')
|
||||
}
|
||||
|
||||
const publicWAMBuffer = new BinaryInfo()
|
||||
|
||||
const uqTagId = generateMdTagPrefix()
|
||||
@@ -220,6 +240,28 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
return sendRawMessage(buff)
|
||||
}
|
||||
|
||||
// Configure unified session manager with sendNode now that it's defined
|
||||
if (unifiedSessionManager) {
|
||||
// Create a wrapper that matches the expected signature
|
||||
const sendNodeForSession = async (node: BinaryNode): Promise<void> => {
|
||||
await sendNode(node)
|
||||
}
|
||||
// Recreate with proper sendNode
|
||||
unifiedSessionManager = createUnifiedSessionManager({
|
||||
enabled: true,
|
||||
logger,
|
||||
enableCircuitBreaker,
|
||||
sendNode: sendNodeForSession
|
||||
})
|
||||
}
|
||||
|
||||
/** Send unified_session telemetry */
|
||||
const sendUnifiedSession = async (trigger: 'login' | 'pairing' | 'presence' | 'manual' = 'manual'): Promise<void> => {
|
||||
if (unifiedSessionManager) {
|
||||
await unifiedSessionManager.send(trigger)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a message with a certain tag to be received
|
||||
* @param msgId the message tag to await
|
||||
@@ -783,6 +825,9 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
connectionCircuitBreaker?.destroy()
|
||||
preKeyCircuitBreaker?.destroy()
|
||||
|
||||
// Clean up unified session manager
|
||||
unifiedSessionManager?.destroy()
|
||||
|
||||
ws.removeAllListeners('close')
|
||||
ws.removeAllListeners('open')
|
||||
ws.removeAllListeners('message')
|
||||
@@ -1061,6 +1106,11 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
ev.emit('connection.update', { isNewLogin: true, qr: undefined })
|
||||
|
||||
await sendNode(reply)
|
||||
|
||||
// Send unified_session telemetry on successful pairing
|
||||
sendUnifiedSession('pairing').catch(err => {
|
||||
logger.debug({ err }, 'Failed to send unified_session on pairing')
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.info({ trace: error.stack }, 'error in pairing')
|
||||
void end(error)
|
||||
@@ -1093,6 +1143,17 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
recordConnectionAttempt('success')
|
||||
incrementActiveConnections()
|
||||
|
||||
// Update server time offset from success node
|
||||
const serverTime = extractServerTime(node)
|
||||
if (serverTime) {
|
||||
unifiedSessionManager?.updateServerTimeOffset(serverTime)
|
||||
}
|
||||
|
||||
// Send unified_session telemetry on successful login
|
||||
sendUnifiedSession('login').catch(err => {
|
||||
logger.debug({ err }, 'Failed to send unified_session on login')
|
||||
})
|
||||
|
||||
if (node.attrs.lid && authState.creds.me?.id) {
|
||||
const myLID = node.attrs.lid
|
||||
process.nextTick(async () => {
|
||||
@@ -1247,6 +1308,15 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
connectionCircuitBreaker?.reset()
|
||||
preKeyCircuitBreaker?.reset()
|
||||
logger.info('All circuit breakers reset to closed state')
|
||||
},
|
||||
// Unified Session Telemetry
|
||||
/** Send unified_session telemetry manually */
|
||||
sendUnifiedSession,
|
||||
/** Get unified session manager state (for debugging) */
|
||||
getUnifiedSessionState: () => unifiedSessionManager?.getState(),
|
||||
/** Update server time offset (call when receiving server timestamps) */
|
||||
updateServerTimeOffset: (serverTime: string | number) => {
|
||||
unifiedSessionManager?.updateServerTimeOffset(serverTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user