@liveblocks/server 1.1.0 → 1.2.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.cjs CHANGED
@@ -891,6 +891,9 @@ var InMemoryDriver = class {
891
891
  async delete_leased_session(sessionId) {
892
892
  this._leasedSessions.delete(sessionId);
893
893
  }
894
+ takeRowsWritten() {
895
+ return 0;
896
+ }
894
897
  next_actor() {
895
898
  return ++this._nextActor;
896
899
  }
@@ -1445,7 +1448,7 @@ var YjsStorage = class {
1445
1448
  let encodedTargetVector;
1446
1449
  try {
1447
1450
  encodedTargetVector = stateVector.length > 0 ? _jsbase64.Base64.toUint8Array(stateVector) : void 0;
1448
- } catch (e) {
1451
+ } catch (e2) {
1449
1452
  logger.warn(
1450
1453
  "Could not get update from passed vector, returning all updates"
1451
1454
  );
@@ -1701,7 +1704,7 @@ function stripOpId(op2) {
1701
1704
  var __socket, __debug, __lastActiveAt, __hasNotifiedClientStorageUpdateError;
1702
1705
  var BrowserSession = class {
1703
1706
  /** @internal - Never create a BrowserSession instance manually. Use the room.startBrowserSession() API instead. */
1704
- constructor(ticket, socket, debug) {
1707
+ constructor(ticket, socket, debug, createdAt) {
1705
1708
  // ^^ User-defined Session Metadata
1706
1709
  // ^^ User-defined Client Metadata (sent to client in ROOM_STATE)
1707
1710
  __publicField(this, "version");
@@ -1719,6 +1722,7 @@ var BrowserSession = class {
1719
1722
  // Metadata sent to client in ROOM_STATE message's "meta" field
1720
1723
  __privateAdd(this, __socket);
1721
1724
  __privateAdd(this, __debug);
1725
+ /** Updated on every incoming message (including pings) via handleData(). Used for idle timeout detection. */
1722
1726
  __privateAdd(this, __lastActiveAt);
1723
1727
  // We keep a status in-memory in the session of whether we already sent a rejected ops message to the client.
1724
1728
  __privateAdd(this, __hasNotifiedClientStorageUpdateError);
@@ -1730,15 +1734,15 @@ var BrowserSession = class {
1730
1734
  this.publicMeta = ticket.publicMeta;
1731
1735
  __privateSet(this, __socket, socket);
1732
1736
  __privateSet(this, __debug, debug);
1733
- const now = /* @__PURE__ */ new Date();
1737
+ const now = _nullishCoalesce(_optionalChain([createdAt, 'optionalAccess', _49 => _49.getTime, 'call', _50 => _50()]), () => ( Date.now()));
1734
1738
  this.createdAt = now;
1735
1739
  __privateSet(this, __lastActiveAt, now);
1736
1740
  __privateSet(this, __hasNotifiedClientStorageUpdateError, false);
1737
1741
  }
1738
1742
  get lastActiveAt() {
1739
- const lastPing = _optionalChain([__privateGet, 'call', _49 => _49(this, __socket), 'access', _50 => _50.getLastPongTimestamp, 'optionalCall', _51 => _51()]);
1740
- if (lastPing && lastPing > __privateGet(this, __lastActiveAt)) {
1741
- return lastPing;
1743
+ const lastPing = _optionalChain([__privateGet, 'call', _51 => _51(this, __socket), 'access', _52 => _52.getLastPongTimestamp, 'optionalCall', _53 => _53()]);
1744
+ if (lastPing && lastPing.getTime() > __privateGet(this, __lastActiveAt)) {
1745
+ return lastPing.getTime();
1742
1746
  } else {
1743
1747
  return __privateGet(this, __lastActiveAt);
1744
1748
  }
@@ -1746,10 +1750,9 @@ var BrowserSession = class {
1746
1750
  get hasNotifiedClientStorageUpdateError() {
1747
1751
  return __privateGet(this, __hasNotifiedClientStorageUpdateError);
1748
1752
  }
1749
- markActive(now = /* @__PURE__ */ new Date()) {
1750
- if (now > __privateGet(this, __lastActiveAt)) {
1751
- __privateSet(this, __lastActiveAt, now);
1752
- }
1753
+ /** @internal - This should have to be called only from Room, never externally */
1754
+ markActive(now) {
1755
+ __privateSet(this, __lastActiveAt, now ? now.getTime() : Date.now());
1753
1756
  }
1754
1757
  setHasNotifiedClientStorageUpdateError() {
1755
1758
  __privateSet(this, __hasNotifiedClientStorageUpdateError, true);
@@ -1821,6 +1824,15 @@ var Room = class {
1821
1824
  __publicField(this, "meta");
1822
1825
  __publicField(this, "driver");
1823
1826
  __publicField(this, "logger");
1827
+ /**
1828
+ * While a room is in "maintenance mode", all WebSocket connections to the
1829
+ * room should be rejected until it's pulled out of maintenance mode again.
1830
+ * Maintenance mode should only last a couple of milliseconds, typically.
1831
+ *
1832
+ * This mutex ensures that concurrent destructive operations (like
1833
+ * init-storage, storage-reset, etc.) cannot interfere with one another.
1834
+ */
1835
+ __publicField(this, "_maintenanceMode", new (0, _asyncmutex.Mutex)());
1824
1836
  __publicField(this, "_loadData$", null);
1825
1837
  __publicField(this, "_data", null);
1826
1838
  __publicField(this, "_qsize", 0);
@@ -1828,28 +1840,28 @@ var Room = class {
1828
1840
  __publicField(this, "hooks");
1829
1841
  __privateAdd(this, __debug2);
1830
1842
  __privateAdd(this, __allowStreaming);
1831
- const driver = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _52 => _52.storage]), () => ( makeNewInMemoryDriver()));
1843
+ const driver = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _54 => _54.storage]), () => ( makeNewInMemoryDriver()));
1832
1844
  this.meta = meta;
1833
1845
  this.driver = driver;
1834
- this.logger = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _53 => _53.logger]), () => ( BLACK_HOLE));
1835
- __privateSet(this, __allowStreaming, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _54 => _54.allowStreaming]), () => ( true)));
1846
+ this.logger = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _55 => _55.logger]), () => ( BLACK_HOLE));
1847
+ __privateSet(this, __allowStreaming, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _56 => _56.allowStreaming]), () => ( true)));
1836
1848
  this.hooks = {
1837
- isClientMsgAllowed: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _55 => _55.hooks, 'optionalAccess', _56 => _56.isClientMsgAllowed]), () => ( (() => {
1849
+ isClientMsgAllowed: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _57 => _57.hooks, 'optionalAccess', _58 => _58.isClientMsgAllowed]), () => ( (() => {
1838
1850
  return {
1839
1851
  allowed: true
1840
1852
  };
1841
1853
  }))),
