@liveblocks/core 1.1.0-test1 → 1.1.0-yjs2

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +125 -37
  2. package/dist/index.js +324 -193
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -636,14 +636,34 @@ declare enum WebsocketCloseCodes {
636
636
  CLOSE_WITHOUT_RETRY = 4999
637
637
  }
638
638
 
639
+ /**
640
+ * Old connection statuses, here for backward-compatibility reasons only.
641
+ */
642
+ declare type LegacyConnectionStatus = "closed" | "authenticating" | "connecting" | "open" | "unavailable" | "failed";
643
+ /**
644
+ * Returns a human-readable status indicating the current connection status of
645
+ * a Room, as returned by `room.getStatus()`. Can be used to implement
646
+ * a connection status badge.
647
+ */
648
+ declare type Status = "initial" | "connecting" | "connected" | "reconnecting" | "disconnected";
649
+ /**
650
+ * Used to report about app-level reconnection issues.
651
+ *
652
+ * Normal (quick) reconnects won't be reported as a "lost connection". Instead,
653
+ * the application will only get an event if the reconnection attempts by the
654
+ * client are taking (much) longer than usual. Definitely a situation you want
655
+ * to inform your users about, for example, by throwing a toast message on
656
+ * screen, or show a "trying to reconnect" banner.
657
+ */
658
+ declare type LostConnectionEvent = "lost" | "restored" | "failed";
639
659
  /**
640
660
  * Arbitrary record that will be used as the authentication "token". It's the
641
661
  * value that is returned by calling the authentication delegate, and will get
642
662
  * passed to the connection factory delegate. This value will be remembered by
643
663
  * the connection manager, but its value will not be interpreted, so it can be
644
- * any object value.
664
+ * any value (except null).
645
665
  */
646
- declare type BaseAuthResult = Record<string, unknown>;
666
+ declare type BaseAuthResult = NonNullable<Json>;
647
667
  declare type Delegates<T extends BaseAuthResult> = {
648
668
  authenticate: () => Promise<T>;
649
669
  createSocket: (token: T) => IWebSocketInstance;
@@ -735,28 +755,6 @@ declare type CustomEvent<TRoomEvent extends Json> = {
735
755
  connectionId: number;
736
756
  event: TRoomEvent;
737
757
  };
738
- declare type Connection = {
739
- status: "closed";
740
- } | {
741
- status: "authenticating";
742
- } | {
743
- status: "connecting";
744
- id: number;
745
- userId?: string;
746
- userInfo?: Json;
747
- isReadOnly: boolean;
748
- } | {
749
- status: "open";
750
- id: number;
751
- userId?: string;
752
- userInfo?: Json;
753
- isReadOnly: boolean;
754
- } | {
755
- status: "unavailable";
756
- } | {
757
- status: "failed";
758
- };
759
- declare type ConnectionStatus = Connection["status"];
760
758
  declare type StorageStatus = "not-loaded" | "loading" | "synchronizing" | "synchronized";
761
759
  interface History {
762
760
  /**
@@ -893,12 +891,47 @@ declare type SubscribeFn<TPresence extends JsonObject, _TStorage extends LsonObj
893
891
  */
894
892
  (type: "error", listener: ErrorCallback): () => void;
895
893
  /**
896
- * Subscribe to connection state updates.
894
+ * @deprecated This API will be removed in a future version of Liveblocks.
895
+ * Prefer using the newer `.subscribe('status')` API.
896
+ *
897
+ * We recommend making the following changes if you use these APIs:
898
+ *
899
+ * OLD APIs NEW APIs
900
+ * .getConnectionState() --> .getStatus()
901
+ * .subscribe('connection') --> .subscribe('status')
902
+ *
903
+ * OLD STATUSES NEW STATUSES
904
+ * closed --> initial
905
+ * authenticating --> connecting
906
+ * connecting --> connecting
907
+ * open --> connected
908
+ * unavailable --> reconnecting
909
+ * failed --> disconnected
910
+ *
911
+ * Subscribe to legacy connection status updates.
912
+ *
913
+ * @returns Unsubscribe function.
914
+ *
915
+ */
916
+ (type: "connection", listener: Callback<LegacyConnectionStatus>): () => void;
917
+ /**
918
+ * Subscribe to connection status updates. The callback will be called any
919
+ * time the status changes.
897
920
  *
898
921
  * @returns Unsubscribe function.
899
922
  *
900
923
  */
901
- (type: "connection", listener: Callback<ConnectionStatus>): () => void;
924
+ (type: "status", listener: Callback<Status>): () => void;
925
+ /**
926
+ * Subscribe to the exceptional event where reconnecting to the Liveblocks
927
+ * servers is taking longer than usual. This typically is a sign of a client
928
+ * that has lost internet connectivity.
929
+ *
930
+ * This isn't problematic (because the Liveblocks client is still trying to
931
+ * reconnect), but it's typically a good idea to inform users about it if
932
+ * the connection takes too long to recover.
933
+ */
934
+ (type: "lost-connection", listener: Callback<LostConnectionEvent>): () => void;
902
935
  /**
903
936
  * Subscribes to changes made on a Live structure. Returns an unsubscribe function.
904
937
  * In a future version, we will also expose what exactly changed in the Live structure.
@@ -975,7 +1008,30 @@ declare type Room<TPresence extends JsonObject, TStorage extends LsonObject, TUs
975
1008
  * metadata and connection ID (from the auth server).
976
1009
  */
977
1010
  isSelfAware(): boolean;
978
- getConnectionState(): ConnectionStatus;
1011
+ /**
1012
+ * @deprecated This API will be removed in a future version of Liveblocks.
1013
+ * Prefer using `.getStatus()` instead.
1014
+ *
1015
+ * We recommend making the following changes if you use these APIs:
1016
+ *
1017
+ * OLD APIs NEW APIs
1018
+ * .getConnectionState() --> .getStatus()
1019
+ * .subscribe('connection') --> .subscribe('status')
1020
+ *
1021
+ * OLD STATUSES NEW STATUSES
1022
+ * closed --> initial
1023
+ * authenticating --> connecting
1024
+ * connecting --> connecting
1025
+ * open --> connected
1026
+ * unavailable --> reconnecting
1027
+ * failed --> disconnected
1028
+ */
1029
+ getConnectionState(): LegacyConnectionStatus;
1030
+ /**
1031
+ * Return the current connection status for this room. Can be used to display
1032
+ * a status badge for your Liveblocks connection.
1033
+ */
1034
+ getStatus(): Status;
979
1035
  readonly subscribe: SubscribeFn<TPresence, TStorage, TUserMeta, TRoomEvent>;
980
1036
  /**
981
1037
  * Room's history contains functions that let you undo and redo operation made on by the current client on the presence and storage.
@@ -1021,6 +1077,17 @@ declare type Room<TPresence extends JsonObject, TStorage extends LsonObject, TUs
1021
1077
  */
1022
1078
  addToHistory: boolean;
1023
1079
  }): void;
1080
+ /**
1081
+ *
1082
+ * Sends YJS document updates to liveblocks server
1083
+ *
1084
+ * @param {string} data the doc update to send to the server, base64 encoded uint8array
1085
+ */
1086
+ updateDoc(data: string): void;
1087
+ /**
1088
+ * Sends a request for the current document from liveblocks server
1089
+ */
1090
+ getDoc(stateVector?: string): void;
1024
1091
  /**
1025
1092
  * Broadcasts an event to other users in the room. Event broadcasted to the room can be listened with {@link Room.subscribe}("event").
1026
1093
  * @param {any} event the event to broadcast. Should be serializable to JSON
@@ -1056,6 +1123,9 @@ declare type Room<TPresence extends JsonObject, TStorage extends LsonObject, TUs
1056
1123
  */
1057
1124
  getStorageSnapshot(): LiveObject<TStorage> | null;
1058
1125
  readonly events: {
1126
+ readonly connection: Observable<LegacyConnectionStatus>;
1127
+ readonly status: Observable<Status>;
1128
+ readonly lostConnection: Observable<LostConnectionEvent>;
1059
1129
  readonly customEvent: Observable<{
1060
1130
  connectionId: number;
1061
1131
  event: TRoomEvent;
@@ -1066,15 +1136,16 @@ declare type Room<TPresence extends JsonObject, TStorage extends LsonObject, TUs
1066
1136
  event: OthersEvent<TPresence, TUserMeta>;
1067
1137
  }>;
1068
1138
  readonly error: Observable<Error>;
1069
- readonly connection: Observable<ConnectionStatus>;
1070
1139
  readonly storage: Observable<StorageUpdate[]>;
1071
1140
  readonly history: Observable<HistoryEvent>;
1072
1141
  /**
1073
- * Subscribe to the storage loaded event. Will fire at most once during the
1074
- * lifetime of a Room.
1142
+ * Subscribe to the storage loaded event. Will fire any time a full Storage
1143
+ * copy is downloaded. (This happens after the initial connect, and on
1144
+ * every reconnect.)
1075
1145
  */
1076
1146
  readonly storageDidLoad: Observable<void>;
1077
1147
  readonly storageStatus: Observable<StorageStatus>;
1148
+ readonly docUpdated: Observable<string[]>;
1078
1149
  };
