vojo/apps/widget-whatsapp
2026-05-06 17:29:11 +03:00
..
src fix(bots-widgets): default data-input to mouse and drop dead danger:hover rose override so hover works from frame zero on hybrid devices 2026-05-06 17:29:11 +03:00
index.html feat(bots-whatsapp): land Preact widget for mautrix-whatsapp QR + pairing-code login, Meta-ToS warning card, and cross-iframe external-link relay 2026-05-05 15:25:16 +03:00
package-lock.json feat(bots-whatsapp): land Preact widget for mautrix-whatsapp QR + pairing-code login, Meta-ToS warning card, and cross-iframe external-link relay 2026-05-05 15:25:16 +03:00
package.json feat(bots-whatsapp): land Preact widget for mautrix-whatsapp QR + pairing-code login, Meta-ToS warning card, and cross-iframe external-link relay 2026-05-05 15:25:16 +03:00
README.md feat(bots-whatsapp): land Preact widget for mautrix-whatsapp QR + pairing-code login, Meta-ToS warning card, and cross-iframe external-link relay 2026-05-05 15:25:16 +03:00
tsconfig.json feat(bots-whatsapp): land Preact widget for mautrix-whatsapp QR + pairing-code login, Meta-ToS warning card, and cross-iframe external-link relay 2026-05-05 15:25:16 +03:00
vite.config.ts feat(bots-whatsapp): land Preact widget for mautrix-whatsapp QR + pairing-code login, Meta-ToS warning card, and cross-iframe external-link relay 2026-05-05 15:25:16 +03:00

@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):

  1. QR (!wa login qr) — bridge emits a rotating m.image whose body is the raw whatsmeow handshake payload (<ref>,<noise>,<identity>,<adv>, four base64 fields). The widget renders it as a QR matrix client-side. Whatsmeow qrIntervals = [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 an m.replace edit of the original event; the state machine matches on the original id and repaints the matrix.

  2. 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

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.