Files
message-inbox-web-frontend-sdk/docs/IIOS_INTEGRATION.md
T
2026-07-17 21:48:37 +05:30

135 lines
6.2 KiB
Markdown

# 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.