# Wiring the demo to the real IIOS service This app talks to a pluggable **BFF backend**. Today it defaults to the in-memory mock; an **IIOS adapter is implemented** and switches on with one env var. This doc explains what's built, how to turn it on, and what still needs IIOS-side endpoints. > Reference: `D:\iios\docs\IIOS_API_AND_SDK_GUIDE.md` (endpoints, auth, DTOs). ## The seam Every BFF route calls `getBackend()` — never the store directly. `BFF_BACKEND` picks the impl: ``` apps/web/lib/backend/ Backend.ts the interface every route uses (all async) mock.ts MockBackend — wraps the in-memory store (default) iios.ts IiosBackend — proxies the real IIOS service index.ts getBackend() → mock | iios (from BFF_BACKEND) apps/web/lib/iios/ client.ts server-only IIOS REST client (dev-token auth, lazy token mint) map.ts IIOS → SDK type mappers (toMessage / toInboxItem / toTicket) ``` The SDK, hooks, and every UI component are unchanged between mock and IIOS — all translation lives in `lib/iios/map.ts`. ## Turn it on 1. **Run IIOS** (from `D:\iios`, needs Docker for Postgres): ```bash docker compose up -d # Postgres on :5434 pnpm install pnpm -F @insignia/iios-service prisma:generate pnpm -F @insignia/iios-service prisma:migrate cd packages/iios-service && IIOS_DEV_TOKENS=1 pnpm start # → :3200 ``` 2. **Point this app at it** (`apps/web/.env.local`): ```bash BFF_BACKEND="iios" IIOS_URL="http://localhost:3200" IIOS_APP_ID="portal-demo" IIOS_USER_ID="james" IIOS_ORG_ID="org_A" ``` 3. `pnpm dev` → the BFF now mints a dev token and proxies IIOS. `IIOS_*` are **server-only** (no `NEXT_PUBLIC_`), so the token never reaches the browser. ## ✅ Verified live (no Docker required) The IIOS path has been run **end-to-end** against a real IIOS service + real Postgres, and the round-trip through this app's BFF was confirmed (a real ingested message came back through `GET /api/bff/channels/:threadId/messages`, and `/api/bff/support/tickets` returned IIOS's real data instead of the mock set). Docker isn't needed — a native PostgreSQL works. Exact steps that worked on Windows (PostgreSQL 18 already installed): ```bash # 1. Create the iios role + database on the existing Postgres (port 5432) # (as the postgres superuser) psql -U postgres -c "CREATE ROLE iios LOGIN PASSWORD 'iios' CREATEDB;" psql -U postgres -c "CREATE DATABASE iios OWNER iios;" # 2. Point IIOS at it — packages/iios-service/.env # DATABASE_URL="postgresql://iios:iios@localhost:5432/iios?schema=public" # (the repo default is :5434 via docker-compose; :5432 is fine too) # JWT_SECRET=... · APP_SECRETS={"portal-demo":"dev-secret"} · IIOS_DEV_TOKENS=1 # 3. From D:\iios — allow prisma/esbuild builds in pnpm-workspace.yaml, then: pnpm install pnpm -r build node packages/iios-service/node_modules/prisma/build/index.js generate --schema packages/iios-service/prisma/schema.prisma node packages/iios-service/node_modules/prisma/build/index.js migrate deploy --schema packages/iios-service/prisma/schema.prisma node packages/iios-service/dist/main.js # → iios-service listening on :3200 # 4. Point THIS app at it — apps/web/.env.local BFF_BACKEND=iios IIOS_URL=http://localhost:3200 # then `pnpm dev` (restart) — the BFF now mints a dev token and proxies IIOS. ``` > The mapper in `map.ts` was validated against the real IIOS message shape > (`{interactionId, actorId, occurredAt, parts:[{kind,bodyText}]}`). A **fresh IIOS DB is empty** > and IIOS has no channel-directory endpoint, so `bootstrap`/`listChannels` stay on mock (the demo > keeps its rich sidebar) while messages/inbox/tickets read from IIOS. Seed IIOS (via > `POST /v1/interactions/ingest` or its smoke scripts) to see live data flow through. ## What's wired vs. delegated | BFF method | IIOS backing | Notes | |---|---|---| | `channelMessages` / `thread` | `GET /v1/threads/:id/messages` | maps parts→body, parent links | | `send` | `POST /v1/threads/:id/messages` (+ `idempotency-key`) | | | `listInbox` / `patchInbox` | `GET`/`PATCH /v1/inbox/items` | | | `listTickets` / `getTicket` / `patchTicket` | `/v1/support/tickets` | `getTicket` lists+finds (no single-GET documented) | | `replyTicket` | `POST` to the ticket's linked thread | | | `bootstrap` / `listChannels` / `createChannel` | **delegated to mock** | IIOS has no portal/channel-directory endpoint yet; build one, or compose from `/v1/threads` | | `react` / `pin` / `save` | **delegated to mock** | IIOS has no reaction/pin/save endpoints; keep local or add them | | `search` / `notifications` / `availability` | **delegated to mock** | add IIOS endpoints then swap | Delegating keeps the UI fully functional while you fill the gaps — each is a small, isolated change in `iios.ts`. ## Auth `lib/iios/client.ts#mintToken()` calls `POST /v1/dev/token` and caches the JWT (~100 min). For production, replace `mintToken()` with a real Session-Broker exchange (PPT), keep it server-side, and attach `Authorization: Bearer …` exactly as now. ## Shape mapping Keep this app's `types.ts` as the UI contract; translate in `map.ts` (already defensive about field names). Example: ```ts export function toMessage(m: any, channelId: string): Message { const text = m.content ?? m.body ?? m.parts?.find(p => (p.kind ?? p.partType) === "TEXT")?.bodyText ?? ""; return { id: String(m.id ?? m.interactionId), channelId, authorId: String(m.senderActorId ?? m.actorId ?? "unknown"), ts: Date.parse(m.sentAt ?? m.occurredAt) || Date.now(), body: String(text), parentId: m.parentInteractionId ?? null, reactions: [], deliveryState: "sent" }; } ``` ## Tenancy & safety (carried by IIOS) IIOS enforces tenant isolation (cross-tenant → 403), fail-closed policy/consent gates, and idempotency. The BFF just forwards the scoped token — don't reimplement those client-side. The UI's optimistic updates already tolerate a rejected write (surface an error, refetch). ## Realtime (optional upgrade) Swap the mock `useTyping` + refetch for the IIOS Socket.IO client (`iios-kernel-client`'s `MessageSocket`) and feed `message`/`receipt` events into the same hook state. The component tree doesn't change.