1842
1854
  // YYY .load() isn't called on the RoomServer yet! As soon as it does, these hooks will get called
1843
- onRoomWillLoad: _optionalChain([options, 'optionalAccess', _57 => _57.hooks, 'optionalAccess', _58 => _58.onRoomWillLoad]),
1844
- onRoomDidLoad: _optionalChain([options, 'optionalAccess', _59 => _59.hooks, 'optionalAccess', _60 => _60.onRoomDidLoad]),
1845
- onRoomWillUnload: _optionalChain([options, 'optionalAccess', _61 => _61.hooks, 'optionalAccess', _62 => _62.onRoomWillUnload]),
1846
- onRoomDidUnload: _optionalChain([options, 'optionalAccess', _63 => _63.hooks, 'optionalAccess', _64 => _64.onRoomDidUnload]),
1847
- onSessionDidStart: _optionalChain([options, 'optionalAccess', _65 => _65.hooks, 'optionalAccess', _66 => _66.onSessionDidStart]),
1848
- onSessionDidEnd: _optionalChain([options, 'optionalAccess', _67 => _67.hooks, 'optionalAccess', _68 => _68.onSessionDidEnd]),
1849
- postClientMsgStorageDidUpdate: _optionalChain([options, 'optionalAccess', _69 => _69.hooks, 'optionalAccess', _70 => _70.postClientMsgStorageDidUpdate]),
1850
- postClientMsgYdocDidUpdate: _optionalChain([options, 'optionalAccess', _71 => _71.hooks, 'optionalAccess', _72 => _72.postClientMsgYdocDidUpdate])
1855
+ onRoomWillLoad: _optionalChain([options, 'optionalAccess', _59 => _59.hooks, 'optionalAccess', _60 => _60.onRoomWillLoad]),
1856
+ onRoomDidLoad: _optionalChain([options, 'optionalAccess', _61 => _61.hooks, 'optionalAccess', _62 => _62.onRoomDidLoad]),
1857
+ onRoomWillUnload: _optionalChain([options, 'optionalAccess', _63 => _63.hooks, 'optionalAccess', _64 => _64.onRoomWillUnload]),
1858
+ onRoomDidUnload: _optionalChain([options, 'optionalAccess', _65 => _65.hooks, 'optionalAccess', _66 => _66.onRoomDidUnload]),
1859
+ onSessionDidStart: _optionalChain([options, 'optionalAccess', _67 => _67.hooks, 'optionalAccess', _68 => _68.onSessionDidStart]),
1860
+ onSessionDidEnd: _optionalChain([options, 'optionalAccess', _69 => _69.hooks, 'optionalAccess', _70 => _70.onSessionDidEnd]),
1861
+ postClientMsgStorageDidUpdate: _optionalChain([options, 'optionalAccess', _71 => _71.hooks, 'optionalAccess', _72 => _72.postClientMsgStorageDidUpdate]),
1862
+ postClientMsgYdocDidUpdate: _optionalChain([options, 'optionalAccess', _73 => _73.hooks, 'optionalAccess', _74 => _74.postClientMsgYdocDidUpdate])
1851
1863
  };
