@liveblocks/node 1.7.1 → 1.8.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/dist/index.mjs CHANGED
@@ -3,6 +3,9 @@ var DEFAULT_BASE_URL = "https://api.liveblocks.io";
3
3
  async function fetchPolyfill() {
4
4
  return typeof globalThis.fetch !== "undefined" ? globalThis.fetch : (await import("node-fetch")).default;
5
5
  }
6
+ function isSomething(input) {
7
+ return input !== null && input !== void 0;
8
+ }
6
9
  function isNonEmpty(value) {
7
10
  return typeof value === "string" && value.length > 0;
8
11
  }
@@ -39,16 +42,21 @@ function toURLSearchParams(params) {
39
42
  return result;
40
43
  }
41
44
  function urljoin(baseUrl, path, params) {
42
- const url = new URL(path, baseUrl);
45
+ const url2 = new URL(path, baseUrl);
43
46
  if (params !== void 0) {
44
- url.search = (params instanceof URLSearchParams ? params : toURLSearchParams(params)).toString();
47
+ url2.search = (params instanceof URLSearchParams ? params : toURLSearchParams(params)).toString();
45
48
  }
46
- return url.toString();
49
+ return url2.toString();
50
+ }
51
+ function url(strings, ...values) {
52
+ return strings.reduce(
53
+ (result, str, i) => result + encodeURIComponent(values[i - 1] ?? "") + str
54
+ );
47
55
  }
48
56
 
49
57
  // src/authorize.ts
