@liveblocks/client 0.13.2 → 0.15.0-alpha.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.
- package/lib/cjs/AbstractCrdt.d.ts +13 -7
- package/lib/cjs/AbstractCrdt.js +2 -5
- package/lib/cjs/LiveList.d.ts +17 -6
- package/lib/cjs/LiveList.js +140 -15
- package/lib/cjs/LiveMap.d.ts +12 -4
- package/lib/cjs/LiveMap.js +57 -7
- package/lib/cjs/LiveObject.d.ts +22 -8
- package/lib/cjs/LiveObject.js +109 -24
- package/lib/cjs/LiveRegister.d.ts +13 -5
- package/lib/cjs/LiveRegister.js +23 -4
- package/lib/cjs/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/cjs/{immutable/index.js → immutable.js} +77 -19
- package/lib/cjs/index.d.ts +2 -1
- package/lib/cjs/index.js +8 -1
- package/lib/cjs/live.d.ts +7 -0
- package/lib/cjs/room.d.ts +11 -3
- package/lib/cjs/room.js +150 -76
- package/lib/cjs/types.d.ts +33 -1
- package/lib/cjs/utils.d.ts +4 -1
- package/lib/cjs/utils.js +101 -1
- package/lib/esm/AbstractCrdt.d.ts +13 -7
- package/lib/esm/AbstractCrdt.js +2 -5
- package/lib/esm/LiveList.d.ts +17 -6
- package/lib/esm/LiveList.js +141 -16
- package/lib/esm/LiveMap.d.ts +12 -4
- package/lib/esm/LiveMap.js +57 -7
- package/lib/esm/LiveObject.d.ts +22 -8
- package/lib/esm/LiveObject.js +109 -24
- package/lib/esm/LiveRegister.d.ts +13 -5
- package/lib/esm/LiveRegister.js +24 -5
- package/lib/esm/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/esm/{immutable/index.js → immutable.js} +75 -19
- package/lib/esm/index.d.ts +2 -1
- package/lib/esm/index.js +1 -0
- package/lib/esm/live.d.ts +7 -0
- package/lib/esm/room.d.ts +11 -3
- package/lib/esm/room.js +151 -77
- package/lib/esm/types.d.ts +33 -1
- package/lib/esm/utils.d.ts +4 -1
- package/lib/esm/utils.js +99 -1
- package/package.json +1 -1
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, mergeStorageUpdates, } from "./utils";
|
|
11
11
|
import auth, { parseToken } from "./authentication";
|
|
12
12
|
import { ClientMessageType, ServerMessageType, OpType, } from "./live";
|
|
13
13
|
import { LiveMap } from "./LiveMap";
|
|
@@ -36,6 +36,9 @@ function makeOthers(presenceMap) {
|
|
|
36
36
|
get count() {
|
|
37
37
|
return array.length;
|
|
38
38
|
},
|
|
39
|
+
[Symbol.iterator]() {
|
|
40
|
+
return array[Symbol.iterator]();
|
|
41
|
+
},
|
|
39
42
|
map(callback) {
|
|
40
43
|
return array.map(callback);
|
|
41
44
|
},
|
|
@@ -107,18 +110,23 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
107
110
|
};
|
|
108
111
|
return genericSubscribe(cb);
|
|
109
112
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
113
|
+
function createOrUpdateRootFromMessage(message) {
|
|
114
|
+
if (message.items.length === 0) {
|
|
115
|
+
throw new Error("Internal error: cannot load storage without items");
|
|
116
|
+
}
|
|
117
|
+
if (state.root) {
|
|
118
|
+
updateRoot(message.items);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
state.root = load(message.items);
|
|
122
|
+
}
|
|
112
123
|
for (const key in state.defaultStorageRoot) {
|
|
113
124
|
if (state.root.get(key) == null) {
|
|
114
125
|
state.root.set(key, state.defaultStorageRoot[key]);
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
|
-
function
|
|
119
|
-
if (items.length === 0) {
|
|
120
|
-
throw new Error("Internal error: cannot load storage without items");
|
|
121
|
-
}
|
|
129
|
+
function buildRootAndParentToChildren(items) {
|
|
122
130
|
const parentToChildren = new Map();
|
|
123
131
|
let root = null;
|
|
124
132
|
for (const tuple of items) {
|
|
@@ -139,6 +147,23 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
139
147
|
if (root == null) {
|
|
140
148
|
throw new Error("Root can't be null");
|
|
141
149
|
}
|
|
150
|
+
return [root, parentToChildren];
|
|
151
|
+
}
|
|
152
|
+
function updateRoot(items) {
|
|
153
|
+
if (!state.root) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const currentItems = new Map();
|
|
157
|
+
state.items.forEach((liveCrdt, id) => {
|
|
158
|
+
currentItems.set(id, liveCrdt._toSerializedCrdt());
|
|
159
|
+
});
|
|
160
|
+
// Get operations that represent the diff between 2 states.
|
|
161
|
+
const ops = getTreesDiffOperations(currentItems, new Map(items));
|
|
162
|
+
const result = apply(ops, false);
|
|
163
|
+
notify(result.updates);
|
|
164
|
+
}
|
|
165
|
+
function load(items) {
|
|
166
|
+
const [root, parentToChildren] = buildRootAndParentToChildren(items);
|
|
142
167
|
return LiveObject._deserialize(root, parentToChildren, {
|
|
143
168
|
addItem,
|
|
144
169
|
deleteItem,
|
|
@@ -168,22 +193,22 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
168
193
|
state.undoStack.push(historyItem);
|
|
169
194
|
}
|
|
170
195
|
}
|
|
171
|
-
function storageDispatch(ops, reverse,
|
|
196
|
+
function storageDispatch(ops, reverse, storageUpdates) {
|
|
172
197
|
if (state.isBatching) {
|
|
173
198
|
state.batch.ops.push(...ops);
|
|
174
|
-
|
|
175
|
-
state.batch.updates.
|
|
176
|
-
}
|
|
199
|
+
storageUpdates.forEach((value, key) => {
|
|
200
|
+
state.batch.updates.storageUpdates.set(key, mergeStorageUpdates(state.batch.updates.storageUpdates.get(key), value));
|
|
201
|
+
});
|
|
177
202
|
state.batch.reverseOps.push(...reverse);
|
|
178
203
|
}
|
|
179
204
|
else {
|
|
180
205
|
addToUndoStack(reverse);
|
|
181
206
|
state.redoStack = [];
|
|
182
207
|
dispatch(ops);
|
|
183
|
-
notify({
|
|
208
|
+
notify({ storageUpdates: storageUpdates });
|
|
184
209
|
}
|
|
185
210
|
}
|
|
186
|
-
function notify({
|
|
211
|
+
function notify({ storageUpdates = new Map(), presence = false, others = [], }) {
|
|
187
212
|
if (others.length > 0) {
|
|
188
213
|
state.others = makeOthers(state.users);
|
|
189
214
|
for (const event of others) {
|
|
@@ -197,28 +222,9 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
197
222
|
listener(state.me);
|
|
198
223
|
}
|
|
199
224
|
}
|
|
200
|
-
if (
|
|
225
|
+
if (storageUpdates.size > 0) {
|
|
201
226
|
for (const subscriber of state.listeners.storage) {
|
|
202
|
-
subscriber(Array.from(
|
|
203
|
-
if (m instanceof LiveObject) {
|
|
204
|
-
return {
|
|
205
|
-
type: "LiveObject",
|
|
206
|
-
node: m,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
else if (m instanceof LiveList) {
|
|
210
|
-
return {
|
|
211
|
-
type: "LiveList",
|
|
212
|
-
node: m,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
return {
|
|
217
|
-
type: "LiveMap",
|
|
218
|
-
node: m,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
}));
|
|
227
|
+
subscriber(Array.from(storageUpdates.values()));
|
|
222
228
|
}
|
|
223
229
|
}
|
|
224
230
|
}
|
|
@@ -227,7 +233,10 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
227
233
|
state.connection.state === "connecting") {
|
|
228
234
|
return state.connection.id;
|
|
229
235
|
}
|
|
230
|
-
|
|
236
|
+
else if (state.lastConnectionId !== null) {
|
|
237
|
+
return state.lastConnectionId;
|
|
238
|
+
}
|
|
239
|
+
throw new Error("Internal. Tried to get connection id but connection was never open");
|
|
231
240
|
}
|
|
232
241
|
function generateId() {
|
|
233
242
|
return `${getConnectionId()}:${state.clock++}`;
|
|
@@ -235,10 +244,13 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
235
244
|
function generateOpId() {
|
|
236
245
|
return `${getConnectionId()}:${state.opClock++}`;
|
|
237
246
|
}
|
|
238
|
-
function apply(item) {
|
|
247
|
+
function apply(item, isLocal) {
|
|
239
248
|
const result = {
|
|
240
249
|
reverse: [],
|
|
241
|
-
updates: {
|
|
250
|
+
updates: {
|
|
251
|
+
storageUpdates: new Map(),
|
|
252
|
+
presence: false,
|
|
253
|
+
},
|
|
242
254
|
};
|
|
243
255
|
for (const op of item) {
|
|
244
256
|
if (op.type === "presence") {
|
|
@@ -262,16 +274,23 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
262
274
|
result.updates.presence = true;
|
|
263
275
|
}
|
|
264
276
|
else {
|
|
265
|
-
|
|
277
|
+
// Ops applied after undo/redo don't have an opId.
|
|
278
|
+
if (isLocal && !op.opId) {
|
|
279
|
+
op.opId = generateOpId();
|
|
280
|
+
}
|
|
281
|
+
const applyOpResult = applyOp(op, isLocal);
|
|
266
282
|
if (applyOpResult.modified) {
|
|
267
|
-
result.updates.
|
|
283
|
+
result.updates.storageUpdates.set(applyOpResult.modified.node._id, mergeStorageUpdates(result.updates.storageUpdates.get(applyOpResult.modified.node._id), applyOpResult.modified));
|
|
268
284
|
result.reverse.unshift(...applyOpResult.reverse);
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
287
|
}
|
|
272
288
|
return result;
|
|
273
289
|
}
|
|
274
|
-
function applyOp(op) {
|
|
290
|
+
function applyOp(op, isLocal) {
|
|
291
|
+
if (op.opId) {
|
|
292
|
+
state.offlineOperations.delete(op.opId);
|
|
293
|
+
}
|
|
275
294
|
switch (op.type) {
|
|
276
295
|
case OpType.DeleteObjectKey:
|
|
277
296
|
case OpType.UpdateObject:
|
|
@@ -280,7 +299,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
280
299
|
if (item == null) {
|
|
281
300
|
return { modified: false };
|
|
282
301
|
}
|
|
283
|
-
return item._apply(op);
|
|
302
|
+
return item._apply(op, isLocal);
|
|
284
303
|
}
|
|
285
304
|
case OpType.SetParentKey: {
|
|
286
305
|
const item = state.items.get(op.id);
|
|
@@ -289,17 +308,12 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
289
308
|
}
|
|
290
309
|
if (item._parent instanceof LiveList) {
|
|
291
310
|
const previousKey = item._parentKey;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
parentKey: previousKey,
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
modified: item._parent,
|
|
302
|
-
};
|
|
311
|
+
if (previousKey === op.parentKey) {
|
|
312
|
+
return { modified: false };
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
return item._parent._setChildKey(op.parentKey, item, previousKey);
|
|
316
|
+
}
|
|
303
317
|
}
|
|
304
318
|
return { modified: false };
|
|
305
319
|
}
|
|
@@ -308,28 +322,28 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
308
322
|
if (parent == null || getItem(op.id) != null) {
|
|
309
323
|
return { modified: false };
|
|
310
324
|
}
|
|
311
|
-
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data));
|
|
325
|
+
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal);
|
|
312
326
|
}
|
|
313
327
|
case OpType.CreateList: {
|
|
314
328
|
const parent = state.items.get(op.parentId);
|
|
315
329
|
if (parent == null || getItem(op.id) != null) {
|
|
316
330
|
return { modified: false };
|
|
317
331
|
}
|
|
318
|
-
return parent._attachChild(op.id, op.parentKey, new LiveList());
|
|
332
|
+
return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal);
|
|
319
333
|
}
|
|
320
334
|
case OpType.CreateRegister: {
|
|
321
335
|
const parent = state.items.get(op.parentId);
|
|
322
336
|
if (parent == null || getItem(op.id) != null) {
|
|
323
337
|
return { modified: false };
|
|
324
338
|
}
|
|
325
|
-
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data));
|
|
339
|
+
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal);
|
|
326
340
|
}
|
|
327
341
|
case OpType.CreateMap: {
|
|
328
342
|
const parent = state.items.get(op.parentId);
|
|
329
343
|
if (parent == null || getItem(op.id) != null) {
|
|
330
344
|
return { modified: false };
|
|
331
345
|
}
|
|
332
|
-
return parent._attachChild(op.id, op.parentKey, new LiveMap());
|
|
346
|
+
return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal);
|
|
333
347
|
}
|
|
334
348
|
}
|
|
335
349
|
return { modified: false };
|
|
@@ -421,7 +435,9 @@ See v0.13 release notes for more information.
|
|
|
421
435
|
state.socket = socket;
|
|
422
436
|
}
|
|
423
437
|
function authenticationFailure(error) {
|
|
424
|
-
|
|
438
|
+
if (process.env.NODE_ENV !== "production") {
|
|
439
|
+
console.error("Call to authentication endpoint failed", error);
|
|
440
|
+
}
|
|
425
441
|
updateConnection({ state: "unavailable" });
|
|
426
442
|
state.numberOfRetry++;
|
|
427
443
|
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
@@ -520,7 +536,7 @@ See v0.13 release notes for more information.
|
|
|
520
536
|
subMessages.push(message);
|
|
521
537
|
}
|
|
522
538
|
const updates = {
|
|
523
|
-
|
|
539
|
+
storageUpdates: new Map(),
|
|
524
540
|
others: [],
|
|
525
541
|
};
|
|
526
542
|
for (const subMessage of subMessages) {
|
|
@@ -549,15 +565,16 @@ See v0.13 release notes for more information.
|
|
|
549
565
|
break;
|
|
550
566
|
}
|
|
551
567
|
case ServerMessageType.InitialStorageState: {
|
|
552
|
-
|
|
568
|
+
createOrUpdateRootFromMessage(subMessage);
|
|
569
|
+
applyAndSendOfflineOps();
|
|
553
570
|
_getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
|
|
554
571
|
break;
|
|
555
572
|
}
|
|
556
573
|
case ServerMessageType.UpdateStorage: {
|
|
557
|
-
const applyResult = apply(subMessage.ops);
|
|
558
|
-
|
|
559
|
-
updates.
|
|
560
|
-
}
|
|
574
|
+
const applyResult = apply(subMessage.ops, false);
|
|
575
|
+
applyResult.updates.storageUpdates.forEach((value, key) => {
|
|
576
|
+
updates.storageUpdates.set(key, mergeStorageUpdates(updates.storageUpdates.get(key), value));
|
|
577
|
+
});
|
|
561
578
|
break;
|
|
562
579
|
}
|
|
563
580
|
}
|
|
@@ -587,14 +604,20 @@ See v0.13 release notes for more information.
|
|
|
587
604
|
updateConnection({ state: "failed" });
|
|
588
605
|
const error = new LiveblocksError(event.reason, event.code);
|
|
589
606
|
for (const listener of state.listeners.error) {
|
|
590
|
-
|
|
607
|
+
if (process.env.NODE_ENV !== "production") {
|
|
608
|
+
console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
|
|
609
|
+
}
|
|
591
610
|
listener(error);
|
|
592
611
|
}
|
|
593
612
|
}
|
|
594
613
|
else if (event.wasClean === false) {
|
|
595
|
-
updateConnection({ state: "unavailable" });
|
|
596
614
|
state.numberOfRetry++;
|
|
597
|
-
|
|
615
|
+
const delay = getRetryDelay();
|
|
616
|
+
if (process.env.NODE_ENV !== "production") {
|
|
617
|
+
console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
|
|
618
|
+
}
|
|
619
|
+
updateConnection({ state: "unavailable" });
|
|
620
|
+
state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
|
|
598
621
|
}
|
|
599
622
|
else {
|
|
600
623
|
updateConnection({ state: "closed" });
|
|
@@ -618,6 +641,10 @@ See v0.13 release notes for more information.
|
|
|
618
641
|
if (state.connection.state === "connecting") {
|
|
619
642
|
updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
|
|
620
643
|
state.numberOfRetry = 0;
|
|
644
|
+
state.lastConnectionId = state.connection.id;
|
|
645
|
+
if (state.root) {
|
|
646
|
+
state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
|
|
647
|
+
}
|
|
621
648
|
tryFlushing();
|
|
622
649
|
}
|
|
623
650
|
else {
|
|
@@ -657,11 +684,29 @@ See v0.13 release notes for more information.
|
|
|
657
684
|
clearInterval(state.intervalHandles.heartbeat);
|
|
658
685
|
connect();
|
|
659
686
|
}
|
|
660
|
-
function
|
|
661
|
-
if (state.
|
|
687
|
+
function applyAndSendOfflineOps() {
|
|
688
|
+
if (state.offlineOperations.size === 0) {
|
|
662
689
|
return;
|
|
663
690
|
}
|
|
664
|
-
|
|
691
|
+
const messages = [];
|
|
692
|
+
const ops = Array.from(state.offlineOperations.values());
|
|
693
|
+
const result = apply(ops, true);
|
|
694
|
+
messages.push({
|
|
695
|
+
type: ClientMessageType.UpdateStorage,
|
|
696
|
+
ops: ops,
|
|
697
|
+
});
|
|
698
|
+
notify(result.updates);
|
|
699
|
+
effects.send(messages);
|
|
700
|
+
}
|
|
701
|
+
function tryFlushing() {
|
|
702
|
+
const storageOps = state.buffer.storageOperations;
|
|
703
|
+
if (storageOps.length > 0) {
|
|
704
|
+
storageOps.forEach((op) => {
|
|
705
|
+
state.offlineOperations.set(op.opId, op);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
|
|
709
|
+
state.buffer.storageOperations = [];
|
|
665
710
|
return;
|
|
666
711
|
}
|
|
667
712
|
const now = Date.now();
|
|
@@ -736,8 +781,10 @@ See v0.13 release notes for more information.
|
|
|
736
781
|
function getOthers() {
|
|
737
782
|
return state.others;
|
|
738
783
|
}
|
|
739
|
-
function broadcastEvent(event
|
|
740
|
-
|
|
784
|
+
function broadcastEvent(event, options = {
|
|
785
|
+
shouldQueueEventIfNotReady: false,
|
|
786
|
+
}) {
|
|
787
|
+
if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
|
|
741
788
|
return;
|
|
742
789
|
}
|
|
743
790
|
state.buffer.messages.push({
|
|
@@ -779,7 +826,7 @@ See v0.13 release notes for more information.
|
|
|
779
826
|
return;
|
|
780
827
|
}
|
|
781
828
|
state.isHistoryPaused = false;
|
|
782
|
-
const result = apply(historyItem);
|
|
829
|
+
const result = apply(historyItem, true);
|
|
783
830
|
notify(result.updates);
|
|
784
831
|
state.redoStack.push(result.reverse);
|
|
785
832
|
for (const op of historyItem) {
|
|
@@ -798,7 +845,7 @@ See v0.13 release notes for more information.
|
|
|
798
845
|
return;
|
|
799
846
|
}
|
|
800
847
|
state.isHistoryPaused = false;
|
|
801
|
-
const result = apply(historyItem);
|
|
848
|
+
const result = apply(historyItem, true);
|
|
802
849
|
notify(result.updates);
|
|
803
850
|
state.undoStack.push(result.reverse);
|
|
804
851
|
for (const op of historyItem) {
|
|
@@ -821,8 +868,11 @@ See v0.13 release notes for more information.
|
|
|
821
868
|
if (state.batch.reverseOps.length > 0) {
|
|
822
869
|
addToUndoStack(state.batch.reverseOps);
|
|
823
870
|
}
|
|
824
|
-
|
|
825
|
-
|
|
871
|
+
if (state.batch.ops.length > 0) {
|
|
872
|
+
// Only clear the redo stack if something has changed during a batch
|
|
873
|
+
// Clear the redo stack because batch is always called from a local operation
|
|
874
|
+
state.redoStack = [];
|
|
875
|
+
}
|
|
826
876
|
if (state.batch.ops.length > 0) {
|
|
827
877
|
dispatch(state.batch.ops);
|
|
828
878
|
}
|
|
@@ -832,7 +882,7 @@ See v0.13 release notes for more information.
|
|
|
832
882
|
reverseOps: [],
|
|
833
883
|
updates: {
|
|
834
884
|
others: [],
|
|
835
|
-
|
|
885
|
+
storageUpdates: new Map(),
|
|
836
886
|
presence: false,
|
|
837
887
|
},
|
|
838
888
|
};
|
|
@@ -850,6 +900,16 @@ See v0.13 release notes for more information.
|
|
|
850
900
|
}
|
|
851
901
|
state.pausedHistory = [];
|
|
852
902
|
}
|
|
903
|
+
function simulateSocketClose() {
|
|
904
|
+
if (state.socket) {
|
|
905
|
+
state.socket.close();
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function simulateSendCloseEvent(event) {
|
|
909
|
+
if (state.socket) {
|
|
910
|
+
onClose(event);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
853
913
|
return {
|
|
854
914
|
// Internal
|
|
855
915
|
onOpen,
|
|
@@ -858,6 +918,9 @@ See v0.13 release notes for more information.
|
|
|
858
918
|
authenticationSuccess,
|
|
859
919
|
heartbeat,
|
|
860
920
|
onNavigatorOnline,
|
|
921
|
+
// Internal dev tools
|
|
922
|
+
simulateSocketClose,
|
|
923
|
+
simulateSendCloseEvent,
|
|
861
924
|
// onWakeUp,
|
|
862
925
|
onVisibilityChange,
|
|
863
926
|
getUndoStack: () => state.undoStack,
|
|
@@ -889,6 +952,7 @@ See v0.13 release notes for more information.
|
|
|
889
952
|
export function defaultState(me, defaultStorageRoot) {
|
|
890
953
|
return {
|
|
891
954
|
connection: { state: "closed" },
|
|
955
|
+
lastConnectionId: null,
|
|
892
956
|
socket: null,
|
|
893
957
|
listeners: {
|
|
894
958
|
event: [],
|
|
@@ -930,9 +994,14 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
930
994
|
isBatching: false,
|
|
931
995
|
batch: {
|
|
932
996
|
ops: [],
|
|
933
|
-
updates: {
|
|
997
|
+
updates: {
|
|
998
|
+
storageUpdates: new Map(),
|
|
999
|
+
presence: false,
|
|
1000
|
+
others: [],
|
|
1001
|
+
},
|
|
934
1002
|
reverseOps: [],
|
|
935
1003
|
},
|
|
1004
|
+
offlineOperations: new Map(),
|
|
936
1005
|
};
|
|
937
1006
|
}
|
|
938
1007
|
export function createRoom(name, options) {
|
|
@@ -978,6 +1047,11 @@ export function createRoom(name, options) {
|
|
|
978
1047
|
pause: machine.pauseHistory,
|
|
979
1048
|
resume: machine.resumeHistory,
|
|
980
1049
|
},
|
|
1050
|
+
// @ts-ignore
|
|
1051
|
+
internalDevTools: {
|
|
1052
|
+
closeWebsocket: machine.simulateSocketClose,
|
|
1053
|
+
sendCloseEvent: machine.simulateSendCloseEvent,
|
|
1054
|
+
},
|
|
981
1055
|
};
|
|
982
1056
|
return {
|
|
983
1057
|
connect: machine.connect,
|
package/lib/esm/types.d.ts
CHANGED
|
@@ -16,17 +16,45 @@ export declare type RoomEventCallbackMap = {
|
|
|
16
16
|
error: ErrorCallback;
|
|
17
17
|
connection: ConnectionCallback;
|
|
18
18
|
};
|
|
19
|
+
export declare type UpdateDelta = {
|
|
20
|
+
type: "update";
|
|
21
|
+
} | {
|
|
22
|
+
type: "delete";
|
|
23
|
+
};
|
|
19
24
|
export declare type LiveMapUpdates<TKey extends string = string, TValue = any> = {
|
|
20
25
|
type: "LiveMap";
|
|
21
26
|
node: LiveMap<TKey, TValue>;
|
|
27
|
+
updates: Record<TKey, UpdateDelta>;
|
|
22
28
|
};
|
|
29
|
+
export declare type LiveObjectUpdateDelta<T> = Partial<{
|
|
30
|
+
[Property in keyof T]: UpdateDelta;
|
|
31
|
+
}>;
|
|
23
32
|
export declare type LiveObjectUpdates<TData = any> = {
|
|
24
33
|
type: "LiveObject";
|
|
25
34
|
node: LiveObject<TData>;
|
|
35
|
+
updates: LiveObjectUpdateDelta<TData>;
|
|
36
|
+
};
|
|
37
|
+
export declare type ListUpdateDelta = {
|
|
38
|
+
type: "insert";
|
|
39
|
+
} | {
|
|
40
|
+
type: "delete";
|
|
41
|
+
};
|
|
42
|
+
export declare type LiveListUpdateDelta = {
|
|
43
|
+
index: number;
|
|
44
|
+
type: "insert" | "delete";
|
|
26
45
|
};
|
|
27
46
|
export declare type LiveListUpdates<TItem = any> = {
|
|
28
47
|
type: "LiveList";
|
|
29
48
|
node: LiveList<TItem>;
|
|
49
|
+
updates: LiveListUpdateDelta[];
|
|
50
|
+
};
|
|
51
|
+
export declare type BroadcastOptions = {
|
|
52
|
+
/**
|
|
53
|
+
* Whether or not event is queued if the connection is currently closed.
|
|
54
|
+
*
|
|
55
|
+
* ❗ 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
|
|
56
|
+
*/
|
|
57
|
+
shouldQueueEventIfNotReady: boolean;
|
|
30
58
|
};
|
|
31
59
|
export declare type StorageUpdate = LiveMapUpdates | LiveObjectUpdates | LiveListUpdates;
|
|
32
60
|
export declare type StorageCallback = (updates: StorageUpdate[]) => void;
|
|
@@ -65,6 +93,10 @@ export interface Others<TPresence extends Presence = Presence> {
|
|
|
65
93
|
* Number of other users in the room.
|
|
66
94
|
*/
|
|
67
95
|
readonly count: number;
|
|
96
|
+
/**
|
|
97
|
+
* Returns a new Iterator object that contains the users.
|
|
98
|
+
*/
|
|
99
|
+
[Symbol.iterator](): IterableIterator<User<TPresence>>;
|
|
68
100
|
/**
|
|
69
101
|
* Returns the array of connected users in room.
|
|
70
102
|
*/
|
|
@@ -423,7 +455,7 @@ export declare type Room = {
|
|
|
423
455
|
* }
|
|
424
456
|
* });
|
|
425
457
|
*/
|
|
426
|
-
broadcastEvent: (event: any) => void;
|
|
458
|
+
broadcastEvent: (event: any, options?: BroadcastOptions) => void;
|
|
427
459
|
/**
|
|
428
460
|
* Get the room's storage asynchronously.
|
|
429
461
|
* The storage's root is a {@link LiveObject}.
|
package/lib/esm/utils.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { AbstractCrdt, Doc } from "./AbstractCrdt";
|
|
2
|
-
import { SerializedCrdtWithId } from "./live";
|
|
2
|
+
import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live";
|
|
3
|
+
import { StorageUpdate } from "./types";
|
|
3
4
|
export declare function remove<T>(array: T[], item: T): void;
|
|
4
5
|
export declare function isSameNodeOrChildOf(node: AbstractCrdt, parent: AbstractCrdt): boolean;
|
|
5
6
|
export declare function deserialize(entry: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): AbstractCrdt;
|
|
6
7
|
export declare function isCrdt(obj: any): obj is AbstractCrdt;
|
|
7
8
|
export declare function selfOrRegisterValue(obj: AbstractCrdt): any;
|
|
8
9
|
export declare function selfOrRegister(obj: any): AbstractCrdt;
|
|
10
|
+
export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[];
|
|
11
|
+
export declare function mergeStorageUpdates(first: StorageUpdate | undefined, second: StorageUpdate): StorageUpdate;
|
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,101 @@ 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
|
+
}
|
|
141
|
+
export function mergeStorageUpdates(first, second) {
|
|
142
|
+
if (!first) {
|
|
143
|
+
return second;
|
|
144
|
+
}
|
|
145
|
+
if (second.type === "LiveObject") {
|
|
146
|
+
const updates = first.updates;
|
|
147
|
+
for (const [key, value] of Object.entries(second.updates)) {
|
|
148
|
+
updates[key] = value;
|
|
149
|
+
}
|
|
150
|
+
return Object.assign(Object.assign({}, second), { updates: updates });
|
|
151
|
+
}
|
|
152
|
+
else if (second.type === "LiveMap") {
|
|
153
|
+
const updates = first.updates;
|
|
154
|
+
for (const [key, value] of Object.entries(second.updates)) {
|
|
155
|
+
updates[key] = value;
|
|
156
|
+
}
|
|
157
|
+
return Object.assign(Object.assign({}, second), { updates: updates });
|
|
158
|
+
}
|
|
159
|
+
else if (second.type === "LiveList") {
|
|
160
|
+
const updates = first.updates;
|
|
161
|
+
return Object.assign(Object.assign({}, second), { updates: updates.concat(second.updates) });
|
|
162
|
+
}
|
|
163
|
+
return second;
|
|
164
|
+
}
|