diff --git a/CHANGELOG.md b/CHANGELOG.md index b612b1f54..05574d5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * **[WIDGET-WORKS]** Disable useCachedGroupMetadata for all groups to prevent LID 406 errors * **[WIDGET-WORKS]** Add HTTP timeout (30s) and maxRedirects (10) to all media download axios calls to prevent indefinite hangs on Azure/ActiveStorage URLs * **[WIDGET-WORKS]** Fix mimetype detection for Azure Storage URLs - prevent `mimeTypes.lookup()` returning "false" string for URLs without file extensions +* **[WIDGET-WORKS]** Fix LID messages not syncing to Chatwoot during historical sync (syncFullHistory) - batch resolve @lid identifiers to phone numbers using IsOnWhatsapp table before messages.set processing. Unresolved LIDs create temporary contacts (self-healing on next real-time message). Resolves 942 unsynced LID messages across production instances. Disable with `CHATWOOT_LID_HISTORICAL_SYNC_ENABLED=false` **Tier 2 (Optional - disabled by default):** * **[WIDGET-WORKS]** `LOG_BAILEYS=debug`: Enhanced Baileys connection.update logging with wsCloseCode/wsCloseReason diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 3e81eaee9..6b4f8cf85 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1042,6 +1042,74 @@ export class BaileysStartupService extends ChannelStartupService { chatwootImport.setRepositoryMessagesCache(instance, messagesRepository); } + // [WIDGET-WORKS] LID resolution for historical sync (messages.set) + // Context: During historical sync, Baileys provides stripped message keys without remoteJidAlt field. + // This prevents LID→phone resolution that works in real-time (see line 1521-1522). + // Solution: Batch resolve LID JIDs using IsOnWhatsapp table before message processing. + // Unresolved LIDs pass through as-is; self-healing at chatwoot.service.ts:644-666 will merge later. + // Related: PR #2275 upstream fix for real-time @lid handling + if (this.configService.get('CHATWOOT').LID_HISTORICAL_SYNC_ENABLED) { + const lidJids = new Set( + messages + .filter((m) => m.key?.remoteJid?.endsWith('@lid')) + .map((m) => m.key.remoteJid) + .filter((jid): jid is string => !!jid), + ); + + if (lidJids.size > 0) { + this.logger.verbose( + `[messages.set] Found ${lidJids.size} unique LID identifiers, attempting resolution via IsOnWhatsapp table`, + ); + + try { + const onWhatsappRecords = await this.prismaRepository.isOnWhatsapp.findMany({ + where: { + OR: Array.from(lidJids).map((jid) => ({ jidOptions: { contains: jid } })), + }, + }); + + const lidToPhoneMap = new Map(); + onWhatsappRecords.forEach((record) => { + if (!record.jidOptions || !record.remoteJid.endsWith('@s.whatsapp.net')) { + return; + } + const lidInOptions = record.jidOptions.split(',').find((j) => j.endsWith('@lid')); + if (lidInOptions && lidJids.has(lidInOptions)) { + lidToPhoneMap.set(lidInOptions, record.remoteJid); + } + }); + + this.logger.verbose( + `[messages.set] Resolved ${lidToPhoneMap.size}/${lidJids.size} LID identifiers to phone numbers`, + ); + + // Replace LID with phone number where mapping exists + let replacedCount = 0; + messages.forEach((m) => { + if (m.key?.remoteJid?.endsWith('@lid') && lidToPhoneMap.has(m.key.remoteJid)) { + const originalLid = m.key.remoteJid; + m.key.remoteJid = lidToPhoneMap.get(originalLid); + replacedCount++; + } + }); + + if (replacedCount > 0) { + this.logger.verbose(`[messages.set] Replaced ${replacedCount} message remoteJids from LID to phone`); + } + + const unresolvedCount = lidJids.size - lidToPhoneMap.size; + if (unresolvedCount > 0) { + this.logger.verbose( + `[messages.set] ${unresolvedCount} LID identifiers not found in IsOnWhatsapp cache - will create temporary contacts (self-healing on next real-time message)`, + ); + } + } catch (error) { + this.logger.error(`[messages.set] Error during LID resolution: ${error?.message}`); + // Continue processing - better to have LID contacts than miss messages entirely + } + } + } + for (const m of messages) { if (!m.message || !m.key || !m.messageTimestamp) { continue; diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 7c4e382e7..98df826f9 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -328,6 +328,10 @@ export type Chatwoot = { }; PLACEHOLDER_MEDIA_MESSAGE: boolean; }; + // [WIDGET-WORKS] Enable LID→phone resolution during historical sync (messages.set) + // Resolves @lid identifiers using IsOnWhatsapp table before Chatwoot import + // Default: true (enabled), set to false to disable + LID_HISTORICAL_SYNC_ENABLED: boolean; }; export type Openai = { ENABLED: boolean; API_KEY_GLOBAL?: string }; export type Dify = { ENABLED: boolean }; @@ -822,6 +826,8 @@ export class ConfigService { }, PLACEHOLDER_MEDIA_MESSAGE: process.env?.CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE === 'true', }, + // [WIDGET-WORKS] LID historical sync feature flag (default: true, opt-out with =false) + LID_HISTORICAL_SYNC_ENABLED: process.env?.CHATWOOT_LID_HISTORICAL_SYNC_ENABLED !== 'false', }, OPENAI: { ENABLED: process.env?.OPENAI_ENABLED === 'true',