@liveblocks/chat-sdk-adapter 0.0.0 → 3.16.0-feeds2

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/dist/index.cjs ADDED
@@ -0,0 +1,972 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/adapter.ts
2
+
3
+
4
+
5
+
6
+
7
+ var _core = require('@liveblocks/core');
8
+
9
+
10
+
11
+
12
+ var _node = require('@liveblocks/node');
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+ var _chat = require('chat');
21
+ var ADAPTER_PREFIX = "liveblocks";
22
+ var LiveblocksAdapter = (_class = class {
23
+ __init() {this.name = "liveblocks"}
24
+
25
+ #client;
26
+ #webhookHandler;
27
+ #resolveUsers;
28
+ #resolveGroupsInfo;
29
+ #logger;
30
+ #botUserId;
31
+ #chat = null;
32
+ constructor(config) {;_class.prototype.__init.call(this);
33
+ this.#client = new (0, _node.Liveblocks)({ secret: config.apiKey });
34
+ this.#webhookHandler = new (0, _node.WebhookHandler)(config.webhookSecret);
35
+ this.#resolveUsers = config.resolveUsers;
36
+ this.#resolveGroupsInfo = config.resolveGroupsInfo;
37
+ this.#botUserId = config.botUserId;
38
+ this.userName = _nullishCoalesce(config.botUserName, () => ( "liveblocks-bot"));
39
+ this.#logger = _nullishCoalesce(config.logger, () => ( new (0, _chat.ConsoleLogger)("info").child(ADAPTER_PREFIX)));
40
+ }
41
+ async initialize(chat) {
42
+ this.#chat = chat;
43
+ }
44
+ async handleWebhook(request, options) {
45
+ let event;
46
+ try {
47
+ event = this.#webhookHandler.verifyRequest({
48
+ headers: request.headers,
49
+ rawBody: await request.text()
50
+ });
51
+ } catch (error) {
52
+ this.#logger.error("Failed to verify webhook request", { error });
53
+ return new Response("Invalid webhook request", { status: 401 });
54
+ }
55
+ if (event.type === "commentCreated") {
56
+ const threadId = this.encodeThreadId({
57
+ roomId: event.data.roomId,
58
+ threadId: event.data.threadId
59
+ });
60
+ const comment = await this.#client.getComment({
61
+ roomId: event.data.roomId,
62
+ threadId: event.data.threadId,
63
+ commentId: event.data.commentId
64
+ });
65
+ if (comment.deletedAt !== void 0) {
66
+ return new Response(null, { status: 200 });
67
+ }
68
+ _optionalChain([this, 'access', _2 => _2.#chat, 'optionalAccess', _3 => _3.processMessage, 'call', _4 => _4(
69
+ this,
70
+ threadId,
71
+ () => this.#convertLiveblocksCommentDataToChatMessage(comment),
72
+ options
73
+ )]);
74
+ } else if (event.type === "commentReactionAdded" || event.type === "commentReactionRemoved") {
75
+ const threadId = this.encodeThreadId({
76
+ roomId: event.data.roomId,
77
+ threadId: event.data.threadId
78
+ });
79
+ const userId = event.type === "commentReactionAdded" ? event.data.addedBy : event.data.removedBy;
80
+ const resolvedUsers = await _optionalChain([this, 'access', _5 => _5.#resolveUsers, 'optionalCall', _6 => _6({ userIds: [userId] })]);
81
+ const user = _optionalChain([resolvedUsers, 'optionalAccess', _7 => _7[0]]);
82
+ _optionalChain([this, 'access', _8 => _8.#chat, 'optionalAccess', _9 => _9.processReaction, 'call', _10 => _10(
83
+ {
84
+ added: event.type === "commentReactionAdded",
85
+ emoji: _chat.defaultEmojiResolver.fromGChat(event.data.emoji),
86
+ rawEmoji: event.data.emoji,
87
+ messageId: event.data.commentId,
88
+ threadId,
89
+ user: {
90
+ userId,
91
+ userName: _nullishCoalesce(_optionalChain([user, 'optionalAccess', _11 => _11.name]), () => ( userId)),
92
+ fullName: _nullishCoalesce(_optionalChain([user, 'optionalAccess', _12 => _12.name]), () => ( userId)),
93
+ // This assumes that the current bot is the only bot in the thread; if we want
94
+ // to support multiple bots, we need to add a way to determine the bot's user id.
95
+ isBot: userId === this.#botUserId,
96
+ isMe: userId === this.#botUserId
97
+ },
98
+ raw: event.data,
99
+ adapter: this
100
+ },
101
+ options
102
+ )]);
103
+ }
104
+ return new Response(null, { status: 200 });
105
+ }
106
+ async postMessage(threadId, message) {
107
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
108
+ const comment = await this.#client.createComment({
109
+ roomId,
110
+ threadId: threadId_liveblocks,
111
+ data: {
112
+ userId: this.#botUserId,
113
+ body: convertPostableMessageToCommentBody(message)
114
+ }
115
+ });
116
+ return { id: comment.id, threadId, raw: comment };
117
+ }
118
+ async editMessage(threadId, messageId, message) {
119
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
120
+ const comment = await this.#client.editComment({
121
+ roomId,
122
+ threadId: threadId_liveblocks,
123
+ commentId: messageId,
124
+ data: {
125
+ body: convertPostableMessageToCommentBody(message)
126
+ }
127
+ });
128
+ return { id: comment.id, threadId, raw: comment };
129
+ }
130
+ async deleteMessage(threadId, messageId) {
131
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
132
+ await this.#client.deleteComment({
133
+ roomId,
134
+ threadId: threadId_liveblocks,
135
+ commentId: messageId
136
+ });
137
+ }
138
+ async addReaction(threadId, messageId, emoji) {
139
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
140
+ await this.#client.addCommentReaction({
141
+ roomId,
142
+ threadId: threadId_liveblocks,
143
+ commentId: messageId,
144
+ data: {
145
+ // Liveblocks expects unicode emoji; 'toGChat' converts normalized names (e.g. 'thumbs_up') to unicode ('👍').
146
+ // Unknown normalized names (e.g. 'custom_emoji') will fail Liveblocks validation since they are not valid unicode emoji.
147
+ emoji: _chat.defaultEmojiResolver.toGChat(emoji),
148
+ userId: this.#botUserId
149
+ }
150
+ });
151
+ }
152
+ async removeReaction(threadId, messageId, emoji) {
153
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
154
+ await this.#client.removeCommentReaction({
155
+ roomId,
156
+ threadId: threadId_liveblocks,
157
+ commentId: messageId,
158
+ data: {
159
+ emoji: _chat.defaultEmojiResolver.toGChat(emoji),
160
+ userId: this.#botUserId
161
+ }
162
+ });
163
+ }
164
+ async fetchMessages(threadId, options) {
165
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
166
+ const thread = await this.#client.getThread({
167
+ roomId,
168
+ threadId: threadId_liveblocks
169
+ });
170
+ const comments = thread.comments.filter(
171
+ (comment) => comment.deletedAt === void 0
172
+ );
173
+ const direction = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _13 => _13.direction]), () => ( "backward"));
174
+ const limit = _optionalChain([options, 'optionalAccess', _14 => _14.limit]);
175
+ const startingAfter = _optionalChain([options, 'optionalAccess', _15 => _15.cursor]);
176
+ const sliced = slicePageByCreatedAt(comments, {
177
+ direction: direction === "forward" ? "ascending" : "descending",
178
+ limit,
179
+ startingAfter
180
+ });
181
+ const messages = await Promise.all(
182
+ sliced.data.map(
183
+ (comment) => this.#convertLiveblocksCommentDataToChatMessage(comment)
184
+ )
185
+ );
186
+ return { messages, nextCursor: sliced.nextCursor };
187
+ }
188
+ async fetchThread(threadId) {
189
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
190
+ const thread = await this.#client.getThread({
191
+ roomId,
192
+ threadId: threadId_liveblocks
193
+ });
194
+ return {
195
+ id: threadId,
196
+ channelId: `${ADAPTER_PREFIX}:${roomId}`,
197
+ metadata: {
198
+ resolved: thread.resolved,
199
+ ...thread.metadata
200
+ },
201
+ channelName: thread.roomId,
202
+ isDM: false
203
+ };
204
+ }
205
+ async fetchMessage(threadId, messageId) {
206
+ try {
207
+ const { roomId, threadId: threadId_liveblocks } = this.decodeThreadId(threadId);
208
+ const comment = await this.#client.getComment({
209
+ roomId,
210
+ threadId: threadId_liveblocks,
211
+ commentId: messageId
212
+ });
213
+ if (comment.deletedAt !== void 0) {
214
+ return null;
215
+ }
216
+ return this.#convertLiveblocksCommentDataToChatMessage(comment);
217
+ } catch (error) {
218
+ if (error instanceof _node.LiveblocksError && error.status === 404) {
219
+ return null;
220
+ }
221
+ throw error;
222
+ }
223
+ }
224
+ async listThreads(channelId, options) {
225
+ const roomId = getRoomIdFromChannelId(channelId);
226
+ const { data } = await this.#client.getThreads({ roomId });
227
+ const threads = data.map((thread) => {
228
+ const nonDeletedComments = thread.comments.filter((comment) => comment.deletedAt === void 0).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
229
+ const firstNonDeletedComment = nonDeletedComments[0];
230
+ if (firstNonDeletedComment === void 0) return null;
231
+ return {
232
+ id: this.encodeThreadId({
233
+ roomId,
234
+ threadId: thread.id
235
+ }),
236
+ updatedAt: thread.updatedAt,
237
+ numOfComments: nonDeletedComments.length,
238
+ firstComment: firstNonDeletedComment
239
+ };
240
+ }).filter((thread) => thread !== null);
241
+ const limit = _optionalChain([options, 'optionalAccess', _16 => _16.limit]);
242
+ const startingAfter = _optionalChain([options, 'optionalAccess', _17 => _17.cursor]);
243
+ const sliced = slicePageByUpdatedAt(threads, {
244
+ limit,
245
+ startingAfter
246
+ });
247
+ return {
248
+ threads: await Promise.all(
249
+ sliced.data.map(async (thread) => ({
250
+ id: thread.id,
251
+ rootMessage: await this.#convertLiveblocksCommentDataToChatMessage(
252
+ thread.firstComment
253
+ ),
254
+ lastReplyAt: thread.updatedAt,
255
+ replyCount: thread.numOfComments - 1
256
+ }))
257
+ ),
258
+ nextCursor: sliced.nextCursor
259
+ };
260
+ }
261
+ async fetchChannelInfo(channelId) {
262
+ const room = await this.#client.getRoom(getRoomIdFromChannelId(channelId));
263
+ return {
264
+ id: room.id,
265
+ name: room.id,
266
+ isDM: false,
267
+ metadata: {}
268
+ };
269
+ }
270
+ async fetchChannelMessages(channelId, options) {
271
+ const roomId = getRoomIdFromChannelId(channelId);
272
+ const { data } = await this.#client.getThreads({ roomId });
273
+ const comments = data.map((thread) => {
274
+ const nonDeletedComments = thread.comments.filter((comment) => comment.deletedAt === void 0).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
275
+ const firstNonDeletedComment = nonDeletedComments[0];
276
+ if (firstNonDeletedComment !== void 0) {
277
+ return firstNonDeletedComment;
278
+ }
279
+ return null;
280
+ }).filter((comment) => comment !== null);
281
+ const direction = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _18 => _18.direction]), () => ( "backward"));
282
+ const limit = _optionalChain([options, 'optionalAccess', _19 => _19.limit]);
283
+ const startingAfter = _optionalChain([options, 'optionalAccess', _20 => _20.cursor]);
284
+ const sliced = slicePageByCreatedAt(comments, {
285
+ direction: direction === "forward" ? "ascending" : "descending",
286
+ limit,
287
+ startingAfter
288
+ });
289
+ const messages = await Promise.all(
290
+ sliced.data.map(
291
+ (comment) => this.#convertLiveblocksCommentDataToChatMessage(comment)
292
+ )
293
+ );
294
+ return { messages, nextCursor: sliced.nextCursor };
295
+ }
296
+ async postChannelMessage(channelId, message) {
297
+ const roomId = getRoomIdFromChannelId(channelId);
298
+ const thread = await this.#client.createThread({
299
+ roomId,
300
+ data: {
301
+ comment: {
302
+ userId: this.#botUserId,
303
+ body: convertPostableMessageToCommentBody(message)
304
+ }
305
+ }
306
+ });
307
+ const firstComment = thread.comments[0];
308
+ if (firstComment === void 0) {
309
+ throw new Error(`Failed to create thread in room ${channelId}`);
310
+ }
311
+ return {
312
+ id: firstComment.id,
313
+ threadId: this.encodeThreadId({
314
+ roomId,
315
+ threadId: thread.id
316
+ }),
317
+ raw: firstComment
318
+ };
319
+ }
320
+ // This method isn't used by the Chat SDK, but it's required to implement the Adapter interface,
321
+ // so we will return a less rich message here. We have a separate (asynchronous) method for converting a comment to a message.
322
+ parseMessage(data) {
323
+ return new (0, _chat.Message)({
324
+ id: data.id,
325
+ threadId: this.encodeThreadId({
326
+ roomId: data.roomId,
327
+ threadId: data.threadId
328
+ }),
329
+ raw: data,
330
+ formatted: { type: "root", children: [] },
331
+ text: "",
332
+ author: {
333
+ userId: data.userId,
334
+ userName: data.userId,
335
+ fullName: data.userId,
336
+ isBot: data.userId === this.#botUserId,
337
+ isMe: data.userId === this.#botUserId
338
+ },
339
+ metadata: {
340
+ dateSent: data.createdAt,
341
+ edited: !!data.editedAt,
342
+ editedAt: data.editedAt
343
+ },
344
+ attachments: data.attachments.map(
345
+ (att) => this.#createAttachment(data.roomId, att)
346
+ )
347
+ });
348
+ }
349
+ renderFormatted(content) {
350
+ return _chat.toPlainText.call(void 0, content);
351
+ }
352
+ channelIdFromThreadId(threadId) {
353
+ const { roomId } = this.decodeThreadId(threadId);
354
+ return `${ADAPTER_PREFIX}:${roomId}`;
355
+ }
356
+ /**
357
+ * This method is a no-op as typing indicators are not supported by Liveblocks Comments.
358
+ */
359
+ startTyping(_threadId, _status) {
360
+ return Promise.resolve();
361
+ }
362
+ #createAttachment(roomId, attachment) {
363
+ const client = this.#client;
364
+ return {
365
+ type: getAttachmentType(attachment.mimeType),
366
+ name: attachment.name,
367
+ mimeType: attachment.mimeType,
368
+ size: attachment.size,
369
+ fetchData: async () => {
370
+ const { url } = await client.getAttachment({
371
+ roomId,
372
+ attachmentId: attachment.id
373
+ });
374
+ const response = await fetch(url);
375
+ if (!response.ok) {
376
+ throw new Error(
377
+ `Failed to fetch attachment "${attachment.name}": ${response.status} ${response.statusText}`
378
+ );
379
+ }
380
+ return Buffer.from(await response.arrayBuffer());
381
+ }
382
+ };
383
+ }
384
+ /**
385
+ * Encodes a Liveblocks room ID and thread ID into a single thread ID string.
386
+ *
387
+ * Format: `liveblocks:{roomId}:{threadId}`
388
+ *
389
+ * **Note**: Room IDs may contain colons (':'), which are preserved during encoding/decoding.
390
+ * However, Liveblocks thread IDs must not contain colons as the last colon is used as the delimiter when decoding.
391
+ */
392
+ encodeThreadId(data) {
393
+ return `${ADAPTER_PREFIX}:${data.roomId}:${data.threadId}`;
394
+ }
395
+ /**
396
+ * Decodes an encoded thread ID string back into its room ID and thread ID components.
397
+ *
398
+ * @throws {Error} If the thread ID format is invalid
399
+ */
400
+ decodeThreadId(threadId) {
401
+ const parts = threadId.split(":");
402
+ if (parts.length < 3 || parts[0] !== ADAPTER_PREFIX) {
403
+ throw new Error(
404
+ `Invalid thread ID: ${threadId}. Expected format: liveblocks:{roomId}:{threadId}`
405
+ );
406
+ }
407
+ return {
408
+ roomId: parts.slice(1, -1).join(":"),
409
+ threadId: parts[parts.length - 1]
410
+ };
411
+ }
412
+ async #convertLiveblocksCommentDataToChatMessage(comment) {
413
+ const mentions = _core.getMentionsFromCommentBody.call(void 0, comment.body);
414
+ const userIds = /* @__PURE__ */ new Set([comment.userId]);
415
+ const groupIds = /* @__PURE__ */ new Set();
416
+ for (const mention of mentions) {
417
+ if (mention.kind === "user") {
418
+ userIds.add(mention.id);
419
+ } else if (mention.kind === "group") {
420
+ groupIds.add(mention.id);
421
+ }
422
+ }
423
+ const [users, groups] = await Promise.all([
424
+ this.#resolveUsers ? this.#resolveUsers({ userIds: Array.from(userIds) }) : void 0,
425
+ this.#resolveGroupsInfo && groupIds.size > 0 ? this.#resolveGroupsInfo({ groupIds: Array.from(groupIds) }) : void 0
426
+ ]);
427
+ const resolvedUsers = /* @__PURE__ */ new Map();
428
+ if (users !== void 0) {
429
+ for (const [index, userId] of Array.from(userIds).entries()) {
430
+ const user = users[index];
431
+ if (user === void 0) continue;
432
+ resolvedUsers.set(userId, user);
433
+ }
434
+ }
435
+ const resolvedGroups = /* @__PURE__ */ new Map();
436
+ if (groups !== void 0) {
437
+ for (const [index, groupId] of Array.from(groupIds).entries()) {
438
+ const group = groups[index];
439
+ if (group === void 0) continue;
440
+ resolvedGroups.set(groupId, group);
441
+ }
442
+ }
443
+ const links = /* @__PURE__ */ new Set();
444
+ const nodes = comment.body.content.map((block) => {
445
+ const children = [];
446
+ for (const inline of block.children) {
447
+ if (_core.isCommentBodyMention.call(void 0, inline)) {
448
+ if (inline.kind === "user") {
449
+ children.push({
450
+ type: "text",
451
+ value: _nullishCoalesce(_optionalChain([resolvedUsers, 'access', _21 => _21.get, 'call', _22 => _22(inline.id), 'optionalAccess', _23 => _23.name]), () => ( inline.id))
452
+ });
453
+ } else {
454
+ children.push({
455
+ type: "text",
456
+ value: _nullishCoalesce(_optionalChain([resolvedGroups, 'access', _24 => _24.get, 'call', _25 => _25(inline.id), 'optionalAccess', _26 => _26.name]), () => ( inline.id))
457
+ });
458
+ }
459
+ } else if (_core.isCommentBodyLink.call(void 0, inline)) {
460
+ links.add(inline.url);
461
+ children.push({
462
+ type: "link",
463
+ children: [{ type: "text", value: _nullishCoalesce(inline.text, () => ( "")) }],
464
+ url: inline.url
465
+ });
466
+ } else if (_core.isCommentBodyText.call(void 0, inline)) {
467
+ if (inline.code) {
468
+ children.push({
469
+ type: "inlineCode",
470
+ value: inline.text
471
+ });
472
+ } else {
473
+ let node = {
474
+ type: "text",
475
+ value: inline.text
476
+ };
477
+ if (inline.strikethrough) {
478
+ node = { type: "delete", children: [node] };
479
+ }
480
+ if (inline.italic) {
481
+ node = { type: "emphasis", children: [node] };
482
+ }
483
+ if (inline.bold) {
484
+ node = { type: "strong", children: [node] };
485
+ }
486
+ children.push(node);
487
+ }
488
+ }
489
+ }
490
+ return { type: "paragraph", children };
491
+ });
492
+ const text = comment.body.content.reduce((acc, block) => {
493
+ return acc + block.children.reduce((acc2, inline) => {
494
+ if (_core.isCommentBodyMention.call(void 0, inline)) {
495
+ if (inline.kind === "user") {
496
+ return acc2 + (_nullishCoalesce(_optionalChain([resolvedUsers, 'access', _27 => _27.get, 'call', _28 => _28(inline.id), 'optionalAccess', _29 => _29.name]), () => ( inline.id)));
497
+ } else {
498
+ return acc2 + (_nullishCoalesce(_optionalChain([resolvedGroups, 'access', _30 => _30.get, 'call', _31 => _31(inline.id), 'optionalAccess', _32 => _32.name]), () => ( inline.id)));
499
+ }
500
+ } else if (_core.isCommentBodyLink.call(void 0, inline)) {
501
+ if (inline.text) {
502
+ return acc2 + inline.text;
503
+ } else {
504
+ return acc2 + inline.url;
505
+ }
506
+ } else if (_core.isCommentBodyText.call(void 0, inline)) {
507
+ return acc2 + inline.text;
508
+ }
509
+ return acc2;
510
+ }, "");
511
+ }, "");
512
+ return new (0, _chat.Message)({
513
+ id: comment.id,
514
+ threadId: this.encodeThreadId({
515
+ roomId: comment.roomId,
516
+ threadId: comment.threadId
517
+ }),
518
+ raw: comment,
519
+ formatted: { type: "root", children: nodes },
520
+ text,
521
+ isMention: resolvedUsers.has(this.#botUserId),
522
+ links: Array.from(links.values()).map((url) => ({ url })),
523
+ author: {
524
+ userId: comment.userId,
525
+ userName: _nullishCoalesce(_optionalChain([resolvedUsers, 'access', _33 => _33.get, 'call', _34 => _34(comment.userId), 'optionalAccess', _35 => _35.name]), () => ( comment.userId)),
526
+ fullName: _nullishCoalesce(_optionalChain([resolvedUsers, 'access', _36 => _36.get, 'call', _37 => _37(comment.userId), 'optionalAccess', _38 => _38.name]), () => ( comment.userId)),
527
+ // This assumes that the current bot is the only bot in the thread; if we want
528
+ // to support multiple bots, we need to add a way to determine the bot's user id.
529
+ isBot: comment.userId === this.#botUserId,
530
+ isMe: comment.userId === this.#botUserId
531
+ },
532
+ metadata: {
533
+ dateSent: comment.createdAt,
534
+ edited: comment.editedAt !== void 0,
535
+ editedAt: comment.editedAt
536
+ },
537
+ attachments: comment.attachments.map(
538
+ (attachment) => this.#createAttachment(comment.roomId, attachment)
539
+ )
540
+ });
541
+ }
542
+ }, _class);
543
+ function getRoomIdFromChannelId(channelId) {
544
+ const prefix = `${ADAPTER_PREFIX}:`;
545
+ if (!channelId.startsWith(prefix)) {
546
+ throw new Error(
547
+ `Invalid channel ID: "${channelId}". Expected format: ${prefix}{roomId}`
548
+ );
549
+ }
550
+ const roomId = channelId.slice(prefix.length);
551
+ if (roomId === "") {
552
+ throw new Error(
553
+ `Invalid channel ID: "${channelId}". Expected format: ${prefix}{roomId}`
554
+ );
555
+ }
556
+ return roomId;
557
+ }
558
+ function convertPostableMessageToCommentBody(message) {
559
+ if (typeof message === "string") {
560
+ return convertChatRootElementToCommentBodyRootElement(
561
+ _chat.parseMarkdown.call(void 0, message)
562
+ );
563
+ } else if ("raw" in message) {
564
+ return convertChatRootElementToCommentBodyRootElement(
565
+ _chat.parseMarkdown.call(void 0, message.raw)
566
+ );
567
+ } else if ("markdown" in message) {
568
+ return convertChatRootElementToCommentBodyRootElement(
569
+ _chat.parseMarkdown.call(void 0, message.markdown)
570
+ );
571
+ } else if ("ast" in message) {
572
+ return convertChatRootElementToCommentBodyRootElement(message.ast);
573
+ } else if ("card" in message) {
574
+ return convertChatRootElementToCommentBodyRootElement(
575
+ _chat.parseMarkdown.call(void 0,
576
+ _nullishCoalesce(message.fallbackText, () => ( convertCardToMarkdownString(message.card)))
577
+ )
578
+ );
579
+ } else if ("type" in message && message.type === "card") {
580
+ return convertChatRootElementToCommentBodyRootElement(
581
+ _chat.parseMarkdown.call(void 0, convertCardToMarkdownString(message))
582
+ );
583
+ } else {
584
+ console.error(`Unexpected message type: ${JSON.stringify(message)}`);
585
+ return {
586
+ version: 1,
587
+ content: []
588
+ };
589
+ }
590
+ }
591
+ function convertCardToMarkdownString(card) {
592
+ const parts = [];
593
+ if (card.title) {
594
+ parts.push(`**${card.title}**`);
595
+ }
596
+ if (card.subtitle) {
597
+ parts.push(card.subtitle);
598
+ }
599
+ for (const child of card.children) {
600
+ parts.push(convertCardChildToMarkdownString(child));
601
+ }
602
+ return parts.join("\n");
603
+ }
604
+ function convertCardChildToMarkdownString(child) {
605
+ switch (child.type) {
606
+ case "text":
607
+ return child.content;
608
+ case "fields":
609
+ return child.children.map((field) => `**${field.label}**: ${field.value}`).join("\n");
610
+ case "actions":
611
+ return "";
612
+ case "table": {
613
+ let markdown = "|";
614
+ for (const header of child.headers) {
615
+ markdown += ` ${header} |`;
616
+ }
617
+ markdown += "\n|";
618
+ for (const _ of child.headers) {
619
+ markdown += "--- |";
620
+ }
621
+ markdown += "\n";
622
+ for (const row of child.rows) {
623
+ markdown += "|";
624
+ for (const cell of row) {
625
+ markdown += ` ${cell} |`;
626
+ }
627
+ markdown += "\n";
628
+ }
629
+ return markdown;
630
+ }
631
+ case "section":
632
+ return child.children.map((c) => convertCardChildToMarkdownString(c)).filter(Boolean).join("\n");
633
+ case "link":
634
+ return `[${child.label}](${child.url})`;
635
+ case "divider":
636
+ return "---";
637
+ case "image":
638
+ return `![${_nullishCoalesce(child.alt, () => ( ""))}](${child.url})`;
639
+ default:
640
+ return "";
641
+ }
642
+ }
643
+ function convertChatRootElementToCommentBodyRootElement(root) {
644
+ return {
645
+ version: 1,
646
+ content: root.children.flatMap(
647
+ (child) => convertChatBlockElementToCommentBodyBlockElement(child)
648
+ )
649
+ };
650
+ }
651
+ function convertChatBlockElementToCommentBodyBlockElement(node) {
652
+ switch (node.type) {
653
+ case "paragraph": {
654
+ const children = [];
655
+ for (const child of node.children) {
656
+ children.push(
657
+ convertChatInlineElementToCommentBodyInlineElement(child)
658
+ );
659
+ }
660
+ return {
661
+ type: "paragraph",
662
+ children
663
+ };
664
+ }
665
+ case "blockquote": {
666
+ return node.children.flatMap((child) => {
667
+ return convertChatBlockElementToCommentBodyBlockElement(child);
668
+ });
669
+ }
670
+ case "list": {
671
+ return node.children.flatMap((child) => {
672
+ return convertChatBlockElementToCommentBodyBlockElement(child);
673
+ });
674
+ }
675
+ case "listItem": {
676
+ return node.children.flatMap((child) => {
677
+ return convertChatBlockElementToCommentBodyBlockElement(child);
678
+ });
679
+ }
680
+ case "heading": {
681
+ return convertChatBlockElementToCommentBodyBlockElement({
682
+ type: "paragraph",
683
+ children: node.children
684
+ });
685
+ }
686
+ case "code": {
687
+ return convertChatBlockElementToCommentBodyBlockElement({
688
+ type: "paragraph",
689
+ children: [{ type: "text", value: node.value }]
690
+ });
691
+ }
692
+ case "html": {
693
+ return convertChatBlockElementToCommentBodyBlockElement({
694
+ type: "paragraph",
695
+ children: [{ type: "text", value: node.value }]
696
+ });
697
+ }
698
+ case "table": {
699
+ return {
700
+ type: "paragraph",
701
+ children: [{ text: _chat.tableToAscii.call(void 0, node) }]
702
+ };
703
+ }
704
+ case "link":
705
+ case "image":
706
+ case "strong":
707
+ case "emphasis":
708
+ case "inlineCode":
709
+ case "delete":
710
+ case "text": {
711
+ return {
712
+ type: "paragraph",
713
+ children: [convertChatInlineElementToCommentBodyInlineElement(node)]
714
+ };
715
+ }
716
+ case "break":
717
+ case "thematicBreak":
718
+ case "definition":
719
+ case "tableCell":
720
+ case "tableRow":
721
+ case "yaml":
722
+ case "footnoteDefinition":
723
+ case "footnoteReference":
724
+ case "imageReference":
725
+ case "linkReference":
726
+ default: {
727
+ return [];
728
+ }
729
+ }
730
+ }
731
+ function convertChatInlineElementToCommentBodyInlineElement(inline) {
732
+ switch (inline.type) {
733
+ case "text":
734
+ return { text: inline.value };
735
+ case "link":
736
+ return {
737
+ type: "link",
738
+ url: inline.url,
739
+ // Link elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),
740
+ // so we convert the children to plain text to match the expected format
741
+ text: inline.children.map((child) => {
742
+ return convertChatInlineElementToPlainText(child);
743
+ }).join("")
744
+ };
745
+ case "image":
746
+ return { text: inline.url };
747
+ case "emphasis":
748
+ return {
749
+ // Emphasis elements in Liveblocks comments are considered leaf nodes (i.e. they do not have inline elements as children),
750
+ // so we convert the children to plain text to match the expected format
751
+ text: inline.children.map((child) => {
752
+ return convertChatInlineElementToPlainText(child);
753
+ }).join(""),
754
+ italic: true
755
+ };
756
+ case "strong":
757
+ return {
758
+ text: inline.children.map((child) => {
759
+ return convertChatInlineElementToPlainText(child);
760
+ }).join(""),
761
+ bold: true
762
+ };
763
+ case "delete":
764
+ return {
765
+ text: inline.children.map((child) => {
766
+ return convertChatInlineElementToPlainText(child);
767
+ }).join(""),
768
+ strikethrough: true
769
+ };
770
+ case "inlineCode":
771
+ return {
772
+ text: inline.value,
773
+ code: true
774
+ };
775
+ case "html":
776
+ return { text: inline.value };
777
+ case "break":
778
+ case "linkReference":
779
+ case "imageReference":
780
+ case "footnoteReference":
781
+ default: {
782
+ return { text: "" };
783
+ }
784
+ }
785
+ }
786
+ function convertChatInlineElementToPlainText(inline) {
787
+ switch (inline.type) {
788
+ case "text":
789
+ return inline.value;
790
+ case "link":
791
+ return inline.children.map((child) => {
792
+ return convertChatInlineElementToPlainText(child);
793
+ }).join("");
794
+ case "image":
795
+ return inline.url;
796
+ case "emphasis":
797
+ return inline.children.map((child) => {
798
+ return convertChatInlineElementToPlainText(child);
799
+ }).join("");
800
+ case "strong":
801
+ return inline.children.map((child) => {
802
+ return convertChatInlineElementToPlainText(child);
803
+ }).join("");
804
+ case "delete":
805
+ return inline.children.map((child) => {
806
+ return convertChatInlineElementToPlainText(child);
807
+ }).join("");
808
+ case "inlineCode":
809
+ return inline.value;
810
+ case "html":
811
+ return inline.value;
812
+ case "break":
813
+ case "linkReference":
814
+ case "imageReference":
815
+ case "footnoteReference":
816
+ default:
817
+ return "";
818
+ }
819
+ }
820
+ function encodePaginationCursorByCreatedAt(id, createdAt) {
821
+ return base64UrlEncode(
822
+ JSON.stringify([
823
+ ["id", id],
824
+ ["createdAt", createdAt.getTime()]
825
+ ])
826
+ );
827
+ }
828
+ function decodePaginationCursorByCreatedAt(cursor) {
829
+ try {
830
+ const parsed = JSON.parse(base64UrlDecode(cursor));
831
+ if (!Array.isArray(parsed) || parsed.length !== 2 || _optionalChain([parsed, 'access', _39 => _39[0], 'optionalAccess', _40 => _40[0]]) !== "id" || _optionalChain([parsed, 'access', _41 => _41[1], 'optionalAccess', _42 => _42[0]]) !== "createdAt") {
832
+ throw new Error("Invalid cursor structure");
833
+ }
834
+ return {
835
+ id: parsed[0][1],
836
+ createdAt: new Date(parsed[1][1])
837
+ };
838
+ } catch (e) {
839
+ throw new Error(`Invalid pagination cursor: ${cursor}`);
840
+ }
841
+ }
842
+ function encodePaginationCursorByUpdatedAt(id, updatedAt) {
843
+ return base64UrlEncode(
844
+ JSON.stringify([
845
+ ["id", id],
846
+ ["updatedAt", updatedAt.getTime()]
847
+ ])
848
+ );
849
+ }
850
+ function decodePaginationCursorByUpdatedAt(cursor) {
851
+ try {
852
+ const parsed = JSON.parse(base64UrlDecode(cursor));
853
+ if (!Array.isArray(parsed) || parsed.length !== 2 || _optionalChain([parsed, 'access', _43 => _43[0], 'optionalAccess', _44 => _44[0]]) !== "id" || _optionalChain([parsed, 'access', _45 => _45[1], 'optionalAccess', _46 => _46[0]]) !== "updatedAt") {
854
+ throw new Error("Invalid cursor structure");
855
+ }
856
+ return {
857
+ id: parsed[0][1],
858
+ updatedAt: new Date(parsed[1][1])
859
+ };
860
+ } catch (e2) {
861
+ throw new Error(`Invalid pagination cursor: ${cursor}`);
862
+ }
863
+ }
864
+ function base64UrlEncode(str) {
865
+ const bytes = new TextEncoder().encode(str);
866
+ const binary = String.fromCharCode(...bytes);
867
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
868
+ }
869
+ function base64UrlDecode(str) {
870
+ let s = str.replace(/-/g, "+").replace(/_/g, "/");
871
+ while (s.length % 4) s += "=";
872
+ const binary = atob(s);
873
+ const bytes = Uint8Array.from(binary, (c) => c.codePointAt(0));
874
+ return new TextDecoder().decode(bytes);
875
+ }
876
+ function slicePageByCreatedAt(data, options) {
877
+ const { direction, limit, startingAfter } = options;
878
+ data = data.slice().sort((a, b) => {
879
+ if (a.createdAt.getTime() !== b.createdAt.getTime()) {
880
+ return a.createdAt.getTime() - b.createdAt.getTime();
881
+ }
882
+ return b.id.localeCompare(a.id);
883
+ });
884
+ let startIndex;
885
+ let endIndex;
886
+ if (direction === "descending") {
887
+ if (startingAfter) {
888
+ const cursor = decodePaginationCursorByCreatedAt(startingAfter);
889
+ endIndex = data.findIndex(
890
+ (c) => c.createdAt.getTime() > cursor.createdAt.getTime() || c.createdAt.getTime() === cursor.createdAt.getTime() && c.id <= cursor.id
891
+ );
892
+ if (endIndex === -1) {
893
+ endIndex = data.length;
894
+ }
895
+ } else {
896
+ endIndex = data.length;
897
+ }
898
+ startIndex = limit !== void 0 ? Math.max(0, endIndex - limit) : 0;
899
+ } else {
900
+ if (startingAfter) {
901
+ const cursor = decodePaginationCursorByCreatedAt(startingAfter);
902
+ startIndex = data.findIndex(
903
+ (c) => c.createdAt.getTime() > cursor.createdAt.getTime() || c.createdAt.getTime() === cursor.createdAt.getTime() && c.id < cursor.id
904
+ );
905
+ if (startIndex === -1) {
906
+ return { data: [], nextCursor: void 0 };
907
+ }
908
+ } else {
909
+ startIndex = 0;
910
+ }
911
+ endIndex = limit !== void 0 ? Math.min(data.length, startIndex + limit) : data.length;
912
+ }
913
+ const page = data.slice(startIndex, endIndex);
914
+ if (page.length === 0) {
915
+ return { data: [], nextCursor: void 0 };
916
+ }
917
+ let nextCursor;
918
+ if (direction === "descending") {
919
+ nextCursor = startIndex > 0 ? encodePaginationCursorByCreatedAt(page[0].id, page[0].createdAt) : void 0;
920
+ } else {
921
+ nextCursor = endIndex < data.length ? encodePaginationCursorByCreatedAt(
922
+ page[page.length - 1].id,
923
+ page[page.length - 1].createdAt
924
+ ) : void 0;
925
+ }
926
+ return { data: page, nextCursor };
927
+ }
928
+ function slicePageByUpdatedAt(data, options) {
929
+ const { limit, startingAfter } = options;
930
+ data = data.slice().sort((a, b) => {
931
+ if (a.updatedAt.getTime() !== b.updatedAt.getTime()) {
932
+ return a.updatedAt.getTime() - b.updatedAt.getTime();
933
+ }
934
+ return b.id.localeCompare(a.id);
935
+ });
936
+ let endIndex;
937
+ if (startingAfter) {
938
+ const cursor = decodePaginationCursorByUpdatedAt(startingAfter);
939
+ endIndex = data.findIndex(
940
+ (c) => c.updatedAt.getTime() > cursor.updatedAt.getTime() || c.updatedAt.getTime() === cursor.updatedAt.getTime() && c.id <= cursor.id
941
+ );
942
+ if (endIndex === -1) {
943
+ endIndex = data.length;
944
+ }
945
+ } else {
946
+ endIndex = data.length;
947
+ }
948
+ const startIndex = limit !== void 0 ? Math.max(0, endIndex - limit) : 0;
949
+ const page = data.slice(startIndex, endIndex);
950
+ if (page.length === 0) {
951
+ return { data: [], nextCursor: void 0 };
952
+ }
953
+ const nextCursor = startIndex > 0 ? encodePaginationCursorByUpdatedAt(page[0].id, page[0].updatedAt) : void 0;
954
+ return { data: page, nextCursor };
955
+ }
956
+ function getAttachmentType(mimeType) {
957
+ if (mimeType.startsWith("image/")) {
958
+ return "image";
959
+ } else if (mimeType.startsWith("video/")) {
960
+ return "video";
961
+ } else if (mimeType.startsWith("audio/")) {
962
+ return "audio";
963
+ }
964
+ return "file";
965
+ }
966
+ function createLiveblocksAdapter(config) {
967
+ return new LiveblocksAdapter(config);
968
+ }
969
+
970
+
971
+ exports.createLiveblocksAdapter = createLiveblocksAdapter;
972
+ //# sourceMappingURL=index.cjs.map