@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/cjs/room.js
CHANGED
|
@@ -27,15 +27,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
27
27
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
28
28
|
});
|
|
29
29
|
};
|
|
30
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
-
};
|
|
33
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
31
|
exports.createRoom = exports.defaultState = exports.makeStateMachine = void 0;
|
|
35
32
|
const utils_1 = require("./utils");
|
|
36
33
|
const authentication_1 = __importStar(require("./authentication"));
|
|
37
34
|
const live_1 = require("./live");
|
|
38
|
-
const
|
|
35
|
+
const LiveMap_1 = require("./LiveMap");
|
|
36
|
+
const LiveObject_1 = require("./LiveObject");
|
|
37
|
+
const LiveList_1 = require("./LiveList");
|
|
38
|
+
const AbstractCrdt_1 = require("./AbstractCrdt");
|
|
39
|
+
const LiveRegister_1 = require("./LiveRegister");
|
|
39
40
|
const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];
|
|
40
41
|
const HEARTBEAT_INTERVAL = 30000;
|
|
41
42
|
// const WAKE_UP_CHECK_INTERVAL = 2000;
|
|
@@ -107,13 +108,275 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
107
108
|
return setTimeout(connect, delay);
|
|
108
109
|
},
|
|
109
110
|
};
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
function genericSubscribe(callback) {
|
|
112
|
+
state.listeners.storage.push(callback);
|
|
113
|
+
return () => (0, utils_1.remove)(state.listeners.storage, callback);
|
|
114
|
+
}
|
|
115
|
+
function crdtSubscribe(crdt, innerCallback, options) {
|
|
116
|
+
const cb = (updates) => {
|
|
117
|
+
const relatedUpdates = [];
|
|
118
|
+
for (const update of updates) {
|
|
119
|
+
if ((options === null || options === void 0 ? void 0 : options.isDeep) && (0, utils_1.isSameNodeOrChildOf)(update.node, crdt)) {
|
|
120
|
+
relatedUpdates.push(update);
|
|
121
|
+
}
|
|
122
|
+
else if (update.node._id === crdt._id) {
|
|
123
|
+
innerCallback(update.node);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if ((options === null || options === void 0 ? void 0 : options.isDeep) && relatedUpdates.length > 0) {
|
|
127
|
+
innerCallback(relatedUpdates);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return genericSubscribe(cb);
|
|
131
|
+
}
|
|
132
|
+
function createRootFromMessage(message) {
|
|
133
|
+
state.root = load(message.items);
|
|
134
|
+
for (const key in state.defaultStorageRoot) {
|
|
135
|
+
if (state.root.get(key) == null) {
|
|
136
|
+
state.root.set(key, state.defaultStorageRoot[key]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function load(items) {
|
|
141
|
+
if (items.length === 0) {
|
|
142
|
+
throw new Error("Internal error: cannot load storage without items");
|
|
143
|
+
}
|
|
144
|
+
const parentToChildren = new Map();
|
|
145
|
+
let root = null;
|
|
146
|
+
for (const tuple of items) {
|
|
147
|
+
const parentId = tuple[1].parentId;
|
|
148
|
+
if (parentId == null) {
|
|
149
|
+
root = tuple;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const children = parentToChildren.get(parentId);
|
|
153
|
+
if (children != null) {
|
|
154
|
+
children.push(tuple);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
parentToChildren.set(parentId, [tuple]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (root == null) {
|
|
162
|
+
throw new Error("Root can't be null");
|
|
163
|
+
}
|
|
164
|
+
return LiveObject_1.LiveObject._deserialize(root, parentToChildren, {
|
|
165
|
+
addItem,
|
|
166
|
+
deleteItem,
|
|
167
|
+
generateId,
|
|
168
|
+
generateOpId,
|
|
169
|
+
dispatch: storageDispatch,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function addItem(id, item) {
|
|
173
|
+
state.items.set(id, item);
|
|
174
|
+
}
|
|
175
|
+
function deleteItem(id) {
|
|
176
|
+
state.items.delete(id);
|
|
177
|
+
}
|
|
178
|
+
function getItem(id) {
|
|
179
|
+
return state.items.get(id);
|
|
180
|
+
}
|
|
181
|
+
function addToUndoStack(historyItem) {
|
|
182
|
+
// If undo stack is too large, we remove the older item
|
|
183
|
+
if (state.undoStack.length >= 50) {
|
|
184
|
+
state.undoStack.shift();
|
|
185
|
+
}
|
|
186
|
+
if (state.isHistoryPaused) {
|
|
187
|
+
state.pausedHistory.unshift(...historyItem);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
state.undoStack.push(historyItem);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function storageDispatch(ops, reverse, modified) {
|
|
194
|
+
if (state.isBatching) {
|
|
195
|
+
state.batch.ops.push(...ops);
|
|
196
|
+
for (const item of modified) {
|
|
197
|
+
state.batch.updates.nodes.add(item);
|
|
198
|
+
}
|
|
199
|
+
state.batch.reverseOps.push(...reverse);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
addToUndoStack(reverse);
|
|
203
|
+
state.redoStack = [];
|
|
204
|
+
dispatch(ops);
|
|
205
|
+
notify({ nodes: new Set(modified) });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function notify({ nodes = new Set(), presence = false, others = [], }) {
|
|
209
|
+
if (others.length > 0) {
|
|
210
|
+
state.others = makeOthers(state.users);
|
|
211
|
+
for (const event of others) {
|
|
212
|
+
for (const listener of state.listeners["others"]) {
|
|
213
|
+
listener(state.others, event);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (presence) {
|
|
218
|
+
for (const listener of state.listeners["my-presence"]) {
|
|
219
|
+
listener(state.me);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (nodes.size > 0) {
|
|
223
|
+
for (const subscriber of state.listeners.storage) {
|
|
224
|
+
subscriber(Array.from(nodes).map((m) => {
|
|
225
|
+
if (m instanceof LiveObject_1.LiveObject) {
|
|
226
|
+
return {
|
|
227
|
+
type: "LiveObject",
|
|
228
|
+
node: m,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
else if (m instanceof LiveList_1.LiveList) {
|
|
232
|
+
return {
|
|
233
|
+
type: "LiveList",
|
|
234
|
+
node: m,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
return {
|
|
239
|
+
type: "LiveMap",
|
|
240
|
+
node: m,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function getConnectionId() {
|
|
248
|
+
if (state.connection.state === "open" ||
|
|
249
|
+
state.connection.state === "connecting") {
|
|
250
|
+
return state.connection.id;
|
|
251
|
+
}
|
|
252
|
+
throw new Error("Internal. Tried to get connection id but connection is not open");
|
|
253
|
+
}
|
|
254
|
+
function generateId() {
|
|
255
|
+
return `${getConnectionId()}:${state.clock++}`;
|
|
256
|
+
}
|
|
257
|
+
function generateOpId() {
|
|
258
|
+
return `${getConnectionId()}:${state.opClock++}`;
|
|
259
|
+
}
|
|
260
|
+
function apply(item) {
|
|
261
|
+
const result = {
|
|
262
|
+
reverse: [],
|
|
263
|
+
updates: { nodes: new Set(), presence: false },
|
|
264
|
+
};
|
|
265
|
+
for (const op of item) {
|
|
266
|
+
if (op.type === "presence") {
|
|
267
|
+
const reverse = {
|
|
268
|
+
type: "presence",
|
|
269
|
+
data: {},
|
|
270
|
+
};
|
|
271
|
+
for (const key in op.data) {
|
|
272
|
+
reverse.data[key] = state.me[key];
|
|
273
|
+
}
|
|
274
|
+
state.me = Object.assign(Object.assign({}, state.me), op.data);
|
|
275
|
+
if (state.buffer.presence == null) {
|
|
276
|
+
state.buffer.presence = op.data;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
for (const key in op.data) {
|
|
280
|
+
state.buffer.presence[key] = op.data;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
result.reverse.unshift(reverse);
|
|
284
|
+
result.updates.presence = true;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const applyOpResult = applyOp(op);
|
|
288
|
+
if (applyOpResult.modified) {
|
|
289
|
+
result.updates.nodes.add(applyOpResult.modified);
|
|
290
|
+
result.reverse.unshift(...applyOpResult.reverse);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
function applyOp(op) {
|
|
297
|
+
switch (op.type) {
|
|
298
|
+
case live_1.OpType.DeleteObjectKey:
|
|
299
|
+
case live_1.OpType.UpdateObject:
|
|
300
|
+
case live_1.OpType.DeleteCrdt: {
|
|
301
|
+
const item = state.items.get(op.id);
|
|
302
|
+
if (item == null) {
|
|
303
|
+
return { modified: false };
|
|
304
|
+
}
|
|
305
|
+
return item._apply(op);
|
|
306
|
+
}
|
|
307
|
+
case live_1.OpType.SetParentKey: {
|
|
308
|
+
const item = state.items.get(op.id);
|
|
309
|
+
if (item == null) {
|
|
310
|
+
return { modified: false };
|
|
311
|
+
}
|
|
312
|
+
if (item._parent instanceof LiveList_1.LiveList) {
|
|
313
|
+
const previousKey = item._parentKey;
|
|
314
|
+
item._parent._setChildKey(op.parentKey, item);
|
|
315
|
+
return {
|
|
316
|
+
reverse: [
|
|
317
|
+
{
|
|
318
|
+
type: live_1.OpType.SetParentKey,
|
|
319
|
+
id: item._id,
|
|
320
|
+
parentKey: previousKey,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
modified: item._parent,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return { modified: false };
|
|
327
|
+
}
|
|
328
|
+
case live_1.OpType.CreateObject: {
|
|
329
|
+
const parent = state.items.get(op.parentId);
|
|
330
|
+
if (parent == null || getItem(op.id) != null) {
|
|
331
|
+
return { modified: false };
|
|
332
|
+
}
|
|
333
|
+
return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data));
|
|
334
|
+
}
|
|
335
|
+
case live_1.OpType.CreateList: {
|
|
336
|
+
const parent = state.items.get(op.parentId);
|
|
337
|
+
if (parent == null || getItem(op.id) != null) {
|
|
338
|
+
return { modified: false };
|
|
339
|
+
}
|
|
340
|
+
return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList());
|
|
341
|
+
}
|
|
342
|
+
case live_1.OpType.CreateRegister: {
|
|
343
|
+
const parent = state.items.get(op.parentId);
|
|
344
|
+
if (parent == null || getItem(op.id) != null) {
|
|
345
|
+
return { modified: false };
|
|
346
|
+
}
|
|
347
|
+
return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data));
|
|
348
|
+
}
|
|
349
|
+
case live_1.OpType.CreateMap: {
|
|
350
|
+
const parent = state.items.get(op.parentId);
|
|
351
|
+
if (parent == null || getItem(op.id) != null) {
|
|
352
|
+
return { modified: false };
|
|
353
|
+
}
|
|
354
|
+
return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap());
|
|
355
|
+
}
|
|
113
356
|
}
|
|
114
|
-
|
|
357
|
+
return { modified: false };
|
|
358
|
+
}
|
|
359
|
+
function subscribe(firstParam, listener, options) {
|
|
360
|
+
if (firstParam instanceof AbstractCrdt_1.AbstractCrdt) {
|
|
361
|
+
return crdtSubscribe(firstParam, listener, options);
|
|
362
|
+
}
|
|
363
|
+
else if (typeof firstParam === "function") {
|
|
364
|
+
return genericSubscribe(firstParam);
|
|
365
|
+
}
|
|
366
|
+
else if (!isValidRoomEventType(firstParam)) {
|
|
367
|
+
throw new Error(`"${firstParam}" is not a valid event name`);
|
|
368
|
+
}
|
|
369
|
+
state.listeners[firstParam].push(listener);
|
|
370
|
+
return () => {
|
|
371
|
+
const callbacks = state.listeners[firstParam];
|
|
372
|
+
(0, utils_1.remove)(callbacks, listener);
|
|
373
|
+
};
|
|
115
374
|
}
|
|
116
375
|
function unsubscribe(event, callback) {
|
|
376
|
+
console.warn(`unsubscribe is depreacted and will be removed in a future version.
|
|
377
|
+
use the callback returned by subscribe instead.
|
|
378
|
+
See v0.13 release notes for more information.
|
|
379
|
+
`);
|
|
117
380
|
if (!isValidRoomEventType(event)) {
|
|
118
381
|
throw new Error(`"${event}" is not a valid event name`);
|
|
119
382
|
}
|
|
@@ -145,20 +408,28 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
145
408
|
updateConnection({ state: "authenticating" });
|
|
146
409
|
effects.authenticate();
|
|
147
410
|
}
|
|
148
|
-
function updatePresence(overrides) {
|
|
149
|
-
const
|
|
150
|
-
if (state.
|
|
151
|
-
state.
|
|
411
|
+
function updatePresence(overrides, options) {
|
|
412
|
+
const oldValues = {};
|
|
413
|
+
if (state.buffer.presence == null) {
|
|
414
|
+
state.buffer.presence = {};
|
|
152
415
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
416
|
+
for (const key in overrides) {
|
|
417
|
+
state.buffer.presence[key] = overrides[key];
|
|
418
|
+
oldValues[key] = state.me[key];
|
|
419
|
+
}
|
|
420
|
+
state.me = Object.assign(Object.assign({}, state.me), overrides);
|
|
421
|
+
if (state.isBatching) {
|
|
422
|
+
if (options === null || options === void 0 ? void 0 : options.addToHistory) {
|
|
423
|
+
state.batch.reverseOps.push({ type: "presence", data: oldValues });
|
|
156
424
|
}
|
|
425
|
+
state.batch.updates.presence = true;
|
|
157
426
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
427
|
+
else {
|
|
428
|
+
tryFlushing();
|
|
429
|
+
if (options === null || options === void 0 ? void 0 : options.addToHistory) {
|
|
430
|
+
addToUndoStack([{ type: "presence", data: oldValues }]);
|
|
431
|
+
}
|
|
432
|
+
notify({ presence: true });
|
|
162
433
|
}
|
|
163
434
|
}
|
|
164
435
|
function authenticationSuccess(token, socket) {
|
|
@@ -199,25 +470,20 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
199
470
|
presence: Object.assign(Object.assign({}, user.presence), message.data),
|
|
200
471
|
};
|
|
201
472
|
}
|
|
202
|
-
|
|
473
|
+
return {
|
|
203
474
|
type: "update",
|
|
204
475
|
updates: message.data,
|
|
205
476
|
user: state.users[message.actor],
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function updateUsers(event) {
|
|
209
|
-
state.others = makeOthers(state.users);
|
|
210
|
-
for (const listener of state.listeners["others"]) {
|
|
211
|
-
listener(state.others, event);
|
|
212
|
-
}
|
|
477
|
+
};
|
|
213
478
|
}
|
|
214
479
|
function onUserLeftMessage(message) {
|
|
215
480
|
const userLeftMessage = message;
|
|
216
481
|
const user = state.users[userLeftMessage.actor];
|
|
217
482
|
if (user) {
|
|
218
483
|
delete state.users[userLeftMessage.actor];
|
|
219
|
-
|
|
484
|
+
return { type: "leave", user };
|
|
220
485
|
}
|
|
486
|
+
return null;
|
|
221
487
|
}
|
|
222
488
|
function onRoomStateMessage(message) {
|
|
223
489
|
const newUsers = {};
|
|
@@ -231,7 +497,7 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
231
497
|
};
|
|
232
498
|
}
|
|
233
499
|
state.users = newUsers;
|
|
234
|
-
|
|
500
|
+
return { type: "reset" };
|
|
235
501
|
}
|
|
236
502
|
function onNavigatorOnline() {
|
|
237
503
|
if (state.connection.state === "unavailable") {
|
|
@@ -250,17 +516,17 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
250
516
|
info: message.info,
|
|
251
517
|
id: message.id,
|
|
252
518
|
};
|
|
253
|
-
updateUsers({ type: "enter", user: state.users[message.actor] });
|
|
254
519
|
if (state.me) {
|
|
255
520
|
// Send current presence to new user
|
|
256
521
|
// TODO: Consider storing it on the backend
|
|
257
|
-
state.
|
|
522
|
+
state.buffer.messages.push({
|
|
258
523
|
type: live_1.ClientMessageType.UpdatePresence,
|
|
259
524
|
data: state.me,
|
|
260
525
|
targetActor: message.actor,
|
|
261
526
|
});
|
|
262
527
|
tryFlushing();
|
|
263
528
|
}
|
|
529
|
+
return { type: "enter", user: state.users[message.actor] };
|
|
264
530
|
}
|
|
265
531
|
function onMessage(event) {
|
|
266
532
|
if (event.data === "pong") {
|
|
@@ -268,29 +534,57 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
268
534
|
return;
|
|
269
535
|
}
|
|
270
536
|
const message = JSON.parse(event.data);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
537
|
+
let subMessages = [];
|
|
538
|
+
if (Array.isArray(message)) {
|
|
539
|
+
subMessages = message;
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
subMessages.push(message);
|
|
543
|
+
}
|
|
544
|
+
const updates = {
|
|
545
|
+
nodes: new Set(),
|
|
546
|
+
others: [],
|
|
547
|
+
};
|
|
548
|
+
for (const subMessage of subMessages) {
|
|
549
|
+
switch (subMessage.type) {
|
|
550
|
+
case live_1.ServerMessageType.UserJoined: {
|
|
551
|
+
updates.others.push(onUserJoinedMessage(message));
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case live_1.ServerMessageType.UpdatePresence: {
|
|
555
|
+
updates.others.push(onUpdatePresenceMessage(subMessage));
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case live_1.ServerMessageType.Event: {
|
|
559
|
+
onEvent(subMessage);
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case live_1.ServerMessageType.UserLeft: {
|
|
563
|
+
const event = onUserLeftMessage(subMessage);
|
|
564
|
+
if (event) {
|
|
565
|
+
updates.others.push(event);
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case live_1.ServerMessageType.RoomState: {
|
|
570
|
+
updates.others.push(onRoomStateMessage(subMessage));
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
case live_1.ServerMessageType.InitialStorageState: {
|
|
574
|
+
createRootFromMessage(subMessage);
|
|
575
|
+
_getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
case live_1.ServerMessageType.UpdateStorage: {
|
|
579
|
+
const applyResult = apply(subMessage.ops);
|
|
580
|
+
for (const node of applyResult.updates.nodes) {
|
|
581
|
+
updates.nodes.add(node);
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
291
585
|
}
|
|
292
586
|
}
|
|
293
|
-
|
|
587
|
+
notify(updates);
|
|
294
588
|
}
|
|
295
589
|
// function onWakeUp() {
|
|
296
590
|
// // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example)
|
|
@@ -310,7 +604,7 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
310
604
|
}
|
|
311
605
|
clearTimeout(state.timeoutHandles.reconnect);
|
|
312
606
|
state.users = {};
|
|
313
|
-
|
|
607
|
+
notify({ others: [{ type: "reset" }] });
|
|
314
608
|
if (event.code >= 4000 && event.code <= 4100) {
|
|
315
609
|
updateConnection({ state: "failed" });
|
|
316
610
|
const error = new LiveblocksError(event.reason, event.code);
|
|
@@ -399,7 +693,7 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
399
693
|
return;
|
|
400
694
|
}
|
|
401
695
|
effects.send(messages);
|
|
402
|
-
state.
|
|
696
|
+
state.buffer = {
|
|
403
697
|
messages: [],
|
|
404
698
|
storageOperations: [],
|
|
405
699
|
presence: null,
|
|
@@ -415,19 +709,19 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
415
709
|
}
|
|
416
710
|
function flushDataToMessages(state) {
|
|
417
711
|
const messages = [];
|
|
418
|
-
if (state.
|
|
712
|
+
if (state.buffer.presence) {
|
|
419
713
|
messages.push({
|
|
420
714
|
type: live_1.ClientMessageType.UpdatePresence,
|
|
421
|
-
data: state.
|
|
715
|
+
data: state.buffer.presence,
|
|
422
716
|
});
|
|
423
717
|
}
|
|
424
|
-
for (const event of state.
|
|
718
|
+
for (const event of state.buffer.messages) {
|
|
425
719
|
messages.push(event);
|
|
426
720
|
}
|
|
427
|
-
if (state.
|
|
721
|
+
if (state.buffer.storageOperations.length > 0) {
|
|
428
722
|
messages.push({
|
|
429
723
|
type: live_1.ClientMessageType.UpdateStorage,
|
|
430
|
-
ops: state.
|
|
724
|
+
ops: state.buffer.storageOperations,
|
|
431
725
|
});
|
|
432
726
|
}
|
|
433
727
|
return messages;
|
|
@@ -449,7 +743,7 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
449
743
|
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
450
744
|
clearInterval(state.intervalHandles.heartbeat);
|
|
451
745
|
state.users = {};
|
|
452
|
-
|
|
746
|
+
notify({ others: [{ type: "reset" }] });
|
|
453
747
|
clearListeners();
|
|
454
748
|
}
|
|
455
749
|
function clearListeners() {
|
|
@@ -467,44 +761,115 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
467
761
|
if (state.socket == null) {
|
|
468
762
|
return;
|
|
469
763
|
}
|
|
470
|
-
state.
|
|
764
|
+
state.buffer.messages.push({
|
|
471
765
|
type: live_1.ClientMessageType.ClientEvent,
|
|
472
766
|
event,
|
|
473
767
|
});
|
|
474
768
|
tryFlushing();
|
|
475
769
|
}
|
|
476
770
|
function dispatch(ops) {
|
|
477
|
-
state.
|
|
771
|
+
state.buffer.storageOperations.push(...ops);
|
|
478
772
|
tryFlushing();
|
|
479
773
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
state.flushData.messages.push({ type: live_1.ClientMessageType.FetchStorage });
|
|
483
|
-
tryFlushing();
|
|
484
|
-
},
|
|
485
|
-
dispatch,
|
|
486
|
-
getConnectionId: () => {
|
|
487
|
-
const me = getSelf();
|
|
488
|
-
if (me) {
|
|
489
|
-
return me.connectionId;
|
|
490
|
-
}
|
|
491
|
-
throw new Error("Unexpected");
|
|
492
|
-
},
|
|
493
|
-
defaultRoot: state.defaultStorageRoot,
|
|
494
|
-
});
|
|
774
|
+
let _getInitialStatePromise = null;
|
|
775
|
+
let _getInitialStateResolver = null;
|
|
495
776
|
function getStorage() {
|
|
496
777
|
return __awaiter(this, void 0, void 0, function* () {
|
|
497
|
-
|
|
778
|
+
if (state.root) {
|
|
779
|
+
return {
|
|
780
|
+
root: state.root,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
if (_getInitialStatePromise == null) {
|
|
784
|
+
state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage });
|
|
785
|
+
tryFlushing();
|
|
786
|
+
_getInitialStatePromise = new Promise((resolve) => (_getInitialStateResolver = resolve));
|
|
787
|
+
}
|
|
788
|
+
yield _getInitialStatePromise;
|
|
498
789
|
return {
|
|
499
|
-
root:
|
|
790
|
+
root: state.root,
|
|
500
791
|
};
|
|
501
792
|
});
|
|
502
793
|
}
|
|
503
794
|
function undo() {
|
|
504
|
-
|
|
795
|
+
if (state.isBatching) {
|
|
796
|
+
throw new Error("undo is not allowed during a batch");
|
|
797
|
+
}
|
|
798
|
+
const historyItem = state.undoStack.pop();
|
|
799
|
+
if (historyItem == null) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
state.isHistoryPaused = false;
|
|
803
|
+
const result = apply(historyItem);
|
|
804
|
+
notify(result.updates);
|
|
805
|
+
state.redoStack.push(result.reverse);
|
|
806
|
+
for (const op of historyItem) {
|
|
807
|
+
if (op.type !== "presence") {
|
|
808
|
+
state.buffer.storageOperations.push(op);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
tryFlushing();
|
|
505
812
|
}
|
|
506
813
|
function redo() {
|
|
507
|
-
|
|
814
|
+
if (state.isBatching) {
|
|
815
|
+
throw new Error("redo is not allowed during a batch");
|
|
816
|
+
}
|
|
817
|
+
const historyItem = state.redoStack.pop();
|
|
818
|
+
if (historyItem == null) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
state.isHistoryPaused = false;
|
|
822
|
+
const result = apply(historyItem);
|
|
823
|
+
notify(result.updates);
|
|
824
|
+
state.undoStack.push(result.reverse);
|
|
825
|
+
for (const op of historyItem) {
|
|
826
|
+
if (op.type !== "presence") {
|
|
827
|
+
state.buffer.storageOperations.push(op);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
tryFlushing();
|
|
831
|
+
}
|
|
832
|
+
function batch(callback) {
|
|
833
|
+
if (state.isBatching) {
|
|
834
|
+
throw new Error("batch should not be called during a batch");
|
|
835
|
+
}
|
|
836
|
+
state.isBatching = true;
|
|
837
|
+
try {
|
|
838
|
+
callback();
|
|
839
|
+
}
|
|
840
|
+
finally {
|
|
841
|
+
state.isBatching = false;
|
|
842
|
+
if (state.batch.reverseOps.length > 0) {
|
|
843
|
+
addToUndoStack(state.batch.reverseOps);
|
|
844
|
+
}
|
|
845
|
+
// Clear the redo stack because batch is always called from a local operation
|
|
846
|
+
state.redoStack = [];
|
|
847
|
+
if (state.batch.ops.length > 0) {
|
|
848
|
+
dispatch(state.batch.ops);
|
|
849
|
+
}
|
|
850
|
+
notify(state.batch.updates);
|
|
851
|
+
state.batch = {
|
|
852
|
+
ops: [],
|
|
853
|
+
reverseOps: [],
|
|
854
|
+
updates: {
|
|
855
|
+
others: [],
|
|
856
|
+
nodes: new Set(),
|
|
857
|
+
presence: false,
|
|
858
|
+
},
|
|
859
|
+
};
|
|
860
|
+
tryFlushing();
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function pauseHistory() {
|
|
864
|
+
state.pausedHistory = [];
|
|
865
|
+
state.isHistoryPaused = true;
|
|
866
|
+
}
|
|
867
|
+
function resumeHistory() {
|
|
868
|
+
state.isHistoryPaused = false;
|
|
869
|
+
if (state.pausedHistory.length > 0) {
|
|
870
|
+
addToUndoStack(state.pausedHistory);
|
|
871
|
+
}
|
|
872
|
+
state.pausedHistory = [];
|
|
508
873
|
}
|
|
509
874
|
return {
|
|
510
875
|
// Internal
|
|
@@ -516,6 +881,8 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
516
881
|
onNavigatorOnline,
|
|
517
882
|
// onWakeUp,
|
|
518
883
|
onVisibilityChange,
|
|
884
|
+
getUndoStack: () => state.undoStack,
|
|
885
|
+
getItemsCount: () => state.items.size,
|
|
519
886
|
// Core
|
|
520
887
|
connect,
|
|
521
888
|
disconnect,
|
|
@@ -524,8 +891,11 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
524
891
|
// Presence
|
|
525
892
|
updatePresence,
|
|
526
893
|
broadcastEvent,
|
|
894
|
+
batch,
|
|
527
895
|
undo,
|
|
528
896
|
redo,
|
|
897
|
+
pauseHistory,
|
|
898
|
+
resumeHistory,
|
|
529
899
|
getStorage,
|
|
530
900
|
selectors: {
|
|
531
901
|
// Core
|
|
@@ -548,6 +918,7 @@ function defaultState(me, defaultStorageRoot) {
|
|
|
548
918
|
"my-presence": [],
|
|
549
919
|
error: [],
|
|
550
920
|
connection: [],
|
|
921
|
+
storage: [],
|
|
551
922
|
},
|
|
552
923
|
numberOfRetry: 0,
|
|
553
924
|
lastFlushTime: 0,
|
|
@@ -556,7 +927,7 @@ function defaultState(me, defaultStorageRoot) {
|
|
|
556
927
|
reconnect: 0,
|
|
557
928
|
pongTimeout: 0,
|
|
558
929
|
},
|
|
559
|
-
|
|
930
|
+
buffer: {
|
|
560
931
|
presence: me == null ? {} : me,
|
|
561
932
|
messages: [],
|
|
562
933
|
storageOperations: [],
|
|
@@ -569,12 +940,27 @@ function defaultState(me, defaultStorageRoot) {
|
|
|
569
940
|
others: makeOthers({}),
|
|
570
941
|
defaultStorageRoot,
|
|
571
942
|
idFactory: null,
|
|
943
|
+
// Storage
|
|
944
|
+
clock: 0,
|
|
945
|
+
opClock: 0,
|
|
946
|
+
items: new Map(),
|
|
947
|
+
root: undefined,
|
|
948
|
+
undoStack: [],
|
|
949
|
+
redoStack: [],
|
|
950
|
+
isHistoryPaused: false,
|
|
951
|
+
pausedHistory: [],
|
|
952
|
+
isBatching: false,
|
|
953
|
+
batch: {
|
|
954
|
+
ops: [],
|
|
955
|
+
updates: { nodes: new Set(), presence: false, others: [] },
|
|
956
|
+
reverseOps: [],
|
|
957
|
+
},
|
|
572
958
|
};
|
|
573
959
|
}
|
|
574
960
|
exports.defaultState = defaultState;
|
|
575
961
|
function createRoom(name, options) {
|
|
576
962
|
const throttleDelay = options.throttle || 100;
|
|
577
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/
|
|
963
|
+
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
|
|
578
964
|
let authEndpoint;
|
|
579
965
|
if (options.authEndpoint) {
|
|
580
966
|
authEndpoint = options.authEndpoint;
|
|
@@ -608,8 +994,13 @@ function createRoom(name, options) {
|
|
|
608
994
|
getOthers: machine.selectors.getOthers,
|
|
609
995
|
broadcastEvent: machine.broadcastEvent,
|
|
610
996
|
getStorage: machine.getStorage,
|
|
611
|
-
|
|
612
|
-
|
|
997
|
+
batch: machine.batch,
|
|
998
|
+
history: {
|
|
999
|
+
undo: machine.undo,
|
|
1000
|
+
redo: machine.redo,
|
|
1001
|
+
pause: machine.pauseHistory,
|
|
1002
|
+
resume: machine.resumeHistory,
|
|
1003
|
+
},
|
|
613
1004
|
};
|
|
614
1005
|
return {
|
|
615
1006
|
connect: machine.connect,
|