@liveblocks/client 0.13.0-beta.1 → 0.13.3

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/lib/cjs/room.js CHANGED
@@ -58,6 +58,9 @@ function makeOthers(presenceMap) {
58
58
  get count() {
59
59
  return array.length;
60
60
  },
61
+ [Symbol.iterator]() {
62
+ return array[Symbol.iterator]();
63
+ },
61
64
  map(callback) {
62
65
  return array.map(callback);
63
66
  },
@@ -443,7 +446,9 @@ See v0.13 release notes for more information.
443
446
  state.socket = socket;
444
447
  }
445
448
  function authenticationFailure(error) {
446
- console.error(error);
449
+ if (process.env.NODE_ENV !== "production") {
450
+ console.error("Call to authentication endpoint failed", error);
451
+ }
447
452
  updateConnection({ state: "unavailable" });
448
453
  state.numberOfRetry++;
449
454
  state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
@@ -609,13 +614,20 @@ See v0.13 release notes for more information.
609
614
  updateConnection({ state: "failed" });
610
615
  const error = new LiveblocksError(event.reason, event.code);
611
616
  for (const listener of state.listeners.error) {
617
+ if (process.env.NODE_ENV !== "production") {
618
+ console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
619
+ }
612
620
  listener(error);
613
621
  }
614
622
  }
615
623
  else if (event.wasClean === false) {
616
- updateConnection({ state: "unavailable" });
617
624
  state.numberOfRetry++;
618
- state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
625
+ const delay = getRetryDelay();
626
+ if (process.env.NODE_ENV !== "production") {
627
+ console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
628
+ }
629
+ updateConnection({ state: "unavailable" });
630
+ state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
619
631
  }
