6.2 KiB
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
- Run IIOS (from
D:\iios, needs Docker for Postgres):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 - Point this app at it (
apps/web/.env.local):BFF_BACKEND="iios" IIOS_URL="http://localhost:3200" IIOS_APP_ID="portal-demo" IIOS_USER_ID="james" IIOS_ORG_ID="org_A" pnpm dev→ the BFF now mints a dev token and proxies IIOS.IIOS_*are server-only (noNEXT_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):
# 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.tswas 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, sobootstrap/listChannelsstay on mock (the demo keeps its rich sidebar) while messages/inbox/tickets read from IIOS. Seed IIOS (viaPOST /v1/interactions/ingestor 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:
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.