diff --git a/package-lock.json b/package-lock.json index 714de0f..d660907 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1030,7 +1030,7 @@ "node_modules/@insignia/iios-messaging-ui": { "version": "0.1.0", "resolved": "file:../iios/packages/iios-messaging-ui/insignia-iios-messaging-ui-0.1.0.tgz", - "integrity": "sha512-6pSVWACjex1sXWzVqx+42rM6vgWEJ+1ud5y3DLKogJiQmfgFZgCB8vTw0PN/5FECugXC7iGuc9rV9RsHsP+EDw==", + "integrity": "sha512-B6Vzb75lBv4W3ZuKE4muMSz/OyUrE0NxImUI6sHaCBby5xveHVyanPiwkxiaqg9zZPYQMYiZRMONYwlNqyJvig==", "peerDependencies": { "@insignia/iios-kernel-client": "*", "react": ">=18" diff --git a/src/lib/crm-messaging-adapter.ts b/src/lib/crm-messaging-adapter.ts index c340fdd..0ecb00e 100644 --- a/src/lib/crm-messaging-adapter.ts +++ b/src/lib/crm-messaging-adapter.ts @@ -6,11 +6,15 @@ // When no socket is available (token failed / demo), it degrades to a 4s history poll. import type { + ChannelSummary, + ChannelVisibility, Conversation, + CreateChannelInput, Membership, Message, MessageEvent, MessagingAdapter, + Person, Reaction, SendOpts, Unsubscribe, @@ -106,11 +110,11 @@ export class CrmMessagingAdapter implements MessagingAdapter { async send(threadId: string, content: string, opts?: SendOpts): Promise { if (this.socket) { - const m = await this.socket.sendMessage( - threadId, - content, - opts?.parentInteractionId ? { parentInteractionId: opts.parentInteractionId } : undefined, - ); + const sendOpts = { + ...(opts?.parentInteractionId ? { parentInteractionId: opts.parentInteractionId } : {}), + ...(opts?.mentions && opts.mentions.length ? { mentions: opts.mentions } : {}), + }; + const m = await this.socket.sendMessage(threadId, content, Object.keys(sendOpts).length ? sendOpts : undefined); return this.fromKernel(m); } const m = await this.sdk.command("crm.messenger.send", { threadId, content }); @@ -152,6 +156,46 @@ export class CrmMessagingAdapter implements MessagingAdapter { if (this.socket) await this.socket.markRead(threadId, messageId); } + // ── channels + members (BFF, except join which is a governed socket self-join) ── + async browseChannels(): Promise { + const rows = await this.sdk.query>( + "crm.messenger.channel.browse", + {}, + ); + return rows.map((c) => ({ + threadId: c.threadId, + name: c.name, + topic: c.topic, + visibility: (c.visibility === "private" ? "private" : "public") as ChannelVisibility, + memberCount: c.memberCount, + joined: c.joined, + })); + } + + async createChannel(input: CreateChannelInput): Promise<{ threadId: string }> { + return this.sdk.command<{ threadId: string }>("crm.messenger.channel.create", { + name: input.name, + ...(input.topic ? { topic: input.topic } : {}), + visibility: input.visibility, + }); + } + + async joinChannel(threadId: string): Promise { + // Governed public self-join over the socket (the BFF has no join verb; OPA enforces it). + if (!this.socket) throw new Error("joining a channel needs a live connection"); + await this.socket.openThread(threadId); + this.joined.add(threadId); + } + + async leaveChannel(threadId: string): Promise { + await this.sdk.command("crm.messenger.channel.leave", { threadId }); + } + + async listMembers(threadId: string): Promise { + const rows = await this.sdk.query>("crm.messenger.members", { threadId }); + return rows.map((r) => ({ id: r.userId, name: r.displayName, kind: "staff" as const })); + } + // ── polling fallback (no socket) ─────────────────────────────── private startPoll(threadId: string): void { if (this.polls.has(threadId)) return;