@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 +79 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -9
- package/dist/index.d.ts +38 -9
- package/dist/index.js +48 -12
- package/dist/index.js.map +1 -1
- package/package.json +32 -18
package/dist/index.d.cts
CHANGED
|
@@ -562,6 +562,11 @@ interface IStorageDriver {
|
|
|
562
562
|
* Delete a leased session by session ID.
|
|
563
563
|
*/
|
|
564
564
|
delete_leased_session(sessionId: string): Awaitable<void>;
|
|
565
|
+
/**
|
|
566
|
+
* Return the number of storage rows written since last call to this method,
|
|
567
|
+
* and reset the counter.
|
|
568
|
+
*/
|
|
569
|
+
takeRowsWritten?(): number;
|
|
565
570
|
}
|
|
566
571
|
|
|
567
572
|
/**
|
|
@@ -838,7 +843,7 @@ declare class YjsStorage {
|
|
|
838
843
|
* @returns a base64 encoded array of YJS updates
|
|
839
844
|
*/
|
|
840
845
|
getYDocUpdate(logger: Logger, stateVector?: string, guid?: Guid, isV2?: boolean): Promise<string | null>;
|
|
841
|
-
getYDocUpdateBinary(logger: Logger, stateVector?: string, guid?: Guid, isV2?: boolean): Promise<Uint8Array | null>;
|
|
846
|
+
getYDocUpdateBinary(logger: Logger, stateVector?: string, guid?: Guid, isV2?: boolean): Promise<Uint8Array<ArrayBuffer> | null>;
|
|
842
847
|
getYStateVector(guid?: Guid): Promise<string | null>;
|
|
843
848
|
getSnapshotHash(options: {
|
|
844
849
|
guid?: Guid;
|
|
@@ -890,6 +895,8 @@ declare class YjsStorage {
|
|
|
890
895
|
|
|
891
896
|
type LoadingState = "initial" | "loading" | "loaded";
|
|
892
897
|
type ActorID = Brand<number, "ActorID">;
|
|
898
|
+
/** Number of milliseconds since Unix epoch. */
|
|
899
|
+
type Millis = Brand<number, "Millis">;
|
|
893
900
|
/**
|
|
894
901
|
* Session keys are also known as the "nonce" in the protocol. It's a random,
|
|
895
902
|
* unique, but PRIVATE, identifier for the session, and it's important that
|
|
@@ -938,14 +945,13 @@ declare class BrowserSession<SM, CM extends JsonObject> {
|
|
|
938
945
|
#private;
|
|
939
946
|
readonly version: ProtocolVersion;
|
|
940
947
|
readonly actor: ActorID;
|
|
941
|
-
readonly createdAt:
|
|
948
|
+
readonly createdAt: Millis;
|
|
942
949
|
readonly user: IUserData;
|
|
943
950
|
readonly scopes: string[];
|
|
944
951
|
readonly meta: SM;
|
|
945
952
|
readonly publicMeta?: CM;
|
|
946
|
-
get lastActiveAt():
|
|
953
|
+
get lastActiveAt(): Millis;
|
|
947
954
|
get hasNotifiedClientStorageUpdateError(): boolean;
|
|
948
|
-
markActive(now?: Date): void;
|
|
949
955
|
setHasNotifiedClientStorageUpdateError(): void;
|
|
950
956
|
sendPong(): number;
|
|
951
957
|
send(serverMsg: ServerMsg | ServerMsg[] | PreSerializedServerMsg): number;
|
|
@@ -1048,6 +1054,15 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1048
1054
|
meta: RM;
|
|
1049
1055
|
readonly driver: IStorageDriver;
|
|
1050
1056
|
logger: Logger;
|
|
1057
|
+
/**
|
|
1058
|
+
* While a room is in "maintenance mode", all WebSocket connections to the
|
|
1059
|
+
* room should be rejected until it's pulled out of maintenance mode again.
|
|
1060
|
+
* Maintenance mode should only last a couple of milliseconds, typically.
|
|
1061
|
+
*
|
|
1062
|
+
* This mutex ensures that concurrent destructive operations (like
|
|
1063
|
+
* init-storage, storage-reset, etc.) cannot interfere with one another.
|
|
1064
|
+
*/
|
|
1065
|
+
private readonly _maintenanceMode;
|
|
1051
1066
|
private _loadData$;
|
|
1052
1067
|
private _data;
|
|
1053
1068
|
private _qsize;
|
|
@@ -1060,6 +1075,17 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1060
1075
|
get yjsStorage(): YjsStorage;
|
|
1061
1076
|
get mutex(): Mutex;
|
|
1062
1077
|
private get data();
|
|
1078
|
+
/**
|
|
1079
|
+
* Returns true if the room is currently in maintenance mode.
|
|
1080
|
+
* When in maintenance mode, callers should refuse new WebSocket connections.
|
|
1081
|
+
*/
|
|
1082
|
+
get isInMaintenance(): boolean;
|
|
1083
|
+
/**
|
|
1084
|
+
* Tries to enter maintenance mode and run the given callback exclusively.
|
|
1085
|
+
* If the room is already in maintenance mode, throws E_ALREADY_LOCKED
|
|
1086
|
+
* immediately instead of queuing the request.
|
|
1087
|
+
*/
|
|
1088
|
+
runInMaintenanceMode<T>(callback: () => Promise<T>): Promise<T>;
|
|
1063
1089
|
/**
|
|
1064
1090
|
* Initializes the Room, so it's ready to start accepting connections. Safe
|
|
1065
1091
|
* to call multiple times. After awaiting `room.load()` the Room is ready to
|
|
@@ -1102,6 +1128,8 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1102
1128
|
ticket: Ticket<SM, CM>;
|
|
1103
1129
|
socket: IServerWebSocket;
|
|
1104
1130
|
lastActivity: Date;
|
|
1131
|
+
/** Original session creation time (e.g. from persisted attachment). Required for correct session duration after hibernation. */
|
|
1132
|
+
createdAt?: Date;
|
|
1105
1133
|
}[]): void;
|
|
1106
1134
|
private sendSessionStartMessages;
|
|
1107
1135
|
/**
|
|
@@ -1230,7 +1258,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1230
1258
|
/**
|
|
1231
1259
|
* Concatenates multiple Uint8Arrays into a single Uint8Array.
|
|
1232
1260
|
*/
|
|
1233
|
-
declare function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array
|
|
1261
|
+
declare function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array<ArrayBuffer>;
|
|
1234
1262
|
/**
|
|
1235
1263
|
* Check if a leased session is expired.
|
|
1236
1264
|
* Returns true if the current time is greater than or equal to updatedAt + ttl.
|
|
@@ -1468,18 +1496,19 @@ declare class InMemoryDriver implements IStorageDriver {
|
|
|
1468
1496
|
initialActor?: number;
|
|
1469
1497
|
initialNodes?: Iterable<[string, SerializedCrdt]>;
|
|
1470
1498
|
});
|
|
1471
|
-
raw_iter_nodes():
|
|
1499
|
+
raw_iter_nodes(): MapIterator<[string, SerializedCrdt]>;
|
|
1472
1500
|
/** Deletes all nodes and replaces them with the given document. */
|
|
1473
1501
|
DANGEROUSLY_reset_nodes(doc: PlainLsonObject): void;
|
|
1474
1502
|
get_meta(key: string): Promise<Json | undefined>;
|
|
1475
1503
|
put_meta(key: string, value: Json): Promise<void>;
|
|
1476
1504
|
delete_meta(key: string): Promise<void>;
|
|
1477
|
-
list_leased_sessions(): Promise<
|
|
1505
|
+
list_leased_sessions(): Promise<MapIterator<[string, LeasedSession]>>;
|
|
1478
1506
|
get_leased_session(sessionId: string): Promise<LeasedSession | undefined>;
|
|
1479
1507
|
put_leased_session(session: LeasedSession): Promise<void>;
|
|
1480
1508
|
delete_leased_session(sessionId: string): Promise<void>;
|
|
1509
|
+
takeRowsWritten(): number;
|
|
1481
1510
|
next_actor(): number;
|
|
1482
|
-
iter_y_updates(docId: YDocId): Promise<IterableIterator<[string, Uint8Array]>>;
|
|
1511
|
+
iter_y_updates(docId: YDocId): Promise<IterableIterator<[string, Uint8Array<ArrayBufferLike>]>>;
|
|
1483
1512
|
write_y_updates(docId: YDocId, key: string, data: Uint8Array): Promise<void>;
|
|
1484
1513
|
delete_y_updates(docId: YDocId, keys: string[]): Promise<void>;
|
|
1485
1514
|
/** @private Only use this in unit tests, never in production. */
|
|
@@ -1487,4 +1516,4 @@ declare class InMemoryDriver implements IStorageDriver {
|
|
|
1487
1516
|
load_nodes_api(): IStorageDriverNodeAPI;
|
|
1488
1517
|
}
|
|
1489
1518
|
|
|
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 };
|
|
1519
|
+
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, type Millis, 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
|
@@ -562,6 +562,11 @@ interface IStorageDriver {
|
|
|
562
562
|
* Delete a leased session by session ID.
|
|
563
563
|
*/
|
|
564
564
|
delete_leased_session(sessionId: string): Awaitable<void>;
|
|
565
|
+
/**
|
|
566
|
+
* Return the number of storage rows written since last call to this method,
|
|
567
|
+
* and reset the counter.
|
|
568
|
+
*/
|
|
569
|
+
takeRowsWritten?(): number;
|
|
565
570
|
}
|
|
566
571
|
|
|
567
572
|
/**
|
|
@@ -838,7 +843,7 @@ declare class YjsStorage {
|
|
|
838
843
|
* @returns a base64 encoded array of YJS updates
|
|
839
844
|
*/
|
|
840
845
|
getYDocUpdate(logger: Logger, stateVector?: string, guid?: Guid, isV2?: boolean): Promise<string | null>;
|
|
841
|
-
getYDocUpdateBinary(logger: Logger, stateVector?: string, guid?: Guid, isV2?: boolean): Promise<Uint8Array | null>;
|
|
846
|
+
getYDocUpdateBinary(logger: Logger, stateVector?: string, guid?: Guid, isV2?: boolean): Promise<Uint8Array<ArrayBuffer> | null>;
|
|
842
847
|
getYStateVector(guid?: Guid): Promise<string | null>;
|
|
843
848
|
getSnapshotHash(options: {
|
|
844
849
|
guid?: Guid;
|
|
@@ -890,6 +895,8 @@ declare class YjsStorage {
|
|
|
890
895
|
|
|
891
896
|
type LoadingState = "initial" | "loading" | "loaded";
|
|
892
897
|
type ActorID = Brand<number, "ActorID">;
|
|
898
|
+
/** Number of milliseconds since Unix epoch. */
|
|
899
|
+
type Millis = Brand<number, "Millis">;
|
|
893
900
|
/**
|
|
894
901
|
* Session keys are also known as the "nonce" in the protocol. It's a random,
|
|
895
902
|
* unique, but PRIVATE, identifier for the session, and it's important that
|
|
@@ -938,14 +945,13 @@ declare class BrowserSession<SM, CM extends JsonObject> {
|
|
|
938
945
|
#private;
|
|
939
946
|
readonly version: ProtocolVersion;
|
|
940
947
|
readonly actor: ActorID;
|
|
941
|
-
readonly createdAt:
|
|
948
|
+
readonly createdAt: Millis;
|
|
942
949
|
readonly user: IUserData;
|
|
943
950
|
readonly scopes: string[];
|
|
944
951
|
readonly meta: SM;
|
|
945
952
|
readonly publicMeta?: CM;
|
|
946
|
-
get lastActiveAt():
|
|
953
|
+
get lastActiveAt(): Millis;
|
|
947
954
|
get hasNotifiedClientStorageUpdateError(): boolean;
|
|
948
|
-
markActive(now?: Date): void;
|
|
949
955
|
setHasNotifiedClientStorageUpdateError(): void;
|
|
950
956
|
sendPong(): number;
|
|
951
957
|
send(serverMsg: ServerMsg | ServerMsg[] | PreSerializedServerMsg): number;
|
|
@@ -1048,6 +1054,15 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1048
1054
|
meta: RM;
|
|
1049
1055
|
readonly driver: IStorageDriver;
|
|
1050
1056
|
logger: Logger;
|
|
1057
|
+
/**
|
|
1058
|
+
* While a room is in "maintenance mode", all WebSocket connections to the
|
|
1059
|
+
* room should be rejected until it's pulled out of maintenance mode again.
|
|
1060
|
+
* Maintenance mode should only last a couple of milliseconds, typically.
|
|
1061
|
+
*
|
|
1062
|
+
* This mutex ensures that concurrent destructive operations (like
|
|
1063
|
+
* init-storage, storage-reset, etc.) cannot interfere with one another.
|
|
1064
|
+
*/
|
|
1065
|
+
private readonly _maintenanceMode;
|
|
1051
1066
|
private _loadData$;
|
|
1052
1067
|
private _data;
|
|
1053
1068
|
private _qsize;
|
|
@@ -1060,6 +1075,17 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1060
1075
|
get yjsStorage(): YjsStorage;
|
|
1061
1076
|
get mutex(): Mutex;
|
|
1062
1077
|
private get data();
|
|
1078
|
+
/**
|
|
1079
|
+
* Returns true if the room is currently in maintenance mode.
|
|
1080
|
+
* When in maintenance mode, callers should refuse new WebSocket connections.
|
|
1081
|
+
*/
|
|
1082
|
+
get isInMaintenance(): boolean;
|
|
1083
|
+
/**
|
|
1084
|
+
* Tries to enter maintenance mode and run the given callback exclusively.
|
|
1085
|
+
* If the room is already in maintenance mode, throws E_ALREADY_LOCKED
|
|
1086
|
+
* immediately instead of queuing the request.
|
|
1087
|
+
*/
|
|
1088
|
+
runInMaintenanceMode<T>(callback: () => Promise<T>): Promise<T>;
|
|
1063
1089
|
/**
|
|
1064
1090
|
* Initializes the Room, so it's ready to start accepting connections. Safe
|
|
1065
1091
|
* to call multiple times. After awaiting `room.load()` the Room is ready to
|
|
@@ -1102,6 +1128,8 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1102
1128
|
ticket: Ticket<SM, CM>;
|
|
1103
1129
|
socket: IServerWebSocket;
|
|
1104
1130
|
lastActivity: Date;
|
|
1131
|
+
/** Original session creation time (e.g. from persisted attachment). Required for correct session duration after hibernation. */
|
|
1132
|
+
createdAt?: Date;
|
|
1105
1133
|
}[]): void;
|
|
1106
1134
|
private sendSessionStartMessages;
|
|
1107
1135
|
/**
|
|
@@ -1230,7 +1258,7 @@ declare class Room<RM, SM, CM extends JsonObject, C = undefined> {
|
|
|
1230
1258
|
/**
|
|
1231
1259
|
* Concatenates multiple Uint8Arrays into a single Uint8Array.
|
|
1232
1260
|
*/
|
|
1233
|
-
declare function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array
|
|
1261
|
+
declare function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array<ArrayBuffer>;
|
|
1234
1262
|
/**
|
|
1235
1263
|
* Check if a leased session is expired.
|
|
1236
1264
|
* Returns true if the current time is greater than or equal to updatedAt + ttl.
|
|
@@ -1468,18 +1496,19 @@ declare class InMemoryDriver implements IStorageDriver {
|
|
|
1468
1496
|
initialActor?: number;
|
|
1469
1497
|
initialNodes?: Iterable<[string, SerializedCrdt]>;
|
|
1470
1498
|
});
|
|
1471
|
-
raw_iter_nodes():
|
|
1499
|
+
raw_iter_nodes(): MapIterator<[string, SerializedCrdt]>;
|
|
1472
1500
|
/** Deletes all nodes and replaces them with the given document. */
|
|
1473
1501
|
DANGEROUSLY_reset_nodes(doc: PlainLsonObject): void;
|
|
1474
1502
|
get_meta(key: string): Promise<Json | undefined>;
|
|
1475
1503
|
put_meta(key: string, value: Json): Promise<void>;
|
|
1476
1504
|
delete_meta(key: string): Promise<void>;
|
|
1477
|
-
list_leased_sessions(): Promise<
|
|
1505
|
+
list_leased_sessions(): Promise<MapIterator<[string, LeasedSession]>>;
|
|
1478
1506
|
get_leased_session(sessionId: string): Promise<LeasedSession | undefined>;
|
|
1479
1507
|
put_leased_session(session: LeasedSession): Promise<void>;
|
|
1480
1508
|
delete_leased_session(sessionId: string): Promise<void>;
|
|
1509
|
+
takeRowsWritten(): number;
|
|
1481
1510
|
next_actor(): number;
|
|
1482
|
-
iter_y_updates(docId: YDocId): Promise<IterableIterator<[string, Uint8Array]>>;
|
|
1511
|
+
iter_y_updates(docId: YDocId): Promise<IterableIterator<[string, Uint8Array<ArrayBufferLike>]>>;
|
|
1483
1512
|
write_y_updates(docId: YDocId, key: string, data: Uint8Array): Promise<void>;
|
|
1484
1513
|
delete_y_updates(docId: YDocId, keys: string[]): Promise<void>;
|
|
1485
1514
|
/** @private Only use this in unit tests, never in production. */
|
|
@@ -1487,4 +1516,4 @@ declare class InMemoryDriver implements IStorageDriver {
|
|
|
1487
1516
|
load_nodes_api(): IStorageDriverNodeAPI;
|
|
1488
1517
|
}
|
|
1489
1518
|
|
|
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 };
|
|
1519
|
+
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, type Millis, 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
|
@@ -650,7 +650,7 @@ import {
|
|
|
650
650
|
tryParseJson,
|
|
651
651
|
WebsocketCloseCodes as CloseCode
|
|
652
652
|
} from "@liveblocks/core";
|
|
653
|
-
import { Mutex } from "async-mutex";
|
|
653
|
+
import { Mutex, tryAcquire } from "async-mutex";
|
|
654
654
|
import { array as array2, formatInline } from "decoders";
|
|
655
655
|
import { chunked } from "itertools";
|
|
656
656
|
import { nanoid as nanoid2 } from "nanoid";
|
|
@@ -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 ? Base64.toUint8Array(stateVector) : void 0;
|
|
1448
|
-
} catch
|
|
1451
|
+
} catch {
|
|
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 =
|
|
1737
|
+
const now = createdAt?.getTime() ?? 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
1743
|
const lastPing = __privateGet(this, __socket).getLastPongTimestamp?.();
|
|
1740
|
-
if (lastPing && lastPing > __privateGet(this, __lastActiveAt)) {
|
|
1741
|
-
return lastPing;
|
|
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
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
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 Mutex());
|
|
1824
1836
|
__publicField(this, "_loadData$", null);
|
|
1825
1837
|
__publicField(this, "_data", null);
|
|
1826
1838
|
__publicField(this, "_qsize", 0);
|
|
@@ -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 tryAcquire(this._maintenanceMode).runExclusive(callback);
|
|
1912
|
+
}
|
|
1913
|
+
// ------------------------------------------------------------------------------------
|
|
1884
1914
|
// Public API
|
|
1885
1915
|
// ------------------------------------------------------------------------------------
|
|
1886
1916
|
/**
|
|
@@ -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(
|
|
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
|
}
|
|
@@ -2119,6 +2154,7 @@ var Room = class {
|
|
|
2119
2154
|
);
|
|
2120
2155
|
}) {
|
|
2121
2156
|
const text = typeof data === "string" ? data : raise3("Unsupported message format");
|
|
2157
|
+
this.sessions.get(key)?.markActive();
|
|
2122
2158
|
if (text === "ping") {
|
|
2123
2159
|
await this.handlePing(key, ctx);
|
|
2124
2160
|
} else {
|