feat(sdk): P4.5 @insignia/iios-support-web (composes message-web + support REST)

SupportProvider (wraps MessageProvider + support client); useEscalate, useTickets/
useAssignedTickets (+createTicket/patch), useCallbackRequest, useAvailability;
re-exports useThread/useMessages. Boundary allowlist: support-web -> contracts+
kernel-client+message-web+inbox-web.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 12:00:46 +05:30
parent e9e61b52bc
commit c53258c8eb
7 changed files with 181 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@insignia/iios-support-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:*",
"@insignia/iios-message-web": "workspace:*"
},
"peerDependencies": {
"react": ">=18"
},
"devDependencies": {
"@types/react": "^19.0.0",
"react": "^19.0.0",
"tsup": "^8.3.5",
"typescript": "^5.7.3"
}
}
+11
View File
@@ -0,0 +1,11 @@
export {
SupportProvider,
useEscalate,
useTickets,
useAssignedTickets,
useCallbackRequest,
useAvailability,
} from './react';
// Re-export the chat hooks so a support app imports everything from one place.
export { useThread, useMessages } from '@insignia/iios-message-web';
export type { Ticket, TicketState, CallbackRequest, Message } from '@insignia/iios-kernel-client';
+91
View File
@@ -0,0 +1,91 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { MessageProvider } from '@insignia/iios-message-web';
import { RestClient, type Ticket, type CallbackRequest, type TicketState } from '@insignia/iios-kernel-client';
const SupportContext = createContext<RestClient | null>(null);
/** Wraps MessageProvider (chat) and provides a support REST client. */
export function SupportProvider({
serviceUrl,
token,
children,
}: {
serviceUrl: string;
token: string;
children: React.ReactNode;
}): React.ReactElement {
const client = useMemo(() => new RestClient({ serviceUrl, token }), [serviceUrl, token]);
return (
<SupportContext.Provider value={client}>
<MessageProvider serviceUrl={serviceUrl} token={token}>
{children}
</MessageProvider>
</SupportContext.Provider>
);
}
function useSupportClient(): RestClient {
const c = useContext(SupportContext);
if (!c) throw new Error('support hooks must be used within <SupportProvider>');
return c;
}
/** Escalate a chat thread to support (creates + queues a ticket). */
export function useEscalate(): (threadId: string, subject?: string) => Promise<Ticket> {
const client = useSupportClient();
return (threadId, subject) => client.escalate(threadId, subject);
}
/** The caller's tickets (scope 'mine') or an agent's assigned tickets ('assigned'). */
export function useTickets(scope: 'mine' | 'assigned' = 'mine', pollMs = 3000): {
tickets: Ticket[];
refresh: () => Promise<void>;
createTicket: (subject: string, opts?: { priority?: string; threadId?: string }) => Promise<Ticket>;
patch: (id: string, state: TicketState) => Promise<void>;
} {
const client = useSupportClient();
const [tickets, setTickets] = useState<Ticket[]>([]);
const refresh = useCallback(async () => {
try {
setTickets(await client.listTickets(scope));
} catch {
/* transient */
}
}, [client, scope]);
useEffect(() => {
void refresh();
const t = setInterval(() => void refresh(), pollMs);
return () => clearInterval(t);
}, [refresh, pollMs]);
const createTicket = async (subject: string, opts?: { priority?: string; threadId?: string }) => {
const t = await client.createTicket({ subject, ...opts });
await refresh();
return t;
};
const patch = async (id: string, state: TicketState) => {
await client.patchTicket(id, state);
await refresh();
};
return { tickets, refresh, createTicket, patch };
}
/** Agent convenience: their assigned tickets. */
export function useAssignedTickets(pollMs = 3000) {
return useTickets('assigned', pollMs);
}
export function useCallbackRequest(): (input: {
preferChannel: string;
preferredTime?: string;
notes?: string;
ticketId?: string;
}) => Promise<CallbackRequest> {
const client = useSupportClient();
return (input) => client.requestCallback(input);
}
/** Agent presence: set AVAILABLE/OFFLINE. */
export function useAvailability(): (state: string) => Promise<unknown> {
const client = useSupportClient();
return (state) => client.setAvailability(state);
}
+14
View File
@@ -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"]
}
+9
View File
@@ -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-message-web', '@insignia/iios-kernel-client'],
});