Add the browser send proxy and web sender

crates-server/yaak-send-proxy: a stateless executor over yaak-http's
HttpTransaction. It takes a rendered request, streams timeline events,
the response head, body chunks and the resulting cookies back as NDJSON,
and keeps nothing. Private/loopback/link-local/metadata ranges are refused
after DNS on every hop (an AddressFilter on the resolver plus a per-hop URL
check), with size caps, a timeout ceiling, a rate limit, host allow/deny
lists and an optional token.

The web host now sends through it: the wasm worker resolves and renders
the request (render_http_request moved into yaak-models so it builds for
wasm; re-exported from its old paths), the tab posts it, and stores what
comes back where the desktop stores it. Requests needing auth plugins or
template functions are refused with the reason until plugins run in the
browser.
This commit is contained in:
Gregory Schier
2026-08-17 06:51:48 -07:00
parent 93fba4d9b4
commit 253b939b34
39 changed files with 2485 additions and 244 deletions
+13 -8
View File
@@ -7,11 +7,12 @@
* the origin, so two tabs stay coherent for the same reason two desktop windows
* do: one process holds the data and pushes every write to all of them.
*
* What a page genuinely cannot do is not faked. There is no file dialog, no
* second window, no clipboard read without a prompt, and — in this slice — no
* sending. Those report false through `capabilities` and refuse with a reason
* if called anyway, so a missing feature shows up as a disabled control or a
* toast that explains itself, never as a silent no-op.
* Sending goes through a small stateless proxy, because a page cannot see a
* response the way a desktop app can (see send.ts). What a page genuinely
* cannot do is not faked: there is no file dialog, no second window, no
* clipboard read without a prompt. Those report false through `capabilities`
* and refuse with a reason if called anyway, so a missing feature shows up as a
* disabled control or a toast that explains itself, never as a silent no-op.
*/
import type {
@@ -32,17 +33,21 @@ import { requestPersistence } from "./storage";
/** What this host can do, reported honestly. */
function capabilitiesFor(): PlatformCapabilities {
return {
// Through the send proxy: the tab renders, the proxy executes, the tab
// stores. Requests needing plugin auth or template functions are refused
// with the reason until plugins run here.
httpSending: true,
grpc: false,
websocket: false,
git: false,
sync: false,
// Certificates and proxies are decided by whoever puts the bytes on the
// wire. Nothing in the browser does yet.
// wire, and the send proxy uses its own.
tlsOptions: false,
// The jar can be edited and stored here; only filling it needs the sender.
cookieJar: true,
localFiles: false,
timeline: false,
// The proxy streams the engine's events back and the sender stores them.
timeline: true,
// Whether the host can put a *second window* on this data on demand — what
// `cmd_new_child_window` does for Settings and workspace switching. A tab
// can't, so those open in place instead. This is not a claim that nothing