@liveblocks/client 0.13.2 → 0.14.0-beta.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.
@@ -1,5 +1,5 @@
1
1
  import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt";
2
- import { Op, SerializedCrdtWithId } from "./live";
2
+ import { Op, SerializedCrdt, SerializedCrdtWithId } from "./live";
3
3
  /**
4
4
  * The LiveObject class is similar to a JavaScript object that is synchronized on all clients.
5
5
  * Keys should be a string, and values should be serializable to JSON.
@@ -11,13 +11,15 @@ export declare class LiveObject<T extends Record<string, any> = Record<string, a
11
11
  /**
12
12
  * INTERNAL
13
13
  */
14
- _serialize(parentId?: string, parentKey?: string): Op[];
14
+ _serialize(parentId?: string, parentKey?: string, doc?: Doc): Op[];
15
15
  /**
16
16
  * INTERNAL
17
17
  */
18
- static _deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<{
19
- [key: string]: any;
20
- }>;
18
+ static _deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<Record<string, any>>;
19
+ /**
20
+ * INTERNAL
21
+ */
22
+ static _deserializeChildren(object: LiveObject, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<Record<string, any>>;
21
23
  /**
22
24
  * INTERNAL
23
25
  */
@@ -25,11 +27,15 @@ export declare class LiveObject<T extends Record<string, any> = Record<string, a
25
27
  /**
26
28
  * INTERNAL
27
29
  */
28
- _attachChild(id: string, key: keyof T, child: AbstractCrdt): ApplyResult;
30
+ _attachChild(id: string, key: keyof T, child: AbstractCrdt, isLocal: boolean): ApplyResult;
29
31
  /**
30
32
  * INTERNAL
31
33
  */
32
34
  _detachChild(child: AbstractCrdt): void;
35
+ /**
36
+ * INTERNAL
37
+ */
38
+ _detachChildren(): void;
33
39
  /**
34
40
  * INTERNAL
35
41
  */
@@ -37,7 +43,11 @@ export declare class LiveObject<T extends Record<string, any> = Record<string, a
37
43
  /**
38
44
  * INTERNAL
39
45
  */
40
- _apply(op: Op): ApplyResult;
46
+ _apply(op: Op, isLocal: boolean): ApplyResult;
47
+ /**
48
+ * INTERNAL
49
+ */
50
+ _toSerializedCrdt(): SerializedCrdt;
41
51
  /**
42
52
  * Transform the LiveObject into a javascript object
43
53
  */
@@ -35,13 +35,14 @@ export class LiveObject extends AbstractCrdt {
35
35
  /**
36
36
  * INTERNAL
37
37
  */
38
- _serialize(parentId, parentKey) {
38
+ _serialize(parentId, parentKey, doc) {
39
39
  if (this._id == null) {
40
40
  throw new Error("Cannot serialize item is not attached");
41
41
  }
42
42
  const ops = [];
43
43
  const op = {
44
44
  id: this._id,
45
+ opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(),
45
46
  type: OpType.CreateObject,
46
47
  parentId,
47
48
  parentKey,
@@ -50,7 +51,7 @@ export class LiveObject extends AbstractCrdt {
50
51
  ops.push(op);
51
52
  for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) {
52
53
  if (value instanceof AbstractCrdt) {
53
- ops.push(...value._serialize(this._id, key));
54
+ ops.push(...value._serialize(this._id, key, doc));
54
55
  }
55
56
  else {
56
57
  op.data[key] = value;
@@ -67,7 +68,13 @@ export class LiveObject extends AbstractCrdt {
67
68
  }
68
69
  const object = new LiveObject(item.data);
69
70
  object._attach(id, doc);
70
- const children = parentToChildren.get(id);
71
+ return this._deserializeChildren(object, parentToChildren, doc);
72
+ }
73
+ /**
74
+ * INTERNAL
75
+ */
76
+ static _deserializeChildren(object, parentToChildren, doc) {
77
+ const children = parentToChildren.get(object._id);
71
78
  if (children == null) {
72
79
  return object;
73
80
  }
@@ -96,7 +103,7 @@ export class LiveObject extends AbstractCrdt {
96
103
  /**
97
104
  * INTERNAL
98
105
  */
99
- _attachChild(id, key, child) {
106
+ _attachChild(id, key, child, isLocal) {
100
107
  if (this._doc == null) {
101
108
  throw new Error("Can't attach child if doc is not present");
102
109
  }
@@ -138,6 +145,15 @@ export class LiveObject extends AbstractCrdt {
138
145
  child._detach();
139
146
  }
140
147
  }
148
+ /**
149
+ * INTERNAL
150
+ */
151
+ _detachChildren() {
152
+ for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) {
153
+ __classPrivateFieldGet(this, _LiveObject_map, "f").delete(key);
154
+ value._detach();
155
+ }
156
+ }
141
157
  /**
142
158
  * INTERNAL
143
159
  */
@@ -152,14 +168,26 @@ export class LiveObject extends AbstractCrdt {
152
168
  /**
153
169
  * INTERNAL
154
170
  */
155
- _apply(op) {
171
+ _apply(op, isLocal) {
156
172
  if (op.type === OpType.UpdateObject) {
157
- return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyUpdate).call(this, op);
173
+ return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyUpdate).call(this, op, isLocal);
158
174
  }
159
175
  else if (op.type === OpType.DeleteObjectKey) {
160
176
  return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyDeleteObjectKey).call(this, op);
161
177
  }
162
- return super._apply(op);
178
+ return super._apply(op, isLocal);
179
+ }
180
+ /**
181
+ * INTERNAL
182
+ */
183
+ _toSerializedCrdt() {
184
+ var _a;
185
+ return {
186
+ type: CrdtType.Object,
187
+ parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id,
188
+ parentKey: this._parentKey,
189
+ data: this.toObject(),
190
+ };
163
191
  }
164
192
  /**
165
193
  * Transform the LiveObject into a javascript object
@@ -215,7 +243,14 @@ export class LiveObject extends AbstractCrdt {
215
243
  ];
216
244
  }
217
245
  __classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString);
218
- this._doc.dispatch([{ type: OpType.DeleteObjectKey, key: keyAsString, id: this._id }], reverse, [this]);
246
+ this._doc.dispatch([
247
+ {
248
+ type: OpType.DeleteObjectKey,
249
+ key: keyAsString,
250
+ id: this._id,
251
+ opId: this._doc.generateOpId(),
252
+ },
253
+ ], reverse, [this]);
219
254
  }
220
255
  /**
221
256
  * Adds or updates multiple properties at once with an object.
@@ -262,7 +297,7 @@ export class LiveObject extends AbstractCrdt {
262
297
  if (newValue instanceof AbstractCrdt) {
263
298
  newValue._setParentLink(this, key);
264
299
  newValue._attach(this._doc.generateId(), this._doc);
265
- ops.push(...newValue._serialize(this._id, key));
300
+ ops.push(...newValue._serialize(this._id, key, this._doc));
266
301
  }
267
302
  else {
268
303
  updatedProps[key] = newValue;
@@ -283,7 +318,7 @@ export class LiveObject extends AbstractCrdt {
283
318
  this._doc.dispatch(ops, reverseOps, [this]);
284
319
  }
285
320
  }
286
- _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _LiveObject_instances = new WeakSet(), _LiveObject_applyUpdate = function _LiveObject_applyUpdate(op) {
321
+ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _LiveObject_instances = new WeakSet(), _LiveObject_applyUpdate = function _LiveObject_applyUpdate(op, isLocal) {
287
322
  let isModified = false;
288
323
  const reverse = [];
289
324
  const reverseUpdate = {
@@ -305,11 +340,6 @@ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _
305
340
  reverse.push({ type: OpType.DeleteObjectKey, id: this._id, key });
306
341
  }
307
342
  }
308
- let isLocal = false;
309
- if (op.opId == null) {
310
- isLocal = true;
311
- op.opId = this._doc.generateOpId();
312
- }
313
343
  for (const key in op.data) {
314
344
  if (isLocal) {
315
345
  __classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, op.opId);
@@ -1,5 +1,5 @@
1
1
  import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt";
2
- import { SerializedCrdtWithId, Op } from "./live";
2
+ import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live";
3
3
  /**
4
4
  * INTERNAL
5
5
  */
@@ -14,8 +14,12 @@ export declare class LiveRegister<TValue = any> extends AbstractCrdt {
14
14
  /**
15
15
  * INTERNAL
16
16
  */
17
- _serialize(parentId: string, parentKey: string): Op[];
18
- _attachChild(id: string, key: string, crdt: AbstractCrdt): ApplyResult;
17
+ _serialize(parentId: string, parentKey: string, doc?: Doc): Op[];
18
+ /**
19
+ * INTERNAL
20
+ */
21
+ _toSerializedCrdt(): SerializedCrdt;
22
+ _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
19
23
  _detachChild(crdt: AbstractCrdt): void;
20
- _apply(op: Op): ApplyResult;
24
+ _apply(op: Op, isLocal: boolean): ApplyResult;
21
25
  }
@@ -11,7 +11,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  };
12
12
  var _LiveRegister_data;
13
13
  import { AbstractCrdt } from "./AbstractCrdt";
14
- import { CrdtType, OpType } from "./live";
14
+ import { CrdtType, OpType, } from "./live";
15
15
  /**
16
16
  * INTERNAL
17
17
  */
@@ -38,13 +38,14 @@ export class LiveRegister extends AbstractCrdt {
38
38
  /**
39
39
  * INTERNAL
40
40
  */
41
- _serialize(parentId, parentKey) {
41
+ _serialize(parentId, parentKey, doc) {
42
42
  if (this._id == null || parentId == null || parentKey == null) {
43
43
  throw new Error("Cannot serialize register if parentId or parentKey is undefined");
44
44
  }
45
45
  return [
46
46
  {
47
47
  type: OpType.CreateRegister,
48
+ opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(),
48
49
  id: this._id,
49
50
  parentId,
50
51
  parentKey,
@@ -52,14 +53,26 @@ export class LiveRegister extends AbstractCrdt {
52
53
  },
53
54
  ];
54
55
  }
55
- _attachChild(id, key, crdt) {
56
+ /**
57
+ * INTERNAL
58
+ */
59
+ _toSerializedCrdt() {
60
+ var _a;
61
+ return {
62
+ type: CrdtType.Register,
63
+ parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id,
64
+ parentKey: this._parentKey,
65
+ data: this.data,
66
+ };
67
+ }
68
+ _attachChild(id, key, crdt, isLocal) {
56
69
  throw new Error("Method not implemented.");
57
70
  }
58
71
  _detachChild(crdt) {
59
72
  throw new Error("Method not implemented.");
60
73
  }
61
- _apply(op) {
62
- return super._apply(op);
74
+ _apply(op, isLocal) {
75
+ return super._apply(op, isLocal);
63
76
  }
64
77
  }
65
78
  _LiveRegister_data = new WeakMap();
package/lib/esm/live.d.ts CHANGED
@@ -122,6 +122,7 @@ export declare type UpdateObjectOp = {
122
122
  };
123
123
  };
124
124
  export declare type CreateObjectOp = {
125
+ opId?: string;
125
126
  id: string;
126
127
  type: OpType.CreateObject;
127
128
  parentId?: string;
@@ -131,18 +132,21 @@ export declare type CreateObjectOp = {
131
132
  };
132
133
  };
133
134
  export declare type CreateListOp = {
135
+ opId?: string;
134
136
  id: string;
135
137
  type: OpType.CreateList;
136
138
  parentId: string;
137
139
  parentKey: string;
138
140
  };
139
141
  export declare type CreateMapOp = {
142
+ opId?: string;
140
143
  id: string;
141
144
  type: OpType.CreateMap;
142
145
  parentId: string;
143
146
  parentKey: string;
144
147
  };
145
148
  export declare type CreateRegisterOp = {
149
+ opId?: string;
146
150
  id: string;
147
151
  type: OpType.CreateRegister;
148
152
  parentId: string;
@@ -150,15 +154,18 @@ export declare type CreateRegisterOp = {
150
154
  data: any;
151
155
  };
152
156
  export declare type DeleteCrdtOp = {
157
+ opId?: string;
153
158
  id: string;
154
159
  type: OpType.DeleteCrdt;
155
160
  };
156
161
  export declare type SetParentKeyOp = {
162
+ opId?: string;
157
163
  id: string;
158
164
  type: OpType.SetParentKey;
159
165
  parentKey: string;
160
166
  };
161
167
  export declare type DeleteObjectKeyOp = {
168
+ opId?: string;
162
169
  id: string;
163
170
  type: OpType.DeleteObjectKey;
164
171
  key: string;
package/lib/esm/room.d.ts CHANGED
@@ -11,6 +11,7 @@ declare type HistoryItem = Array<Op | {
11
11
  declare type IdFactory = () => string;
12
12
  export declare type State = {
13
13
  connection: Connection;
14
+ lastConnectionId: number | null;
14
15
  socket: WebSocket | null;
15
16
  lastFlushTime: number;
16
17
  buffer: {
@@ -62,6 +63,7 @@ export declare type State = {
62
63
  nodes: Set<AbstractCrdt>;
63
64
  };
64
65
  };
66
+ offlineOperations: Map<string, Op>;
65
67
  };
66
68
  export declare type Effects = {
67
69
  authenticate(): void;
@@ -89,6 +91,12 @@ export declare function makeStateMachine(state: State, context: Context, mockedE
89
91
  authenticationSuccess: (token: AuthenticationToken, socket: WebSocket) => void;
90
92
  heartbeat: () => void;
91
93
  onNavigatorOnline: () => void;
94
+ simulateSocketClose: () => void;
95
+ simulateSendCloseEvent: (event: {
96
+ code: number;
97
+ wasClean: boolean;
98
+ reason: any;
99
+ }) => void;
92
100
  onVisibilityChange: (visibilityState: VisibilityState) => void;
93
101
  getUndoStack: () => HistoryItem[];
94
102
  getItemsCount: () => number;
package/lib/esm/room.js CHANGED
@@ -7,7 +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";
10
+ import { getTreesDiffOperations, isSameNodeOrChildOf, remove } from "./utils";
11
11
  import auth, { parseToken } from "./authentication";
12
12
  import { ClientMessageType, ServerMessageType, OpType, } from "./live";
13
13
  import { LiveMap } from "./LiveMap";
@@ -107,18 +107,23 @@ export function makeStateMachine(state, context, mockedEffects) {
107
107
  };
108
108
  return genericSubscribe(cb);
109
109
  }
110
- function createRootFromMessage(message) {
111
- state.root = load(message.items);
110
+ function createOrUpdateRootFromMessage(message) {
111
+ if (message.items.length === 0) {
112
+ throw new Error("Internal error: cannot load storage without items");
113
+ }
114
+ if (state.root) {
115
+ updateRoot(message.items);
116
+ }
117
+ else {
118
+ state.root = load(message.items);
119
+ }
112
120
  for (const key in state.defaultStorageRoot) {
113
121
  if (state.root.get(key) == null) {
114
122
  state.root.set(key, state.defaultStorageRoot[key]);
115
123
  }
116
124
  }
117
125
  }
118
- function load(items) {
119
- if (items.length === 0) {
120
- throw new Error("Internal error: cannot load storage without items");
121
- }
126
+ function buildRootAndParentToChildren(items) {
122
127
  const parentToChildren = new Map();
123
128
  let root = null;
124
129
  for (const tuple of items) {
@@ -139,6 +144,23 @@ export function makeStateMachine(state, context, mockedEffects) {
139
144
  if (root == null) {
140
145
  throw new Error("Root can't be null");
141
146
  }
147
+ return [root, parentToChildren];
148
+ }
149
+ function updateRoot(items) {
150
+ if (!state.root) {
151
+ return;
152
+ }
153
+ const currentItems = new Map();
154
+ state.items.forEach((liveCrdt, id) => {
155
+ currentItems.set(id, liveCrdt._toSerializedCrdt());
156
+ });
157
+ // Get operations that represent the diff between 2 states.
158
+ const ops = getTreesDiffOperations(currentItems, new Map(items));
159
+ const result = apply(ops, false);
160
+ notify(result.updates);
161
+ }
162
+ function load(items) {
163
+ const [root, parentToChildren] = buildRootAndParentToChildren(items);
142
164
  return LiveObject._deserialize(root, parentToChildren, {
143
165
  addItem,
144
166
  deleteItem,
@@ -227,7 +249,10 @@ export function makeStateMachine(state, context, mockedEffects) {
227
249
  state.connection.state === "connecting") {
228
250
  return state.connection.id;
229
251
  }
230
- throw new Error("Internal. Tried to get connection id but connection is not open");
252
+ else if (state.lastConnectionId !== null) {
253
+ return state.lastConnectionId;
254
+ }
255
+ throw new Error("Internal. Tried to get connection id but connection was never open");
231
256
  }
232
257
  function generateId() {
233
258
  return `${getConnectionId()}:${state.clock++}`;
@@ -235,7 +260,7 @@ export function makeStateMachine(state, context, mockedEffects) {
235
260
  function generateOpId() {
236
261
  return `${getConnectionId()}:${state.opClock++}`;
237
262
  }
238
- function apply(item) {
263
+ function apply(item, isLocal) {
239
264
  const result = {
240
265
  reverse: [],
241
266
  updates: { nodes: new Set(), presence: false },
@@ -262,7 +287,11 @@ export function makeStateMachine(state, context, mockedEffects) {
262
287
  result.updates.presence = true;
263
288
  }
264
289
  else {
265
- const applyOpResult = applyOp(op);
290
+ // Ops applied after undo/redo don't have an opId.
291
+ if (isLocal && !op.opId) {
292
+ op.opId = generateOpId();
293
+ }
294
+ const applyOpResult = applyOp(op, isLocal);
266
295
  if (applyOpResult.modified) {
267
296
  result.updates.nodes.add(applyOpResult.modified);
268
297
  result.reverse.unshift(...applyOpResult.reverse);
@@ -271,7 +300,10 @@ export function makeStateMachine(state, context, mockedEffects) {
271
300
  }
272
301
  return result;
273
302
  }
274
- function applyOp(op) {
303
+ function applyOp(op, isLocal) {
304
+ if (op.opId) {
305
+ state.offlineOperations.delete(op.opId);
306
+ }
275
307
  switch (op.type) {
276
308
  case OpType.DeleteObjectKey:
277
309
  case OpType.UpdateObject:
@@ -280,7 +312,7 @@ export function makeStateMachine(state, context, mockedEffects) {
280
312
  if (item == null) {
281
313
  return { modified: false };
282
314
  }
283
- return item._apply(op);
315
+ return item._apply(op, isLocal);
284
316
  }
285
317
  case OpType.SetParentKey: {
286
318
  const item = state.items.get(op.id);
@@ -308,28 +340,28 @@ export function makeStateMachine(state, context, mockedEffects) {
308
340
  if (parent == null || getItem(op.id) != null) {
309
341
  return { modified: false };
310
342
  }
311
- return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data));
343
+ return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal);
312
344
  }
313
345
  case OpType.CreateList: {
314
346
  const parent = state.items.get(op.parentId);
315
347
  if (parent == null || getItem(op.id) != null) {
316
348
  return { modified: false };
317
349
  }
318
- return parent._attachChild(op.id, op.parentKey, new LiveList());
350
+ return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal);
319
351
  }
320
352
  case OpType.CreateRegister: {
321
353
  const parent = state.items.get(op.parentId);
322
354
  if (parent == null || getItem(op.id) != null) {
323
355
  return { modified: false };
324
356
  }
325
- return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data));
357
+ return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal);
326
358
  }
327
359
  case OpType.CreateMap: {
328
360
  const parent = state.items.get(op.parentId);
329
361
  if (parent == null || getItem(op.id) != null) {
330
362
  return { modified: false };
331
363
  }
332
- return parent._attachChild(op.id, op.parentKey, new LiveMap());
364
+ return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal);
333
365
  }
334
366
  }
335
367
  return { modified: false };
@@ -549,12 +581,13 @@ See v0.13 release notes for more information.
549
581
  break;
550
582
  }
551
583
  case ServerMessageType.InitialStorageState: {
552
- createRootFromMessage(subMessage);
584
+ createOrUpdateRootFromMessage(subMessage);
585
+ applyAndSendOfflineOps();
553
586
  _getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
554
587
  break;
555
588
  }
556
589
  case ServerMessageType.UpdateStorage: {
557
- const applyResult = apply(subMessage.ops);
590
+ const applyResult = apply(subMessage.ops, false);
558
591
  for (const node of applyResult.updates.nodes) {
559
592
  updates.nodes.add(node);
560
593
  }
@@ -618,6 +651,10 @@ See v0.13 release notes for more information.
618
651
  if (state.connection.state === "connecting") {
619
652
  updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
620
653
  state.numberOfRetry = 0;
654
+ state.lastConnectionId = state.connection.id;
655
+ if (state.root) {
656
+ state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
657
+ }
621
658
  tryFlushing();
622
659
  }
623
660
  else {
@@ -657,11 +694,29 @@ See v0.13 release notes for more information.
657
694
  clearInterval(state.intervalHandles.heartbeat);
658
695
  connect();
659
696
  }
660
- function tryFlushing() {
661
- if (state.socket == null) {
697
+ function applyAndSendOfflineOps() {
698
+ if (state.offlineOperations.size === 0) {
662
699
  return;
663
700
  }
664
- if (state.socket.readyState !== WebSocket.OPEN) {
701
+ const messages = [];
702
+ const ops = Array.from(state.offlineOperations.values());
703
+ const result = apply(ops, true);
704
+ messages.push({
705
+ type: ClientMessageType.UpdateStorage,
706
+ ops: ops,
707
+ });
708
+ notify(result.updates);
709
+ effects.send(messages);
710
+ }
711
+ function tryFlushing() {
712
+ const storageOps = state.buffer.storageOperations;
713
+ if (storageOps.length > 0) {
714
+ storageOps.forEach((op) => {
715
+ state.offlineOperations.set(op.opId, op);
716
+ });
717
+ }
718
+ if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
719
+ state.buffer.storageOperations = [];
665
720
  return;
666
721
  }
667
722
  const now = Date.now();
@@ -779,7 +834,7 @@ See v0.13 release notes for more information.
779
834
  return;
780
835
  }
781
836
  state.isHistoryPaused = false;
782
- const result = apply(historyItem);
837
+ const result = apply(historyItem, true);
783
838
  notify(result.updates);
784
839
  state.redoStack.push(result.reverse);
785
840
  for (const op of historyItem) {
@@ -798,7 +853,7 @@ See v0.13 release notes for more information.
798
853
  return;
799
854
  }
800
855
  state.isHistoryPaused = false;
801
- const result = apply(historyItem);
856
+ const result = apply(historyItem, true);
802
857
  notify(result.updates);
803
858
  state.undoStack.push(result.reverse);
804
859
  for (const op of historyItem) {
@@ -850,6 +905,16 @@ See v0.13 release notes for more information.
850
905
  }
851
906
  state.pausedHistory = [];
852
907
  }
908
+ function simulateSocketClose() {
909
+ if (state.socket) {
910
+ state.socket.close();
911
+ }
912
+ }
913
+ function simulateSendCloseEvent(event) {
914
+ if (state.socket) {
915
+ onClose(event);
916
+ }
917
+ }
853
918
  return {
854
919
  // Internal
855
920
  onOpen,
@@ -858,6 +923,9 @@ See v0.13 release notes for more information.
858
923
  authenticationSuccess,
859
924
  heartbeat,
860
925
  onNavigatorOnline,
926
+ // Internal dev tools
927
+ simulateSocketClose,
928
+ simulateSendCloseEvent,
861
929
  // onWakeUp,
862
930
  onVisibilityChange,
863
931
  getUndoStack: () => state.undoStack,
@@ -889,6 +957,7 @@ See v0.13 release notes for more information.
889
957
  export function defaultState(me, defaultStorageRoot) {
890
958
  return {
891
959
  connection: { state: "closed" },
960
+ lastConnectionId: null,
892
961
  socket: null,
893
962
  listeners: {
894
963
  event: [],
@@ -933,6 +1002,7 @@ export function defaultState(me, defaultStorageRoot) {
933
1002
  updates: { nodes: new Set(), presence: false, others: [] },
934
1003
  reverseOps: [],
935
1004
  },
1005
+ offlineOperations: new Map(),
936
1006
  };
937
1007
  }
938
1008
  export function createRoom(name, options) {
@@ -978,6 +1048,11 @@ export function createRoom(name, options) {
978
1048
  pause: machine.pauseHistory,
979
1049
  resume: machine.resumeHistory,
980
1050
  },
1051
+ // @ts-ignore
1052
+ internalDevTools: {
1053
+ closeWebsocket: machine.simulateSocketClose,
1054
+ sendCloseEvent: machine.simulateSendCloseEvent,
1055
+ },
981
1056
  };
982
1057
  return {
983
1058
  connect: machine.connect,
@@ -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[];