| .. | ||
| src | ||
| .gitignore | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
@vojo/widget-telegram
Vojo Telegram bridge management widget — mounts inside /bots/telegram
in the Vojo client. See 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.
# 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:
# 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):
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
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/<slug>/ plus a Caddy block, no docker-compose edit.
Hosting (server-side, runbook)
- DNS:
widgets.vojo.chatA/AAAA → server. Verify withdig. ~/vojo/docker-compose.yml— Caddyvolumes:adds (one parent mount, future widgets reuse it):- ./widgets:/var/www/widgets~/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 } }mkdir -p ~/vojo/widgets/telegram(placeholder so cert provisioning has something to serve), thendocker compose up -d caddyto apply.- Verify directly:
curl -I https://widgets.vojo.chat/telegram/index.htmlshould return 200 and theContent-Security-Policyheader.
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:
"experience": {
"type": "matrix-widget",
"url": "https://widgets.vojo.chat/telegram/index.html"
}
Capacitor (Android)
capacitor.config.ts already has a placeholder. Uncomment and set:
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:<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.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.