# @vojo/widget-telegram Vojo Telegram bridge management widget — mounts inside `/bots/telegram` in the Vojo client. See [`docs/plans/bots_tab.md`](../../docs/plans/bots_tab.md) Phase 3 for product context and the matrix-widget-api contract. This is **not** a Telegram client. It's a small panel that drives the mautrix-telegram bridge bot (`@telegrambot:vojo.chat`) by sending text commands in the control DM and rendering the bot's text replies. M11 ships only the bootstrap + a `ping` button to verify the host handshake. ## Layout ``` src/ ├── bootstrap.ts Parse URL params the host appends (matches BotWidgetEmbed.ts) ├── widget-api.ts Inline matrix-widget-api postMessage transport (no SDK) ├── App.tsx UI: bootstrap card, action buttons, transcript pane ├── main.tsx Entry: init bootstrap, render App or diagnostic └── styles.css Theme-aware CSS variables ``` ## Local development **Don't touch the committed `config.json`.** Create `config.local.json` at the project root once — gitignored, never deployed. The host's Vite dev server overlays it on top of `/config.json` responses (see `serveLocalConfigOverlay` in `vite.config.js`); prod builds ignore the overlay entirely. ```bash # one-time: install widget deps cd apps/widget-telegram && npm install # one-time: create config.local.json (gitignored) at the project root cat > /home/ubuntu/projects/vojo/cinny/config.local.json <<'JSON' { "bots": [ { "id": "telegram", "experience": { "type": "matrix-widget", "url": "http://localhost:8081/" } } ] } JSON ``` The overlay merges `bots[]` by `id`, so just `{ id, experience }` is enough — base bot's `mxid` and `name` are preserved. Top-level fields not present in `config.local.json` are inherited from `config.json`. Run both servers: ```bash # terminal 1 — widget on :8081 with HMR cd apps/widget-telegram && npm run dev # terminal 2 — host SPA on :8080 cd /home/ubuntu/projects/vojo/cinny && npm start ``` Open `http://localhost:8080/bots/telegram`. Iframe loads cross-origin from the widget dev server, HMR works, no proxy. `http://localhost:*` URLs are accepted by the host's URL validator only in dev builds (`import.meta.env.DEV` branch in `src/app/features/bots/catalog.ts`); production builds drop the branch via Vite's dead-code elimination, AND production-only enforces an origin allowlist (`PROD_WIDGET_ORIGINS`) so prod can never embed `localhost` even if config.json is poisoned. Deploy is unchanged. `config.local.json` is gitignored, never shipped. You don't need to revert anything before `Deploy to vojo.chat` — there is nothing in tracked files that points at localhost. Standalone preview of the widget bundle (no host, useful for visual iteration): ```bash cd apps/widget-telegram npm run dev # vite dev server on :8081 — shows missing-params banner # without host, expected. npm run preview # serves the production build from dist/ ``` ## Build ```bash npm run build ``` Outputs to `apps/widget-telegram/dist/`. Deploy by rsyncing `dist/*` into `~/vojo/widgets/telegram/` on the production host (Caddy serves this via the `widgets.vojo.chat` block). One parent `~/vojo/widgets/` directory hosts every bot widget — adding a second one is `mkdir ~/vojo/widgets//` plus a Caddy block, no docker-compose edit. ## Hosting (server-side, runbook) 1. DNS: `widgets.vojo.chat` A/AAAA → server. Verify with `dig`. 2. `~/vojo/docker-compose.yml` — Caddy `volumes:` adds (one parent mount, future widgets reuse it): ```yaml - ./widgets:/var/www/widgets ``` 3. `~/vojo/caddy/Caddyfile` — append: ``` widgets.vojo.chat { encode zstd gzip header { Content-Security-Policy "frame-ancestors https://vojo.chat https://localhost" X-Content-Type-Options "nosniff" Referrer-Policy "no-referrer" Cache-Control "no-cache, no-store, must-revalidate" -Server } handle_path /telegram/* { root * /var/www/widgets/telegram try_files {path} /index.html file_server } handle { respond "Not Found" 404 } } ``` 4. `mkdir -p ~/vojo/widgets/telegram` (placeholder so cert provisioning has something to serve), then `docker compose up -d caddy` to apply. 5. Verify directly: `curl -I https://widgets.vojo.chat/telegram/index.html` should return 200 and the `Content-Security-Policy` header. ## Updating the production /config.json Once the widget is live at `https://widgets.vojo.chat/telegram/index.html`, add to the host repo's `config.json`: ```json "experience": { "type": "matrix-widget", "url": "https://widgets.vojo.chat/telegram/index.html" } ``` ## Capacitor (Android) `capacitor.config.ts` already has a placeholder. Uncomment and set: ```ts server: { allowNavigation: ['widgets.vojo.chat'] } ``` Without this, Android's WebView hijacks the cross-origin iframe URL into `Intent.ACTION_VIEW` and the iframe stays blank. Rebuild the APK after. ## Capability contract The widget requests EXACTLY this set (matches the host's `BotWidgetDriver.getBotWidgetCapabilities`): ``` org.matrix.msc2762.timeline: 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.state_event:m.room.member ``` Anything else is silently dropped by the host. To extend the surface, update `BotWidgetDriver.ts` upstream — that requires a security review per Phase 2 plan §M9.