@liveblocks/client 0.13.2 → 0.14.1

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 (41) hide show
  1. package/lib/cjs/AbstractCrdt.d.ts +8 -4
  2. package/lib/cjs/AbstractCrdt.js +2 -2
  3. package/lib/cjs/LiveList.d.ts +9 -4
  4. package/lib/cjs/LiveList.js +58 -9
  5. package/lib/cjs/LiveMap.d.ts +7 -3
  6. package/lib/cjs/LiveMap.js +23 -5
  7. package/lib/cjs/LiveObject.d.ts +17 -7
  8. package/lib/cjs/LiveObject.js +45 -15
  9. package/lib/cjs/LiveRegister.d.ts +8 -4
  10. package/lib/cjs/LiveRegister.js +17 -4
  11. package/lib/cjs/client.js +50 -7
  12. package/lib/cjs/{immutable/index.d.ts → immutable.d.ts} +5 -3
  13. package/lib/cjs/{immutable/index.js → immutable.js} +98 -21
  14. package/lib/cjs/index.d.ts +1 -1
  15. package/lib/cjs/live.d.ts +7 -0
  16. package/lib/cjs/room.d.ts +17 -9
  17. package/lib/cjs/room.js +203 -81
  18. package/lib/cjs/types.d.ts +26 -1
  19. package/lib/cjs/utils.d.ts +2 -1
  20. package/lib/cjs/utils.js +76 -1
  21. package/lib/esm/AbstractCrdt.d.ts +8 -4
  22. package/lib/esm/AbstractCrdt.js +2 -2
  23. package/lib/esm/LiveList.d.ts +9 -4
  24. package/lib/esm/LiveList.js +59 -10
  25. package/lib/esm/LiveMap.d.ts +7 -3
  26. package/lib/esm/LiveMap.js +23 -5
  27. package/lib/esm/LiveObject.d.ts +17 -7
  28. package/lib/esm/LiveObject.js +45 -15
  29. package/lib/esm/LiveRegister.d.ts +8 -4
  30. package/lib/esm/LiveRegister.js +18 -5
  31. package/lib/esm/client.js +50 -7
  32. package/lib/esm/{immutable/index.d.ts → immutable.d.ts} +5 -3
  33. package/lib/esm/{immutable/index.js → immutable.js} +96 -21
  34. package/lib/esm/index.d.ts +1 -1
  35. package/lib/esm/live.d.ts +7 -0
  36. package/lib/esm/room.d.ts +17 -9
  37. package/lib/esm/room.js +203 -62
  38. package/lib/esm/types.d.ts +26 -1
  39. package/lib/esm/utils.d.ts +2 -1
  40. package/lib/esm/utils.js +75 -1
  41. package/package.json +6 -2
package/lib/esm/room.js CHANGED
@@ -7,8 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { isSameNodeOrChildOf, remove } from "./utils";
11
- import auth, { parseToken } from "./authentication";
10
+ import { getTreesDiffOperations, isSameNodeOrChildOf, remove } from "./utils";
12
11
  import { ClientMessageType, ServerMessageType, OpType, } from "./live";
13
12
  import { LiveMap } from "./LiveMap";
14
13
  import { LiveObject } from "./LiveObject";
