Skip to content

Commit f4406d7

Browse files
committed
fix(webapp): CORS + allowJWT on public session create + append preflight
Two fixes needed by browser clients hitting the public session API (TriggerChatTransport's direct accessToken path, WebSocket-less session drivers, anything origin'd off the dashboard): - POST /api/v1/sessions: allowJWT: true + corsStrategy: "all" on the action. Pre-fix, the create endpoint only accepted secret-key auth, so any browser-originated sessions.create(...) 401'd. The loader (list) already had these; matches that shape. - POST /realtime/v1/sessions/:session/:io/append: export both { action, loader } so Remix routes the OPTIONS preflight to the route builder's CORS handler. With only { action } exported, the preflight returns 400 'No loader for route' and Chrome surfaces the follow-up POST as net::ERR_FAILED. Same pattern as /api/v1/tasks/:id/trigger (which already exports both). Validated by an end-to-end UI smoke on references/ai-chat: new chat → send → streamed assistant reply in ~4s → second turn reuses the same session + run, lastEventId advances 10 → 21.
1 parent 4cadc19 commit f4406d7

3 files changed

Lines changed: 15 additions & 2 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
CORS + preflight parity on the public session API so browser-side chat transports can hit the session endpoints without being blocked:
7+
8+
- `POST /api/v1/sessions` (session upsert) gains `allowJWT: true` + `corsStrategy: "all"` so PATs minted by `chat.createTriggerAction` (and other browser-side session flows) pass the route's auth + respond to CORS preflight. Previously this route only accepted secret-key auth, which broke any browser-originated `sessions.create(...)` call — including the transport's direct `accessToken` fallback path.
9+
- `POST /realtime/v1/sessions/:session/:io/append` now exports both `{ action, loader }`. The route builder installs the OPTIONS preflight handler on the `loader` even for write-only routes; without the loader export, the CORS preflight was returning 400 ("No loader for route") and Chrome treated the follow-up `POST` as `net::ERR_FAILED`.
10+
11+
Validated by an end-to-end UI smoke against the `references/ai-chat` app: brand-new chat → send → streamed assistant reply in ~4s → follow-up turn on the same session → `lastEventId` advances from 10 → 21.

apps/webapp/app/routes/api.v1.sessions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ const { action } = createActionApiRoute(
9090
body: CreateSessionRequestBody,
9191
method: "POST",
9292
maxContentLength: 1024 * 32, // 32KB — metadata is the only thing that grows
93+
allowJWT: true,
94+
corsStrategy: "all",
9395
},
9496
async ({ authentication, body }) => {
9597
try {

apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const ParamsSchema = z.object({
2727
// below S2's ceiling. See https://s2.dev/docs/limits.
2828
const MAX_APPEND_BODY_BYTES = 1024 * 512;
2929

30-
const { action } = createActionApiRoute(
30+
const { action, loader } = createActionApiRoute(
3131
{
3232
params: ParamsSchema,
3333
method: "POST",
@@ -125,4 +125,4 @@ const { action } = createActionApiRoute(
125125
}
126126
);
127127

128-
export { action };
128+
export { action, loader };

0 commit comments

Comments
 (0)