diff options
| author | Adam Malczewski <[email protected]> | 2026-06-06 23:30:38 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-06 23:30:38 +0900 |
| commit | 812621ca2f93d2aaeda98a2d5b5ab6bc8568d596 (patch) | |
| tree | 99cebe8b2bbb46fb3f9d647ae0f5064589774e39 | |
| parent | 395d7565b46416e9914db099b9a4c226f4c95648 (diff) | |
| download | dispatch-812621ca2f93d2aaeda98a2d5b5ab6bc8568d596.tar.gz dispatch-812621ca2f93d2aaeda98a2d5b5ab6bc8568d596.zip | |
feat(transport-http): wildcard CORS + bump contract pkgs to 0.1.0 (FE Slice 2 handoff)
Unblock the browser frontend (Vite origin :24204 -> HTTP backend :24203):
- transport-http: wildcard CORS via hono/cors on all routes
(Access-Control-Allow-Origin: *, Allow-Methods GET/POST/OPTIONS,
Allow-Headers Content-Type) + OPTIONS preflight (204). Headers present on
the streamed POST /chat NDJSON response too. +4 app.fetch tests.
- wire / transport-contract / ui-contract: 0.0.0 -> 0.1.0 as the FE-consumable
baseline (semver convention ยง2.9: major = cross-repo fan-out signal).
Verified live: OPTIONS /chat -> 204 with CORS headers; GET /models -> 200 with
Access-Control-Allow-Origin: *. typecheck clean, 502 vitest + 89 bun, biome clean.
| -rw-r--r-- | packages/transport-contract/package.json | 2 | ||||
| -rw-r--r-- | packages/transport-http/src/app.test.ts | 50 | ||||
| -rw-r--r-- | packages/transport-http/src/app.ts | 10 | ||||
| -rw-r--r-- | packages/ui-contract/package.json | 2 | ||||
| -rw-r--r-- | packages/wire/package.json | 2 |
5 files changed, 63 insertions, 3 deletions
diff --git a/packages/transport-contract/package.json b/packages/transport-contract/package.json index 03efc2b..0c097db 100644 --- a/packages/transport-contract/package.json +++ b/packages/transport-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dispatch/transport-contract", - "version": "0.0.0", + "version": "0.1.0", "type": "module", "private": true, "main": "dist/index.js", diff --git a/packages/transport-http/src/app.test.ts b/packages/transport-http/src/app.test.ts index 2adf4db..aa47dce 100644 --- a/packages/transport-http/src/app.test.ts +++ b/packages/transport-http/src/app.test.ts @@ -563,3 +563,53 @@ describe("GET /conversations/:id logging", () => { expect(infoLogs[0]?.attrs?.count).toBe(2); }); }); + +describe("CORS", () => { + function createTestApp() { + return createApp({ + conversationStore: createFakeConversationStore(), + orchestrator: createFakeOrchestrator([ + { type: "done", conversationId: "conv1", turnId: "turn1", reason: "stop" }, + ]), + credentialStore: createFakeCredentialStore(["opencode/m1"]), + }); + } + + it("POST /chat response carries Access-Control-Allow-Origin: *", async () => { + const app = createTestApp(); + const res = await app.request("/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message: "hi", conversationId: "conv1" }), + }); + expect(res.status).toBe(200); + expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*"); + }); + + it("GET /models response carries the CORS headers", async () => { + const app = createTestApp(); + const res = await app.request("/models"); + expect(res.status).toBe(200); + expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*"); + expect(res.headers.get("Access-Control-Expose-Headers")).toBeDefined(); + }); + + it("GET /conversations/:id response carries the CORS headers", async () => { + const app = createTestApp(); + const res = await app.request("/conversations/conv1"); + expect(res.status).toBe(200); + expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*"); + expect(res.headers.get("Access-Control-Expose-Headers")).toBeDefined(); + }); + + it("OPTIONS preflight for /chat returns 204 with Allow-Methods + Allow-Headers", async () => { + const app = createTestApp(); + const res = await app.request("/chat", { method: "OPTIONS" }); + expect(res.status).toBe(204); + expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*"); + expect(res.headers.get("Access-Control-Allow-Methods")).toContain("GET"); + expect(res.headers.get("Access-Control-Allow-Methods")).toContain("POST"); + expect(res.headers.get("Access-Control-Allow-Methods")).toContain("OPTIONS"); + expect(res.headers.get("Access-Control-Allow-Headers")).toContain("Content-Type"); + }); +}); diff --git a/packages/transport-http/src/app.ts b/packages/transport-http/src/app.ts index bd9db4e..a8edc71 100644 --- a/packages/transport-http/src/app.ts +++ b/packages/transport-http/src/app.ts @@ -1,6 +1,7 @@ import type { AgentEvent, Logger } from "@dispatch/kernel"; import type { ConversationHistoryResponse, ModelsResponse } from "@dispatch/transport-contract"; import { Hono } from "hono"; +import { cors } from "hono/cors"; import { isParseError, isSinceSeqError, @@ -45,6 +46,15 @@ export function createApp(opts: CreateServerOptions): Hono { const log = opts.logger ?? noopLogger; const generateId = opts.generateId ?? (() => crypto.randomUUID()); + app.use( + "*", + cors({ + origin: "*", + allowMethods: ["GET", "POST", "OPTIONS"], + allowHeaders: ["Content-Type"], + }), + ); + app.get("/health", (c) => c.json({ ok: true })); app.get("/conversations/:id", async (c) => { diff --git a/packages/ui-contract/package.json b/packages/ui-contract/package.json index e1f4c35..e7dd93f 100644 --- a/packages/ui-contract/package.json +++ b/packages/ui-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dispatch/ui-contract", - "version": "0.0.0", + "version": "0.1.0", "type": "module", "private": true, "main": "dist/index.js", diff --git a/packages/wire/package.json b/packages/wire/package.json index 2893e79..4d72a81 100644 --- a/packages/wire/package.json +++ b/packages/wire/package.json @@ -1,6 +1,6 @@ { "name": "@dispatch/wire", - "version": "0.0.0", + "version": "0.1.0", "type": "module", "private": true, "main": "dist/index.js", |
