@liveblocks/server 1.0.4 → 1.0.5

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
@@ -850,9 +850,11 @@ var InMemoryDriver = class {
850
850
  __publicField(this, "_nodes");
851
851
  __publicField(this, "_metadb");
852
852
  __publicField(this, "_ydb");
853
+ __publicField(this, "_leasedSessions");
853
854
  this._nodes = /* @__PURE__ */ new Map();
854
855
  this._metadb = /* @__PURE__ */ new Map();
855
856
  this._ydb = /* @__PURE__ */ new Map();
857
+ this._leasedSessions = /* @__PURE__ */ new Map();
856
858
  this._nextActor = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _35 => _35.initialActor]), () => ( -1));
857
859
  for (const [key, value] of _nullishCoalesce(_optionalChain([options, 'optionalAccess', _36 => _36.initialNodes]), () => ( []))) {
858
860
  this._nodes.set(key, value);
@@ -877,6 +879,18 @@ var InMemoryDriver = class {
877
879
  async delete_meta(key) {
878
880
  this._metadb.delete(key);
879
881
  }
882
+ async list_leased_sessions() {
883
+ return this._leasedSessions.entries();
884
+ }
885
+ async get_leased_session(sessionId) {
886
+ return this._leasedSessions.get(sessionId);
887
+ }
888
+ async put_leased_session(session) {
889
+ this._leasedSessions.set(session.sessionId, session);
890
+ }
891
+ async delete_leased_session(sessionId) {
892
+ this._leasedSessions.delete(sessionId);
893
+ }
880
894
  next_actor() {
881
895
  return ++this._nextActor;
882
896
  }
@@ -1681,6 +1695,10 @@ function makeRoomStateMsg(actor, nonce, scopes, users, publicMeta) {
1681
1695
  meta: _nullishCoalesce(publicMeta, () => ( {}))
1682
1696
  };
1683
1697
  }
1698
+ function isLeasedSessionExpired(leasedSession) {
1699
+ const now = Date.now();
1700
+ return now >= leasedSession.updatedAt + leasedSession.ttl;
1701
+ }
1684
1702
 
1685
1703
  // src/Room.ts
1686
1704
  var messagesDecoder = _decoders.array.call(void 0, clientMsgDecoder);
@@ -1993,6 +2011,50 @@ var Room = class {
1993
2011
  newSession.markActive(lastActivity);
1994
2012
  }
1995
2013
  }
2014
+ async sendSessionStartMessages(newSession, ticket, ctx, defer = () => {
2015
+ throw new Error(
2016
+ "One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to sendSessionStartMessages() to collect async side effects."
2017
+ );
2018
+ }) {
2019
+ const users = {};
2020
+ for (const session of this.otherSessions(ticket.sessionKey)) {
2021
+ users[session.actor] = {
2022
+ id: session.user.id,
2023
+ info: session.user.info,
2024
+ scopes: session.scopes
2025
+ };
2026
+ }
2027
+ const leasedSessions = await this.listLeasedSessions(
2028
+ ctx,
2029
+ defer
2030
+ );
2031
+ for (const leasedSession of leasedSessions) {
2032
+ users[leasedSession.actorId] = {
2033
+ id: leasedSession.sessionId,
2034
+ info: leasedSession.info,
2035
+ scopes: []
2036
+ };
2037
+ }
2038
+ newSession.send(
2039
+ makeRoomStateMsg(
2040
+ newSession.actor,
2041
+ ticket.sessionKey,
2042
+ // called "nonce" in the protocol
2043
+ newSession.scopes,
2044
+ users,
2045
+ ticket.publicMeta
2046
+ )
2047
+ );
2048
+ for (const leasedSession of leasedSessions) {
2049
+ newSession.send({
2050
+ type: _core.ServerMsgCode.UPDATE_PRESENCE,
2051
+ actor: leasedSession.actorId,
2052
+ targetActor: newSession.actor,
2053
+ // full presence to new user
2054
+ data: leasedSession.presence
2055
+ });
2056
+ }
2057
+ }
1996
2058
  /**
1997
2059
  * Registers a new BrowserSession into the Room server's session list, along with
1998
2060
  * the socket connection to use for that BrowserSession, now that it is known.
@@ -2001,7 +2063,7 @@ var Room = class {
2001
2063
  * - Sends a ROOM_STATE message to the socket.
2002
2064
  * - Broadcasts a USER_JOINED message to all other sessions in the room.
2003
2065
  */
