The in-app Remote Control Server serves its own web UI over the local network today. This document plans the “connect from anywhere” upgrade: a statically hosted UI that reaches the Mac over Tailscale HTTPS, using host and token passed as URL params.
Status: frontend parameter routing, locked-down CORS, and the app connect flow are implemented.
Static hosting, DNS, and tailscale serve remain one-time maintainer setup.
Phone browser
-> https://rc.toki.aashutosh.dev/#host=<mac>.<tailnet>.ts.net&token=XYZ (Vercel, HTTPS)
app.js reads host + token, calls:
-> https://<mac>.<tailnet>.ts.net/api/...?token=XYZ (Tailscale HTTPS)
tailscale serve proxies to the Mac's server on 127.0.0.1:8765
Access requires three gates: a valid link token, the six-digit verification code shown separately in Toki, and the phone being on the user’s tailnet. The browser exchanges the link token and code for a random session token that expires after the lifetime selected in Toki (12 hours recommended, 2 days maximum). Five failed code attempts within a minute trigger a temporary rate limit. The verification code rotates every two minutes; already verified sessions remain valid for their selected lifetime.
A page served over HTTPS (Vercel, GitHub Pages, anything) cannot fetch() a plain HTTP endpoint:
browsers block it as mixed content. The only HTTP exception is localhost, which on the phone
means the phone itself, not the Mac. So a hosted UI can only reach the Mac if the Mac’s API is
also HTTPS. tailscale serve provides that with a real, phone-trusted certificate. Over plain
LAN HTTP the hosted UI is a dead end; that path stays served directly from the Mac instead.
webui/app.js: if #host= is present, use https://<host> as the API base; otherwise keep
same-origin so the locally served path still works.https://rc.toki.aashutosh.dev/#host=<mac>.ts.net&token=…, App set to Toki RC) and direct
(https://<mac>.ts.net/?token=…, App set to Same as host). The in-page QR scanner resolves both
into the host-plus-token pair the manual form asks for, so a QR built for one page still works
when scanned on the other. Only a LAN link (http://<ip>:8765/?token=…) is unusable from the
hosted page, because an HTTPS page cannot call a plain-HTTP address; the scanner names the host
it can’t reach rather than rejecting the code.Access-Control-Allow-Origin (locked to the hosted origin), allow the
Content-Type header, and answer OPTIONS preflight. The page origin (hosted) and the API
origin (tailnet host) differ, so cross-origin handling is required.Vercel or Cloudflare Pages, whichever the maintainer prefers (functionally equivalent here):
Sources/Toki/Resources/webui, framework
preset “Other” (static, no build step).rc.toki.aashutosh.dev as a CNAME on the existing domain (no new domain).main; pull requests get preview URLs.The source is the same one the app bundles, but the two do not update together, and that is a
compatibility constraint rather than a detail. The hosted page changes the moment a release lands
on main; the Mac it talks to changes whenever its owner updates. During a beta the drift runs the
other way, with main behind the release branch. So both directions have to work:
Authorization header.Neither fallback can be dropped until the older side is gone. Tests/test_remote_cross_version.js
runs the page’s real token-transport logic against a server of each vintage.
tailscale serve --bg 443 http://127.0.0.1:8765, giving
https://<mac>.<tailnet>.ts.net.rc.toki.aashutosh.dev.Toki RC (hosted), build the QR and connect URL as
https://rc.toki.aashutosh.dev/#host=<tailnet-host>&token=<token>.tailscale status --json.rc.toki.aashutosh.dev serves only static interface files. Agent discovery, transcripts, and replies
stay on the Mac and travel directly between the browser and Mac over the tailnet.tailscale serve --bg 443 http://127.0.0.1:8765 on the Mac.Toki RC (hosted) under App, then open Connect and copy its URL.The hosted UI accepts only *.ts.net API hosts. The API emits cross-origin headers only for
https://rc.toki.aashutosh.dev; other web origins cannot use the browser CORS path.
Once the UI is on HTTPS it can become an installable PWA (web app manifest plus a service worker), which unlocks Web Push so the phone is notified when an agent needs input. On iOS this requires the user to Add to Home Screen (Web Push is limited to installed PWAs, iOS 16.4+). This is why the hosting work is a prerequisite for notifications.