@liveblocks/client 0.12.2 → 0.13.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/README.md +34 -6
- package/lib/cjs/AbstractCrdt.d.ts +61 -0
- package/lib/cjs/AbstractCrdt.js +98 -0
- package/lib/cjs/LiveList.d.ts +133 -0
- package/lib/cjs/LiveList.js +374 -0
- package/lib/cjs/LiveMap.d.ts +83 -0
- package/lib/cjs/LiveMap.js +272 -0
- package/lib/cjs/LiveObject.d.ts +66 -0
- package/lib/cjs/LiveObject.js +368 -0
- package/lib/cjs/LiveRegister.d.ts +21 -0
- package/lib/cjs/LiveRegister.js +69 -0
- package/lib/cjs/index.d.ts +3 -1
- package/lib/cjs/index.js +7 -5
- package/lib/cjs/room.d.ts +50 -9
- package/lib/cjs/room.js +476 -85
- package/lib/cjs/types.d.ts +220 -40
- package/lib/cjs/utils.d.ts +7 -0
- package/lib/cjs/utils.js +64 -1
- package/lib/esm/AbstractCrdt.d.ts +61 -0
- package/lib/esm/AbstractCrdt.js +94 -0
- package/lib/esm/LiveList.d.ts +133 -0
- package/lib/esm/LiveList.js +370 -0
- package/lib/esm/LiveMap.d.ts +83 -0
- package/lib/esm/LiveMap.js +268 -0
- package/lib/esm/LiveObject.d.ts +66 -0
- package/lib/esm/LiveObject.js +364 -0
- package/lib/esm/LiveRegister.d.ts +21 -0
- package/lib/esm/LiveRegister.js +65 -0
- package/lib/esm/index.d.ts +3 -1
- package/lib/esm/index.js +3 -1
- package/lib/esm/room.d.ts +50 -9
- package/lib/esm/room.js +478 -84
- package/lib/esm/types.d.ts +220 -40
- package/lib/esm/utils.d.ts +7 -0
- package/lib/esm/utils.js +58 -0
- package/package.json +3 -3
- package/lib/cjs/doc.d.ts +0 -347
- package/lib/cjs/doc.js +0 -1349
- package/lib/cjs/storage.d.ts +0 -21
- package/lib/cjs/storage.js +0 -68
- package/lib/esm/doc.d.ts +0 -347
- package/lib/esm/doc.js +0 -1342
- package/lib/esm/storage.d.ts +0 -21
- package/lib/esm/storage.js +0 -65
package/lib/esm/room.js
CHANGED
|
@@ -7,10 +7,14 @@ 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 { remove } from "./utils";
|
|
10
|
+
import { isSameNodeOrChildOf, remove } from "./utils";
|
|
11
11
|
import auth, { parseToken } from "./authentication";
|
|
12
|
-
import { ClientMessageType, ServerMessageType, } from "./live";
|
|
13
|
-
import
|
|
12
|
+
import { ClientMessageType, ServerMessageType, OpType, } from "./live";
|
|
13
|
+
import { LiveMap } from "./LiveMap";
|
|
14
|
+
import { LiveObject } from "./LiveObject";
|
|
15
|
+
import { LiveList } from "./LiveList";
|
|
16
|
+
import { AbstractCrdt } from "./AbstractCrdt";
|
|
17
|
+
import { LiveRegister } from "./LiveRegister";
|
|
14
18
|
const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];
|
|
15
19
|
const HEARTBEAT_INTERVAL = 30000;
|
|
16
20
|
// const WAKE_UP_CHECK_INTERVAL = 2000;
|
|
@@ -82,13 +86,275 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
82
86
|
return setTimeout(connect, delay);
|
|
83
87
|
},
|
|
84
88
|
};
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
function genericSubscribe(callback) {
|
|
90
|
+
state.listeners.storage.push(callback);
|
|
91
|
+
return () => remove(state.listeners.storage, callback);
|
|
92
|
+
}
|
|
93
|
+
function crdtSubscribe(crdt, innerCallback, options) {
|
|
94
|
+
const cb = (updates) => {
|
|
95
|
+
const relatedUpdates = [];
|
|
96
|
+
for (const update of updates) {
|
|
97
|
+
if ((options === null || options === void 0 ? void 0 : options.isDeep) && isSameNodeOrChildOf(update.node, crdt)) {
|
|
98
|
+
relatedUpdates.push(update);
|
|
99
|
+
}
|
|
100
|
+
else if (update.node._id === crdt._id) {
|
|
101
|
+
innerCallback(update.node);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if ((options === null || options === void 0 ? void 0 : options.isDeep) && relatedUpdates.length > 0) {
|
|
105
|
+
innerCallback(relatedUpdates);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return genericSubscribe(cb);
|
|
109
|
+
}
|
|
110
|
+
function createRootFromMessage(message) {
|
|
111
|
+
state.root = load(message.items);
|
|
112
|
+
for (const key in state.defaultStorageRoot) {
|
|
113
|
+
if (state.root.get(key) == null) {
|
|
114
|
+
state.root.set(key, state.defaultStorageRoot[key]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function load(items) {
|
|
119
|
+
if (items.length === 0) {
|
|
120
|
+
throw new Error("Internal error: cannot load storage without items");
|
|
121
|
+
}
|
|
122
|
+
const parentToChildren = new Map();
|
|
123
|
+
let root = null;
|
|
124
|
+
for (const tuple of items) {
|
|
125
|
+
const parentId = tuple[1].parentId;
|
|
126
|
+
if (parentId == null) {
|
|
127
|
+
root = tuple;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const children = parentToChildren.get(parentId);
|
|
131
|
+
if (children != null) {
|
|
132
|
+
children.push(tuple);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
parentToChildren.set(parentId, [tuple]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (root == null) {
|
|
140
|
+
throw new Error("Root can't be null");
|
|
141
|
+
}
|
|
142
|
+
return LiveObject._deserialize(root, parentToChildren, {
|
|
143
|
+
addItem,
|
|
144
|
+
deleteItem,
|
|
145
|
+
generateId,
|
|
146
|
+
generateOpId,
|
|
147
|
+
dispatch: storageDispatch,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function addItem(id, item) {
|
|
151
|
+
state.items.set(id, item);
|
|
152
|
+
}
|
|
153
|
+
function deleteItem(id) {
|
|
154
|
+
state.items.delete(id);
|
|
155
|
+
}
|
|
156
|
+
function getItem(id) {
|
|
157
|
+
return state.items.get(id);
|
|
158
|
+
}
|
|
159
|
+
function addToUndoStack(historyItem) {
|
|
160
|
+
// If undo stack is too large, we remove the older item
|
|
161
|
+
if (state.undoStack.length >= 50) {
|
|
162
|
+
state.undoStack.shift();
|
|
163
|
+
}
|
|
164
|
+
if (state.isHistoryPaused) {
|
|
165
|
+
state.pausedHistory.unshift(...historyItem);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
state.undoStack.push(historyItem);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function storageDispatch(ops, reverse, modified) {
|
|
172
|
+
if (state.isBatching) {
|
|
173
|
+
state.batch.ops.push(...ops);
|
|
174
|
+
for (const item of modified) {
|
|
175
|
+
state.batch.updates.nodes.add(item);
|
|
176
|
+
}
|
|
177
|
+
state.batch.reverseOps.push(...reverse);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
addToUndoStack(reverse);
|
|
181
|
+
state.redoStack = [];
|
|
182
|
+
dispatch(ops);
|
|
183
|
+
notify({ nodes: new Set(modified) });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function notify({ nodes = new Set(), presence = false, others = [], }) {
|
|
187
|
+
if (others.length > 0) {
|
|
188
|
+
state.others = makeOthers(state.users);
|
|
189
|
+
for (const event of others) {
|
|
190
|
+
for (const listener of state.listeners["others"]) {
|
|
191
|
+
listener(state.others, event);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (presence) {
|
|
196
|
+
for (const listener of state.listeners["my-presence"]) {
|
|
197
|
+
listener(state.me);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (nodes.size > 0) {
|
|
201
|
+
for (const subscriber of state.listeners.storage) {
|
|
202
|
+
subscriber(Array.from(nodes).map((m) => {
|
|
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
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function getConnectionId() {
|
|
226
|
+
if (state.connection.state === "open" ||
|
|
227
|
+
state.connection.state === "connecting") {
|
|
228
|
+
return state.connection.id;
|
|
229
|
+
}
|
|
230
|
+
throw new Error("Internal. Tried to get connection id but connection is not open");
|
|
231
|
+
}
|
|
232
|
+
function generateId() {
|
|
233
|
+
return `${getConnectionId()}:${state.clock++}`;
|
|
234
|
+
}
|
|
235
|
+
function generateOpId() {
|
|
236
|
+
return `${getConnectionId()}:${state.opClock++}`;
|
|
237
|
+
}
|
|
238
|
+
function apply(item) {
|
|
239
|
+
const result = {
|
|
240
|
+
reverse: [],
|
|
241
|
+
updates: { nodes: new Set(), presence: false },
|
|
242
|
+
};
|
|
243
|
+
for (const op of item) {
|
|
244
|
+
if (op.type === "presence") {
|
|
245
|
+
const reverse = {
|
|
246
|
+
type: "presence",
|
|
247
|
+
data: {},
|
|
248
|
+
};
|
|
249
|
+
for (const key in op.data) {
|
|
250
|
+
reverse.data[key] = state.me[key];
|
|
251
|
+
}
|
|
252
|
+
state.me = Object.assign(Object.assign({}, state.me), op.data);
|
|
253
|
+
if (state.buffer.presence == null) {
|
|
254
|
+
state.buffer.presence = op.data;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
for (const key in op.data) {
|
|
258
|
+
state.buffer.presence[key] = op.data;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
result.reverse.unshift(reverse);
|
|
262
|
+
result.updates.presence = true;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const applyOpResult = applyOp(op);
|
|
266
|
+
if (applyOpResult.modified) {
|
|
267
|
+
result.updates.nodes.add(applyOpResult.modified);
|
|
268
|
+
result.reverse.unshift(...applyOpResult.reverse);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
function applyOp(op) {
|
|
275
|
+
switch (op.type) {
|
|
276
|
+
case OpType.DeleteObjectKey:
|
|
277
|
+
case OpType.UpdateObject:
|
|
278
|
+
case OpType.DeleteCrdt: {
|
|
279
|
+
const item = state.items.get(op.id);
|
|
280
|
+
if (item == null) {
|
|
281
|
+
return { modified: false };
|
|
282
|
+
}
|
|
283
|
+
return item._apply(op);
|
|
284
|
+
}
|
|
285
|
+
case OpType.SetParentKey: {
|
|
286
|
+
const item = state.items.get(op.id);
|
|
287
|
+
if (item == null) {
|
|
288
|
+
return { modified: false };
|
|
289
|
+
}
|
|
290
|
+
if (item._parent instanceof LiveList) {
|
|
291
|
+
const previousKey = item._parentKey;
|
|
292
|
+
item._parent._setChildKey(op.parentKey, item);
|
|
293
|
+
return {
|
|
294
|
+
reverse: [
|
|
295
|
+
{
|
|
296
|
+
type: OpType.SetParentKey,
|
|
297
|
+
id: item._id,
|
|
298
|
+
parentKey: previousKey,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
modified: item._parent,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return { modified: false };
|
|
305
|
+
}
|
|
306
|
+
case OpType.CreateObject: {
|
|
307
|
+
const parent = state.items.get(op.parentId);
|
|
308
|
+
if (parent == null || getItem(op.id) != null) {
|
|
309
|
+
return { modified: false };
|
|
310
|
+
}
|
|
311
|
+
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data));
|
|
312
|
+
}
|
|
313
|
+
case OpType.CreateList: {
|
|
314
|
+
const parent = state.items.get(op.parentId);
|
|
315
|
+
if (parent == null || getItem(op.id) != null) {
|
|
316
|
+
return { modified: false };
|
|
317
|
+
}
|
|
318
|
+
return parent._attachChild(op.id, op.parentKey, new LiveList());
|
|
319
|
+
}
|
|
320
|
+
case OpType.CreateRegister: {
|
|
321
|
+
const parent = state.items.get(op.parentId);
|
|
322
|
+
if (parent == null || getItem(op.id) != null) {
|
|
323
|
+
return { modified: false };
|
|
324
|
+
}
|
|
325
|
+
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data));
|
|
326
|
+
}
|
|
327
|
+
case OpType.CreateMap: {
|
|
328
|
+
const parent = state.items.get(op.parentId);
|
|
329
|
+
if (parent == null || getItem(op.id) != null) {
|
|
330
|
+
return { modified: false };
|
|
331
|
+
}
|
|
332
|
+
return parent._attachChild(op.id, op.parentKey, new LiveMap());
|
|
333
|
+
}
|
|
88
334
|
}
|
|
89
|
-
|
|
335
|
+
return { modified: false };
|
|
336
|
+
}
|
|
337
|
+
function subscribe(firstParam, listener, options) {
|
|
338
|
+
if (firstParam instanceof AbstractCrdt) {
|
|
339
|
+
return crdtSubscribe(firstParam, listener, options);
|
|
340
|
+
}
|
|
341
|
+
else if (typeof firstParam === "function") {
|
|
342
|
+
return genericSubscribe(firstParam);
|
|
343
|
+
}
|
|
344
|
+
else if (!isValidRoomEventType(firstParam)) {
|
|
345
|
+
throw new Error(`"${firstParam}" is not a valid event name`);
|
|
346
|
+
}
|
|
347
|
+
state.listeners[firstParam].push(listener);
|
|
348
|
+
return () => {
|
|
349
|
+
const callbacks = state.listeners[firstParam];
|
|
350
|
+
remove(callbacks, listener);
|
|
351
|
+
};
|
|
90
352
|
}
|
|
91
353
|
function unsubscribe(event, callback) {
|
|
354
|
+
console.warn(`unsubscribe is depreacted and will be removed in a future version.
|
|
355
|
+
use the callback returned by subscribe instead.
|
|
356
|
+
See v0.13 release notes for more information.
|
|
357
|
+
`);
|
|
92
358
|
if (!isValidRoomEventType(event)) {
|
|
93
359
|
throw new Error(`"${event}" is not a valid event name`);
|
|
94
360
|
}
|
|
@@ -120,20 +386,28 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
120
386
|
updateConnection({ state: "authenticating" });
|
|
121
387
|
effects.authenticate();
|
|
122
388
|
}
|
|
123
|
-
function updatePresence(overrides) {
|
|
124
|
-
const
|
|
125
|
-
if (state.
|
|
126
|
-
state.
|
|
389
|
+
function updatePresence(overrides, options) {
|
|
390
|
+
const oldValues = {};
|
|
391
|
+
if (state.buffer.presence == null) {
|
|
392
|
+
state.buffer.presence = {};
|
|
127
393
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
394
|
+
for (const key in overrides) {
|
|
395
|
+
state.buffer.presence[key] = overrides[key];
|
|
396
|
+
oldValues[key] = state.me[key];
|
|
397
|
+
}
|
|
398
|
+
state.me = Object.assign(Object.assign({}, state.me), overrides);
|
|
399
|
+
if (state.isBatching) {
|
|
400
|
+
if (options === null || options === void 0 ? void 0 : options.addToHistory) {
|
|
401
|
+
state.batch.reverseOps.push({ type: "presence", data: oldValues });
|
|
131
402
|
}
|
|
403
|
+
state.batch.updates.presence = true;
|
|
132
404
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
405
|
+
else {
|
|
406
|
+
tryFlushing();
|
|
407
|
+
if (options === null || options === void 0 ? void 0 : options.addToHistory) {
|
|
408
|
+
addToUndoStack([{ type: "presence", data: oldValues }]);
|
|
409
|
+
}
|
|
410
|
+
notify({ presence: true });
|
|
137
411
|
}
|
|
138
412
|
}
|
|
139
413
|
function authenticationSuccess(token, socket) {
|
|
@@ -174,25 +448,20 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
174
448
|
presence: Object.assign(Object.assign({}, user.presence), message.data),
|
|
175
449
|
};
|
|
176
450
|
}
|
|
177
|
-
|
|
451
|
+
return {
|
|
178
452
|
type: "update",
|
|
179
453
|
updates: message.data,
|
|
180
454
|
user: state.users[message.actor],
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function updateUsers(event) {
|
|
184
|
-
state.others = makeOthers(state.users);
|
|
185
|
-
for (const listener of state.listeners["others"]) {
|
|
186
|
-
listener(state.others, event);
|
|
187
|
-
}
|
|
455
|
+
};
|
|
188
456
|
}
|
|
189
457
|
function onUserLeftMessage(message) {
|
|
190
458
|
const userLeftMessage = message;
|
|
191
459
|
const user = state.users[userLeftMessage.actor];
|
|
192
460
|
if (user) {
|
|
193
461
|
delete state.users[userLeftMessage.actor];
|
|
194
|
-
|
|
462
|
+
return { type: "leave", user };
|
|
195
463
|
}
|
|
464
|
+
return null;
|
|
196
465
|
}
|
|
197
466
|
function onRoomStateMessage(message) {
|
|
198
467
|
const newUsers = {};
|
|
@@ -206,7 +475,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
206
475
|
};
|
|
207
476
|
}
|
|
208
477
|
state.users = newUsers;
|
|
209
|
-
|
|
478
|
+
return { type: "reset" };
|
|
210
479
|
}
|
|
211
480
|
function onNavigatorOnline() {
|
|
212
481
|
if (state.connection.state === "unavailable") {
|
|
@@ -225,17 +494,17 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
225
494
|
info: message.info,
|
|
226
495
|
id: message.id,
|
|
227
496
|
};
|
|
228
|
-
updateUsers({ type: "enter", user: state.users[message.actor] });
|
|
229
497
|
if (state.me) {
|
|
230
498
|
// Send current presence to new user
|
|
231
499
|
// TODO: Consider storing it on the backend
|
|
232
|
-
state.
|
|
500
|
+
state.buffer.messages.push({
|
|
233
501
|
type: ClientMessageType.UpdatePresence,
|
|
234
502
|
data: state.me,
|
|
235
503
|
targetActor: message.actor,
|
|
236
504
|
});
|
|
237
505
|
tryFlushing();
|
|
238
506
|
}
|
|
507
|
+
return { type: "enter", user: state.users[message.actor] };
|
|
239
508
|
}
|
|
240
509
|
function onMessage(event) {
|
|
241
510
|
if (event.data === "pong") {
|
|
@@ -243,29 +512,57 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
243
512
|
return;
|
|
244
513
|
}
|
|
245
514
|
const message = JSON.parse(event.data);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
515
|
+
let subMessages = [];
|
|
516
|
+
if (Array.isArray(message)) {
|
|
517
|
+
subMessages = message;
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
subMessages.push(message);
|
|
521
|
+
}
|
|
522
|
+
const updates = {
|
|
523
|
+
nodes: new Set(),
|
|
524
|
+
others: [],
|
|
525
|
+
};
|
|
526
|
+
for (const subMessage of subMessages) {
|
|
527
|
+
switch (subMessage.type) {
|
|
528
|
+
case ServerMessageType.UserJoined: {
|
|
529
|
+
updates.others.push(onUserJoinedMessage(message));
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
case ServerMessageType.UpdatePresence: {
|
|
533
|
+
updates.others.push(onUpdatePresenceMessage(subMessage));
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
case ServerMessageType.Event: {
|
|
537
|
+
onEvent(subMessage);
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
case ServerMessageType.UserLeft: {
|
|
541
|
+
const event = onUserLeftMessage(subMessage);
|
|
542
|
+
if (event) {
|
|
543
|
+
updates.others.push(event);
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case ServerMessageType.RoomState: {
|
|
548
|
+
updates.others.push(onRoomStateMessage(subMessage));
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case ServerMessageType.InitialStorageState: {
|
|
552
|
+
createRootFromMessage(subMessage);
|
|
553
|
+
_getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case ServerMessageType.UpdateStorage: {
|
|
557
|
+
const applyResult = apply(subMessage.ops);
|
|
558
|
+
for (const node of applyResult.updates.nodes) {
|
|
559
|
+
updates.nodes.add(node);
|
|
560
|
+
}
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
266
563
|
}
|
|
267
564
|
}
|
|
268
|
-
|
|
565
|
+
notify(updates);
|
|
269
566
|
}
|
|
270
567
|
// function onWakeUp() {
|
|
271
568
|
// // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example)
|
|
@@ -285,7 +582,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
285
582
|
}
|
|
286
583
|
clearTimeout(state.timeoutHandles.reconnect);
|
|
287
584
|
state.users = {};
|
|
288
|
-
|
|
585
|
+
notify({ others: [{ type: "reset" }] });
|
|
289
586
|
if (event.code >= 4000 && event.code <= 4100) {
|
|
290
587
|
updateConnection({ state: "failed" });
|
|
291
588
|
const error = new LiveblocksError(event.reason, event.code);
|
|
@@ -374,7 +671,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
374
671
|
return;
|
|
375
672
|
}
|
|
376
673
|
effects.send(messages);
|
|
377
|
-
state.
|
|
674
|
+
state.buffer = {
|
|
378
675
|
messages: [],
|
|
379
676
|
storageOperations: [],
|
|
380
677
|
presence: null,
|
|
@@ -390,19 +687,19 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
390
687
|
}
|
|
391
688
|
function flushDataToMessages(state) {
|
|
392
689
|
const messages = [];
|
|
393
|
-
if (state.
|
|
690
|
+
if (state.buffer.presence) {
|
|
394
691
|
messages.push({
|
|
395
692
|
type: ClientMessageType.UpdatePresence,
|
|
396
|
-
data: state.
|
|
693
|
+
data: state.buffer.presence,
|
|
397
694
|
});
|
|
398
695
|
}
|
|
399
|
-
for (const event of state.
|
|
696
|
+
for (const event of state.buffer.messages) {
|
|
400
697
|
messages.push(event);
|
|
401
698
|
}
|
|
402
|
-
if (state.
|
|
699
|
+
if (state.buffer.storageOperations.length > 0) {
|
|
403
700
|
messages.push({
|
|
404
701
|
type: ClientMessageType.UpdateStorage,
|
|
405
|
-
ops: state.
|
|
702
|
+
ops: state.buffer.storageOperations,
|
|
406
703
|
});
|
|
407
704
|
}
|
|
408
705
|
return messages;
|
|
@@ -424,7 +721,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
424
721
|
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
425
722
|
clearInterval(state.intervalHandles.heartbeat);
|
|
426
723
|
state.users = {};
|
|
427
|
-
|
|
724
|
+
notify({ others: [{ type: "reset" }] });
|
|
428
725
|
clearListeners();
|
|
429
726
|
}
|
|
430
727
|
function clearListeners() {
|
|
@@ -442,44 +739,115 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
442
739
|
if (state.socket == null) {
|
|
443
740
|
return;
|
|
444
741
|
}
|
|
445
|
-
state.
|
|
742
|
+
state.buffer.messages.push({
|
|
446
743
|
type: ClientMessageType.ClientEvent,
|
|
447
744
|
event,
|
|
448
745
|
});
|
|
449
746
|
tryFlushing();
|
|
450
747
|
}
|
|
451
748
|
function dispatch(ops) {
|
|
452
|
-
state.
|
|
749
|
+
state.buffer.storageOperations.push(...ops);
|
|
453
750
|
tryFlushing();
|
|
454
751
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
state.flushData.messages.push({ type: ClientMessageType.FetchStorage });
|
|
458
|
-
tryFlushing();
|
|
459
|
-
},
|
|
460
|
-
dispatch,
|
|
461
|
-
getConnectionId: () => {
|
|
462
|
-
const me = getSelf();
|
|
463
|
-
if (me) {
|
|
464
|
-
return me.connectionId;
|
|
465
|
-
}
|
|
466
|
-
throw new Error("Unexpected");
|
|
467
|
-
},
|
|
468
|
-
defaultRoot: state.defaultStorageRoot,
|
|
469
|
-
});
|
|
752
|
+
let _getInitialStatePromise = null;
|
|
753
|
+
let _getInitialStateResolver = null;
|
|
470
754
|
function getStorage() {
|
|
471
755
|
return __awaiter(this, void 0, void 0, function* () {
|
|
472
|
-
|
|
756
|
+
if (state.root) {
|
|
757
|
+
return {
|
|
758
|
+
root: state.root,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
if (_getInitialStatePromise == null) {
|
|
762
|
+
state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
|
|
763
|
+
tryFlushing();
|
|
764
|
+
_getInitialStatePromise = new Promise((resolve) => (_getInitialStateResolver = resolve));
|
|
765
|
+
}
|
|
766
|
+
yield _getInitialStatePromise;
|
|
473
767
|
return {
|
|
474
|
-
root:
|
|
768
|
+
root: state.root,
|
|
475
769
|
};
|
|
476
770
|
});
|
|
477
771
|
}
|
|
478
772
|
function undo() {
|
|
479
|
-
|
|
773
|
+
if (state.isBatching) {
|
|
774
|
+
throw new Error("undo is not allowed during a batch");
|
|
775
|
+
}
|
|
776
|
+
const historyItem = state.undoStack.pop();
|
|
777
|
+
if (historyItem == null) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
state.isHistoryPaused = false;
|
|
781
|
+
const result = apply(historyItem);
|
|
782
|
+
notify(result.updates);
|
|
783
|
+
state.redoStack.push(result.reverse);
|
|
784
|
+
for (const op of historyItem) {
|
|
785
|
+
if (op.type !== "presence") {
|
|
786
|
+
state.buffer.storageOperations.push(op);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
tryFlushing();
|
|
480
790
|
}
|
|
481
791
|
function redo() {
|
|
482
|
-
|
|
792
|
+
if (state.isBatching) {
|
|
793
|
+
throw new Error("redo is not allowed during a batch");
|
|
794
|
+
}
|
|
795
|
+
const historyItem = state.redoStack.pop();
|
|
796
|
+
if (historyItem == null) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
state.isHistoryPaused = false;
|
|
800
|
+
const result = apply(historyItem);
|
|
801
|
+
notify(result.updates);
|
|
802
|
+
state.undoStack.push(result.reverse);
|
|
803
|
+
for (const op of historyItem) {
|
|
804
|
+
if (op.type !== "presence") {
|
|
805
|
+
state.buffer.storageOperations.push(op);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
tryFlushing();
|
|
809
|
+
}
|
|
810
|
+
function batch(callback) {
|
|
811
|
+
if (state.isBatching) {
|
|
812
|
+
throw new Error("batch should not be called during a batch");
|
|
813
|
+
}
|
|
814
|
+
state.isBatching = true;
|
|
815
|
+
try {
|
|
816
|
+
callback();
|
|
817
|
+
}
|
|
818
|
+
finally {
|
|
819
|
+
state.isBatching = false;
|
|
820
|
+
if (state.batch.reverseOps.length > 0) {
|
|
821
|
+
addToUndoStack(state.batch.reverseOps);
|
|
822
|
+
}
|
|
823
|
+
// Clear the redo stack because batch is always called from a local operation
|
|
824
|
+
state.redoStack = [];
|
|
825
|
+
if (state.batch.ops.length > 0) {
|
|
826
|
+
dispatch(state.batch.ops);
|
|
827
|
+
}
|
|
828
|
+
notify(state.batch.updates);
|
|
829
|
+
state.batch = {
|
|
830
|
+
ops: [],
|
|
831
|
+
reverseOps: [],
|
|
832
|
+
updates: {
|
|
833
|
+
others: [],
|
|
834
|
+
nodes: new Set(),
|
|
835
|
+
presence: false,
|
|
836
|
+
},
|
|
837
|
+
};
|
|
838
|
+
tryFlushing();
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function pauseHistory() {
|
|
842
|
+
state.pausedHistory = [];
|
|
843
|
+
state.isHistoryPaused = true;
|
|
844
|
+
}
|
|
845
|
+
function resumeHistory() {
|
|
846
|
+
state.isHistoryPaused = false;
|
|
847
|
+
if (state.pausedHistory.length > 0) {
|
|
848
|
+
addToUndoStack(state.pausedHistory);
|
|
849
|
+
}
|
|
850
|
+
state.pausedHistory = [];
|
|
483
851
|
}
|
|
484
852
|
return {
|
|
485
853
|
// Internal
|
|
@@ -491,6 +859,8 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
491
859
|
onNavigatorOnline,
|
|
492
860
|
// onWakeUp,
|
|
493
861
|
onVisibilityChange,
|
|
862
|
+
getUndoStack: () => state.undoStack,
|
|
863
|
+
getItemsCount: () => state.items.size,
|
|
494
864
|
// Core
|
|
495
865
|
connect,
|
|
496
866
|
disconnect,
|
|
@@ -499,8 +869,11 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
499
869
|
// Presence
|
|
500
870
|
updatePresence,
|
|
501
871
|
broadcastEvent,
|
|
872
|
+
batch,
|
|
502
873
|
undo,
|
|
503
874
|
redo,
|
|
875
|
+
pauseHistory,
|
|
876
|
+
resumeHistory,
|
|
504
877
|
getStorage,
|
|
505
878
|
selectors: {
|
|
506
879
|
// Core
|
|
@@ -522,6 +895,7 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
522
895
|
"my-presence": [],
|
|
523
896
|
error: [],
|
|
524
897
|
connection: [],
|
|
898
|
+
storage: [],
|
|
525
899
|
},
|
|
526
900
|
numberOfRetry: 0,
|
|
527
901
|
lastFlushTime: 0,
|
|
@@ -530,7 +904,7 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
530
904
|
reconnect: 0,
|
|
531
905
|
pongTimeout: 0,
|
|
532
906
|
},
|
|
533
|
-
|
|
907
|
+
buffer: {
|
|
534
908
|
presence: me == null ? {} : me,
|
|
535
909
|
messages: [],
|
|
536
910
|
storageOperations: [],
|
|
@@ -543,11 +917,26 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
543
917
|
others: makeOthers({}),
|
|
544
918
|
defaultStorageRoot,
|
|
545
919
|
idFactory: null,
|
|
920
|
+
// Storage
|
|
921
|
+
clock: 0,
|
|
922
|
+
opClock: 0,
|
|
923
|
+
items: new Map(),
|
|
924
|
+
root: undefined,
|
|
925
|
+
undoStack: [],
|
|
926
|
+
redoStack: [],
|
|
927
|
+
isHistoryPaused: false,
|
|
928
|
+
pausedHistory: [],
|
|
929
|
+
isBatching: false,
|
|
930
|
+
batch: {
|
|
931
|
+
ops: [],
|
|
932
|
+
updates: { nodes: new Set(), presence: false, others: [] },
|
|
933
|
+
reverseOps: [],
|
|
934
|
+
},
|
|
546
935
|
};
|
|
547
936
|
}
|
|
548
937
|
export function createRoom(name, options) {
|
|
549
938
|
const throttleDelay = options.throttle || 100;
|
|
550
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/
|
|
939
|
+
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
|
|
551
940
|
let authEndpoint;
|
|
552
941
|
if (options.authEndpoint) {
|
|
553
942
|
authEndpoint = options.authEndpoint;
|
|
@@ -581,8 +970,13 @@ export function createRoom(name, options) {
|
|
|
581
970
|
getOthers: machine.selectors.getOthers,
|
|
582
971
|
broadcastEvent: machine.broadcastEvent,
|
|
583
972
|
getStorage: machine.getStorage,
|
|
584
|
-
|
|
585
|
-
|
|
973
|
+
batch: machine.batch,
|
|
974
|
+
history: {
|
|
975
|
+
undo: machine.undo,
|
|
976
|
+
redo: machine.redo,
|
|
977
|
+
pause: machine.pauseHistory,
|
|
978
|
+
resume: machine.resumeHistory,
|
|
979
|
+
},
|
|
586
980
|
};
|
|
587
981
|
return {
|
|
588
982
|
connect: machine.connect,
|