1852
- __privateSet(this, __debug2, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _73 => _73.enableDebugLogging]), () => ( false)));
1864
+ __privateSet(this, __debug2, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _75 => _75.enableDebugLogging]), () => ( false)));
1853
1865
  }
1854
1866
  get loadingState() {
1855
1867
  if (this._loadData$ === null) {
@@ -1881,6 +1893,24 @@ var Room = class {
1881
1893
  }
1882
1894
  // prettier-ignore
1883
1895
  // ------------------------------------------------------------------------------------
1896
+ // Maintenance mode
1897
+ // ------------------------------------------------------------------------------------
1898
+ /**
1899
+ * Returns true if the room is currently in maintenance mode.
1900
+ * When in maintenance mode, callers should refuse new WebSocket connections.
1901
+ */
1902
+ get isInMaintenance() {
1903
+ return this._maintenanceMode.isLocked();
1904
+ }
1905
+ /**
1906
+ * Tries to enter maintenance mode and run the given callback exclusively.
1907
+ * If the room is already in maintenance mode, throws E_ALREADY_LOCKED
1908
+ * immediately instead of queuing the request.
1909
+ */
1910
+ async runInMaintenanceMode(callback) {
1911
+ return _asyncmutex.tryAcquire.call(void 0, this._maintenanceMode).runExclusive(callback);
1912
+ }
1913
+ // ------------------------------------------------------------------------------------
1884
1914
  // Public API
1885
1915
  // ------------------------------------------------------------------------------------
1886
1916
  /**
@@ -1905,13 +1935,13 @@ var Room = class {
1905
1935
  * room will be reloaded from storage.
1906
1936
  */
1907
1937
  unload(ctx) {
1908
- _optionalChain([this, 'access', _74 => _74.hooks, 'access', _75 => _75.onRoomWillUnload, 'optionalCall', _76 => _76(ctx)]);
1938
+ _optionalChain([this, 'access', _76 => _76.hooks, 'access', _77 => _77.onRoomWillUnload, 'optionalCall', _78 => _78(ctx)]);
1909
1939
  if (this._data) {
1910
1940
  this.storage.unload();
1911
1941
  this.yjsStorage.unload();
1912
1942
  }
1913
1943
  this._loadData$ = null;
1914
- _optionalChain([this, 'access', _77 => _77.hooks, 'access', _78 => _78.onRoomDidUnload, 'optionalCall', _79 => _79(ctx)]);
1944
+ _optionalChain([this, 'access', _79 => _79.hooks, 'access', _80 => _80.onRoomDidUnload, 'optionalCall', _81 => _81(ctx)]);
1915
1945
  }
1916
1946
  /**
1917
1947
  * Issues a Ticket with a new/unique actor ID
@@ -1925,17 +1955,17 @@ var Room = class {
1925
1955
  * unused Ticket will simply get garbage collected.
1926
1956
  */
1927
1957
  async createTicket(options) {
1928
- const actor$ = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _80 => _80.actor]), () => ( this.getNextActor()));
1958
+ const actor$ = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _82 => _82.actor]), () => ( this.getNextActor()));
1929
1959
  const sessionKey = _nanoid.nanoid.call(void 0, );
