175 lines
6.4 KiB
Markdown
175 lines
6.4 KiB
Markdown
# @vojo/widget-telegram
|
|
|
|
Vojo Telegram bridge management widget — mounts inside `/bots/telegram`
|
|
in the Vojo client.
|
|
|
|
This is **not** a Telegram client. It's a control panel for the
|
|
mautrix-telegram bridge that talks to the bridge's **provisioning HTTP
|
|
API** (bridgev2 `/_matrix/provision/v3/*`, exposed by Caddy at
|
|
`https://vojo.chat/_provision/telegram`). It signs the user in
|
|
(phone+code+2FA or QR), shows the linked account, lists Telegram
|
|
contacts, resolves @usernames / +phones, and creates DM portals on
|
|
demand.
|
|
|
|
Auth: the widget requests MSC1960 OpenID credentials from the host
|
|
(`get_openid` over the widget API; granted by `BotWidgetDriver.askOpenID`
|
|
when config.json opts the bot into the `vojo.openid` capability) and
|
|
sends them to the bridge as `Authorization: Bearer openid:<token>`. The
|
|
OpenID token only proves identity — it is not a Matrix access token.
|
|
|
|
There is no bot text-command transport and no reply parsing: the legacy
|
|
`!tg`-command dialect (`bridge-protocol/`) was deleted when the bridge
|
|
API became reachable. The bot control DM still exists (BotShell needs a
|
|
room and the «Show chat» fallback), the widget just doesn't read or
|
|
write it — it requests **zero** MSC2762 capabilities.
|
|
|
|
## Layout
|
|
|
|
```
|
|
src/
|
|
├── bootstrap.ts Parse URL params the host appends (matches BotWidgetEmbed.ts)
|
|
├── widget-api.ts Inline matrix-widget-api postMessage transport: handshake,
|
|
│ theme, MSC1960 get_openid, io.vojo.bot-widget verbs
|
|
├── provisioning.ts Typed client for the bridgev2 provisioning API + identifier
|
|
│ helpers (wire contract documented in the file header)
|
|
├── errors.ts Bridge/Telegram error → localized copy mapping
|
|
├── login.tsx Login flow over the v3 step machine + forms (phone/code/
|
|
│ password/QR with long-poll rotation)
|
|
├── contacts.tsx Contacts list, search-as-filter, resolve-probe, create DM
|
|
├── App.tsx Shell: boot/disconnected/connected phases, tabs, account
|
|
├── ui.tsx Icons, initials avatar, command cards, notices
|
|
├── main.tsx Entry: init bootstrap, render App or diagnostic
|
|
└── styles.css Theme-aware CSS (Dawn palette, light remap via data-theme)
|
|
```
|
|
|
|
## 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.
|
|
|
|
Note the overlay merges bot entries **shallowly** — your local
|
|
`experience` object replaces the base one wholesale, so it must carry
|
|
`provisioningUrl` and `capabilities` too:
|
|
|
|
```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/",
|
|
"provisioningUrl": "https://vojo.chat/_provision/telegram",
|
|
"capabilities": ["vojo.openid"]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
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. The provisioning calls
|
|
go straight to the prod bridge API (CORS `*` + per-request bearer auth),
|
|
acting on whatever account you're signed in with — same trust model as
|
|
the dev client talking to the prod homeserver.
|
|
|
|
`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.
|
|
|
|
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).
|
|
|
|
## Server-side requirements
|
|
|
|
1. The widget static files at `widgets.vojo.chat/telegram/` (Caddy
|
|
`handle_path /telegram/*` block — see git history of this README for
|
|
the full runbook).
|
|
2. The bridge provisioning API exposed at the URL configured in
|
|
config.json `experience.provisioningUrl`. Caddy block inside the
|
|
`vojo.chat` site:
|
|
|
|
```
|
|
handle /_provision/telegram/* {
|
|
uri replace /_provision/telegram /_matrix/provision
|
|
reverse_proxy telegram-bridge:29317 # host:port from appservice.address
|
|
}
|
|
```
|
|
|
|
Path-scoped on purpose: the same bridge listener serves the appservice
|
|
transaction endpoints (`/_matrix/app/*`), which must stay internal.
|
|
3. `provisioning.shared_secret` in the bridge config must NOT be
|
|
`disable` (a ≥16-char secret enables the API; the widget never sees
|
|
the secret — it authenticates with per-user OpenID tokens).
|
|
|
|
## Updating the production /config.json
|
|
|
|
```json
|
|
"experience": {
|
|
"type": "matrix-widget",
|
|
"url": "https://widgets.vojo.chat/telegram/index.html",
|
|
"provisioningUrl": "https://vojo.chat/_provision/telegram",
|
|
"capabilities": ["vojo.openid"]
|
|
}
|
|
```
|
|
|
|
## 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 **no** MSC2762 capabilities — the handshake replies
|
|
with an empty list. The only privileged surfaces are:
|
|
|
|
- MSC1960 `get_openid` — granted by `BotWidgetDriver.askOpenID` iff
|
|
config.json declares `"capabilities": ["vojo.openid"]` for this bot;
|
|
- `io.vojo.bot-widget` side-channel verbs `open-external-url` and
|
|
`open-matrix-to` (origin-pinned and validated host-side in
|
|
`BotWidgetEmbed.onWidgetMessage`).
|