-
Notifications
You must be signed in to change notification settings - Fork 0
gateway integration
How the SPA talks to the NoETL gateway. Three modules, one SSE channel, three frame families, and a small set of REST endpoints.
Source-of-truth files:
-
src/api/gatewaySession.ts— session lifecycle. -
src/api/noetlClient.ts— playbook execution + SSE callbacks. -
src/api/calendarSubscription.ts— calendar live updates via playbook transport andcalendar.event.touchedSSE signal.
Browser ───────► POST /api/auth/login ──► gateway issues session_token
Browser ───────► POST /api/auth/validate ──► gateway confirms session_token
Browser ───────► GET /events?session_token=… ──► SSE channel opens
Server pushes:
- message (init, client_id)
- ping (heartbeat)
- playbook/result
- playbook/state
(incl. calendar.event.touched)
Browser ───────► POST /graphql ──► executePlaybook mutation
Response: { requestId, executionId }
Browser ◄─────── playbook/result frame ◄── on completion
(or playbook/state frames) (or lifecycle frames)
Every request from the browser carries the session token. Authorization is checked at the gateway. The SPA never holds a database credential or a third-party API token.
gatewaySession.ts covers three flows:
The SPA logs the user into Auth0 (@auth0/auth0-react),
receives an ID token, and exchanges it with the gateway:
const response = await fetch(`${gateway}/api/auth/login`, {
method: 'POST',
body: JSON.stringify({ id_token })
});
// response: { session_token, expires_at, user_id, ... }The gateway verifies the Auth0 token against the Auth0 JWKS,
upserts the user, creates a session_token, and returns it.
The SPA stores the session_token in localStorage.
On app load, the SPA calls POST /api/auth/validate with the
stored session_token. If the gateway returns OK, the user
is signed in. If not, the SPA clears storage and routes to
the Auth0 login page.
A standard Auth0 SDK call, plus clearing the
session_token from storage. The gateway-side session is
allowed to expire naturally; there is no explicit
"invalidate session" call today.
See Auth and session for the deeper flow and how to swap Auth0 for another identity provider.
noetlClient.ts exposes:
executePlaybook(path, workload, options): Promise<...>Internally:
- The SPA opens (or reuses) the SSE channel via
connectSSE(session_token). - The SPA waits for the SSE channel to confirm
client_id. - The SPA POSTs to
/graphqlwith theexecutePlaybookmutation, including theclient_id(so the gateway knows which SSE client to deliver the callback to). - The gateway dispatches the playbook to NoETL via
/api/execute. - NoETL runs the playbook; on completion, the gateway forwards
a
playbook/resultSSE frame to the client. - The SPA's
pendingCallbacksmap resolves the corresponding promise.
If the SSE callback does not arrive within CALLBACK_GRACE_MS
(currently 30s), the SPA falls back to waitForExecution
which polls /api/executions/{id} every 1.5s, up to 200
attempts (5 min). This path will be removed once
playbook/state SSE coverage proves out in production.
Frames the gateway emits on /events:
Frame type / event |
Payload | Sent when |
|---|---|---|
message (init) |
{ result: { clientId } } |
On connection, once. |
ping |
{ heartbeat: true } |
Periodic keep-alive. |
playbook/result |
{ requestId, executionId, status, data, error? } |
A playbook execution completed and the gateway has its final result (delivered via /api/internal/callback). |
playbook/state |
{ execution_id, event_type: "step.exit | playbook.completed | playbook.failed | calendar.event.touched", step_name, status, at } |
A NoETL execution lifecycle event matching the gateway's FORWARDED_EVENT_TYPES allowlist was observed on NATS. |
Each frame is filtered client-side by listening on the
matching addEventListener name.
The calendar widget reads and refreshes via executePlaybook against
the travel/playbooks/catalog/calendar/list read playbook. This is the
final shape as of Round 3 of the Firestore-removal work
(noetl/ai-meta#23).
src/api/calendarSubscription.ts implements the transport:
subscribeToCalendarEvents(trip_id, events_path, onItems, options?)The module:
- On mount, calls
executePlaybook( "travel/playbooks/catalog/calendar/list", { trip_id, thread_path, user_uid }). Waits for theplaybook/resultframe and feedsdisplay_eventstoonItems. - Adds a
playbook/stateSSE listener. Triggers a re-read when:-
event_type === "calendar.event.touched"— the specific signal the itinerary-planner emits after writing a calendar event, forwarded by the gateway as of v2.12.0 / noetl/ai-meta#25. -
event_type === "playbook.completed"— fallback for turns that finish without writing calendar events (e.g. clears loading state).
-
- Returns an unsubscribe function that removes the SSE listener and
aborts any in-flight
executePlaybookcall.
events_path is now optional in CalendarViewPayload and is no longer
emitted by the orchestrator. calendarSubscription.ts handles absent
events_path gracefully (falls back to deriving user_uid / thread_path
from context, or emits a console.warn if neither is available).
This transport satisfies the Ephemeral Blueprints gatekeeper rule: the gateway stays stateless, data access happens inside a playbook under that playbook's policy block, and the SPA holds no database credential.
For pushback discipline:
- The SPA never calls Auth0 SPA "authorization endpoint" to
fetch a token for an API directly. The gateway is the API;
the SPA holds only a gateway
session_token. - The SPA never holds a database client. The
firebasedependency was removed in #50; the bundle no longer shipsfirebase/firestore. - The SPA never reads or writes a third-party API token. Even
VITE_GOOGLE_MAPS_KEYis a restricted widget-only key for the Maps embed, not a server-side auth. - The SPA never polls an arbitrary NoETL endpoint. The only
polling that exists is the cold-start fallback in
waitForExecution, gated to error cases.
The gateway-integration modules have unit coverage with mocked SSE servers:
Run with npm test.
Note: src/api/gatewaySubscriptions.test.ts was removed in Round 3
(noetl/ai-meta#23) along
with gatewaySubscriptions.ts itself.
- Architecture — where the gateway sits in the layering.
- Auth and session — identity provider swap.
- Gateway repo:
noetl/gateway. - Foundational principle: Ephemeral Blueprints — gateway as gatekeeper only.
Travel SPA
Architecture
- Architecture
- Widget contract
- Business data via playbooks
- Playbook: itinerary-planner
- Playbook: calendar/list
Integration
Operations
See also
- noetl wiki (app)
- ops wiki (deploy)
- Ephemeral Blueprints