1930
- const info = _optionalChain([options, 'optionalAccess', _81 => _81.info]);
1960
+ const info = _optionalChain([options, 'optionalAccess', _83 => _83.info]);
1931
1961
  const ticket = {
1932
- version: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _82 => _82.version]), () => ( HIGHEST_PROTOCOL_VERSION)),
1962
+ version: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _84 => _84.version]), () => ( HIGHEST_PROTOCOL_VERSION)),
1933
1963
  actor: await actor$,
1934
1964
  sessionKey,
1935
- meta: _optionalChain([options, 'optionalAccess', _83 => _83.meta]),
1936
- publicMeta: _optionalChain([options, 'optionalAccess', _84 => _84.publicMeta]),
1937
- user: _optionalChain([options, 'optionalAccess', _85 => _85.id]) ? { id: options.id, info } : { anonymousId: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _86 => _86.anonymousId]), () => ( _nanoid.nanoid.call(void 0, ))), info },
1938
- scopes: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _87 => _87.scopes]), () => ( ["room:write"]))
1965
+ meta: _optionalChain([options, 'optionalAccess', _85 => _85.meta]),
1966
+ publicMeta: _optionalChain([options, 'optionalAccess', _86 => _86.publicMeta]),
1967
+ user: _optionalChain([options, 'optionalAccess', _87 => _87.id]) ? { id: options.id, info } : { anonymousId: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _88 => _88.anonymousId]), () => ( _nanoid.nanoid.call(void 0, ))), info },
1968
+ scopes: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _89 => _89.scopes]), () => ( ["room:write"]))
1939
1969
  };
