feat(messenger): channels + @mentions in the CRM SDK adapter
CrmMessagingAdapter now implements the full SDK contract: - browseChannels/createChannel/leaveChannel over the be-crm channel door (crm.messenger.channel.*); joinChannel is a governed public self-join over the socket (openThread) — the BFF has no join verb, OPA enforces it - listMembers over crm.messenger.members → drives @mention autocomplete - send forwards mentions[] on the socket path (→ IIOS MENTION inbox items) So the CRM messenger (rendered by the SDK) now gets sectioned Channels/DMs, browse+join+create, and @mention autocomplete/highlight — no embedded code. (SDK re-packed to the local tarball; publish for deploy.) tsc + next build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Generated
+1
-1
@@ -1030,7 +1030,7 @@
|
|||||||
"node_modules/@insignia/iios-messaging-ui": {
|
"node_modules/@insignia/iios-messaging-ui": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "file:../iios/packages/iios-messaging-ui/insignia-iios-messaging-ui-0.1.0.tgz",
|
"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": {
|
"peerDependencies": {
|
||||||
"@insignia/iios-kernel-client": "*",
|
"@insignia/iios-kernel-client": "*",
|
||||||
"react": ">=18"
|
"react": ">=18"
|
||||||
|
|||||||
@@ -6,11 +6,15 @@
|
|||||||
// When no socket is available (token failed / demo), it degrades to a 4s history poll.
|
// When no socket is available (token failed / demo), it degrades to a 4s history poll.
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ChannelSummary,
|
||||||
|
ChannelVisibility,
|
||||||
Conversation,
|
Conversation,
|
||||||
|
CreateChannelInput,
|
||||||
Membership,
|
Membership,
|
||||||
Message,
|
Message,
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
MessagingAdapter,
|
MessagingAdapter,
|
||||||
|
Person,
|
||||||
Reaction,
|
Reaction,
|
||||||
SendOpts,
|
SendOpts,
|
||||||
Unsubscribe,
|
Unsubscribe,
|
||||||
@@ -106,11 +110,11 @@ export class CrmMessagingAdapter implements MessagingAdapter {
|
|||||||
|
|
||||||
async send(threadId: string, content: string, opts?: SendOpts): Promise<Message> {
|
async send(threadId: string, content: string, opts?: SendOpts): Promise<Message> {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
const m = await this.socket.sendMessage(
|
const sendOpts = {
|
||||||
threadId,
|
...(opts?.parentInteractionId ? { parentInteractionId: opts.parentInteractionId } : {}),
|
||||||
content,
|
...(opts?.mentions && opts.mentions.length ? { mentions: opts.mentions } : {}),
|
||||||
opts?.parentInteractionId ? { parentInteractionId: opts.parentInteractionId } : undefined,
|
};
|
||||||
);
|
const m = await this.socket.sendMessage(threadId, content, Object.keys(sendOpts).length ? sendOpts : undefined);
|
||||||
return this.fromKernel(m);
|
return this.fromKernel(m);
|
||||||
}
|
}
|
||||||
const m = await this.sdk.command<MessageDTO>("crm.messenger.send", { threadId, content });
|
const m = await this.sdk.command<MessageDTO>("crm.messenger.send", { threadId, content });
|
||||||
@@ -152,6 +156,46 @@ export class CrmMessagingAdapter implements MessagingAdapter {
|
|||||||
if (this.socket) await this.socket.markRead(threadId, messageId);
|
if (this.socket) await this.socket.markRead(threadId, messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── channels + members (BFF, except join which is a governed socket self-join) ──
|
||||||
|
async browseChannels(): Promise<ChannelSummary[]> {
|
||||||
|
const rows = await this.sdk.query<Array<{ threadId: string; name: string; topic: string | null; visibility: string; memberCount: number; joined: boolean }>>(
|
||||||
|
"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<void> {
|
||||||
|
// 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<void> {
|
||||||
|
await this.sdk.command("crm.messenger.channel.leave", { threadId });
|
||||||
|
}
|
||||||
|
|
||||||
|
async listMembers(threadId: string): Promise<Person[]> {
|
||||||
|
const rows = await this.sdk.query<Array<{ userId: string; displayName: string; role: string }>>("crm.messenger.members", { threadId });
|
||||||
|
return rows.map((r) => ({ id: r.userId, name: r.displayName, kind: "staff" as const }));
|
||||||
|
}
|
||||||
|
|
||||||
// ── polling fallback (no socket) ───────────────────────────────
|
// ── polling fallback (no socket) ───────────────────────────────
|
||||||
private startPoll(threadId: string): void {
|
private startPoll(threadId: string): void {
|
||||||
if (this.polls.has(threadId)) return;
|
if (this.polls.has(threadId)) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user