1079
1150
  /**
1080
1151
  * Batches modifications made during the given function.
@@ -1168,6 +1239,7 @@ declare type AuthEndpoint = string | ((room: string) => Promise<{
1168
1239
  */
1169
1240
  declare type ClientOptions = {
1170
1241
  throttle?: number;
1242
+ lostConnectionTimeout?: number;
1171
1243
  polyfills?: Polyfills;
1172
1244
  unstable_fallbackToHTTP?: boolean;
1173
1245
  /**
@@ -1396,12 +1468,14 @@ declare enum ClientMsgCode {
1396
1468
  UPDATE_PRESENCE = 100,
1397
1469
  BROADCAST_EVENT = 103,
1398
1470
  FETCH_STORAGE = 200,
1399
- UPDATE_STORAGE = 201
1471
+ UPDATE_STORAGE = 201,
1472
+ FETCH_DOC = 300,
1473
+ UPDATE_DOC = 301
1400
1474
  }
1401
1475
  /**
1402
1476
  * Messages that can be sent from the client to the server.
1403
1477
  */
1404
- declare type ClientMsg<TPresence extends JsonObject, TRoomEvent extends Json> = BroadcastEventClientMsg<TRoomEvent> | UpdatePresenceClientMsg<TPresence> | UpdateStorageClientMsg | FetchStorageClientMsg;
1478
+ declare type ClientMsg<TPresence extends JsonObject, TRoomEvent extends Json> = BroadcastEventClientMsg<TRoomEvent> | UpdatePresenceClientMsg<TPresence> | UpdateStorageClientMsg | FetchStorageClientMsg | FetchDocClientMsg | UpdateDocClientMsg;
1405
1479
  declare type BroadcastEventClientMsg<TRoomEvent extends Json> = {
1406
1480
  type: ClientMsgCode.BROADCAST_EVENT;
1407
1481
  event: TRoomEvent;
@@ -1439,6 +1513,14 @@ declare type UpdateStorageClientMsg = {
1439
1513
  declare type FetchStorageClientMsg = {
1440
1514
  readonly type: ClientMsgCode.FETCH_STORAGE;
1441
1515
  };
1516
+ declare type FetchDocClientMsg = {
1517
+ readonly type: ClientMsgCode.FETCH_DOC;
1518
+ readonly vector?: string;
1519
+ };
1520
+ declare type UpdateDocClientMsg = {
1521
+ readonly type: ClientMsgCode.UPDATE_DOC;
1522
+ readonly data: string;
1523
+ };
1442
1524
 
1443
1525
  declare type IdTuple<T> = [id: string, value: T];
1444
1526
  declare enum CrdtType {
@@ -1488,12 +1570,14 @@ declare enum ServerMsgCode {
1488
1570
  ROOM_STATE = 104,
1489
1571
  INITIAL_STORAGE_STATE = 200,
1490
1572
  UPDATE_STORAGE = 201,
1491
- REJECT_STORAGE_OP = 299
1573
+ REJECT_STORAGE_OP = 299,
1574
+ FETCH_DOC = 300,
1575
+ DOC_UPDATE = 301
1492
1576
  }
1493
1577
  /**
1494
1578
  * Messages that can be sent from the server to the client.
1495
1579
  */
1496
- declare type ServerMsg<TPresence extends JsonObject, TUserMeta extends BaseUserMeta, TRoomEvent extends Json> = UpdatePresenceServerMsg<TPresence> | UserJoinServerMsg<TUserMeta> | UserLeftServerMsg | BroadcastedEventServerMsg<TRoomEvent> | RoomStateServerMsg<TUserMeta> | InitialDocumentStateServerMsg | UpdateStorageServerMsg | RejectedStorageOpServerMsg;
1580
+ declare type ServerMsg<TPresence extends JsonObject, TUserMeta extends BaseUserMeta, TRoomEvent extends Json> = UpdatePresenceServerMsg<TPresence> | UserJoinServerMsg<TUserMeta> | UserLeftServerMsg | BroadcastedEventServerMsg<TRoomEvent> | RoomStateServerMsg<TUserMeta> | InitialDocumentStateServerMsg | UpdateStorageServerMsg | RejectedStorageOpServerMsg | FetchDoc;
1497
1581
  /**
1498
1582
  * Sent by the WebSocket server and broadcasted to all clients to announce that
1499
1583
  * a User updated their presence. For example, when a user moves their cursor.
@@ -1576,6 +1660,10 @@ declare type UserLeftServerMsg = {
1576
1660
  readonly type: ServerMsgCode.USER_LEFT;
1577
1661
  readonly actor: number;
1578
1662
  };
1663
+ declare type FetchDoc = {
1664
+ readonly type: ServerMsgCode.FETCH_DOC;
1665
+ readonly data: string[];
1666
+ };
1579
1667
  /**
1580
1668
  * Sent by the WebSocket server and broadcasted to all clients to announce that
1581
1669
  * a User broadcasted an Event to everyone in the Room.
@@ -1762,7 +1850,7 @@ declare type ClientToPanelMessage =
1762
1850
  | {
1763
1851
  msg: "room::sync::full";
1764
1852
  roomId: string;
1765
- status: ConnectionStatus;
1853
+ status: Status;
1766
1854
  storage: readonly LsonTreeNode[] | null;
1767
1855
  me: UserTreeNode | null;
1768
1856
  others: readonly UserTreeNode[];
@@ -1773,7 +1861,7 @@ declare type ClientToPanelMessage =
1773
1861
  | {
1774
1862
  msg: "room::sync::partial";
1775
1863
  roomId: string;
1776
- status?: ConnectionStatus;
1864
+ status?: Status;
1777
1865
  storage?: readonly LsonTreeNode[];
1778
1866
  me?: UserTreeNode;
1779
1867
  others?: readonly UserTreeNode[];
@@ -1824,4 +1912,4 @@ declare type EnsureJson<T> = [
1824
1912
  [K in keyof T]: EnsureJson<T[K]>;
1825
1913
  };
1826
1914
 
1827
- export { AckOp, AppOnlyAuthToken, AuthToken, BaseAuthResult, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, ConnectionStatus, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, Delegates, DeleteCrdtOp, DeleteObjectKeyOp, DevToolsTreeNode as DevTools, protocol as DevToolsMsg, EnsureJson, FetchStorageClientMsg, History, IWebSocket, IWebSocketCloseEvent, IWebSocketEvent, IWebSocketInstance, IWebSocketMessageEvent, IdTuple, Immutable, InitialDocumentStateServerMsg, Json, JsonArray, JsonObject, JsonScalar, LiveList, LiveListUpdate, LiveMap, LiveMapUpdate, LiveNode, LiveObject, LiveObjectUpdate, LiveStructure, Lson, LsonObject, NodeMap, Op, OpCode, Others, ParentToChildNodeMap, PlainLson, PlainLsonFields, PlainLsonList, PlainLsonMap, PlainLsonObject, RejectedStorageOpServerMsg, Resolve, Room, RoomAuthToken, RoomInitializers, RoomStateServerMsg, SerializedChild, SerializedCrdt, SerializedList, SerializedMap, SerializedObject, SerializedRegister, SerializedRootObject, ServerMsg, ServerMsgCode, SetParentKeyOp, StorageStatus, StorageUpdate, ToImmutable, ToJson, UpdateObjectOp, UpdatePresenceClientMsg, UpdatePresenceServerMsg, UpdateStorageClientMsg, UpdateStorageServerMsg, User, UserJoinServerMsg, UserLeftServerMsg, WebsocketCloseCodes, asArrayWithLegacyMethods, asPos, assert, assertNever, b64decode, createClient, deprecate, deprecateIf, errorIf, freeze, isAppOnlyAuthToken, isAuthToken, isChildCrdt, isJsonArray, isJsonObject, isJsonScalar, isPlainObject, isRoomAuthToken, isRootCrdt, legacy_patchImmutableObject, lsonToJson, makePosition, nn, patchLiveObjectKey, shallow, throwUsageError, toPlainLson, tryParseJson, withTimeout };
1915
+ export { AckOp, AppOnlyAuthToken, AuthToken, BaseAuthResult, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, Delegates, DeleteCrdtOp, DeleteObjectKeyOp, DevToolsTreeNode as DevTools, protocol as DevToolsMsg, EnsureJson, FetchDocClientMsg, FetchStorageClientMsg, History, IWebSocket, IWebSocketCloseEvent, IWebSocketEvent, IWebSocketInstance, IWebSocketMessageEvent, IdTuple, Immutable, InitialDocumentStateServerMsg, Json, JsonArray, JsonObject, JsonScalar, LegacyConnectionStatus, LiveList, LiveListUpdate, LiveMap, LiveMapUpdate, LiveNode, LiveObject, LiveObjectUpdate, LiveStructure, LostConnectionEvent, Lson, LsonObject, NodeMap, Op, OpCode, Others, ParentToChildNodeMap, PlainLson, PlainLsonFields, PlainLsonList, PlainLsonMap, PlainLsonObject, RejectedStorageOpServerMsg, Resolve, Room, RoomAuthToken, RoomInitializers, RoomStateServerMsg, SerializedChild, SerializedCrdt, SerializedList, SerializedMap, SerializedObject, SerializedRegister, SerializedRootObject, ServerMsg, ServerMsgCode, SetParentKeyOp, Status, StorageStatus, StorageUpdate, ToImmutable, ToJson, UpdateDocClientMsg, UpdateObjectOp, UpdatePresenceClientMsg, UpdatePresenceServerMsg, UpdateStorageClientMsg, UpdateStorageServerMsg, User, UserJoinServerMsg, UserLeftServerMsg, WebsocketCloseCodes, asArrayWithLegacyMethods, asPos, assert, assertNever, b64decode, createClient, deprecate, deprecateIf, errorIf, freeze, isAppOnlyAuthToken, isAuthToken, isChildCrdt, isJsonArray, isJsonObject, isJsonScalar, isPlainObject, isRoomAuthToken, isRootCrdt, legacy_patchImmutableObject, lsonToJson, makePosition, nn, patchLiveObjectKey, shallow, throwUsageError, toPlainLson, tryParseJson, withTimeout };
package/dist/index.js CHANGED
@@ -157,7 +157,7 @@ var onMessageFromPanel = eventSource.observable;
157
157
  // src/devtools/index.ts
158
158
  var VERSION = true ? (
159
159
  /* istanbul ignore next */
160
- "1.1.0-test1"
160
+ "1.1.0-yjs2"
161
161
  ) : "dev";
162
162
  var _devtoolsSetupHasRun = false;
163
163
  function setupDevTools(getAllRooms) {
@@ -199,7 +199,7 @@ function startSyncStream(room) {
199
199
  fullSync(room);
200
200
  unsubsByRoomId.set(room.id, [
201
201
  // When the connection status changes
202
- room.events.connection.subscribe(() => partialSyncConnection(room)),
202
+ room.events.status.subscribe(() => partialSyncConnection(room)),
203
203
  // When storage initializes, send the update
204
204
  room.events.storageDidLoad.subscribeOnce(() => partialSyncStorage(room)),
205
205
  // Any time storage updates, send the new storage root
@@ -213,7 +213,7 @@ function partialSyncConnection(room) {
213
213
  sendToPanel({
214
214
  msg: "room::sync::partial",
215
215
  roomId: room.id,
216
- status: room.getConnectionState()
216
+ status: room.getStatus()
217
217
  });
218
218
  }
219
219
  function partialSyncStorage(room) {
@@ -254,7 +254,7 @@ function fullSync(room) {
254
254
  sendToPanel({
255
255
  msg: "room::sync::full",
256
256
  roomId: room.id,
257
- status: room.getConnectionState(),
257
+ status: room.getStatus(),
258
258
  storage: (_a = root == null ? void 0 : root.toTreeNode("root").payload) != null ? _a : null,
259
259
  me,
260
260
  others
@@ -823,22 +823,37 @@ function withTimeout(promise, millis, errmsg = "Timed out") {
823
823
  }
824
824
 
825
825
  // src/connection.ts
826
- function toPublicConnectionStatus(state) {
826
+ function newToLegacyStatus(status) {
827
+ switch (status) {
828
+ case "connecting":
829
+ return "connecting";
830
+ case "connected":
831
+ return "open";
832
+ case "reconnecting":
833
+ return "unavailable";
834
+ case "disconnected":
835
+ return "failed";
836
+ case "initial":
837
+ return "closed";
838
+ default:
839
+ return "closed";
840
+ }
841
+ }
842
+ function toNewConnectionStatus(machine) {
843
+ const state = machine.currentState;
827
844
  switch (state) {
828
845
  case "@ok.connected":
829
846
  case "@ok.awaiting-pong":
830
- return "open";
847
+ return "connected";
831
848
  case "@idle.initial":
832
- return "closed";
849
+ return "initial";
833
850
  case "@auth.busy":
834
851
  case "@auth.backoff":
835
- return "authenticating";
836
852
  case "@connecting.busy":
837
- return "connecting";
838
853
  case "@connecting.backoff":
839
- return "unavailable";
854
+ return machine.context.successCount > 0 ? "reconnecting" : "connecting";
840
855
  case "@idle.failed":
841
- return "failed";
856
+ return "disconnected";
842
857
  default:
843
858
  return assertNever(state, "Unknown state");
844
859
  }
@@ -873,6 +888,9 @@ function increaseBackoffDelayAggressively(context) {
873
888
  backoffDelay: nextBackoffDelay(context.backoffDelay, BACKOFF_DELAYS_SLOW)
874
889
  });
875
890
  }
891
+ function resetSuccessCount(context) {
892
+ context.patch({ successCount: 0 });
893
+ }
876
894
  function log(level, message) {
877
895
  const logger = level === 2 /* ERROR */ ? error : level === 1 /* WARN */ ? warn : (
878
896
  /* black hole */
@@ -883,9 +901,34 @@ function log(level, message) {
883
901
  logger(message);
884
902
  };
885
903
  }
886
- function sendHeartbeat(ctx) {
887
- var _a;
888
- (_a = ctx.socket) == null ? void 0 : _a.send("ping");
904
+ function logPrematureErrorOrCloseEvent(e) {
905
+ const conn = "Connection to Liveblocks websocket server";
906
+ return (ctx) => {
907
+ if (e instanceof Error) {
908
+ warn(`${conn} could not be established. ${String(e)}`);
909
+ } else {
910
+ warn(
911
+ isCloseEvent(e) ? `${conn} closed prematurely (code: ${e.code}). Retrying in ${ctx.backoffDelay}ms.` : `${conn} could not be established.`
912
+ );
913
+ }
914
+ };
915
+ }
916
+ function logCloseEvent(event) {
917
+ return (ctx) => {
918
+ warn(
919
+ `Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${ctx.backoffDelay}ms.`
920
+ );
921
+ };
922
+ }
923
+ var logPermanentClose = log(
924
+ 1 /* WARN */,
925
+ "Connection to WebSocket closed permanently. Won't retry."
926
+ );
927
+ function isCloseEvent(error2) {
928
+ return !(error2 instanceof Error) && error2.type === "close";
929
+ }
930
+ function isCustomCloseEvent(error2) {
931
+ return isCloseEvent(error2) && error2.code >= 4e3 && error2.code < 4100;
889
932
  }
890
933
  function enableTracing(machine) {
891
934
  const start = (/* @__PURE__ */ new Date()).getTime();
@@ -916,16 +959,18 @@ function defineConnectivityEvents(machine) {
916
959
  const statusDidChange = makeEventSource();
917
960
  const didConnect = makeEventSource();
918
961
  const didDisconnect = makeEventSource();
919
- let oldPublicStatus = null;
920
- const unsubscribe = machine.events.didEnterState.subscribe((newState) => {
921
- const newPublicStatus = toPublicConnectionStatus(newState);
922
- statusDidChange.notify(newPublicStatus);
923
- if (oldPublicStatus === "open" && newPublicStatus !== "open") {
962
+ let lastStatus = null;
963
+ const unsubscribe = machine.events.didEnterState.subscribe(() => {
964
+ const currStatus = toNewConnectionStatus(machine);
965
+ if (currStatus !== lastStatus) {
966
+ statusDidChange.notify(currStatus);
967
+ }
968
+ if (lastStatus === "connected" && currStatus !== "connected") {
924
969
  didDisconnect.notify();
925
- } else if (oldPublicStatus !== "open" && newPublicStatus === "open") {
970
+ } else if (lastStatus !== "connected" && currStatus === "connected") {
926
971
  didConnect.notify();
927
972
  }
928
- oldPublicStatus = newPublicStatus;
973
+ lastStatus = currStatus;
929
974
  });
930
975
  return {
931
976
  statusDidChange: statusDidChange.observable,
@@ -940,6 +985,7 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
940
985
  onMessage.pause();
941
986
  const onLiveblocksError = makeEventSource();
942
987
  const initialContext = {
988
+ successCount: 0,
943
989
  token: null,
944
990
  socket: null,
945
991
  backoffDelay: RESET_DELAY
@@ -948,11 +994,11 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
948
994
  machine.addTransitions("*", {
949
995
  RECONNECT: {
950
996
  target: "@auth.backoff",
951
- effect: increaseBackoffDelay
997
+ effect: [increaseBackoffDelay, resetSuccessCount]
952
998
  },
953
999
  DISCONNECT: "@idle.initial"
954
1000
  });
955
- machine.addTransitions("@idle.*", {
1001
+ machine.onEnter("@idle.*", resetSuccessCount).addTransitions("@idle.*", {
956
1002
  CONNECT: (_, ctx) => (
957
1003
  // If we still have a known token, try to reconnect to the socket directly,
958
1004
  // otherwise, try to obtain a new token
@@ -1097,41 +1143,41 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1097
1143
  effect: log(2 /* ERROR */, err.message)
1098
1144
  };
1099
1145
  }
1146
+ if (isCloseEvent(err) && err.code === 4999) {
1147
+ return {
1148
+ target: "@idle.failed",
1149
+ effect: log(2 /* ERROR */, err.reason)
1150
+ };
1151
+ }
1152
+ if (isCustomCloseEvent(err) && err.code !== 4001) {
1153
+ return {
1154
+ target: "@connecting.backoff",
1155
+ effect: [
1156
+ increaseBackoffDelayAggressively,
1157
+ logPrematureErrorOrCloseEvent(err)
1158
+ ]
1159
+ };
1160
+ }
1100
1161
  return {
1101
1162
  target: "@auth.backoff",
1102
- effect: [
1103
- // Increase the backoff delay conditionally
1104
- // TODO: This is ugly. DRY this up with the other code 40xx checks elsewhere.
1105
- !(err instanceof Error) && err.type === "close" && err.code >= 4e3 && err.code <= 4100 ? increaseBackoffDelayAggressively : increaseBackoffDelay,
1106
- // Produce a useful log message
1107
- (ctx) => {
1108
- if (err instanceof Error) {
1109
- warn(String(err));
1110
- } else {
1111
- warn(
1112
- err.type === "close" ? `Connection to Liveblocks websocket server closed prematurely (code: ${err.code}). Retrying in ${ctx.backoffDelay}ms.` : "Connection to Liveblocks websocket server could not be established."
1113
- );
1114
- }
1115
- }
1116
- ]
1163
+ effect: [increaseBackoffDelay, logPrematureErrorOrCloseEvent(err)]
1117
1164
  };
1118
1165
  }
1119
1166
  );
1120
- machine.addTimedTransition("@ok.connected", HEARTBEAT_INTERVAL, {
1167
+ const sendHeartbeat = {
1121
1168
  target: "@ok.awaiting-pong",
1122
- effect: sendHeartbeat
1123
- }).addTransitions("@ok.connected", {
1124
- WINDOW_GOT_FOCUS: { target: "@ok.awaiting-pong", effect: sendHeartbeat }
1125
- });
1126
- const noPongAction = {
1127
- target: "@connecting.busy",
1128
- // Log implicit connection loss and drop the current open socket
1129
- effect: log(
1130
- 1 /* WARN */,
1131
- "Received no pong from server, assume implicit connection loss."
1132
- )
1169
+ effect: (ctx) => {
1170
+ var _a;
1171
+ (_a = ctx.socket) == null ? void 0 : _a.send("ping");
1172
+ }
1133
1173
  };
1134
- machine.onEnter("@ok.*", () => {
1174
+ machine.addTimedTransition("@ok.connected", HEARTBEAT_INTERVAL, sendHeartbeat).addTransitions("@ok.connected", {
1175
+ NAVIGATOR_OFFLINE: sendHeartbeat,
1176
+ // Don't take the browser's word for it when it says it's offline. Do a ping/pong to make sure.
1177
+ WINDOW_GOT_FOCUS: sendHeartbeat
1178
+ });
1179
+ machine.onEnter("@ok.*", (ctx) => {
1180
+ ctx.patch({ successCount: ctx.successCount + 1 });
1135
1181
  const timerID = setTimeout(
1136
1182
  // On the next tick, start delivering all messages that have already
1137
1183
  // been received, and continue synchronous delivery of all future
@@ -1139,13 +1185,20 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1139
1185
  onMessage.unpause,
1140
1186
  0
1141
1187
  );
1142
- return (ctx) => {
1143
- teardownSocket(ctx.socket);
1144
- ctx.patch({ socket: null });
1188
+ return (ctx2) => {
1189
+ teardownSocket(ctx2.socket);
1190
+ ctx2.patch({ socket: null });
1145
1191
  clearTimeout(timerID);
1146
1192
  onMessage.pause();
1147
1193
  };
1148
- }).addTimedTransition("@ok.awaiting-pong", PONG_TIMEOUT, noPongAction).addTransitions("@ok.awaiting-pong", { PONG_TIMEOUT: noPongAction }).addTransitions("@ok.awaiting-pong", { PONG: "@ok.connected" }).addTransitions("@ok.*", {
1194
+ }).addTransitions("@ok.awaiting-pong", { PONG: "@ok.connected" }).addTimedTransition("@ok.awaiting-pong", PONG_TIMEOUT, {
1195
+ target: "@connecting.busy",
1196
+ // Log implicit connection loss and drop the current open socket
1197
+ effect: log(
1198
+ 1 /* WARN */,
1199
+ "Received no pong from server, assume implicit connection loss."
1200
+ )
1201
+ }).addTransitions("@ok.*", {
1149
1202
  // When a socket receives an error, this can cause the closing of the
1150
1203
  // socket, or not. So always check to see if the socket is still OPEN or
1151
1204
  // not. When still OPEN, don't transition.
@@ -1163,37 +1216,31 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1163
1216
  if (e.event.code === 4999) {
1164
1217
  return {
1165
1218
  target: "@idle.failed",
1166
- effect: log(
1167
- 1 /* WARN */,
1168
- "Connection to WebSocket closed permanently. Won't retry."
1169
- )
1219
+ effect: logPermanentClose
1170
1220
  };
1171
1221
  }
1172
- if (e.event.code >= 4e3 && e.event.code <= 4100) {
1222
+ if (e.event.code === 4001) {
1223
+ return {
1224
+ target: "@auth.backoff",
1225
+ effect: [increaseBackoffDelay, logCloseEvent(e.event)]
1226
+ };
1227
+ }
1228
+ if (isCustomCloseEvent(e.event)) {
1173
1229
  return {
1174
1230
  target: "@connecting.backoff",
1175
1231
  effect: [
1176
1232
  increaseBackoffDelayAggressively,
1177
- (ctx) => warn(
1178
- `Connection to Liveblocks websocket server closed (code: ${e.event.code}). Retrying in ${ctx.backoffDelay}ms.`
1179
- ),
1180
- (_, { event }) => {
1181
- if (event.code >= 4e3 && event.code <= 4100) {
1182
- const err = new LiveblocksError(event.reason, event.code);
1183
- onLiveblocksError.notify(err);
1184
- }
1233
+ logCloseEvent(e.event),
1234
+ () => {
1235
+ const err = new LiveblocksError(e.event.reason, e.event.code);
1236
+ onLiveblocksError.notify(err);
1185
1237
  }
1186
1238
  ]
1187
1239
  };
1188
1240
  }
1189
1241
  return {
1190
1242
  target: "@connecting.backoff",
1191
- effect: [
1192
- increaseBackoffDelay,
1193
- (ctx) => warn(
1194
- `Connection to Liveblocks websocket server closed (code: ${e.event.code}). Retrying in ${ctx.backoffDelay}ms.`
1195
- )
1196
- ]
1243
+ effect: [increaseBackoffDelay, logCloseEvent(e.event)]
1197
1244
  };
1198
1245
  }
1199
1246
  });
@@ -1202,7 +1249,10 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1202
1249
  const win = typeof window !== "undefined" ? window : void 0;
1203
1250
  const root = win != null ? win : doc;
1204
1251
  machine.onEnter("*", (ctx) => {
1205
- function onBackOnline() {
1252
+ function onNetworkOffline() {
1253
+ machine.send({ type: "NAVIGATOR_OFFLINE" });
1254
+ }
1255
+ function onNetworkBackOnline() {
1206
1256
  machine.send({ type: "NAVIGATOR_ONLINE" });
1207
1257
  }
1208
1258
  function onVisibilityChange() {
@@ -1210,11 +1260,13 @@ function createConnectionStateMachine(delegates, enableDebugLogging) {
1210
1260
  machine.send({ type: "WINDOW_GOT_FOCUS" });
1211
1261
  }
1212
1262
  }
1213
- win == null ? void 0 : win.addEventListener("online", onBackOnline);
1263
+ win == null ? void 0 : win.addEventListener("online", onNetworkBackOnline);
1264
+ win == null ? void 0 : win.addEventListener("offline", onNetworkOffline);
1214
1265
  root == null ? void 0 : root.addEventListener("visibilitychange", onVisibilityChange);
1215
1266
  return () => {
1216
1267
  root == null ? void 0 : root.removeEventListener("visibilitychange", onVisibilityChange);
1217
- win == null ? void 0 : win.removeEventListener("online", onBackOnline);
1268
+ win == null ? void 0 : win.removeEventListener("online", onNetworkBackOnline);
1269
+ win == null ? void 0 : win.removeEventListener("offline", onNetworkOffline);
1218
1270
  teardownSocket(ctx.socket);
1219
1271
  };
1220
1272
  });
@@ -1249,22 +1301,21 @@ var ManagedSocket = class {
1249
1301
  this.events = events;
1250
1302
  this.cleanups = cleanups;
1251
1303
  }
1252
- get status() {
1304
+ getLegacyStatus() {
1305
+ return newToLegacyStatus(this.getStatus());
1306
+ }
1307
+ getStatus() {
1253
1308
  try {
1254
- return toPublicConnectionStatus(this.machine.currentState);
1309
+ return toNewConnectionStatus(this.machine);
1255
1310
  } catch (e) {
1256
- return "closed";
1311
+ return "initial";
1257
1312
  }
1258
1313
  }
1259
1314
  /**
1260
1315
  * Returns the current auth token.
1261
1316
  */
1262
1317
  get token() {
1263
- const tok = this.machine.context.token;
1264
- if (tok === null) {
1265
- throw new Error("Unexpected null token here");
1266
- }
1267
- return tok;
1318
+ return this.machine.context.token;
1268
1319
  }
1269
1320
  /**
1270
1321
  * Call this method to try to connect to a WebSocket. This only has an effect
@@ -3935,6 +3986,8 @@ var ClientMsgCode = /* @__PURE__ */ ((ClientMsgCode2) => {
3935
3986
  ClientMsgCode2[ClientMsgCode2["BROADCAST_EVENT"] = 103] = "BROADCAST_EVENT";
3936
3987
  ClientMsgCode2[ClientMsgCode2["FETCH_STORAGE"] = 200] = "FETCH_STORAGE";
3937
3988
  ClientMsgCode2[ClientMsgCode2["UPDATE_STORAGE"] = 201] = "UPDATE_STORAGE";
3989
+ ClientMsgCode2[ClientMsgCode2["FETCH_DOC"] = 300] = "FETCH_DOC";
3990
+ ClientMsgCode2[ClientMsgCode2["UPDATE_DOC"] = 301] = "UPDATE_DOC";
3938
3991
  return ClientMsgCode2;
3939
3992
  })(ClientMsgCode || {});
3940
3993
 
@@ -3948,6 +4001,8 @@ var ServerMsgCode = /* @__PURE__ */ ((ServerMsgCode2) => {
3948
4001
  ServerMsgCode2[ServerMsgCode2["INITIAL_STORAGE_STATE"] = 200] = "INITIAL_STORAGE_STATE";
3949
4002
  ServerMsgCode2[ServerMsgCode2["UPDATE_STORAGE"] = 201] = "UPDATE_STORAGE";
3950
4003
  ServerMsgCode2[ServerMsgCode2["REJECT_STORAGE_OP"] = 299] = "REJECT_STORAGE_OP";
4004
+ ServerMsgCode2[ServerMsgCode2["FETCH_DOC"] = 300] = "FETCH_DOC";
4005
+ ServerMsgCode2[ServerMsgCode2["DOC_UPDATE"] = 301] = "DOC_UPDATE";
3951
4006
  return ServerMsgCode2;
3952
4007
  })(ServerMsgCode || {});
3953
4008
 
@@ -4173,9 +4228,6 @@ function makeIdFactory(connectionId) {
4173
4228
  let count = 0;
4174
4229
  return () => `${connectionId}:${count++}`;
4175
4230
  }
4176
- function isConnectionSelfAware(connection) {
4177
- return connection.status === "open" || connection.status === "connecting";
4178
- }
4179
4231
  function userToTreeNode(key, user) {
4180
4232
  return {
4181
4233
  type: "User",
@@ -4204,7 +4256,6 @@ function createRoom(options, config) {
4204
4256
  config.enableDebugLogging
4205
4257
  );
4206
4258
  const context = {
4207
- lastConnectionId: null,
4208
4259
  buffer: {
4209
4260
  flushTimerID: void 0,
4210
4261
  lastFlushedAt: 0,
@@ -4218,7 +4269,7 @@ function createRoom(options, config) {
4218
4269
  messages: [],
4219
4270
  storageOperations: []
4220
4271
  },
4221
- connection: new ValueRef({ status: "closed" }),
4272
+ sessionInfo: new ValueRef(null),
4222
4273
  me: new MeRef(initialPresence),
4223
4274
  others: new OthersRef(),
4224
4275
  initialStorage,
@@ -4238,55 +4289,78 @@ function createRoom(options, config) {
4238
4289
  };
4239
4290
  const doNotBatchUpdates = (cb) => cb();
4240
4291
  const batchUpdates = (_d = config.unstable_batchedUpdates) != null ? _d : doNotBatchUpdates;
4292
+ let lastToken;
4241
4293
  function onStatusDidChange(newStatus) {
4242
- if (newStatus === "open" || newStatus === "connecting") {
4243
- context.connection.set({
4244
- status: newStatus,
4245
- id: managedSocket.token.parsed.actor,
4246
- userInfo: managedSocket.token.parsed.info,
4247
- userId: managedSocket.token.parsed.id,
4248
- isReadOnly: isStorageReadOnly(managedSocket.token.parsed.scopes)
4294
+ var _a2;
4295
+ const token = (_a2 = managedSocket.token) == null ? void 0 : _a2.parsed;
4296
+ if (token !== void 0 && token !== lastToken) {
4297
+ context.sessionInfo.set({
4298
+ id: token.actor,
4299
+ userInfo: token.info,
4300
+ userId: token.id,
4301
+ isReadOnly: isStorageReadOnly(token.scopes)
4249
4302
  });
4250
- } else {
4251
- context.connection.set({ status: newStatus });
4303
+ lastToken = token;
4252
4304
  }
4253
4305
  batchUpdates(() => {
4254
- eventHub.connection.notify(newStatus);
4306
+ eventHub.status.notify(newStatus);
4307
+ eventHub.connection.notify(newToLegacyStatus(newStatus));
4255
4308
  });
4256
4309
  }
4257
- function onDidConnect() {
4258
- const conn = context.connection.current;
4259
- if (conn.status !== "open") {
4260
- throw new Error("Unexpected non-open state here");
4261
- }
4262
- if (context.lastConnectionId !== void 0) {
4263
- context.buffer.me = {
4264
- type: "full",
4265
- data: (
4266
- // Because state.me.current is a readonly object, we'll have to
4267
- // make a copy here. Otherwise, type errors happen later when
4268
- // "patching" my presence.
4269
- __spreadValues({}, context.me.current)
4270
- )
4271
- };
4272
- tryFlushing();
4310
+ let _connectionLossTimerId;
4311
+ let _hasLostConnection = false;
4312
+ function handleConnectionLossEvent(newStatus) {
4313
+ if (newStatus === "reconnecting") {
4314
+ _connectionLossTimerId = setTimeout(() => {
4315
+ batchUpdates(() => {
4316
+ eventHub.lostConnection.notify("lost");
4317
+ _hasLostConnection = true;
4318
+ context.others.clearOthers();
4319
+ notify({ others: [{ type: "reset" }] }, doNotBatchUpdates);
4320
+ });
4321
+ }, config.lostConnectionTimeout);
4322
+ } else {
4323
+ clearTimeout(_connectionLossTimerId);
4324
+ if (_hasLostConnection) {
4325
+ if (newStatus === "disconnected") {
4326
+ batchUpdates(() => {
4327
+ eventHub.lostConnection.notify("failed");
4328
+ });
4329
+ } else {
4330
+ batchUpdates(() => {
4331
+ eventHub.lostConnection.notify("restored");
4332
+ });
4333
+ }
4334
+ _hasLostConnection = false;
4335
+ }
4273
4336
  }
4274
- context.lastConnectionId = conn.id;
4275
- context.idFactory = makeIdFactory(conn.id);
4276
- if (context.root) {
4277
- context.buffer.messages.push({ type: 200 /* FETCH_STORAGE */ });
4337
+ }
4338
+ function onDidConnect() {
4339
+ const sessionInfo = context.sessionInfo.current;
4340
+ if (sessionInfo === null) {
4341
+ throw new Error("Unexpected missing session info");
4342
+ }
4343
+ context.buffer.me = {
4344
+ type: "full",
4345
+ data: (
4346
+ // Because context.me.current is a readonly object, we'll have to
4347
+ // make a copy here. Otherwise, type errors happen later when
4348
+ // "patching" my presence.
4349
+ __spreadValues({}, context.me.current)
4350
+ )
4351
+ };
4352
+ context.idFactory = makeIdFactory(sessionInfo.id);
4353
+ if (_getStorage$ !== null) {
4354
+ refreshStorage({ flush: false });
4278
4355
  }
4279
- tryFlushing();
4356
+ flushNowOrSoon();
4280
4357
  }
4281
4358
  function onDidDisconnect() {
4282
4359
  clearTimeout(context.buffer.flushTimerID);
4283
- batchUpdates(() => {
4284
- context.others.clearOthers();
4285
- notify({ others: [{ type: "reset" }] }, doNotBatchUpdates);
4286
- });
4287
4360
  }
4288
4361
  managedSocket.events.onMessage.subscribe(handleServerMessage);
4289
4362
  managedSocket.events.statusDidChange.subscribe(onStatusDidChange);
4363
+ managedSocket.events.statusDidChange.subscribe(handleConnectionLossEvent);
4290
4364
  managedSocket.events.didConnect.subscribe(onDidConnect);
4291
4365
  managedSocket.events.didDisconnect.subscribe(onDidDisconnect);
4292
4366
  managedSocket.events.onLiveblocksError.subscribe((err) => {
@@ -4340,7 +4414,8 @@ function createRoom(options, config) {
4340
4414
  }
4341
4415
  },
4342
4416
  assertStorageIsWritable: () => {
4343
- if (isConnectionSelfAware(context.connection.current) && context.connection.current.isReadOnly) {
4417
+ var _a2;
4418
+ if ((_a2 = context.sessionInfo.current) == null ? void 0 : _a2.isReadOnly) {
4344
4419
  throw new Error(
4345
4420
  "Cannot write to storage with a read only user, please ensure the user has write permissions"
4346
4421
  );
@@ -4348,22 +4423,27 @@ function createRoom(options, config) {
4348
4423
  }
4349
4424
  };
4350
4425
  const eventHub = {
4426
+ connection: makeEventSource(),
4427
+ // Old/deprecated API
4428
+ status: makeEventSource(),
4429
+ // New/recommended API
4430
+ lostConnection: makeEventSource(),
4351
4431
  customEvent: makeEventSource(),
4352
4432
  me: makeEventSource(),
4353
4433
  others: makeEventSource(),
4354
4434
  error: makeEventSource(),
4355
- connection: makeEventSource(),
4356
4435
  storage: makeEventSource(),
4357
4436
  history: makeEventSource(),
4358
4437
  storageDidLoad: makeEventSource(),
4359
- storageStatus: makeEventSource()
4438
+ storageStatus: makeEventSource(),
4439
+ docUpdated: makeEventSource()
4360
4440
  };
4361
4441
  function sendMessages(messageOrMessages) {
4362
- var _a2;
4442
+ var _a2, _b2;
4363
4443
  const message = JSON.stringify(messageOrMessages);
4364
4444
  if (config.unstable_fallbackToHTTP) {
4365
4445
  const size = new TextEncoder().encode(message).length;
4366
- if (size > MAX_MESSAGE_SIZE && managedSocket.token.raw && config.httpSendEndpoint) {
4446
+ if (size > MAX_MESSAGE_SIZE && ((_a2 = managedSocket.token) == null ? void 0 : _a2.raw) && config.httpSendEndpoint) {
4367
4447
  if (isTokenExpired(managedSocket.token.parsed)) {
4368
4448
  return managedSocket.reconnect();
4369
4449
  }
@@ -4371,7 +4451,7 @@ function createRoom(options, config) {
4371
4451
  message,
4372
4452
  managedSocket.token.raw,
4373
4453
  config.httpSendEndpoint,
4374
- (_a2 = config.polyfills) == null ? void 0 : _a2.fetch
4454
+ (_b2 = config.polyfills) == null ? void 0 : _b2.fetch
4375
4455
  );
4376
4456
  warn(
4377
4457
  "Message was too large for websockets and sent over HTTP instead"
@@ -4382,15 +4462,17 @@ function createRoom(options, config) {
4382
4462
  managedSocket.send(message);
4383
4463
  }
4384
4464
  const self = new DerivedRef(
4385
- context.connection,
4465
+ context.sessionInfo,
4386
4466
  context.me,
4387
- (conn, me) => isConnectionSelfAware(conn) ? {
4388
- connectionId: conn.id,
4389
- id: conn.userId,
4390
- info: conn.userInfo,
4391
- presence: me,
4392
- isReadOnly: conn.isReadOnly
4393
- } : null
4467
+ (info, me) => {
4468
+ return info !== null ? {
4469
+ connectionId: info.id,
4470
+ id: info.userId,
4471
+ info: info.userInfo,
4472
+ presence: me,
4473
+ isReadOnly: info.isReadOnly
4474
+ } : null;
4475
+ }
4394
4476
  );
4395
4477
  const selfAsTreeNode = new DerivedRef(
4396
4478
  self,
@@ -4400,7 +4482,7 @@ function createRoom(options, config) {
4400
4482
  if (message.items.length === 0) {
4401
4483
  throw new Error("Internal error: cannot load storage without items");
4402
4484
  }
4403
- if (context.root) {
4485
+ if (context.root !== void 0) {
4404
4486
  updateRoot(message.items, batchedUpdatesWrapper);
4405
4487
  } else {
4406
4488
  context.root = LiveObject._fromItems(message.items, pool);
@@ -4412,7 +4494,7 @@ function createRoom(options, config) {
4412
4494
  }
4413
4495
  }
4414
4496
  function updateRoot(items, batchedUpdatesWrapper) {
4415
- if (!context.root) {
4497
+ if (context.root === void 0) {
4416
4498
  return;
4417
4499
  }
4418
4500
  const currentItems = /* @__PURE__ */ new Map();
@@ -4459,11 +4541,9 @@ function createRoom(options, config) {
4459
4541
  });
4460
4542
  }
4461
4543
  function getConnectionId() {
4462
- const conn = context.connection.current;
4463
- if (isConnectionSelfAware(conn)) {
4464
- return conn.id;
4465
- } else if (context.lastConnectionId !== null) {
4466
- return context.lastConnectionId;
4544
+ const info = context.sessionInfo.current;
4545
+ if (info) {
4546
+ return info.id;
4467
4547
  }
4468
4548
  throw new Error(
4469
4549
  "Internal. Tried to get connection id but connection was never open"
@@ -4612,7 +4692,7 @@ function createRoom(options, config) {
4612
4692
  }
4613
4693
  context.activeBatch.updates.presence = true;
4614
4694
  } else {
4615
- tryFlushing();
4695
+ flushNowOrSoon();
4616
4696
  batchUpdates(() => {
4617
4697
  if (options2 == null ? void 0 : options2.addToHistory) {
4618
4698
  addToUndoStack(
@@ -4699,7 +4779,7 @@ function createRoom(options, config) {
4699
4779
  data: context.me.current,
4700
4780
  targetActor: message.actor
4701
4781
  });
4702
- tryFlushing();
4782
+ flushNowOrSoon();
4703
4783
  const user = context.others.getUser(message.actor);
4704
4784
  return user ? { type: "enter", user } : void 0;
4705
4785
  }
@@ -4777,6 +4857,10 @@ function createRoom(options, config) {
4777
4857
  }
4778
4858
  break;
4779
4859
  }
4860
+ case 300 /* FETCH_DOC */: {
4861
+ eventHub.docUpdated.notify(message.data);
4862
+ break;
4863
+ }
4780
4864
  case 104 /* ROOM_STATE */: {
4781
4865
  updates.others.push(onRoomStateMessage(message));
4782
4866
  break;
@@ -4785,9 +4869,7 @@ function createRoom(options, config) {
4785
4869
  const unacknowledgedOps = new Map(context.unacknowledgedOps);
4786
4870
  createOrUpdateRootFromMessage(message, doNotBatchUpdates);
4787
4871
  applyAndSendOps(unacknowledgedOps, doNotBatchUpdates);
4788
- if (_getInitialStateResolver !== null) {
4789
- _getInitialStateResolver();
4790
- }
4872
+ _resolveStoragePromise == null ? void 0 : _resolveStoragePromise();
4791
4873
  notifyStorageStatus();
4792
4874
  eventHub.storageDidLoad.notify();
4793
4875
  break;
@@ -4834,7 +4916,7 @@ ${Array.from(traces).join("\n\n")}`
4834
4916
  notify(updates, doNotBatchUpdates);
4835
4917
  });
4836
4918
  }
4837
- function tryFlushing() {
4919
+ function flushNowOrSoon() {
4838
4920
  const storageOps = context.buffer.storageOperations;
4839
4921
  if (storageOps.length > 0) {
4840
4922
  for (const op of storageOps) {
@@ -4842,7 +4924,7 @@ ${Array.from(traces).join("\n\n")}`
4842
4924
  }
4843
4925
  notifyStorageStatus();
4844
4926
  }
4845
- if (managedSocket.status !== "open") {
4927
+ if (managedSocket.getStatus() !== "connected") {
4846
4928
  context.buffer.storageOperations = [];
4847
4929
  return;
4848
4930
  }
@@ -4864,7 +4946,7 @@ ${Array.from(traces).join("\n\n")}`
4864
4946
  } else {
4865
4947
  clearTimeout(context.buffer.flushTimerID);
4866
4948
  context.buffer.flushTimerID = setTimeout(
4867
- tryFlushing,
4949
+ flushNowOrSoon,
4868
4950
  config.throttleDelay - elapsedMillis
4869
4951
  );
4870
4952
  }
@@ -4897,34 +4979,49 @@ ${Array.from(traces).join("\n\n")}`
4897
4979
  }
4898
4980
  return messages;
4899
4981
  }
4982
+ function updateDoc(data) {
4983
+ context.buffer.messages.push({
4984
+ type: 301 /* UPDATE_DOC */,
4985
+ data
4986
+ });
4987
+ flushNowOrSoon();
4988
+ }
4900
4989
  function broadcastEvent(event, options2 = {
4901
4990
  shouldQueueEventIfNotReady: false
4902
4991
  }) {
4903
- if (managedSocket.status !== "open" && !options2.shouldQueueEventIfNotReady) {
4992
+ if (managedSocket.getStatus() !== "connected" && !options2.shouldQueueEventIfNotReady) {
4904
4993
  return;
4905
4994
  }
4906
4995
  context.buffer.messages.push({
4907
4996
  type: 103 /* BROADCAST_EVENT */,
4908
4997
  event
4909
4998
  });
4910
- tryFlushing();
4999
+ flushNowOrSoon();
4911
5000
  }
4912
5001
  function dispatchOps(ops) {
4913
5002
  context.buffer.storageOperations.push(...ops);
4914
- tryFlushing();
5003
+ flushNowOrSoon();
5004
+ }
5005
+ let _getStorage$ = null;
5006
+ let _resolveStoragePromise = null;
5007
+ function refreshStorage(options2) {
5008
+ const messages = context.buffer.messages;
5009
+ if (!messages.some((msg) => msg.type === 200 /* FETCH_STORAGE */)) {
5010
+ messages.push({ type: 200 /* FETCH_STORAGE */ });
5011
+ }
5012
+ if (options2.flush) {
5013
+ flushNowOrSoon();
5014
+ }
4915
5015
  }
4916
- let _getInitialStatePromise = null;
4917
- let _getInitialStateResolver = null;
4918
5016
  function startLoadingStorage() {
4919
- if (_getInitialStatePromise === null) {
4920
- context.buffer.messages.push({ type: 200 /* FETCH_STORAGE */ });
4921
- tryFlushing();
4922
- _getInitialStatePromise = new Promise(
4923
- (resolve) => _getInitialStateResolver = resolve
4924
- );
5017
+ if (_getStorage$ === null) {
5018
+ refreshStorage({ flush: true });
5019
+ _getStorage$ = new Promise((resolve) => {
5020
+ _resolveStoragePromise = resolve;
5021
+ });
4925
5022
  notifyStorageStatus();
4926
5023
  }
4927
- return _getInitialStatePromise;
5024
+ return _getStorage$;
4928
5025
  }
4929
5026
  function getStorageSnapshot() {
4930
5027
  const root = context.root;
@@ -4937,7 +5034,7 @@ ${Array.from(traces).join("\n\n")}`
4937
5034
  }
4938
5035
  function getStorage() {
4939
5036
  return __async(this, null, function* () {
4940
- if (context.root) {
5037
+ if (context.root !== void 0) {
4941
5038
  return Promise.resolve({
4942
5039
  root: context.root
4943
5040
  });
@@ -4948,6 +5045,10 @@ ${Array.from(traces).join("\n\n")}`
4948
5045
  };
4949
5046
  });
4950
5047
  }
5048
+ function getDoc(vector = "") {
5049
+ context.buffer.messages.push({ type: 300 /* FETCH_DOC */, vector });
5050
+ flushNowOrSoon();
5051
+ }
4951
5052
  function undo() {
4952
5053
  if (context.activeBatch) {
4953
5054
  throw new Error("undo is not allowed during a batch");
@@ -4968,7 +5069,7 @@ ${Array.from(traces).join("\n\n")}`
4968
5069
  context.buffer.storageOperations.push(op);
4969
5070
  }
4970
5071
  }
4971
- tryFlushing();
5072
+ flushNowOrSoon();
4972
5073
  }
4973
5074
  function redo() {
4974
5075
  if (context.activeBatch) {
@@ -4990,7 +5091,7 @@ ${Array.from(traces).join("\n\n")}`
4990
5091
  context.buffer.storageOperations.push(op);
4991
5092
  }
4992
5093
  }
4993
- tryFlushing();
5094
+ flushNowOrSoon();
4994
5095
  }
4995
5096
  function batch(callback) {
4996
5097
  if (context.activeBatch) {
@@ -5022,7 +5123,7 @@ ${Array.from(traces).join("\n\n")}`
5022
5123
  dispatchOps(currentBatch.ops);
5023
5124
  }
5024
5125
  notify(currentBatch.updates, doNotBatchUpdates);
5025
- tryFlushing();
5126
+ flushNowOrSoon();
5026
5127
  }
5027
5128
  });
5028
5129
  return returnValue;
@@ -5038,13 +5139,11 @@ ${Array.from(traces).join("\n\n")}`
5038
5139
  }
5039
5140
  }
5040
5141
  function getStorageStatus() {
5041
- if (_getInitialStatePromise === null) {
5042
- return "not-loaded";
5043
- }
5044
5142
  if (context.root === void 0) {
5045
- return "loading";
5143
+ return _getStorage$ === null ? "not-loaded" : "loading";
5144
+ } else {
5145
+ return context.unacknowledgedOps.size === 0 ? "synchronized" : "synchronizing";
5046
5146
  }
5047
- return context.unacknowledgedOps.size === 0 ? "synchronized" : "synchronizing";
5048
5147
  }
5049
5148
  let _lastStorageStatus = getStorageStatus();
5050
5149
  function notifyStorageStatus() {
@@ -5059,15 +5158,20 @@ ${Array.from(traces).join("\n\n")}`
5059
5158
  (others) => others.map((other, index) => userToTreeNode(`Other ${index}`, other))
5060
5159
  );
5061
5160
  const events = {
5161
+ connection: eventHub.connection.observable,
5162
+ // Old/deprecated API
5163
+ status: eventHub.status.observable,
5164
+ // New/recommended API
5165
+ lostConnection: eventHub.lostConnection.observable,
5062
5166
  customEvent: eventHub.customEvent.observable,
5063
5167
  others: eventHub.others.observable,
5064
5168
  me: eventHub.me.observable,
5065
5169
  error: eventHub.error.observable,
5066
- connection: eventHub.connection.observable,
5067
5170
  storage: eventHub.storage.observable,
5068
5171
  history: eventHub.history.observable,
5069
5172
  storageDidLoad: eventHub.storageDidLoad.observable,
5070
- storageStatus: eventHub.storageStatus.observable
5173
+ storageStatus: eventHub.storageStatus.observable,
5174
+ docUpdated: eventHub.docUpdated.observable
5071
5175
  };
5072
5176
  return {
5073
5177
  /* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
@@ -5091,7 +5195,7 @@ ${Array.from(traces).join("\n\n")}`
5091
5195
  send: {
5092
5196
  // These exist only for our E2E testing app
5093
5197
  explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
5094
- implicitClose: () => managedSocket._privateSendMachineEvent({ type: "PONG_TIMEOUT" })
5198
+ implicitClose: () => managedSocket._privateSendMachineEvent({ type: "NAVIGATOR_OFFLINE" })
5095
5199
  }
5096
5200
  },
5097
5201
  id: config.roomId,
@@ -5102,6 +5206,7 @@ ${Array.from(traces).join("\n\n")}`
5102
5206
  destroy: () => managedSocket.destroy(),
5103
5207
  // Presence
5104
5208
  updatePresence,
5209
+ updateDoc,
5105
5210
  broadcastEvent,
5106
5211
  // Storage
5107
5212
  batch,
@@ -5113,13 +5218,15 @@ ${Array.from(traces).join("\n\n")}`
5113
5218
  pause: pauseHistory,
5114
5219
  resume: resumeHistory
5115
5220
  },
5221
+ getDoc,
5116
5222
  getStorage,
5117
5223
  getStorageSnapshot,
5118
5224
  getStorageStatus,
5119
5225
  events,
5120
5226
  // Core
5121
- getConnectionState: () => context.connection.current.status,
5122
- isSelfAware: () => isConnectionSelfAware(context.connection.current),
5227
+ getStatus: () => managedSocket.getStatus(),
5228
+ getConnectionState: () => managedSocket.getLegacyStatus(),
5229
+ isSelfAware: () => context.sessionInfo.current !== null,
5123
5230
  getSelf: () => self.current,
5124
5231
  // Presence
5125
5232
  getPresence: () => context.me.current,
@@ -5171,6 +5278,12 @@ function makeClassicSubscribeFn(events) {
5171
5278
  return events.connection.subscribe(
5172
5279
  callback
5173
5280
  );
5281
+ case "status":
5282
+ return events.status.subscribe(callback);
5283
+ case "lost-connection":
5284
+ return events.lostConnection.subscribe(
5285
+ callback
5286
+ );
5174
5287
  case "history":
5175
5288
  return events.history.subscribe(callback);
5176
5289
  case "storage-status":
@@ -5204,7 +5317,7 @@ function makeClassicSubscribeFn(events) {
5204
5317
  return subscribe;
5205
5318
  }
5206
5319
  function isRoomEventName(value) {
5207
- return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "connection" || value === "history" || value === "storage-status";
5320
+ return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "history" || value === "status" || value === "storage-status" || value === "lost-connection" || value === "connection";
5208
5321
  }
5209
5322
  function makeCreateSocketDelegateForRoom(liveblocksServer, WebSocketPolyfill) {
5210
5323
  return (richToken) => {
@@ -5221,7 +5334,7 @@ function makeCreateSocketDelegateForRoom(liveblocksServer, WebSocketPolyfill) {
5221
5334
  // @ts-ignore (__PACKAGE_VERSION__ will be injected by the build script)
5222
5335
  true ? (
5223
5336
  /* istanbul ignore next */
5224
- "1.1.0-test1"
5337
+ "1.1.0-yjs2"
5225
5338
  ) : "dev"}`
5226
5339
  );
5227
5340
  };
@@ -5324,20 +5437,28 @@ function fetchAuthEndpoint(fetch2, endpoint, body) {
5324
5437
  var MIN_THROTTLE = 16;
5325
5438
  var MAX_THROTTLE = 1e3;
5326
5439
  var DEFAULT_THROTTLE = 100;
5440
+ var MIN_LOST_CONNECTION_TIMEOUT = 200;
5441
+ var RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT = 1e3;
5442
+ var MAX_LOST_CONNECTION_TIMEOUT = 3e4;
5443
+ var DEFAULT_LOST_CONNECTION_TIMEOUT = 5e3;
5327
5444
  function getServerFromClientOptions(clientOptions) {
5328
5445
  const rawOptions = clientOptions;
5329
5446
  return typeof rawOptions.liveblocksServer === "string" ? rawOptions.liveblocksServer : "wss://api.liveblocks.io/v6";
5330
5447
  }
5331
5448
  function createClient(options) {
5449
+ var _a, _b;
5332
5450
  const clientOptions = options;
5333
- const throttleDelay = getThrottleDelayFromOptions(clientOptions);
5451
+ const throttleDelay = getThrottle((_a = clientOptions.throttle) != null ? _a : DEFAULT_THROTTLE);
5452
+ const lostConnectionTimeout = getLostConnectionTimeout(
5453
+ (_b = clientOptions.lostConnectionTimeout) != null ? _b : DEFAULT_LOST_CONNECTION_TIMEOUT
5454
+ );
5334
5455
  const rooms = /* @__PURE__ */ new Map();
5335
5456
  function getRoom(roomId) {
5336
5457
  const room = rooms.get(roomId);
5337
5458
  return room ? room : null;
5338
5459
  }
5339
5460
  function enter(roomId, options2) {
5340
- var _a, _b, _c;
5461
+ var _a2, _b2, _c;
5341
5462
  const existingRoom = rooms.get(roomId);
5342
5463
  if (existingRoom !== void 0) {
5343
5464
  return existingRoom;
@@ -5348,12 +5469,13 @@ function createClient(options) {
5348
5469
  );
5349
5470
  const newRoom = createRoom(
5350
5471
  {
5351
- initialPresence: (_a = options2.initialPresence) != null ? _a : {},
5472
+ initialPresence: (_a2 = options2.initialPresence) != null ? _a2 : {},
5352
5473
  initialStorage: options2.initialStorage
5353
5474
  },
5354
5475
  {
5355
5476
  roomId,
5356
5477
  throttleDelay,
5478
+ lostConnectionTimeout,
5357
5479
  polyfills: clientOptions.polyfills,
5358
5480
  delegates: clientOptions.mockedDelegates,
5359
5481
  enableDebugLogging: clientOptions.enableDebugLogging,
@@ -5370,7 +5492,7 @@ function createClient(options) {
5370
5492
  rooms.set(roomId, newRoom);
5371
5493
  setupDevTools(() => Array.from(rooms.keys()));
5372
5494
  linkDevTools(roomId, newRoom);
5373
- const shouldConnect = (_b = options2.shouldInitiallyConnect) != null ? _b : true;
5495
+ const shouldConnect = (_b2 = options2.shouldInitiallyConnect) != null ? _b2 : true;
5374
5496
  if (shouldConnect) {
5375
5497
  if (typeof atob === "undefined") {
5376
5498
  if (((_c = clientOptions.polyfills) == null ? void 0 : _c.atob) === void 0) {
@@ -5398,16 +5520,25 @@ function createClient(options) {
5398
5520
  leave
5399
5521
  };
5400
5522
  }
5401
- function getThrottleDelayFromOptions(options) {
5402
- if (options.throttle === void 0) {
5403
- return DEFAULT_THROTTLE;
5404
- }
5405
- if (typeof options.throttle !== "number" || options.throttle < MIN_THROTTLE || options.throttle > MAX_THROTTLE) {
5523
+ function checkBounds(option, value, min, max, recommendedMin) {
5524
+ if (typeof value !== "number" || value < min || value > max) {
5406
5525
  throw new Error(
5407
- `throttle should be a number between ${MIN_THROTTLE} and ${MAX_THROTTLE}.`
5526
+ `${option} should be a number between ${recommendedMin != null ? recommendedMin : min} and ${max}.`
5408
5527
  );
5409
5528
  }
5410
- return options.throttle;
5529
+ return value;
5530
+ }
5531
+ function getThrottle(value) {
5532
+ return checkBounds("throttle", value, MIN_THROTTLE, MAX_THROTTLE);
5533
+ }
5534
+ function getLostConnectionTimeout(value) {
5535
+ return checkBounds(
5536
+ "lostConnectionTimeout",
5537
+ value,
5538
+ MIN_LOST_CONNECTION_TIMEOUT,
5539
+ MAX_LOST_CONNECTION_TIMEOUT,
5540
+ RECOMMENDED_MIN_LOST_CONNECTION_TIMEOUT
5541
+ );
5411
5542
  }
5412
5543
  function prepareAuthentication(clientOptions, roomId) {
5413
5544
  const { publicApiKey, authEndpoint } = clientOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/core",
3
- "version": "1.1.0-test1",
3
+ "version": "1.1.0-yjs2",
4
4
  "description": "Shared code and foundational internals for Liveblocks",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "dev": "tsup --watch",
22
22
  "build": "tsup",
23
- "format": "eslint --fix src/; prettier --write src/",
23
+ "format": "(eslint --fix src/ || true) && prettier --write src/",
24
24
  "lint": "eslint src/",
25
25
  "test": "jest --silent --verbose --color=always",
26
26
  "test:types": "tsd",