1940
1970
  if (__privateGet(this, __debug2)) {
1941
1971
  console.log(`new ticket created: ${JSON.stringify(ticket)}`);
@@ -1973,8 +2003,13 @@ var Room = class {
1973
2003
  if (this.sessions.size > 0) {
1974
2004
  throw new Error("This API can only be called before any sessions exist");
1975
2005
  }
1976
- for (const { ticket, socket, lastActivity } of sessions) {
1977
- const newSession = new BrowserSession(ticket, socket, __privateGet(this, __debug2));
2006
+ for (const { ticket, socket, lastActivity, createdAt } of sessions) {
2007
+ const newSession = new BrowserSession(
2008
+ ticket,
2009
+ socket,
2010
+ __privateGet(this, __debug2),
2011
+ createdAt
2012
+ );
1978
2013
  this.sessions.set(ticket.sessionKey, newSession);
1979
2014
  newSession.markActive(lastActivity);
1980
2015
  }
@@ -2064,7 +2099,7 @@ var Room = class {
2064
2099
  ctx,
2065
2100
  defer
2066
2101
  );
2067
- const p$ = _optionalChain([this, 'access', _88 => _88.hooks, 'access', _89 => _89.onSessionDidStart, 'optionalCall', _90 => _90(newSession, ctx)]);
2102
+ const p$ = _optionalChain([this, 'access', _90 => _90.hooks, 'access', _91 => _91.onSessionDidStart, 'optionalCall', _92 => _92(newSession, ctx)]);
2068
2103
  if (p$) defer(p$);
2069
2104
  }
2070
2105
  /**
@@ -2088,7 +2123,7 @@ var Room = class {
2088
2123
  for (const other of this.otherSessions(key)) {
2089
2124
  other.send({ type: _core.ServerMsgCode.USER_LEFT, actor: session.actor });
2090
2125
  }
2091
- const p$ = _optionalChain([this, 'access', _91 => _91.hooks, 'access', _92 => _92.onSessionDidEnd, 'optionalCall', _93 => _93(session, ctx)]);
2126
+ const p$ = _optionalChain([this, 'access', _93 => _93.hooks, 'access', _94 => _94.onSessionDidEnd, 'optionalCall', _95 => _95(session, ctx)]);
2092
2127
  if (p$) defer(p$);
2093
2128
  }
2094
2129
  }
@@ -2119,6 +2154,7 @@ var Room = class {
2119
2154
  );
2120
2155
  }) {
2121
2156
  const text = typeof data === "string" ? data : _core.raise.call(void 0, "Unsupported message format");
2157
+ _optionalChain([this, 'access', _96 => _96.sessions, 'access', _97 => _97.get, 'call', _98 => _98(key), 'optionalAccess', _99 => _99.markActive, 'call', _100 => _100()]);
2122
2158
  if (text === "ping") {
2123
2159
  await this.handlePing(key, ctx);
2124
2160
  } else {
@@ -2392,7 +2428,7 @@ var Room = class {
2392
2428
  }
2393
2429
  // Don't ever manually call this!
2394
2430
  async _load(ctx) {
2395
- await _optionalChain([this, 'access', _94 => _94.hooks, 'access', _95 => _95.onRoomWillLoad, 'optionalCall', _96 => _96(ctx)]);
2431
+ await _optionalChain([this, 'access', _101 => _101.hooks, 'access', _102 => _102.onRoomWillLoad, 'optionalCall', _103 => _103(ctx)]);
2396
2432
  const storage = await this._loadStorage();
2397
2433
  const yjsStorage = await this._loadYjsStorage();
2398
2434
  this._data = {
@@ -2400,7 +2436,7 @@ var Room = class {
2400
2436
  storage,
2401
2437
  yjsStorage
2402
2438
  };
2403
- await _optionalChain([this, 'access', _97 => _97.hooks, 'access', _98 => _98.onRoomDidLoad, 'optionalCall', _99 => _99(ctx)]);
2439
+ await _optionalChain([this, 'access', _104 => _104.hooks, 'access', _105 => _105.onRoomDidLoad, 'optionalCall', _106 => _106(ctx)]);
2404
2440
  }
2405
2441
  /**
2406
2442
  * Returns a new, unique, actor ID.
@@ -2441,7 +2477,7 @@ var Room = class {
2441
2477
  }
2442
2478
  const sent = session.sendPong();
2443
2479
  if (sent !== 0) {
2444
- await _optionalChain([this, 'access', _100 => _100.hooks, 'access', _101 => _101.onDidPong, 'optionalCall', _102 => _102(ctx)]);
2480
+ await _optionalChain([this, 'access', _107 => _107.hooks, 'access', _108 => _108.onDidPong, 'optionalCall', _109 => _109(ctx)]);
2445
2481
  }
2446
2482
  }
2447
2483
  async _processClientMsg_withExclusiveAccess(sessionKey, messages, ctx, defer) {
@@ -2580,7 +2616,7 @@ var Room = class {
2580
2616
  break;
2581
2617
  }
2582
2618
  case _core.ClientMsgCode.UPDATE_STORAGE: {
2583
- _optionalChain([this, 'access', _103 => _103.driver, 'access', _104 => _104.bump_storage_version, 'optionalCall', _105 => _105()]);
2619
+ _optionalChain([this, 'access', _110 => _110.driver, 'access', _111 => _111.bump_storage_version, 'optionalCall', _112 => _112()]);
2584
2620
  const result = await this.storage.applyOps(msg.ops);
2585
2621
  const opsToForward = result.flatMap(
2586
2622
  (r) => r.action === "accepted" ? [r.op] : []
@@ -2612,7 +2648,7 @@ var Room = class {
2612
2648
  });
2613
2649
  }
2614
2650
  if (opsToForward.length > 0) {
2615
- const p$ = _optionalChain([this, 'access', _106 => _106.hooks, 'access', _107 => _107.postClientMsgStorageDidUpdate, 'optionalCall', _108 => _108(ctx)]);
2651
+ const p$ = _optionalChain([this, 'access', _113 => _113.hooks, 'access', _114 => _114.postClientMsgStorageDidUpdate, 'optionalCall', _115 => _115(ctx)]);
2616
2652
  if (p$) defer(p$);
2617
2653
  }
2618
2654
  break;
@@ -2663,7 +2699,7 @@ var Room = class {
2663
2699
  defer
2664
2700
  );
2665
2701
  if (result.isUpdated) {
2666
- const p$ = _optionalChain([this, 'access', _109 => _109.hooks, 'access', _110 => _110.postClientMsgYdocDidUpdate, 'optionalCall', _111 => _111(ctx, session)]);
2702
+ const p$ = _optionalChain([this, 'access', _116 => _116.hooks, 'access', _117 => _117.postClientMsgYdocDidUpdate, 'optionalCall', _118 => _118(ctx, session)]);
2667
2703
  if (p$) defer(p$);
2668
2704
  }
2669
2705
  break;
@@ -2671,7 +2707,7 @@ var Room = class {
2671
2707
  default: {
2672
2708
  try {
2673
2709
  return _core.assertNever.call(void 0, msg, "Unrecognized client msg");
2674
- } catch (e2) {
2710
+ } catch (e3) {
2675
2711
  }
2676
2712
  }
2677
2713
  }