Skip to content

Commit 25cf5ed

Browse files
garrytanclaude
andauthored
v1.39.0.0 feat: buildFetchHandler factory unblocks gbrowser submodule consumption (#1511)
* feat: buildFetchHandler factory unblocks gbrowser submodule consumption Add buildFetchHandler(cfg: ServerConfig): ServerHandle in browse/src/server.ts. Refactor start() to delegate handler construction to the factory and read env once via resolveConfigFromEnv(). Wire the beforeRoute hook (runs after the tunnel surface filter, before per-route dispatch). Auth is now cfg-driven end-to-end. Module-level AUTH_TOKEN const + initRegistry(AUTH_TOKEN) boot call, validateAuth, and shutdown are deleted; factory closure owns them. start() threads cfg.authToken into launchHeaded, the state-file write, and the factory. initRegistry is idempotent for same-token re-init; throws clearly for different-token re-init. __resetRegistry() test helper added (mirrors __resetConnectRateLimit). Existing tests that did rotateRoot() -> initRegistry('fixed-token') swap to __resetRegistry() to avoid the new guard. 14 factory contract tests added covering ServerHandle shape, auth wiring, validation throws, hook semantics across both surfaces, and registry idempotency. Source-pattern tests in dual-listener.test.ts and server-auth.test.ts updated for the new identifiers (handle.fetchLocal/fetchTunnel, authToken, shutdownFn). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: bump version and changelog (v1.39.0.0) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ea51b45 commit 25cf5ed

12 files changed

Lines changed: 587 additions & 200 deletions

CHANGELOG.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,54 @@
11
# Changelog
22

3+
## [1.39.0.0] - 2026-05-14
4+
5+
## **`buildFetchHandler` ships. Embedders compose overlay routes on top of**
6+
## **gstack's dispatch without forking the browse server.**
7+
8+
The browse daemon's request handler is now exposed as a factory. Embedders pass a `ServerConfig` with their own `authToken`, `browserManager`, and an optional `beforeRoute` hook, and gstack returns a `ServerHandle` with `fetchLocal`, `fetchTunnel`, `shutdown`, and `stopListeners`. The CLI path delegates to the same factory, so externally-observable behavior is unchanged. Auth state is now cfg-driven end-to-end: the module-level `AUTH_TOKEN` constant, its `initRegistry` boot call, the module `validateAuth`, and the module `shutdown` are deleted, and the factory closure owns those responsibilities so the embedder's browser is the one that actually closes on shutdown. The `beforeRoute` hook fires after the tunnel surface filter and before per-route dispatch. Returning a `Response` short-circuits gstack; returning `null` falls through to the gstack route. Invalid bearer resolves to `null` at the hook (per a new security warning in the JSDoc), so overlay code gates on its own trust signal rather than re-implementing bearer auth.
9+
10+
### The numbers that matter
11+
12+
Source: `bun test browse/test/server-factory.test.ts` — 28 tests covering both the type surface (14 pre-existing) and the new factory contract (14 added), all green in 344 ms. Plus 49 token-registry tests, 8 browser-skills-e2e tests, 29 browser-skill-commands tests, 15 skill-token tests — every test that uses `initRegistry` under the new idempotency guard passes. Zero new test regressions versus main across the rest of the suite.
13+
14+
| Surface | Before | After |
15+
|---|---|---|
16+
| `buildFetchHandler(cfg: ServerConfig): ServerHandle` | type-only; throwing factory not exported | live factory used by CLI + ready for gbrowser submodule |
17+
| `beforeRoute` overlay hook | declared in `ServerConfig` since v1.34.0.0, never wired | runs after tunnel filter and before per-route dispatch; short-circuits on `Response`, falls through on `null` |
18+
| Module-level `AUTH_TOKEN` const | `sanitizeAuthToken(process.env.AUTH_TOKEN) ?? randomUUID()` baked at import time, read by 7+ call sites | deleted; cfg.authToken is the single source of truth, threaded through `launchHeaded`, the state file write, and the factory in one pass |
19+
| Module-level `validateAuth` | reads module `AUTH_TOKEN` | deleted; factory-scoped closure reads `cfg.authToken` |
20+
| Module-level `shutdown` | closes module-level `browserManager` (wrong for phoenix) | deleted; factory-scoped `shutdown` closes `cfg.browserManager` |
21+
| `initRegistry` | overwrites `rootToken` unconditionally | idempotent for same token; throws clearly for different token (catches embedder misconfiguration at boot) |
22+
| `__resetRegistry()` test helper | did not exist | mirrors `__resetConnectRateLimit`; lets tests start with a clean registry without tripping the new guard |
23+
| Net diff | — | ~500 LOC moved + 14 new contract tests + 1 idempotency guard + 1 hook wiring + 4 test files updated to use `__resetRegistry` |
24+
25+
The factory deletes the import-time env coupling that v1.34.0.0 documented but couldn't fix on its own.
26+
27+
### What this means for embedders
28+
29+
gbrowser v0.6.0.0 (phoenix overlay) can now ship. Phoenix imports `buildFetchHandler` directly, passes its own `BrowserManager` and an overlay hook, and the same gstack dispatch carries every command. No fork, no duplicated routes, no need to set `process.env.AUTH_TOKEN` before importing. For the CLI, nothing changes.
30+
31+
### Itemized changes
32+
33+
#### Added
34+
- `buildFetchHandler(cfg: ServerConfig): ServerHandle` in `browse/src/server.ts`.
35+
- `beforeRoute` hook wiring in the request handler, with a security warning JSDoc for overlay authors.
36+
- 14 factory contract tests in `browse/test/server-factory.test.ts` (covers ServerHandle shape, auth wiring, validation throws, hook semantics across both surfaces, and registry idempotency / mismatch-throw).
37+
- `__resetRegistry()` test-only export in `browse/src/token-registry.ts` (mirrors `__resetConnectRateLimit`).
38+
- Module-level `activeShutdown` ref so module-level timers and signal handlers route through the factory-scoped shutdown.
39+
40+
#### Changed
41+
- `start()` delegates handler construction to `buildFetchHandler`. Reads env once via `resolveConfigFromEnv()` and threads the resulting `authToken` into `launchHeaded`, the state-file write, and the factory.
42+
- Auth is now cfg-driven end-to-end. Module-level `AUTH_TOKEN` const, `initRegistry(AUTH_TOKEN)` boot call, `validateAuth`, and `shutdown` are deleted; factory closure owns them.
43+
- `initRegistry` is idempotent for same-token re-init; throws clearly for different-token re-init with a message pointing embedders to `buildFetchHandler`.
44+
- Bun.serve return value (`server`) is captured in `start()` (Codex outside-voice finding #8).
45+
- `ServerConfig.beforeRoute` JSDoc updated for contract honesty plus a security warning about not returning privileged data from the hook without re-checking auth.
46+
47+
#### For contributors
48+
- Lifecycle singletons (`LOCAL_LISTEN_PORT`, `tunnelActive`, inspector state, `isShuttingDown`) intentionally stay at module scope; auth state does not. Multi-handle isolation is captured as a follow-up TODO.
49+
- Existing tests that followed `rotateRoot() → initRegistry('fixed-token')` swap to `__resetRegistry() → initRegistry('fixed-token')` so the new mismatch guard doesn't fire.
50+
- Source-pattern tests in `dual-listener.test.ts` and `server-auth.test.ts` updated to match the new identifiers (`handle.fetchLocal`/`handle.fetchTunnel`, `authToken`, `shutdownFn`).
51+
352
## [1.38.1.0] - 2026-05-14
453

554
## **Every review skill ends with a build-actionable task checklist. Federation sync stops dropping office-hours design docs. Surrogate sanitization gets a defense-in-depth second layer on top of v1.38.0.0's choke point.**

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.38.1.0
1+
1.39.0.0

0 commit comments

Comments
 (0)