@liveblocks/core 2.17.0-channels1 → 2.17.0-rc1

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
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "2.17.0-channels1";
9
+ var PKG_VERSION = "2.17.0-rc1";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -166,6 +166,14 @@ function wrapWithTitle(method) {
166
166
  var warnWithTitle = wrapWithTitle("warn");
167
167
  var errorWithTitle = wrapWithTitle("error");
168
168
 
169
+ // src/lib/guards.ts
170
+ function isPlainObject(blob) {
171
+ return blob !== null && typeof blob === "object" && Object.prototype.toString.call(blob) === "[object Object]";
172
+ }
173
+ function isStartsWithOperator(blob) {
174
+ return isPlainObject(blob) && typeof blob.startsWith === "string";
175
+ }
176
+
169
177
  // src/lib/utils.ts
170
178
  function raise(msg) {
171
179
  throw new Error(msg);
@@ -173,12 +181,6 @@ function raise(msg) {
173
181
  function entries(obj) {
174
182
  return Object.entries(obj);
175
183
  }
176
- function keys(obj) {
177
- return Object.keys(obj);
178
- }
179
- function values(obj) {
180
- return Object.values(obj);
181
- }
182
184
  function mapValues(obj, mapFn) {
183
185
  const result = {};
184
186
  for (const pair of Object.entries(obj)) {
@@ -257,13 +259,48 @@ function memoizeOnSuccess(factoryFn) {
257
259
  }
258
260
 
259
261
  // src/lib/autoRetry.ts
260
- var HttpError = class extends Error {
261
- constructor(message, status, details) {
262
+ var HttpError = class _HttpError extends Error {
263
+ response;
264
+ details;
265
+ constructor(message, response, details) {
262
266
  super(message);
263
- this.message = message;
264
- this.status = status;
267
+ this.name = "HttpError";
268
+ this.response = response;
265
269
  this.details = details;
266
270
  }
271
+ static async fromResponse(response) {
272
+ let bodyAsText;
273
+ try {
274
+ bodyAsText = await response.text();
275
+ } catch {
276
+ }
277
+ const bodyAsJson = bodyAsText ? tryParseJson(bodyAsText) : void 0;
278
+ let bodyAsJsonObject;
279
+ if (isPlainObject(bodyAsJson)) {
280
+ bodyAsJsonObject = bodyAsJson;
281
+ }
282
+ let message = "";
283
+ message ||= typeof bodyAsJsonObject?.message === "string" ? bodyAsJsonObject.message : "";
284
+ message ||= typeof bodyAsJsonObject?.error === "string" ? bodyAsJsonObject.error : "";
285
+ if (bodyAsJson === void 0) {
286
+ message ||= bodyAsText || "";
287
+ }
288
+ message ||= response.statusText;
289
+ let path;
290
+ try {
291
+ path = new URL(response.url).pathname;
292
+ } catch {
293
+ }
294
+ message += path !== void 0 ? ` (got status ${response.status} from ${path})` : ` (got status ${response.status})`;
295
+ const details = bodyAsJsonObject;
296
+ return new _HttpError(message, response, details);
297
+ }
298
+ /**
299
+ * Convenience accessor for response.status.
300
+ */
301
+ get status() {
302
+ return this.response.status;
303
+ }
267
304
  };
268
305
  var DONT_RETRY_4XX = (x) => x instanceof HttpError && x.status >= 400 && x.status < 500;
269
306
  async function autoRetry(promiseFn, maxTries, backoff, shouldStopRetrying = DONT_RETRY_4XX) {
@@ -329,7 +366,12 @@ function makeEventSource() {
329
366
  }).finally(() => unsub?.());
330
367
  }
331
368
  function notify(event) {
332
- _observers.forEach((callback) => callback(event));
369
+ let called = false;
370
+ for (const callback of _observers) {
371
+ callback(event);
372
+ called = true;
373
+ }
374
+ return called;
333
375
  }
334
376
  function count() {
335
377
  return _observers.size;
@@ -370,8 +412,9 @@ function makeBufferableEventSource() {
370
412
  function notifyOrBuffer(event) {
371
413
  if (_buffer !== null) {
372
414
  _buffer.push(event);
415
+ return false;
373
416
  } else {
374
- eventSource2.notify(event);
417
+ return eventSource2.notify(event);
375
418
  }
376
419
  }
377
420
  return {
@@ -676,32 +719,15 @@ var MutableSignal = class extends AbstractSignal {
676
719
  };
677
720
 
678
721
  // src/lib/stringify.ts
679
- var EXPLICIT_UNDEFINED_PLACEHOLDER = "_explicit_undefined";
680
722
  function replacer(_key, value) {
681
723
  return value !== null && typeof value === "object" && !Array.isArray(value) ? Object.keys(value).sort().reduce((sorted, key) => {
682
724
  sorted[key] = value[key];
683
725
  return sorted;
684
- }, {}) : value === void 0 ? EXPLICIT_UNDEFINED_PLACEHOLDER : value;
685
- }
686
- function reviver(key, value) {
687
- if (!key && value === EXPLICIT_UNDEFINED_PLACEHOLDER) {
688
- return void 0;
689
- }
690
- if (value && typeof value === "object") {
691
- for (const k in value) {
692
- if (value[k] === EXPLICIT_UNDEFINED_PLACEHOLDER) {
693
- Object.defineProperty(value, k, { value: void 0 });
694
- }
695
- }
696
- }
697
- return value;
726
+ }, {}) : value;
698
727
  }
699
728
  function stringify(value) {
700
729
  return JSON.stringify(value, replacer);
701
730
  }
702
- function unstringify(value) {
703
- return JSON.parse(value, reviver);
704
- }
705
731
 
706
732
  // src/lib/batch.ts
707
733
  var DEFAULT_SIZE = 50;
@@ -917,14 +943,6 @@ var DefaultMap = class extends Map {
917
943
  }
918
944
  };
919
945
 
920
- // src/lib/guards.ts
921
- function isPlainObject(blob) {
922
- return blob !== null && typeof blob === "object" && Object.prototype.toString.call(blob) === "[object Object]";
923
- }
924
- function isStartsWithOperator(blob) {
925
- return isPlainObject(blob) && typeof blob.startsWith === "string";
926
- }
927
-
928
946
  // src/lib/objectToQuery.ts
929
947
  var identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
930
948
  function objectToQuery(obj) {
@@ -974,9 +992,7 @@ function objectToQuery(obj) {
974
992
  ...getFiltersFromKeyValuePairsWithOperator(nKeyValuePairsWithOperator)
975
993
  ];
976
994
  });
977
- return filterList.map(
978
- ({ key, operator, value }) => formatFilter(key, operator, formatFilterValue(value))
979
- ).join(" AND ");
995
+ return filterList.map(({ key, operator, value }) => `${key}${operator}${quote(value)}`).join(" ");
980
996
  }
981
997
  var getFiltersFromKeyValuePairs = (keyValuePairs) => {
982
998
  const filters = [];
@@ -1003,29 +1019,27 @@ var getFiltersFromKeyValuePairsWithOperator = (keyValuePairsWithOperator) => {
1003
1019
  return filters;
1004
1020
  };
1005
1021
  var isSimpleValue = (value) => {
1006
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1007
- };
1008
- var formatFilter = (key, operator, value) => {
1009
- return `${key}${operator}${value}`;
1022
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
1010
1023
  };
1011
1024
  var formatFilterKey = (key, nestedKey) => {
1012
1025
  if (nestedKey) {
1013
- return `${key}[${JSON.stringify(nestedKey)}]`;
1026
+ return `${key}[${quote(nestedKey)}]`;
1014
1027
  }
1015
1028
  return key;
1016
1029
  };
1017
- var formatFilterValue = (value) => {
1018
- if (typeof value === "string") {
1019
- if (isStringEmpty(value)) {
1020
- throw new Error("Value cannot be empty");
1021
- }
1022
- return JSON.stringify(value);
1023
- }
1024
- return value.toString();
1025
- };
1026
1030
  var isStringEmpty = (value) => {
1027
1031
  return !value || value.toString().trim() === "";
1028
1032
  };
1033
+ function quote(input) {
1034
+ const result = JSON.stringify(input);
1035
+ if (typeof input !== "string") {
1036
+ return result;
1037
+ }
1038
+ if (result.includes("'")) {
1039
+ return result;
1040
+ }
1041
+ return `'${result.slice(1, -1).replace(/\\"/g, '"')}'`;
1042
+ }
1029
1043
 
1030
1044
  // src/lib/url.ts
1031
1045
  function toURLSearchParams(params) {
@@ -1044,9 +1058,9 @@ function urljoin(baseUrl, path, params) {
1044
1058
  }
1045
1059
  return url2.toString();
1046
1060
  }
1047
- function url(strings, ...values2) {
1061
+ function url(strings, ...values) {
1048
1062
  return strings.reduce(
1049
- (result, str, i) => result + encodeURIComponent(values2[i - 1] ?? "") + str
1063
+ (result, str, i) => result + encodeURIComponent(values[i - 1] ?? "") + str
1050
1064
  );
1051
1065
  }
1052
1066
 
@@ -1691,21 +1705,6 @@ function createApiClient({
1691
1705
  await authManager.getAuthValue({ requestedScope: "comments:read" })
1692
1706
  );
1693
1707
  }
1694
- async function getChannelsNotificationSettings(options) {
1695
- return httpClient.get(
1696
- url`/v2/c/channels-notification-settings`,
1697
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
1698
- void 0,
1699
- { signal: options?.signal }
1700
- );
1701
- }
1702
- async function updateChannelsNotificationSettings(settings) {
1703
- return httpClient.post(
1704
- url`/v2/c/channels-notification-settings`,
1705
- await authManager.getAuthValue({ requestedScope: "comments:read" }),
1706
- settings
1707
- );
1708
- }
1709
1708
  async function getUserThreads_experimental(options) {
1710
1709
  let query;
1711
1710
  if (options?.query) {
@@ -1771,9 +1770,7 @@ function createApiClient({
1771
1770
  // Room notifications
1772
1771
  markRoomInboxNotificationAsRead,
1773
1772
  updateNotificationSettings,
1774
- // Channel notification settings
1775
1773
  getNotificationSettings,
1776
- updateChannelsNotificationSettings,
1777
1774
  // Room text editor
1778
1775
  createTextMention,
1779
1776
  deleteTextMention,
@@ -1797,7 +1794,6 @@ function createApiClient({
1797
1794
  markInboxNotificationAsRead,
1798
1795
  deleteAllInboxNotifications,
1799
1796
  deleteInboxNotification,
1800
- getChannelsNotificationSettings,
1801
1797
  // User threads
1802
1798
  getUserThreads_experimental,
1803
1799
  getUserThreadsSince_experimental
@@ -1868,14 +1864,7 @@ var HttpClient = class {
1868
1864
  async #fetch(endpoint, authValue, options, params) {
1869
1865
  const response = await this.#rawFetch(endpoint, authValue, options, params);
1870
1866
  if (!response.ok) {
1871
- let error3;
1872
- try {
1873
- const errorBody = await response.json();
1874
- error3 = new HttpError(errorBody.message, response.status, errorBody);
1875
- } catch {
1876
- error3 = new HttpError(response.statusText, response.status);
1877
- }
1878
- throw error3;
1867
+ throw await HttpError.fromResponse(response);
1879
1868
  }
1880
1869
  let body;
1881
1870
  try {
@@ -2519,13 +2508,6 @@ var StopRetrying = class extends Error {
2519
2508
  super(reason);
2520
2509
  }
2521
2510
  };
2522
- var LiveblocksError = class extends Error {
2523
- /** @internal */
2524
- constructor(message, code) {
2525
- super(message);
2526
- this.code = code;
2527
- }
2528
- };
2529
2511
  function nextBackoffDelay(currentDelay, delays) {
2530
2512
  return delays.find((delay) => delay > currentDelay) ?? delays[delays.length - 1];
2531
2513
  }
@@ -2635,11 +2617,10 @@ var assign = (patch) => (ctx) => ctx.patch(patch);
2635
2617
  function createConnectionStateMachine(delegates, options) {
2636
2618
  const onMessage = makeBufferableEventSource();
2637
2619
  onMessage.pause();
2638
- const onLiveblocksError = makeEventSource();
2639
- function fireErrorEvent(errmsg, errcode) {
2620
+ const onConnectionError = makeEventSource();
2621
+ function fireErrorEvent(message, code) {
2640
2622
  return () => {
2641
- const err = new LiveblocksError(errmsg, errcode);
2642
- onLiveblocksError.notify(err);
2623
+ onConnectionError.notify({ message, code });
2643
2624
  };
2644
2625
  }
2645
2626
  const initialContext = {
@@ -2997,7 +2978,7 @@ function createConnectionStateMachine(delegates, options) {
2997
2978
  didConnect,
2998
2979
  didDisconnect,
2999
2980
  onMessage: onMessage.observable,
3000
- onLiveblocksError: onLiveblocksError.observable
2981
+ onConnectionError: onConnectionError.observable
3001
2982
  }
3002
2983
  };
3003
2984
  }
@@ -6261,8 +6242,85 @@ var ManagedOthers = class {
6261
6242
  }
6262
6243
  };
6263
6244
 
6245
+ // src/types/LiveblocksError.ts
6246
+ var LiveblocksError = class _LiveblocksError extends Error {
6247
+ context;
6248
+ constructor(message, context, cause) {
6249
+ super(message, { cause });
6250
+ this.context = context;
6251
+ this.name = "LiveblocksError";
6252
+ }
6253
+ /** Convenience accessor for error.context.roomId (if available) */
6254
+ get roomId() {
6255
+ return this.context.roomId;
6256
+ }
6257
+ /** @deprecated Prefer using `context.code` instead, to enable type narrowing */
6258
+ get code() {
6259
+ return this.context.code;
6260
+ }
6261
+ /**
6262
+ * Creates a LiveblocksError from a generic error, by attaching Liveblocks
6263
+ * contextual information like room ID, thread ID, etc.
6264
+ */
6265
+ static from(context, cause) {
6266
+ return new _LiveblocksError(
6267
+ defaultMessageFromContext(context),
6268
+ context,
6269
+ cause
6270
+ );
6271
+ }
6272
+ };
6273
+ function defaultMessageFromContext(context) {
6274
+ switch (context.type) {
6275
+ case "ROOM_CONNECTION_ERROR": {
6276
+ switch (context.code) {
6277
+ case 4001:
6278
+ return "Not allowed to connect to the room";
6279
+ case 4005:
6280
+ return "Room is already full";
6281
+ case 4006:
6282
+ return "Kicked out of the room, because the room ID changed";
6283
+ default:
6284
+ return "Could not connect to the room";
6285
+ }
6286
+ }
6287
+ case "CREATE_THREAD_ERROR":
6288
+ return "Could not create new thread";
6289
+ case "DELETE_THREAD_ERROR":
6290
+ return "Could not delete thread";
6291
+ case "EDIT_THREAD_METADATA_ERROR":
6292
+ return "Could not edit thread metadata";
6293
+ case "MARK_THREAD_AS_RESOLVED_ERROR":
6294
+ return "Could not mark thread as resolved";
6295
+ case "MARK_THREAD_AS_UNRESOLVED_ERROR":
6296
+ return "Could not mark thread as unresolved";
6297
+ case "CREATE_COMMENT_ERROR":
6298
+ return "Could not create new comment";
6299
+ case "EDIT_COMMENT_ERROR":
6300
+ return "Could not edit comment";
6301
+ case "DELETE_COMMENT_ERROR":
6302
+ return "Could not delete comment";
6303
+ case "ADD_REACTION_ERROR":
6304
+ return "Could not add reaction";
6305
+ case "REMOVE_REACTION_ERROR":
6306
+ return "Could not remove reaction";
6307
+ case "MARK_INBOX_NOTIFICATION_AS_READ_ERROR":
6308
+ return "Could not mark inbox notification as read";
6309
+ case "DELETE_INBOX_NOTIFICATION_ERROR":
6310
+ return "Could not delete inbox notification";
6311
+ case "MARK_ALL_INBOX_NOTIFICATIONS_AS_READ_ERROR":
6312
+ return "Could not mark all inbox notifications as read";
6313
+ case "DELETE_ALL_INBOX_NOTIFICATIONS_ERROR":
6314
+ return "Could not delete all inbox notifications";
6315
+ case "UPDATE_NOTIFICATION_SETTINGS_ERROR":
6316
+ return "Could not update notification settings";
6317
+ default:
6318
+ return assertNever(context, "Unhandled case");
6319
+ }
6320
+ }
6321
+
6264
6322
  // src/room.ts
6265
- var MAX_SOCKET_MESSAGE_SIZE = 1024 * 1024 - 1024;
6323
+ var MAX_SOCKET_MESSAGE_SIZE = 1024 * 1024 - 512;
6266
6324
  function makeIdFactory(connectionId) {
6267
6325
  let count = 0;
6268
6326
  return () => `${connectionId}:${count++}`;
@@ -6425,13 +6483,17 @@ function createRoom(options, config) {
6425
6483
  managedSocket.events.statusDidChange.subscribe(handleConnectionLossEvent);
6426
6484
  managedSocket.events.didConnect.subscribe(onDidConnect);
6427
6485
  managedSocket.events.didDisconnect.subscribe(onDidDisconnect);
6428
- managedSocket.events.onLiveblocksError.subscribe((err) => {
6429
- if (process.env.NODE_ENV !== "production") {
6430
- error2(
6431
- `Connection to websocket server closed. Reason: ${err.message} (code: ${err.code}).`
6432
- );
6486
+ managedSocket.events.onConnectionError.subscribe(({ message, code }) => {
6487
+ const type = "ROOM_CONNECTION_ERROR";
6488
+ const err = new LiveblocksError(message, { type, code, roomId });
6489
+ const didNotify = config.errorEventSource.notify(err);
6490
+ if (!didNotify) {
6491
+ if (process.env.NODE_ENV !== "production") {
6492
+ error2(
6493
+ `Connection to websocket server closed. Reason: ${message} (code: ${code}).`
6494
+ );
6495
+ }
6433
6496
  }
6434
- eventHub.error.notify(err);
6435
6497
  });
6436
6498
  const pool = {
6437
6499
  roomId: config.roomId,
@@ -6494,7 +6556,6 @@ function createRoom(options, config) {
6494
6556
  self: makeEventSource(),
6495
6557
  myPresence: makeEventSource(),
6496
6558
  others: makeEventSource(),
6497
- error: makeEventSource(),
6498
6559
  storageBatch: makeEventSource(),
6499
6560
  history: makeEventSource(),
6500
6561
  storageDidLoad: makeEventSource(),
@@ -6528,24 +6589,82 @@ function createRoom(options, config) {
6528
6589
  async function createTextVersion() {
6529
6590
  return httpClient.createTextVersion({ roomId });
6530
6591
  }
6592
+ function* chunkOps(msg) {
6593
+ const { ops, ...rest } = msg;
6594
+ if (ops.length < 2) {
6595
+ throw new Error("Cannot split ops into smaller chunks");
6596
+ }
6597
+ const mid = Math.floor(ops.length / 2);
6598
+ const firstHalf = ops.slice(0, mid);
6599
+ const secondHalf = ops.slice(mid);
6600
+ for (const halfOps of [firstHalf, secondHalf]) {
6601
+ const half = { ops: halfOps, ...rest };
6602
+ const text = JSON.stringify([half]);
6603
+ if (!isTooBigForWebSocket(text)) {
6604
+ yield text;
6605
+ } else {
6606
+ yield* chunkOps(half);
6607
+ }
6608
+ }
6609
+ }
6610
+ function* chunkMessages(messages) {
6611
+ if (messages.length < 2) {
6612
+ if (messages[0].type === 201 /* UPDATE_STORAGE */) {
6613
+ yield* chunkOps(messages[0]);
6614
+ return;
6615
+ } else {
6616
+ throw new Error(
6617
+ "Cannot split into chunks smaller than the allowed message size"
6618
+ );
6619
+ }
6620
+ }
6621
+ const mid = Math.floor(messages.length / 2);
6622
+ const firstHalf = messages.slice(0, mid);
6623
+ const secondHalf = messages.slice(mid);
6624
+ for (const half of [firstHalf, secondHalf]) {
6625
+ const text = JSON.stringify(half);
6626
+ if (!isTooBigForWebSocket(text)) {
6627
+ yield text;
6628
+ } else {
6629
+ yield* chunkMessages(half);
6630
+ }
6631
+ }
6632
+ }
6633
+ function isTooBigForWebSocket(text) {
6634
+ if (text.length * 4 < MAX_SOCKET_MESSAGE_SIZE) {
6635
+ return false;
6636
+ }
6637
+ return new TextEncoder().encode(text).length >= MAX_SOCKET_MESSAGE_SIZE;
6638
+ }
6531
6639
  function sendMessages(messages) {
6532
- const serializedPayload = JSON.stringify(messages);
6533
- const nonce = context.dynamicSessionInfoSig.get()?.nonce;
6534
- if (config.unstable_fallbackToHTTP && nonce) {
6535
- const size = new TextEncoder().encode(serializedPayload).length;
6536
- if (size > MAX_SOCKET_MESSAGE_SIZE) {
6640
+ const strategy = config.largeMessageStrategy ?? "default";
6641
+ const text = JSON.stringify(messages);
6642
+ if (!isTooBigForWebSocket(text)) {
6643
+ return managedSocket.send(text);
6644
+ }
6645
+ switch (strategy) {
6646
+ case "default": {
6647
+ error2("Message is too large for websockets, not sending. Configure largeMessageStrategy option to deal with this.");
6648
+ return;
6649
+ }
6650
+ case "split": {
6651
+ warn("Message is too large for websockets, splitting into smaller chunks");
6652
+ for (const chunk2 of chunkMessages(messages)) {
6653
+ managedSocket.send(chunk2);
6654
+ }
6655
+ return;
6656
+ }
6657
+ case "experimental-fallback-to-http": {
6658
+ warn("Message is too large for websockets, so sending over HTTP instead");
6659
+ const nonce = context.dynamicSessionInfoSig.get()?.nonce ?? raise("Session is not authorized to send message over HTTP");
6537
6660
  void httpClient.sendMessages({ roomId, nonce, messages }).then((resp) => {
6538
6661
  if (!resp.ok && resp.status === 403) {
6539
6662
  managedSocket.reconnect();
6540
6663
  }
6541
6664
  });
6542
- warn(
6543
- "Message was too large for websockets and sent over HTTP instead"
6544
- );
6545
6665
  return;
6546
6666
  }
6547
6667
  }
6548
- managedSocket.send(serializedPayload);
6549
6668
  }
6550
6669
  const self = DerivedSignal.from(
6551
6670
  context.staticSessionInfoSig,
@@ -7334,7 +7453,6 @@ ${Array.from(traces).join("\n\n")}`
7334
7453
  others: eventHub.others.observable,
7335
7454
  self: eventHub.self.observable,
7336
7455
  myPresence: eventHub.myPresence.observable,
7337
- error: eventHub.error.observable,
7338
7456
  /** @deprecated */
7339
7457
  storage: eventHub.storageBatch.observable,
7340
7458
  storageBatch: eventHub.storageBatch.observable,
@@ -7525,7 +7643,11 @@ ${Array.from(traces).join("\n\n")}`
7525
7643
  attachmentUrlsStore: httpClient.getOrCreateAttachmentUrlsStore(roomId)
7526
7644
  },
7527
7645
  id: config.roomId,
7528
- subscribe: makeClassicSubscribeFn(events),
7646
+ subscribe: makeClassicSubscribeFn(
7647
+ config.roomId,
7648
+ events,
7649
+ config.errorEventSource
7650
+ ),
7529
7651
  connect: () => managedSocket.connect(),
7530
7652
  reconnect: () => managedSocket.reconnect(),
7531
7653
  disconnect: () => managedSocket.disconnect(),
@@ -7594,7 +7716,7 @@ ${Array.from(traces).join("\n\n")}`
7594
7716
  { enumerable: false }
7595
7717
  );
7596
7718
  }
7597
- function makeClassicSubscribeFn(events) {
7719
+ function makeClassicSubscribeFn(roomId, events, errorEvents) {
7598
7720
  function subscribeToLiveStructureDeeply(node, callback) {
7599
7721
  return events.storageBatch.subscribe((updates) => {
7600
7722
  const relatedUpdates = updates.filter(
@@ -7634,8 +7756,13 @@ function makeClassicSubscribeFn(events) {
7634
7756
  return cb(others, internalEvent);
7635
7757
  });
7636
7758
  }
7637
- case "error":
7638
- return events.error.subscribe(callback);
7759
+ case "error": {
7760
+ return errorEvents.subscribe((err) => {
7761
+ if (err.roomId === roomId) {
7762
+ return callback(err);
7763
+ }
7764
+ });
7765
+ }
7639
7766
  case "status":
7640
7767
  return events.status.subscribe(callback);
7641
7768
  case "lost-connection":
@@ -7807,7 +7934,8 @@ function createClient(options) {
7807
7934
  },
7808
7935
  enableDebugLogging: clientOptions.enableDebugLogging,
7809
7936
  baseUrl,
7810
- unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP,
7937
+ errorEventSource: liveblocksErrorSource,
7938
+ largeMessageStrategy: clientOptions.largeMessageStrategy ?? (clientOptions.unstable_fallbackToHTTP ? "experimental-fallback-to-http" : void 0),
7811
7939
  unstable_streamData: !!clientOptions.unstable_streamData,
7812
7940
  roomHttpClient: httpClient,
7813
7941
  createSyncSource
@@ -7889,6 +8017,7 @@ function createClient(options) {
7889
8017
  }
7890
8018
  const syncStatusSources = [];
7891
8019
  const syncStatusSignal = new Signal("synchronized");
8020
+ const liveblocksErrorSource = makeEventSource();
7892
8021
  function getSyncStatus() {
7893
8022
  const status = syncStatusSignal.get();
7894
8023
  return status === "synchronizing" ? status : "synchronized";
@@ -7940,9 +8069,6 @@ function createClient(options) {
7940
8069
  markInboxNotificationAsRead: httpClient.markInboxNotificationAsRead,
7941
8070
  deleteAllInboxNotifications: httpClient.deleteAllInboxNotifications,
7942
8071
  deleteInboxNotification: httpClient.deleteInboxNotification,
7943
- // Public channel notification settings API
7944
- getChannelsNotificationSettings: httpClient.getChannelsNotificationSettings,
7945
- updateChannelsNotificationSettings: httpClient.updateChannelsNotificationSettings,
7946
8072
  // Advanced resolvers APIs
7947
8073
  resolvers: {
7948
8074
  invalidateUsers: invalidateResolvedUsers,
@@ -7951,6 +8077,7 @@ function createClient(options) {
7951
8077
  },
7952
8078
  getSyncStatus,
7953
8079
  events: {
8080
+ error: liveblocksErrorSource,
7954
8081
  syncStatus: syncStatusSignal
7955
8082
  },
7956
8083
  // Internal
@@ -7966,7 +8093,14 @@ function createClient(options) {
7966
8093
  httpClient,
7967
8094
  // Type-level helper only, it's effectively only an identity-function at runtime
7968
8095
  as: () => client,
7969
- createSyncSource
8096
+ createSyncSource,
8097
+ emitError: (context, cause) => {
8098
+ const error3 = LiveblocksError.from(context, cause);
8099
+ const didNotify = liveblocksErrorSource.notify(error3);
8100
+ if (!didNotify) {
8101
+ error2(error3.message);
8102
+ }
8103
+ }
7970
8104
  }
7971
8105
  },
7972
8106
  kInternal,
@@ -8131,9 +8265,9 @@ function escapeHtml(value) {
8131
8265
  var HtmlSafeString = class {
8132
8266
  #strings;
8133
8267
  #values;
8134
- constructor(strings, values2) {
8268
+ constructor(strings, values) {
8135
8269
  this.#strings = strings;
8136
- this.#values = values2;
8270
+ this.#values = values;
8137
8271
  }
8138
8272
  toString() {
8139
8273
  return this.#strings.reduce((result, str, i) => {
@@ -8141,8 +8275,8 @@ var HtmlSafeString = class {
8141
8275
  });
8142
8276
  }
8143
8277
  };
8144
- function html(strings, ...values2) {
8145
- return new HtmlSafeString(strings, values2);
8278
+ function html(strings, ...values) {
8279
+ return new HtmlSafeString(strings, values);
8146
8280
  }
8147
8281
  var markdownEscapables = {
8148
8282
  _: "\\_",
@@ -8187,9 +8321,9 @@ function escapeMarkdown(value) {
8187
8321
  var MarkdownSafeString = class {
8188
8322
  #strings;
8189
8323
  #values;
8190
- constructor(strings, values2) {
8324
+ constructor(strings, values) {
8191
8325
  this.#strings = strings;
8192
- this.#values = values2;
8326
+ this.#values = values;
8193
8327
  }
8194
8328
  toString() {
8195
8329
  return this.#strings.reduce((result, str, i) => {
@@ -8197,8 +8331,8 @@ var MarkdownSafeString = class {
8197
8331
  });
8198
8332
  }
8199
8333
  };
8200
- function markdown(strings, ...values2) {
8201
- return new MarkdownSafeString(strings, values2);
8334
+ function markdown(strings, ...values) {
8335
+ return new MarkdownSafeString(strings, values);
8202
8336
  }
8203
8337
  function toAbsoluteUrl(url2) {
8204
8338
  if (url2.startsWith("http://") || url2.startsWith("https://")) {
@@ -8802,6 +8936,7 @@ function makePoller(callback, intervalMs, options) {
8802
8936
  }
8803
8937
  doc?.addEventListener("visibilitychange", onVisibilityChange);
8804
8938
  win?.addEventListener("online", onVisibilityChange);
8939
+ win?.addEventListener("focus", pollNowIfStale);
8805
8940
  fsm.start();
8806
8941
  return {
8807
8942
  inc,
@@ -8923,11 +9058,6 @@ var SortedList = class _SortedList {
8923
9058
  }
8924
9059
  };
8925
9060
 
8926
- // src/protocol/ChannelsNotificationSettings.ts
8927
- function isChannelNotificationSettingEnabled(setting) {
8928
- return values(setting).every((enabled) => enabled === true);
8929
- }
8930
-
8931
9061
  // src/types/Others.ts
8932
9062
  var TextEditorType = /* @__PURE__ */ ((TextEditorType2) => {
8933
9063
  TextEditorType2["Lexical"] = "lexical";
@@ -8949,6 +9079,7 @@ export {
8949
9079
  LiveList,
8950
9080
  LiveMap,
8951
9081
  LiveObject,
9082
+ LiveblocksError,
8952
9083
  MutableSignal,
8953
9084
  NotificationsApiError,
8954
9085
  OpCode,
@@ -8982,14 +9113,12 @@ export {
8982
9113
  deprecate,
8983
9114
  deprecateIf,
8984
9115
  detectDupes,
8985
- entries,
8986
9116
  errorIf,
8987
9117
  freeze,
8988
9118
  generateCommentUrl,
8989
9119
  getMentionedIdsFromCommentBody,
8990
9120
  html,
8991
9121
  htmlSafe,
8992
- isChannelNotificationSettingEnabled,
8993
9122
  isChildCrdt,
8994
9123
  isCommentBodyLink,
8995
9124
  isCommentBodyMention,
@@ -9002,7 +9131,6 @@ export {
9002
9131
  isRootCrdt,
9003
9132
  isStartsWithOperator,
9004
9133
  kInternal,
9005
- keys,
9006
9134
  legacy_patchImmutableObject,
9007
9135
  lsonToJson,
9008
9136
  makeEventSource,
@@ -9023,7 +9151,6 @@ export {
9023
9151
  toAbsoluteUrl,
9024
9152
  toPlainLson,
9025
9153
  tryParseJson,
9026
- unstringify,
9027
9154
  url,
9028
9155
  urljoin,
9029
9156
  wait,