@@ -36,6 +35,9 @@ function makeOthers(presenceMap) {
36
35
  get count() {
37
36
  return array.length;
38
37
  },
38
+ [Symbol.iterator]() {
39
+ return array[Symbol.iterator]();
40
+ },
39
41
  map(callback) {
40
42
  return array.map(callback);
41
43
  },
@@ -50,16 +52,12 @@ function log(...params) {
50
52
  }
51
53
  export function makeStateMachine(state, context, mockedEffects) {
52
54
  const effects = mockedEffects || {
53
- authenticate() {
55
+ authenticate(auth, createWebSocket) {
54
56
  return __awaiter(this, void 0, void 0, function* () {
55
57
  try {
56
- const token = yield auth(context.authEndpoint, context.room, context.publicApiKey);
58
+ const { token } = yield auth(context.room);
57
59
  const parsedToken = parseToken(token);
58
- const socket = new WebSocket(`${context.liveblocksServer}/?token=${token}`);
59
- socket.addEventListener("message", onMessage);
60
- socket.addEventListener("open", onOpen);
61
- socket.addEventListener("close", onClose);
62
- socket.addEventListener("error", onError);
60
+ const socket = createWebSocket(token);
63
61
  authenticationSuccess(parsedToken, socket);
64
62
  }
65
63
  catch (er) {
@@ -107,18 +105,23 @@ export function makeStateMachine(state, context, mockedEffects) {
107
105
  };
108
106
  return genericSubscribe(cb);
109
107
  }
110
- function createRootFromMessage(message) {
111
- state.root = load(message.items);
108
+ function createOrUpdateRootFromMessage(message) {
109
+ if (message.items.length === 0) {
110
+ throw new Error("Internal error: cannot load storage without items");
111
+ }
112
+ if (state.root) {
113
+ updateRoot(message.items);
114
+ }
115
+ else {
116
+ state.root = load(message.items);
117
+ }
112
118
  for (const key in state.defaultStorageRoot) {
113
119
  if (state.root.get(key) == null) {
114
120
  state.root.set(key, state.defaultStorageRoot[key]);
115
121
  }
116
122
  }
117
123
  }
118
- function load(items) {
119
- if (items.length === 0) {
120
- throw new Error("Internal error: cannot load storage without items");
121
- }
124
+ function buildRootAndParentToChildren(items) {
122
125
  const parentToChildren = new Map();
123
126
  let root = null;
124
127
  for (const tuple of items) {
@@ -139,6 +142,23 @@ export function makeStateMachine(state, context, mockedEffects) {
139
142
  if (root == null) {
140
143
  throw new Error("Root can't be null");
141
144
  }
145
+ return [root, parentToChildren];
146
+ }
147
+ function updateRoot(items) {
148
+ if (!state.root) {
149
+ return;
150
+ }
151
+ const currentItems = new Map();
152
+ state.items.forEach((liveCrdt, id) => {
153
+ currentItems.set(id, liveCrdt._toSerializedCrdt());
154
+ });
155
+ // Get operations that represent the diff between 2 states.
156
+ const ops = getTreesDiffOperations(currentItems, new Map(items));
157
+ const result = apply(ops, false);
158
+ notify(result.updates);
159
+ }
160
+ function load(items) {
161
+ const [root, parentToChildren] = buildRootAndParentToChildren(items);
142
162
  return LiveObject._deserialize(root, parentToChildren, {
143
163
  addItem,
144
164
  deleteItem,
@@ -227,7 +247,10 @@ export function makeStateMachine(state, context, mockedEffects) {
227
247
  state.connection.state === "connecting") {
228
248
  return state.connection.id;
229
249
  }
230
- throw new Error("Internal. Tried to get connection id but connection is not open");
250
+ else if (state.lastConnectionId !== null) {
251
+ return state.lastConnectionId;
252
+ }
253
+ throw new Error("Internal. Tried to get connection id but connection was never open");
231
254
  }
232
255
  function generateId() {
233
256
  return `${getConnectionId()}:${state.clock++}`;
@@ -235,7 +258,7 @@ export function makeStateMachine(state, context, mockedEffects) {
235
258
  function generateOpId() {
236
259
  return `${getConnectionId()}:${state.opClock++}`;
237
260
  }
238
- function apply(item) {
261
+ function apply(item, isLocal) {
239
262
  const result = {
240
263
  reverse: [],
241
264
  updates: { nodes: new Set(), presence: false },
@@ -262,7 +285,11 @@ export function makeStateMachine(state, context, mockedEffects) {
262
285
  result.updates.presence = true;
263
286
  }
264
287
  else {
265
- const applyOpResult = applyOp(op);
288
+ // Ops applied after undo/redo don't have an opId.
289
+ if (isLocal && !op.opId) {
290
+ op.opId = generateOpId();
291
+ }
292
+ const applyOpResult = applyOp(op, isLocal);
266
293
  if (applyOpResult.modified) {
267
294
  result.updates.nodes.add(applyOpResult.modified);
268
295
  result.reverse.unshift(...applyOpResult.reverse);
@@ -271,7 +298,10 @@ export function makeStateMachine(state, context, mockedEffects) {
271
298
  }
272
299
  return result;
273
300
  }
274
- function applyOp(op) {
301
+ function applyOp(op, isLocal) {
302
+ if (op.opId) {
303
+ state.offlineOperations.delete(op.opId);
304
+ }
275
305
  switch (op.type) {
276
306
  case OpType.DeleteObjectKey:
277
307
  case OpType.UpdateObject:
@@ -280,7 +310,7 @@ export function makeStateMachine(state, context, mockedEffects) {
280
310
  if (item == null) {
281
311
  return { modified: false };
282
312
  }
283
- return item._apply(op);
313
+ return item._apply(op, isLocal);
284
314
  }
285
315
  case OpType.SetParentKey: {
286
316
  const item = state.items.get(op.id);
@@ -308,28 +338,28 @@ export function makeStateMachine(state, context, mockedEffects) {
308
338
  if (parent == null || getItem(op.id) != null) {
309
339
  return { modified: false };
310
340
  }
311
- return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data));
341
+ return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal);
312
342
  }
313
343
  case OpType.CreateList: {
314
344
  const parent = state.items.get(op.parentId);
315
345
  if (parent == null || getItem(op.id) != null) {
316
346
  return { modified: false };
317
347
  }
318
- return parent._attachChild(op.id, op.parentKey, new LiveList());
348
+ return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal);
319
349
  }
320
350
  case OpType.CreateRegister: {
321
351
  const parent = state.items.get(op.parentId);
322
352
  if (parent == null || getItem(op.id) != null) {
323
353
  return { modified: false };
324
354
  }
325
- return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data));
355
+ return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal);
326
356
  }
327
357
  case OpType.CreateMap: {
328
358
  const parent = state.items.get(op.parentId);
329
359
  if (parent == null || getItem(op.id) != null) {
330
360
  return { modified: false };
331
361
  }
332
- return parent._attachChild(op.id, op.parentKey, new LiveMap());
362
+ return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal);
333
363
  }
334
364
  }
335
365
  return { modified: false };
@@ -376,15 +406,14 @@ See v0.13 release notes for more information.
376
406
  : null;
377
407
  }
378
408
  function connect() {
379
- if (typeof window === "undefined") {
380
- return;
381
- }
382
409
  if (state.connection.state !== "closed" &&
383
410
  state.connection.state !== "unavailable") {
384
411
  return null;
385
412
  }
413
+ const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
414
+ const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
386
415
  updateConnection({ state: "authenticating" });
387
- effects.authenticate();
416
+ effects.authenticate(auth, createWebSocket);
388
417
  }
389
418
  function updatePresence(overrides, options) {
390
419
  const oldValues = {};
@@ -411,6 +440,10 @@ See v0.13 release notes for more information.
411
440
  }
412
441
  }
413
442
  function authenticationSuccess(token, socket) {
443
+ socket.addEventListener("message", onMessage);
444
+ socket.addEventListener("open", onOpen);
445
+ socket.addEventListener("close", onClose);
446
+ socket.addEventListener("error", onError);
414
447
  updateConnection({
415
448
  state: "connecting",
416
449
  id: token.actor,
@@ -421,7 +454,9 @@ See v0.13 release notes for more information.
421
454
  state.socket = socket;
422
455
  }
423
456
  function authenticationFailure(error) {
424
- console.error(error);
457
+ if (process.env.NODE_ENV !== "production") {
458
+ console.error("Call to authentication endpoint failed", error);
459
+ }
425
460
  updateConnection({ state: "unavailable" });
426
461
  state.numberOfRetry++;
427
462
  state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
@@ -549,12 +584,13 @@ See v0.13 release notes for more information.
549
584
  break;
550
585
  }
551
586
  case ServerMessageType.InitialStorageState: {
552
- createRootFromMessage(subMessage);
587
+ createOrUpdateRootFromMessage(subMessage);
588
+ applyAndSendOfflineOps();
553
589
  _getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
554
590
  break;
555
591
  }
556
592
  case ServerMessageType.UpdateStorage: {
557
- const applyResult = apply(subMessage.ops);
593
+ const applyResult = apply(subMessage.ops, false);
558
594
  for (const node of applyResult.updates.nodes) {
559
595
  updates.nodes.add(node);
560
596
  }
@@ -587,14 +623,20 @@ See v0.13 release notes for more information.
587
623
  updateConnection({ state: "failed" });
588
624
  const error = new LiveblocksError(event.reason, event.code);
589
625
  for (const listener of state.listeners.error) {
590
- console.error(`Liveblocks WebSocket connection closed. Reason: ${error.message} (code: ${error.code})`);
626
+ if (process.env.NODE_ENV !== "production") {
627
+ console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
628
+ }
591
629
  listener(error);
592
630
  }
593
631
  }
594
632
  else if (event.wasClean === false) {
595
- updateConnection({ state: "unavailable" });
596
633
  state.numberOfRetry++;
597
- state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
634
+ const delay = getRetryDelay();
635
+ if (process.env.NODE_ENV !== "production") {
636
+ console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
637
+ }
638
+ updateConnection({ state: "unavailable" });
639
+ state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
598
640
  }
599
641
  else {
600
642
  updateConnection({ state: "closed" });
@@ -618,6 +660,10 @@ See v0.13 release notes for more information.
618
660
  if (state.connection.state === "connecting") {
619
661
  updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
620
662
  state.numberOfRetry = 0;
663
+ state.lastConnectionId = state.connection.id;
664
+ if (state.root) {
665
+ state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
666
+ }
621
667
  tryFlushing();
622
668
  }
623
669
  else {
@@ -631,7 +677,7 @@ See v0.13 release notes for more information.
631
677
  }
632
678
  clearTimeout(state.timeoutHandles.pongTimeout);
633
679
  state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
634
- if (state.socket.readyState === WebSocket.OPEN) {
680
+ if (state.socket.readyState === state.socket.OPEN) {
635
681
  state.socket.send("ping");
636
682
  }
637
683
  }
@@ -657,11 +703,29 @@ See v0.13 release notes for more information.
657
703
  clearInterval(state.intervalHandles.heartbeat);
658
704
  connect();
659
705
  }
660
- function tryFlushing() {
661
- if (state.socket == null) {
706
+ function applyAndSendOfflineOps() {
707
+ if (state.offlineOperations.size === 0) {
662
708
  return;
663
709
  }
664
- if (state.socket.readyState !== WebSocket.OPEN) {
710
+ const messages = [];
711
+ const ops = Array.from(state.offlineOperations.values());
712
+ const result = apply(ops, true);
713
+ messages.push({
714
+ type: ClientMessageType.UpdateStorage,
715
+ ops: ops,
716
+ });
717
+ notify(result.updates);
718
+ effects.send(messages);
719
+ }
720
+ function tryFlushing() {
721
+ const storageOps = state.buffer.storageOperations;
722
+ if (storageOps.length > 0) {
723
+ storageOps.forEach((op) => {
724
+ state.offlineOperations.set(op.opId, op);
725
+ });
726
+ }
727
+ if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
728
+ state.buffer.storageOperations = [];
665
729
  return;
666
730
  }
667
731
  const now = Date.now();
@@ -736,8 +800,10 @@ See v0.13 release notes for more information.
736
800
  function getOthers() {
737
801
  return state.others;
738
802
  }
739
- function broadcastEvent(event) {
740
- if (state.socket == null) {
803
+ function broadcastEvent(event, options = {
804
+ shouldQueueEventIfNotReady: false,
805
+ }) {
806
+ if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
741
807
  return;
742
808
  }
743
809
  state.buffer.messages.push({
@@ -779,7 +845,7 @@ See v0.13 release notes for more information.
779
845
  return;
780
846
  }
781
847
  state.isHistoryPaused = false;
782
- const result = apply(historyItem);
848
+ const result = apply(historyItem, true);
783
849
  notify(result.updates);
784
850
  state.redoStack.push(result.reverse);
785
851
  for (const op of historyItem) {
@@ -798,7 +864,7 @@ See v0.13 release notes for more information.
798
864
  return;
799
865
  }
800
866
  state.isHistoryPaused = false;
801
- const result = apply(historyItem);
867
+ const result = apply(historyItem, true);
802
868
  notify(result.updates);
803
869
  state.undoStack.push(result.reverse);
804
870
  for (const op of historyItem) {
@@ -850,14 +916,26 @@ See v0.13 release notes for more information.
850
916
  }
851
917
  state.pausedHistory = [];
852
918
  }
919
+ function simulateSocketClose() {
920
+ if (state.socket) {
921
+ state.socket.close();
922
+ }
923
+ }
924
+ function simulateSendCloseEvent(event) {
925
+ if (state.socket) {
926
+ onClose(event);
927
+ }
928
+ }
853
929
  return {
854
930
  // Internal
855
- onOpen,
856
931
  onClose,
857
932
  onMessage,
858
933
  authenticationSuccess,
859
934
  heartbeat,
860
935
  onNavigatorOnline,
936
+ // Internal dev tools
937
+ simulateSocketClose,
938
+ simulateSendCloseEvent,
861
939
  // onWakeUp,
862
940
  onVisibilityChange,
863
941
  getUndoStack: () => state.undoStack,
@@ -889,6 +967,7 @@ See v0.13 release notes for more information.
889
967
  export function defaultState(me, defaultStorageRoot) {
890
968
  return {
891
969
  connection: { state: "closed" },
970
+ lastConnectionId: null,
892
971
  socket: null,
893
972
  listeners: {
894
973
  event: [],
@@ -933,28 +1012,12 @@ export function defaultState(me, defaultStorageRoot) {
933
1012
  updates: { nodes: new Set(), presence: false, others: [] },
934
1013
  reverseOps: [],
935
1014
  },
1015
+ offlineOperations: new Map(),
936
1016
  };
937
1017
  }
938
- export function createRoom(name, options) {
939
- const throttleDelay = options.throttle || 100;
940
- const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
941
- let authEndpoint;
942
- if (options.authEndpoint) {
943
- authEndpoint = options.authEndpoint;
944
- }
945
- else {
946
- const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
947
- "https://liveblocks.io/api/public/authorize";
948
- authEndpoint = publicAuthorizeEndpoint;
949
- }
1018
+ export function createRoom(options, context) {
950
1019
  const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
951
- const machine = makeStateMachine(state, {
952
- throttleDelay,
953
- liveblocksServer,
954
- authEndpoint,
955
- room: name,
956
- publicApiKey: options.publicApiKey,
957
- });
1020
+ const machine = makeStateMachine(state, context);
958
1021
  const room = {
959
1022
  /////////////
960
1023
  // Core //
@@ -978,6 +1041,11 @@ export function createRoom(name, options) {
978
1041
  pause: machine.pauseHistory,
979
1042
  resume: machine.resumeHistory,
980
1043
  },
1044
+ // @ts-ignore
1045
+ internalDevTools: {
1046
+ closeWebsocket: machine.simulateSocketClose,
1047
+ sendCloseEvent: machine.simulateSendCloseEvent,
1048
+ },
981
1049
  };
982
1050
  return {
983
1051
  connect: machine.connect,
@@ -993,3 +1061,76 @@ class LiveblocksError extends Error {
993
1061
  this.code = code;
994
1062
  }
995
1063
  }
1064
+ function parseToken(token) {
1065
+ const tokenParts = token.split(".");
1066
+ if (tokenParts.length !== 3) {
1067
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1068
+ }
1069
+ const data = JSON.parse(atob(tokenParts[1]));
1070
+ if (typeof data.actor !== "number") {
1071
+ throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1072
+ }
1073
+ return data;
1074
+ }
1075
+ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
1076
+ if (typeof window === "undefined" && WebSocketPolyfill == null) {
1077
+ throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
1078
+ }
1079
+ const ws = WebSocketPolyfill || WebSocket;
1080
+ return (token) => {
1081
+ return new ws(`${liveblocksServer}/?token=${token}`);
1082
+ };
1083
+ }
1084
+ function prepareAuthEndpoint(authentication, fetchPolyfill) {
1085
+ if (authentication.type === "public") {
1086
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1087
+ throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
1088
+ }
1089
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1090
+ room,
1091
+ publicApiKey: authentication.publicApiKey,
1092
+ });
1093
+ }
1094
+ if (authentication.type === "private") {
1095
+ if (typeof window === "undefined" && fetchPolyfill == null) {
1096
+ throw new Error("To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill.");
1097
+ }
1098
+ return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1099
+ room,
1100
+ });
1101
+ }
1102
+ if (authentication.type === "custom") {
1103
+ return authentication.callback;
1104
+ }
1105
+ throw new Error("Internal error. Unexpected authentication type");
1106
+ }
1107
+ function fetchAuthEndpoint(fetch, endpoint, body) {
1108
+ return __awaiter(this, void 0, void 0, function* () {
1109
+ const res = yield fetch(endpoint, {
1110
+ method: "POST",
1111
+ headers: {
1112
+ "Content-Type": "application/json",
1113
+ },
1114
+ body: JSON.stringify(body),
1115
+ });
1116
+ if (!res.ok) {
1117
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1118
+ }
1119
+ let authResponse = null;
1120
+ try {
1121
+ authResponse = yield res.json();
1122
+ }
1123
+ catch (er) {
1124
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1125
+ }
1126
+ if (typeof authResponse.token !== "string") {
1127
+ throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
1128
+ }
1129
+ return authResponse;
1130
+ });
1131
+ }
1132
+ class AuthenticationError extends Error {
1133
+ constructor(message) {
1134
+ super(message);
1135
+ }
1136
+ }
@@ -28,6 +28,14 @@ export declare type LiveListUpdates<TItem = any> = {
28
28
  type: "LiveList";
29
29
  node: LiveList<TItem>;
30
30
  };
31
+ export declare type BroadcastOptions = {
32
+ /**
33
+ * Whether or not event is queued if the connection is currently closed.
34
+ *
35
+ * ❗ We are not sure if we want to support this option in the future so it might be deprecated to be replaced by something else
36
+ */
37
+ shouldQueueEventIfNotReady: boolean;
38
+ };
31
39
  export declare type StorageUpdate = LiveMapUpdates | LiveObjectUpdates | LiveListUpdates;
32
40
  export declare type StorageCallback = (updates: StorageUpdate[]) => void;
33
41
  export declare type Client = {
@@ -65,6 +73,10 @@ export interface Others<TPresence extends Presence = Presence> {
65
73
  * Number of other users in the room.
66
74
  */
67
75
  readonly count: number;
76
+ /**
77
+ * Returns a new Iterator object that contains the users.
78
+ */
79
+ [Symbol.iterator](): IterableIterator<User<TPresence>>;
68
80
  /**
69
81
  * Returns the array of connected users in room.
70
82
  */
@@ -109,6 +121,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
109
121
  */
110
122
  export declare type ClientOptions = {
111
123
  throttle?: number;
124
+ fetchPolyfill?: any;
125
+ WebSocketPolyfill?: any;
112
126
  } & ({
113
127
  publicApiKey: string;
114
128
  authEndpoint?: never;
@@ -119,6 +133,17 @@ export declare type ClientOptions = {
119
133
  export declare type AuthorizeResponse = {
120
134
  token: string;
121
135
  };
136
+ export declare type Authentication = {
137
+ type: "public";
138
+ publicApiKey: string;
139
+ url: string;
140
+ } | {
141
+ type: "private";
142
+ url: string;
143
+ } | {
144
+ type: "custom";
145
+ callback: (room: string) => Promise<AuthorizeResponse>;
146
+ };
122
147
  declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
123
148
  export declare type Connection = {
124
149
  state: "closed" | "authenticating" | "unavailable" | "failed";
@@ -423,7 +448,7 @@ export declare type Room = {
423
448
  * }
424
449
  * });
425
450
  */
426
- broadcastEvent: (event: any) => void;
451
+ broadcastEvent: (event: any, options?: BroadcastOptions) => void;
427
452
  /**
428
453
  * Get the room's storage asynchronously.
429
454
  * The storage's root is a {@link LiveObject}.
@@ -1,8 +1,9 @@
1
1
  import { AbstractCrdt, Doc } from "./AbstractCrdt";
2
- import { SerializedCrdtWithId } from "./live";
2
+ import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live";
3
3
  export declare function remove<T>(array: T[], item: T): void;
4
4
  export declare function isSameNodeOrChildOf(node: AbstractCrdt, parent: AbstractCrdt): boolean;
5
5
  export declare function deserialize(entry: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): AbstractCrdt;
6
6
  export declare function isCrdt(obj: any): obj is AbstractCrdt;
7
7
  export declare function selfOrRegisterValue(obj: AbstractCrdt): any;
8
8
  export declare function selfOrRegister(obj: any): AbstractCrdt;
9
+ export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[];
package/lib/esm/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CrdtType, } from "./live";
1
+ import { CrdtType, OpType, } from "./live";
2
2
  import { LiveList } from "./LiveList";
3
3
  import { LiveMap } from "./LiveMap";
4
4
  import { LiveObject } from "./LiveObject";
@@ -64,3 +64,77 @@ export function selfOrRegister(obj) {
64
64
  return new LiveRegister(obj);
65
65
  }
66
66
  }
67
+ export function getTreesDiffOperations(currentItems, newItems) {
68
+ const ops = [];
69
+ currentItems.forEach((_, id) => {
70
+ if (!newItems.get(id)) {
71
+ // Delete crdt
72
+ ops.push({
73
+ type: OpType.DeleteCrdt,
74
+ id: id,
75
+ });
76
+ }
77
+ });
78
+ newItems.forEach((crdt, id) => {
79
+ const currentCrdt = currentItems.get(id);
80
+ if (currentCrdt) {
81
+ if (crdt.type === CrdtType.Object) {
82
+ if (JSON.stringify(crdt.data) !==
83
+ JSON.stringify(currentCrdt.data)) {
84
+ ops.push({
85
+ type: OpType.UpdateObject,
86
+ id: id,
87
+ data: crdt.data,
88
+ });
89
+ }
90
+ }
91
+ if (crdt.parentKey !== currentCrdt.parentKey) {
92
+ ops.push({
93
+ type: OpType.SetParentKey,
94
+ id: id,
95
+ parentKey: crdt.parentKey,
96
+ });
97
+ }
98
+ }
99
+ else {
100
+ // new Crdt
101
+ switch (crdt.type) {
102
+ case CrdtType.Register:
103
+ ops.push({
104
+ type: OpType.CreateRegister,
105
+ id: id,
106
+ parentId: crdt.parentId,
107
+ parentKey: crdt.parentKey,
108
+ data: crdt.data,
109
+ });
110
+ break;
111
+ case CrdtType.List:
112
+ ops.push({
113
+ type: OpType.CreateList,
114
+ id: id,
115
+ parentId: crdt.parentId,
116
+ parentKey: crdt.parentKey,
117
+ });
118
+ break;
119
+ case CrdtType.Object:
120
+ ops.push({
121
+ type: OpType.CreateObject,
122
+ id: id,
123
+ parentId: crdt.parentId,
124
+ parentKey: crdt.parentKey,
125
+ data: crdt.data,
126
+ });
127
+ break;
128
+ case CrdtType.Map:
129
+ ops.push({
130
+ type: OpType.CreateMap,
131
+ id: id,
132
+ parentId: crdt.parentId,
133
+ parentKey: crdt.parentKey,
134
+ });
135
+ break;
136
+ }
137
+ }
138
+ });
139
+ return ops;
140
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/client",
3
- "version": "0.13.2",
3
+ "version": "0.14.1",
4
4
  "description": "",
5
5
  "main": "./lib/cjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -29,9 +29,13 @@
29
29
  "@babel/preset-env": "^7.12.16",
30
30
  "@babel/preset-typescript": "^7.12.16",
31
31
  "@types/jest": "^26.0.21",
32
+ "@types/node-fetch": "^2.6.1",
33
+ "@types/ws": "^8.2.2",
32
34
  "babel-jest": "^26.6.3",
33
35
  "jest": "^26.6.3",
34
- "typescript": "^4.4.0"
36
+ "node-fetch": "2.6.7",
37
+ "typescript": "^4.4.0",
38
+ "ws": "^8.5.0"
35
39
  },
36
40
  "repository": {
37
41
  "type": "git",