Context
OpenWA has a pluggable engine architecture — the seam is IWhatsAppEngine (neutral types), and only the adapter layer (src/engine/adapters/*, src/plugins/engines/whatsapp-web-js/*) should know whatsapp-web.js. A code audit found several places outside that seam that assume whatsapp-web.js specifics and would break (or silently misbehave) under a different engine (e.g. Baileys, which uses @s.whatsapp.net JIDs and a different ack/presence/type model).
PR #264 already addressed the lightweight items (engine-neutral typing via sendChatState, mark-chat-read JID validation relaxed, real engine library version surfaced). This issue tracks the remaining, larger decouplings — each is its own PR to keep regression surface contained.
Findings (ranked)
🔴 High — delivery ack scale leaks whatsapp-web.js integers end-to-end
The raw wwebjs MessageAck integer (-1..4) flows through the neutral interface (MessageResult.ack, onMessageAck(id, ack: number)), is interpreted in message-status.util.ts (ackToMessageStatus) and session.service.ts (ack < 0 → message.failed), embedded in webhook payloads + idempotency keys, re-emitted by events.gateway.ts, and re-mapped again in the dashboard (Chats.tsx statusMap, useWebSocket.ts). Baileys uses a different/offset integer scale → every delivery/read state would mis-map.
- Fix: introduce a neutral
DeliveryStatus enum (pending|sent|delivered|read|played|failed) at the seam; each adapter maps its native codes; services/webhook/ws/dashboard consume the neutral string.
🔴 High — IncomingMessage.type is the raw wwebjs MessageTypes string
message-mapper.ts assigns type: msg.type (chat/ptt/voice/revoked/…) directly; the dashboard switches on those tokens (Chats.tsx media render, 'ptt'/'voice', and a latent 'chat' vs 'text' mismatch to verify).
- Fix: normalize to a neutral
MessageType union/enum at the adapter boundary ('chat' → 'text', 'ptt'/'voice' → 'voice', …); consumers switch on the neutral type.
🟠 Medium — JID construction outside the adapter
contact.controller.ts builds ${number}@c.us in the check-number response.
dashboard/MessageTester.tsx builds …@c.us client-side.
session.service.ts hardcodes status@broadcast.
- Fix: let the engine canonicalize numbers → JIDs (
checkNumberExists returns the resolved id); surface an isStatusBroadcast/chatType flag instead of string-matching.
🟠 Medium — Puppeteer/browser config baked into the neutral engine contract
infra.controller.ts, EngineCreateOptions, and plugin.createEngine pass headless/puppeteerArgs/executablePath to every engine — meaningless for a non-browser engine.
- Fix: pass only neutral fields generically; deliver engine-specific config via each plugin's own (declared) config schema (opaque bag).
🟠 Medium — EngineFactory hard-registers only the wwebjs plugin
registerBuiltInEngines() registers a single engine and create() resolves by type, but there's no real registry for a second engine.
- Fix: a registry (
Map<engineType, IEnginePlugin>) seeded by each built-in engine module, selectable by config.
Notes
- Audit verified: there are zero raw
import 'whatsapp-web.js' statements outside the adapter layer — the leaks are semantic (values/shapes/JIDs), not direct imports.
- Swagger example strings using
@c.us are documentation-only (low priority; genericize opportunistically).
Context
OpenWA has a pluggable engine architecture — the seam is
IWhatsAppEngine(neutral types), and only the adapter layer (src/engine/adapters/*,src/plugins/engines/whatsapp-web-js/*) should know whatsapp-web.js. A code audit found several places outside that seam that assume whatsapp-web.js specifics and would break (or silently misbehave) under a different engine (e.g. Baileys, which uses@s.whatsapp.netJIDs and a different ack/presence/type model).PR #264 already addressed the lightweight items (engine-neutral typing via
sendChatState,mark-chat-readJID validation relaxed, real engine library version surfaced). This issue tracks the remaining, larger decouplings — each is its own PR to keep regression surface contained.Findings (ranked)
🔴 High — delivery ack scale leaks whatsapp-web.js integers end-to-end
The raw wwebjs
MessageAckinteger (-1..4) flows through the neutral interface (MessageResult.ack,onMessageAck(id, ack: number)), is interpreted inmessage-status.util.ts(ackToMessageStatus) andsession.service.ts(ack < 0→message.failed), embedded in webhook payloads + idempotency keys, re-emitted byevents.gateway.ts, and re-mapped again in the dashboard (Chats.tsxstatusMap,useWebSocket.ts). Baileys uses a different/offset integer scale → every delivery/read state would mis-map.DeliveryStatusenum (pending|sent|delivered|read|played|failed) at the seam; each adapter maps its native codes; services/webhook/ws/dashboard consume the neutral string.🔴 High —
IncomingMessage.typeis the raw wwebjsMessageTypesstringmessage-mapper.tsassignstype: msg.type(chat/ptt/voice/revoked/…) directly; the dashboard switches on those tokens (Chats.tsxmedia render,'ptt'/'voice', and a latent'chat'vs'text'mismatch to verify).MessageTypeunion/enum at the adapter boundary ('chat' → 'text','ptt'/'voice' → 'voice', …); consumers switch on the neutral type.🟠 Medium — JID construction outside the adapter
contact.controller.tsbuilds${number}@c.usin the check-number response.dashboard/MessageTester.tsxbuilds…@c.usclient-side.session.service.tshardcodesstatus@broadcast.checkNumberExistsreturns the resolved id); surface anisStatusBroadcast/chatTypeflag instead of string-matching.🟠 Medium — Puppeteer/browser config baked into the neutral engine contract
infra.controller.ts,EngineCreateOptions, andplugin.createEnginepassheadless/puppeteerArgs/executablePathto every engine — meaningless for a non-browser engine.🟠 Medium —
EngineFactoryhard-registers only the wwebjs pluginregisterBuiltInEngines()registers a single engine andcreate()resolves by type, but there's no real registry for a second engine.Map<engineType, IEnginePlugin>) seeded by each built-in engine module, selectable by config.Notes
import 'whatsapp-web.js'statements outside the adapter layer — the leaks are semantic (values/shapes/JIDs), not direct imports.@c.usare documentation-only (low priority; genericize opportunistically).