A parent that never holds a cookie
Track 3 split Voyager in two: a parent target that reasons on plain values, and an engine child that owns WebKit. Cookies live in WebKit's stores, so they live in the child. The parent never asks to see them, with one exception, and that exception has its own narrow verb in the contract.
The exception is the family catalog. Away from the service host, Firelink answers its catalog only to a paired launcher device or to the hub's own signed-in session. Voyager pairs no device; Dad ruled for reusing the hub sign-in instead ("option 2"). Voyager makes a few requests of its own, outside any page: page icons, Pippa's PIN sign-in, the catalog. Only the catalog fetch has to carry a cookie from a web sign-in, the session cookie that Dad's sign-in to the hub left in Voyager. Track 6 tells the catalog's side of that. This lesson is the cookie's side: how the parent gets one header without ever holding a jar.
The question, worded narrowly
The contract's method is cookieHeader(for:path:named:identity:), and its comment is the specification: "The Cookie header identity's store would send to origin at path, made of the cookies named in names and no other." It goes on: the parent asks for one request it makes itself, "and keeps the value nowhere. Nil when there is none, and always for the private identity."
Every argument narrows the answer:
- identity: the default one, the identity family apps open in. The private identity always gets
nil. - origin: the hub's own scheme, host and port.
- path: the catalog's,
/api/v1/launcher/catalog. - names: exactly one, Firelink's session cookie for the hub's scheme (
cwkfirelink_session_tlsover https,cwkfirelink_sessionover http). On loopback no name is asked for at all, because the Mac that serves the hub needs no session.
What comes back is a string ready to be a header. It is read for one request and kept nowhere. When the hub refuses one, the catalog model remembers only its hashValue, in memory, so the same stale cookie is not sent again at every change of cookies.
The child answers
The WebKit child reads every cookie of that identity's store, there in the child, and passes them to RequestCookies.header. That function is pure and lives in the parent's target, in the contract file, so the fake engine answers the parent with the very same rule the real child uses, and the rule can be tested with no browser at all.
How a browser picks cookies, in a dozen lines
| Rule | Why it is there |
|---|---|
| The cookie's name is one of those asked for | Cookies ignore ports. Pippa's own session lives on the same tailnet host as the hub, on another port, and only the name keeps it out of the header. |
| The cookie's domain equals the host exactly | Stricter than a browser on purpose. Firelink sets no Domain, so its cookie is host-only. A cookie scoped to a wider domain is not the one Firelink set, whichever host set it: any host under that domain could have, the hub's own host included. |
| Not expired | A cookie with no expiry date, a session cookie, counts. |
A Secure cookie only over https | The same rule a browser follows, so plain http never carries it. |
| The request's path is under the cookie's | RFC 6265 section 5.1.4: the same path, a prefix that ends in a /, or a prefix followed by a / in the request. /api matches the catalog's path; /ap does not. |
Longer paths first, joined with ; | The order browsers send them in. |
The core test onlyFirelinksCookieForTheHubsHostIsChosen builds a jar of six cookies, each failing one rule: Pippa's session on the same host, the right name on another host, the right name scoped to the whole .tail.test domain, an expired one, one on /admin, and the real one. Only cwkfirelink_session_tls=hub comes back.
Told which identity, never which cookie
The parent also needs to know when to ask again: if the hub refused, Dad may sign in to it in a tab a moment later. The engine registers a CookieWatcher on each persistent store. When that store's cookies change, the watcher reports the identity, and nothing else: not which cookie, not its value. The cookie store holds its observers weakly, so the engine keeps the watchers, and a watcher keeps no store.
The parent reacts only when the changed identity is the default one and the catalog is waiting for a hub session. Then it reads the header once more, and if the hub has not already refused that exact session, it fetches the catalog. Dad signs in to the hub in an ordinary tab and Home fills itself in. The private identity's changes are never reported.
The real-WebKit test theChildHandsOverOnlyTheCookieAskedFor plants Firelink's cookie and a pippa_session on one synthetic host in one identity's store, waits for the watcher to name that identity, and then checks four answers: the hub gets only Firelink's cookie, another host gets nil, another identity gets nil, and the private identity gets nil and is never reported.
Where the header goes
FirelinkHubSession.transport adds the header only to requests for the hub's own origin, with the scheme's default port filled in so https://hub/ and https://hub:443/ count as one, and the kit's transport refuses redirects. The header cannot follow a redirect to another host.