620
632
  else {
621
633
  updateConnection({ state: "closed" });
@@ -757,8 +769,10 @@ See v0.13 release notes for more information.
757
769
  function getOthers() {
758
770
  return state.others;
759
771
  }
760
- function broadcastEvent(event) {
761
- if (state.socket == null) {
772
+ function broadcastEvent(event, options = {
773
+ shouldQueueEventIfNotReady: false,
774
+ }) {
775
+ if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
762
776
  return;
763
777
  }
764
778
  state.buffer.messages.push({
@@ -839,10 +853,14 @@ See v0.13 release notes for more information.
839
853
  }
840
854
  finally {
841
855
  state.isBatching = false;
842
- addToUndoStack(state.batch.reverseOps);
856
+ if (state.batch.reverseOps.length > 0) {
857
+ addToUndoStack(state.batch.reverseOps);
858
+ }
843
859
  // Clear the redo stack because batch is always called from a local operation
844
860
  state.redoStack = [];
845
- dispatch(state.batch.ops);
861
+ if (state.batch.ops.length > 0) {
862
+ dispatch(state.batch.ops);
863
+ }
846
864
  notify(state.batch.updates);
847
865
  state.batch = {
848
866
  ops: [],
@@ -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
  */
@@ -277,25 +289,57 @@ export declare type Room = {
277
289
  }): () => void;
278
290
  };
279
291
  /**
280
- * Room's history contains function that let you undo and redo operation made on by the current client on the presence and storage.
292
+ * Room's history contains functions that let you undo and redo operation made on by the current client on the presence and storage.
281
293
  */
282
294
  history: {
283
295
  /**
284
296
  * Undoes the last operation executed by the current client.
285
297
  * It does not impact operations made by other clients.
298
+ *
299
+ * @example
300
+ * room.updatePresence({ selectedId: "xxx" }, { addToHistory: true });
301
+ * room.updatePresence({ selectedId: "yyy" }, { addToHistory: true });
302
+ * room.history.undo();
303
+ * // room.getPresence() equals { selectedId: "xxx" }
286
304
  */
287
305
  undo: () => void;
288
306
  /**
289
307
  * Redoes the last operation executed by the current client.
290
308
  * It does not impact operations made by other clients.
309
+ *
310
+ * @example
311
+ * room.updatePresence({ selectedId: "xxx" }, { addToHistory: true });
312
+ * room.updatePresence({ selectedId: "yyy" }, { addToHistory: true });
313
+ * room.history.undo();
314
+ * // room.getPresence() equals { selectedId: "xxx" }
315
+ * room.history.redo();
316
+ * // room.getPresence() equals { selectedId: "yyy" }
291
317
  */
292
318
  redo: () => void;
293
319
  /**
294
320
  * All future modifications made on the Room will be merged together to create a single history item until resume is called.
321
+ *
322
+ * @example
323
+ * room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });
324
+ * room.history.pause();
325
+ * room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });
326
+ * room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });
327
+ * room.history.resume();
328
+ * room.history.undo();
329
+ * // room.getPresence() equals { cursor: { x: 0, y: 0 } }
295
330
  */
296
331
  pause: () => void;
297
332
  /**
298
333
  * Resumes history. Modifications made on the Room are not merged into a single history item anymore.
334
+ *
335
+ * @example
336
+ * room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });
337
+ * room.history.pause();
338
+ * room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });
339
+ * room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });
340
+ * room.history.resume();
341
+ * room.history.undo();
342
+ * // room.getPresence() equals { cursor: { x: 0, y: 0 } }
299
343
  */
300
344
  resume: () => void;
301
345
  };
@@ -391,7 +435,14 @@ export declare type Room = {
391
435
  * }
392
436
  * });
393
437
  */
394
- broadcastEvent: (event: any) => void;
438
+ broadcastEvent: (event: any, options?: BroadcastOptions) => void;
439
+ /**
440
+ * Get the room's storage asynchronously.
441
+ * The storage's root is a {@link LiveObject}.
442
+ *
443
+ * @example
444
+ * const { root } = await room.getStorage();
445
+ */
395
446
  getStorage: <TRoot>() => Promise<{
396
447
  root: LiveObject<TRoot>;
397
448
  }>;
@@ -400,6 +451,13 @@ export declare type Room = {
400
451
  * All the modifications are sent to other clients in a single message.
401
452
  * All the subscribers are called only after the batch is over.
402
453
  * All the modifications are merged in a single history item (undo/redo).
454
+ *
455
+ * @example
456
+ * const { root } = await room.getStorage();
457
+ * room.batch(() => {
458
+ * root.set("x", 0);
459
+ * room.updatePresence({ cursor: { x: 100, y: 100 }});
460
+ * });
403
461
  */
404
462
  batch: (fn: () => void) => void;
405
463
  };
@@ -64,6 +64,7 @@ export declare class LiveList<T> extends AbstractCrdt {
64
64
  * @param index The index of the element to delete
65
65
  */
66
66
  delete(index: number): void;
67
+ clear(): void;
67
68
  /**
68
69
  * Returns an Array of all the elements in the LiveList.
69
70
  */
@@ -253,6 +253,28 @@ export class LiveList extends AbstractCrdt {
253
253
  }
254
254
  }
255
255
  }
256
+ clear() {
257
+ if (this._doc) {
258
+ let ops = [];
259
+ let reverseOps = [];
260
+ for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) {
261
+ item[0]._detach();
262
+ const childId = item[0]._id;
263
+ if (childId) {
264
+ ops.push({ id: childId, type: OpType.DeleteCrdt });
265
+ reverseOps.push(...item[0]._serialize(this._id, item[1]));
266
+ }
267
+ }
268
+ __classPrivateFieldSet(this, _LiveList_items, [], "f");
269
+ this._doc.dispatch(ops, reverseOps, [this]);
270
+ }
271
+ else {
272
+ for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) {
273
+ item[0]._detach();
274
+ }
275
+ __classPrivateFieldSet(this, _LiveList_items, [], "f");
276
+ }
277
+ }
256
278
  /**
257
279
  * Returns an Array of all the elements in the LiveList.
258
280
  */
@@ -53,6 +53,11 @@ export declare class LiveObject<T extends Record<string, any> = Record<string, a
53
53
  * @param key The key of the property to get
54
54
  */
55
55
  get<TKey extends keyof T>(key: TKey): T[TKey];
