@liveblocks/server 1.0.4 → 1.0.6

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, JsonObject, Json, Brand, asPos, SerializedRootObject, SerializedChild, StorageNode, SerializedCrdt, Awaitable, PlainLsonObject, NodeMap, NodeStream, ClientWireOp, IgnoredOp, IUserInfo, ServerMsg as ServerMsg$1, BaseUserMeta } from '@liveblocks/core';
1
+ import { DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, JsonObject, Json, Brand, asPos, SerializedCrdt, IUserInfo, SerializedRootObject, SerializedChild, StorageNode, Awaitable, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, IgnoredOp, ServerMsg as ServerMsg$1, BaseUserMeta } from '@liveblocks/core';
2
2
  export { BroadcastEventClientMsg, ClientMsg, ClientWireOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, DeleteCrdtOp, DeleteObjectKeyOp, FetchStorageClientMsg, FetchYDocClientMsg, HasOpId, IgnoredOp, Op, RoomStateServerMsg, ServerMsg, ServerWireOp, SetParentKeyOp, UpdateObjectOp, UpdatePresenceClientMsg, UpdateStorageClientMsg, UpdateYDocClientMsg } from '@liveblocks/core';
3
3
  import * as decoders from 'decoders';
4
4
  import { Decoder } from 'decoders';
@@ -313,6 +313,32 @@ declare class Logger {
313
313
  */
314
314
 
315
315
  type Pos = ReturnType<typeof asPos>;
316
+ type NodeTuple<T extends SerializedCrdt = SerializedCrdt> = [
317
+ id: string,
318
+ value: T
319
+ ];
320
+ type NodeMap = {
321
+ size: number;
322
+ [Symbol.iterator]: () => IterableIterator<[id: string, node: SerializedCrdt]>;
323
+ clear: () => void;
324
+ delete: (key: string) => boolean;
325
+ get: (key: string) => SerializedCrdt | undefined;
326
+ has: (key: string) => boolean;
327
+ keys: () => Iterable<string>;
328
+ set(key: string, value: SerializedCrdt): void;
329
+ };
330
+ type NodeStream = Iterable<NodeTuple>;
331
+ /**
332
+ * Leased session data structure for server-side sessions with temporarily persisted presence.
333
+ */
334
+ type LeasedSession = {
335
+ sessionId: string;
336
+ presence: Json;
337
+ updatedAt: number;
338
+ info: IUserInfo;
339
+ ttl: number;
340
+ actorId: number;
341
+ };
316
342
 
317
343
  /**
318
344
  * Copyright (c) Liveblocks Inc.
@@ -514,6 +540,28 @@ interface IStorageDriver {
514
540
  * @private Test-only: never use in production.
515
541
  */
516
542
  DANGEROUSLY_wipe_all_y_updates(): Awaitable<void>;
543
+ /**
544
+ * List all leased sessions.
545
+ * Note: Does NOT filter by expiration - returns all stored sessions.
546
+ * Expiration logic is handled at the Room.ts level.
547
+ */
548
+ list_leased_sessions(): Awaitable<Iterable<[sessionId: string, session: LeasedSession]>>;
549
+ /**
550
+ * Get a specific leased session by session ID.
551
+ * Note: Does NOT check expiration - returns the stored session if it exists.
552
+ * Expiration logic is handled at the Room.ts level.
553
+ */
554
+ get_leased_session(sessionId: string): Awaitable<LeasedSession | undefined>;
555
+ /**
556
+ * Create or update a leased session.
557
+ * Note: This is a full replace operation - the caller is responsible for
558
+ * merging/patching presence if needed.
559
+ */
560
+ put_leased_session(session: LeasedSession): Awaitable<void>;
561
+ /**
562
+ * Delete a leased session by session ID.
563
+ */
564
+ delete_leased_session(sessionId: string): Awaitable<void>;
517
565
  }
518
566
 
519
567
  /**
@@ -639,7 +687,7 @@ declare function snapshotToPlainLson_lazy(snapshot: IReadableSnapshot): StringGe
639
687
  * Takes a copy of the provided nodes, so the snapshot is isolated from
640
688
  * subsequent mutations to the source.
641
689
  */
642
- declare function makeInMemorySnapshot(values: NodeMap | NodeStream): IReadableSnapshot;
690
+ declare function makeInMemorySnapshot(values: NodeMap$1 | NodeStream$1): IReadableSnapshot;
643
691
 
