@liveblocks/core 1.4.8 → 1.5.0-subdoc

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.js 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 = "1.4.8";
9
+ var PKG_VERSION = "1.5.0-subdoc";
10
10
  var PKG_FORMAT = "cjs";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -585,8 +585,8 @@ function tryParseJson(rawMessage) {
585
585
  return void 0;
586
586
  }
587
587
  }
588
- function deepClone(items) {
589
- return JSON.parse(JSON.stringify(items));
588
+ function deepClone(value) {
589
+ return JSON.parse(JSON.stringify(value));
590
590
  }
591
591
  function b64decode(b64value) {
592
592
  try {
@@ -673,6 +673,9 @@ function shouldRetryWithoutReauth(code) {
673
673
  }
674
674
 
675
675
  // src/connection.ts
676
+ function isIdle(status) {
677
+ return status === "initial" || status === "disconnected";
678
+ }
676
679
  function newToLegacyStatus(status) {
677
680
  switch (status) {
678
681
  case "connecting":
@@ -701,6 +704,7 @@ function toNewConnectionStatus(machine) {
701
704
  case "@auth.backoff":
702
705
  case "@connecting.busy":
703
706
  case "@connecting.backoff":
707
+ case "@idle.zombie":
704
708
  return machine.context.successCount > 0 ? "reconnecting" : "connecting";
705
709
  case "@idle.failed":
706
710
  return "disconnected";
@@ -842,7 +846,7 @@ function createConnectionStateMachine(delegates, options) {
842
846
  socket: null,
843
847
  backoffDelay: RESET_DELAY
844
848
  };
845
- const machine = new FSM(initialContext).addState("@idle.initial").addState("@idle.failed").addState("@auth.busy").addState("@auth.backoff").addState("@connecting.busy").addState("@connecting.backoff").addState("@ok.connected").addState("@ok.awaiting-pong");
849
+ const machine = new FSM(initialContext).addState("@idle.initial").addState("@idle.failed").addState("@idle.zombie").addState("@auth.busy").addState("@auth.backoff").addState("@connecting.busy").addState("@connecting.backoff").addState("@ok.connected").addState("@ok.awaiting-pong");
846
850
  machine.addTransitions("*", {
847
851
  RECONNECT: {
848
852
  target: "@auth.backoff",
@@ -1052,11 +1056,20 @@ function createConnectionStateMachine(delegates, options) {
1052
1056
  _optionalChain([ctx, 'access', _14 => _14.socket, 'optionalAccess', _15 => _15.send, 'call', _16 => _16("ping")]);
1053
1057
  }
1054
1058
  };
1055
- machine.addTimedTransition("@ok.connected", HEARTBEAT_INTERVAL, sendHeartbeat).addTransitions("@ok.connected", {
1056
- NAVIGATOR_OFFLINE: sendHeartbeat,
1059
+ const maybeHeartbeat = () => {
1060
+ const doc = typeof document !== "undefined" ? document : void 0;
1061
+ const canZombie = _optionalChain([doc, 'optionalAccess', _17 => _17.visibilityState]) === "hidden" && delegates.canZombie();
1062
+ return canZombie ? "@idle.zombie" : sendHeartbeat;
1063
+ };
1064
+ machine.addTimedTransition("@ok.connected", HEARTBEAT_INTERVAL, maybeHeartbeat).addTransitions("@ok.connected", {
1065
+ NAVIGATOR_OFFLINE: maybeHeartbeat,
1057
1066
  // Don't take the browser's word for it when it says it's offline. Do a ping/pong to make sure.
1058
1067
  WINDOW_GOT_FOCUS: sendHeartbeat
1059
1068
  });
1069
+ machine.addTransitions("@idle.zombie", {
1070
+ WINDOW_GOT_FOCUS: "@connecting.backoff"
1071
+ // When in zombie state, the client will try to wake up automatically when the window regains focus
1072
+ });
1060
1073
  machine.onEnter("@ok.*", (ctx) => {
1061
1074
  ctx.patch({ successCount: ctx.successCount + 1 });
1062
1075
  const timerID = setTimeout(
@@ -1084,7 +1097,7 @@ function createConnectionStateMachine(delegates, options) {
1084
1097
  // socket, or not. So always check to see if the socket is still OPEN or
1085
1098
  // not. When still OPEN, don't transition.
1086
1099
  EXPLICIT_SOCKET_ERROR: (_, context) => {
1087
- if (_optionalChain([context, 'access', _17 => _17.socket, 'optionalAccess', _18 => _18.readyState]) === 1) {
1100
+ if (_optionalChain([context, 'access', _18 => _18.socket, 'optionalAccess', _19 => _19.readyState]) === 1) {
1088
1101
  return null;
1089
1102
  }
1090
1103
  return {
@@ -1136,17 +1149,17 @@ function createConnectionStateMachine(delegates, options) {
1136
1149
  machine.send({ type: "NAVIGATOR_ONLINE" });
1137
1150
  }
1138
1151
  function onVisibilityChange() {
1139
- if (_optionalChain([doc, 'optionalAccess', _19 => _19.visibilityState]) === "visible") {
1152
+ if (_optionalChain([doc, 'optionalAccess', _20 => _20.visibilityState]) === "visible") {
1140
1153
  machine.send({ type: "WINDOW_GOT_FOCUS" });
1141
1154
  }
1142
1155
  }
1143
- _optionalChain([win, 'optionalAccess', _20 => _20.addEventListener, 'call', _21 => _21("online", onNetworkBackOnline)]);
1144
- _optionalChain([win, 'optionalAccess', _22 => _22.addEventListener, 'call', _23 => _23("offline", onNetworkOffline)]);
1145
- _optionalChain([root, 'optionalAccess', _24 => _24.addEventListener, 'call', _25 => _25("visibilitychange", onVisibilityChange)]);
1156
+ _optionalChain([win, 'optionalAccess', _21 => _21.addEventListener, 'call', _22 => _22("online", onNetworkBackOnline)]);
1157
+ _optionalChain([win, 'optionalAccess', _23 => _23.addEventListener, 'call', _24 => _24("offline", onNetworkOffline)]);
1158
+ _optionalChain([root, 'optionalAccess', _25 => _25.addEventListener, 'call', _26 => _26("visibilitychange", onVisibilityChange)]);
1146
1159
  return () => {
1147
- _optionalChain([root, 'optionalAccess', _26 => _26.removeEventListener, 'call', _27 => _27("visibilitychange", onVisibilityChange)]);
1148
- _optionalChain([win, 'optionalAccess', _28 => _28.removeEventListener, 'call', _29 => _29("online", onNetworkBackOnline)]);
1149
- _optionalChain([win, 'optionalAccess', _30 => _30.removeEventListener, 'call', _31 => _31("offline", onNetworkOffline)]);
1160
+ _optionalChain([root, 'optionalAccess', _27 => _27.removeEventListener, 'call', _28 => _28("visibilitychange", onVisibilityChange)]);
1161
+ _optionalChain([win, 'optionalAccess', _29 => _29.removeEventListener, 'call', _30 => _30("online", onNetworkBackOnline)]);
1162
+ _optionalChain([win, 'optionalAccess', _31 => _31.removeEventListener, 'call', _32 => _32("offline", onNetworkOffline)]);
1150
1163
  teardownSocket(ctx.socket);
1151
1164
  };
1152
1165
  });
@@ -1235,7 +1248,7 @@ var ManagedSocket = class {
1235
1248
  * message if this is somehow impossible.
1236
1249
  */
1237
1250
  send(data) {
1238
- const socket = _optionalChain([this, 'access', _32 => _32.machine, 'access', _33 => _33.context, 'optionalAccess', _34 => _34.socket]);
1251
+ const socket = _optionalChain([this, 'access', _33 => _33.machine, 'access', _34 => _34.context, 'optionalAccess', _35 => _35.socket]);
1239
1252
  if (socket === null) {
1240
1253
  warn("Cannot send: not connected yet", data);
1241
1254
  } else if (socket.readyState !== 1) {
@@ -1287,6 +1300,12 @@ function createAuthManager(authOptions) {
1287
1300
  const tokens = [];
1288
1301
  const expiryTimes = [];
1289
1302
  const requestPromises = /* @__PURE__ */ new Map();
1303
+ function reset() {
1304
+ seenTokens.clear();
1305
+ tokens.length = 0;
1306
+ expiryTimes.length = 0;
1307
+ requestPromises.clear();
1308
+ }
1290
1309
  function hasCorrespondingScopes(requestedScope, scopes) {
1291
1310
  if (requestedScope === "comments:read") {
1292
1311
  return scopes.includes("comments:read" /* CommentsRead */) || scopes.includes("comments:write" /* CommentsWrite */) || scopes.includes("room:read" /* Read */) || scopes.includes("room:write" /* Write */);
@@ -1318,11 +1337,11 @@ function createAuthManager(authOptions) {
1318
1337
  return void 0;
1319
1338
  }
1320
1339
  async function makeAuthRequest(roomId) {
1321
- const fetcher = _nullishCoalesce(_optionalChain([authOptions, 'access', _35 => _35.polyfills, 'optionalAccess', _36 => _36.fetch]), () => ( (typeof window === "undefined" ? void 0 : window.fetch)));
1340
+ const fetcher = _nullishCoalesce(_optionalChain([authOptions, 'access', _36 => _36.polyfills, 'optionalAccess', _37 => _37.fetch]), () => ( (typeof window === "undefined" ? void 0 : window.fetch)));
1322
1341
  if (authentication.type === "private") {
1323
1342
  if (fetcher === void 0) {
1324
1343
  throw new StopRetrying(
1325
- "To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
1344
+ "To use Liveblocks client in a non-DOM environment with a url as auth endpoint, you need to provide a fetch polyfill."
1326
1345
  );
1327
1346
  }
1328
1347
  const response = await fetchAuthEndpoint(fetcher, authentication.url, {
@@ -1386,6 +1405,7 @@ function createAuthManager(authOptions) {
1386
1405
  }
1387
1406
  }
1388
1407
  return {
1408
+ reset,
1389
1409
  getAuthValue
1390
1410
  };
1391
1411
  }
@@ -1467,6 +1487,9 @@ async function fetchAuthEndpoint(fetch2, endpoint, body) {
1467
1487
  return { token };
1468
1488
  }
1469
1489
 
1490
+ // src/constants.ts
1491
+ var DEFAULT_BASE_URL = "https://api.liveblocks.io";
1492
+
1470
1493
  // src/devtools/bridge.ts
1471
1494
  var _bridgeActive = false;
1472
1495
  function activateBridge(allowed) {
@@ -1480,7 +1503,7 @@ function sendToPanel(message, options) {
1480
1503
  ...message,
1481
1504
  source: "liveblocks-devtools-client"
1482
1505
  };
1483
- if (!(_optionalChain([options, 'optionalAccess', _37 => _37.force]) || _bridgeActive)) {
1506
+ if (!(_optionalChain([options, 'optionalAccess', _38 => _38.force]) || _bridgeActive)) {
1484
1507
  return;
1485
1508
  }
1486
1509
  window.postMessage(fullMsg, "*");
@@ -1488,7 +1511,7 @@ function sendToPanel(message, options) {
1488
1511
  var eventSource = makeEventSource();
1489
1512
  if (process.env.NODE_ENV !== "production" && typeof window !== "undefined") {
1490
1513
  window.addEventListener("message", (event) => {
1491
- if (event.source === window && _optionalChain([event, 'access', _38 => _38.data, 'optionalAccess', _39 => _39.source]) === "liveblocks-devtools-panel") {
1514
+ if (event.source === window && _optionalChain([event, 'access', _39 => _39.data, 'optionalAccess', _40 => _40.source]) === "liveblocks-devtools-panel") {
1492
1515
  eventSource.notify(event.data);
1493
1516
  } else {
1494
1517
  }
@@ -1624,7 +1647,7 @@ function fullSync(room) {
1624
1647
  msg: "room::sync::full",
1625
1648
  roomId: room.id,
1626
1649
  status: room.getStatus(),
1627
- storage: _nullishCoalesce(_optionalChain([root, 'optionalAccess', _40 => _40.toTreeNode, 'call', _41 => _41("root"), 'access', _42 => _42.payload]), () => ( null)),
1650
+ storage: _nullishCoalesce(_optionalChain([root, 'optionalAccess', _41 => _41.toTreeNode, 'call', _42 => _42("root"), 'access', _43 => _43.payload]), () => ( null)),
1628
1651
  me,
1629
1652
  others
1630
1653
  });
@@ -1718,7 +1741,7 @@ function getAuthBearerHeaderFromAuthValue(authValue) {
1718
1741
  return authValue.token.raw;
1719
1742
  }
1720
1743
  }
1721
- function createCommentsApi(roomId, getAuthValue, { serverEndpoint }) {
1744
+ function createCommentsApi(roomId, getAuthValue, config) {
1722
1745
  async function fetchJson(endpoint, options) {
1723
1746
  const response = await fetchApi(roomId, endpoint, options);
1724
1747
  if (!response.ok) {
@@ -1745,13 +1768,14 @@ function createCommentsApi(roomId, getAuthValue, { serverEndpoint }) {
1745
1768
  }
1746
1769
  async function fetchApi(roomId2, endpoint, options) {
1747
1770
  const authValue = await getAuthValue();
1748
- const url = `${serverEndpoint}/c/rooms/${encodeURIComponent(
1749
- roomId2
1750
- )}${endpoint}`;
1771
+ const url = new URL(
1772
+ `/v2/c/rooms/${encodeURIComponent(roomId2)}${endpoint}`,
1773
+ config.baseUrl
1774
+ ).toString();
1751
1775
  return await fetch(url, {
1752
1776
  ...options,
1753
1777
  headers: {
1754
- ..._optionalChain([options, 'optionalAccess', _43 => _43.headers]),
1778
+ ..._optionalChain([options, 'optionalAccess', _44 => _44.headers]),
1755
1779
  Authorization: `Bearer ${getAuthBearerHeaderFromAuthValue(authValue)}`
1756
1780
  }
1757
1781
  });
@@ -2264,7 +2288,7 @@ var LiveRegister = class _LiveRegister extends AbstractCrdt {
2264
2288
  return [
2265
2289
  {
2266
2290
  type: 8 /* CREATE_REGISTER */,
2267
- opId: _optionalChain([pool, 'optionalAccess', _44 => _44.generateOpId, 'call', _45 => _45()]),
2291
+ opId: _optionalChain([pool, 'optionalAccess', _45 => _45.generateOpId, 'call', _46 => _46()]),
2268
2292
  id: this._id,
2269
2293
  parentId,
2270
2294
  parentKey,
@@ -2309,6 +2333,9 @@ var LiveRegister = class _LiveRegister extends AbstractCrdt {
2309
2333
  _toImmutable() {
2310
2334
  return this._data;
2311
2335
  }
2336
+ clone() {
2337
+ return deepClone(this.data);
2338
+ }
2312
2339
  };
2313
2340
 
2314
2341
  // src/crdts/LiveList.ts
@@ -2363,7 +2390,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2363
2390
  const ops = [];
2364
2391
  const op = {
2365
2392
  id: this._id,
2366
- opId: _optionalChain([pool, 'optionalAccess', _46 => _46.generateOpId, 'call', _47 => _47()]),
2393
+ opId: _optionalChain([pool, 'optionalAccess', _47 => _47.generateOpId, 'call', _48 => _48()]),
2367
2394
  type: 2 /* CREATE_LIST */,
2368
2395
  parentId,
2369
2396
  parentKey
@@ -2640,7 +2667,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2640
2667
  _applyInsertUndoRedo(op) {
2641
2668
  const { id, parentKey: key } = op;
2642
2669
  const child = creationOpToLiveNode(op);
2643
- if (_optionalChain([this, 'access', _48 => _48._pool, 'optionalAccess', _49 => _49.getNode, 'call', _50 => _50(id)]) !== void 0) {
2670
+ if (_optionalChain([this, 'access', _49 => _49._pool, 'optionalAccess', _50 => _50.getNode, 'call', _51 => _51(id)]) !== void 0) {
2644
2671
  return { modified: false };
2645
2672
  }
2646
2673
  child._attach(id, nn(this._pool));
@@ -2648,8 +2675,8 @@ var LiveList = class _LiveList extends AbstractCrdt {
2648
2675
  const existingItemIndex = this._indexOfPosition(key);
2649
2676
  let newKey = key;
2650
2677
  if (existingItemIndex !== -1) {
2651
- const before2 = _optionalChain([this, 'access', _51 => _51._items, 'access', _52 => _52[existingItemIndex], 'optionalAccess', _53 => _53._parentPos]);
2652
- const after2 = _optionalChain([this, 'access', _54 => _54._items, 'access', _55 => _55[existingItemIndex + 1], 'optionalAccess', _56 => _56._parentPos]);
2678
+ const before2 = _optionalChain([this, 'access', _52 => _52._items, 'access', _53 => _53[existingItemIndex], 'optionalAccess', _54 => _54._parentPos]);
2679
+ const after2 = _optionalChain([this, 'access', _55 => _55._items, 'access', _56 => _56[existingItemIndex + 1], 'optionalAccess', _57 => _57._parentPos]);
2653
2680
  newKey = makePosition(before2, after2);
2654
2681
  child._setParentLink(this, newKey);
2655
2682
  }
@@ -2664,7 +2691,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2664
2691
  _applySetUndoRedo(op) {
2665
2692
  const { id, parentKey: key } = op;
2666
2693
  const child = creationOpToLiveNode(op);
2667
- if (_optionalChain([this, 'access', _57 => _57._pool, 'optionalAccess', _58 => _58.getNode, 'call', _59 => _59(id)]) !== void 0) {
2694
+ if (_optionalChain([this, 'access', _58 => _58._pool, 'optionalAccess', _59 => _59.getNode, 'call', _60 => _60(id)]) !== void 0) {
2668
2695
  return { modified: false };
2669
2696
  }
2670
2697
  this._unacknowledgedSets.set(key, nn(op.opId));
@@ -2786,7 +2813,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2786
2813
  } else {
2787
2814
  this._items[existingItemIndex]._setParentLink(
2788
2815
  this,
2789
- makePosition(newKey, _optionalChain([this, 'access', _60 => _60._items, 'access', _61 => _61[existingItemIndex + 1], 'optionalAccess', _62 => _62._parentPos]))
2816
+ makePosition(newKey, _optionalChain([this, 'access', _61 => _61._items, 'access', _62 => _62[existingItemIndex + 1], 'optionalAccess', _63 => _63._parentPos]))
2790
2817
  );
2791
2818
  const previousIndex = this._items.indexOf(child);
2792
2819
  child._setParentLink(this, newKey);
@@ -2812,7 +2839,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2812
2839
  if (existingItemIndex !== -1) {
2813
2840
  this._items[existingItemIndex]._setParentLink(
2814
2841
  this,
2815
- makePosition(newKey, _optionalChain([this, 'access', _63 => _63._items, 'access', _64 => _64[existingItemIndex + 1], 'optionalAccess', _65 => _65._parentPos]))
2842
+ makePosition(newKey, _optionalChain([this, 'access', _64 => _64._items, 'access', _65 => _65[existingItemIndex + 1], 'optionalAccess', _66 => _66._parentPos]))
2816
2843
  );
2817
2844
  }
2818
2845
  child._setParentLink(this, newKey);
@@ -2831,7 +2858,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2831
2858
  if (existingItemIndex !== -1) {
2832
2859
  this._items[existingItemIndex]._setParentLink(
2833
2860
  this,
2834
- makePosition(newKey, _optionalChain([this, 'access', _66 => _66._items, 'access', _67 => _67[existingItemIndex + 1], 'optionalAccess', _68 => _68._parentPos]))
2861
+ makePosition(newKey, _optionalChain([this, 'access', _67 => _67._items, 'access', _68 => _68[existingItemIndex + 1], 'optionalAccess', _69 => _69._parentPos]))
2835
2862
  );
2836
2863
  }
2837
2864
  child._setParentLink(this, newKey);
@@ -2859,7 +2886,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2859
2886
  if (existingItemIndex !== -1) {
2860
2887
  this._items[existingItemIndex]._setParentLink(
2861
2888
  this,
2862
- makePosition(newKey, _optionalChain([this, 'access', _69 => _69._items, 'access', _70 => _70[existingItemIndex + 1], 'optionalAccess', _71 => _71._parentPos]))
2889
+ makePosition(newKey, _optionalChain([this, 'access', _70 => _70._items, 'access', _71 => _71[existingItemIndex + 1], 'optionalAccess', _72 => _72._parentPos]))
2863
2890
  );
2864
2891
  }
2865
2892
  child._setParentLink(this, newKey);
@@ -2917,7 +2944,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2917
2944
  * @param element The element to add to the end of the LiveList.
2918
2945
  */
2919
2946
  push(element) {
2920
- _optionalChain([this, 'access', _72 => _72._pool, 'optionalAccess', _73 => _73.assertStorageIsWritable, 'call', _74 => _74()]);
2947
+ _optionalChain([this, 'access', _73 => _73._pool, 'optionalAccess', _74 => _74.assertStorageIsWritable, 'call', _75 => _75()]);
2921
2948
  return this.insert(element, this.length);
2922
2949
  }
2923
2950
  /**
@@ -2926,7 +2953,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2926
2953
  * @param index The index at which you want to insert the element.
2927
2954
  */
2928
2955
  insert(element, index) {
2929
- _optionalChain([this, 'access', _75 => _75._pool, 'optionalAccess', _76 => _76.assertStorageIsWritable, 'call', _77 => _77()]);
2956
+ _optionalChain([this, 'access', _76 => _76._pool, 'optionalAccess', _77 => _77.assertStorageIsWritable, 'call', _78 => _78()]);
2930
2957
  if (index < 0 || index > this._items.length) {
2931
2958
  throw new Error(
2932
2959
  `Cannot insert list item at index "${index}". index should be between 0 and ${this._items.length}`
@@ -2956,7 +2983,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
2956
2983
  * @param targetIndex The index where the element should be after moving.
2957
2984
  */
2958
2985
  move(index, targetIndex) {
2959
- _optionalChain([this, 'access', _78 => _78._pool, 'optionalAccess', _79 => _79.assertStorageIsWritable, 'call', _80 => _80()]);
2986
+ _optionalChain([this, 'access', _79 => _79._pool, 'optionalAccess', _80 => _80.assertStorageIsWritable, 'call', _81 => _81()]);
2960
2987
  if (targetIndex < 0) {
2961
2988
  throw new Error("targetIndex cannot be less than 0");
2962
2989
  }
@@ -3014,7 +3041,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
3014
3041
  * @param index The index of the element to delete
3015
3042
  */
3016
3043
  delete(index) {
3017
- _optionalChain([this, 'access', _81 => _81._pool, 'optionalAccess', _82 => _82.assertStorageIsWritable, 'call', _83 => _83()]);
3044
+ _optionalChain([this, 'access', _82 => _82._pool, 'optionalAccess', _83 => _83.assertStorageIsWritable, 'call', _84 => _84()]);
3018
3045
  if (index < 0 || index >= this._items.length) {
3019
3046
  throw new Error(
3020
3047
  `Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
@@ -3047,7 +3074,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
3047
3074
  }
3048
3075
  }
3049
3076
  clear() {
3050
- _optionalChain([this, 'access', _84 => _84._pool, 'optionalAccess', _85 => _85.assertStorageIsWritable, 'call', _86 => _86()]);
3077
+ _optionalChain([this, 'access', _85 => _85._pool, 'optionalAccess', _86 => _86.assertStorageIsWritable, 'call', _87 => _87()]);
3051
3078
  if (this._pool) {
3052
3079
  const ops = [];
3053
3080
  const reverseOps = [];
@@ -3081,7 +3108,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
3081
3108
  }
3082
3109
  }
3083
3110
  set(index, item) {
3084
- _optionalChain([this, 'access', _87 => _87._pool, 'optionalAccess', _88 => _88.assertStorageIsWritable, 'call', _89 => _89()]);
3111
+ _optionalChain([this, 'access', _88 => _88._pool, 'optionalAccess', _89 => _89.assertStorageIsWritable, 'call', _90 => _90()]);
3085
3112
  if (index < 0 || index >= this._items.length) {
3086
3113
  throw new Error(
3087
3114
  `Cannot set list item at index "${index}". index should be between 0 and ${this._items.length - 1}`
@@ -3229,7 +3256,7 @@ var LiveList = class _LiveList extends AbstractCrdt {
3229
3256
  _shiftItemPosition(index, key) {
3230
3257
  const shiftedPosition = makePosition(
3231
3258
  key,
3232
- this._items.length > index + 1 ? _optionalChain([this, 'access', _90 => _90._items, 'access', _91 => _91[index + 1], 'optionalAccess', _92 => _92._parentPos]) : void 0
3259
+ this._items.length > index + 1 ? _optionalChain([this, 'access', _91 => _91._items, 'access', _92 => _92[index + 1], 'optionalAccess', _93 => _93._parentPos]) : void 0
3233
3260
  );
3234
3261
  this._items[index]._setParentLink(this, shiftedPosition);
3235
3262
  }
@@ -3252,6 +3279,9 @@ var LiveList = class _LiveList extends AbstractCrdt {
3252
3279
  const result = this._items.map((node) => node.toImmutable());
3253
3280
  return process.env.NODE_ENV === "production" ? result : Object.freeze(result);
3254
3281
  }
3282
+ clone() {
3283
+ return new _LiveList(this._items.map((item) => item.clone()));
3284
+ }
3255
3285
  };
3256
3286
  var LiveListIterator = class {
3257
3287
  constructor(items) {
@@ -3335,10 +3365,10 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
3335
3365
  this.unacknowledgedSet = /* @__PURE__ */ new Map();
3336
3366
  if (entries2) {
3337
3367
  const mappedEntries = [];
3338
- for (const entry of entries2) {
3339
- const value = lsonToLiveNode(entry[1]);
3340
- value._setParentLink(this, entry[0]);
3341
- mappedEntries.push([entry[0], value]);
3368
+ for (const [key, value] of entries2) {
3369
+ const node = lsonToLiveNode(value);
3370
+ node._setParentLink(this, key);
3371
+ mappedEntries.push([key, node]);
3342
3372
  }
3343
3373
  this._map = new Map(mappedEntries);
3344
3374
  } else {
@@ -3355,7 +3385,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
3355
3385
  const ops = [];
3356
3386
  const op = {
3357
3387
  id: this._id,
3358
- opId: _optionalChain([pool, 'optionalAccess', _93 => _93.generateOpId, 'call', _94 => _94()]),
3388
+ opId: _optionalChain([pool, 'optionalAccess', _94 => _94.generateOpId, 'call', _95 => _95()]),
3359
3389
  type: 7 /* CREATE_MAP */,
3360
3390
  parentId,
3361
3391
  parentKey
@@ -3502,7 +3532,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
3502
3532
  * @param value The value of the element to add. Should be serializable to JSON.
3503
3533
  */
3504
3534
  set(key, value) {
3505
- _optionalChain([this, 'access', _95 => _95._pool, 'optionalAccess', _96 => _96.assertStorageIsWritable, 'call', _97 => _97()]);
3535
+ _optionalChain([this, 'access', _96 => _96._pool, 'optionalAccess', _97 => _97.assertStorageIsWritable, 'call', _98 => _98()]);
3506
3536
  const oldValue = this._map.get(key);
3507
3537
  if (oldValue) {
3508
3538
  oldValue._detach();
@@ -3548,7 +3578,7 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
3548
3578
  * @returns true if an element existed and has been removed, or false if the element does not exist.
3549
3579
  */
3550
3580
  delete(key) {
3551
- _optionalChain([this, 'access', _98 => _98._pool, 'optionalAccess', _99 => _99.assertStorageIsWritable, 'call', _100 => _100()]);
3581
+ _optionalChain([this, 'access', _99 => _99._pool, 'optionalAccess', _100 => _100.assertStorageIsWritable, 'call', _101 => _101()]);
3552
3582
  const item = this._map.get(key);
3553
3583
  if (item === void 0) {
3554
3584
  return false;
@@ -3669,6 +3699,11 @@ var LiveMap = class _LiveMap extends AbstractCrdt {
3669
3699
  }
3670
3700
  return freeze(result);
3671
3701
  }
3702
+ clone() {
3703
+ return new _LiveMap(
3704
+ Array.from(this._map).map(([key, node]) => [key, node.clone()])
3705
+ );
3706
+ }
3672
3707
  };
3673
3708
 
3674
3709
  // src/crdts/LiveObject.ts
@@ -3722,7 +3757,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
3722
3757
  if (this._id === void 0) {
3723
3758
  throw new Error("Cannot serialize item is not attached");
3724
3759
  }
3725
- const opId = _optionalChain([pool, 'optionalAccess', _101 => _101.generateOpId, 'call', _102 => _102()]);
3760
+ const opId = _optionalChain([pool, 'optionalAccess', _102 => _102.generateOpId, 'call', _103 => _103()]);
3726
3761
  const ops = [];
3727
3762
  const op = parentId !== void 0 && parentKey !== void 0 ? {
3728
3763
  type: 4 /* CREATE_OBJECT */,
@@ -4003,7 +4038,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
4003
4038
  * @param value The value of the property to add
4004
4039
  */
4005
4040
  set(key, value) {
4006
- _optionalChain([this, 'access', _103 => _103._pool, 'optionalAccess', _104 => _104.assertStorageIsWritable, 'call', _105 => _105()]);
4041
+ _optionalChain([this, 'access', _104 => _104._pool, 'optionalAccess', _105 => _105.assertStorageIsWritable, 'call', _106 => _106()]);
4007
4042
  this.update({ [key]: value });
4008
4043
  }
4009
4044
  /**
@@ -4018,7 +4053,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
4018
4053
  * @param key The key of the property to delete
4019
4054
  */
4020
4055
  delete(key) {
4021
- _optionalChain([this, 'access', _106 => _106._pool, 'optionalAccess', _107 => _107.assertStorageIsWritable, 'call', _108 => _108()]);
4056
+ _optionalChain([this, 'access', _107 => _107._pool, 'optionalAccess', _108 => _108.assertStorageIsWritable, 'call', _109 => _109()]);
4022
4057
  const keyAsString = key;
4023
4058
  const oldValue = this._map.get(keyAsString);
4024
4059
  if (oldValue === void 0) {
@@ -4071,7 +4106,7 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
4071
4106
  * @param patch The object used to overrides properties
4072
4107
  */
4073
4108
  update(patch) {
4074
- _optionalChain([this, 'access', _109 => _109._pool, 'optionalAccess', _110 => _110.assertStorageIsWritable, 'call', _111 => _111()]);
4109
+ _optionalChain([this, 'access', _110 => _110._pool, 'optionalAccess', _111 => _111.assertStorageIsWritable, 'call', _112 => _112()]);
4075
4110
  if (this._pool === void 0 || this._id === void 0) {
4076
4111
  for (const key in patch) {
4077
4112
  const newValue = patch[key];
@@ -4179,6 +4214,16 @@ var LiveObject = class _LiveObject extends AbstractCrdt {
4179
4214
  }
4180
4215
  return process.env.NODE_ENV === "production" ? result : Object.freeze(result);
4181
4216
  }
4217
+ clone() {
4218
+ return new _LiveObject(
4219
+ Object.fromEntries(
4220
+ Array.from(this._map).map(([key, value]) => [
4221
+ key,
4222
+ isLiveStructure(value) ? value.clone() : deepClone(value)
4223
+ ])
4224
+ )
4225
+ );
4226
+ }
4182
4227
  };
4183
4228
 
4184
4229
  // src/crdts/liveblocks-helpers.ts
@@ -4264,6 +4309,9 @@ function isLiveObject(value) {
4264
4309
  function isLiveRegister(value) {
4265
4310
  return value instanceof LiveRegister;
4266
4311
  }
4312
+ function cloneLson(value) {
4313
+ return value === void 0 ? void 0 : isLiveStructure(value) ? value.clone() : deepClone(value);
4314
+ }
4267
4315
  function liveNodeToLson(obj) {
4268
4316
  if (obj instanceof LiveRegister) {
4269
4317
  return obj.data;
@@ -4698,10 +4746,40 @@ function userToTreeNode(key, user) {
4698
4746
  payload: user
4699
4747
  };
4700
4748
  }
4749
+ function installBackgroundTabSpy() {
4750
+ const doc = typeof document !== "undefined" ? document : void 0;
4751
+ const inBackgroundSince = { current: null };
4752
+ function onVisibilityChange() {
4753
+ if (_optionalChain([doc, 'optionalAccess', _113 => _113.visibilityState]) === "hidden") {
4754
+ inBackgroundSince.current = _nullishCoalesce(inBackgroundSince.current, () => ( Date.now()));
4755
+ } else {
4756
+ inBackgroundSince.current = null;
4757
+ }
4758
+ }
4759
+ _optionalChain([doc, 'optionalAccess', _114 => _114.addEventListener, 'call', _115 => _115("visibilitychange", onVisibilityChange)]);
4760
+ const unsub = () => {
4761
+ _optionalChain([doc, 'optionalAccess', _116 => _116.removeEventListener, 'call', _117 => _117("visibilitychange", onVisibilityChange)]);
4762
+ };
4763
+ return [inBackgroundSince, unsub];
4764
+ }
4701
4765
  function createRoom(options, config) {
4702
4766
  const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
4703
4767
  const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
4704
- const delegates = config.delegates;
4768
+ const [inBackgroundSince, uninstallBgTabSpy] = installBackgroundTabSpy();
4769
+ const delegates = {
4770
+ ...config.delegates,
4771
+ // A connection is allowed to go into "zombie state" only if all of the
4772
+ // following conditions apply:
4773
+ //
4774
+ // - The `backgroundKeepAliveTimeout` client option is configured
4775
+ // - The browser window has been in the background for at least
4776
+ // `backgroundKeepAliveTimeout` milliseconds
4777
+ // - There are no pending changes
4778
+ //
4779
+ canZombie() {
4780
+ return config.backgroundKeepAliveTimeout !== void 0 && inBackgroundSince.current !== null && Date.now() > inBackgroundSince.current + config.backgroundKeepAliveTimeout && getStorageStatus() !== "synchronizing";
4781
+ }
4782
+ };
4705
4783
  const managedSocket = new ManagedSocket(
4706
4784
  delegates,
4707
4785
  config.enableDebugLogging
@@ -4764,7 +4842,6 @@ function createRoom(options, config) {
4764
4842
  }
4765
4843
  batchUpdates(() => {
4766
4844
  eventHub.status.notify(newStatus);
4767
- eventHub.connection.notify(newToLegacyStatus(newStatus));
4768
4845
  notifySelfChanged(doNotBatchUpdates);
4769
4846
  });
4770
4847
  }
@@ -4870,7 +4947,7 @@ function createRoom(options, config) {
4870
4947
  }
4871
4948
  },
4872
4949
  assertStorageIsWritable: () => {
4873
- const scopes = _optionalChain([context, 'access', _112 => _112.dynamicSessionInfo, 'access', _113 => _113.current, 'optionalAccess', _114 => _114.scopes]);
4950
+ const scopes = _optionalChain([context, 'access', _118 => _118.dynamicSessionInfo, 'access', _119 => _119.current, 'optionalAccess', _120 => _120.scopes]);
4874
4951
  if (scopes === void 0) {
4875
4952
  return;
4876
4953
  }
@@ -4901,15 +4978,13 @@ function createRoom(options, config) {
4901
4978
  comments: makeEventSource()
4902
4979
  };
4903
4980
  async function httpSend(authTokenOrPublicApiKey, roomId, nonce, messages) {
4904
- const baseUrl = new URL(config.liveblocksServer);
4905
- baseUrl.protocol = "https";
4906
4981
  const url = new URL(
4907
4982
  `/v2/c/rooms/${encodeURIComponent(roomId)}/send-message`,
4908
- baseUrl
4909
- );
4910
- const fetcher = _optionalChain([config, 'access', _115 => _115.polyfills, 'optionalAccess', _116 => _116.fetch]) || /* istanbul ignore next */
4983
+ config.baseUrl
4984
+ ).toString();
4985
+ const fetcher = _optionalChain([config, 'access', _121 => _121.polyfills, 'optionalAccess', _122 => _122.fetch]) || /* istanbul ignore next */
4911
4986
  fetch;
4912
- return fetcher(url.toString(), {
4987
+ return fetcher(url, {
4913
4988
  method: "POST",
4914
4989
  headers: {
4915
4990
  "Content-Type": "application/json",
@@ -4920,7 +4995,7 @@ function createRoom(options, config) {
4920
4995
  }
4921
4996
  function sendMessages(messages) {
4922
4997
  const serializedPayload = JSON.stringify(messages);
4923
- const nonce = _optionalChain([context, 'access', _117 => _117.dynamicSessionInfo, 'access', _118 => _118.current, 'optionalAccess', _119 => _119.nonce]);
4998
+ const nonce = _optionalChain([context, 'access', _123 => _123.dynamicSessionInfo, 'access', _124 => _124.current, 'optionalAccess', _125 => _125.nonce]);
4924
4999
  if (config.unstable_fallbackToHTTP && managedSocket.authValue && nonce) {
4925
5000
  const size = new TextEncoder().encode(serializedPayload).length;
4926
5001
  if (size > MAX_SOCKET_MESSAGE_SIZE) {
@@ -4990,7 +5065,7 @@ function createRoom(options, config) {
4990
5065
  const stackSizeBefore = context.undoStack.length;
4991
5066
  for (const key in context.initialStorage) {
4992
5067
  if (context.root.get(key) === void 0) {
4993
- context.root.set(key, context.initialStorage[key]);
5068
+ context.root.set(key, cloneLson(context.initialStorage[key]));
4994
5069
  }
4995
5070
  }
4996
5071
  context.undoStack.length = stackSizeBefore;
@@ -5021,25 +5096,23 @@ function createRoom(options, config) {
5021
5096
  _addToRealUndoStack(historyOps, batchedUpdatesWrapper);
5022
5097
  }
5023
5098
  }
5024
- function notify({
5025
- storageUpdates = /* @__PURE__ */ new Map(),
5026
- presence = false,
5027
- others: otherEvents = []
5028
- }, batchedUpdatesWrapper) {
5099
+ function notify(updates, batchedUpdatesWrapper) {
5100
+ const storageUpdates = updates.storageUpdates;
5101
+ const othersUpdates = updates.others;
5029
5102
  batchedUpdatesWrapper(() => {
5030
- if (otherEvents.length > 0) {
5103
+ if (othersUpdates !== void 0 && othersUpdates.length > 0) {
5031
5104
  const others = context.others.current;
5032
- for (const event of otherEvents) {
5033
- eventHub.others.notify({ others, event });
5105
+ for (const event of othersUpdates) {
5106
+ eventHub.others.notify({ ...event, others });
5034
5107
  }
5035
5108
  }
5036
- if (presence) {
5109
+ if (_nullishCoalesce(updates.presence, () => ( false))) {
5037
5110
  notifySelfChanged(doNotBatchUpdates);
5038
5111
  eventHub.myPresence.notify(context.myPresence.current);
5039
5112
  }
5040
- if (storageUpdates.size > 0) {
5041
- const updates = Array.from(storageUpdates.values());
5042
- eventHub.storage.notify(updates);
5113
+ if (storageUpdates !== void 0 && storageUpdates.size > 0) {
5114
+ const updates2 = Array.from(storageUpdates.values());
5115
+ eventHub.storage.notify(updates2);
5043
5116
  }
5044
5117
  notifyStorageStatus();
5045
5118
  });
@@ -5187,7 +5260,7 @@ function createRoom(options, config) {
5187
5260
  }
5188
5261
  context.myPresence.patch(patch);
5189
5262
  if (context.activeBatch) {
5190
- if (_optionalChain([options2, 'optionalAccess', _120 => _120.addToHistory])) {
5263
+ if (_optionalChain([options2, 'optionalAccess', _126 => _126.addToHistory])) {
5191
5264
  context.activeBatch.reverseOps.unshift({
5192
5265
  type: "presence",
5193
5266
  data: oldValues
@@ -5197,7 +5270,7 @@ function createRoom(options, config) {
5197
5270
  } else {
5198
5271
  flushNowOrSoon();
5199
5272
  batchUpdates(() => {
5200
- if (_optionalChain([options2, 'optionalAccess', _121 => _121.addToHistory])) {
5273
+ if (_optionalChain([options2, 'optionalAccess', _127 => _127.addToHistory])) {
5201
5274
  addToUndoStack(
5202
5275
  [{ type: "presence", data: oldValues }],
5203
5276
  doNotBatchUpdates
@@ -5377,7 +5450,7 @@ function createRoom(options, config) {
5377
5450
  const unacknowledgedOps = new Map(context.unacknowledgedOps);
5378
5451
  createOrUpdateRootFromMessage(message, doNotBatchUpdates);
5379
5452
  applyAndSendOps(unacknowledgedOps, doNotBatchUpdates);
5380
- _optionalChain([_resolveStoragePromise, 'optionalCall', _122 => _122()]);
5453
+ _optionalChain([_resolveStoragePromise, 'optionalCall', _128 => _128()]);
5381
5454
  notifyStorageStatus();
5382
5455
  eventHub.storageDidLoad.notify();
5383
5456
  break;
@@ -5400,7 +5473,7 @@ function createRoom(options, config) {
5400
5473
  if (process.env.NODE_ENV !== "production") {
5401
5474
  const traces = /* @__PURE__ */ new Set();
5402
5475
  for (const opId of message.opIds) {
5403
- const trace = _optionalChain([context, 'access', _123 => _123.opStackTraces, 'optionalAccess', _124 => _124.get, 'call', _125 => _125(opId)]);
5476
+ const trace = _optionalChain([context, 'access', _129 => _129.opStackTraces, 'optionalAccess', _130 => _130.get, 'call', _131 => _131(opId)]);
5404
5477
  if (trace) {
5405
5478
  traces.add(trace);
5406
5479
  }
@@ -5497,10 +5570,11 @@ ${Array.from(traces).join("\n\n")}`
5497
5570
  }
5498
5571
  return messages;
5499
5572
  }
5500
- function updateYDoc(update) {
5573
+ function updateYDoc(update, guid) {
5501
5574
  const clientMsg = {
5502
5575
  type: 301 /* UPDATE_YDOC */,
5503
- update
5576
+ update,
5577
+ guid
5504
5578
  };
5505
5579
  context.buffer.messages.push(clientMsg);
5506
5580
  eventHub.ydoc.notify(clientMsg);
@@ -5563,13 +5637,14 @@ ${Array.from(traces).join("\n\n")}`
5563
5637
  root: nn(context.root)
5564
5638
  };
5565
5639
  }
5566
- function fetchYDoc(vector) {
5640
+ function fetchYDoc(vector, guid) {
5567
5641
  if (!context.buffer.messages.find((m) => {
5568
- return m.type === 300 /* FETCH_YDOC */ && m.vector === vector;
5642
+ return m.type === 300 /* FETCH_YDOC */ && m.vector === vector && m.guid === guid;
5569
5643
  })) {
5570
5644
  context.buffer.messages.push({
5571
5645
  type: 300 /* FETCH_YDOC */,
5572
- vector
5646
+ vector,
5647
+ guid
5573
5648
  });
5574
5649
  }
5575
5650
  flushNowOrSoon();
@@ -5687,10 +5762,7 @@ ${Array.from(traces).join("\n\n")}`
5687
5762
  (others) => others.map((other, index) => userToTreeNode(`Other ${index}`, other))
5688
5763
  );
5689
5764
  const events = {
5690
- connection: eventHub.connection.observable,
5691
- // Old/deprecated API
5692
5765
  status: eventHub.status.observable,
5693
- // New/recommended API
5694
5766
  lostConnection: eventHub.lostConnection.observable,
5695
5767
  customEvent: eventHub.customEvent.observable,
5696
5768
  others: eventHub.others.observable,
@@ -5705,14 +5777,14 @@ ${Array.from(traces).join("\n\n")}`
5705
5777
  comments: eventHub.comments.observable
5706
5778
  };
5707
5779
  const commentsApi = createCommentsApi(config.roomId, delegates.authenticate, {
5708
- serverEndpoint: "https://api.liveblocks.io/v2"
5780
+ baseUrl: config.baseUrl
5709
5781
  });
5710
5782
  return Object.defineProperty(
5711
5783
  {
5712
5784
  /* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
5713
5785
  __internal: {
5714
5786
  get presenceBuffer() {
5715
- return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _126 => _126.buffer, 'access', _127 => _127.presenceUpdates, 'optionalAccess', _128 => _128.data]), () => ( null)));
5787
+ return deepClone(_nullishCoalesce(_optionalChain([context, 'access', _132 => _132.buffer, 'access', _133 => _133.presenceUpdates, 'optionalAccess', _134 => _134.data]), () => ( null)));
5716
5788
  },
5717
5789
  // prettier-ignore
5718
5790
  get undoStack() {
@@ -5738,7 +5810,10 @@ ${Array.from(traces).join("\n\n")}`
5738
5810
  connect: () => managedSocket.connect(),
5739
5811
  reconnect: () => managedSocket.reconnect(),
5740
5812
  disconnect: () => managedSocket.disconnect(),
5741
- destroy: () => managedSocket.destroy(),
5813
+ destroy: () => {
5814
+ uninstallBgTabSpy();
5815
+ managedSocket.destroy();
5816
+ },
5742
5817
  // Presence
5743
5818
  updatePresence,
5744
5819
  updateYDoc,
@@ -5809,16 +5884,19 @@ function makeClassicSubscribeFn(events) {
5809
5884
  return events.myPresence.subscribe(callback);
5810
5885
  case "others": {
5811
5886
  const cb = callback;
5812
- return events.others.subscribe(
5813
- ({ others, event }) => cb(others, event)
5814
- );
5887
+ return events.others.subscribe((event) => {
5888
+ const { others, ...internalEvent } = event;
5889
+ return cb(others, internalEvent);
5890
+ });
5815
5891
  }
5816
5892
  case "error":
5817
5893
  return events.error.subscribe(callback);
5818
- case "connection":
5819
- return events.connection.subscribe(
5820
- callback
5894
+ case "connection": {
5895
+ const cb = callback;
5896
+ return events.status.subscribe(
5897
+ (status) => cb(newToLegacyStatus(status))
5821
5898
  );
5899
+ }
5822
5900
  case "status":
5823
5901
  return events.status.subscribe(callback);
5824
5902
  case "lost-connection":
@@ -5848,7 +5926,7 @@ function makeClassicSubscribeFn(events) {
5848
5926
  }
5849
5927
  if (isLiveNode(first)) {
5850
5928
  const node = first;
5851
- if (_optionalChain([options, 'optionalAccess', _129 => _129.isDeep])) {
5929
+ if (_optionalChain([options, 'optionalAccess', _135 => _135.isDeep])) {
5852
5930
  const storageCallback = second;
5853
5931
  return subscribeToLiveStructureDeeply(node, storageCallback);
5854
5932
  } else {
@@ -5870,15 +5948,17 @@ function makeAuthDelegateForRoom(roomId, authManager) {
5870
5948
  return authManager.getAuthValue("room:read", roomId);
5871
5949
  };
5872
5950
  }
5873
- function makeCreateSocketDelegateForRoom(roomId, liveblocksServer, WebSocketPolyfill) {
5951
+ function makeCreateSocketDelegateForRoom(roomId, baseUrl, WebSocketPolyfill) {
5874
5952
  return (authValue) => {
5875
5953
  const ws = _nullishCoalesce(WebSocketPolyfill, () => ( (typeof WebSocket === "undefined" ? void 0 : WebSocket)));
5876
5954
  if (ws === void 0) {
5877
5955
  throw new StopRetrying(
5878
- "To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill."
5956
+ "To use Liveblocks client in a non-DOM environment, you need to provide a WebSocket polyfill."
5879
5957
  );
5880
5958
  }
5881
- const url = new URL(liveblocksServer);
5959
+ const url = new URL(baseUrl);
5960
+ url.protocol = url.protocol === "http:" ? "ws" : "wss";
5961
+ url.pathname = "/v7";
5882
5962
  url.searchParams.set("roomId", roomId);
5883
5963
  if (authValue.type === "secret") {
5884
5964
  url.searchParams.set("tok", authValue.token.raw);
@@ -5896,13 +5976,20 @@ function makeCreateSocketDelegateForRoom(roomId, liveblocksServer, WebSocketPoly
5896
5976
  var MIN_THROTTLE = 16;
5897
5977
  var MAX_THROTTLE = 1e3;
5898
5978
  var DEFAULT_THROTTLE = 100;
5979
+ var MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT = 6e4;
5899
5980
  var MIN_LOST_CONNECTION_TIMEOUT = 200;
5900
5981
  var RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1e3;
5901
5982
  var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
5902
5983
  var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
5903
- function getServerFromClientOptions(clientOptions) {
5904
- const rawOptions = clientOptions;
5905
- return typeof rawOptions.liveblocksServer === "string" ? rawOptions.liveblocksServer : "wss://api.liveblocks.io/v7";
5984
+ function getBaseUrlFromClientOptions(clientOptions) {
5985
+ if ("liveblocksServer" in clientOptions) {
5986
+ throw new Error("Client option no longer supported");
5987
+ }
5988
+ if (typeof clientOptions.baseUrl === "string" && clientOptions.baseUrl.startsWith("http")) {
5989
+ return clientOptions.baseUrl;
5990
+ } else {
5991
+ return DEFAULT_BASE_URL;
5992
+ }
5906
5993
  }
5907
5994
  function createClient(options) {
5908
5995
  const clientOptions = options;
@@ -5910,21 +5997,45 @@ function createClient(options) {
5910
5997
  const lostConnectionTimeout = getLostConnectionTimeout(
5911
5998
  _nullishCoalesce(clientOptions.lostConnectionTimeout, () => ( DEFAULT_LOST_CONNECTION_TIMEOUT))
5912
5999
  );
6000
+ const backgroundKeepAliveTimeout = getBackgroundKeepAliveTimeout(
6001
+ clientOptions.backgroundKeepAliveTimeout
6002
+ );
5913
6003
  const authManager = createAuthManager(options);
5914
- const rooms = /* @__PURE__ */ new Map();
5915
- function getRoom(roomId) {
5916
- const room = rooms.get(roomId);
5917
- return room ? room : null;
6004
+ const roomsById = /* @__PURE__ */ new Map();
6005
+ function teardownRoom(room) {
6006
+ unlinkDevTools(room.id);
6007
+ roomsById.delete(room.id);
6008
+ room.destroy();
6009
+ }
6010
+ function leaseRoom(info) {
6011
+ const leave = () => {
6012
+ const self = leave;
6013
+ if (!info.unsubs.delete(self)) {
6014
+ warn(
6015
+ "This leave function was already called. Calling it more than once has no effect."
6016
+ );
6017
+ } else {
6018
+ if (info.unsubs.size === 0) {
6019
+ teardownRoom(info.room);
6020
+ }
6021
+ }
6022
+ };
6023
+ info.unsubs.add(leave);
6024
+ return {
6025
+ room: info.room,
6026
+ leave
6027
+ };
5918
6028
  }
5919
- function enter(roomId, options2) {
5920
- const existingRoom = rooms.get(roomId);
5921
- if (existingRoom !== void 0) {
5922
- return existingRoom;
6029
+ function enterRoom(roomId, options2) {
6030
+ const existing = roomsById.get(roomId);
6031
+ if (existing !== void 0) {
6032
+ return leaseRoom(existing);
5923
6033
  }
5924
6034
  deprecateIf(
5925
6035
  options2.initialPresence === null || options2.initialPresence === void 0,
5926
6036
  "Please provide an initial presence value for the current user when entering the room."
5927
6037
  );
6038
+ const baseUrl = getBaseUrlFromClientOptions(clientOptions);
5928
6039
  const newRoom = createRoom(
5929
6040
  {
5930
6041
  initialPresence: _nullishCoalesce(options2.initialPresence, () => ( {})),
@@ -5934,28 +6045,33 @@ function createClient(options) {
5934
6045
  roomId,
5935
6046
  throttleDelay,
5936
6047
  lostConnectionTimeout,
6048
+ backgroundKeepAliveTimeout,
5937
6049
  polyfills: clientOptions.polyfills,
5938
6050
  delegates: _nullishCoalesce(clientOptions.mockedDelegates, () => ( {
5939
6051
  createSocket: makeCreateSocketDelegateForRoom(
5940
6052
  roomId,
5941
- getServerFromClientOptions(clientOptions),
5942
- _optionalChain([clientOptions, 'access', _130 => _130.polyfills, 'optionalAccess', _131 => _131.WebSocket])
6053
+ baseUrl,
6054
+ _optionalChain([clientOptions, 'access', _136 => _136.polyfills, 'optionalAccess', _137 => _137.WebSocket])
5943
6055
  ),
5944
6056
  authenticate: makeAuthDelegateForRoom(roomId, authManager)
5945
6057
  })),
5946
6058
  enableDebugLogging: clientOptions.enableDebugLogging,
5947
- unstable_batchedUpdates: _optionalChain([options2, 'optionalAccess', _132 => _132.unstable_batchedUpdates]),
5948
- liveblocksServer: getServerFromClientOptions(clientOptions),
6059
+ unstable_batchedUpdates: _optionalChain([options2, 'optionalAccess', _138 => _138.unstable_batchedUpdates]),
6060
+ baseUrl,
5949
6061
  unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP
5950
6062
  }
5951
6063
  );
5952
- rooms.set(roomId, newRoom);
5953
- setupDevTools(() => Array.from(rooms.keys()));
6064
+ const newRoomInfo = {
6065
+ room: newRoom,
6066
+ unsubs: /* @__PURE__ */ new Set()
6067
+ };
6068
+ roomsById.set(roomId, newRoomInfo);
6069
+ setupDevTools(() => Array.from(roomsById.keys()));
5954
6070
  linkDevTools(roomId, newRoom);
5955
- const shouldConnect = _nullishCoalesce(options2.shouldInitiallyConnect, () => ( true));
6071
+ const shouldConnect = _nullishCoalesce(_nullishCoalesce(options2.autoConnect, () => ( options2.shouldInitiallyConnect)), () => ( true));
5956
6072
  if (shouldConnect) {
5957
6073
  if (typeof atob === "undefined") {
5958
- if (_optionalChain([clientOptions, 'access', _133 => _133.polyfills, 'optionalAccess', _134 => _134.atob]) === void 0) {
6074
+ if (_optionalChain([clientOptions, 'access', _139 => _139.polyfills, 'optionalAccess', _140 => _140.atob]) === void 0) {
5959
6075
  throw new Error(
5960
6076
  "You need to polyfill atob to use the client in your environment. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/atob-polyfill"
5961
6077
  );
@@ -5964,30 +6080,63 @@ function createClient(options) {
5964
6080
  }
5965
6081
  newRoom.connect();
5966
6082
  }
5967
- return newRoom;
6083
+ return leaseRoom(newRoomInfo);
5968
6084
  }
5969
- function leave(roomId) {
5970
- unlinkDevTools(roomId);
5971
- const room = rooms.get(roomId);
5972
- if (room !== void 0) {
5973
- room.destroy();
5974
- rooms.delete(roomId);
6085
+ function enter(roomId, options2) {
6086
+ const { room, leave: _ } = enterRoom(roomId, options2);
6087
+ return room;
6088
+ }
6089
+ function getRoom(roomId) {
6090
+ const room = _optionalChain([roomsById, 'access', _141 => _141.get, 'call', _142 => _142(roomId), 'optionalAccess', _143 => _143.room]);
6091
+ return room ? room : null;
6092
+ }
6093
+ function forceLeave(roomId) {
6094
+ const unsubs = _nullishCoalesce(_optionalChain([roomsById, 'access', _144 => _144.get, 'call', _145 => _145(roomId), 'optionalAccess', _146 => _146.unsubs]), () => ( /* @__PURE__ */ new Set()));
6095
+ for (const unsub of unsubs) {
6096
+ unsub();
6097
+ }
6098
+ }
6099
+ function logout() {
6100
+ authManager.reset();
6101
+ for (const { room } of roomsById.values()) {
6102
+ if (!isIdle(room.getStatus())) {
6103
+ room.reconnect();
6104
+ }
5975
6105
  }
5976
6106
  }
5977
6107
  return {
5978
- getRoom,
6108
+ logout,
6109
+ // Old, deprecated APIs
5979
6110
  enter,
5980
- leave
6111
+ getRoom,
6112
+ leave: forceLeave,
6113
+ // New, preferred API
6114
+ enterRoom
5981
6115
  };
5982
6116
  }
5983
6117
  function checkBounds(option, value, min, max, recommendedMin) {
5984
- if (typeof value !== "number" || value < min || value > max) {
6118
+ if (typeof value !== "number" || value < min || max === void 0 || value > max) {
5985
6119
  throw new Error(
5986
- `${option} should be a number between ${_nullishCoalesce(recommendedMin, () => ( min))} and ${max}.`
6120
+ max !== void 0 ? `${option} should be between ${_nullishCoalesce(recommendedMin, () => ( min))} and ${max}.` : `${option} should be at least ${_nullishCoalesce(recommendedMin, () => ( min))}.`
5987
6121
  );
5988
6122
  }
5989
6123
  return value;
5990
6124
  }
6125
+ function getBackgroundKeepAliveTimeout(value) {
6126
+ if (value === void 0)
6127
+ return void 0;
6128
+ if (typeof document === "undefined") {
6129
+ warn(
6130
+ "Setting backgroundKeepAliveTimeout won't have an effect in a non-DOM environment."
6131
+ );
6132
+ return void 0;
6133
+ }
6134
+ return checkBounds(
6135
+ "backgroundKeepAliveTimeout",
6136
+ value,
6137
+ MIN_BACKGROUND_KEEP_ALIVE_TIMEOUT
6138
+ );
6139
+ }
5991
6140
  function getThrottle(value) {
5992
6141
  return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
5993
6142
  }
@@ -6230,12 +6379,12 @@ function legacy_patchImmutableNode(state, path, update) {
6230
6379
  }
6231
6380
  const newState = Object.assign({}, state);
6232
6381
  for (const key in update.updates) {
6233
- if (_optionalChain([update, 'access', _135 => _135.updates, 'access', _136 => _136[key], 'optionalAccess', _137 => _137.type]) === "update") {
6382
+ if (_optionalChain([update, 'access', _147 => _147.updates, 'access', _148 => _148[key], 'optionalAccess', _149 => _149.type]) === "update") {
6234
6383
  const val = update.node.get(key);
6235
6384
  if (val !== void 0) {
6236
6385
  newState[key] = lsonToJson(val);
6237
6386
  }
6238
- } else if (_optionalChain([update, 'access', _138 => _138.updates, 'access', _139 => _139[key], 'optionalAccess', _140 => _140.type]) === "delete") {
6387
+ } else if (_optionalChain([update, 'access', _150 => _150.updates, 'access', _151 => _151[key], 'optionalAccess', _152 => _152.type]) === "delete") {
6239
6388
  delete newState[key];
6240
6389
  }
6241
6390
  }
@@ -6296,12 +6445,12 @@ function legacy_patchImmutableNode(state, path, update) {
6296
6445
  }
6297
6446
  const newState = Object.assign({}, state);
6298
6447
  for (const key in update.updates) {
6299
- if (_optionalChain([update, 'access', _141 => _141.updates, 'access', _142 => _142[key], 'optionalAccess', _143 => _143.type]) === "update") {
6448
+ if (_optionalChain([update, 'access', _153 => _153.updates, 'access', _154 => _154[key], 'optionalAccess', _155 => _155.type]) === "update") {
6300
6449
  const value = update.node.get(key);
6301
6450
  if (value !== void 0) {
6302
6451
  newState[key] = lsonToJson(value);
6303
6452
  }
6304
- } else if (_optionalChain([update, 'access', _144 => _144.updates, 'access', _145 => _145[key], 'optionalAccess', _146 => _146.type]) === "delete") {
6453
+ } else if (_optionalChain([update, 'access', _156 => _156.updates, 'access', _157 => _157[key], 'optionalAccess', _158 => _158.type]) === "delete") {
6305
6454
  delete newState[key];
6306
6455
  }
6307
6456
  }
@@ -6391,7 +6540,7 @@ function createCacheItem(key, asyncFunction, options) {
6391
6540
  let previousState = { isLoading: false };
6392
6541
  const eventSource2 = makeEventSource();
6393
6542
  function notify() {
6394
- const isEqual = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _147 => _147.isStateEqual]), () => ( isShallowEqual));
6543
+ const isEqual = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _159 => _159.isStateEqual]), () => ( isShallowEqual));
6395
6544
  if (!isEqual(previousState, state)) {
6396
6545
  previousState = state;
6397
6546
  eventSource2.notify(state);
@@ -6459,7 +6608,7 @@ function createAsyncCache(asyncFunction, options) {
6459
6608
  return create(key).get();
6460
6609
  }
6461
6610
  function getState(key) {
6462
- return _optionalChain([cache, 'access', _148 => _148.get, 'call', _149 => _149(key), 'optionalAccess', _150 => _150.getState, 'call', _151 => _151()]);
6611
+ return _optionalChain([cache, 'access', _160 => _160.get, 'call', _161 => _161(key), 'optionalAccess', _162 => _162.getState, 'call', _163 => _163()]);
6463
6612
  }
6464
6613
  function revalidate(key) {
6465
6614
  return create(key).revalidate();
@@ -6636,5 +6785,6 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
6636
6785
 
6637
6786
 
6638
6787
 
6639
- exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.OpCode = OpCode; exports.ServerMsgCode = ServerMsgCode; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.b64decode = b64decode; exports.console = fancy_console_exports; exports.createAsyncCache = createAsyncCache; exports.createClient = createClient; exports.createCommentsApi = createCommentsApi; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.errorIf = errorIf; exports.freeze = freeze; exports.isChildCrdt = isChildCrdt; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isLiveNode = isLiveNode; exports.isPlainObject = isPlainObject; exports.isRootCrdt = isRootCrdt; exports.legacy_patchImmutableObject = legacy_patchImmutableObject; exports.lsonToJson = lsonToJson; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.nn = nn; exports.patchLiveObjectKey = patchLiveObjectKey; exports.shallow = shallow; exports.stringify = stringify; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.tryParseJson = tryParseJson; exports.withTimeout = withTimeout;
6788
+
6789
+ exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.OpCode = OpCode; exports.ServerMsgCode = ServerMsgCode; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.b64decode = b64decode; exports.cloneLson = cloneLson; exports.console = fancy_console_exports; exports.createAsyncCache = createAsyncCache; exports.createClient = createClient; exports.createCommentsApi = createCommentsApi; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.detectDupes = detectDupes; exports.errorIf = errorIf; exports.freeze = freeze; exports.isChildCrdt = isChildCrdt; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isLiveNode = isLiveNode; exports.isPlainObject = isPlainObject; exports.isRootCrdt = isRootCrdt; exports.legacy_patchImmutableObject = legacy_patchImmutableObject; exports.lsonToJson = lsonToJson; exports.makeEventSource = makeEventSource; exports.makePoller = makePoller; exports.makePosition = makePosition; exports.nn = nn; exports.patchLiveObjectKey = patchLiveObjectKey; exports.shallow = shallow; exports.stringify = stringify; exports.throwUsageError = throwUsageError; exports.toPlainLson = toPlainLson; exports.tryParseJson = tryParseJson; exports.withTimeout = withTimeout;
6640
6790
  //# sourceMappingURL=index.js.map