first commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Server-side client for the real IIOS service (see D:\iios\docs\IIOS_API_AND_SDK_GUIDE.md).
|
||||
* Runs only in BFF route handlers — never shipped to the browser. Auth uses a dev token
|
||||
* minted from /v1/dev/token (requires the IIOS service running with IIOS_DEV_TOKENS=1);
|
||||
* in production swap mintToken() for a real Session-Broker exchange.
|
||||
*/
|
||||
|
||||
const IIOS_URL = process.env.IIOS_URL ?? "http://localhost:3200";
|
||||
const APP_ID = process.env.IIOS_APP_ID ?? "portal-demo";
|
||||
const USER_ID = process.env.IIOS_USER_ID ?? "james";
|
||||
const ORG_ID = process.env.IIOS_ORG_ID ?? "org_A";
|
||||
|
||||
let cachedToken: { token: string; exp: number } | null = null;
|
||||
|
||||
async function mintToken(): Promise<string> {
|
||||
// reuse for ~100 min (IIOS dev tokens live 2h)
|
||||
if (cachedToken && cachedToken.exp > Date.now()) return cachedToken.token;
|
||||
const res = await fetch(`${IIOS_URL}/v1/dev/token`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ appId: APP_ID, userId: USER_ID, orgId: ORG_ID }),
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!res.ok) throw new Error(`IIOS token mint failed: ${res.status} ${await res.text().catch(() => "")}`);
|
||||
const { token } = (await res.json()) as { token: string };
|
||||
cachedToken = { token, exp: Date.now() + 100 * 60_000 };
|
||||
return token;
|
||||
}
|
||||
|
||||
async function req<T>(path: string, init?: RequestInit & { idempotencyKey?: string }): Promise<T> {
|
||||
const token = await mintToken();
|
||||
const headers: Record<string, string> = {
|
||||
"content-type": "application/json",
|
||||
authorization: `Bearer ${token}`,
|
||||
...(init?.headers as Record<string, string> | undefined),
|
||||
};
|
||||
if (init?.idempotencyKey) headers["idempotency-key"] = init.idempotencyKey;
|
||||
const res = await fetch(`${IIOS_URL}${path}`, { ...init, headers, cache: "no-store" });
|
||||
if (!res.ok) throw new Error(`IIOS ${res.status} ${path}: ${await res.text().catch(() => "")}`);
|
||||
if (res.status === 204) return undefined as T;
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
export const iios = {
|
||||
info: { url: IIOS_URL, appId: APP_ID, userId: USER_ID, orgId: ORG_ID },
|
||||
health: () => req<{ status: string; db: boolean }>("/health"),
|
||||
getThreadMessages: (threadId: string) => req<any>(`/v1/threads/${threadId}/messages`),
|
||||
sendMessage: (threadId: string, content: string, idem?: string) =>
|
||||
req<any>(`/v1/threads/${threadId}/messages`, { method: "POST", body: JSON.stringify({ content }), idempotencyKey: idem }),
|
||||
listInbox: (state?: string) => req<any>(`/v1/inbox/items${state ? `?state=${state}` : ""}`),
|
||||
patchInbox: (id: string, state: string, reason?: string) =>
|
||||
req<any>(`/v1/inbox/items/${id}`, { method: "PATCH", body: JSON.stringify({ state, reason }) }),
|
||||
listTickets: (scope?: string) => req<any>(`/v1/support/tickets${scope ? `?scope=${scope}` : ""}`),
|
||||
patchTicket: (id: string, state: string, reason?: string) =>
|
||||
req<any>(`/v1/support/tickets/${id}`, { method: "PATCH", body: JSON.stringify({ state, reason }) }),
|
||||
createTicket: (subject: string, priority?: string, threadId?: string) =>
|
||||
req<any>(`/v1/support/tickets`, { method: "POST", body: JSON.stringify({ subject, priority, threadId }) }),
|
||||
escalate: (threadId: string, subject?: string) =>
|
||||
req<any>(`/v1/support/escalate`, { method: "POST", body: JSON.stringify({ threadId, subject }) }),
|
||||
};
|
||||
|
||||
export async function iiosReachable(): Promise<boolean> {
|
||||
try {
|
||||
const h = await iios.health();
|
||||
return !!h?.status;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user