@liveblocks/core 1.0.8 → 1.0.10

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.ts CHANGED
@@ -129,6 +129,63 @@ declare type UpdateDelta = {
129
129
  type: "delete";
130
130
  };
131
131
 
132
+ /**
133
+ * "Plain LSON" is a JSON-based format that's used when serializing Live structures
134
+ * to send them over HTTP (e.g. in the API endpoint to let users upload their initial
135
+ * Room storage, in the API endpoint to fetch a Room's storage, ...).
136
+ *
137
+ * In the client, you would typically create LSON values using:
138
+ *
139
+ * new LiveObject({ x: 0, y: 0 })
140
+ *
141
+ * But over HTTP, this has to be serialized somehow. The "Plain LSON" format
142
+ * is what's used in the POST /init-storage-new endpoint, to allow users to
143
+ * control which parts of their data structure should be considered "Live"
144
+ * objects, and which parts are "normal" objects.
145
+ *
146
+ * So if they have a structure like:
147
+ *
148
+ * { x: 0, y: 0 }
149
+ *
150
+ * And want to make it a Live object, they can serialize it by wrapping it in
151
+ * a special "annotation":
152
+ *
153
+ * {
154
+ * "liveblocksType": "LiveObject",
155
+ * "data": { x: 0, y: 0 },
156
+ * }
157
+ *
158
+ * This "Plain LSON" data format defines exactly those wrappings.
159
+ *
160
+ * To summarize:
161
+ *
162
+ * LSON value | Plain LSON equivalent
163
+ * ----------------------+----------------------------------------------
164
+ * 42 | 42
165
+ * [1, 2, 3] | [1, 2, 3]
166
+ * { x: 0, y: 0 } | { x: 0, y: 0 }
167
+ * ----------------------+----------------------------------------------
168
+ * new LiveList(...) | { liveblocksType: "LiveList", data: ... }
169
+ * new LiveMap(...) | { liveblocksType: "LiveMap", data: ... }
170
+ * new LiveObject(...) | { liveblocksType: "LiveObject", data: ... }
171
+ *
172
+ */
173
+
174
+ declare type PlainLsonFields = Record<string, PlainLson>;
175
+ declare type PlainLsonObject = {
176
+ liveblocksType: "LiveObject";
177
+ data: PlainLsonFields;
178
+ };
179
+ declare type PlainLsonMap = {
180
+ liveblocksType: "LiveMap";
181
+ data: PlainLsonFields;
182
+ };
183
+ declare type PlainLsonList = {
184
+ liveblocksType: "LiveList";
185
+ data: PlainLson[];
186
+ };
187
+ declare type PlainLson = PlainLsonObject | PlainLsonMap | PlainLsonList | Json;
188
+
132
189
  declare type LiveObjectUpdateDelta<O extends {
133
190
  [key: string]: unknown;
134
191
  }> = {
@@ -197,6 +254,10 @@ declare class LiveObject<O extends LsonObject> extends AbstractCrdt {
197
254
  declare type ToImmutable<L extends Lson | LsonObject> = L extends LiveList<infer I> ? readonly ToImmutable<I>[] : L extends LiveObject<infer O> ? ToImmutable<O> : L extends LiveMap<infer K, infer V> ? ReadonlyMap<K, ToImmutable<V>> : L extends LsonObject ? {
198
255
  readonly [K in keyof L]: ToImmutable<Exclude<L[K], undefined>> | (undefined extends L[K] ? undefined : never);
199
256
  } : L extends Json ? L : never;
257
+ /**
258
+ * Returns PlainLson for a given Json or LiveStructure, suitable for calling the storage init api
259
+ */
260
+ declare function toPlainLson(lson: Lson): PlainLson;
200
261
 
201
262
  /**
202
263
  * A LiveMap notification that is sent in-client to any subscribers whenever
@@ -268,12 +329,15 @@ declare class LiveMap<TKey extends string, TValue extends Lson> extends Abstract
268
329
  }
269
330
 
270
331
  declare type StorageCallback = (updates: StorageUpdate[]) => void;
332
+ declare type LiveMapUpdate = LiveMapUpdates<string, Lson>;
333
+ declare type LiveObjectUpdate = LiveObjectUpdates<LsonObject>;
334
+ declare type LiveListUpdate = LiveListUpdates<Lson>;
271
335
  /**
272
336
  * The payload of notifications sent (in-client) when LiveStructures change.
273
337
  * Messages of this kind are not originating from the network, but are 100%
274
338
  * in-client.
275
339
  */
276
- declare type StorageUpdate = LiveMapUpdates<string, Lson> | LiveObjectUpdates<LsonObject> | LiveListUpdates<Lson>;
340
+ declare type StorageUpdate = LiveMapUpdate | LiveObjectUpdate | LiveListUpdate;
277
341
 
278
342
  declare abstract class AbstractCrdt {
279
343
  get roomId(): string | null;
@@ -1051,6 +1115,7 @@ declare type AuthEndpoint = string | ((room: string) => Promise<{
1051
1115
  declare type ClientOptions = {
1052
1116
  throttle?: number;
1053
1117
  polyfills?: Polyfills;
1118
+ unstable_fallbackToHTTP?: boolean;
1054
1119
  /**
1055
1120
  * Backward-compatible way to set `polyfills.fetch`.
1056
1121
  */
@@ -1546,63 +1611,6 @@ SerializedCrdt>;
1546
1611
  declare type ParentToChildNodeMap = Map<string, // Parent's node ID
1547
1612
  IdTuple<SerializedChild>[]>;
1548
1613
 
1549
- /**
1550
- * "Plain LSON" is a JSON-based format that's used when serializing Live structures
1551
- * to send them over HTTP (e.g. in the API endpoint to let users upload their initial
1552
- * Room storage, in the API endpoint to fetch a Room's storage, ...).
1553
- *
1554
- * In the client, you would typically create LSON values using:
1555
- *
1556
- * new LiveObject({ x: 0, y: 0 })
1557
- *
1558
- * But over HTTP, this has to be serialized somehow. The "Plain LSON" format
1559
- * is what's used in the POST /init-storage-new endpoint, to allow users to
1560
- * control which parts of their data structure should be considered "Live"
1561
- * objects, and which parts are "normal" objects.
1562
- *
1563
- * So if they have a structure like:
1564
- *
1565
- * { x: 0, y: 0 }
1566
- *
1567
- * And want to make it a Live object, they can serialize it by wrapping it in
1568
- * a special "annotation":
1569
- *
1570
- * {
1571
- * "liveblocksType": "LiveObject",
1572
- * "data": { x: 0, y: 0 },
1573
- * }
1574
- *
1575
- * This "Plain LSON" data format defines exactly those wrappings.
1576
- *
1577
- * To summarize:
1578
- *
1579
- * LSON value | Plain LSON equivalent
1580
- * ----------------------+----------------------------------------------
1581
- * 42 | 42
1582
- * [1, 2, 3] | [1, 2, 3]
1583
- * { x: 0, y: 0 } | { x: 0, y: 0 }
1584
- * ----------------------+----------------------------------------------
1585
- * new LiveList(...) | { liveblocksType: "LiveList", data: ... }
1586
- * new LiveMap(...) | { liveblocksType: "LiveMap", data: ... }
1587
- * new LiveObject(...) | { liveblocksType: "LiveObject", data: ... }
1588
- *
1589
- */
1590
-
1591
- declare type PlainLsonFields = Record<string, PlainLson>;
1592
- declare type PlainLsonObject = {
1593
- liveblocksType: "LiveObject";
1594
- data: PlainLsonFields;
1595
- };
1596
- declare type PlainLsonMap = {
1597
- liveblocksType: "LiveMap";
1598
- data: PlainLsonFields;
1599
- };
1600
- declare type PlainLsonList = {
1601
- liveblocksType: "LiveList";
1602
- data: PlainLson[];
1603
- };
1604
- declare type PlainLson = PlainLsonObject | PlainLsonMap | PlainLsonList | Json;
1605
-
1606
1614
  declare type JsonTreeNode = {
1607
1615
  readonly type: "Json";
1608
1616
  readonly id: string;
@@ -1779,4 +1787,4 @@ declare type EnsureJson<T> = [
1779
1787
  [K in keyof T]: EnsureJson<T[K]>;
1780
1788
  };
1781
1789
 
1782
- export { AckOp, AppOnlyAuthToken, AuthToken, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, ConnectionStatus, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, DeleteCrdtOp, DeleteObjectKeyOp, DevToolsTreeNode as DevTools, protocol as DevToolsMsg, EnsureJson, FetchStorageClientMsg, History, IWebSocket, IWebSocketCloseEvent, IWebSocketEvent, IWebSocketInstance, IWebSocketMessageEvent, IdTuple, Immutable, InitialDocumentStateServerMsg, Json, JsonArray, JsonObject, JsonScalar, LiveList, LiveMap, LiveNode, LiveObject, 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, tryParseJson };
1790
+ export { AckOp, AppOnlyAuthToken, AuthToken, BaseUserMeta, BroadcastEventClientMsg, BroadcastOptions, BroadcastedEventServerMsg, Client, ClientMsg, ClientMsgCode, ConnectionStatus, CrdtType, CreateChildOp, CreateListOp, CreateMapOp, CreateObjectOp, CreateOp, CreateRegisterOp, CreateRootObjectOp, 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 };
package/dist/index.js CHANGED
@@ -117,7 +117,7 @@ var onMessageFromPanel = eventSource.observable;
117
117
  // src/devtools/index.ts
118
118
  var VERSION = true ? (
119
119
  /* istanbul ignore next */
120
- "1.0.8"
120
+ "1.0.10"
121
121
  ) : "dev";
122
122
  var _devtoolsSetupHasRun = false;
123
123
  function setupDevTools(getAllRooms) {
@@ -2955,7 +2955,8 @@ function hasJwtMeta(data) {
2955
2955
  }
2956
2956
  function isTokenExpired(token) {
2957
2957
  const now = Date.now() / 1e3;
2958
- return now > token.exp - 300 || now < token.iat + 300;
2958
+ const valid = now <= token.exp - 300 && now >= token.iat - 300;
2959
+ return !valid;
2959
2960
  }
2960
2961
  function isStringList(value) {
2961
2962
  return Array.isArray(value) && value.every((i) => typeof i === "string");
@@ -2983,20 +2984,22 @@ function parseJwtToken(token) {
2983
2984
  }
2984
2985
  function parseRoomAuthToken(tokenString) {
2985
2986
  const data = parseJwtToken(tokenString);
2986
- if (data && isRoomAuthToken(data)) {
2987
- const _a = data, {
2988
- maxConnections: _legacyField
2989
- } = _a, token = __objRest(_a, [
2990
- // If this legacy field is found on the token, pretend it wasn't there,
2991
- // to make all internally used token payloads uniform
2992
- "maxConnections"
2993
- ]);
2994
- return token;
2995
- } else {
2987
+ if (!(data && isRoomAuthToken(data))) {
2996
2988
  throw new Error(
2997
2989
  "Authentication error: we expected a room token but did not get one. Hint: if you are using a callback, ensure the room is passed when creating the token. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClientCallback"
2998
2990
  );
2999
2991
  }
2992
+ const _a = data, {
2993
+ maxConnections: _legacyField
2994
+ } = _a, parsedToken = __objRest(_a, [
2995
+ // If this legacy field is found on the token, pretend it wasn't there,
2996
+ // to make all internally used token payloads uniform
2997
+ "maxConnections"
2998
+ ]);
2999
+ return {
3000
+ raw: tokenString,
3001
+ parsed: parsedToken
3002
+ };
3000
3003
  }
3001
3004
 
3002
3005
  // src/protocol/ClientMsg.ts
@@ -3255,6 +3258,7 @@ var BACKOFF_RETRY_DELAYS = [250, 500, 1e3, 2e3, 4e3, 8e3, 1e4];
3255
3258
  var BACKOFF_RETRY_DELAYS_SLOW = [2e3, 3e4, 6e4, 3e5];
3256
3259
  var HEARTBEAT_INTERVAL = 3e4;
3257
3260
  var PONG_TIMEOUT = 2e3;
3261
+ var MAX_MESSAGE_SIZE = 1024 * 1024 - 128;
3258
3262
  function makeIdFactory(connectionId) {
3259
3263
  let count = 0;
3260
3264
  return () => `${connectionId}:${count++}`;
@@ -3380,21 +3384,20 @@ function createRoom(options, config) {
3380
3384
  storageStatus: makeEventSource()
3381
3385
  };
3382
3386
  const effects = config.mockedEffects || {
3383
- authenticate(auth, createWebSocket) {
3387
+ authenticateAndConnect(auth, createWebSocket) {
3384
3388
  const prevToken = context.token;
3385
3389
  if (prevToken !== null && !isTokenExpired(prevToken.parsed)) {
3386
- const socket = createWebSocket(prevToken.raw);
3387
- authenticationSuccess(prevToken.parsed, socket);
3390
+ const socket = createWebSocket(prevToken);
3391
+ handleAuthSuccess(prevToken.parsed, socket);
3388
3392
  return void 0;
3389
3393
  } else {
3390
- void auth(config.roomId).then(({ token }) => {
3394
+ void auth().then((token) => {
3391
3395
  if (context.connection.current.status !== "authenticating") {
3392
3396
  return;
3393
3397
  }
3394
- const parsedToken = parseRoomAuthToken(token);
3395
3398
  const socket = createWebSocket(token);
3396
- authenticationSuccess(parsedToken, socket);
3397
- context.token = { raw: token, parsed: parsedToken };
3399
+ handleAuthSuccess(token.parsed, socket);
3400
+ context.token = token;
3398
3401
  }).catch(
3399
3402
  (er) => authenticationFailure(
3400
3403
  er instanceof Error ? er : new Error(String(er))
@@ -3404,15 +3407,34 @@ function createRoom(options, config) {
3404
3407
  }
3405
3408
  },
3406
3409
  send(messageOrMessages) {
3410
+ var _a2, _b;
3407
3411
  if (context.socket === null) {
3408
3412
  throw new Error("Can't send message if socket is null");
3409
3413
  }
3410
3414
  if (context.socket.readyState === context.socket.OPEN) {
3411
- context.socket.send(JSON.stringify(messageOrMessages));
3415
+ const message = JSON.stringify(messageOrMessages);
3416
+ if (config.unstable_fallbackToHTTP) {
3417
+ const size = new TextEncoder().encode(message).length;
3418
+ if (size > MAX_MESSAGE_SIZE && ((_a2 = context.token) == null ? void 0 : _a2.raw) && config.httpSendEndpoint) {
3419
+ if (isTokenExpired(context.token.parsed)) {
3420
+ return reconnect();
3421
+ }
3422
+ void httpSend(
3423
+ message,
3424
+ context.token.raw,
3425
+ config.httpSendEndpoint,
3426
+ (_b = config.polyfills) == null ? void 0 : _b.fetch
3427
+ );
3428
+ warn(
3429
+ "Message was too large for websockets and sent over HTTP instead"
3430
+ );
3431
+ return;
3432
+ }
3433
+ }
3434
+ context.socket.send(message);
3412
3435
  }
3413
3436
  },
3414
- scheduleFlush: (delay) => setTimeout(tryFlushing, delay),
3415
- scheduleReconnect: (delay) => setTimeout(connect, delay),
3437
+ scheduleReconnect: (delay) => setTimeout(handleConnect, delay),
3416
3438
  startHeartbeatInterval: () => setInterval(heartbeat, HEARTBEAT_INTERVAL),
3417
3439
  schedulePongTimeout: () => setTimeout(pongTimeout, PONG_TIMEOUT)
3418
3440
  };
@@ -3621,12 +3643,13 @@ function createRoom(options, config) {
3621
3643
  }
3622
3644
  }
3623
3645
  }
3624
- function connect() {
3646
+ function handleConnect() {
3625
3647
  var _a2, _b;
3626
3648
  if (context.connection.current.status !== "closed" && context.connection.current.status !== "unavailable") {
3627
3649
  return;
3628
3650
  }
3629
3651
  const auth = prepareAuthEndpoint(
3652
+ config.roomId,
3630
3653
  config.authentication,
3631
3654
  (_a2 = config.polyfills) == null ? void 0 : _a2.fetch
3632
3655
  );
@@ -3635,7 +3658,7 @@ function createRoom(options, config) {
3635
3658
  (_b = config.polyfills) == null ? void 0 : _b.WebSocket
3636
3659
  );
3637
3660
  updateConnection({ status: "authenticating" }, batchUpdates);
3638
- effects.authenticate(auth, createWebSocket);
3661
+ effects.authenticateAndConnect(auth, createWebSocket);
3639
3662
  }
3640
3663
  function updatePresence(patch, options2) {
3641
3664
  const oldValues = {};
@@ -3678,11 +3701,11 @@ function createRoom(options, config) {
3678
3701
  function isStorageReadOnly(scopes) {
3679
3702
  return scopes.includes("room:read" /* Read */) && scopes.includes("room:presence:write" /* PresenceWrite */) && !scopes.includes("room:write" /* Write */);
3680
3703
  }
3681
- function authenticationSuccess(token, socket) {
3682
- socket.addEventListener("message", onMessage);
3683
- socket.addEventListener("open", onOpen);
3684
- socket.addEventListener("close", onClose);
3685
- socket.addEventListener("error", onError);
3704
+ function handleAuthSuccess(token, socket) {
3705
+ socket.addEventListener("message", handleRawSocketMessage);
3706
+ socket.addEventListener("open", handleSocketOpen);
3707
+ socket.addEventListener("close", handleExplicitClose);
3708
+ socket.addEventListener("error", handleSocketError);
3686
3709
  updateConnection(
3687
3710
  {
3688
3711
  status: "connecting",
@@ -3706,8 +3729,8 @@ function createRoom(options, config) {
3706
3729
  clearTimeout(context.timers.reconnect);
3707
3730
  context.timers.reconnect = effects.scheduleReconnect(getRetryDelay());
3708
3731
  }
3709
- function onVisibilityChange(visibilityState) {
3710
- if (visibilityState === "visible" && context.connection.current.status === "open") {
3732
+ function handleWindowGotFocus() {
3733
+ if (context.connection.current.status === "open") {
3711
3734
  log("Heartbeat after visibility change");
3712
3735
  heartbeat();
3713
3736
  }
@@ -3761,7 +3784,7 @@ function createRoom(options, config) {
3761
3784
  }
3762
3785
  return { type: "reset" };
3763
3786
  }
3764
- function onNavigatorOnline() {
3787
+ function handleNavigatorBackOnline() {
3765
3788
  if (context.connection.current.status === "unavailable") {
3766
3789
  log("Try to reconnect after connectivity change");
3767
3790
  reconnect();
@@ -3824,11 +3847,17 @@ function createRoom(options, config) {
3824
3847
  notify(result.updates, batchedUpdatesWrapper);
3825
3848
  effects.send(messages);
3826
3849
  }
3827
- function onMessage(event) {
3850
+ function handleRawSocketMessage(event) {
3828
3851
  if (event.data === "pong") {
3829
- clearTimeout(context.timers.pongTimeout);
3830
- return;
3852
+ transition({ type: "RECEIVE_PONG" });
3853
+ } else {
3854
+ handleServerMessage(event);
3831
3855
  }
3856
+ }
3857
+ function handlePong() {
3858
+ clearTimeout(context.timers.pongTimeout);
3859
+ }
3860
+ function handleServerMessage(event) {
3832
3861
  if (typeof event.data !== "string") {
3833
3862
  return;
3834
3863
  }
@@ -3929,7 +3958,7 @@ ${Array.from(traces).join("\n\n")}`
3929
3958
  notify(updates, doNotBatchUpdates);
3930
3959
  });
3931
3960
  }
3932
- function onClose(event) {
3961
+ function handleExplicitClose(event) {
3933
3962
  context.socket = null;
3934
3963
  clearTimeout(context.timers.flush);
3935
3964
  clearTimeout(context.timers.reconnect);
@@ -3980,9 +4009,9 @@ ${Array.from(traces).join("\n\n")}`
3980
4009
  }
3981
4010
  return BACKOFF_RETRY_DELAYS[context.numRetries < BACKOFF_RETRY_DELAYS.length ? context.numRetries : BACKOFF_RETRY_DELAYS.length - 1];
3982
4011
  }
3983
- function onError() {
4012
+ function handleSocketError() {
3984
4013
  }
3985
- function onOpen() {
4014
+ function handleSocketOpen() {
3986
4015
  clearInterval(context.timers.heartbeat);
3987
4016
  context.timers.heartbeat = effects.startHeartbeatInterval();
3988
4017
  if (context.connection.current.status === "connecting") {
@@ -4025,12 +4054,12 @@ ${Array.from(traces).join("\n\n")}`
4025
4054
  log("Pong timeout. Trying to reconnect.");
4026
4055
  reconnect();
4027
4056
  }
4028
- function disconnect() {
4057
+ function handleDisconnect() {
4029
4058
  if (context.socket) {
4030
- context.socket.removeEventListener("open", onOpen);
4031
- context.socket.removeEventListener("message", onMessage);
4032
- context.socket.removeEventListener("close", onClose);
4033
- context.socket.removeEventListener("error", onError);
4059
+ context.socket.removeEventListener("open", handleSocketOpen);
4060
+ context.socket.removeEventListener("message", handleRawSocketMessage);
4061
+ context.socket.removeEventListener("close", handleExplicitClose);
4062
+ context.socket.removeEventListener("error", handleSocketError);
4034
4063
  context.socket.close();
4035
4064
  context.socket = null;
4036
4065
  }
@@ -4049,10 +4078,10 @@ ${Array.from(traces).join("\n\n")}`
4049
4078
  }
4050
4079
  function reconnect() {
4051
4080
  if (context.socket) {
4052
- context.socket.removeEventListener("open", onOpen);
4053
- context.socket.removeEventListener("message", onMessage);
4054
- context.socket.removeEventListener("close", onClose);
4055
- context.socket.removeEventListener("error", onError);
4081
+ context.socket.removeEventListener("open", handleSocketOpen);
4082
+ context.socket.removeEventListener("message", handleRawSocketMessage);
4083
+ context.socket.removeEventListener("close", handleExplicitClose);
4084
+ context.socket.removeEventListener("error", handleSocketError);
4056
4085
  context.socket.close();
4057
4086
  context.socket = null;
4058
4087
  }
@@ -4061,7 +4090,7 @@ ${Array.from(traces).join("\n\n")}`
4061
4090
  clearInterval(context.timers.heartbeat);
4062
4091
  clearTimeout(context.timers.pongTimeout);
4063
4092
  updateConnection({ status: "unavailable" }, batchUpdates);
4064
- connect();
4093
+ handleConnect();
4065
4094
  }
4066
4095
  function tryFlushing() {
4067
4096
  const storageOps = context.buffer.storageOperations;
@@ -4091,7 +4120,8 @@ ${Array.from(traces).join("\n\n")}`
4091
4120
  };
4092
4121
  } else {
4093
4122
  clearTimeout(context.timers.flush);
4094
- context.timers.flush = effects.scheduleFlush(
4123
+ context.timers.flush = setTimeout(
4124
+ tryFlushing,
4095
4125
  config.throttleDelay - elapsedMillis
4096
4126
  );
4097
4127
  }
@@ -4264,14 +4294,11 @@ ${Array.from(traces).join("\n\n")}`
4264
4294
  _addToRealUndoStack(historyOps, batchUpdates);
4265
4295
  }
4266
4296
  }
4267
- function simulateCloseWebsocket() {
4297
+ function handleImplicitClose() {
4268
4298
  if (context.socket) {
4269
4299
  context.socket = null;
4270
4300
  }
4271
4301
  }
4272
- function simulateSendCloseEvent(event) {
4273
- onClose(event);
4274
- }
4275
4302
  function getStorageStatus() {
4276
4303
  if (_getInitialStatePromise === null) {
4277
4304
  return "not-loaded";
@@ -4304,6 +4331,28 @@ ${Array.from(traces).join("\n\n")}`
4304
4331
  storageDidLoad: eventHub.storageDidLoad.observable,
4305
4332
  storageStatus: eventHub.storageStatus.observable
4306
4333
  };
4334
+ function transition(event) {
4335
+ switch (event.type) {
4336
+ case "CONNECT":
4337
+ return handleConnect();
4338
+ case "DISCONNECT":
4339
+ return handleDisconnect();
4340
+ case "RECEIVE_PONG":
4341
+ return handlePong();
4342
+ case "AUTH_SUCCESS":
4343
+ return handleAuthSuccess(event.token, event.socket);
4344
+ case "WINDOW_GOT_FOCUS":
4345
+ return handleWindowGotFocus();
4346
+ case "NAVIGATOR_ONLINE":
4347
+ return handleNavigatorBackOnline();
4348
+ case "IMPLICIT_CLOSE":
4349
+ return handleImplicitClose();
4350
+ case "EXPLICIT_CLOSE":
4351
+ return handleExplicitClose(event.closeEvent);
4352
+ default:
4353
+ return assertNever(event, "Invalid event");
4354
+ }
4355
+ }
4307
4356
  return {
4308
4357
  /* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
4309
4358
  __internal: {
@@ -4315,20 +4364,36 @@ ${Array.from(traces).join("\n\n")}`
4315
4364
  return context.numRetries;
4316
4365
  },
4317
4366
  // prettier-ignore
4318
- onClose,
4319
- onMessage,
4320
- authenticationSuccess,
4321
- onNavigatorOnline,
4322
- simulateCloseWebsocket,
4323
- simulateSendCloseEvent,
4324
- onVisibilityChange,
4325
- getUndoStack: () => context.undoStack,
4326
- getItemsCount: () => context.nodes.size,
4327
- connect,
4328
- disconnect,
4367
+ get undoStack() {
4368
+ return context.undoStack;
4369
+ },
4370
+ // prettier-ignore
4371
+ get nodeCount() {
4372
+ return context.nodes.size;
4373
+ },
4374
+ // prettier-ignore
4329
4375
  // Support for the Liveblocks browser extension
4330
4376
  getSelf_forDevTools: () => selfAsTreeNode.current,
4331
- getOthers_forDevTools: () => others_forDevTools.current
4377
+ getOthers_forDevTools: () => others_forDevTools.current,
4378
+ // prettier-ignore
4379
+ send: {
4380
+ explicitClose: (closeEvent) => transition({ type: "EXPLICIT_CLOSE", closeEvent }),
4381
+ implicitClose: () => transition({ type: "IMPLICIT_CLOSE" }),
4382
+ authSuccess: (token, socket) => transition({ type: "AUTH_SUCCESS", token, socket }),
4383
+ navigatorOnline: () => transition({ type: "NAVIGATOR_ONLINE" }),
4384
+ windowGotFocus: () => transition({ type: "WINDOW_GOT_FOCUS" }),
4385
+ pong: () => transition({ type: "RECEIVE_PONG" }),
4386
+ connect: () => transition({ type: "CONNECT" }),
4387
+ disconnect: () => transition({ type: "DISCONNECT" }),
4388
+ /**
4389
+ * This one looks differently from the rest, because receiving messages
4390
+ * is handled orthorgonally from all other possible events above,
4391
+ * because it does not matter what the connectivity state of the
4392
+ * machine is, so there won't be an explicit state machine transition
4393
+ * needed for this event.
4394
+ */
4395
+ incomingMessage: handleServerMessage
4396
+ }
4332
4397
  },
4333
4398
  id: config.roomId,
4334
4399
  subscribe: makeClassicSubscribeFn(events),
@@ -4404,10 +4469,6 @@ function makeClassicSubscribeFn(events) {
4404
4469
  return events.connection.subscribe(
4405
4470
  callback
4406
4471
  );
4407
- case "storage":
4408
- return events.storage.subscribe(
4409
- callback
4410
- );
4411
4472
  case "history":
4412
4473
  return events.history.subscribe(callback);
4413
4474
  case "storage-status":
@@ -4456,34 +4517,49 @@ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
4456
4517
  );
4457
4518
  }
4458
4519
  const ws = WebSocketPolyfill || WebSocket;
4459
- return (token) => {
4520
+ return (richToken) => {
4521
+ const token = richToken.raw;
4460
4522
  return new ws(
4461
4523
  `${liveblocksServer}/?token=${token}&version=${// prettier-ignore
4462
4524
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4463
4525
  // @ts-ignore (__PACKAGE_VERSION__ will be injected by the build script)
4464
4526
  true ? (
4465
4527
  /* istanbul ignore next */
4466
- "1.0.8"
4528
+ "1.0.10"
4467
4529
  ) : "dev"}`
4468
4530
  );
4469
4531
  };
4470
4532
  }
4471
- function prepareAuthEndpoint(authentication, fetchPolyfill) {
4533
+ function httpSend(message, token, endpoint, fetchPolyfill) {
4534
+ return __async(this, null, function* () {
4535
+ const fetcher = fetchPolyfill || /* istanbul ignore next */
4536
+ fetch;
4537
+ return fetcher(endpoint, {
4538
+ method: "POST",
4539
+ headers: {
4540
+ "Content-Type": "application/json",
4541
+ Authorization: `Bearer ${token}`
4542
+ },
4543
+ body: message
4544
+ });
4545
+ });
4546
+ }
4547
+ function prepareAuthEndpoint(roomId, authentication, fetchPolyfill) {
4472
4548
  if (authentication.type === "public") {
4473
4549
  if (typeof window === "undefined" && fetchPolyfill === void 0) {
4474
4550
  throw new Error(
4475
4551
  "To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill."
4476
4552
  );
4477
4553
  }
4478
- return (room) => fetchAuthEndpoint(
4554
+ return () => fetchAuthEndpoint(
4479
4555
  fetchPolyfill || /* istanbul ignore next */
4480
4556
  fetch,
4481
4557
  authentication.url,
4482
4558
  {
4483
- room,
4559
+ room: roomId,
4484
4560
  publicApiKey: authentication.publicApiKey
4485
4561
  }
4486
- );
4562
+ ).then(({ token }) => parseRoomAuthToken(token));
4487
4563
  }
4488
4564
  if (authentication.type === "private") {
4489
4565
  if (typeof window === "undefined" && fetchPolyfill === void 0) {
@@ -4491,19 +4567,19 @@ function prepareAuthEndpoint(authentication, fetchPolyfill) {
4491
4567
  "To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
4492
4568
  );
4493
4569
  }
4494
- return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
4495
- room
4496
- });
4570
+ return () => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
4571
+ room: roomId
4572
+ }).then(({ token }) => parseRoomAuthToken(token));
4497
4573
  }
4498
4574
  if (authentication.type === "custom") {
4499
- return (room) => __async(this, null, function* () {
4500
- const response = yield authentication.callback(room);
4575
+ return () => __async(this, null, function* () {
4576
+ const response = yield authentication.callback(roomId);
4501
4577
  if (!response || !response.token) {
4502
4578
  throw new Error(
4503
4579
  'Authentication error. We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
4504
4580
  );
4505
4581
  }
4506
- return response;
4582
+ return parseRoomAuthToken(response.token);
4507
4583
  });
4508
4584
  }
4509
4585
  throw new Error("Internal error. Unexpected authentication type");
@@ -4588,7 +4664,12 @@ function createClient(options) {
4588
4664
  polyfills: clientOptions.polyfills,
4589
4665
  unstable_batchedUpdates: options2 == null ? void 0 : options2.unstable_batchedUpdates,
4590
4666
  liveblocksServer: getServerFromClientOptions(clientOptions),
4591
- authentication: prepareAuthentication(clientOptions, roomId)
4667
+ authentication: prepareAuthentication(clientOptions, roomId),
4668
+ httpSendEndpoint: buildLiveblocksHttpSendEndpoint(
4669
+ clientOptions,
4670
+ roomId
4671
+ ),
4672
+ unstable_fallbackToHTTP: !!clientOptions.unstable_fallbackToHTTP
4592
4673
  }
4593
4674
  );
4594
4675
  rooms.set(roomId, newRoom);
@@ -4604,7 +4685,7 @@ function createClient(options) {
4604
4685
  }
4605
4686
  global.atob = clientOptions.polyfills.atob;
4606
4687
  }
4607
- newRoom.__internal.connect();
4688
+ newRoom.__internal.send.connect();
4608
4689
  }
4609
4690
  return newRoom;
4610
4691
  }
@@ -4612,7 +4693,7 @@ function createClient(options) {
4612
4693
  unlinkDevTools(roomId);
4613
4694
  const room = rooms.get(roomId);
4614
4695
  if (room !== void 0) {
4615
- room.__internal.disconnect();
4696
+ room.__internal.send.disconnect();
4616
4697
  rooms.delete(roomId);
4617
4698
  }
4618
4699
  }
@@ -4620,14 +4701,16 @@ function createClient(options) {
4620
4701
  typeof window.addEventListener !== "undefined") {
4621
4702
  window.addEventListener("online", () => {
4622
4703
  for (const [, room] of rooms) {
4623
- room.__internal.onNavigatorOnline();
4704
+ room.__internal.send.navigatorOnline();
4624
4705
  }
4625
4706
  });
4626
4707
  }
4627
4708
  if (typeof document !== "undefined") {
4628
4709
  document.addEventListener("visibilitychange", () => {
4629
- for (const [, room] of rooms) {
4630
- room.__internal.onVisibilityChange(document.visibilityState);
4710
+ if (document.visibilityState === "visible") {
4711
+ for (const [, room] of rooms) {
4712
+ room.__internal.send.windowGotFocus();
4713
+ }
4631
4714
  }
4632
4715
  });
4633
4716
  }
@@ -4690,6 +4773,14 @@ function prepareAuthentication(clientOptions, roomId) {
4690
4773
  "Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
4691
4774
  );
4692
4775
  }
4776
+ function buildLiveblocksHttpSendEndpoint(options, roomId) {
4777
+ if (options.httpSendEndpoint) {
4778
+ return options.httpSendEndpoint.replace("{roomId}", roomId);
4779
+ }
4780
+ return `https://api.liveblocks.io/v2/rooms/${encodeURIComponent(
4781
+ roomId
4782
+ )}/send-message`;
4783
+ }
4693
4784
  function buildLiveblocksPublicAuthorizeEndpoint(options, roomId) {
4694
4785
  if (options.publicAuthorizeEndpoint) {
4695
4786
  return options.publicAuthorizeEndpoint.replace("{roomId}", roomId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/core",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Shared code and foundational internals for Liveblocks",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",