@liveblocks/chat-sdk-adapter 3.16.0 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,57 +1,234 @@
|
|
|
1
|
-
<p>
|
|
2
|
-
<a href="https://liveblocks.io#gh-light-mode-only"><img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-light.svg" alt="Liveblocks" /></a>
|
|
3
|
-
<a href="https://liveblocks.io#gh-dark-mode-only"><img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-dark.svg" alt="Liveblocks" /></a>
|
|
4
|
-
</p>
|
|
5
|
-
|
|
6
1
|
# `@liveblocks/chat-sdk-adapter`
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
</p>
|
|
13
|
-
|
|
14
|
-
`@liveblocks/chat-sdk-adapter` is a [Chat SDK](https://chat-sdk.dev) platform
|
|
15
|
-
adapter backed by [Liveblocks](https://liveblocks.io) **Comments**. It maps
|
|
16
|
-
rooms, threads, and comments to the Chat SDK’s `Channel` / `Thread` / `Message`
|
|
17
|
-
model so you can build bots that read and post in Liveblocks comment threads.
|
|
3
|
+
[Chat SDK](https://chat-sdk.dev/docs) adapter backed by
|
|
4
|
+
[Liveblocks Comments](https://liveblocks.io/docs/products/comments). It maps
|
|
5
|
+
Liveblocks rooms, threads, and comments to the Chat SDK’s `Channel` / `Thread` /
|
|
6
|
+
`Message` model so you can build bots that read and post in comment threads.
|
|
18
7
|
|
|
19
8
|
## Installation
|
|
20
9
|
|
|
21
|
-
```
|
|
10
|
+
```bash
|
|
22
11
|
npm install @liveblocks/chat-sdk-adapter chat
|
|
23
12
|
```
|
|
24
13
|
|
|
25
|
-
|
|
14
|
+
See the [Chat SDK documentation](https://chat-sdk.dev/docs) for core concepts
|
|
15
|
+
and the
|
|
16
|
+
[Liveblocks API reference](https://liveblocks.io/docs/api-reference/liveblocks-chat-sdk-adapter)
|
|
17
|
+
for product-specific detail.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Create the adapter, then pass it as `adapters.liveblocks` when constructing the
|
|
22
|
+
Chat SDK `Chat` instance. For a runnable bot (state, handlers, webhooks), see
|
|
23
|
+
[Full example](#full-example).
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { createLiveblocksAdapter } from "@liveblocks/chat-sdk-adapter";
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const adapter = createLiveblocksAdapter({
|
|
29
|
+
apiKey: process.env.LIVEBLOCKS_SECRET_KEY!,
|
|
30
|
+
webhookSecret: process.env.LIVEBLOCKS_WEBHOOK_SECRET!,
|
|
31
|
+
botUserId: "my-bot-user",
|
|
32
|
+
botUserName: "MyBot",
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
| Option | Type | Default | Description |
|
|
39
|
+
| ------------------- | ---------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| `apiKey` | `string` | — | Liveblocks secret key (`sk_...`) for REST API calls |
|
|
41
|
+
| `webhookSecret` | `string` | — | Webhook signing secret (`whsec_...`) from the dashboard |
|
|
42
|
+
| `botUserId` | `string` | — | User ID used when the bot creates, edits, or reacts to comments; must match your app’s user identifiers |
|
|
43
|
+
| `botUserName` | `string` | `"liveblocks-bot"` | Display name for the bot |
|
|
44
|
+
| `resolveUsers` | `function` | — | Resolves user IDs for @mentions; return one entry per input id in order, or `undefined` to skip (see TSDoc types) |
|
|
45
|
+
| `resolveGroupsInfo` | `function` | — | Resolves group IDs for @mentions; same ordering rules as `resolveUsers` |
|
|
46
|
+
| `logger` | `Logger` | `ConsoleLogger("info")` child | Chat SDK–compatible logger |
|
|
47
|
+
|
|
48
|
+
Resolver return types follow `@liveblocks/core` user and group metadata shapes
|
|
49
|
+
(`U["info"]`, `DGI`).
|
|
50
|
+
|
|
51
|
+
### Resolving mentions
|
|
52
|
+
|
|
53
|
+
When comments contain @mentions, provide `resolveUsers` and optional
|
|
54
|
+
`resolveGroupsInfo`:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const adapter = createLiveblocksAdapter({
|
|
58
|
+
apiKey: process.env.LIVEBLOCKS_SECRET_KEY!,
|
|
59
|
+
webhookSecret: process.env.LIVEBLOCKS_WEBHOOK_SECRET!,
|
|
60
|
+
botUserId: "my-bot-user",
|
|
61
|
+
|
|
62
|
+
resolveUsers: async ({ userIds }) => {
|
|
63
|
+
const users = await getUsersFromDatabase(userIds);
|
|
64
|
+
return users.map((user) => ({
|
|
65
|
+
name: user.fullName,
|
|
66
|
+
avatar: user.avatarUrl,
|
|
67
|
+
}));
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
resolveGroupsInfo: async ({ groupIds }) => {
|
|
71
|
+
const groups = await getGroupsFromDatabase(groupIds);
|
|
72
|
+
return groups.map((group) => ({ name: group.displayName }));
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
30
76
|
|
|
31
|
-
##
|
|
77
|
+
## Platform setup
|
|
78
|
+
|
|
79
|
+
1. Create a [Liveblocks project](https://liveblocks.io/docs/get-started) with
|
|
80
|
+
rooms using [Comments](https://liveblocks.io/docs/products/comments).
|
|
81
|
+
2. In the dashboard, copy a **secret key** (`sk_...`) for server-side REST API
|
|
82
|
+
calls.
|
|
83
|
+
3. Create a **webhook signing secret** (`whsec_...`) and configure webhooks to
|
|
84
|
+
subscribe to:
|
|
85
|
+
- `commentCreated`
|
|
86
|
+
- `commentReactionAdded`
|
|
87
|
+
- `commentReactionRemoved`
|
|
88
|
+
4. Choose a stable `botUserId` consistent with how your app identifies users
|
|
89
|
+
(the bot should be a real user ID in your system or a dedicated bot ID you
|
|
90
|
+
issue).
|
|
91
|
+
|
|
92
|
+
Point your Liveblocks webhook URL at the route that forwards requests to
|
|
93
|
+
`bot.webhooks.liveblocks` (see [Webhook events](#webhook-events)).
|
|
94
|
+
|
|
95
|
+
## Webhook events
|
|
96
|
+
|
|
97
|
+
Supported Liveblocks webhook types:
|
|
98
|
+
|
|
99
|
+
| Event | Role |
|
|
100
|
+
| ------------------------ | ---------------------------------- |
|
|
101
|
+
| `commentCreated` | Drives Chat SDK message processing |
|
|
102
|
+
| `commentReactionAdded` | Drives reaction handlers |
|
|
103
|
+
| `commentReactionRemoved` | Drives reaction handlers |
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export async function POST(request: Request) {
|
|
107
|
+
return bot.webhooks.liveblocks(request, {
|
|
108
|
+
waitUntil: (p) => void p,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
```
|
|
32
112
|
|
|
33
|
-
|
|
34
|
-
|
|
113
|
+
The adapter verifies signatures with `webhookSecret`; invalid requests get
|
|
114
|
+
**401**.
|
|
35
115
|
|
|
36
|
-
>
|
|
37
|
-
>
|
|
116
|
+
> **Serverless:** Passing `waitUntil` (e.g. on Vercel) lets work continue after
|
|
117
|
+
> the response is sent.
|
|
38
118
|
|
|
39
|
-
##
|
|
119
|
+
## ID encoding
|
|
40
120
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
[upcoming releases](https://github.com/liveblocks/liveblocks/milestones).
|
|
121
|
+
- **Thread ID:** `liveblocks:{roomId}:{threadId}`
|
|
122
|
+
- **Channel ID:** `liveblocks:{roomId}`
|
|
44
123
|
|
|
45
|
-
|
|
124
|
+
### `encodeThreadId`
|
|
46
125
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
and general Liveblocks tips.
|
|
126
|
+
```typescript
|
|
127
|
+
adapter.encodeThreadId(data: { roomId: string; threadId: string }): string;
|
|
128
|
+
```
|
|
51
129
|
|
|
52
|
-
|
|
130
|
+
```typescript
|
|
131
|
+
const encoded = adapter.encodeThreadId({
|
|
132
|
+
roomId: "my-room",
|
|
133
|
+
threadId: "th_abc123",
|
|
134
|
+
});
|
|
135
|
+
// "liveblocks:my-room:th_abc123"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `decodeThreadId`
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
adapter.decodeThreadId(threadId: string): { roomId: string; threadId: string };
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Throws if the format is invalid. Room IDs may contain `:`; the **last** `:`
|
|
145
|
+
separates `threadId`, so Liveblocks thread IDs must not contain `:`.
|
|
146
|
+
|
|
147
|
+
## Features
|
|
148
|
+
|
|
149
|
+
| Area | Support |
|
|
150
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
151
|
+
| Threads / channels | Maps rooms and comment threads to Chat SDK models |
|
|
152
|
+
| Post, edit, delete | Comments via REST |
|
|
153
|
+
| Reactions | Unicode emoji only; names like `thumbs_up` normalize to emoji where supported; unknown custom ids can fail API validation — e.g. `addReaction(…, "👍")` or `"thumbs_up"` |
|
|
154
|
+
| Mentions | Users and groups when resolvers are provided |
|
|
155
|
+
| Attachments | Fetched via Liveblocks attachment URLs |
|
|
156
|
+
| Typing indicators | Not supported — `startTyping` is a no-op |
|
|
157
|
+
|
|
158
|
+
### Message format
|
|
159
|
+
|
|
160
|
+
Liveblocks Comments use a simpler body model than full Markdown. Outbound
|
|
161
|
+
content from the Chat SDK is converted automatically; some structure is
|
|
162
|
+
flattened.
|
|
163
|
+
|
|
164
|
+
**Supported:** paragraphs with bold, italic, code, strikethrough, links,
|
|
165
|
+
@mentions (users and groups).
|
|
166
|
+
|
|
167
|
+
**Flattened to plain text / paragraphs:** headings, bullet and numbered lists,
|
|
168
|
+
code blocks, tables (ASCII in a paragraph), raw HTML. Card payloads become
|
|
169
|
+
markdown/plain text (or `fallbackText`); interactivity is not preserved.
|
|
170
|
+
|
|
171
|
+
## Full example
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { Chat } from "chat";
|
|
175
|
+
import {
|
|
176
|
+
createLiveblocksAdapter,
|
|
177
|
+
type LiveblocksAdapter,
|
|
178
|
+
} from "@liveblocks/chat-sdk-adapter";
|
|
179
|
+
import { createMemoryState } from "@chat-adapter/state-memory";
|
|
180
|
+
|
|
181
|
+
const bot = new Chat<{ liveblocks: LiveblocksAdapter }>({
|
|
182
|
+
userName: "MyBot",
|
|
183
|
+
adapters: {
|
|
184
|
+
liveblocks: createLiveblocksAdapter({
|
|
185
|
+
apiKey: process.env.LIVEBLOCKS_SECRET_KEY!,
|
|
186
|
+
webhookSecret: process.env.LIVEBLOCKS_WEBHOOK_SECRET!,
|
|
187
|
+
botUserId: "my-bot-user",
|
|
188
|
+
botUserName: "MyBot",
|
|
189
|
+
resolveUsers: async ({ userIds }) => {
|
|
190
|
+
const users = await getUsersFromDatabase(userIds);
|
|
191
|
+
return users.map((user) => ({ name: user.fullName }));
|
|
192
|
+
},
|
|
193
|
+
}),
|
|
194
|
+
},
|
|
195
|
+
state: createMemoryState(),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
bot.onNewMention(async (thread, message) => {
|
|
199
|
+
await thread.adapter.addReaction(thread.id, message.id, "👀");
|
|
200
|
+
await thread.post(`Hello, ${message.author.userName}!`);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
bot.onReaction(async (event) => {
|
|
204
|
+
if (!event.added) return;
|
|
205
|
+
await event.adapter.postMessage(
|
|
206
|
+
event.threadId,
|
|
207
|
+
`${event.user.userName} reacted with "${event.emoji.name}"`
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
```
|
|
53
211
|
|
|
54
|
-
|
|
55
|
-
[
|
|
212
|
+
Wire Liveblocks to the same webhook handler as in
|
|
213
|
+
[Webhook events](#webhook-events) (for example a Next.js `POST` route that calls
|
|
214
|
+
`bot.webhooks.liveblocks`).
|
|
215
|
+
|
|
216
|
+
## Examples using Chat SDK
|
|
217
|
+
|
|
218
|
+
- **[Chat SDK Bot](https://liveblocks.io/examples/chat-sdk-bot/nextjs-chat-sdk-bot)**
|
|
219
|
+
— Next.js bot that responds to @mentions and reactions in Liveblocks comment
|
|
220
|
+
threads
|
|
221
|
+
([source](https://github.com/liveblocks/liveblocks/tree/main/examples/nextjs-chat-sdk-bot)).
|
|
222
|
+
- **[Chat SDK AI Bot](https://liveblocks.io/examples/chat-sdk-ai-bot/nextjs-chat-sdk-ai-bot)**
|
|
223
|
+
— Same stack with an AI-powered reply flow
|
|
224
|
+
([source](https://github.com/liveblocks/liveblocks/tree/main/examples/nextjs-chat-sdk-ai-bot)).
|
|
225
|
+
|
|
226
|
+
Full walkthrough:
|
|
227
|
+
[Get started with a Chat SDK bot using Liveblocks and Next.js](https://liveblocks.io/docs/get-started/nextjs-chat-sdk-bot).
|
|
228
|
+
|
|
229
|
+
More collaborative examples:
|
|
230
|
+
[liveblocks.io/examples](https://liveblocks.io/examples).
|
|
231
|
+
|
|
232
|
+
## License
|
|
56
233
|
|
|
57
|
-
See [LICENSE](../../licenses/LICENSE-APACHE-2.0)
|
|
234
|
+
Apache License 2.0. See [LICENSE](../../licenses/LICENSE-APACHE-2.0).
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/liveblocks/liveblocks/packages/liveblocks-chat-sdk-adapter/dist/index.cjs","../src/adapter.ts"],"names":["acc"],"mappings":"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","file":"/home/runner/work/liveblocks/liveblocks/packages/liveblocks-chat-sdk-adapter/dist/index.cjs","sourcesContent":[null,"import {\n type Awaitable,\n type BaseGroupInfo,\n type BaseUserMeta,\n type CommentBody,\n type CommentBodyInlineElement,\n type CommentBodyParagraph,\n getMentionsFromCommentBody,\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n type ResolveGroupsInfoArgs,\n type ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport {\n type CommentData,\n Liveblocks,\n LiveblocksError,\n type WebhookEvent,\n WebhookHandler,\n} from \"@liveblocks/node\";\nimport {\n type Adapter,\n type AdapterPostableMessage,\n type Attachment,\n type CardChild,\n type CardElement,\n type ChannelInfo,\n type ChatInstance,\n ConsoleLogger,\n defaultEmojiResolver,\n type EmojiValue,\n type FetchOptions,\n type FetchResult,\n type FormattedContent,\n type Link,\n type ListThreadsOptions,\n type ListThreadsResult,\n type Logger,\n Message,\n type Paragraph,\n parseMarkdown,\n type RawMessage,\n type Root,\n tableToAscii,\n type Text,\n type ThreadInfo,\n toPlainText,\n type WebhookOptions,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Strong,\n} from \"chat\";\n\ntype PhrasingContent = Paragraph[\"children\"][number];\n\nconst ADAPTER_PREFIX = \"liveblocks\";\nexport class LiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n> implements Adapter<{ roomId: string; threadId: string }, CommentData>\n{\n readonly name = \"liveblocks\";\n readonly userName: string;\n readonly #client: Liveblocks;\n readonly #webhookHandler: WebhookHandler;\n readonly #resolveUsers:\n | ((\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>)\n | undefined;\n readonly #resolveGroupsInfo:\n | ((\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>)\n | undefined;\n readonly #logger: Logger;\n readonly #botUserId: string;\n #chat: ChatInstance | null = null;\n constructor(config: LiveblocksAdapterConfig<U, DGI>) {\n this.#client = new Liveblocks({ secret: config.apiKey });\n this.#webhookHandler = new WebhookHandler(config.webhookSecret);\n this.#resolveUsers = config.resolveUsers;\n this.#resolveGroupsInfo = config.resolveGroupsInfo;\n this.#botUserId = config.botUserId;\n this.userName = config.botUserName ?? \"liveblocks-bot\";\n this.#logger =\n config.logger ?? new ConsoleLogger(\"info\").child(ADAPTER_PREFIX);\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.#chat = chat;\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions\n ): Promise<Response> {\n let event: WebhookEvent;\n try {\n event = this.#webhookHandler.verifyRequest({\n headers: request.headers,\n rawBody: await request.text(),\n });\n } catch (error) {\n this.#logger.error(\"Failed to verify webhook request\", { error });\n return new Response(\"Invalid webhook request\", { status: 401 });\n }\n\n if (event.type === \"commentCreated\") {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const comment = await this.#client.getComment({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n commentId: event.data.commentId,\n });\n if (comment.deletedAt !== undefined) {\n return new Response(null, { status: 200 });\n }\n\n this.#chat?.processMessage(\n this,\n threadId,\n () => this.#convertLiveblocksCommentDataToChatMessage(comment),\n options\n );\n } else if (\n event.type === \"commentReactionAdded\" ||\n event.type === \"commentReactionRemoved\"\n ) {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const userId =\n event.type === \"commentReactionAdded\"\n ? event.data.addedBy\n : event.data.removedBy;\n\n const resolvedUsers = await this.#resolveUsers?.({ userIds: [userId] });\n const user = resolvedUsers?.[0];\n\n this.#chat?.processReaction(\n {\n added: event.type === \"commentReactionAdded\",\n emoji: defaultEmojiResolver.fromGChat(event.data.emoji),\n rawEmoji: event.data.emoji,\n messageId: event.data.commentId,\n threadId,\n user: {\n userId,\n userName: user?.name ?? userId,\n fullName: user?.name ?? userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: userId === this.#botUserId,\n isMe: userId === this.#botUserId,\n },\n raw: event.data,\n adapter: this,\n },\n options\n );\n }\n\n return new Response(null, { status: 200 });\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n const comment = await this.#client.createComment({\n roomId,\n threadId: threadId_liveblocks,\n data: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n });\n return { id: comment.id, threadId, raw: comment };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.editComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n body: convertPostableMessageToCommentBody(message),\n },\n });\n\n return { id: comment.id, threadId, raw: comment };\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n await this.#client.deleteComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.addCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n // Liveblocks expects unicode emoji; 'toGChat' converts normalized names (e.g. 'thumbs_up') to unicode ('👍').\n // Unknown normalized names (e.g. 'custom_emoji') will fail Liveblocks validation since they are not valid unicode emoji.\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async removeReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.removeCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async fetchMessages(\n threadId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n const comments = thread.comments.filter(\n (comment) => comment.deletedAt === undefined\n );\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get thread' API returns all comments in the thread in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n return {\n id: threadId,\n channelId: `${ADAPTER_PREFIX}:${roomId}`,\n metadata: {\n resolved: thread.resolved,\n ...thread.metadata,\n },\n channelName: thread.roomId,\n isDM: false,\n };\n }\n\n async fetchMessage(\n threadId: string,\n messageId: string\n ): Promise<Message<CommentData> | null> {\n try {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.getComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n if (comment.deletedAt !== undefined) {\n return null;\n }\n return this.#convertLiveblocksCommentDataToChatMessage(comment);\n } catch (error) {\n if (error instanceof LiveblocksError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async listThreads(\n channelId: string,\n options?: ListThreadsOptions\n ): Promise<ListThreadsResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n const threads = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment === undefined) return null;\n\n return {\n id: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n updatedAt: thread.updatedAt,\n numOfComments: nonDeletedComments.length,\n firstComment: firstNonDeletedComment,\n };\n })\n .filter((thread) => thread !== null);\n\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByUpdatedAt(threads, {\n limit,\n startingAfter,\n });\n\n return {\n threads: await Promise.all(\n sliced.data.map(async (thread) => ({\n id: thread.id,\n rootMessage: await this.#convertLiveblocksCommentDataToChatMessage(\n thread.firstComment\n ),\n lastReplyAt: thread.updatedAt,\n replyCount: thread.numOfComments - 1,\n }))\n ),\n nextCursor: sliced.nextCursor,\n };\n }\n\n async fetchChannelInfo(channelId: string): Promise<ChannelInfo> {\n const room = await this.#client.getRoom(getRoomIdFromChannelId(channelId));\n return {\n id: room.id,\n name: room.id,\n isDM: false,\n metadata: {},\n };\n }\n\n async fetchChannelMessages(\n channelId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n\n const comments = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment !== undefined) {\n return firstNonDeletedComment;\n }\n return null;\n })\n .filter((comment) => comment !== null);\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room (sorted by creation date in ascending order)\n // and each thread contains all comments in the thread (sorted by creation date in ascending order),\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async postChannelMessage(\n channelId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const thread = await this.#client.createThread({\n roomId,\n data: {\n comment: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n },\n });\n const firstComment = thread.comments[0];\n if (firstComment === undefined) {\n throw new Error(`Failed to create thread in room ${channelId}`);\n }\n return {\n id: firstComment.id,\n threadId: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n raw: firstComment,\n };\n }\n\n // This method isn't used by the Chat SDK, but it's required to implement the Adapter interface,\n // so we will return a less rich message here. We have a separate (asynchronous) method for converting a comment to a message.\n parseMessage(data: CommentData): Message<CommentData> {\n return new Message({\n id: data.id,\n threadId: this.encodeThreadId({\n roomId: data.roomId,\n threadId: data.threadId,\n }),\n raw: data,\n formatted: { type: \"root\", children: [] },\n text: \"\",\n author: {\n userId: data.userId,\n userName: data.userId,\n fullName: data.userId,\n isBot: data.userId === this.#botUserId,\n isMe: data.userId === this.#botUserId,\n },\n metadata: {\n dateSent: data.createdAt,\n edited: !!data.editedAt,\n editedAt: data.editedAt,\n },\n attachments: data.attachments.map((att) =>\n this.#createAttachment(data.roomId, att)\n ),\n });\n }\n\n renderFormatted(content: FormattedContent): string {\n // Liveblocks comments do not support markdown as input, so we convert the content to plain text.\n return toPlainText(content);\n }\n\n channelIdFromThreadId(threadId: string): string {\n const { roomId } = this.decodeThreadId(threadId);\n return `${ADAPTER_PREFIX}:${roomId}`;\n }\n\n /**\n * This method is a no-op as typing indicators are not supported by Liveblocks Comments.\n */\n startTyping(_threadId: string, _status?: string): Promise<void> {\n return Promise.resolve();\n }\n\n #createAttachment(\n roomId: string,\n attachment: CommentData[\"attachments\"][number]\n ): Attachment {\n const client = this.#client;\n return {\n type: getAttachmentType(attachment.mimeType),\n name: attachment.name,\n mimeType: attachment.mimeType,\n size: attachment.size,\n fetchData: async () => {\n const { url } = await client.getAttachment({\n roomId,\n attachmentId: attachment.id,\n });\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch attachment \"${attachment.name}\": ${response.status} ${response.statusText}`\n );\n }\n return Buffer.from(await response.arrayBuffer());\n },\n };\n }\n\n /**\n * Encodes a Liveblocks room ID and thread ID into a single thread ID string.\n *\n * Format: `liveblocks:{roomId}:{threadId}`\n *\n * **Note**: Room IDs may contain colons (':'), which are preserved during encoding/decoding.\n * However, Liveblocks thread IDs must not contain colons as the last colon is used as the delimiter when decoding.\n */\n encodeThreadId(data: { roomId: string; threadId: string }): string {\n return `${ADAPTER_PREFIX}:${data.roomId}:${data.threadId}`;\n }\n\n /**\n * Decodes an encoded thread ID string back into its room ID and thread ID components.\n *\n * @throws {Error} If the thread ID format is invalid\n */\n decodeThreadId(threadId: string): { roomId: string; threadId: string } {\n const parts = threadId.split(\":\");\n if (parts.length < 3 || parts[0] !== ADAPTER_PREFIX) {\n throw new Error(\n `Invalid thread ID: ${threadId}. Expected format: liveblocks:{roomId}:{threadId}`\n );\n }\n return {\n roomId: parts.slice(1, -1).join(\":\"),\n threadId: parts[parts.length - 1]!,\n };\n }\n\n async #convertLiveblocksCommentDataToChatMessage(\n comment: Extract<CommentData, { body: CommentBody }>\n ): Promise<Message<CommentData>> {\n const mentions = getMentionsFromCommentBody(comment.body);\n const userIds = new Set<string>([comment.userId]); // Initialize with the author's user id\n const groupIds = new Set<string>();\n for (const mention of mentions) {\n if (mention.kind === \"user\") {\n userIds.add(mention.id);\n } else if (mention.kind === \"group\") {\n groupIds.add(mention.id);\n }\n }\n\n const [users, groups] = await Promise.all([\n this.#resolveUsers\n ? this.#resolveUsers({ userIds: Array.from(userIds) })\n : undefined,\n this.#resolveGroupsInfo && groupIds.size > 0\n ? this.#resolveGroupsInfo({ groupIds: Array.from(groupIds) })\n : undefined,\n ]);\n\n const resolvedUsers = new Map<string, U[\"info\"]>();\n if (users !== undefined) {\n for (const [index, userId] of Array.from(userIds).entries()) {\n const user = users[index];\n if (user === undefined) continue;\n resolvedUsers.set(userId, user);\n }\n }\n const resolvedGroups = new Map<string, DGI>();\n if (groups !== undefined) {\n for (const [index, groupId] of Array.from(groupIds).entries()) {\n const group = groups[index];\n if (group === undefined) continue;\n resolvedGroups.set(groupId, group);\n }\n }\n\n const links = new Set<string>();\n\n const nodes: Paragraph[] = comment.body.content.map((block) => {\n const children: Array<\n Text | Link | Emphasis | Strong | InlineCode | Delete\n > = [];\n for (const inline of block.children) {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n children.push({\n type: \"text\",\n value: resolvedUsers.get(inline.id)?.name ?? inline.id,\n });\n } else {\n children.push({\n type: \"text\",\n value: resolvedGroups.get(inline.id)?.name ?? inline.id,\n });\n }\n } else if (isCommentBodyLink(inline)) {\n links.add(inline.url);\n children.push({\n type: \"link\",\n children: [{ type: \"text\", value: inline.text ?? \"\" }],\n url: inline.url,\n });\n } else if (isCommentBodyText(inline)) {\n if (inline.code) {\n children.push({\n type: \"inlineCode\",\n value: inline.text,\n });\n } else {\n // Build nested structure for combined styles (bold, italic, strikethrough)\n let node: Text | Emphasis | Strong | Delete = {\n type: \"text\",\n value: inline.text,\n };\n\n if (inline.strikethrough) {\n node = { type: \"delete\", children: [node] };\n }\n if (inline.italic) {\n node = { type: \"emphasis\", children: [node] };\n }\n if (inline.bold) {\n node = { type: \"strong\", children: [node] };\n }\n\n children.push(node);\n }\n }\n }\n return { type: \"paragraph\", children };\n });\n\n const text = comment.body.content.reduce((acc, block) => {\n return (\n acc +\n block.children.reduce((acc, inline) => {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n return acc + (resolvedUsers.get(inline.id)?.name ?? inline.id);\n } else {\n return acc + (resolvedGroups.get(inline.id)?.name ?? inline.id);\n }\n } else if (isCommentBodyLink(inline)) {\n if (inline.text) {\n return acc + inline.text;\n } else {\n return acc + inline.url;\n }\n } else if (isCommentBodyText(inline)) {\n return acc + inline.text;\n }\n return acc;\n }, \"\")\n );\n }, \"\");\n\n return new Message({\n id: comment.id,\n threadId: this.encodeThreadId({\n roomId: comment.roomId,\n threadId: comment.threadId,\n }),\n raw: comment,\n formatted: { type: \"root\", children: nodes },\n text,\n isMention: resolvedUsers.has(this.#botUserId),\n links: Array.from(links.values()).map((url) => ({ url })),\n author: {\n userId: comment.userId,\n userName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n fullName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: comment.userId === this.#botUserId,\n isMe: comment.userId === this.#botUserId,\n },\n metadata: {\n dateSent: comment.createdAt,\n edited: comment.editedAt !== undefined,\n editedAt: comment.editedAt,\n },\n attachments: comment.attachments.map((attachment) =>\n this.#createAttachment(comment.roomId, attachment)\n ),\n });\n }\n}\n\n/**\n * Parses a Chat SDK channel id into the Liveblocks room id for REST API calls.\n *\n * @throws {Error} If `channelId` is missing the `liveblocks:` prefix or has an empty room segment.\n */\nexport function getRoomIdFromChannelId(channelId: string): string {\n const prefix = `${ADAPTER_PREFIX}:`;\n if (!channelId.startsWith(prefix)) {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n const roomId = channelId.slice(prefix.length);\n if (roomId === \"\") {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n return roomId;\n}\n\nexport function convertPostableMessageToCommentBody(\n message: AdapterPostableMessage\n): CommentBody {\n if (typeof message === \"string\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message)\n );\n } else if (\"raw\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.raw)\n );\n } else if (\"markdown\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.markdown)\n );\n } else if (\"ast\" in message) {\n return convertChatRootElementToCommentBodyRootElement(message.ast);\n } else if (\"card\" in message) {\n // Liveblocks comments do not support cards and card elements, so we convert the message to markdown and then to a comment body\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(\n message.fallbackText ?? convertCardToMarkdownString(message.card)\n )\n );\n } else if (\"type\" in message && message.type === \"card\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(convertCardToMarkdownString(message))\n );\n } else {\n console.error(`Unexpected message type: ${JSON.stringify(message)}`);\n return {\n version: 1,\n content: [],\n };\n }\n}\n\nfunction convertCardToMarkdownString(card: CardElement): string {\n const parts: string[] = [];\n if (card.title) {\n parts.push(`**${card.title}**`);\n }\n if (card.subtitle) {\n parts.push(card.subtitle);\n }\n for (const child of card.children) {\n parts.push(convertCardChildToMarkdownString(child));\n }\n return parts.join(\"\\n\");\n}\n\nfunction convertCardChildToMarkdownString(child: CardChild): string {\n switch (child.type) {\n case \"text\":\n return child.content;\n case \"fields\":\n return child.children\n .map((field) => `**${field.label}**: ${field.value}`)\n .join(\"\\n\");\n case \"actions\":\n // Actions are interactive-only — exclude from fallback text. See: https://docs.slack.dev/reference/methods/chat.postMessage\n return \"\";\n case \"table\": {\n let markdown = \"|\";\n for (const header of child.headers) {\n markdown += ` ${header} |`;\n }\n markdown += \"\\n|\";\n for (const _ of child.headers) {\n markdown += \"--- |\";\n }\n markdown += \"\\n\";\n for (const row of child.rows) {\n markdown += \"|\";\n for (const cell of row) {\n markdown += ` ${cell} |`;\n }\n markdown += \"\\n\";\n }\n return markdown;\n }\n case \"section\":\n return child.children\n .map((c) => convertCardChildToMarkdownString(c))\n .filter(Boolean)\n .join(\"\\n\");\n case \"link\":\n return `[${child.label}](${child.url})`;\n case \"divider\":\n return \"---\";\n case \"image\":\n return ``;\n default:\n return \"\";\n }\n}\n\nfunction convertChatRootElementToCommentBodyRootElement(\n root: Root\n): CommentBody {\n return {\n version: 1,\n content: root.children.flatMap((child) =>\n convertChatBlockElementToCommentBodyBlockElement(child)\n ),\n };\n}\n\nfunction convertChatBlockElementToCommentBodyBlockElement(\n node: Root[\"children\"][number]\n): CommentBodyParagraph | CommentBodyParagraph[] {\n switch (node.type) {\n case \"paragraph\": {\n const children: CommentBodyInlineElement[] = [];\n for (const child of node.children) {\n children.push(\n convertChatInlineElementToCommentBodyInlineElement(child)\n );\n }\n return {\n type: \"paragraph\",\n children,\n };\n }\n case \"blockquote\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"list\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"listItem\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"heading\": {\n // Render headings as paragraphs as Liveblocks comments do not support headings\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: node.children,\n });\n }\n case \"code\": {\n // Render code blocks as paragraphs as Liveblocks comments do not support code blocks\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"html\": {\n // Render HTML as paragraphs as Liveblocks comments do not support HTML\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"table\": {\n // Convert table to ASCII table string and render as paragraph as Liveblocks comments do not support tables\n return {\n type: \"paragraph\",\n children: [{ text: tableToAscii(node) }],\n };\n }\n case \"link\":\n case \"image\":\n case \"strong\":\n case \"emphasis\":\n case \"inlineCode\":\n case \"delete\":\n case \"text\": {\n return {\n type: \"paragraph\",\n children: [convertChatInlineElementToCommentBodyInlineElement(node)],\n };\n }\n case \"break\":\n case \"thematicBreak\":\n case \"definition\":\n case \"tableCell\":\n case \"tableRow\":\n case \"yaml\":\n case \"footnoteDefinition\":\n case \"footnoteReference\":\n case \"imageReference\":\n case \"linkReference\":\n default: {\n return [];\n }\n }\n}\n\nfunction convertChatInlineElementToCommentBodyInlineElement(\n inline: PhrasingContent\n): CommentBodyInlineElement {\n switch (inline.type) {\n case \"text\":\n return { text: inline.value };\n case \"link\":\n return {\n type: \"link\",\n url: inline.url,\n // Link elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n };\n case \"image\":\n return { text: inline.url };\n case \"emphasis\":\n return {\n // Emphasis elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n italic: true,\n };\n case \"strong\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n bold: true,\n };\n case \"delete\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n strikethrough: true,\n };\n case \"inlineCode\":\n return {\n text: inline.value,\n code: true,\n };\n case \"html\":\n return { text: inline.value };\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default: {\n return { text: \"\" };\n }\n }\n}\n\nfunction convertChatInlineElementToPlainText(inline: PhrasingContent): string {\n switch (inline.type) {\n case \"text\":\n return inline.value;\n case \"link\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"image\":\n return inline.url;\n case \"emphasis\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"strong\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"delete\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"inlineCode\":\n return inline.value;\n case \"html\":\n return inline.value;\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default:\n return \"\";\n }\n}\n\n/**\n * Encode a pagination cursor using the format `base64url( [[\"id\", <string>], [\"createdAt\", <number>]] )`\n */\nexport function encodePaginationCursorByCreatedAt(\n id: string,\n createdAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"createdAt\", createdAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByCreatedAt(cursor: string): {\n id: string;\n createdAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"createdAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n createdAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nexport function encodePaginationCursorByUpdatedAt(\n id: string,\n updatedAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"updatedAt\", updatedAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByUpdatedAt(cursor: string): {\n id: string;\n updatedAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"updatedAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n updatedAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nfunction base64UrlEncode(str: string): string {\n const bytes = new TextEncoder().encode(str);\n const binary = String.fromCharCode(...bytes);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction base64UrlDecode(str: string): string {\n let s = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (s.length % 4) s += \"=\";\n const binary = atob(s);\n const bytes = Uint8Array.from(binary, (c) => c.codePointAt(0)!);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Slice a page from an in-memory list sorted by `createdAt` (oldest first).\n *\n * Cursors use the same `[[\"id\", ...], [\"createdAt\", ...]]` format as the\n * Liveblocks REST API so that they will be forward-compatible if the backend\n * adds server-side pagination.\n *\n * The cursor is always built from the boundary item of the current page and\n * means \"start after this item\" in the current traversal direction — matching\n * the `startingAfter` semantics used throughout the backend.\n *\n * When no `limit` is provided, all matching items are returned (preserving the\n * pre-pagination behaviour of returning the full list).\n */\nfunction slicePageByCreatedAt<T extends { id: string; createdAt: Date }>(\n data: T[],\n options: {\n /**\n * The direction to slice the page in.\n * - \"ascending\": Slice the page from the oldest item to the newest item.\n * - \"descending\": Slice the page from the newest item to the oldest item.\n */\n direction: \"ascending\" | \"descending\";\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { direction, limit, startingAfter } = options;\n\n // Sort data by 'createdAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.createdAt.getTime() !== b.createdAt.getTime()) {\n return a.createdAt.getTime() - b.createdAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let startIndex: number;\n let endIndex: number;\n\n if (direction === \"descending\") {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n } else {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the first item strictly after the cursor in sort order.\n startIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id < cursor.id)\n );\n if (startIndex === -1) {\n return { data: [], nextCursor: undefined };\n }\n } else {\n startIndex = 0;\n }\n endIndex =\n limit !== undefined\n ? Math.min(data.length, startIndex + limit)\n : data.length;\n }\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n let nextCursor: string | undefined;\n if (direction === \"descending\") {\n nextCursor =\n startIndex > 0\n ? encodePaginationCursorByCreatedAt(page[0]!.id, page[0]!.createdAt)\n : undefined;\n } else {\n nextCursor =\n endIndex < data.length\n ? encodePaginationCursorByCreatedAt(\n page[page.length - 1]!.id,\n page[page.length - 1]!.createdAt\n )\n : undefined;\n }\n\n return { data: page, nextCursor };\n}\n\n/**\n * Same as {@link slicePageByCreatedAt} (descending direction only) but sorts and\n * paginates on `updatedAt`. Thread listing does not expose forward pagination.\n */\nfunction slicePageByUpdatedAt<T extends { id: string; updatedAt: Date }>(\n data: T[],\n options: {\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { limit, startingAfter } = options;\n\n // Sort data by 'updatedAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.updatedAt.getTime() !== b.updatedAt.getTime()) {\n return a.updatedAt.getTime() - b.updatedAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let endIndex: number;\n if (startingAfter) {\n const cursor = decodePaginationCursorByUpdatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.updatedAt.getTime() > cursor.updatedAt.getTime() ||\n (c.updatedAt.getTime() === cursor.updatedAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n const startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n const nextCursor =\n startIndex > 0\n ? encodePaginationCursorByUpdatedAt(page[0]!.id, page[0]!.updatedAt)\n : undefined;\n\n return { data: page, nextCursor };\n}\n\nfunction getAttachmentType(mimeType: string): Attachment[\"type\"] {\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n } else if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n } else if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n return \"file\";\n}\n\nexport interface LiveblocksAdapterConfig<\n U extends BaseUserMeta,\n DGI extends BaseGroupInfo,\n> {\n apiKey: string;\n webhookSecret: string;\n resolveUsers?: (\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n resolveGroupsInfo?: (\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>;\n botUserId: string;\n botUserName?: string;\n logger?: Logger;\n}\n\nexport function createLiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n>(config: LiveblocksAdapterConfig<U, DGI>): LiveblocksAdapter<U, DGI> {\n return new LiveblocksAdapter(config);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/liveblocks/liveblocks/packages/liveblocks-chat-sdk-adapter/dist/index.cjs","../src/adapter.ts"],"names":["acc"],"mappings":"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","file":"/home/runner/work/liveblocks/liveblocks/packages/liveblocks-chat-sdk-adapter/dist/index.cjs","sourcesContent":[null,"import {\n type Awaitable,\n type BaseGroupInfo,\n type BaseUserMeta,\n type CommentBody,\n type CommentBodyInlineElement,\n type CommentBodyParagraph,\n getMentionsFromCommentBody,\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n type ResolveGroupsInfoArgs,\n type ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport {\n type CommentData,\n Liveblocks,\n LiveblocksError,\n type WebhookEvent,\n WebhookHandler,\n} from \"@liveblocks/node\";\nimport {\n type Adapter,\n type AdapterPostableMessage,\n type Attachment,\n type CardChild,\n type CardElement,\n type ChannelInfo,\n type ChatInstance,\n ConsoleLogger,\n defaultEmojiResolver,\n type EmojiValue,\n type FetchOptions,\n type FetchResult,\n type FormattedContent,\n type Link,\n type ListThreadsOptions,\n type ListThreadsResult,\n type Logger,\n Message,\n type Paragraph,\n parseMarkdown,\n type RawMessage,\n type Root,\n tableToAscii,\n type Text,\n type ThreadInfo,\n toPlainText,\n type WebhookOptions,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Strong,\n} from \"chat\";\n\ntype PhrasingContent = Paragraph[\"children\"][number];\n\nconst ADAPTER_PREFIX = \"liveblocks\";\nexport class LiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n> implements Adapter<{ roomId: string; threadId: string }, CommentData>\n{\n readonly name = \"liveblocks\";\n readonly userName: string;\n readonly #client: Liveblocks;\n readonly #webhookHandler: WebhookHandler;\n readonly #resolveUsers:\n | ((\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>)\n | undefined;\n readonly #resolveGroupsInfo:\n | ((\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>)\n | undefined;\n readonly #logger: Logger;\n readonly #botUserId: string;\n #chat: ChatInstance | null = null;\n constructor(config: LiveblocksAdapterConfig<U, DGI>) {\n this.#client = new Liveblocks({ secret: config.apiKey });\n this.#webhookHandler = new WebhookHandler(config.webhookSecret);\n this.#resolveUsers = config.resolveUsers;\n this.#resolveGroupsInfo = config.resolveGroupsInfo;\n this.#botUserId = config.botUserId;\n this.userName = config.botUserName ?? \"liveblocks-bot\";\n this.#logger =\n config.logger ?? new ConsoleLogger(\"info\").child(ADAPTER_PREFIX);\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.#chat = chat;\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions\n ): Promise<Response> {\n let event: WebhookEvent;\n try {\n event = this.#webhookHandler.verifyRequest({\n headers: request.headers,\n rawBody: await request.text(),\n });\n } catch (error) {\n this.#logger.error(\"Failed to verify webhook request\", { error });\n return new Response(\"Invalid webhook request\", { status: 401 });\n }\n\n if (event.type === \"commentCreated\") {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const comment = await this.#client.getComment({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n commentId: event.data.commentId,\n });\n if (comment.deletedAt !== undefined) {\n return new Response(null, { status: 200 });\n }\n\n this.#chat?.processMessage(\n this,\n threadId,\n () => this.#convertLiveblocksCommentDataToChatMessage(comment),\n options\n );\n } else if (\n event.type === \"commentReactionAdded\" ||\n event.type === \"commentReactionRemoved\"\n ) {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const userId =\n event.type === \"commentReactionAdded\"\n ? event.data.addedBy\n : event.data.removedBy;\n\n const resolvedUsers = await this.#resolveUsers?.({ userIds: [userId] });\n const user = resolvedUsers?.[0];\n\n this.#chat?.processReaction(\n {\n added: event.type === \"commentReactionAdded\",\n emoji: defaultEmojiResolver.fromGChat(event.data.emoji),\n rawEmoji: event.data.emoji,\n messageId: event.data.commentId,\n threadId,\n user: {\n userId,\n userName: user?.name ?? userId,\n fullName: user?.name ?? userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: userId === this.#botUserId,\n isMe: userId === this.#botUserId,\n },\n raw: event.data,\n adapter: this,\n },\n options\n );\n }\n\n return new Response(null, { status: 200 });\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n const comment = await this.#client.createComment({\n roomId,\n threadId: threadId_liveblocks,\n data: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n });\n return { id: comment.id, threadId, raw: comment };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.editComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n body: convertPostableMessageToCommentBody(message),\n },\n });\n\n return { id: comment.id, threadId, raw: comment };\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n await this.#client.deleteComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.addCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n // Liveblocks expects unicode emoji; 'toGChat' converts normalized names (e.g. 'thumbs_up') to unicode ('👍').\n // Unknown normalized names (e.g. 'custom_emoji') will fail Liveblocks validation since they are not valid unicode emoji.\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async removeReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.removeCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async fetchMessages(\n threadId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n const comments = thread.comments.filter(\n (comment) => comment.deletedAt === undefined\n );\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get thread' API returns all comments in the thread in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n return {\n id: threadId,\n channelId: `${ADAPTER_PREFIX}:${roomId}`,\n metadata: {\n resolved: thread.resolved,\n ...thread.metadata,\n },\n channelName: thread.roomId,\n isDM: false,\n };\n }\n\n async fetchMessage(\n threadId: string,\n messageId: string\n ): Promise<Message<CommentData> | null> {\n try {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.getComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n if (comment.deletedAt !== undefined) {\n return null;\n }\n return this.#convertLiveblocksCommentDataToChatMessage(comment);\n } catch (error) {\n if (error instanceof LiveblocksError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async listThreads(\n channelId: string,\n options?: ListThreadsOptions\n ): Promise<ListThreadsResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n const threads = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment === undefined) return null;\n\n return {\n id: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n updatedAt: thread.updatedAt,\n numOfComments: nonDeletedComments.length,\n firstComment: firstNonDeletedComment,\n };\n })\n .filter((thread) => thread !== null);\n\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByUpdatedAt(threads, {\n limit,\n startingAfter,\n });\n\n return {\n threads: await Promise.all(\n sliced.data.map(async (thread) => ({\n id: thread.id,\n rootMessage: await this.#convertLiveblocksCommentDataToChatMessage(\n thread.firstComment\n ),\n lastReplyAt: thread.updatedAt,\n replyCount: thread.numOfComments - 1,\n }))\n ),\n nextCursor: sliced.nextCursor,\n };\n }\n\n async fetchChannelInfo(channelId: string): Promise<ChannelInfo> {\n const room = await this.#client.getRoom(getRoomIdFromChannelId(channelId));\n return {\n id: room.id,\n name: room.id,\n isDM: false,\n metadata: {},\n };\n }\n\n async fetchChannelMessages(\n channelId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n\n const comments = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment !== undefined) {\n return firstNonDeletedComment;\n }\n return null;\n })\n .filter((comment) => comment !== null);\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room (sorted by creation date in ascending order)\n // and each thread contains all comments in the thread (sorted by creation date in ascending order),\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async postChannelMessage(\n channelId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const thread = await this.#client.createThread({\n roomId,\n data: {\n comment: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n },\n });\n const firstComment = thread.comments[0];\n if (firstComment === undefined) {\n throw new Error(`Failed to create thread in room ${channelId}`);\n }\n return {\n id: firstComment.id,\n threadId: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n raw: firstComment,\n };\n }\n\n // This method isn't used by the Chat SDK, but it's required to implement the Adapter interface,\n // so we will return a less rich message here. We have a separate (asynchronous) method for converting a comment to a message.\n parseMessage(data: CommentData): Message<CommentData> {\n return new Message({\n id: data.id,\n threadId: this.encodeThreadId({\n roomId: data.roomId,\n threadId: data.threadId,\n }),\n raw: data,\n formatted: { type: \"root\", children: [] },\n text: \"\",\n author: {\n userId: data.userId,\n userName: data.userId,\n fullName: data.userId,\n isBot: data.userId === this.#botUserId,\n isMe: data.userId === this.#botUserId,\n },\n metadata: {\n dateSent: data.createdAt,\n edited: !!data.editedAt,\n editedAt: data.editedAt,\n },\n attachments: data.attachments.map((att) =>\n this.#createAttachment(data.roomId, att)\n ),\n });\n }\n\n renderFormatted(content: FormattedContent): string {\n // Liveblocks comments do not support markdown as input, so we convert the content to plain text.\n return toPlainText(content);\n }\n\n channelIdFromThreadId(threadId: string): string {\n const { roomId } = this.decodeThreadId(threadId);\n return `${ADAPTER_PREFIX}:${roomId}`;\n }\n\n /**\n * This method is a no-op as typing indicators are not supported by Liveblocks Comments.\n */\n startTyping(_threadId: string, _status?: string): Promise<void> {\n return Promise.resolve();\n }\n\n #createAttachment(\n roomId: string,\n attachment: CommentData[\"attachments\"][number]\n ): Attachment {\n const client = this.#client;\n return {\n type: getAttachmentType(attachment.mimeType),\n name: attachment.name,\n mimeType: attachment.mimeType,\n size: attachment.size,\n fetchData: async () => {\n const { url } = await client.getAttachment({\n roomId,\n attachmentId: attachment.id,\n });\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch attachment \"${attachment.name}\": ${response.status} ${response.statusText}`\n );\n }\n return Buffer.from(await response.arrayBuffer());\n },\n };\n }\n\n /**\n * Encodes a Liveblocks room ID and thread ID into a single thread ID string.\n *\n * Format: `liveblocks:{roomId}:{threadId}`\n *\n * **Note**: Room IDs may contain colons (':'), which are preserved during encoding/decoding.\n * However, Liveblocks thread IDs must not contain colons as the last colon is used as the delimiter when decoding.\n */\n encodeThreadId(data: { roomId: string; threadId: string }): string {\n return `${ADAPTER_PREFIX}:${data.roomId}:${data.threadId}`;\n }\n\n /**\n * Decodes an encoded thread ID string back into its room ID and thread ID components.\n *\n * @throws {Error} If the thread ID format is invalid\n */\n decodeThreadId(threadId: string): { roomId: string; threadId: string } {\n const parts = threadId.split(\":\");\n if (parts.length < 3 || parts[0] !== ADAPTER_PREFIX) {\n throw new Error(\n `Invalid thread ID: ${threadId}. Expected format: liveblocks:{roomId}:{threadId}`\n );\n }\n return {\n roomId: parts.slice(1, -1).join(\":\"),\n threadId: parts[parts.length - 1]!,\n };\n }\n\n async #convertLiveblocksCommentDataToChatMessage(\n comment: Extract<CommentData, { body: CommentBody }>\n ): Promise<Message<CommentData>> {\n const mentions = getMentionsFromCommentBody(comment.body);\n const userIds = new Set<string>([comment.userId]); // Initialize with the author's user id\n const groupIds = new Set<string>();\n for (const mention of mentions) {\n if (mention.kind === \"user\") {\n userIds.add(mention.id);\n } else if (mention.kind === \"group\") {\n groupIds.add(mention.id);\n }\n }\n\n const [users, groups] = await Promise.all([\n this.#resolveUsers\n ? this.#resolveUsers({ userIds: Array.from(userIds) })\n : undefined,\n this.#resolveGroupsInfo && groupIds.size > 0\n ? this.#resolveGroupsInfo({ groupIds: Array.from(groupIds) })\n : undefined,\n ]);\n\n const resolvedUsers = new Map<string, U[\"info\"]>();\n if (users !== undefined) {\n for (const [index, userId] of Array.from(userIds).entries()) {\n const user = users[index];\n if (user === undefined) continue;\n resolvedUsers.set(userId, user);\n }\n }\n const resolvedGroups = new Map<string, DGI>();\n if (groups !== undefined) {\n for (const [index, groupId] of Array.from(groupIds).entries()) {\n const group = groups[index];\n if (group === undefined) continue;\n resolvedGroups.set(groupId, group);\n }\n }\n\n const links = new Set<string>();\n\n const nodes: Paragraph[] = comment.body.content.map((block) => {\n const children: Array<\n Text | Link | Emphasis | Strong | InlineCode | Delete\n > = [];\n for (const inline of block.children) {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n children.push({\n type: \"text\",\n value: resolvedUsers.get(inline.id)?.name ?? inline.id,\n });\n } else {\n children.push({\n type: \"text\",\n value: resolvedGroups.get(inline.id)?.name ?? inline.id,\n });\n }\n } else if (isCommentBodyLink(inline)) {\n links.add(inline.url);\n children.push({\n type: \"link\",\n children: [{ type: \"text\", value: inline.text ?? \"\" }],\n url: inline.url,\n });\n } else if (isCommentBodyText(inline)) {\n if (inline.code) {\n children.push({\n type: \"inlineCode\",\n value: inline.text,\n });\n } else {\n // Build nested structure for combined styles (bold, italic, strikethrough)\n let node: Text | Emphasis | Strong | Delete = {\n type: \"text\",\n value: inline.text,\n };\n\n if (inline.strikethrough) {\n node = { type: \"delete\", children: [node] };\n }\n if (inline.italic) {\n node = { type: \"emphasis\", children: [node] };\n }\n if (inline.bold) {\n node = { type: \"strong\", children: [node] };\n }\n\n children.push(node);\n }\n }\n }\n return { type: \"paragraph\", children };\n });\n\n const text = comment.body.content.reduce((acc, block) => {\n return (\n acc +\n block.children.reduce((acc, inline) => {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n return acc + (resolvedUsers.get(inline.id)?.name ?? inline.id);\n } else {\n return acc + (resolvedGroups.get(inline.id)?.name ?? inline.id);\n }\n } else if (isCommentBodyLink(inline)) {\n if (inline.text) {\n return acc + inline.text;\n } else {\n return acc + inline.url;\n }\n } else if (isCommentBodyText(inline)) {\n return acc + inline.text;\n }\n return acc;\n }, \"\")\n );\n }, \"\");\n\n return new Message({\n id: comment.id,\n threadId: this.encodeThreadId({\n roomId: comment.roomId,\n threadId: comment.threadId,\n }),\n raw: comment,\n formatted: { type: \"root\", children: nodes },\n text,\n isMention: resolvedUsers.has(this.#botUserId),\n links: Array.from(links.values()).map((url) => ({ url })),\n author: {\n userId: comment.userId,\n userName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n fullName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: comment.userId === this.#botUserId,\n isMe: comment.userId === this.#botUserId,\n },\n metadata: {\n dateSent: comment.createdAt,\n edited: comment.editedAt !== undefined,\n editedAt: comment.editedAt,\n },\n attachments: comment.attachments.map((attachment) =>\n this.#createAttachment(comment.roomId, attachment)\n ),\n });\n }\n}\n\n/**\n * Parses a Chat SDK channel id into the Liveblocks room id for REST API calls.\n *\n * @throws {Error} If `channelId` is missing the `liveblocks:` prefix or has an empty room segment.\n */\nexport function getRoomIdFromChannelId(channelId: string): string {\n const prefix = `${ADAPTER_PREFIX}:`;\n if (!channelId.startsWith(prefix)) {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n const roomId = channelId.slice(prefix.length);\n if (roomId === \"\") {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n return roomId;\n}\n\nexport function convertPostableMessageToCommentBody(\n message: AdapterPostableMessage\n): CommentBody {\n if (typeof message === \"string\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message)\n );\n } else if (\"raw\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.raw)\n );\n } else if (\"markdown\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.markdown)\n );\n } else if (\"ast\" in message) {\n return convertChatRootElementToCommentBodyRootElement(message.ast);\n } else if (\"card\" in message) {\n // Liveblocks comments do not support cards and card elements, so we convert the message to markdown and then to a comment body\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(\n message.fallbackText ?? convertCardToMarkdownString(message.card)\n )\n );\n } else if (\"type\" in message && message.type === \"card\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(convertCardToMarkdownString(message))\n );\n } else {\n console.error(`Unexpected message type: ${JSON.stringify(message)}`);\n return {\n version: 1,\n content: [],\n };\n }\n}\n\nfunction convertCardToMarkdownString(card: CardElement): string {\n const parts: string[] = [];\n if (card.title) {\n parts.push(`**${card.title}**`);\n }\n if (card.subtitle) {\n parts.push(card.subtitle);\n }\n for (const child of card.children) {\n parts.push(convertCardChildToMarkdownString(child));\n }\n return parts.join(\"\\n\");\n}\n\nfunction convertCardChildToMarkdownString(child: CardChild): string {\n switch (child.type) {\n case \"text\":\n return child.content;\n case \"fields\":\n return child.children\n .map((field) => `**${field.label}**: ${field.value}`)\n .join(\"\\n\");\n case \"actions\":\n // Actions are interactive-only — exclude from fallback text. See: https://docs.slack.dev/reference/methods/chat.postMessage\n return \"\";\n case \"table\": {\n let markdown = \"|\";\n for (const header of child.headers) {\n markdown += ` ${header} |`;\n }\n markdown += \"\\n|\";\n for (const _ of child.headers) {\n markdown += \"--- |\";\n }\n markdown += \"\\n\";\n for (const row of child.rows) {\n markdown += \"|\";\n for (const cell of row) {\n markdown += ` ${cell} |`;\n }\n markdown += \"\\n\";\n }\n return markdown;\n }\n case \"section\":\n return child.children\n .map((c) => convertCardChildToMarkdownString(c))\n .filter(Boolean)\n .join(\"\\n\");\n case \"link\":\n return `[${child.label}](${child.url})`;\n case \"divider\":\n return \"---\";\n case \"image\":\n return ``;\n default:\n return \"\";\n }\n}\n\nfunction convertChatRootElementToCommentBodyRootElement(\n root: Root\n): CommentBody {\n return {\n version: 1,\n content: root.children.flatMap((child) =>\n convertChatBlockElementToCommentBodyBlockElement(child)\n ),\n };\n}\n\nfunction convertChatBlockElementToCommentBodyBlockElement(\n node: Root[\"children\"][number]\n): CommentBodyParagraph | CommentBodyParagraph[] {\n switch (node.type) {\n case \"paragraph\": {\n const children: CommentBodyInlineElement[] = [];\n for (const child of node.children) {\n children.push(\n convertChatInlineElementToCommentBodyInlineElement(child)\n );\n }\n return {\n type: \"paragraph\",\n children,\n };\n }\n case \"blockquote\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"list\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"listItem\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"heading\": {\n // Render headings as paragraphs as Liveblocks comments do not support headings\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: node.children,\n });\n }\n case \"code\": {\n // Render code blocks as paragraphs as Liveblocks comments do not support code blocks\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"html\": {\n // Render HTML as paragraphs as Liveblocks comments do not support HTML\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"table\": {\n // Convert table to ASCII table string and render as paragraph as Liveblocks comments do not support tables\n return {\n type: \"paragraph\",\n children: [{ text: tableToAscii(node) }],\n };\n }\n case \"link\":\n case \"image\":\n case \"strong\":\n case \"emphasis\":\n case \"inlineCode\":\n case \"delete\":\n case \"text\": {\n return {\n type: \"paragraph\",\n children: [convertChatInlineElementToCommentBodyInlineElement(node)],\n };\n }\n case \"break\":\n case \"thematicBreak\":\n case \"definition\":\n case \"tableCell\":\n case \"tableRow\":\n case \"yaml\":\n case \"footnoteDefinition\":\n case \"footnoteReference\":\n case \"imageReference\":\n case \"linkReference\":\n default: {\n return [];\n }\n }\n}\n\nfunction convertChatInlineElementToCommentBodyInlineElement(\n inline: PhrasingContent\n): CommentBodyInlineElement {\n switch (inline.type) {\n case \"text\":\n return { text: inline.value };\n case \"link\":\n return {\n type: \"link\",\n url: inline.url,\n // Link elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n };\n case \"image\":\n return { text: inline.url };\n case \"emphasis\":\n return {\n // Emphasis elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n italic: true,\n };\n case \"strong\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n bold: true,\n };\n case \"delete\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n strikethrough: true,\n };\n case \"inlineCode\":\n return {\n text: inline.value,\n code: true,\n };\n case \"html\":\n return { text: inline.value };\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default: {\n return { text: \"\" };\n }\n }\n}\n\nfunction convertChatInlineElementToPlainText(inline: PhrasingContent): string {\n switch (inline.type) {\n case \"text\":\n return inline.value;\n case \"link\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"image\":\n return inline.url;\n case \"emphasis\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"strong\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"delete\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"inlineCode\":\n return inline.value;\n case \"html\":\n return inline.value;\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default:\n return \"\";\n }\n}\n\n/**\n * Encode a pagination cursor using the format `base64url( [[\"id\", <string>], [\"createdAt\", <number>]] )`\n */\nexport function encodePaginationCursorByCreatedAt(\n id: string,\n createdAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"createdAt\", createdAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByCreatedAt(cursor: string): {\n id: string;\n createdAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"createdAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n createdAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nexport function encodePaginationCursorByUpdatedAt(\n id: string,\n updatedAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"updatedAt\", updatedAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByUpdatedAt(cursor: string): {\n id: string;\n updatedAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"updatedAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n updatedAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nfunction base64UrlEncode(str: string): string {\n const bytes = new TextEncoder().encode(str);\n const binary = String.fromCharCode(...bytes);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction base64UrlDecode(str: string): string {\n let s = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (s.length % 4) s += \"=\";\n const binary = atob(s);\n const bytes = Uint8Array.from(binary, (c) => c.codePointAt(0)!);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Slice a page from an in-memory list sorted by `createdAt` (oldest first).\n *\n * Cursors use the same `[[\"id\", ...], [\"createdAt\", ...]]` format as the\n * Liveblocks REST API so that they will be forward-compatible if the backend\n * adds server-side pagination.\n *\n * The cursor is always built from the boundary item of the current page and\n * means \"start after this item\" in the current traversal direction — matching\n * the `startingAfter` semantics used throughout the backend.\n *\n * When no `limit` is provided, all matching items are returned (preserving the\n * pre-pagination behaviour of returning the full list).\n */\nfunction slicePageByCreatedAt<T extends { id: string; createdAt: Date }>(\n data: T[],\n options: {\n /**\n * The direction to slice the page in.\n * - \"ascending\": Slice the page from the oldest item to the newest item.\n * - \"descending\": Slice the page from the newest item to the oldest item.\n */\n direction: \"ascending\" | \"descending\";\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { direction, limit, startingAfter } = options;\n\n // Sort data by 'createdAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.createdAt.getTime() !== b.createdAt.getTime()) {\n return a.createdAt.getTime() - b.createdAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let startIndex: number;\n let endIndex: number;\n\n if (direction === \"descending\") {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n } else {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the first item strictly after the cursor in sort order.\n startIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id < cursor.id)\n );\n if (startIndex === -1) {\n return { data: [], nextCursor: undefined };\n }\n } else {\n startIndex = 0;\n }\n endIndex =\n limit !== undefined\n ? Math.min(data.length, startIndex + limit)\n : data.length;\n }\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n let nextCursor: string | undefined;\n if (direction === \"descending\") {\n nextCursor =\n startIndex > 0\n ? encodePaginationCursorByCreatedAt(page[0]!.id, page[0]!.createdAt)\n : undefined;\n } else {\n nextCursor =\n endIndex < data.length\n ? encodePaginationCursorByCreatedAt(\n page[page.length - 1]!.id,\n page[page.length - 1]!.createdAt\n )\n : undefined;\n }\n\n return { data: page, nextCursor };\n}\n\n/**\n * Same as {@link slicePageByCreatedAt} (descending direction only) but sorts and\n * paginates on `updatedAt`. Thread listing does not expose forward pagination.\n */\nfunction slicePageByUpdatedAt<T extends { id: string; updatedAt: Date }>(\n data: T[],\n options: {\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { limit, startingAfter } = options;\n\n // Sort data by 'updatedAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.updatedAt.getTime() !== b.updatedAt.getTime()) {\n return a.updatedAt.getTime() - b.updatedAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let endIndex: number;\n if (startingAfter) {\n const cursor = decodePaginationCursorByUpdatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.updatedAt.getTime() > cursor.updatedAt.getTime() ||\n (c.updatedAt.getTime() === cursor.updatedAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n const startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n const nextCursor =\n startIndex > 0\n ? encodePaginationCursorByUpdatedAt(page[0]!.id, page[0]!.updatedAt)\n : undefined;\n\n return { data: page, nextCursor };\n}\n\nfunction getAttachmentType(mimeType: string): Attachment[\"type\"] {\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n } else if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n } else if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n return \"file\";\n}\n\nexport interface LiveblocksAdapterConfig<\n U extends BaseUserMeta,\n DGI extends BaseGroupInfo,\n> {\n /**\n * The Liveblocks secret key. Must start with \"sk_\". Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/apikeys\n */\n apiKey: string;\n /**\n * The Liveblocks webhook signing secret. Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/webhooks\n * @example \"whsec_wPbvQ+u3VtN2e2tRPDKchQ1tBZ3svaHLm\"\n */\n webhookSecret: string;\n /**\n * A function that returns user info from user IDs; used to resolve @user mentions in comment bodies.\n * This function should return an array of user info in the same order as the input user IDs, or `undefined` to skip resolution.\n */\n resolveUsers?: (\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n /**\n * A function that returns group info from group IDs; used to resolve @group mentions in comment bodies.\n * This function should return an array of group info in the same order as the input group IDs, or `undefined` to skip resolution.\n */\n resolveGroupsInfo?: (\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>;\n /**\n * The user ID used when the bot creates, edits, or reacts to comments.\n * This should match your app’s user identifiers.\n */\n botUserId: string;\n /**\n * The display name for the chat user representing the bot.\n * @default \"liveblocks-bot\"\n */\n botUserName?: string;\n /**\n * A Chat SDK–compatible logger.\n * @default ConsoleLogger at info level, scoped to this adapter\n */\n logger?: Logger;\n}\n\n/**\n * Creates a {@link LiveblocksAdapter} configured for Liveblocks Comments.\n */\nexport function createLiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n>(config: LiveblocksAdapterConfig<U, DGI>): LiveblocksAdapter<U, DGI> {\n return new LiveblocksAdapter(config);\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -54,14 +54,44 @@ declare class LiveblocksAdapter<U extends BaseUserMeta = BaseUserMeta, DGI exten
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
interface LiveblocksAdapterConfig<U extends BaseUserMeta, DGI extends BaseGroupInfo> {
|
|
57
|
+
/**
|
|
58
|
+
* The Liveblocks secret key. Must start with "sk_". Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/apikeys
|
|
59
|
+
*/
|
|
57
60
|
apiKey: string;
|
|
61
|
+
/**
|
|
62
|
+
* The Liveblocks webhook signing secret. Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/webhooks
|
|
63
|
+
* @example "whsec_wPbvQ+u3VtN2e2tRPDKchQ1tBZ3svaHLm"
|
|
64
|
+
*/
|
|
58
65
|
webhookSecret: string;
|
|
66
|
+
/**
|
|
67
|
+
* A function that returns user info from user IDs; used to resolve @user mentions in comment bodies.
|
|
68
|
+
* This function should return an array of user info in the same order as the input user IDs, or `undefined` to skip resolution.
|
|
69
|
+
*/
|
|
59
70
|
resolveUsers?: (args: ResolveUsersArgs) => Awaitable<(U["info"] | undefined)[] | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* A function that returns group info from group IDs; used to resolve @group mentions in comment bodies.
|
|
73
|
+
* This function should return an array of group info in the same order as the input group IDs, or `undefined` to skip resolution.
|
|
74
|
+
*/
|
|
60
75
|
resolveGroupsInfo?: (args: ResolveGroupsInfoArgs) => Awaitable<(DGI | undefined)[] | undefined>;
|
|
76
|
+
/**
|
|
77
|
+
* The user ID used when the bot creates, edits, or reacts to comments.
|
|
78
|
+
* This should match your app’s user identifiers.
|
|
79
|
+
*/
|
|
61
80
|
botUserId: string;
|
|
81
|
+
/**
|
|
82
|
+
* The display name for the chat user representing the bot.
|
|
83
|
+
* @default "liveblocks-bot"
|
|
84
|
+
*/
|
|
62
85
|
botUserName?: string;
|
|
86
|
+
/**
|
|
87
|
+
* A Chat SDK–compatible logger.
|
|
88
|
+
* @default ConsoleLogger at info level, scoped to this adapter
|
|
89
|
+
*/
|
|
63
90
|
logger?: Logger;
|
|
64
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Creates a {@link LiveblocksAdapter} configured for Liveblocks Comments.
|
|
94
|
+
*/
|
|
65
95
|
declare function createLiveblocksAdapter<U extends BaseUserMeta = BaseUserMeta, DGI extends BaseGroupInfo = BaseGroupInfo>(config: LiveblocksAdapterConfig<U, DGI>): LiveblocksAdapter<U, DGI>;
|
|
66
96
|
|
|
67
97
|
export { LiveblocksAdapter, type LiveblocksAdapterConfig, createLiveblocksAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -54,14 +54,44 @@ declare class LiveblocksAdapter<U extends BaseUserMeta = BaseUserMeta, DGI exten
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
interface LiveblocksAdapterConfig<U extends BaseUserMeta, DGI extends BaseGroupInfo> {
|
|
57
|
+
/**
|
|
58
|
+
* The Liveblocks secret key. Must start with "sk_". Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/apikeys
|
|
59
|
+
*/
|
|
57
60
|
apiKey: string;
|
|
61
|
+
/**
|
|
62
|
+
* The Liveblocks webhook signing secret. Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/webhooks
|
|
63
|
+
* @example "whsec_wPbvQ+u3VtN2e2tRPDKchQ1tBZ3svaHLm"
|
|
64
|
+
*/
|
|
58
65
|
webhookSecret: string;
|
|
66
|
+
/**
|
|
67
|
+
* A function that returns user info from user IDs; used to resolve @user mentions in comment bodies.
|
|
68
|
+
* This function should return an array of user info in the same order as the input user IDs, or `undefined` to skip resolution.
|
|
69
|
+
*/
|
|
59
70
|
resolveUsers?: (args: ResolveUsersArgs) => Awaitable<(U["info"] | undefined)[] | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* A function that returns group info from group IDs; used to resolve @group mentions in comment bodies.
|
|
73
|
+
* This function should return an array of group info in the same order as the input group IDs, or `undefined` to skip resolution.
|
|
74
|
+
*/
|
|
60
75
|
resolveGroupsInfo?: (args: ResolveGroupsInfoArgs) => Awaitable<(DGI | undefined)[] | undefined>;
|
|
76
|
+
/**
|
|
77
|
+
* The user ID used when the bot creates, edits, or reacts to comments.
|
|
78
|
+
* This should match your app’s user identifiers.
|
|
79
|
+
*/
|
|
61
80
|
botUserId: string;
|
|
81
|
+
/**
|
|
82
|
+
* The display name for the chat user representing the bot.
|
|
83
|
+
* @default "liveblocks-bot"
|
|
84
|
+
*/
|
|
62
85
|
botUserName?: string;
|
|
86
|
+
/**
|
|
87
|
+
* A Chat SDK–compatible logger.
|
|
88
|
+
* @default ConsoleLogger at info level, scoped to this adapter
|
|
89
|
+
*/
|
|
63
90
|
logger?: Logger;
|
|
64
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Creates a {@link LiveblocksAdapter} configured for Liveblocks Comments.
|
|
94
|
+
*/
|
|
65
95
|
declare function createLiveblocksAdapter<U extends BaseUserMeta = BaseUserMeta, DGI extends BaseGroupInfo = BaseGroupInfo>(config: LiveblocksAdapterConfig<U, DGI>): LiveblocksAdapter<U, DGI>;
|
|
66
96
|
|
|
67
97
|
export { LiveblocksAdapter, type LiveblocksAdapterConfig, createLiveblocksAdapter };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import {\n type Awaitable,\n type BaseGroupInfo,\n type BaseUserMeta,\n type CommentBody,\n type CommentBodyInlineElement,\n type CommentBodyParagraph,\n getMentionsFromCommentBody,\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n type ResolveGroupsInfoArgs,\n type ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport {\n type CommentData,\n Liveblocks,\n LiveblocksError,\n type WebhookEvent,\n WebhookHandler,\n} from \"@liveblocks/node\";\nimport {\n type Adapter,\n type AdapterPostableMessage,\n type Attachment,\n type CardChild,\n type CardElement,\n type ChannelInfo,\n type ChatInstance,\n ConsoleLogger,\n defaultEmojiResolver,\n type EmojiValue,\n type FetchOptions,\n type FetchResult,\n type FormattedContent,\n type Link,\n type ListThreadsOptions,\n type ListThreadsResult,\n type Logger,\n Message,\n type Paragraph,\n parseMarkdown,\n type RawMessage,\n type Root,\n tableToAscii,\n type Text,\n type ThreadInfo,\n toPlainText,\n type WebhookOptions,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Strong,\n} from \"chat\";\n\ntype PhrasingContent = Paragraph[\"children\"][number];\n\nconst ADAPTER_PREFIX = \"liveblocks\";\nexport class LiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n> implements Adapter<{ roomId: string; threadId: string }, CommentData>\n{\n readonly name = \"liveblocks\";\n readonly userName: string;\n readonly #client: Liveblocks;\n readonly #webhookHandler: WebhookHandler;\n readonly #resolveUsers:\n | ((\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>)\n | undefined;\n readonly #resolveGroupsInfo:\n | ((\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>)\n | undefined;\n readonly #logger: Logger;\n readonly #botUserId: string;\n #chat: ChatInstance | null = null;\n constructor(config: LiveblocksAdapterConfig<U, DGI>) {\n this.#client = new Liveblocks({ secret: config.apiKey });\n this.#webhookHandler = new WebhookHandler(config.webhookSecret);\n this.#resolveUsers = config.resolveUsers;\n this.#resolveGroupsInfo = config.resolveGroupsInfo;\n this.#botUserId = config.botUserId;\n this.userName = config.botUserName ?? \"liveblocks-bot\";\n this.#logger =\n config.logger ?? new ConsoleLogger(\"info\").child(ADAPTER_PREFIX);\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.#chat = chat;\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions\n ): Promise<Response> {\n let event: WebhookEvent;\n try {\n event = this.#webhookHandler.verifyRequest({\n headers: request.headers,\n rawBody: await request.text(),\n });\n } catch (error) {\n this.#logger.error(\"Failed to verify webhook request\", { error });\n return new Response(\"Invalid webhook request\", { status: 401 });\n }\n\n if (event.type === \"commentCreated\") {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const comment = await this.#client.getComment({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n commentId: event.data.commentId,\n });\n if (comment.deletedAt !== undefined) {\n return new Response(null, { status: 200 });\n }\n\n this.#chat?.processMessage(\n this,\n threadId,\n () => this.#convertLiveblocksCommentDataToChatMessage(comment),\n options\n );\n } else if (\n event.type === \"commentReactionAdded\" ||\n event.type === \"commentReactionRemoved\"\n ) {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const userId =\n event.type === \"commentReactionAdded\"\n ? event.data.addedBy\n : event.data.removedBy;\n\n const resolvedUsers = await this.#resolveUsers?.({ userIds: [userId] });\n const user = resolvedUsers?.[0];\n\n this.#chat?.processReaction(\n {\n added: event.type === \"commentReactionAdded\",\n emoji: defaultEmojiResolver.fromGChat(event.data.emoji),\n rawEmoji: event.data.emoji,\n messageId: event.data.commentId,\n threadId,\n user: {\n userId,\n userName: user?.name ?? userId,\n fullName: user?.name ?? userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: userId === this.#botUserId,\n isMe: userId === this.#botUserId,\n },\n raw: event.data,\n adapter: this,\n },\n options\n );\n }\n\n return new Response(null, { status: 200 });\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n const comment = await this.#client.createComment({\n roomId,\n threadId: threadId_liveblocks,\n data: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n });\n return { id: comment.id, threadId, raw: comment };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.editComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n body: convertPostableMessageToCommentBody(message),\n },\n });\n\n return { id: comment.id, threadId, raw: comment };\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n await this.#client.deleteComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.addCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n // Liveblocks expects unicode emoji; 'toGChat' converts normalized names (e.g. 'thumbs_up') to unicode ('👍').\n // Unknown normalized names (e.g. 'custom_emoji') will fail Liveblocks validation since they are not valid unicode emoji.\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async removeReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.removeCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async fetchMessages(\n threadId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n const comments = thread.comments.filter(\n (comment) => comment.deletedAt === undefined\n );\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get thread' API returns all comments in the thread in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n return {\n id: threadId,\n channelId: `${ADAPTER_PREFIX}:${roomId}`,\n metadata: {\n resolved: thread.resolved,\n ...thread.metadata,\n },\n channelName: thread.roomId,\n isDM: false,\n };\n }\n\n async fetchMessage(\n threadId: string,\n messageId: string\n ): Promise<Message<CommentData> | null> {\n try {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.getComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n if (comment.deletedAt !== undefined) {\n return null;\n }\n return this.#convertLiveblocksCommentDataToChatMessage(comment);\n } catch (error) {\n if (error instanceof LiveblocksError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async listThreads(\n channelId: string,\n options?: ListThreadsOptions\n ): Promise<ListThreadsResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n const threads = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment === undefined) return null;\n\n return {\n id: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n updatedAt: thread.updatedAt,\n numOfComments: nonDeletedComments.length,\n firstComment: firstNonDeletedComment,\n };\n })\n .filter((thread) => thread !== null);\n\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByUpdatedAt(threads, {\n limit,\n startingAfter,\n });\n\n return {\n threads: await Promise.all(\n sliced.data.map(async (thread) => ({\n id: thread.id,\n rootMessage: await this.#convertLiveblocksCommentDataToChatMessage(\n thread.firstComment\n ),\n lastReplyAt: thread.updatedAt,\n replyCount: thread.numOfComments - 1,\n }))\n ),\n nextCursor: sliced.nextCursor,\n };\n }\n\n async fetchChannelInfo(channelId: string): Promise<ChannelInfo> {\n const room = await this.#client.getRoom(getRoomIdFromChannelId(channelId));\n return {\n id: room.id,\n name: room.id,\n isDM: false,\n metadata: {},\n };\n }\n\n async fetchChannelMessages(\n channelId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n\n const comments = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment !== undefined) {\n return firstNonDeletedComment;\n }\n return null;\n })\n .filter((comment) => comment !== null);\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room (sorted by creation date in ascending order)\n // and each thread contains all comments in the thread (sorted by creation date in ascending order),\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async postChannelMessage(\n channelId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const thread = await this.#client.createThread({\n roomId,\n data: {\n comment: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n },\n });\n const firstComment = thread.comments[0];\n if (firstComment === undefined) {\n throw new Error(`Failed to create thread in room ${channelId}`);\n }\n return {\n id: firstComment.id,\n threadId: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n raw: firstComment,\n };\n }\n\n // This method isn't used by the Chat SDK, but it's required to implement the Adapter interface,\n // so we will return a less rich message here. We have a separate (asynchronous) method for converting a comment to a message.\n parseMessage(data: CommentData): Message<CommentData> {\n return new Message({\n id: data.id,\n threadId: this.encodeThreadId({\n roomId: data.roomId,\n threadId: data.threadId,\n }),\n raw: data,\n formatted: { type: \"root\", children: [] },\n text: \"\",\n author: {\n userId: data.userId,\n userName: data.userId,\n fullName: data.userId,\n isBot: data.userId === this.#botUserId,\n isMe: data.userId === this.#botUserId,\n },\n metadata: {\n dateSent: data.createdAt,\n edited: !!data.editedAt,\n editedAt: data.editedAt,\n },\n attachments: data.attachments.map((att) =>\n this.#createAttachment(data.roomId, att)\n ),\n });\n }\n\n renderFormatted(content: FormattedContent): string {\n // Liveblocks comments do not support markdown as input, so we convert the content to plain text.\n return toPlainText(content);\n }\n\n channelIdFromThreadId(threadId: string): string {\n const { roomId } = this.decodeThreadId(threadId);\n return `${ADAPTER_PREFIX}:${roomId}`;\n }\n\n /**\n * This method is a no-op as typing indicators are not supported by Liveblocks Comments.\n */\n startTyping(_threadId: string, _status?: string): Promise<void> {\n return Promise.resolve();\n }\n\n #createAttachment(\n roomId: string,\n attachment: CommentData[\"attachments\"][number]\n ): Attachment {\n const client = this.#client;\n return {\n type: getAttachmentType(attachment.mimeType),\n name: attachment.name,\n mimeType: attachment.mimeType,\n size: attachment.size,\n fetchData: async () => {\n const { url } = await client.getAttachment({\n roomId,\n attachmentId: attachment.id,\n });\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch attachment \"${attachment.name}\": ${response.status} ${response.statusText}`\n );\n }\n return Buffer.from(await response.arrayBuffer());\n },\n };\n }\n\n /**\n * Encodes a Liveblocks room ID and thread ID into a single thread ID string.\n *\n * Format: `liveblocks:{roomId}:{threadId}`\n *\n * **Note**: Room IDs may contain colons (':'), which are preserved during encoding/decoding.\n * However, Liveblocks thread IDs must not contain colons as the last colon is used as the delimiter when decoding.\n */\n encodeThreadId(data: { roomId: string; threadId: string }): string {\n return `${ADAPTER_PREFIX}:${data.roomId}:${data.threadId}`;\n }\n\n /**\n * Decodes an encoded thread ID string back into its room ID and thread ID components.\n *\n * @throws {Error} If the thread ID format is invalid\n */\n decodeThreadId(threadId: string): { roomId: string; threadId: string } {\n const parts = threadId.split(\":\");\n if (parts.length < 3 || parts[0] !== ADAPTER_PREFIX) {\n throw new Error(\n `Invalid thread ID: ${threadId}. Expected format: liveblocks:{roomId}:{threadId}`\n );\n }\n return {\n roomId: parts.slice(1, -1).join(\":\"),\n threadId: parts[parts.length - 1]!,\n };\n }\n\n async #convertLiveblocksCommentDataToChatMessage(\n comment: Extract<CommentData, { body: CommentBody }>\n ): Promise<Message<CommentData>> {\n const mentions = getMentionsFromCommentBody(comment.body);\n const userIds = new Set<string>([comment.userId]); // Initialize with the author's user id\n const groupIds = new Set<string>();\n for (const mention of mentions) {\n if (mention.kind === \"user\") {\n userIds.add(mention.id);\n } else if (mention.kind === \"group\") {\n groupIds.add(mention.id);\n }\n }\n\n const [users, groups] = await Promise.all([\n this.#resolveUsers\n ? this.#resolveUsers({ userIds: Array.from(userIds) })\n : undefined,\n this.#resolveGroupsInfo && groupIds.size > 0\n ? this.#resolveGroupsInfo({ groupIds: Array.from(groupIds) })\n : undefined,\n ]);\n\n const resolvedUsers = new Map<string, U[\"info\"]>();\n if (users !== undefined) {\n for (const [index, userId] of Array.from(userIds).entries()) {\n const user = users[index];\n if (user === undefined) continue;\n resolvedUsers.set(userId, user);\n }\n }\n const resolvedGroups = new Map<string, DGI>();\n if (groups !== undefined) {\n for (const [index, groupId] of Array.from(groupIds).entries()) {\n const group = groups[index];\n if (group === undefined) continue;\n resolvedGroups.set(groupId, group);\n }\n }\n\n const links = new Set<string>();\n\n const nodes: Paragraph[] = comment.body.content.map((block) => {\n const children: Array<\n Text | Link | Emphasis | Strong | InlineCode | Delete\n > = [];\n for (const inline of block.children) {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n children.push({\n type: \"text\",\n value: resolvedUsers.get(inline.id)?.name ?? inline.id,\n });\n } else {\n children.push({\n type: \"text\",\n value: resolvedGroups.get(inline.id)?.name ?? inline.id,\n });\n }\n } else if (isCommentBodyLink(inline)) {\n links.add(inline.url);\n children.push({\n type: \"link\",\n children: [{ type: \"text\", value: inline.text ?? \"\" }],\n url: inline.url,\n });\n } else if (isCommentBodyText(inline)) {\n if (inline.code) {\n children.push({\n type: \"inlineCode\",\n value: inline.text,\n });\n } else {\n // Build nested structure for combined styles (bold, italic, strikethrough)\n let node: Text | Emphasis | Strong | Delete = {\n type: \"text\",\n value: inline.text,\n };\n\n if (inline.strikethrough) {\n node = { type: \"delete\", children: [node] };\n }\n if (inline.italic) {\n node = { type: \"emphasis\", children: [node] };\n }\n if (inline.bold) {\n node = { type: \"strong\", children: [node] };\n }\n\n children.push(node);\n }\n }\n }\n return { type: \"paragraph\", children };\n });\n\n const text = comment.body.content.reduce((acc, block) => {\n return (\n acc +\n block.children.reduce((acc, inline) => {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n return acc + (resolvedUsers.get(inline.id)?.name ?? inline.id);\n } else {\n return acc + (resolvedGroups.get(inline.id)?.name ?? inline.id);\n }\n } else if (isCommentBodyLink(inline)) {\n if (inline.text) {\n return acc + inline.text;\n } else {\n return acc + inline.url;\n }\n } else if (isCommentBodyText(inline)) {\n return acc + inline.text;\n }\n return acc;\n }, \"\")\n );\n }, \"\");\n\n return new Message({\n id: comment.id,\n threadId: this.encodeThreadId({\n roomId: comment.roomId,\n threadId: comment.threadId,\n }),\n raw: comment,\n formatted: { type: \"root\", children: nodes },\n text,\n isMention: resolvedUsers.has(this.#botUserId),\n links: Array.from(links.values()).map((url) => ({ url })),\n author: {\n userId: comment.userId,\n userName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n fullName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: comment.userId === this.#botUserId,\n isMe: comment.userId === this.#botUserId,\n },\n metadata: {\n dateSent: comment.createdAt,\n edited: comment.editedAt !== undefined,\n editedAt: comment.editedAt,\n },\n attachments: comment.attachments.map((attachment) =>\n this.#createAttachment(comment.roomId, attachment)\n ),\n });\n }\n}\n\n/**\n * Parses a Chat SDK channel id into the Liveblocks room id for REST API calls.\n *\n * @throws {Error} If `channelId` is missing the `liveblocks:` prefix or has an empty room segment.\n */\nexport function getRoomIdFromChannelId(channelId: string): string {\n const prefix = `${ADAPTER_PREFIX}:`;\n if (!channelId.startsWith(prefix)) {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n const roomId = channelId.slice(prefix.length);\n if (roomId === \"\") {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n return roomId;\n}\n\nexport function convertPostableMessageToCommentBody(\n message: AdapterPostableMessage\n): CommentBody {\n if (typeof message === \"string\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message)\n );\n } else if (\"raw\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.raw)\n );\n } else if (\"markdown\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.markdown)\n );\n } else if (\"ast\" in message) {\n return convertChatRootElementToCommentBodyRootElement(message.ast);\n } else if (\"card\" in message) {\n // Liveblocks comments do not support cards and card elements, so we convert the message to markdown and then to a comment body\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(\n message.fallbackText ?? convertCardToMarkdownString(message.card)\n )\n );\n } else if (\"type\" in message && message.type === \"card\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(convertCardToMarkdownString(message))\n );\n } else {\n console.error(`Unexpected message type: ${JSON.stringify(message)}`);\n return {\n version: 1,\n content: [],\n };\n }\n}\n\nfunction convertCardToMarkdownString(card: CardElement): string {\n const parts: string[] = [];\n if (card.title) {\n parts.push(`**${card.title}**`);\n }\n if (card.subtitle) {\n parts.push(card.subtitle);\n }\n for (const child of card.children) {\n parts.push(convertCardChildToMarkdownString(child));\n }\n return parts.join(\"\\n\");\n}\n\nfunction convertCardChildToMarkdownString(child: CardChild): string {\n switch (child.type) {\n case \"text\":\n return child.content;\n case \"fields\":\n return child.children\n .map((field) => `**${field.label}**: ${field.value}`)\n .join(\"\\n\");\n case \"actions\":\n // Actions are interactive-only — exclude from fallback text. See: https://docs.slack.dev/reference/methods/chat.postMessage\n return \"\";\n case \"table\": {\n let markdown = \"|\";\n for (const header of child.headers) {\n markdown += ` ${header} |`;\n }\n markdown += \"\\n|\";\n for (const _ of child.headers) {\n markdown += \"--- |\";\n }\n markdown += \"\\n\";\n for (const row of child.rows) {\n markdown += \"|\";\n for (const cell of row) {\n markdown += ` ${cell} |`;\n }\n markdown += \"\\n\";\n }\n return markdown;\n }\n case \"section\":\n return child.children\n .map((c) => convertCardChildToMarkdownString(c))\n .filter(Boolean)\n .join(\"\\n\");\n case \"link\":\n return `[${child.label}](${child.url})`;\n case \"divider\":\n return \"---\";\n case \"image\":\n return ``;\n default:\n return \"\";\n }\n}\n\nfunction convertChatRootElementToCommentBodyRootElement(\n root: Root\n): CommentBody {\n return {\n version: 1,\n content: root.children.flatMap((child) =>\n convertChatBlockElementToCommentBodyBlockElement(child)\n ),\n };\n}\n\nfunction convertChatBlockElementToCommentBodyBlockElement(\n node: Root[\"children\"][number]\n): CommentBodyParagraph | CommentBodyParagraph[] {\n switch (node.type) {\n case \"paragraph\": {\n const children: CommentBodyInlineElement[] = [];\n for (const child of node.children) {\n children.push(\n convertChatInlineElementToCommentBodyInlineElement(child)\n );\n }\n return {\n type: \"paragraph\",\n children,\n };\n }\n case \"blockquote\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"list\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"listItem\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"heading\": {\n // Render headings as paragraphs as Liveblocks comments do not support headings\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: node.children,\n });\n }\n case \"code\": {\n // Render code blocks as paragraphs as Liveblocks comments do not support code blocks\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"html\": {\n // Render HTML as paragraphs as Liveblocks comments do not support HTML\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"table\": {\n // Convert table to ASCII table string and render as paragraph as Liveblocks comments do not support tables\n return {\n type: \"paragraph\",\n children: [{ text: tableToAscii(node) }],\n };\n }\n case \"link\":\n case \"image\":\n case \"strong\":\n case \"emphasis\":\n case \"inlineCode\":\n case \"delete\":\n case \"text\": {\n return {\n type: \"paragraph\",\n children: [convertChatInlineElementToCommentBodyInlineElement(node)],\n };\n }\n case \"break\":\n case \"thematicBreak\":\n case \"definition\":\n case \"tableCell\":\n case \"tableRow\":\n case \"yaml\":\n case \"footnoteDefinition\":\n case \"footnoteReference\":\n case \"imageReference\":\n case \"linkReference\":\n default: {\n return [];\n }\n }\n}\n\nfunction convertChatInlineElementToCommentBodyInlineElement(\n inline: PhrasingContent\n): CommentBodyInlineElement {\n switch (inline.type) {\n case \"text\":\n return { text: inline.value };\n case \"link\":\n return {\n type: \"link\",\n url: inline.url,\n // Link elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n };\n case \"image\":\n return { text: inline.url };\n case \"emphasis\":\n return {\n // Emphasis elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n italic: true,\n };\n case \"strong\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n bold: true,\n };\n case \"delete\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n strikethrough: true,\n };\n case \"inlineCode\":\n return {\n text: inline.value,\n code: true,\n };\n case \"html\":\n return { text: inline.value };\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default: {\n return { text: \"\" };\n }\n }\n}\n\nfunction convertChatInlineElementToPlainText(inline: PhrasingContent): string {\n switch (inline.type) {\n case \"text\":\n return inline.value;\n case \"link\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"image\":\n return inline.url;\n case \"emphasis\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"strong\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"delete\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"inlineCode\":\n return inline.value;\n case \"html\":\n return inline.value;\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default:\n return \"\";\n }\n}\n\n/**\n * Encode a pagination cursor using the format `base64url( [[\"id\", <string>], [\"createdAt\", <number>]] )`\n */\nexport function encodePaginationCursorByCreatedAt(\n id: string,\n createdAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"createdAt\", createdAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByCreatedAt(cursor: string): {\n id: string;\n createdAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"createdAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n createdAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nexport function encodePaginationCursorByUpdatedAt(\n id: string,\n updatedAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"updatedAt\", updatedAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByUpdatedAt(cursor: string): {\n id: string;\n updatedAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"updatedAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n updatedAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nfunction base64UrlEncode(str: string): string {\n const bytes = new TextEncoder().encode(str);\n const binary = String.fromCharCode(...bytes);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction base64UrlDecode(str: string): string {\n let s = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (s.length % 4) s += \"=\";\n const binary = atob(s);\n const bytes = Uint8Array.from(binary, (c) => c.codePointAt(0)!);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Slice a page from an in-memory list sorted by `createdAt` (oldest first).\n *\n * Cursors use the same `[[\"id\", ...], [\"createdAt\", ...]]` format as the\n * Liveblocks REST API so that they will be forward-compatible if the backend\n * adds server-side pagination.\n *\n * The cursor is always built from the boundary item of the current page and\n * means \"start after this item\" in the current traversal direction — matching\n * the `startingAfter` semantics used throughout the backend.\n *\n * When no `limit` is provided, all matching items are returned (preserving the\n * pre-pagination behaviour of returning the full list).\n */\nfunction slicePageByCreatedAt<T extends { id: string; createdAt: Date }>(\n data: T[],\n options: {\n /**\n * The direction to slice the page in.\n * - \"ascending\": Slice the page from the oldest item to the newest item.\n * - \"descending\": Slice the page from the newest item to the oldest item.\n */\n direction: \"ascending\" | \"descending\";\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { direction, limit, startingAfter } = options;\n\n // Sort data by 'createdAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.createdAt.getTime() !== b.createdAt.getTime()) {\n return a.createdAt.getTime() - b.createdAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let startIndex: number;\n let endIndex: number;\n\n if (direction === \"descending\") {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n } else {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the first item strictly after the cursor in sort order.\n startIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id < cursor.id)\n );\n if (startIndex === -1) {\n return { data: [], nextCursor: undefined };\n }\n } else {\n startIndex = 0;\n }\n endIndex =\n limit !== undefined\n ? Math.min(data.length, startIndex + limit)\n : data.length;\n }\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n let nextCursor: string | undefined;\n if (direction === \"descending\") {\n nextCursor =\n startIndex > 0\n ? encodePaginationCursorByCreatedAt(page[0]!.id, page[0]!.createdAt)\n : undefined;\n } else {\n nextCursor =\n endIndex < data.length\n ? encodePaginationCursorByCreatedAt(\n page[page.length - 1]!.id,\n page[page.length - 1]!.createdAt\n )\n : undefined;\n }\n\n return { data: page, nextCursor };\n}\n\n/**\n * Same as {@link slicePageByCreatedAt} (descending direction only) but sorts and\n * paginates on `updatedAt`. Thread listing does not expose forward pagination.\n */\nfunction slicePageByUpdatedAt<T extends { id: string; updatedAt: Date }>(\n data: T[],\n options: {\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { limit, startingAfter } = options;\n\n // Sort data by 'updatedAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.updatedAt.getTime() !== b.updatedAt.getTime()) {\n return a.updatedAt.getTime() - b.updatedAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let endIndex: number;\n if (startingAfter) {\n const cursor = decodePaginationCursorByUpdatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.updatedAt.getTime() > cursor.updatedAt.getTime() ||\n (c.updatedAt.getTime() === cursor.updatedAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n const startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n const nextCursor =\n startIndex > 0\n ? encodePaginationCursorByUpdatedAt(page[0]!.id, page[0]!.updatedAt)\n : undefined;\n\n return { data: page, nextCursor };\n}\n\nfunction getAttachmentType(mimeType: string): Attachment[\"type\"] {\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n } else if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n } else if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n return \"file\";\n}\n\nexport interface LiveblocksAdapterConfig<\n U extends BaseUserMeta,\n DGI extends BaseGroupInfo,\n> {\n apiKey: string;\n webhookSecret: string;\n resolveUsers?: (\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n resolveGroupsInfo?: (\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>;\n botUserId: string;\n botUserName?: string;\n logger?: Logger;\n}\n\nexport function createLiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n>(config: LiveblocksAdapterConfig<U, DGI>): LiveblocksAdapter<U, DGI> {\n return new LiveblocksAdapter(config);\n}\n"],"mappings":"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","names":["acc"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts"],"sourcesContent":["import {\n type Awaitable,\n type BaseGroupInfo,\n type BaseUserMeta,\n type CommentBody,\n type CommentBodyInlineElement,\n type CommentBodyParagraph,\n getMentionsFromCommentBody,\n isCommentBodyLink,\n isCommentBodyMention,\n isCommentBodyText,\n type ResolveGroupsInfoArgs,\n type ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport {\n type CommentData,\n Liveblocks,\n LiveblocksError,\n type WebhookEvent,\n WebhookHandler,\n} from \"@liveblocks/node\";\nimport {\n type Adapter,\n type AdapterPostableMessage,\n type Attachment,\n type CardChild,\n type CardElement,\n type ChannelInfo,\n type ChatInstance,\n ConsoleLogger,\n defaultEmojiResolver,\n type EmojiValue,\n type FetchOptions,\n type FetchResult,\n type FormattedContent,\n type Link,\n type ListThreadsOptions,\n type ListThreadsResult,\n type Logger,\n Message,\n type Paragraph,\n parseMarkdown,\n type RawMessage,\n type Root,\n tableToAscii,\n type Text,\n type ThreadInfo,\n toPlainText,\n type WebhookOptions,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Strong,\n} from \"chat\";\n\ntype PhrasingContent = Paragraph[\"children\"][number];\n\nconst ADAPTER_PREFIX = \"liveblocks\";\nexport class LiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n> implements Adapter<{ roomId: string; threadId: string }, CommentData>\n{\n readonly name = \"liveblocks\";\n readonly userName: string;\n readonly #client: Liveblocks;\n readonly #webhookHandler: WebhookHandler;\n readonly #resolveUsers:\n | ((\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>)\n | undefined;\n readonly #resolveGroupsInfo:\n | ((\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>)\n | undefined;\n readonly #logger: Logger;\n readonly #botUserId: string;\n #chat: ChatInstance | null = null;\n constructor(config: LiveblocksAdapterConfig<U, DGI>) {\n this.#client = new Liveblocks({ secret: config.apiKey });\n this.#webhookHandler = new WebhookHandler(config.webhookSecret);\n this.#resolveUsers = config.resolveUsers;\n this.#resolveGroupsInfo = config.resolveGroupsInfo;\n this.#botUserId = config.botUserId;\n this.userName = config.botUserName ?? \"liveblocks-bot\";\n this.#logger =\n config.logger ?? new ConsoleLogger(\"info\").child(ADAPTER_PREFIX);\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.#chat = chat;\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions\n ): Promise<Response> {\n let event: WebhookEvent;\n try {\n event = this.#webhookHandler.verifyRequest({\n headers: request.headers,\n rawBody: await request.text(),\n });\n } catch (error) {\n this.#logger.error(\"Failed to verify webhook request\", { error });\n return new Response(\"Invalid webhook request\", { status: 401 });\n }\n\n if (event.type === \"commentCreated\") {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const comment = await this.#client.getComment({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n commentId: event.data.commentId,\n });\n if (comment.deletedAt !== undefined) {\n return new Response(null, { status: 200 });\n }\n\n this.#chat?.processMessage(\n this,\n threadId,\n () => this.#convertLiveblocksCommentDataToChatMessage(comment),\n options\n );\n } else if (\n event.type === \"commentReactionAdded\" ||\n event.type === \"commentReactionRemoved\"\n ) {\n const threadId = this.encodeThreadId({\n roomId: event.data.roomId,\n threadId: event.data.threadId,\n });\n\n const userId =\n event.type === \"commentReactionAdded\"\n ? event.data.addedBy\n : event.data.removedBy;\n\n const resolvedUsers = await this.#resolveUsers?.({ userIds: [userId] });\n const user = resolvedUsers?.[0];\n\n this.#chat?.processReaction(\n {\n added: event.type === \"commentReactionAdded\",\n emoji: defaultEmojiResolver.fromGChat(event.data.emoji),\n rawEmoji: event.data.emoji,\n messageId: event.data.commentId,\n threadId,\n user: {\n userId,\n userName: user?.name ?? userId,\n fullName: user?.name ?? userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: userId === this.#botUserId,\n isMe: userId === this.#botUserId,\n },\n raw: event.data,\n adapter: this,\n },\n options\n );\n }\n\n return new Response(null, { status: 200 });\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n const comment = await this.#client.createComment({\n roomId,\n threadId: threadId_liveblocks,\n data: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n });\n return { id: comment.id, threadId, raw: comment };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.editComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n body: convertPostableMessageToCommentBody(message),\n },\n });\n\n return { id: comment.id, threadId, raw: comment };\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n await this.#client.deleteComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.addCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n // Liveblocks expects unicode emoji; 'toGChat' converts normalized names (e.g. 'thumbs_up') to unicode ('👍').\n // Unknown normalized names (e.g. 'custom_emoji') will fail Liveblocks validation since they are not valid unicode emoji.\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async removeReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string\n ): Promise<void> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n await this.#client.removeCommentReaction({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n data: {\n emoji: defaultEmojiResolver.toGChat(emoji),\n userId: this.#botUserId,\n },\n });\n }\n\n async fetchMessages(\n threadId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n const comments = thread.comments.filter(\n (comment) => comment.deletedAt === undefined\n );\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get thread' API returns all comments in the thread in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const thread = await this.#client.getThread({\n roomId,\n threadId: threadId_liveblocks,\n });\n\n return {\n id: threadId,\n channelId: `${ADAPTER_PREFIX}:${roomId}`,\n metadata: {\n resolved: thread.resolved,\n ...thread.metadata,\n },\n channelName: thread.roomId,\n isDM: false,\n };\n }\n\n async fetchMessage(\n threadId: string,\n messageId: string\n ): Promise<Message<CommentData> | null> {\n try {\n const { roomId, threadId: threadId_liveblocks } =\n this.decodeThreadId(threadId);\n\n const comment = await this.#client.getComment({\n roomId,\n threadId: threadId_liveblocks,\n commentId: messageId,\n });\n if (comment.deletedAt !== undefined) {\n return null;\n }\n return this.#convertLiveblocksCommentDataToChatMessage(comment);\n } catch (error) {\n if (error instanceof LiveblocksError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async listThreads(\n channelId: string,\n options?: ListThreadsOptions\n ): Promise<ListThreadsResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n const threads = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment === undefined) return null;\n\n return {\n id: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n updatedAt: thread.updatedAt,\n numOfComments: nonDeletedComments.length,\n firstComment: firstNonDeletedComment,\n };\n })\n .filter((thread) => thread !== null);\n\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room in chronological order,\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByUpdatedAt(threads, {\n limit,\n startingAfter,\n });\n\n return {\n threads: await Promise.all(\n sliced.data.map(async (thread) => ({\n id: thread.id,\n rootMessage: await this.#convertLiveblocksCommentDataToChatMessage(\n thread.firstComment\n ),\n lastReplyAt: thread.updatedAt,\n replyCount: thread.numOfComments - 1,\n }))\n ),\n nextCursor: sliced.nextCursor,\n };\n }\n\n async fetchChannelInfo(channelId: string): Promise<ChannelInfo> {\n const room = await this.#client.getRoom(getRoomIdFromChannelId(channelId));\n return {\n id: room.id,\n name: room.id,\n isDM: false,\n metadata: {},\n };\n }\n\n async fetchChannelMessages(\n channelId: string,\n options?: FetchOptions\n ): Promise<FetchResult<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const { data } = await this.#client.getThreads({ roomId });\n\n const comments = data\n .map((thread) => {\n const nonDeletedComments = thread.comments\n .filter((comment) => comment.deletedAt === undefined)\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n const firstNonDeletedComment = nonDeletedComments[0];\n if (firstNonDeletedComment !== undefined) {\n return firstNonDeletedComment;\n }\n return null;\n })\n .filter((comment) => comment !== null);\n\n const direction = options?.direction ?? \"backward\";\n const limit = options?.limit;\n const startingAfter = options?.cursor;\n\n // The 'Get threads' API returns all threads in the room (sorted by creation date in ascending order)\n // and each thread contains all comments in the thread (sorted by creation date in ascending order),\n // so we perform in-memory pagination to match Chat SDK's expected behavior.\n const sliced = slicePageByCreatedAt(comments, {\n direction: direction === \"forward\" ? \"ascending\" : \"descending\",\n limit,\n startingAfter,\n });\n\n const messages = await Promise.all(\n sliced.data.map((comment) =>\n this.#convertLiveblocksCommentDataToChatMessage(comment)\n )\n );\n\n return { messages, nextCursor: sliced.nextCursor };\n }\n\n async postChannelMessage(\n channelId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<CommentData>> {\n const roomId = getRoomIdFromChannelId(channelId);\n const thread = await this.#client.createThread({\n roomId,\n data: {\n comment: {\n userId: this.#botUserId,\n body: convertPostableMessageToCommentBody(message),\n },\n },\n });\n const firstComment = thread.comments[0];\n if (firstComment === undefined) {\n throw new Error(`Failed to create thread in room ${channelId}`);\n }\n return {\n id: firstComment.id,\n threadId: this.encodeThreadId({\n roomId,\n threadId: thread.id,\n }),\n raw: firstComment,\n };\n }\n\n // This method isn't used by the Chat SDK, but it's required to implement the Adapter interface,\n // so we will return a less rich message here. We have a separate (asynchronous) method for converting a comment to a message.\n parseMessage(data: CommentData): Message<CommentData> {\n return new Message({\n id: data.id,\n threadId: this.encodeThreadId({\n roomId: data.roomId,\n threadId: data.threadId,\n }),\n raw: data,\n formatted: { type: \"root\", children: [] },\n text: \"\",\n author: {\n userId: data.userId,\n userName: data.userId,\n fullName: data.userId,\n isBot: data.userId === this.#botUserId,\n isMe: data.userId === this.#botUserId,\n },\n metadata: {\n dateSent: data.createdAt,\n edited: !!data.editedAt,\n editedAt: data.editedAt,\n },\n attachments: data.attachments.map((att) =>\n this.#createAttachment(data.roomId, att)\n ),\n });\n }\n\n renderFormatted(content: FormattedContent): string {\n // Liveblocks comments do not support markdown as input, so we convert the content to plain text.\n return toPlainText(content);\n }\n\n channelIdFromThreadId(threadId: string): string {\n const { roomId } = this.decodeThreadId(threadId);\n return `${ADAPTER_PREFIX}:${roomId}`;\n }\n\n /**\n * This method is a no-op as typing indicators are not supported by Liveblocks Comments.\n */\n startTyping(_threadId: string, _status?: string): Promise<void> {\n return Promise.resolve();\n }\n\n #createAttachment(\n roomId: string,\n attachment: CommentData[\"attachments\"][number]\n ): Attachment {\n const client = this.#client;\n return {\n type: getAttachmentType(attachment.mimeType),\n name: attachment.name,\n mimeType: attachment.mimeType,\n size: attachment.size,\n fetchData: async () => {\n const { url } = await client.getAttachment({\n roomId,\n attachmentId: attachment.id,\n });\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch attachment \"${attachment.name}\": ${response.status} ${response.statusText}`\n );\n }\n return Buffer.from(await response.arrayBuffer());\n },\n };\n }\n\n /**\n * Encodes a Liveblocks room ID and thread ID into a single thread ID string.\n *\n * Format: `liveblocks:{roomId}:{threadId}`\n *\n * **Note**: Room IDs may contain colons (':'), which are preserved during encoding/decoding.\n * However, Liveblocks thread IDs must not contain colons as the last colon is used as the delimiter when decoding.\n */\n encodeThreadId(data: { roomId: string; threadId: string }): string {\n return `${ADAPTER_PREFIX}:${data.roomId}:${data.threadId}`;\n }\n\n /**\n * Decodes an encoded thread ID string back into its room ID and thread ID components.\n *\n * @throws {Error} If the thread ID format is invalid\n */\n decodeThreadId(threadId: string): { roomId: string; threadId: string } {\n const parts = threadId.split(\":\");\n if (parts.length < 3 || parts[0] !== ADAPTER_PREFIX) {\n throw new Error(\n `Invalid thread ID: ${threadId}. Expected format: liveblocks:{roomId}:{threadId}`\n );\n }\n return {\n roomId: parts.slice(1, -1).join(\":\"),\n threadId: parts[parts.length - 1]!,\n };\n }\n\n async #convertLiveblocksCommentDataToChatMessage(\n comment: Extract<CommentData, { body: CommentBody }>\n ): Promise<Message<CommentData>> {\n const mentions = getMentionsFromCommentBody(comment.body);\n const userIds = new Set<string>([comment.userId]); // Initialize with the author's user id\n const groupIds = new Set<string>();\n for (const mention of mentions) {\n if (mention.kind === \"user\") {\n userIds.add(mention.id);\n } else if (mention.kind === \"group\") {\n groupIds.add(mention.id);\n }\n }\n\n const [users, groups] = await Promise.all([\n this.#resolveUsers\n ? this.#resolveUsers({ userIds: Array.from(userIds) })\n : undefined,\n this.#resolveGroupsInfo && groupIds.size > 0\n ? this.#resolveGroupsInfo({ groupIds: Array.from(groupIds) })\n : undefined,\n ]);\n\n const resolvedUsers = new Map<string, U[\"info\"]>();\n if (users !== undefined) {\n for (const [index, userId] of Array.from(userIds).entries()) {\n const user = users[index];\n if (user === undefined) continue;\n resolvedUsers.set(userId, user);\n }\n }\n const resolvedGroups = new Map<string, DGI>();\n if (groups !== undefined) {\n for (const [index, groupId] of Array.from(groupIds).entries()) {\n const group = groups[index];\n if (group === undefined) continue;\n resolvedGroups.set(groupId, group);\n }\n }\n\n const links = new Set<string>();\n\n const nodes: Paragraph[] = comment.body.content.map((block) => {\n const children: Array<\n Text | Link | Emphasis | Strong | InlineCode | Delete\n > = [];\n for (const inline of block.children) {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n children.push({\n type: \"text\",\n value: resolvedUsers.get(inline.id)?.name ?? inline.id,\n });\n } else {\n children.push({\n type: \"text\",\n value: resolvedGroups.get(inline.id)?.name ?? inline.id,\n });\n }\n } else if (isCommentBodyLink(inline)) {\n links.add(inline.url);\n children.push({\n type: \"link\",\n children: [{ type: \"text\", value: inline.text ?? \"\" }],\n url: inline.url,\n });\n } else if (isCommentBodyText(inline)) {\n if (inline.code) {\n children.push({\n type: \"inlineCode\",\n value: inline.text,\n });\n } else {\n // Build nested structure for combined styles (bold, italic, strikethrough)\n let node: Text | Emphasis | Strong | Delete = {\n type: \"text\",\n value: inline.text,\n };\n\n if (inline.strikethrough) {\n node = { type: \"delete\", children: [node] };\n }\n if (inline.italic) {\n node = { type: \"emphasis\", children: [node] };\n }\n if (inline.bold) {\n node = { type: \"strong\", children: [node] };\n }\n\n children.push(node);\n }\n }\n }\n return { type: \"paragraph\", children };\n });\n\n const text = comment.body.content.reduce((acc, block) => {\n return (\n acc +\n block.children.reduce((acc, inline) => {\n if (isCommentBodyMention(inline)) {\n if (inline.kind === \"user\") {\n return acc + (resolvedUsers.get(inline.id)?.name ?? inline.id);\n } else {\n return acc + (resolvedGroups.get(inline.id)?.name ?? inline.id);\n }\n } else if (isCommentBodyLink(inline)) {\n if (inline.text) {\n return acc + inline.text;\n } else {\n return acc + inline.url;\n }\n } else if (isCommentBodyText(inline)) {\n return acc + inline.text;\n }\n return acc;\n }, \"\")\n );\n }, \"\");\n\n return new Message({\n id: comment.id,\n threadId: this.encodeThreadId({\n roomId: comment.roomId,\n threadId: comment.threadId,\n }),\n raw: comment,\n formatted: { type: \"root\", children: nodes },\n text,\n isMention: resolvedUsers.has(this.#botUserId),\n links: Array.from(links.values()).map((url) => ({ url })),\n author: {\n userId: comment.userId,\n userName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n fullName: resolvedUsers.get(comment.userId)?.name ?? comment.userId,\n // This assumes that the current bot is the only bot in the thread; if we want\n // to support multiple bots, we need to add a way to determine the bot's user id.\n isBot: comment.userId === this.#botUserId,\n isMe: comment.userId === this.#botUserId,\n },\n metadata: {\n dateSent: comment.createdAt,\n edited: comment.editedAt !== undefined,\n editedAt: comment.editedAt,\n },\n attachments: comment.attachments.map((attachment) =>\n this.#createAttachment(comment.roomId, attachment)\n ),\n });\n }\n}\n\n/**\n * Parses a Chat SDK channel id into the Liveblocks room id for REST API calls.\n *\n * @throws {Error} If `channelId` is missing the `liveblocks:` prefix or has an empty room segment.\n */\nexport function getRoomIdFromChannelId(channelId: string): string {\n const prefix = `${ADAPTER_PREFIX}:`;\n if (!channelId.startsWith(prefix)) {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n const roomId = channelId.slice(prefix.length);\n if (roomId === \"\") {\n throw new Error(\n `Invalid channel ID: \"${channelId}\". Expected format: ${prefix}{roomId}`\n );\n }\n return roomId;\n}\n\nexport function convertPostableMessageToCommentBody(\n message: AdapterPostableMessage\n): CommentBody {\n if (typeof message === \"string\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message)\n );\n } else if (\"raw\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.raw)\n );\n } else if (\"markdown\" in message) {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(message.markdown)\n );\n } else if (\"ast\" in message) {\n return convertChatRootElementToCommentBodyRootElement(message.ast);\n } else if (\"card\" in message) {\n // Liveblocks comments do not support cards and card elements, so we convert the message to markdown and then to a comment body\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(\n message.fallbackText ?? convertCardToMarkdownString(message.card)\n )\n );\n } else if (\"type\" in message && message.type === \"card\") {\n return convertChatRootElementToCommentBodyRootElement(\n parseMarkdown(convertCardToMarkdownString(message))\n );\n } else {\n console.error(`Unexpected message type: ${JSON.stringify(message)}`);\n return {\n version: 1,\n content: [],\n };\n }\n}\n\nfunction convertCardToMarkdownString(card: CardElement): string {\n const parts: string[] = [];\n if (card.title) {\n parts.push(`**${card.title}**`);\n }\n if (card.subtitle) {\n parts.push(card.subtitle);\n }\n for (const child of card.children) {\n parts.push(convertCardChildToMarkdownString(child));\n }\n return parts.join(\"\\n\");\n}\n\nfunction convertCardChildToMarkdownString(child: CardChild): string {\n switch (child.type) {\n case \"text\":\n return child.content;\n case \"fields\":\n return child.children\n .map((field) => `**${field.label}**: ${field.value}`)\n .join(\"\\n\");\n case \"actions\":\n // Actions are interactive-only — exclude from fallback text. See: https://docs.slack.dev/reference/methods/chat.postMessage\n return \"\";\n case \"table\": {\n let markdown = \"|\";\n for (const header of child.headers) {\n markdown += ` ${header} |`;\n }\n markdown += \"\\n|\";\n for (const _ of child.headers) {\n markdown += \"--- |\";\n }\n markdown += \"\\n\";\n for (const row of child.rows) {\n markdown += \"|\";\n for (const cell of row) {\n markdown += ` ${cell} |`;\n }\n markdown += \"\\n\";\n }\n return markdown;\n }\n case \"section\":\n return child.children\n .map((c) => convertCardChildToMarkdownString(c))\n .filter(Boolean)\n .join(\"\\n\");\n case \"link\":\n return `[${child.label}](${child.url})`;\n case \"divider\":\n return \"---\";\n case \"image\":\n return ``;\n default:\n return \"\";\n }\n}\n\nfunction convertChatRootElementToCommentBodyRootElement(\n root: Root\n): CommentBody {\n return {\n version: 1,\n content: root.children.flatMap((child) =>\n convertChatBlockElementToCommentBodyBlockElement(child)\n ),\n };\n}\n\nfunction convertChatBlockElementToCommentBodyBlockElement(\n node: Root[\"children\"][number]\n): CommentBodyParagraph | CommentBodyParagraph[] {\n switch (node.type) {\n case \"paragraph\": {\n const children: CommentBodyInlineElement[] = [];\n for (const child of node.children) {\n children.push(\n convertChatInlineElementToCommentBodyInlineElement(child)\n );\n }\n return {\n type: \"paragraph\",\n children,\n };\n }\n case \"blockquote\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"list\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"listItem\": {\n return node.children.flatMap((child) => {\n return convertChatBlockElementToCommentBodyBlockElement(child);\n });\n }\n case \"heading\": {\n // Render headings as paragraphs as Liveblocks comments do not support headings\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: node.children,\n });\n }\n case \"code\": {\n // Render code blocks as paragraphs as Liveblocks comments do not support code blocks\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"html\": {\n // Render HTML as paragraphs as Liveblocks comments do not support HTML\n return convertChatBlockElementToCommentBodyBlockElement({\n type: \"paragraph\",\n children: [{ type: \"text\", value: node.value }],\n });\n }\n case \"table\": {\n // Convert table to ASCII table string and render as paragraph as Liveblocks comments do not support tables\n return {\n type: \"paragraph\",\n children: [{ text: tableToAscii(node) }],\n };\n }\n case \"link\":\n case \"image\":\n case \"strong\":\n case \"emphasis\":\n case \"inlineCode\":\n case \"delete\":\n case \"text\": {\n return {\n type: \"paragraph\",\n children: [convertChatInlineElementToCommentBodyInlineElement(node)],\n };\n }\n case \"break\":\n case \"thematicBreak\":\n case \"definition\":\n case \"tableCell\":\n case \"tableRow\":\n case \"yaml\":\n case \"footnoteDefinition\":\n case \"footnoteReference\":\n case \"imageReference\":\n case \"linkReference\":\n default: {\n return [];\n }\n }\n}\n\nfunction convertChatInlineElementToCommentBodyInlineElement(\n inline: PhrasingContent\n): CommentBodyInlineElement {\n switch (inline.type) {\n case \"text\":\n return { text: inline.value };\n case \"link\":\n return {\n type: \"link\",\n url: inline.url,\n // Link elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n };\n case \"image\":\n return { text: inline.url };\n case \"emphasis\":\n return {\n // Emphasis elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),\n // so we convert the children to plain text to match the expected format\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n italic: true,\n };\n case \"strong\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n bold: true,\n };\n case \"delete\":\n return {\n text: inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\"),\n strikethrough: true,\n };\n case \"inlineCode\":\n return {\n text: inline.value,\n code: true,\n };\n case \"html\":\n return { text: inline.value };\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default: {\n return { text: \"\" };\n }\n }\n}\n\nfunction convertChatInlineElementToPlainText(inline: PhrasingContent): string {\n switch (inline.type) {\n case \"text\":\n return inline.value;\n case \"link\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"image\":\n return inline.url;\n case \"emphasis\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"strong\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"delete\":\n return inline.children\n .map((child) => {\n return convertChatInlineElementToPlainText(child);\n })\n .join(\"\");\n case \"inlineCode\":\n return inline.value;\n case \"html\":\n return inline.value;\n case \"break\":\n case \"linkReference\":\n case \"imageReference\":\n case \"footnoteReference\":\n default:\n return \"\";\n }\n}\n\n/**\n * Encode a pagination cursor using the format `base64url( [[\"id\", <string>], [\"createdAt\", <number>]] )`\n */\nexport function encodePaginationCursorByCreatedAt(\n id: string,\n createdAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"createdAt\", createdAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByCreatedAt(cursor: string): {\n id: string;\n createdAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"createdAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n createdAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nexport function encodePaginationCursorByUpdatedAt(\n id: string,\n updatedAt: Date\n): string {\n return base64UrlEncode(\n JSON.stringify([\n [\"id\", id],\n [\"updatedAt\", updatedAt.getTime()],\n ])\n );\n}\n\nexport function decodePaginationCursorByUpdatedAt(cursor: string): {\n id: string;\n updatedAt: Date;\n} {\n try {\n const parsed = JSON.parse(base64UrlDecode(cursor));\n if (\n !Array.isArray(parsed) ||\n parsed.length !== 2 ||\n parsed[0]?.[0] !== \"id\" ||\n parsed[1]?.[0] !== \"updatedAt\"\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n return {\n id: parsed[0][1] as string,\n updatedAt: new Date(parsed[1][1] as number),\n };\n } catch {\n throw new Error(`Invalid pagination cursor: ${cursor}`);\n }\n}\n\nfunction base64UrlEncode(str: string): string {\n const bytes = new TextEncoder().encode(str);\n const binary = String.fromCharCode(...bytes);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction base64UrlDecode(str: string): string {\n let s = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (s.length % 4) s += \"=\";\n const binary = atob(s);\n const bytes = Uint8Array.from(binary, (c) => c.codePointAt(0)!);\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Slice a page from an in-memory list sorted by `createdAt` (oldest first).\n *\n * Cursors use the same `[[\"id\", ...], [\"createdAt\", ...]]` format as the\n * Liveblocks REST API so that they will be forward-compatible if the backend\n * adds server-side pagination.\n *\n * The cursor is always built from the boundary item of the current page and\n * means \"start after this item\" in the current traversal direction — matching\n * the `startingAfter` semantics used throughout the backend.\n *\n * When no `limit` is provided, all matching items are returned (preserving the\n * pre-pagination behaviour of returning the full list).\n */\nfunction slicePageByCreatedAt<T extends { id: string; createdAt: Date }>(\n data: T[],\n options: {\n /**\n * The direction to slice the page in.\n * - \"ascending\": Slice the page from the oldest item to the newest item.\n * - \"descending\": Slice the page from the newest item to the oldest item.\n */\n direction: \"ascending\" | \"descending\";\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { direction, limit, startingAfter } = options;\n\n // Sort data by 'createdAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.createdAt.getTime() !== b.createdAt.getTime()) {\n return a.createdAt.getTime() - b.createdAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let startIndex: number;\n let endIndex: number;\n\n if (direction === \"descending\") {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n } else {\n if (startingAfter) {\n const cursor = decodePaginationCursorByCreatedAt(startingAfter);\n // Find the first item strictly after the cursor in sort order.\n startIndex = data.findIndex(\n (c) =>\n c.createdAt.getTime() > cursor.createdAt.getTime() ||\n (c.createdAt.getTime() === cursor.createdAt.getTime() &&\n c.id < cursor.id)\n );\n if (startIndex === -1) {\n return { data: [], nextCursor: undefined };\n }\n } else {\n startIndex = 0;\n }\n endIndex =\n limit !== undefined\n ? Math.min(data.length, startIndex + limit)\n : data.length;\n }\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n let nextCursor: string | undefined;\n if (direction === \"descending\") {\n nextCursor =\n startIndex > 0\n ? encodePaginationCursorByCreatedAt(page[0]!.id, page[0]!.createdAt)\n : undefined;\n } else {\n nextCursor =\n endIndex < data.length\n ? encodePaginationCursorByCreatedAt(\n page[page.length - 1]!.id,\n page[page.length - 1]!.createdAt\n )\n : undefined;\n }\n\n return { data: page, nextCursor };\n}\n\n/**\n * Same as {@link slicePageByCreatedAt} (descending direction only) but sorts and\n * paginates on `updatedAt`. Thread listing does not expose forward pagination.\n */\nfunction slicePageByUpdatedAt<T extends { id: string; updatedAt: Date }>(\n data: T[],\n options: {\n limit?: number;\n startingAfter?: string;\n }\n): { data: T[]; nextCursor: string | undefined } {\n const { limit, startingAfter } = options;\n\n // Sort data by 'updatedAt' (oldest first) and use 'id' as a tie-breaker\n data = data.slice().sort((a, b) => {\n if (a.updatedAt.getTime() !== b.updatedAt.getTime()) {\n return a.updatedAt.getTime() - b.updatedAt.getTime();\n }\n return b.id.localeCompare(a.id);\n });\n\n let endIndex: number;\n if (startingAfter) {\n const cursor = decodePaginationCursorByUpdatedAt(startingAfter);\n // Find the cursor's position in sort order, then take everything before it.\n endIndex = data.findIndex(\n (c) =>\n c.updatedAt.getTime() > cursor.updatedAt.getTime() ||\n (c.updatedAt.getTime() === cursor.updatedAt.getTime() &&\n c.id <= cursor.id)\n );\n if (endIndex === -1) {\n endIndex = data.length;\n }\n } else {\n endIndex = data.length;\n }\n const startIndex = limit !== undefined ? Math.max(0, endIndex - limit) : 0;\n\n const page = data.slice(startIndex, endIndex);\n\n if (page.length === 0) {\n return { data: [], nextCursor: undefined };\n }\n\n const nextCursor =\n startIndex > 0\n ? encodePaginationCursorByUpdatedAt(page[0]!.id, page[0]!.updatedAt)\n : undefined;\n\n return { data: page, nextCursor };\n}\n\nfunction getAttachmentType(mimeType: string): Attachment[\"type\"] {\n if (mimeType.startsWith(\"image/\")) {\n return \"image\";\n } else if (mimeType.startsWith(\"video/\")) {\n return \"video\";\n } else if (mimeType.startsWith(\"audio/\")) {\n return \"audio\";\n }\n return \"file\";\n}\n\nexport interface LiveblocksAdapterConfig<\n U extends BaseUserMeta,\n DGI extends BaseGroupInfo,\n> {\n /**\n * The Liveblocks secret key. Must start with \"sk_\". Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/apikeys\n */\n apiKey: string;\n /**\n * The Liveblocks webhook signing secret. Get it from the Liveblocks dashboard: https://liveblocks.io/dashboard/webhooks\n * @example \"whsec_wPbvQ+u3VtN2e2tRPDKchQ1tBZ3svaHLm\"\n */\n webhookSecret: string;\n /**\n * A function that returns user info from user IDs; used to resolve @user mentions in comment bodies.\n * This function should return an array of user info in the same order as the input user IDs, or `undefined` to skip resolution.\n */\n resolveUsers?: (\n args: ResolveUsersArgs\n ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n /**\n * A function that returns group info from group IDs; used to resolve @group mentions in comment bodies.\n * This function should return an array of group info in the same order as the input group IDs, or `undefined` to skip resolution.\n */\n resolveGroupsInfo?: (\n args: ResolveGroupsInfoArgs\n ) => Awaitable<(DGI | undefined)[] | undefined>;\n /**\n * The user ID used when the bot creates, edits, or reacts to comments.\n * This should match your app’s user identifiers.\n */\n botUserId: string;\n /**\n * The display name for the chat user representing the bot.\n * @default \"liveblocks-bot\"\n */\n botUserName?: string;\n /**\n * A Chat SDK–compatible logger.\n * @default ConsoleLogger at info level, scoped to this adapter\n */\n logger?: Logger;\n}\n\n/**\n * Creates a {@link LiveblocksAdapter} configured for Liveblocks Comments.\n */\nexport function createLiveblocksAdapter<\n U extends BaseUserMeta = BaseUserMeta,\n DGI extends BaseGroupInfo = BaseGroupInfo,\n>(config: LiveblocksAdapterConfig<U, DGI>): LiveblocksAdapter<U, DGI> {\n return new LiveblocksAdapter(config);\n}\n"],"mappings":"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","names":["acc"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liveblocks/chat-sdk-adapter",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Liveblocks adapter for the Chat SDK.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Liveblocks Inc.",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@liveblocks/core": "3.
|
|
39
|
-
"@liveblocks/node": "3.
|
|
38
|
+
"@liveblocks/core": "3.17.0",
|
|
39
|
+
"@liveblocks/node": "3.17.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"chat": ">=4.20.0"
|