50
58
  async function authorize(options) {
51
- let url = null;
59
+ let url2 = null;
52
60
  try {
53
61
  const { room, secret, userId, userInfo, groupIds } = (
54
62
  // Ensure we'll validate inputs at runtime
@@ -57,7 +65,7 @@ async function authorize(options) {
57
65
  assertNonEmpty(secret, "secret");
58
66
  assertNonEmpty(room, "room");
59
67
  assertNonEmpty(userId, "userId");
60
- url = buildLiveblocksAuthorizeEndpoint(options, room);
68
+ url2 = buildLiveblocksAuthorizeEndpoint(options, room);
61
69
  const fetch = await fetchPolyfill();
62
70
  const resp = await fetch(buildLiveblocksAuthorizeEndpoint(options, room), {
63
71
  method: "POST",
@@ -78,7 +86,7 @@ async function authorize(options) {
78
86
  } catch (er) {
79
87
  return {
80
88
  status: 503,
81
- body: (url ? `Call to "${url}" failed.` : "Invalid authorize request.") + ' See "error" for more information.',
89
+ body: (url2 ? `Call to "${url2}" failed.` : "Invalid authorize request.") + ' See "error" for more information.',
82
90
  error: er
83
91
  };
84
92
  }
@@ -192,7 +200,7 @@ var Session = class {
192
200
  };
193
201
  }
194
202
  try {
195
- const resp = await this._postFn("/v2/authorize-user", {
203
+ const resp = await this._postFn(url`/v2/authorize-user`, {
196
204
  // Required
197
205
  userId: this._userId,
198
206
  permissions: this.serializePermissions(),
@@ -227,28 +235,57 @@ var Liveblocks = class {
227
235
  }
228
236
  /** @internal */
229
237
  async post(path, json) {
230
- const url = urljoin(this._baseUrl, path);
238
+ const url2 = urljoin(this._baseUrl, path);
231
239
  const headers = {
232
240
  Authorization: `Bearer ${this._secret}`,
233
241
  "Content-Type": "application/json"
234
242
  };
235
243
  const fetch = await fetchPolyfill();
236
- return fetch(url, {
244
+ const res = await fetch(url2, {
237
245
  method: "POST",
238
246
  headers,
239
247
  body: JSON.stringify(json)
240
248
  });
249
+ return res;
241
250
  }
242
251
  /** @internal */
243
- async get(path) {
244
- const url = urljoin(this._baseUrl, path);
252
+ async put(path, json) {
253
+ const url2 = urljoin(this._baseUrl, path);
245
254
  const headers = {
246
255
  Authorization: `Bearer ${this._secret}`,
247
256
  "Content-Type": "application/json"
248
257
  };
249
258
  const fetch = await fetchPolyfill();
250
- return fetch(url, { method: "GET", headers });
259
+ const res = await fetch(url2, {
260
+ method: "PUT",
261
+ headers,
262
+ body: JSON.stringify(json)
263
+ });
264
+ return res;
251
265
  }
266
+ /** @internal */
267
+ async delete(path) {
268
+ const url2 = urljoin(this._baseUrl, path);
269
+ const headers = {
270
+ Authorization: `Bearer ${this._secret}`
271
+ };
272
+ const fetch = await fetchPolyfill();
273
+ const res = await fetch(url2, { method: "DELETE", headers });
274
+ return res;
275
+ }
276
+ /** @internal */
277
+ async get(path, params) {
278
+ const url2 = urljoin(this._baseUrl, path, params);
279
+ const headers = {
280
+ Authorization: `Bearer ${this._secret}`
281
+ };
282
+ const fetch = await fetchPolyfill();
283
+ const res = await fetch(url2, { method: "GET", headers });
284
+ return res;
285
+ }
286
+ /* -------------------------------------------------------------------------------------------------
287
+ * Authentication
288
+ * -----------------------------------------------------------------------------------------------*/
252
289
  /**
253
290
  * Prepares a new session to authorize a user to access Liveblocks.
254
291
  *
@@ -302,7 +339,7 @@ var Liveblocks = class {
302
339
  */
303
340
  // These fields define the security identity of the user. Whatever you pass in here will define which
304
341
  async identifyUser(identity, options) {
305
- const path = "/v2/identify-user";
342
+ const path = url`/v2/identify-user`;
306
343
  const userId = typeof identity === "string" ? identity : identity.userId;
307
344
  const groupIds = typeof identity === "string" ? void 0 : identity.groupIds;
308
345
  assertNonEmpty(userId, "userId");
@@ -328,6 +365,391 @@ var Liveblocks = class {
328
365
  };
329
366
  }
330
367
  }
368
+ /* -------------------------------------------------------------------------------------------------
369
+ * Room
370
+ * -----------------------------------------------------------------------------------------------*/
371
+ /**
372
+ * Returns a list of your rooms. The rooms are returned sorted by creation date, from newest to oldest. You can filter rooms by metadata, users accesses and groups accesses.
373
+ * @param params.limit (optional) A limit on the number of rooms to be returned. The limit can range between 1 and 100, and defaults to 20.
374
+ * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page.
375
+ * @param params.userId (optional) A filter on users accesses.
376
+ * @param params.metadata (optional) A filter on metadata. Multiple metadata keys can be used to filter rooms.
377
+ * @param params.groupIds (optional) A filter on groups accesses. Multiple groups can be used.
378
+ * @returns A list of rooms.
379
+ */
380
+ async getRooms(params = {}) {
381
+ const path = url`/v2/rooms`;
382
+ const queryParams = {
383
+ limit: params.limit,
384
+ startingAfter: params.startingAfter,
385
+ userId: params.userId,
386
+ groupIds: params.groupIds ? params.groupIds.join(",") : void 0,
387
+ // "Flatten" {metadata: {foo: "bar"}} to {"metadata.foo": "bar"}
388
+ ...Object.fromEntries(
389
+ Object.entries(params.metadata ?? {}).map(([key, val]) => {
390
+ const value = Array.isArray(val) ? val.join(",") : val;
391
+ return [`metadata.${key}`, value];
392
+ })
393
+ )
394
+ };
395
+ const res = await this.get(path, queryParams);
396
+ const data = await res.json();
397
+ const rooms = data.data.map((room) => {
398
+ const lastConnectionAt = room.lastConnectionAt ? new Date(room.lastConnectionAt) : void 0;
399
+ const createdAt = room.createdAt ? new Date(room.createdAt) : void 0;
400
+ return {
401
+ ...room,
402
+ lastConnectionAt,
403
+ createdAt
404
+ };
405
+ });
406
+ return {
407
+ ...data,
408
+ data: rooms
409
+ };
410
+ }
411
+ /**
412
+ * Creates a new room with the given id.
413
+ * @param roomId The id of the room to create.
414
+ * @param params.defaultAccesses The default accesses for the room.
415
+ * @param params.groupAccesses (optional) The group accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.
416
+ * @param params.userAccesses (optional) The user accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.
417
+ * @param params.metadata (optional) The metadata for the room. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.
418
+ * @returns The created room.
419
+ */
420
+ async createRoom(roomId, params) {
421
+ const { defaultAccesses, groupsAccesses, usersAccesses, metadata } = params;
422
+ const res = await this.post(url`/v2/rooms`, {
423
+ id: roomId,
424
+ defaultAccesses,
425
+ groupsAccesses,
426
+ usersAccesses,
427
+ metadata
428
+ });
429
+ if (!res.ok) {
430
+ const text = await res.text();
431
+ throw new LiveblocksError(res.status, text);
432
+ }
433
+ const data = await res.json();
434
+ const lastConnectionAt = data.lastConnectionAt ? new Date(data.lastConnectionAt) : void 0;
435
+ const createdAt = data.createdAt ? new Date(data.createdAt) : void 0;
436
+ return {
437
+ ...data,
438
+ lastConnectionAt,
439
+ createdAt
440
+ };
441
+ }
442
+ /**
443
+ * Returns a room with the given id.
444
+ * @param roomId The id of the room to return.
445
+ * @returns The room with the given id.
446
+ */
447
+ async getRoom(roomId) {
448
+ const res = await this.get(url`/v2/rooms/${roomId}`);
449
+ if (!res.ok) {
450
+ const text = await res.text();
451
+ throw new LiveblocksError(res.status, text);
452
+ }
453
+ const data = await res.json();
454
+ const lastConnectionAt = data.lastConnectionAt ? new Date(data.lastConnectionAt) : void 0;
455
+ const createdAt = data.createdAt ? new Date(data.createdAt) : void 0;
456
+ return {
457
+ ...data,
458
+ lastConnectionAt,
459
+ createdAt
460
+ };
461
+ }
462
+ /**
463
+ * Updates specific properties of a room. It’s not necessary to provide the entire room’s information.
464
+ * Setting a property to `null` means to delete this property.
465
+ * @param roomId The id of the room to update.
466
+ * @param params.defaultAccesses (optional) The default accesses for the room.
467
+ * @param params.groupAccesses (optional) The group accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.
468
+ * @param params.userAccesses (optional) The user accesses for the room. Can contain a maximum of 100 entries. Key length has a limit of 40 characters.
469
+ * @param params.metadata (optional) The metadata for the room. Supports upto a maximum of 50 entries. Key length has a limit of 40 characters. Value length has a limit of 256 characters.
470
+ * @returns The updated room.
471
+ */
472
+ async updateRoom(roomId, params) {
473
+ const { defaultAccesses, groupsAccesses, usersAccesses, metadata } = params;
474
+ const res = await this.post(url`/v2/rooms/${roomId}`, {
475
+ defaultAccesses,
476
+ groupsAccesses,
477
+ usersAccesses,
478
+ metadata
479
+ });
480
+ if (!res.ok) {
481
+ const text = await res.text();
482
+ throw new LiveblocksError(res.status, text);
483
+ }
484
+ const data = await res.json();
485
+ const lastConnectionAt = data.lastConnectionAt ? new Date(data.lastConnectionAt) : void 0;
486
+ const createdAt = data.createdAt ? new Date(data.createdAt) : void 0;
487
+ return {
488
+ ...data,
489
+ lastConnectionAt,
490
+ createdAt
491
+ };
492
+ }
493
+ /**
494
+ * Deletes a room with the given id. A deleted room is no longer accessible from the API or the dashboard and it cannot be restored.
495
+ * @param roomId The id of the room to delete.
496
+ */
497
+ async deleteRoom(roomId) {
498
+ const res = await this.delete(url`/v2/rooms/${roomId}`);
499
+ if (!res.ok) {
500
+ const text = await res.text();
501
+ throw new LiveblocksError(res.status, text);
502
+ }
503
+ }
504
+ /**
505
+ * Returns a list of users currently present in the requested room. For better performance, we recommand to call this endpoint every 10 seconds maximum. Duplicates can happen if a user is in the requested room with multiple browser tabs opened.
506
+ * @param roomId The id of the room to get the users from.
507
+ * @returns A list of users currently present in the requested room.
508
+ */
509
+ async getActiveUsers(roomId) {
510
+ const res = await this.get(url`/v2/rooms/${roomId}/active_users`);
511
+ if (!res.ok) {
512
+ const text = await res.text();
513
+ throw new LiveblocksError(res.status, text);
514
+ }
515
+ return await res.json();
516
+ }
517
+ /**
518
+ * Boadcasts an event to a room without having to connect to it via the client from @liveblocks/client. The connectionId passed to event listeners is -1 when using this API.
519
+ * @param roomId The id of the room to broadcast the event to.
520
+ * @param message The message to broadcast. It can be any JSON serializable value.
521
+ */
522
+ async broadcastEvent(roomId, message) {
523
+ const res = await this.post(
524
+ url`/v2/rooms/${roomId}/broadcast_event`,
525
+ message
526
+ );
527
+ if (!res.ok) {
528
+ const text = await res.text();
529
+ throw new LiveblocksError(res.status, text);
530
+ }
531
+ }
532
+ async getStorageDocument(roomId, format = "plain-lson") {
533
+ const res = await this.get(url`/v2/rooms/${roomId}/storage`, { format });
534
+ if (!res.ok) {
535
+ const text = await res.text();
536
+ throw new LiveblocksError(res.status, text);
537
+ }
538
+ return await res.json();
539
+ }
540
+ /**
541
+ * Initializes a room’s Storage. The room must already exist and have an empty Storage.
542
+ * Calling this endpoint will disconnect all users from the room if there are any.
543
+ *
544
+ * @param roomId The id of the room to initialize the storage from.
545
+ * @param document The document to initialize the storage with.
546
+ * @returns The initialized storage document. It is of the same format as the one passed in.
547
+ */
548
+ async initializeStorageDocument(roomId, document) {
549
+ const res = await this.post(url`/v2/rooms/${roomId}/storage`, document);
550
+ if (!res.ok) {
551
+ const text = await res.text();
552
+ throw new LiveblocksError(res.status, text);
553
+ }
554
+ return await res.json();
555
+ }
556
+ /**
557
+ * Deletes all of the room’s Storage data and disconnect all users from the room if there are any. Note that this does not delete the Yjs document in the room if one exists.
558
+ * @param roomId The id of the room to delete the storage from.
559
+ */
560
+ async deleteStorageDocument(roomId) {
561
+ const res = await this.delete(url`/v2/rooms/${roomId}/storage`);
562
+ if (!res.ok) {
563
+ const text = await res.text();
564
+ throw new LiveblocksError(res.status, text);
565
+ }
566
+ }
567
+ /* -------------------------------------------------------------------------------------------------
568
+ * Yjs
569
+ * -----------------------------------------------------------------------------------------------*/
570
+ /**
571
+ * Returns a JSON representation of the room’s Yjs document.
572
+ * @param roomId The id of the room to get the Yjs document from.
573
+ * @param params.format (optional) If true, YText will return formatting.
574
+ * @param params.key (optional) If provided, returns only a single key’s value, e.g. doc.get(key).toJSON().
575
+ * @param params.type (optional) Used with key to override the inferred type, i.e. "ymap" will return doc.get(key, Y.Map).
576
+ * @returns A JSON representation of the room’s Yjs document.
577
+ */
578
+ async getYjsDocument(roomId, params = {}) {
579
+ const { format, key, type } = params;
580
+ const path = url`v2/rooms/${roomId}/ydoc`;
581
+ const res = await this.get(path, {
582
+ formatting: format ? "true" : void 0,
583
+ key,
584
+ type
585
+ });
586
+ if (!res.ok) {
587
+ const text = await res.text();
588
+ throw new LiveblocksError(res.status, text);
589
+ }
590
+ return await res.json();
591
+ }
592
+ /**
593
+ * Send a Yjs binary update to the room’s Yjs document. You can use this endpoint to initialize Yjs data for the room or to update the room’s Yjs document.
594
+ * @param roomId The id of the room to send the Yjs binary update to.
595
+ * @param params The Yjs binary update to send. Read the [Yjs documentation](https://docs.yjs.dev/api/document-updates) to learn how to create a binary update.
596
+ */
597
+ async sendYjsBinaryUpdate(roomId, params) {
598
+ const { update } = params;
599
+ const res = await this.put(url`/v2/rooms/${roomId}/ydoc`, {
600
+ update
601
+ });
602
+ if (!res.ok) {
603
+ const text = await res.text();
604
+ throw new LiveblocksError(res.status, text);
605
+ }
606
+ }
607
+ /**
608
+ * Returns the room’s Yjs document encoded as a single binary update. This can be used by Y.applyUpdate(responseBody) to get a copy of the document in your backend.
609
+ * See [Yjs documentation](https://docs.yjs.dev/api/document-updates) for more information on working with updates.
610
+ * @param roomId The id of the room to get the Yjs document from.
611
+ * @returns The room’s Yjs document encoded as a single binary update.
612
+ */
613
+ async getYjsDocumentAsBinaryUpdate(roomId) {
614
+ const res = await this.get(url`/v2/rooms/${roomId}/ydoc-binary`);
615
+ if (!res.ok) {
616
+ const text = await res.text();
617
+ throw new LiveblocksError(res.status, text);
618
+ }
619
+ return res.arrayBuffer();
620
+ }
621
+ /* -------------------------------------------------------------------------------------------------
622
+ * Schema Validation
623
+ * -----------------------------------------------------------------------------------------------*/
624
+ /**
625
+ * Creates a new schema which can be referenced later to enforce a room’s Storage data structure.
626
+ * @param name The name used to reference the schema. Must be a non-empty string with less than 65 characters and only contain lowercase letters, numbers and dashes
627
+ * @param body The exact allowed shape of data in the room. It is a multi-line string written in the [Liveblocks schema syntax](https://liveblocks.io/docs/platform/schema-validation/syntax).
628
+ * @returns The created schema.
629
+ */
630
+ async createSchema(name, body) {
631
+ const res = await this.post(url`/v2/schemas`, {
632
+ name,
633
+ body
634
+ });
635
+ if (!res.ok) {
636
+ const text = await res.text();
637
+ throw new LiveblocksError(res.status, text);
638
+ }
639
+ const data = await res.json();
640
+ const createdAt = new Date(data.createdAt);
641
+ const updatedAt = new Date(data.updatedAt);
642
+ return {
643
+ ...data,
644
+ createdAt,
645
+ updatedAt
646
+ };
647
+ }
648
+ /**
649
+ * Returns a schema by its id.
650
+ * @param schemaId Id of the schema - this is the combination of the schema name and version of the schema to update. For example, `my-schema@1`.
651
+ * @returns The schema with the given id.
652
+ */
653
+ async getSchema(schemaId) {
654
+ const res = await this.get(url`/v2/schemas/${schemaId}`);
655
+ if (!res.ok) {
656
+ const text = await res.text();
657
+ throw new LiveblocksError(res.status, text);
658
+ }
659
+ const data = await res.json();
660
+ const createdAt = new Date(data.createdAt);
661
+ const updatedAt = new Date(data.updatedAt);
662
+ return {
663
+ ...data,
664
+ createdAt,
665
+ updatedAt
666
+ };
667
+ }
668
+ /**
669
+ * Updates the body for the schema. A schema can only be updated if it is not used by any room.
670
+ * @param schemaId Id of the schema - this is the combination of the schema name and version of the schema to update. For example, `my-schema@1`.
671
+ * @param body The exact allowed shape of data in the room. It is a multi-line string written in the [Liveblocks schema syntax](https://liveblocks.io/docs/platform/schema-validation/syntax).
672
+ * @returns The updated schema. The version of the schema will be incremented.
673
+ */
674
+ async updateSchema(schemaId, body) {
675
+ const res = await this.put(url`/v2/schemas/${schemaId}`, {
676
+ body
677
+ });
678
+ if (!res.ok) {
679
+ const text = await res.text();
680
+ throw new LiveblocksError(res.status, text);
681
+ }
682
+ const data = await res.json();
683
+ const createdAt = new Date(data.createdAt);
684
+ const updatedAt = new Date(data.updatedAt);
685
+ return {
686
+ ...data,
687
+ createdAt,
688
+ updatedAt
689
+ };
690
+ }
691
+ /**
692
+ * Deletes a schema by its id. A schema can only be deleted if it is not used by any room.
693
+ * @param schemaId Id of the schema - this is the combination of the schema name and version of the schema to update. For example, `my-schema@1`.
694
+ */
695
+ async deleteSchema(schemaId) {
696
+ const res = await this.delete(url`/v2/schemas/${schemaId}`);
697
+ if (!res.ok) {
698
+ const text = await res.text();
699
+ throw new LiveblocksError(res.status, text);
700
+ }
701
+ }
702
+ /**
703
+ * Returns the schema attached to a room.
704
+ * @param roomId The id of the room to get the schema from.
705
+ * @returns
706
+ */
707
+ async getSchemaByRoomId(roomId) {
708
+ const res = await this.get(url`/v2/rooms/${roomId}/schema`);
709
+ if (!res.ok) {
710
+ const text = await res.text();
711
+ throw new LiveblocksError(res.status, text);
712
+ }
713
+ const data = await res.json();
714
+ const createdAt = new Date(data.createdAt);
715
+ const updatedAt = new Date(data.updatedAt);
716
+ return {
717
+ ...data,
718
+ createdAt,
719
+ updatedAt
720
+ };
721
+ }
722
+ /**
723
+ * Attaches a schema to a room, and instantly enables runtime schema validation for the room.
724
+ * If the current contents of the room’s Storage do not match the schema, attaching will fail and the error message will give details on why the schema failed to attach.
725
+ * @param roomId The id of the room to attach the schema to.
726
+ * @param schemaId Id of the schema - this is the combination of the schema name and version of the schema to update. For example, `my-schema@1`.
727
+ * @returns The schema id as JSON.
728
+ */
729
+ async attachSchemaToRoom(roomId, schemaId) {
730
+ const res = await this.post(url`/v2/rooms/${roomId}/schema`, {
731
+ schema: schemaId
732
+ });
733
+ if (!res.ok) {
734
+ const text = await res.text();
735
+ throw new LiveblocksError(res.status, text);
736
+ }
737
+ return await res.json();
738
+ }
739
+ /**
740
+ * Detaches a schema from a room, and disables runtime schema validation for the room.
741
+ * @param roomId The id of the room to detach the schema from.
742
+ */
743
+ async detachSchemaFromRoom(roomId) {
744
+ const res = await this.delete(url`/v2/rooms/${roomId}/schema`);
745
+ if (!res.ok) {
746
+ const text = await res.text();
747
+ throw new LiveblocksError(res.status, text);
748
+ }
749
+ }
750
+ /* -------------------------------------------------------------------------------------------------
751
+ * Comments
752
+ * -----------------------------------------------------------------------------------------------*/
331
753
  /**
332
754
  * Gets all the threads in a room.
333
755
  *
@@ -336,17 +758,12 @@ var Liveblocks = class {
336
758
  */
337
759
  async getThreads(params) {
338
760
  const { roomId } = params;
339
- const resp = await this.get(
340
- `/v2/rooms/${encodeURIComponent(roomId)}/threads`
341
- );
342
- const body = await resp.json();
343
- if (resp.status !== 200) {
344
- throw {
345
- status: resp.status,
346
- ...body
347
- };
761
+ const res = await this.get(url`/v2/rooms/${roomId}/threads`);
762
+ if (!res.ok) {
763
+ const text = await res.text();
764
+ throw new LiveblocksError(res.status, text);
348
765
  }
349
- return body;
766
+ return await res.json();
350
767
  }
351
768
  /**
352
769
  * Gets a thread.
@@ -357,19 +774,12 @@ var Liveblocks = class {
357
774
  */
358
775
  async getThread(params) {
359
776
  const { roomId, threadId } = params;
360
- const resp = await this.get(
361
- `/v2/rooms/${encodeURIComponent(roomId)}/threads/${encodeURIComponent(
362
- threadId
363
- )}`
364
- );
365
- const body = await resp.json();
366
- if (resp.status !== 200) {
367
- throw {
368
- status: resp.status,
369
- ...body
370
- };
777
+ const res = await this.get(url`/v2/rooms/${roomId}/threads/${threadId}`);
778
+ if (!res.ok) {
779
+ const text = await res.text();
780
+ throw new LiveblocksError(res.status, text);
371
781
  }
372
- return body;
782
+ return await res.json();
373
783
  }
374
784
  /**
375
785
  * Gets a thread's participants.
@@ -383,19 +793,14 @@ var Liveblocks = class {
383
793
  */
384
794
  async getThreadParticipants(params) {
385
795
  const { roomId, threadId } = params;
386
- const resp = await this.get(
387
- `/v2/rooms/${encodeURIComponent(roomId)}/threads/${encodeURIComponent(
388
- threadId
389
- )}/participants`
796
+ const res = await this.get(
797
+ url`/v2/rooms/${roomId}/threads/${threadId}/participants`
390
798
  );
391
- const body = await resp.json();
392
- if (resp.status !== 200) {
393
- throw {
394
- status: resp.status,
395
- ...body
396
- };
799
+ if (!res.ok) {
800
+ const text = await res.text();
801
+ throw new LiveblocksError(res.status, text);
397
802
  }
398
- return body;
803
+ return await res.json();
399
804
  }
400
805
  /**
401
806
  * Gets a thread's comment.
@@ -407,21 +812,326 @@ var Liveblocks = class {
407
812
  */
408
813
  async getComment(params) {
409
814
  const { roomId, threadId, commentId } = params;
410
- const resp = await this.get(
411
- `/v2/rooms/${encodeURIComponent(roomId)}/threads/${encodeURIComponent(
412
- threadId
413
- )}/comments/${encodeURIComponent(commentId)}`
815
+ const res = await this.get(
816
+ url`/v2/rooms/${roomId}/threads/${threadId}/comments/${commentId}`
414
817
  );
415
- const body = await resp.json();
416
- if (resp.status !== 200) {
417
- throw {
418
- status: resp.status,
419
- ...body
420
- };
818
+ if (!res.ok) {
819
+ const text = await res.text();
820
+ throw new LiveblocksError(res.status, text);
821
+ }
822
+ return await res.json();
823
+ }
824
+ };
825
+ var LiveblocksError = class extends Error {
826
+ constructor(status, message = "") {
827
+ super(message);
828
+ this.name = "LiveblocksError";
829
+ this.status = status;
830
+ }
831
+ };
832
+
833
+ // src/comment-body.ts
834
+ function isCommentBodyParagraph(element) {
835
+ return "type" in element && element.type === "mention";
836
+ }
837
+ function isCommentBodyText(element) {
838
+ return "text" in element && typeof element.text === "string";
839
+ }
840
+ function isCommentBodyMention(element) {
841
+ return "type" in element && element.type === "mention";
842
+ }
843
+ function isCommentBodyLink(element) {
844
+ return "type" in element && element.type === "link";
845
+ }
846
+ var commentBodyElementsGuards = {
847
+ paragraph: isCommentBodyParagraph,
848
+ text: isCommentBodyText,
849
+ link: isCommentBodyLink,
850
+ mention: isCommentBodyMention
851
+ };
852
+ var commentBodyElementsTypes = {
853
+ paragraph: "block",
854
+ text: "inline",
855
+ link: "inline",
856
+ mention: "inline"
857
+ };
858
+ function traverseCommentBody(body, elementOrVisitor, possiblyVisitor) {
859
+ if (!body || !body?.content) {
860
+ return;
861
+ }
862
+ const element = typeof elementOrVisitor === "string" ? elementOrVisitor : void 0;
863
+ const type = element ? commentBodyElementsTypes[element] : "all";
864
+ const guard = element ? commentBodyElementsGuards[element] : () => true;
865
+ const visitor = typeof elementOrVisitor === "function" ? elementOrVisitor : possiblyVisitor;
866
+ for (const block of body.content) {
867
+ if (type === "all" || type === "block") {
868
+ if (guard(block)) {
869
+ visitor?.(block);
870
+ }
871
+ }
872
+ if (type === "all" || type === "inline") {
873
+ for (const inline of block.children) {
874
+ if (guard(inline)) {
875
+ visitor?.(inline);
876
+ }
877
+ }
878
+ }
879
+ }
880
+ }
881
+ function getMentionedIdsFromCommentBody(body) {
882
+ const mentionedIds = /* @__PURE__ */ new Set();
883
+ traverseCommentBody(
884
+ body,
885
+ "mention",
886
+ (mention) => mentionedIds.add(mention.id)
887
+ );
888
+ return Array.from(mentionedIds);
889
+ }
890
+ async function resolveUsersInCommentBody(body, resolveUsers) {
891
+ const resolvedUsers = /* @__PURE__ */ new Map();
892
+ if (!resolveUsers) {
893
+ return resolvedUsers;
894
+ }
895
+ const userIds = getMentionedIdsFromCommentBody(body);
896
+ const users = await resolveUsers({
897
+ userIds
898
+ });
899
+ for (const [index, userId] of userIds.entries()) {
900
+ const user = users?.[index];
901
+ if (user) {
902
+ resolvedUsers.set(userId, user);
903
+ }
904
+ }
905
+ return resolvedUsers;
906
+ }
907
+ var htmlEscapables = {
908
+ "&": "&",
909
+ "<": "&lt;",
910
+ ">": "&gt;",
911
+ '"': "&quot;",
912
+ "'": "&#39;"
913
+ };
914
+ var htmlEscapablesRegex = new RegExp(
915
+ Object.keys(htmlEscapables).map((entity) => `\\${entity}`).join("|"),
916
+ "g"
917
+ );
918
+ function htmlSafe(value) {
919
+ return new HtmlSafeString([String(value)], []);
920
+ }
921
+ function joinHtml(strings) {
922
+ if (strings.length <= 0) {
923
+ return new HtmlSafeString([""], []);
924
+ }
925
+ return new HtmlSafeString(
926
+ ["", ...Array(strings.length - 1).fill(""), ""],
927
+ strings
928
+ );
929
+ }
930
+ function escapeHtml(value) {
931
+ if (value instanceof HtmlSafeString) {
932
+ return value.toString();
933
+ }
934
+ if (Array.isArray(value)) {
935
+ return joinHtml(value).toString();
936
+ }
937
+ return String(value).replace(
938
+ htmlEscapablesRegex,
939
+ (character) => htmlEscapables[character]
940
+ );
941
+ }
942
+ var HtmlSafeString = class {
943
+ constructor(strings, values) {
944
+ this._strings = strings;
945
+ this._values = values;
946
+ }
947
+ toString() {
948
+ return this._strings.reduce((result, str, i) => {
949
+ return result + escapeHtml(this._values[i - 1]) + str;
950
+ });
951
+ }
952
+ };
953
+ function html(strings, ...values) {
954
+ return new HtmlSafeString(strings, values);
955
+ }
956
+ var markdownEscapables = {
957
+ _: "\\_",
958
+ "*": "\\*",
959
+ "#": "\\#",
960
+ "`": "\\`",
961
+ "~": "\\~",
962
+ "!": "\\!",
963
+ "|": "\\|",
964
+ "(": "\\(",
965
+ ")": "\\)",
966
+ "{": "\\{",
967
+ "}": "\\}",
968
+ "[": "\\[",
969
+ "]": "\\]"
970
+ };
971
+ var markdownEscapablesRegex = new RegExp(
972
+ Object.keys(markdownEscapables).map((entity) => `\\${entity}`).join("|"),
973
+ "g"
974
+ );
975
+ function joinMarkdown(strings) {
976
+ if (strings.length <= 0) {
977
+ return new MarkdownSafeString([""], []);
978
+ }
979
+ return new MarkdownSafeString(
980
+ ["", ...Array(strings.length - 1).fill(""), ""],
981
+ strings
982
+ );
983
+ }
984
+ function escapeMarkdown(value) {
985
+ if (value instanceof MarkdownSafeString) {
986
+ return value.toString();
987
+ }
988
+ if (Array.isArray(value)) {
989
+ return joinMarkdown(value).toString();
990
+ }
991
+ return String(value).replace(
992
+ markdownEscapablesRegex,
993
+ (character) => markdownEscapables[character]
994
+ );
995
+ }
996
+ var MarkdownSafeString = class {
997
+ constructor(strings, values) {
998
+ this._strings = strings;
999
+ this._values = values;
1000
+ }
1001
+ toString() {
1002
+ return this._strings.reduce((result, str, i) => {
1003
+ return result + escapeMarkdown(this._values[i - 1]) + str;
1004
+ });
1005
+ }
1006
+ };
1007
+ function markdown(strings, ...values) {
1008
+ return new MarkdownSafeString(strings, values);
1009
+ }
1010
+ function toAbsoluteUrl(url2) {
1011
+ if (url2.startsWith("http://") || url2.startsWith("https://")) {
1012
+ return url2;
1013
+ } else if (url2.startsWith("www.")) {
1014
+ return "https://" + url2;
1015
+ }
1016
+ return;
1017
+ }
1018
+ var stringifyCommentBodyPlainElements = {
1019
+ paragraph: ({ children }) => children,
1020
+ text: ({ element }) => element.text,
1021
+ link: ({ element }) => element.url,
1022
+ mention: ({ element, user }) => {
1023
+ return `@${user?.name ?? element.id}`;
1024
+ }
1025
+ };
1026
+ var stringifyCommentBodyHtmlElements = {
1027
+ paragraph: ({ children }) => {
1028
+ return children ? html`<p>${htmlSafe(children)}</p>` : children;
1029
+ },
1030
+ text: ({ element }) => {
1031
+ let children = element.text;
1032
+ if (!children) {
1033
+ return children;
1034
+ }
1035
+ if (element.bold) {
1036
+ children = html`<strong>${children}</strong>`;
1037
+ }
1038
+ if (element.italic) {
1039
+ children = html`<em>${children}</em>`;
1040
+ }
1041
+ if (element.strikethrough) {
1042
+ children = html`<s>${children}</s>`;
421
1043
  }
422
- return body;
1044
+ if (element.code) {
1045
+ children = html`<code>${children}</code>`;
1046
+ }
1047
+ return children;
1048
+ },
1049
+ link: ({ element, href }) => {
1050
+ return html`<a href="${href}" target="_blank" rel="noopener noreferrer">${element.url}</a>`;
1051
+ },
1052
+ mention: ({ element, user }) => {
1053
+ return html`<span data-mention>@${user?.name ?? element.id}</span>`;
1054
+ }
1055
+ };
1056
+ var stringifyCommentBodyMarkdownElements = {
1057
+ paragraph: ({ children }) => {
1058
+ return children;
1059
+ },
1060
+ text: ({ element }) => {
1061
+ let children = element.text;
1062
+ if (!children) {
1063
+ return children;
1064
+ }
1065
+ if (element.bold) {
1066
+ children = markdown`**${children}**`;
1067
+ }
1068
+ if (element.italic) {
1069
+ children = markdown`_${children}_`;
1070
+ }
1071
+ if (element.strikethrough) {
1072
+ children = markdown`~~${children}~~`;
1073
+ }
1074
+ if (element.code) {
1075
+ children = markdown`\`${children}\``;
1076
+ }
1077
+ return children;
1078
+ },
1079
+ link: ({ element, href }) => {
1080
+ return markdown`[${element.url}](${href})`;
1081
+ },
1082
+ mention: ({ element, user }) => {
1083
+ return markdown`@${user?.name ?? element.id}`;
423
1084
  }
424
1085
  };
1086
+ async function stringifyCommentBody(body, options) {
1087
+ const format = options?.format ?? "plain";
1088
+ const separator = options?.separator ?? (format === "markdown" ? "\n\n" : "\n");
1089
+ const elements = {
1090
+ ...format === "html" ? stringifyCommentBodyHtmlElements : format === "markdown" ? stringifyCommentBodyMarkdownElements : stringifyCommentBodyPlainElements,
1091
+ ...options?.elements
1092
+ };
1093
+ const resolvedUsers = await resolveUsersInCommentBody(
1094
+ body,
1095
+ options?.resolveUsers
1096
+ );
1097
+ const blocks = body.content.map((block, blockIndex) => {
1098
+ switch (block.type) {
1099
+ case "paragraph": {
1100
+ const paragraph = block.children.map((inline, inlineIndex) => {
1101
+ if (isCommentBodyMention(inline)) {
1102
+ return inline.id ? elements.mention(
1103
+ {
1104
+ element: inline,
1105
+ user: resolvedUsers.get(inline.id)
1106
+ },
1107
+ inlineIndex
1108
+ ) : null;
1109
+ }
1110
+ if (isCommentBodyLink(inline)) {
1111
+ return elements.link(
1112
+ {
1113
+ element: inline,
1114
+ href: toAbsoluteUrl(inline.url) ?? inline.url
1115
+ },
1116
+ inlineIndex
1117
+ );
1118
+ }
1119
+ if (isCommentBodyText(inline)) {
1120
+ return elements.text({ element: inline }, inlineIndex);
1121
+ }
1122
+ return null;
1123
+ }).filter(isSomething).join("");
1124
+ return elements.paragraph(
1125
+ { element: block, children: paragraph },
1126
+ blockIndex
1127
+ );
1128
+ }
1129
+ default:
1130
+ return null;
1131
+ }
1132
+ });
1133
+ return blocks.filter(isSomething).join(separator);
1134
+ }
425
1135
 
426
1136
  // src/webhooks.ts
427
1137
  import * as base64 from "@stablelib/base64";
@@ -540,6 +1250,8 @@ var isNotUndefined = (value) => value !== void 0;
540
1250
  export {
541
1251
  Liveblocks,
542
1252
  WebhookHandler,
543
- authorize
1253
+ authorize,
1254
+ getMentionedIdsFromCommentBody,
1255
+ stringifyCommentBody
544
1256
  };
545
1257
  //# sourceMappingURL=index.mjs.map