2004
- startBrowserSession(ticket, socket, ctx, defer = () => {
2066
+ async startBrowserSession(ticket, socket, ctx, defer = () => {
2005
2067
  throw new Error(
2006
2068
  "One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to startBrowserSession() to collect async side effects."
2007
2069
  );
@@ -2021,24 +2083,7 @@ var Room = class {
2021
2083
  }
2022
2084
  const newSession = new BrowserSession(ticket, socket, __privateGet(this, __debug2));
2023
2085
  this.sessions.set(ticket.sessionKey, newSession);
2024
- const users = {};
2025
- for (const session of this.otherSessions(ticket.sessionKey)) {
2026
- users[session.actor] = {
2027
- id: session.user.id,
2028
- info: session.user.info,
2029
- scopes: session.scopes
2030
- };
2031
- }
2032
- newSession.send(
2033
- makeRoomStateMsg(
2034
- newSession.actor,
2035
- ticket.sessionKey,
2036
- // called "nonce" in the protocol
2037
- newSession.scopes,
2038
- users,
2039
- ticket.publicMeta
2040
- )
2041
- );
2086
+ await this.sendSessionStartMessages(newSession, ticket, ctx, defer);
2042
2087
  this.sendToOthers(
2043
2088
  ticket.sessionKey,
2044
2089
  {
@@ -2191,7 +2236,136 @@ var Room = class {
2191
2236
  return Array.from(this.sessions.values());
2192
2237
  }
2193
2238
  /**
2194
- * Will send the given ServerMsg to all Sessions, except the Session
2239
+ * Upsert a leased session. Creates a new session if it doesn't exist (or is expired),
2240
+ * or updates an existing session with merged presence.
2241
+ */
2242
+ async upsertLeasedSession(sessionId, presence, ttl, info, ctx, defer = () => {
2243
+ throw new Error(
2244
+ "One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to upsertLeasedSession() to collect async side effects."
2245
+ );
2246
+ }) {
2247
+ const existingSession = await this.driver.get_leased_session(sessionId);
2248
+ const isExpired = existingSession !== void 0 && isLeasedSessionExpired(existingSession);
2249
+ if (isExpired) {
2250
+ await this.deleteLeasedSession(existingSession, ctx, defer);
2251
+ }
2252
+ if (existingSession === void 0 || isExpired) {
2253
+ const actorId = await this.getNextActor();
2254
+ const now = Date.now();
2255
+ const session = {
2256
+ sessionId,
2257
+ presence,
2258
+ updatedAt: now,
2259
+ info,
2260
+ ttl,
2261
+ actorId
2262
+ };
2263
+ await this.driver.put_leased_session(session);
2264
+ this.sendToAll(
2265
+ {
2266
+ type: _core.ServerMsgCode.USER_JOINED,
2267
+ actor: actorId,
2268
+ id: sessionId,
2269
+ info,
2270
+ scopes: []
2271
+ },
2272
+ ctx,
2273
+ defer
2274
+ );
2275
+ this.sendToAll(
2276
+ {
2277
+ type: _core.ServerMsgCode.UPDATE_PRESENCE,
2278
+ actor: actorId,
2279
+ data: presence,
2280
+ targetActor: 1
2281
+ },
2282
+ ctx,
2283
+ defer
2284
+ );
2285
+ } else {
2286
+ const mergedPresence = {
2287
+ ...existingSession.presence,
2288
+ ...presence
2289
+ };
2290
+ const updatedSession = {
2291
+ ...existingSession,
2292
+ //info, UserInfo is immutable after creation
2293
+ presence: mergedPresence,
2294
+ updatedAt: Date.now(),
2295
+ ttl
2296
+ };
2297
+ await this.driver.put_leased_session(updatedSession);
2298
+ this.sendToAll(
2299
+ {
2300
+ type: _core.ServerMsgCode.UPDATE_PRESENCE,
2301
+ actor: existingSession.actorId,
2302
+ data: presence
2303
+ // Send only the patch, not the full merged presence
2304
+ // NO targetActor - this makes it a partial presence patch
2305
+ },
2306
+ ctx,
2307
+ defer
2308
+ );
2309
+ }
2310
+ }
2311
+ /**
2312
+ * List all server sessions. As a side effect, it will delete expired sessions.
2313
+ */
2314
+ async listLeasedSessions(ctx, defer = () => {
2315
+ throw new Error(
2316
+ "One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to listLeasedSessions() to collect async side effects."
2317
+ );
2318
+ }) {
2319
+ await this.load(ctx);
2320
+ const sessions = await this.driver.list_leased_sessions();
2321
+ const validSessions = [];
2322
+ const toDelete = [];
2323
+ for (const [_, session] of sessions) {
2324
+ if (isLeasedSessionExpired(session)) {
2325
+ toDelete.push(session);
2326
+ } else {
2327
+ validSessions.push(session);
2328
+ }
2329
+ }
2330
+ for (const session of toDelete) {
2331
+ await this.deleteLeasedSession(session, ctx, defer);
2332
+ }
2333
+ return validSessions;
2334
+ }
2335
+ /**
2336
+ * Delete a server session and broadcast USER_LEFT to all sessions.
2337
+ */
2338
+ async deleteLeasedSession(session, ctx, defer = () => {
2339
+ throw new Error(
2340
+ "One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to deleteLeasedSession() to collect async side effects."
2341
+ );
2342
+ }) {
2343
+ this.sendToAll(
2344
+ {
2345
+ type: _core.ServerMsgCode.USER_LEFT,
2346
+ actor: session.actorId
2347
+ },
2348
+ ctx,
2349
+ defer
2350
+ );
2351
+ await this.driver.delete_leased_session(session.sessionId);
2352
+ }
2353
+ /**
2354
+ * Delete all server sessions and broadcast USER_LEFT to all sessions.
2355
+ */
2356
+ async deleteAllLeasedSessions(ctx, defer = () => {
2357
+ throw new Error(
2358
+ "One of your hook handlers returned a promise, but no side effect collector was provided. Pass a `defer` callback to deleteAllLeasedSessions() to collect async side effects."
2359
+ );
2360
+ }) {
2361
+ await this.load(ctx);
2362
+ const sessions = await this.driver.list_leased_sessions();
2363
+ for (const [_, session] of sessions) {
2364
+ await this.deleteLeasedSession(session, ctx, defer);
2365
+ }
2366
+ }
2367
+ /**
2368
+ * Will send the given ServerMsg through all Session, except the Session
2195
2369
  * where the message originates from.
2196
2370
  */
2197
2371
  sendToOthers(sender, serverMsg, ctx, defer = () => {
@@ -2572,5 +2746,6 @@ __allowStreaming = new WeakMap();
2572
2746
 
2573
2747
 
2574
2748
 
2575
- exports.BackendSession = BackendSession; exports.BrowserSession = BrowserSession; exports.ConsoleTarget = ConsoleTarget; exports.DefaultMap = DefaultMap; exports.InMemoryDriver = InMemoryDriver; exports.LogLevel = LogLevel; exports.LogTarget = LogTarget; exports.Logger = Logger; exports.NestedMap = NestedMap; exports.ProtocolVersion = ProtocolVersion; exports.ROOT_YDOC_ID = ROOT_YDOC_ID; exports.Room = Room; exports.UniqueMap = UniqueMap; exports.ackIgnoredOp = ackIgnoredOp; exports.clientMsgDecoder = clientMsgDecoder; exports.concatUint8Arrays = concatUint8Arrays; exports.guidDecoder = guidDecoder; exports.jsonObjectYolo = jsonObjectYolo; exports.jsonYolo = jsonYolo; exports.makeInMemorySnapshot = makeInMemorySnapshot; exports.makeMetadataDB = makeMetadataDB; exports.plainLsonToNodeStream = plainLsonToNodeStream; exports.protocolVersionDecoder = protocolVersionDecoder; exports.quote = quote; exports.serializeServerMsg = serialize; exports.snapshotToLossyJson_eager = snapshotToLossyJson_eager; exports.snapshotToLossyJson_lazy = snapshotToLossyJson_lazy; exports.snapshotToNodeStream = snapshotToNodeStream; exports.snapshotToPlainLson_eager = snapshotToPlainLson_eager; exports.snapshotToPlainLson_lazy = snapshotToPlainLson_lazy; exports.test_only__Storage = Storage; exports.test_only__YjsStorage = YjsStorage; exports.transientClientMsgDecoder = transientClientMsgDecoder; exports.tryCatch = tryCatch;
2749
+
2750
+ exports.BackendSession = BackendSession; exports.BrowserSession = BrowserSession; exports.ConsoleTarget = ConsoleTarget; exports.DefaultMap = DefaultMap; exports.InMemoryDriver = InMemoryDriver; exports.LogLevel = LogLevel; exports.LogTarget = LogTarget; exports.Logger = Logger; exports.NestedMap = NestedMap; exports.ProtocolVersion = ProtocolVersion; exports.ROOT_YDOC_ID = ROOT_YDOC_ID; exports.Room = Room; exports.UniqueMap = UniqueMap; exports.ackIgnoredOp = ackIgnoredOp; exports.clientMsgDecoder = clientMsgDecoder; exports.concatUint8Arrays = concatUint8Arrays; exports.guidDecoder = guidDecoder; exports.isLeasedSessionExpired = isLeasedSessionExpired; exports.jsonObjectYolo = jsonObjectYolo; exports.jsonYolo = jsonYolo; exports.makeInMemorySnapshot = makeInMemorySnapshot; exports.makeMetadataDB = makeMetadataDB; exports.plainLsonToNodeStream = plainLsonToNodeStream; exports.protocolVersionDecoder = protocolVersionDecoder; exports.quote = quote; exports.serializeServerMsg = serialize; exports.snapshotToLossyJson_eager = snapshotToLossyJson_eager; exports.snapshotToLossyJson_lazy = snapshotToLossyJson_lazy; exports.snapshotToNodeStream = snapshotToNodeStream; exports.snapshotToPlainLson_eager = snapshotToPlainLson_eager; exports.snapshotToPlainLson_lazy = snapshotToPlainLson_lazy; exports.test_only__Storage = Storage; exports.test_only__YjsStorage = YjsStorage; exports.transientClientMsgDecoder = transientClientMsgDecoder; exports.tryCatch = tryCatch;
2576
2751
  //# sourceMappingURL=index.cjs.map