56
+ /**
57
+ * Deletes a key from the LiveObject
58
+ * @param key The key of the property to delete
59
+ */
60
+ delete(key: keyof T): void;
56
61
  /**
57
62
  * Adds or updates multiple properties at once with an object.
58
63
  * @param overrides The object used to overrides properties
@@ -183,6 +183,40 @@ export class LiveObject extends AbstractCrdt {
183
183
  get(key) {
184
184
  return __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
185
185
  }
186
+ /**
187
+ * Deletes a key from the LiveObject
188
+ * @param key The key of the property to delete
189
+ */
190
+ delete(key) {
191
+ const keyAsString = key;
192
+ const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(keyAsString);
193
+ if (oldValue === undefined) {
194
+ return;
195
+ }
196
+ if (this._doc == null || this._id == null) {
197
+ if (oldValue instanceof AbstractCrdt) {
198
+ oldValue._detach();
199
+ }
200
+ __classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString);
201
+ return;
202
+ }
203
+ let reverse;
204
+ if (oldValue instanceof AbstractCrdt) {
205
+ oldValue._detach();
206
+ reverse = oldValue._serialize(this._id, keyAsString);
207
+ }
208
+ else {
209
+ reverse = [
210
+ {
211
+ type: OpType.UpdateObject,
212
+ data: { [keyAsString]: oldValue },
213
+ id: this._id,
214
+ },
215
+ ];
216
+ }
217
+ __classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString);
218
+ this._doc.dispatch([{ type: OpType.DeleteObjectKey, key: keyAsString, id: this._id }], reverse, [this]);
219
+ }
186
220
  /**
187
221
  * Adds or updates multiple properties at once with an object.
188
222
  * @param overrides The object used to overrides properties
@@ -306,6 +340,10 @@ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _
306
340
  return isModified ? { modified: this, reverse } : { modified: false };
307
341
  }, _LiveObject_applyDeleteObjectKey = function _LiveObject_applyDeleteObjectKey(op) {
308
342
  const key = op.key;
343
+ // If property does not exist, exit without notifying
344
+ if (__classPrivateFieldGet(this, _LiveObject_map, "f").has(key) === false) {
345
+ return { modified: false };
346
+ }
309
347
  const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
310
348
  let reverse = [];
311
349
  if (isCrdt(oldValue)) {
@@ -0,0 +1,7 @@
1
+ import { LiveList } from "../LiveList";
2
+ import { LiveObject } from "../LiveObject";
3
+ import { StorageUpdate } from "../types";
4
+ export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
5
+ export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
6
+ export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
7
+ export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
@@ -0,0 +1,207 @@
1
+ import { LiveList } from "../LiveList";
2
+ import { LiveMap } from "../LiveMap";
3
+ import { LiveObject } from "../LiveObject";
4
+ export function liveObjectToJson(liveObject) {
5
+ const result = {};
6
+ const obj = liveObject.toObject();
7
+ for (const key in obj) {
8
+ result[key] = liveNodeToJson(obj[key]);
9
+ }
10
+ return result;
11
+ }
12
+ function liveMapToJson(map) {
13
+ const result = {};
14
+ const obj = Object.fromEntries(map);
15
+ for (const key in obj) {
16
+ result[key] = liveNodeToJson(obj[key]);
17
+ }
18
+ return result;
19
+ }
20
+ function liveListToJson(value) {
21
+ return value.toArray().map(liveNodeToJson);
22
+ }
23
+ function liveNodeToJson(value) {
24
+ if (value instanceof LiveObject) {
25
+ return liveObjectToJson(value);
26
+ }
27
+ else if (value instanceof LiveList) {
28
+ return liveListToJson(value);
29
+ }
30
+ else if (value instanceof LiveMap) {
31
+ return liveMapToJson(value);
32
+ }
33
+ return value;
34
+ }
35
+ function isPlainObject(obj) {
36
+ return Object.prototype.toString.call(obj) === "[object Object]";
37
+ }
38
+ function anyToCrdt(obj) {
39
+ if (obj == null) {
40
+ return obj;
41
+ }
42
+ if (Array.isArray(obj)) {
43
+ return new LiveList(obj.map(anyToCrdt));
44
+ }
45
+ if (isPlainObject(obj)) {
46
+ const init = {};
47
+ for (const key in obj) {
48
+ init[key] = anyToCrdt(obj[key]);
49
+ }
50
+ return new LiveObject(init);
51
+ }
52
+ return obj;
53
+ }
54
+ export function patchLiveList(liveList, prev, next) {
55
+ let i = 0;
56
+ let prevEnd = prev.length - 1;
57
+ let nextEnd = next.length - 1;
58
+ let prevNode = prev[0];
59
+ let nextNode = next[0];
60
+ /**
61
+ * For A,B,C => A,B,C,D
62
+ * i = 3, prevEnd = 2, nextEnd = 3
63
+ *
64
+ * For A,B,C => B,C
65
+ * i = 2, prevEnd = 2, nextEnd = 1
66
+ *
67
+ * For B,C => A,B,C
68
+ * i = 0, pre
69
+ */
70
+ outer: {
71
+ while (prevNode === nextNode) {
72
+ ++i;
73
+ if (i > prevEnd || i > nextEnd) {
74
+ break outer;
75
+ }
76
+ prevNode = prev[i];
77
+ nextNode = next[i];
78
+ }
79
+ prevNode = prev[prevEnd];
80
+ nextNode = next[nextEnd];
81
+ while (prevNode === nextNode) {
82
+ prevEnd--;
83
+ nextEnd--;
84
+ if (i > prevEnd || i > nextEnd) {
85
+ break outer;
86
+ }
87
+ prevNode = prev[prevEnd];
88
+ nextNode = next[nextEnd];
89
+ }
90
+ }
91
+ if (i > prevEnd) {
92
+ if (i <= nextEnd) {
93
+ while (i <= nextEnd) {
94
+ liveList.insert(anyToCrdt(next[i]), i);
95
+ i++;
96
+ }
97
+ }
98
+ }
99
+ else if (i > nextEnd) {
100
+ while (i <= prevEnd) {
101
+ liveList.delete(i++);
102
+ }
103
+ }
104
+ else {
105
+ while (i <= prevEnd && i <= nextEnd) {
106
+ prevNode = prev[i];
107
+ nextNode = next[i];
108
+ const liveListNode = liveList.get(i);
109
+ if (liveListNode instanceof LiveObject &&
110
+ isPlainObject(prevNode) &&
111
+ isPlainObject(nextNode)) {
112
+ patchLiveObject(liveListNode, prevNode, nextNode);
113
+ }
114
+ else {
115
+ liveList.delete(i);
116
+ liveList.insert(anyToCrdt(nextNode), i);
117
+ }
118
+ i++;
119
+ }
120
+ while (i <= nextEnd) {
121
+ liveList.insert(anyToCrdt(next[i]), i);
122
+ i++;
123
+ }
124
+ while (i <= prevEnd) {
125
+ liveList.delete(i);
126
+ i++;
127
+ }
128
+ }
129
+ }
130
+ export function patchLiveObject(root, prev, next) {
131
+ const updates = {};
132
+ for (const key in next) {
133
+ if (prev[key] === next[key]) {
134
+ continue;
135
+ }
136
+ else if (Array.isArray(prev[key]) && Array.isArray(next[key])) {
137
+ patchLiveList(root.get(key), prev[key], next[key]);
138
+ }
139
+ else if (isPlainObject(prev[key]) && isPlainObject(next[key])) {
140
+ patchLiveObject(root.get(key), prev[key], next[key]);
141
+ }
142
+ else {
143
+ updates[key] = anyToCrdt(next[key]);
144
+ }
145
+ }
146
+ for (const key in prev) {
147
+ if (next[key] === undefined) {
148
+ root.delete(key);
149
+ }
150
+ }
151
+ if (Object.keys(updates).length > 0) {
152
+ root.update(updates);
153
+ }
154
+ }
155
+ function getParentsPath(node) {
156
+ const path = [];
157
+ while (node._parentKey != null && node._parent != null) {
158
+ if (node._parent instanceof LiveList) {
159
+ path.push(node._parent._indexOfPosition(node._parentKey));
160
+ }
161
+ else {
162
+ path.push(node._parentKey);
163
+ }
164
+ node = node._parent;
165
+ }
166
+ return path;
167
+ }
168
+ export function patchImmutableObject(state, updates) {
169
+ return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
170
+ }
171
+ function patchImmutableObjectWithUpdate(state, update) {
172
+ const path = getParentsPath(update.node);
173
+ return patchImmutableNode(state, path, update);
174
+ }
175
+ function patchImmutableNode(state, path, update) {
176
+ const pathItem = path.pop();
177
+ if (pathItem === undefined) {
178
+ switch (update.type) {
179
+ case "LiveObject": {
180
+ if (typeof state !== "object") {
181
+ throw new Error("Internal: received update on LiveObject but state was not an object");
182
+ }
183
+ return liveObjectToJson(update.node);
184
+ }
185
+ case "LiveList": {
186
+ if (Array.isArray(state) === false) {
187
+ throw new Error("Internal: received update on LiveList but state was not an array");
188
+ }
189
+ return liveListToJson(update.node);
190
+ }
191
+ case "LiveMap": {
192
+ if (typeof state !== "object") {
193
+ throw new Error("Internal: received update on LiveMap but state was not an object");
194
+ }
195
+ return liveMapToJson(update.node);
196
+ }
197
+ }
198
+ }
199
+ if (Array.isArray(state)) {
200
+ const newArray = [...state];
201
+ newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
202
+ return newArray;
203
+ }
204
+ else {
205
+ return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
206
+ }
207
+ }
@@ -0,0 +1,8 @@
1
+ import { LiveList } from "./LiveList";
2
+ import { LiveObject } from "./LiveObject";
3
+ import { StorageUpdate } from "./types";
4
+ export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
5
+ export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
6
+ export declare function patchLiveObjectKey<T>(liveObject: LiveObject<T>, key: keyof T, prev: any, next: any): void;
7
+ export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
8
+ export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;