feat(iios-kernel-client): reconcile to chat-web surface, publish 0.1.1

Brings the shared SDK up to the fuller client chat-web had vendored, so
frontends consume one source of truth instead of copying it:

- types: Message gains senderId/senderName/attachment/parentInteractionId/
  annotations; add Attachment, AnnotationGroup, AnnotationEvent, ThreadSummary,
  SavedItem, LoginResult; MessageEvents gains `annotation`; SocketLike gains
  connected + optional timeout.
- MessageSocket: onConnected, openThread(opts), richer sendMessage(opts)
  (attachment/mentions/parentInteractionId, back-compatible with contentRef),
  pin/save/react via annotate, focus, reconnect-re-subscribe-all.
- RestClient: login, listUsers, listThreads, createThread, listSaved,
  addParticipant.

Bump 0.1.0 -> 0.1.1. Whole monorepo still builds (Message change is additive
for iios-message-web/iios-support-web/demos).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 17:49:25 +05:30
parent 64c498a97a
commit 8c70b6d31f
4 changed files with 177 additions and 16 deletions
+52 -1
View File
@@ -1,5 +1,5 @@
import type { IngestInteractionRequest } from '@insignia/iios-contracts';
import type { Message, InboxItem, InboxState, Ticket, TicketState, CallbackRequest, RouteBinding, RouteDecision, AiArtifact, AiJobResult, Meeting, MeetingActionItem } from './types';
import type { Message, InboxItem, InboxState, Ticket, TicketState, CallbackRequest, RouteBinding, RouteDecision, AiArtifact, AiJobResult, Meeting, MeetingActionItem, ThreadSummary, SavedItem, LoginResult } from './types';
export interface RestConfig {
serviceUrl: string;
@@ -57,6 +57,57 @@ export class RestClient {
return (await r.json()) as InboxItem;
}
// ─── auth + threads (app surface) ─────────────────────────────
/** Dev IdP login (POST /v1/dev/login). A real IdP issues the same JWT — this is the swap point. */
async login(username: string, password: string): Promise<LoginResult> {
const r = await fetch(this.url('/v1/dev/login'), {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (r.status === 401) throw new Error('invalid username or password');
if (!r.ok) throw new Error(`login failed (${r.status}) — is the service running with IIOS_DEV_TOKENS=1?`);
return (await r.json()) as LoginResult;
}
/** Dev directory: known usernames. A real app validates against its user store / MDM. */
async listUsers(): Promise<string[]> {
const r = await fetch(this.url('/v1/dev/users'), { headers: this.headers() });
if (!r.ok) return [];
return ((await r.json()) as { users: string[] }).users;
}
/** Server-authoritative conversation list (works cross-device, shows unread + members). */
async listThreads(): Promise<ThreadSummary[]> {
const r = await fetch(this.url('/v1/threads'), { headers: this.headers() });
if (!r.ok) throw new Error(`listThreads ${r.status}`);
return (await r.json()) as ThreadSummary[];
}
/** Create a thread with generic app attributes (membership hint, creator role, subject/name). */
async createThread(opts: { membership?: string; creatorRole?: string; subject?: string }): Promise<{ threadId: string }> {
return this.post<{ threadId: string }>('/v1/threads', opts);
}
/** My saved messages (personal bookmarks), newest first, with thread context. */
async listSaved(): Promise<SavedItem[]> {
const r = await fetch(this.url('/v1/threads/my-annotations?type=save'), { headers: this.headers() });
if (!r.ok) throw new Error(`listSaved ${r.status}`);
return (await r.json()) as SavedItem[];
}
/** Governed add-participant. A policy 403 (e.g. DM cap) surfaces its reason. */
async addParticipant(threadId: string, userId: string): Promise<void> {
const r = await fetch(this.url(`/v1/threads/${threadId}/participants`), {
method: 'POST',
headers: this.headers(),
body: JSON.stringify({ userId }),
});
if (r.ok) return;
const body = (await r.json().catch(() => ({}))) as { message?: string };
throw new Error(body.message ?? `could not add member (${r.status})`);
}
// ─── support ──────────────────────────────────────────────────
async createTicket(body: { subject: string; priority?: string; threadId?: string }): Promise<Ticket> {
return this.post<Ticket>('/v1/support/tickets', body);