644
692
  /**
645
693
  * Copyright (c) Liveblocks Inc.
@@ -1055,6 +1103,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1055
1103
  socket: IServerWebSocket;
1056
1104
  lastActivity: Date;
1057
1105
  }[]): void;
1106
+ private sendSessionStartMessages;
1058
1107
  /**
1059
1108
  * Registers a new BrowserSession into the Room server's session list, along with
1060
1109
  * the socket connection to use for that BrowserSession, now that it is known.
@@ -1063,7 +1112,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1063
1112
  * - Sends a ROOM_STATE message to the socket.
1064
1113
  * - Broadcasts a USER_JOINED message to all other sessions in the room.
1065
1114
  */
1066
- startBrowserSession(ticket: Ticket<SM, CM>, socket: IServerWebSocket, ctx?: C, defer?: (promise: Promise<void>) => void): void;
1115
+ startBrowserSession(ticket: Ticket<SM, CM>, socket: IServerWebSocket, ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1067
1116
  /**
1068
1117
  * Unregisters the BrowserSession for the given actor. Call this when the socket has
1069
1118
  * been closed from the client's end.
@@ -1116,7 +1165,24 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1116
1165
  getSession(sessionKey: SessionKey): BrowserSession<SM, CM> | undefined;
1117
1166
  listSessions(): BrowserSession<SM, CM>[];
1118
1167
  /**
1119
- * Will send the given ServerMsg to all Sessions, except the Session
1168
+ * Upsert a leased session. Creates a new session if it doesn't exist (or is expired),
1169
+ * or updates an existing session with merged presence.
1170
+ */
1171
+ upsertLeasedSession(sessionId: string, presence: JsonObject, ttl: number, info: IUserInfo, ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1172
+ /**
1173
+ * List all server sessions. As a side effect, it will delete expired sessions.
1174
+ */
1175
+ listLeasedSessions(ctx?: C, defer?: (promise: Promise<void>) => void): Promise<LeasedSession[]>;
1176
+ /**
1177
+ * Delete a server session and broadcast USER_LEFT to all sessions.
1178
+ */
1179
+ deleteLeasedSession(session: LeasedSession, ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1180
+ /**
1181
+ * Delete all server sessions and broadcast USER_LEFT to all sessions.
1182
+ */
1183
+ deleteAllLeasedSessions(ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1184
+ /**
1185
+ * Will send the given ServerMsg through all Session, except the Session
1120
1186
  * where the message originates from.
1121
1187
  */
1122
1188
  sendToOthers(sender: SessionKey, serverMsg: ServerMsg | readonly ServerMsg[], ctx?: C, defer?: (promise: Promise<void>) => void): void;
@@ -1165,6 +1231,11 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1165
1231
  * Concatenates multiple Uint8Arrays into a single Uint8Array.
1166
1232
  */
1167
1233
  declare function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array;
1234
+ /**
1235
+ * Check if a leased session is expired.
1236
+ * Returns true if the current time is greater than or equal to updatedAt + ttl.
1237
+ */
1238
+ declare function isLeasedSessionExpired(leasedSession: LeasedSession): boolean;
1168
1239
 
1169
1240
  /**
1170
1241
  * Copyright (c) Liveblocks Inc.
@@ -1392,6 +1463,7 @@ declare class InMemoryDriver implements IStorageDriver {
1392
1463
  private _nodes;
1393
1464
  private _metadb;
1394
1465
  private _ydb;
1466
+ private _leasedSessions;
1395
1467
  constructor(options?: {
1396
1468
  initialActor?: number;
1397
1469
  initialNodes?: Iterable<[string, SerializedCrdt]>;
@@ -1402,6 +1474,10 @@ declare class InMemoryDriver implements IStorageDriver {
1402
1474
  get_meta(key: string): Promise<Json | undefined>;
1403
1475
  put_meta(key: string, value: Json): Promise<void>;
1404
1476
  delete_meta(key: string): Promise<void>;
1477
+ list_leased_sessions(): Promise<IterableIterator<[string, LeasedSession]>>;
1478
+ get_leased_session(sessionId: string): Promise<LeasedSession | undefined>;
1479
+ put_leased_session(session: LeasedSession): Promise<void>;
1480
+ delete_leased_session(sessionId: string): Promise<void>;
1405
1481
  next_actor(): number;
1406
1482
  iter_y_updates(docId: YDocId): Promise<IterableIterator<[string, Uint8Array]>>;
1407
1483
  write_y_updates(docId: YDocId, key: string, data: Uint8Array): Promise<void>;
@@ -1411,4 +1487,4 @@ declare class InMemoryDriver implements IStorageDriver {
1411
1487
  load_nodes_api(): IStorageDriverNodeAPI;
1412
1488
  }
1413
1489
 
1414
- export { type ActorID, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LoadingState, LogLevel, LogTarget, Logger, type MetadataDB, NestedMap, type Pos, type PreSerializedServerMsg, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, concatUint8Arrays, guidDecoder, jsonObjectYolo, jsonYolo, makeInMemorySnapshot, makeMetadataDB, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
1490
+ export { type ActorID, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LeasedSession, type LoadingState, LogLevel, LogTarget, Logger, type MetadataDB, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, type PreSerializedServerMsg, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, concatUint8Arrays, guidDecoder, isLeasedSessionExpired, jsonObjectYolo, jsonYolo, makeInMemorySnapshot, makeMetadataDB, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, JsonObject, Json, Brand, asPos, SerializedRootObject, SerializedChild, StorageNode, SerializedCrdt, Awaitable, PlainLsonObject, NodeMap, NodeStream, ClientWireOp, IgnoredOp, IUserInfo, ServerMsg as ServerMsg$1, BaseUserMeta } from '@liveblocks/core';
1
+ import { DistributiveOmit, SetParentKeyOp, DeleteCrdtOp, ClientMsg as ClientMsg$1, JsonObject, Json, Brand, asPos, SerializedCrdt, IUserInfo, SerializedRootObject, SerializedChild, StorageNode, Awaitable, PlainLsonObject, NodeMap as NodeMap$1, NodeStream as NodeStream$1, ClientWireOp, IgnoredOp, ServerMsg as ServerMsg$1, BaseUserMeta } from '@liveblocks/core';
2
2
  export { BroadcastEventClientMsg, ClientMsg, ClientWireOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, DeleteCrdtOp, DeleteObjectKeyOp, FetchStorageClientMsg, FetchYDocClientMsg, HasOpId, IgnoredOp, Op, RoomStateServerMsg, ServerMsg, ServerWireOp, SetParentKeyOp, UpdateObjectOp, UpdatePresenceClientMsg, UpdateStorageClientMsg, UpdateYDocClientMsg } from '@liveblocks/core';
3
3
  import * as decoders from 'decoders';
4
4
  import { Decoder } from 'decoders';
@@ -313,6 +313,32 @@ declare class Logger {
313
313
  */
314
314
 
315
315
  type Pos = ReturnType<typeof asPos>;
316
+ type NodeTuple<T extends SerializedCrdt = SerializedCrdt> = [
317
+ id: string,
318
+ value: T
319
+ ];
320
+ type NodeMap = {
321
+ size: number;
322
+ [Symbol.iterator]: () => IterableIterator<[id: string, node: SerializedCrdt]>;
323
+ clear: () => void;
324
+ delete: (key: string) => boolean;
325
+ get: (key: string) => SerializedCrdt | undefined;
326
+ has: (key: string) => boolean;
327
+ keys: () => Iterable<string>;
328
+ set(key: string, value: SerializedCrdt): void;
329
+ };
330
+ type NodeStream = Iterable<NodeTuple>;
331
+ /**
332
+ * Leased session data structure for server-side sessions with temporarily persisted presence.
333
+ */
334
+ type LeasedSession = {
335
+ sessionId: string;
336
+ presence: Json;
337
+ updatedAt: number;
338
+ info: IUserInfo;
339
+ ttl: number;
340
+ actorId: number;
341
+ };
316
342
 
317
343
  /**
318
344
  * Copyright (c) Liveblocks Inc.
@@ -514,6 +540,28 @@ interface IStorageDriver {
514
540
  * @private Test-only: never use in production.
515
541
  */
516
542
  DANGEROUSLY_wipe_all_y_updates(): Awaitable<void>;
543
+ /**
544
+ * List all leased sessions.
545
+ * Note: Does NOT filter by expiration - returns all stored sessions.
546
+ * Expiration logic is handled at the Room.ts level.
547
+ */
548
+ list_leased_sessions(): Awaitable<Iterable<[sessionId: string, session: LeasedSession]>>;
549
+ /**
550
+ * Get a specific leased session by session ID.
551
+ * Note: Does NOT check expiration - returns the stored session if it exists.
552
+ * Expiration logic is handled at the Room.ts level.
553
+ */
554
+ get_leased_session(sessionId: string): Awaitable<LeasedSession | undefined>;
555
+ /**
556
+ * Create or update a leased session.
557
+ * Note: This is a full replace operation - the caller is responsible for
558
+ * merging/patching presence if needed.
559
+ */
560
+ put_leased_session(session: LeasedSession): Awaitable<void>;
561
+ /**
562
+ * Delete a leased session by session ID.
563
+ */
564
+ delete_leased_session(sessionId: string): Awaitable<void>;
517
565
  }
518
566
 
519
567
  /**
@@ -639,7 +687,7 @@ declare function snapshotToPlainLson_lazy(snapshot: IReadableSnapshot): StringGe
639
687
  * Takes a copy of the provided nodes, so the snapshot is isolated from
640
688
  * subsequent mutations to the source.
641
689
  */
642
- declare function makeInMemorySnapshot(values: NodeMap | NodeStream): IReadableSnapshot;
690
+ declare function makeInMemorySnapshot(values: NodeMap$1 | NodeStream$1): IReadableSnapshot;
643
691
 
644
692
  /**
645
693
  * Copyright (c) Liveblocks Inc.
@@ -1055,6 +1103,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1055
1103
  socket: IServerWebSocket;
1056
1104
  lastActivity: Date;
1057
1105
  }[]): void;
1106
+ private sendSessionStartMessages;
1058
1107
  /**
1059
1108
  * Registers a new BrowserSession into the Room server's session list, along with
1060
1109
  * the socket connection to use for that BrowserSession, now that it is known.
@@ -1063,7 +1112,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1063
1112
  * - Sends a ROOM_STATE message to the socket.
1064
1113
  * - Broadcasts a USER_JOINED message to all other sessions in the room.
1065
1114
  */
1066
- startBrowserSession(ticket: Ticket<SM, CM>, socket: IServerWebSocket, ctx?: C, defer?: (promise: Promise<void>) => void): void;
1115
+ startBrowserSession(ticket: Ticket<SM, CM>, socket: IServerWebSocket, ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1067
1116
  /**
1068
1117
  * Unregisters the BrowserSession for the given actor. Call this when the socket has
1069
1118
  * been closed from the client's end.
@@ -1116,7 +1165,24 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1116
1165
  getSession(sessionKey: SessionKey): BrowserSession<SM, CM> | undefined;
1117
1166
  listSessions(): BrowserSession<SM, CM>[];
1118
1167
  /**
1119
- * Will send the given ServerMsg to all Sessions, except the Session
1168
+ * Upsert a leased session. Creates a new session if it doesn't exist (or is expired),
1169
+ * or updates an existing session with merged presence.
1170
+ */
1171
+ upsertLeasedSession(sessionId: string, presence: JsonObject, ttl: number, info: IUserInfo, ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1172
+ /**
1173
+ * List all server sessions. As a side effect, it will delete expired sessions.
1174
+ */
1175
+ listLeasedSessions(ctx?: C, defer?: (promise: Promise<void>) => void): Promise<LeasedSession[]>;
1176
+ /**
1177
+ * Delete a server session and broadcast USER_LEFT to all sessions.
1178
+ */
1179
+ deleteLeasedSession(session: LeasedSession, ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1180
+ /**
1181
+ * Delete all server sessions and broadcast USER_LEFT to all sessions.
1182
+ */
1183
+ deleteAllLeasedSessions(ctx?: C, defer?: (promise: Promise<void>) => void): Promise<void>;
1184
+ /**
1185
+ * Will send the given ServerMsg through all Session, except the Session
1120
1186
  * where the message originates from.
1121
1187
  */
1122
1188
  sendToOthers(sender: SessionKey, serverMsg: ServerMsg | readonly ServerMsg[], ctx?: C, defer?: (promise: Promise<void>) => void): void;
@@ -1165,6 +1231,11 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
1165
1231
  * Concatenates multiple Uint8Arrays into a single Uint8Array.
1166
1232
  */
1167
1233
  declare function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array;
1234
+ /**
1235
+ * Check if a leased session is expired.
1236
+ * Returns true if the current time is greater than or equal to updatedAt + ttl.
1237
+ */
1238
+ declare function isLeasedSessionExpired(leasedSession: LeasedSession): boolean;
1168
1239
 
1169
1240
  /**
1170
1241
  * Copyright (c) Liveblocks Inc.
@@ -1392,6 +1463,7 @@ declare class InMemoryDriver implements IStorageDriver {
1392
1463
  private _nodes;
1393
1464
  private _metadb;
1394
1465
  private _ydb;
1466
+ private _leasedSessions;
1395
1467
  constructor(options?: {
1396
1468
  initialActor?: number;
1397
1469
  initialNodes?: Iterable<[string, SerializedCrdt]>;
@@ -1402,6 +1474,10 @@ declare class InMemoryDriver implements IStorageDriver {
1402
1474
  get_meta(key: string): Promise<Json | undefined>;
1403
1475
  put_meta(key: string, value: Json): Promise<void>;
1404
1476
  delete_meta(key: string): Promise<void>;
1477
+ list_leased_sessions(): Promise<IterableIterator<[string, LeasedSession]>>;
1478
+ get_leased_session(sessionId: string): Promise<LeasedSession | undefined>;
1479
+ put_leased_session(session: LeasedSession): Promise<void>;
1480
+ delete_leased_session(sessionId: string): Promise<void>;
1405
1481
  next_actor(): number;
1406
1482
  iter_y_updates(docId: YDocId): Promise<IterableIterator<[string, Uint8Array]>>;
1407
1483
  write_y_updates(docId: YDocId, key: string, data: Uint8Array): Promise<void>;
@@ -1411,4 +1487,4 @@ declare class InMemoryDriver implements IStorageDriver {
1411
1487
  load_nodes_api(): IStorageDriverNodeAPI;
1412
1488
  }
1413
1489
 
1414
- export { type ActorID, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LoadingState, LogLevel, LogTarget, Logger, type MetadataDB, NestedMap, type Pos, type PreSerializedServerMsg, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, concatUint8Arrays, guidDecoder, jsonObjectYolo, jsonYolo, makeInMemorySnapshot, makeMetadataDB, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
1490
+ export { type ActorID, BackendSession, BrowserSession, ConsoleTarget, type CreateTicketOptions, DefaultMap, type FixOp, type Guid, type IReadableSnapshot, type IServerWebSocket, type IStorageDriver, type IStorageDriverNodeAPI, type IUserData, InMemoryDriver, type LeasedSession, type LoadingState, LogLevel, LogTarget, Logger, type MetadataDB, NestedMap, type NodeMap, type NodeStream, type NodeTuple, type Pos, type PreSerializedServerMsg, ProtocolVersion, ROOT_YDOC_ID, Room, type SessionKey, type Ticket, UniqueMap, type YDocId, type YUpdate, type YVector, ackIgnoredOp, clientMsgDecoder, concatUint8Arrays, guidDecoder, isLeasedSessionExpired, jsonObjectYolo, jsonYolo, makeInMemorySnapshot, makeMetadataDB, plainLsonToNodeStream, protocolVersionDecoder, quote, serialize as serializeServerMsg, snapshotToLossyJson_eager, snapshotToLossyJson_lazy, snapshotToNodeStream, snapshotToPlainLson_eager, snapshotToPlainLson_lazy, Storage as test_only__Storage, YjsStorage as test_only__YjsStorage, transientClientMsgDecoder, tryCatch };
package/dist/index.js 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 = options?.initialActor ?? -1;
857
859
  for (const [key, value] of options?.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: 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 = array2(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: ServerMsgCode2.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: ServerMsgCode2.USER_JOINED,
2267
+ actor: actorId,
2268
+ id: sessionId,
2269
+ info,
2270
+ scopes: []
2271
+ },
2272
+ ctx,
2273
+ defer
2274
+ );
2275
+ this.sendToAll(
2276
+ {
2277
+ type: ServerMsgCode2.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: ServerMsgCode2.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: ServerMsgCode2.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 = () => {
@@ -2555,6 +2729,7 @@ export {
2555
2729
  clientMsgDecoder,
2556
2730
  concatUint8Arrays,
2557
2731
  guidDecoder,
2732
+ isLeasedSessionExpired,
2558
2733
  jsonObjectYolo,
2559
2734
  jsonYolo,
2560
2735
  makeInMemorySnapshot,