From 954ad20a783b57c4463dc8201a56eadef83b83da Mon Sep 17 00:00:00 2001 From: maaz519 Date: Wed, 29 Jul 2026 14:02:37 +0530 Subject: [PATCH] feat(messenger): paged history + scrollback (SDK 0.3.0, kernel-client 0.2.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CrmMessagingAdapter.historyPage returns {messages, hasMore}. The socket path (openThread) serves the newest page since it also joins the room; scrollback has no socket verb, so `before` goes over the data door — by then the room is already joined, so nothing live is missed either way. Opening a long thread now loads one page instead of every message ever sent, and the SDK renders a "Load older messages" control. Co-Authored-By: Claude Opus 4.8 --- package-lock.json | 18 ++++++++--------- package.json | 4 ++-- src/lib/crm-messaging-adapter.ts | 34 ++++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 45532ee..1fa2c70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "@abe-kap/appshell-sdk": "^0.2.6", "@huggingface/transformers": "^4.2.0", "@imgly/background-removal": "^1.7.0", - "@insignia/iios-kernel-client": "^0.1.6", - "@insignia/iios-messaging-ui": "^0.2.0", + "@insignia/iios-kernel-client": "^0.2.0", + "@insignia/iios-messaging-ui": "^0.3.0", "@photo-gallery/sdk": "file:./vendor/photo-gallery-sdk", "@tanstack/react-query": "^5.101.4", "@tensorflow-models/coco-ssd": "^2.2.3", @@ -1103,20 +1103,20 @@ "integrity": "sha512-+ycbP8ORdwllMtn7dmxIL7lnDpDDkV+gHqT2OKJtMplMivjKU03gsuuEveQxnrE1CMvZ3fcSljmfMs10Cw131g==" }, "node_modules/@insignia/iios-kernel-client": { - "version": "0.1.6", - "resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-kernel-client/-/0.1.6/iios-kernel-client-0.1.6.tgz", - "integrity": "sha512-QfjqaGj8acKfwWQvYsf6Rxj1TeLQN+3fNTTlL+PkAP2usAxcdSkgRKkfHxy9FrhoxLXahQBuOn3wnUzd6Qh39Q==", + "version": "0.2.0", + "resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-kernel-client/-/0.2.0/iios-kernel-client-0.2.0.tgz", + "integrity": "sha512-/uO/FocOYZpaDvaP08UYX3ISk8bUaSUnNj1mRHfZkE8oBJSBOs1ZPJ21t6V1FFhlFI+FjEMaysJKJi1sRDTl8Q==", "dependencies": { "@insignia/iios-contracts": "0.1.0", "socket.io-client": "^4.8.1" } }, "node_modules/@insignia/iios-messaging-ui": { - "version": "0.2.0", - "resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-messaging-ui/-/0.2.0/iios-messaging-ui-0.2.0.tgz", - "integrity": "sha512-eM3K9CMtPumTvpoL7FrX+RcdKylhIDgMDZ4/7GCGoe6P1lj+HcAMmUX38cyLx7qq21DaDMnUcq3RNMW69gsDNw==", + "version": "0.3.0", + "resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-messaging-ui/-/0.3.0/iios-messaging-ui-0.3.0.tgz", + "integrity": "sha512-LFxqVk+RY6E2foR38yll2Q18Qd1mNl9YTONENw+fy1Dt2IDAVMcfN73DCJ8AQjwAsZoTqjsWDfMxgGKyLWT2Uw==", "peerDependencies": { - "@insignia/iios-kernel-client": "*", + "@insignia/iios-kernel-client": ">=0.2.0", "@tanstack/react-query": ">=5", "react": ">=18", "react-dom": ">=18" diff --git a/package.json b/package.json index 972de88..48f0767 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "@abe-kap/appshell-sdk": "^0.2.6", "@huggingface/transformers": "^4.2.0", "@imgly/background-removal": "^1.7.0", - "@insignia/iios-kernel-client": "^0.1.6", - "@insignia/iios-messaging-ui": "^0.2.0", + "@insignia/iios-kernel-client": "^0.2.0", + "@insignia/iios-messaging-ui": "^0.3.0", "@photo-gallery/sdk": "file:./vendor/photo-gallery-sdk", "@tanstack/react-query": "^5.101.4", "@tensorflow-models/coco-ssd": "^2.2.3", diff --git a/src/lib/crm-messaging-adapter.ts b/src/lib/crm-messaging-adapter.ts index 21ffd69..17dfa5a 100644 --- a/src/lib/crm-messaging-adapter.ts +++ b/src/lib/crm-messaging-adapter.ts @@ -136,21 +136,39 @@ export class CrmMessagingAdapter implements MessagingAdapter { return { threadId: res.threadId }; } - async history(threadId: string): Promise { - if (this.socket) { - // openThread both JOINS the room and returns history, so it is the only call needed on a - // cache miss. The SDK caches per thread, so this no longer fires on every selection. - const res = await this.socket.openThread(threadId); + async history(threadId: string, opts?: { limit?: number; before?: string }): Promise { + return (await this.historyPage(threadId, opts)).messages; + } + + /** + * A page of history plus whether older messages exist. + * + * Socket path: openThread both JOINS the room and returns the newest page, so it is the only call + * needed on a cache miss. Scrollback (`before`) has no socket verb, so it goes over the door — + * the room is already joined by then, so nothing live is missed. + */ + async historyPage( + threadId: string, + opts?: { limit?: number; before?: string }, + ): Promise<{ messages: Message[]; hasMore: boolean }> { + if (this.socket && !opts?.before) { + const res = await this.socket.openThread(threadId, { ...(opts?.limit ? { limit: opts.limit } : {}) }); this.joined.add(threadId); - return Promise.all( + const messages = await Promise.all( res.history.map((m) => { this.ingestReactions(m); return this.toKernelMessage(m); }), ); + return { messages, hasMore: res.hasMore ?? false }; } - const msgs = await this.sdk.query("crm.messenger.history", { threadId }); - return Promise.all(msgs.map((m) => this.toDtoMessage(m))); + const res = await this.sdk.query<{ messages: MessageDTO[]; hasMore: boolean }>("crm.messenger.history", { + threadId, + ...(opts?.limit ? { limit: opts.limit } : {}), + ...(opts?.before ? { before: opts.before } : {}), + }); + const messages = await Promise.all(res.messages.map((m) => this.toDtoMessage(m))); + return { messages, hasMore: res.hasMore ?? false }; } async send(threadId: string, content: string, opts?: SendOpts): Promise {