From 0a1631dbc0397c0cbd5a57ec31e08496f8acb488 Mon Sep 17 00:00:00 2001 From: maaz519 Date: Wed, 1 Jul 2026 04:30:42 +0530 Subject: [PATCH] feat(sdk): P3.4 @insignia/iios-inbox-web + kernel-client inbox REST RestClient.listInboxItems/patchInboxItem + InboxItem type; iios-inbox-web InboxProvider + useInbox (poll + snooze/done/reopen). Boundary allowlist extended. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/iios-inbox-web/package.json | 27 ++++++++++ packages/iios-inbox-web/src/index.ts | 2 + packages/iios-inbox-web/src/react.tsx | 63 ++++++++++++++++++++++++ packages/iios-inbox-web/tsconfig.json | 14 ++++++ packages/iios-inbox-web/tsup.config.ts | 9 ++++ packages/iios-kernel-client/src/rest.ts | 19 ++++++- packages/iios-kernel-client/src/types.ts | 17 +++++++ pnpm-lock.yaml | 19 +++++++ scripts/check-import-boundary.mjs | 1 + 9 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 packages/iios-inbox-web/package.json create mode 100644 packages/iios-inbox-web/src/index.ts create mode 100644 packages/iios-inbox-web/src/react.tsx create mode 100644 packages/iios-inbox-web/tsconfig.json create mode 100644 packages/iios-inbox-web/tsup.config.ts diff --git a/packages/iios-inbox-web/package.json b/packages/iios-inbox-web/package.json new file mode 100644 index 0000000..81d1c57 --- /dev/null +++ b/packages/iios-inbox-web/package.json @@ -0,0 +1,27 @@ +{ + "name": "@insignia/iios-inbox-web", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } }, + "files": ["dist"], + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@insignia/iios-kernel-client": "workspace:*" + }, + "peerDependencies": { + "react": ">=18" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "react": "^19.0.0", + "tsup": "^8.3.5", + "typescript": "^5.7.3" + } +} diff --git a/packages/iios-inbox-web/src/index.ts b/packages/iios-inbox-web/src/index.ts new file mode 100644 index 0000000..d5b9cc4 --- /dev/null +++ b/packages/iios-inbox-web/src/index.ts @@ -0,0 +1,2 @@ +export { InboxProvider, useInbox } from './react'; +export type { InboxItem, InboxState } from '@insignia/iios-kernel-client'; diff --git a/packages/iios-inbox-web/src/react.tsx b/packages/iios-inbox-web/src/react.tsx new file mode 100644 index 0000000..ae49793 --- /dev/null +++ b/packages/iios-inbox-web/src/react.tsx @@ -0,0 +1,63 @@ +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { RestClient, type InboxItem, type InboxState } from '@insignia/iios-kernel-client'; + +const InboxContext = createContext(null); + +/** Provides an inbox REST client to the inbox hooks. */ +export function InboxProvider({ + serviceUrl, + token, + children, +}: { + serviceUrl: string; + token: string; + children: React.ReactNode; +}): React.ReactElement { + const client = useMemo(() => new RestClient({ serviceUrl, token }), [serviceUrl, token]); + return {children}; +} + +function useClient(): RestClient { + const c = useContext(InboxContext); + if (!c) throw new Error('useInbox must be used within '); + return c; +} + +/** The caller's inbox items + actions. Polls on an interval (default 3s). */ +export function useInbox(opts?: { state?: InboxState; pollMs?: number }): { + items: InboxItem[]; + refresh: () => Promise; + snooze: (id: string) => Promise; + done: (id: string) => Promise; + reopen: (id: string) => Promise; +} { + const client = useClient(); + const [items, setItems] = useState([]); + + const refresh = useCallback(async () => { + try { + setItems(await client.listInboxItems(opts?.state)); + } catch { + /* transient — keep last items */ + } + }, [client, opts?.state]); + + useEffect(() => { + void refresh(); + const t = setInterval(() => void refresh(), opts?.pollMs ?? 3000); + return () => clearInterval(t); + }, [refresh, opts?.pollMs]); + + const patch = async (id: string, state: InboxState): Promise => { + await client.patchInboxItem(id, { state }); + await refresh(); + }; + + return { + items, + refresh, + snooze: (id) => patch(id, 'SNOOZED'), + done: (id) => patch(id, 'DONE'), + reopen: (id) => patch(id, 'OPEN'), + }; +} diff --git a/packages/iios-inbox-web/tsconfig.json b/packages/iios-inbox-web/tsconfig.json new file mode 100644 index 0000000..4a4d5c4 --- /dev/null +++ b/packages/iios-inbox-web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "jsx": "react-jsx", + "types": ["react"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"] +} diff --git a/packages/iios-inbox-web/tsup.config.ts b/packages/iios-inbox-web/tsup.config.ts new file mode 100644 index 0000000..1cf9cbf --- /dev/null +++ b/packages/iios-inbox-web/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + clean: true, + external: ['react', 'react-dom', '@insignia/iios-kernel-client'], +}); diff --git a/packages/iios-kernel-client/src/rest.ts b/packages/iios-kernel-client/src/rest.ts index 64c0808..5f5e724 100644 --- a/packages/iios-kernel-client/src/rest.ts +++ b/packages/iios-kernel-client/src/rest.ts @@ -1,5 +1,5 @@ import type { IngestInteractionRequest } from '@insignia/iios-contracts'; -import type { Message } from './types'; +import type { Message, InboxItem, InboxState } from './types'; export interface RestConfig { serviceUrl: string; @@ -40,6 +40,23 @@ export class RestClient { return (await r.json()) as Message; } + async listInboxItems(state?: InboxState): Promise { + const q = state ? `?state=${encodeURIComponent(state)}` : ''; + const r = await fetch(this.url(`/v1/inbox/items${q}`), { headers: this.headers() }); + if (!r.ok) throw new Error(`listInboxItems ${r.status}`); + return (await r.json()) as InboxItem[]; + } + + async patchInboxItem(id: string, body: { state: InboxState; reason?: string }): Promise { + const r = await fetch(this.url(`/v1/inbox/items/${id}`), { + method: 'PATCH', + headers: this.headers(), + body: JSON.stringify(body), + }); + if (!r.ok) throw new Error(`patchInboxItem ${r.status}`); + return (await r.json()) as InboxItem; + } + async ingest(body: IngestInteractionRequest, idempotencyKey: string): Promise { const r = await fetch(this.url('/v1/interactions/ingest'), { method: 'POST', diff --git a/packages/iios-kernel-client/src/types.ts b/packages/iios-kernel-client/src/types.ts index 1387d18..8d70be0 100644 --- a/packages/iios-kernel-client/src/types.ts +++ b/packages/iios-kernel-client/src/types.ts @@ -32,6 +32,23 @@ export interface MessageEvents { typing: (e: TypingEvent) => void; } +export type InboxState = 'OPEN' | 'SNOOZED' | 'DONE' | 'ARCHIVED' | 'CANCELLED' | 'STALE'; + +export interface InboxItem { + id: string; + ownerActorId: string; + kind: string; + state: InboxState; + title: string; + summary?: string | null; + priority: string; + threadId?: string | null; + sourceInteractionId?: string | null; + traceId?: string | null; + createdAt: string; + updatedAt: string; +} + /** Minimal socket surface so the facade can be unit-tested with a fake. */ export interface SocketLike { on(event: string, handler: (...args: unknown[]) => void): unknown; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d783e5..9182184 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,25 @@ importers: packages/iios-contracts: {} + packages/iios-inbox-web: + dependencies: + '@insignia/iios-kernel-client': + specifier: workspace:* + version: link:../iios-kernel-client + devDependencies: + '@types/react': + specifier: ^19.0.0 + version: 19.2.17 + react: + specifier: ^19.0.0 + version: 19.2.7 + tsup: + specifier: ^8.3.5 + version: 8.5.1(jiti@2.7.0)(postcss@8.5.16)(typescript@5.9.3) + typescript: + specifier: ^5.7.3 + version: 5.9.3 + packages/iios-kernel-client: dependencies: '@insignia/iios-contracts': diff --git a/scripts/check-import-boundary.mjs b/scripts/check-import-boundary.mjs index 481679e..d869cb6 100644 --- a/scripts/check-import-boundary.mjs +++ b/scripts/check-import-boundary.mjs @@ -19,6 +19,7 @@ const ALLOWED = { '@insignia/iios-kernel-client': ['@insignia/iios-contracts'], '@insignia/iios-service': ['@insignia/iios-contracts', '@insignia/iios-testkit'], '@insignia/iios-message-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'], + '@insignia/iios-inbox-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'], }; const KNOWN = Object.keys(ALLOWED);