**Note** from Bead: User Host Layer · [canonical source](https://redfish.acequia.io/guerin/.agents/3d011d4e-3212-477c-aa3f-a058e24aa36a/2026-07-02/notes/06-user-host-layer.md) · session 2026-07-02 · discussion: Talk: User Host Layer
The bead's center of gravity: the tier at which a **user's device becomes a host and origin-proxy** for URIs it does not own, and syncs the bytes to an established origin without a server in the middle.
## The service worker as a cross-domain origin-proxy Stephen: "Any web page, if it has a service worker, can start to store URIs for any domain in a named cache, and the service worker can then proxy it as if it's coming from that domain." - A page with a service worker holds a **named cache** containing URIs from another domain (say `livetil.es`). - The SW **intercepts fetches and answers them as if they came from that domain**, satisfying the request locally. The origin is wherever the SW says it is. - Behind the scenes, the SW **syncs** that cache to something that has "established the origin" from that origin: a **localhost node** or a page already loaded from the real origin (reachable over **WebRTC**). Stephen was careful about "real": what matters is a peer that has *established the origin*, not that it is canonical.
## Why WebRTC, and why not postMessage Crossing the domain boundary is the real trick. Two PWAs on different domains cannot postMessage each other, and postMessage is the wrong pipe anyway: - **postMessage serializes to JSON**, so a 2 GB GeoTIFF or video would be base64-encoded, ~33% inflation plus a full copy, destroying it. Explicitly refused ("I don't want to convert that to base64"). - **WebRTC data channels stream binary directly, peer-to-peer, no serialization tax.** This is the transport for large binaries between two sites. - postMessage on the *same device* is also avoided for the same conversion reason.
## CORS dissolves You are not crossing domains at request time. The SW owns the named cache entirely, so from the page's perspective the bytes are same-origin. The cross-domain movement happens over the WebRTC data channel, *below* the application layer, where CORS (an application-layer concern) does not apply. See [07](07-forman-matrix-corridors-patches.md): the matrix predates the domain boundaries.
## Signaling rides the pub/sub layer How does the cross-domain sync happen without a relay server mediating? The **signaling itself rides the acequia pub/sub** ([04](04-transactional-fields-offer-acceptance.md)): potentialOffers ("I'm a livetil.es origin, accept WebRTC connections") meet potentialAcceptances from SW instances ("I want to sync cache with you") in res potentia; the handshake crystallizes; the data channel opens; binary flows. Same infrastructure carries both the tile offer and the SDP offer for opening the pipe. It is not two networks.
## The OPFS / Node / File System Access constraint A concrete gotcha worked out at the end of the session: - **Node.js (Termux on Android) cannot reach OPFS.** OPFS is a browser API, sandboxed to the browser. So Tian's local Node server cannot serve his OPFS tile cache directly. - The **browser** can reach OPFS via the service worker, and the SW can proxy requests to localhost. - On Android, a local Node server *can* read the filesystem directly via the **File System Access API** handle to a directory (you cannot get the gallery or other protected directories, but you can get user-granted ones). - So the working shape: OPFS holds tiles + the GeoTIFF; either the SW proxies OPFS to the page, or a Termux Node server reads a File-System-Access-granted directory and publishes a potentialOffer pointing at the same directory URI. If the accessibility cost of reaching the phone is too high, remote peers just accept a different potentialOffer from the field (a relay, a closer peer). Tian never decides the connection technology; he publishes the offer, the field resolves it.
## Acequia node = web dev server hosting the fields Every acequia node (Tian's phone, a relay, nephele) runs a web dev server and can host `/offerPotentials/` and `/acceptancePotentials/` **field** directories under different URIs, with the acequia access JSON accepting tokens (or open) per the permissions the node's owner sets. Tian PUTs an offer out there; one of the services he is looking for might be **backup or distributed caching**: he publishes "I have a GeoTIFF and sparse tiles, looking for backup/caching partners," and nodes with spare always-on storage respond with acceptancePotentials. See [04](04-transactional-fields-offer-acceptance.md) for the field-hosting correction and permission gating; feedback_cloud-auth-goes-inta-goes-outa for the goes-inta/goes-outa membrane.
## Open seams - Ingest gesture: file-picker vs PWA-as-share-target (make hosting a habit, not a tool). - What keeps WebRTC channels warm when there is no immediate transaction (pub/sub heartbeat, or just being a parciante in the commons)? See [07](07-forman-matrix-corridors-patches.md), "who tends the matrix?" - Mortality UI: when the phone that is holding a place up sleeps or dies, does the map surface it honestly ("this map is alive because Tian is holding it up"), and show stale gen-N tiles vs holes? - Is TN's origin-hood contingent on the PWA being foregrounded (phone OSes kill SWs aggressively)?