5.5 KiB
@vojo/widget-whatsapp
Vojo WhatsApp bridge management widget — mounts inside /bots/whatsapp
in the Vojo client. Drives the mautrix-whatsapp bridge bot
(@whatsappbot:vojo.chat) by sending bridgev2 commands in the control DM
and rendering the bot's text replies into a typed login flow.
This is not a WhatsApp client — Vojo continues using the Matrix room the bridge writes to. The widget is a panel that handles authentication (QR scan or pairing code) and surfaces session status.
Layout
src/
├── bootstrap.ts Parse URL params the host appends (mirrors BotWidgetEmbed.ts)
├── widget-api.ts Inline matrix-widget-api postMessage transport (no SDK)
├── App.tsx UI: login forms, QR / pairing-code panels, transcript pane
├── main.tsx Entry: init bootstrap, render App or diagnostic
├── styles.css Theme-aware CSS (Vojo Dawn palette)
├── state.ts Login state machine + hydrate-from-timeline
├── i18n/ Russian primary + English fallback
└── bridge-protocol/
├── types.ts LoginEvent discriminated union
├── parser.ts Dispatch shim
└── dialects/
└── bridgev2_v0264.ts Regex table pinned to mautrix-whatsapp v0.26.4
Login flows
WhatsApp's mautrix bridge ships TWO login flows (see
pkg/connector/login.go::GetLoginFlows):
-
QR (
!wa login qr) — bridge emits a rotatingm.imagewhose body is the raw whatsmeow handshake payload (<ref>,<noise>,<identity>,<adv>, four base64 fields). The widget renders it as a QR matrix client-side. WhatsmeowqrIntervals = [60s, 20s, 20s, 20s, 20s, 20s]— first QR lasts 60 seconds, then five rotations of 20 seconds each. Total active window: 2 minutes 40 seconds. Each rotation arrives as anm.replaceedit of the original event; the state machine matches on the original id and repaints the matrix. -
Pairing code (
!wa login phone) — alternative for users whose camera doesn't work or who prefer typing. The user enters a phone number; the bridge replies with two notices:Input the pairing code in the WhatsApp mobile app to log in- The 8-character code itself (
XXXX-XXXX, custom base32 alphabet). The widget renders the code prominently and the user enters it in WhatsApp → Settings → Linked devices → Link with phone number.
There is no 2FA cloud-password step — multidevice handshake is
single-factor. The state machine has no awaiting_password arm.
Capability contract
The widget requests EXACTLY this set (matches the host's
BotWidgetDriver.getBotWidgetCapabilities):
org.matrix.msc2762.timeline:<roomId>
org.matrix.msc2762.send.event:m.room.message#m.text
org.matrix.msc2762.receive.event:m.room.message#m.text
org.matrix.msc2762.receive.event:m.room.message#m.notice
org.matrix.msc2762.receive.event:m.room.message#m.image
org.matrix.msc2762.receive.event:m.room.redaction
org.matrix.msc2762.receive.state_event:m.room.member
Anything else is silently dropped by the host. The capability set is
identical to the Telegram widget's M13 expansion — the host driver
already supports m.image + m.room.redaction.
Local development
cd apps/widget-whatsapp && npm install
cd /home/ubuntu/projects/vojo/cinny && cat > config.local.json <<'JSON'
{
"bots": [
{ "id": "whatsapp", "experience": { "type": "matrix-widget", "url": "http://localhost:8083/" } }
]
}
JSON
Run both servers:
# terminal 1 — widget on :8083 with HMR
cd apps/widget-whatsapp && npm run dev
# terminal 2 — host SPA on :8080
cd /home/ubuntu/projects/vojo/cinny && npm start
Open http://localhost:8080/bots/whatsapp. The host's URL validator
accepts http://localhost:* only in dev builds.
Build
npm run build
Outputs to apps/widget-whatsapp/dist/. Deploy by rsyncing dist/* into
~/vojo/widgets/whatsapp/ on the production host. The VSCode task
Deploy widgets already includes the third subshell — running it from
the host root pushes all three widgets in sequence.
Capacitor (Android)
capacitor.config.ts already allows widgets.vojo.chat for the existing
TG / Discord widgets — no extra entry needed for WhatsApp.
Hosting (server-side)
Same Caddy widgets.vojo.chat block as the other widgets — add a third
handle_path /whatsapp/* { … } block alongside /telegram/* and
/discord/*. Then mkdir -p ~/vojo/widgets/whatsapp on the server, run
the deploy task, and verify with
curl -I https://widgets.vojo.chat/whatsapp/index.html.
Source-of-truth pointers
- mautrix-whatsapp connector: https://github.com/mautrix/whatsapp/blob/main/pkg/connector/login.go
- mautrix-whatsapp connector (post-login session events): https://github.com/mautrix/whatsapp/blob/main/pkg/connector/handlewhatsapp.go
- whatsmeow QR format: https://github.com/tulir/whatsmeow/blob/main/pair.go (
makeQRData) - whatsmeow pairing-code: https://github.com/tulir/whatsmeow/blob/main/pair-code.go (
PairPhone) - bridgev2 commands layer (shared with mautrix-telegram): https://github.com/mautrix/go/blob/main/bridgev2/commands/login.go
The dialect file src/bridge-protocol/dialects/bridgev2_v0264.ts has
inline upstream pointers per regex; when the bridge image is upgraded,
spot-check those pointers and either confirm the wording is still valid
or drop a sibling dialect file with new regexes.