@liveblocks/client 0.12.3 → 0.13.2
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/immutable/index.d.ts +7 -0
- package/lib/cjs/immutable/index.js +214 -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 +477 -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/immutable/index.d.ts +7 -0
- package/lib/esm/immutable/index.js +207 -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 +479 -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,11 +604,12 @@ 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);
|
|
317
611
|
for (const listener of state.listeners.error) {
|
|
612
|
+
console.error(`Liveblocks WebSocket connection closed. Reason: ${error.message} (code: ${error.code})`);
|
|
318
613
|
listener(error);
|
|
319
614
|
}
|
|
320
615
|
}
|
|
@@ -399,7 +694,7 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
399
694
|
return;
|
|
400
695
|
}
|
|
401
696
|
effects.send(messages);
|
|
402
|
-
state.
|
|
697
|
+
state.buffer = {
|
|
403
698
|
messages: [],
|
|
404
699
|
storageOperations: [],
|
|
405
700
|
presence: null,
|
|
@@ -415,19 +710,19 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
415
710
|
}
|
|
416
711
|
function flushDataToMessages(state) {
|
|
417
712
|
const messages = [];
|
|
418
|
-
if (state.
|
|
713
|
+
if (state.buffer.presence) {
|
|
419
714
|
messages.push({
|
|
420
715
|
type: live_1.ClientMessageType.UpdatePresence,
|
|
421
|
-
data: state.
|
|
716
|
+
data: state.buffer.presence,
|
|
422
717
|
});
|
|
423
718
|
}
|
|
424
|
-
for (const event of state.
|
|
719
|
+
for (const event of state.buffer.messages) {
|
|
425
720
|
messages.push(event);
|
|
426
721
|
}
|
|
427
|
-
if (state.
|
|
722
|
+
if (state.buffer.storageOperations.length > 0) {
|
|
428
723
|
messages.push({
|
|
429
724
|
type: live_1.ClientMessageType.UpdateStorage,
|
|
430
|
-
ops: state.
|
|
725
|
+
ops: state.buffer.storageOperations,
|
|
431
726
|
});
|
|
432
727
|
}
|
|
433
728
|
return messages;
|
|
@@ -449,7 +744,7 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
449
744
|
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
450
745
|
clearInterval(state.intervalHandles.heartbeat);
|
|
451
746
|
state.users = {};
|
|
452
|
-
|
|
747
|
+
notify({ others: [{ type: "reset" }] });
|
|
453
748
|
clearListeners();
|
|
454
749
|
}
|
|
455
750
|
function clearListeners() {
|
|
@@ -467,44 +762,115 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
467
762
|
if (state.socket == null) {
|
|
468
763
|
return;
|
|
469
764
|
}
|
|
470
|
-
state.
|
|
765
|
+
state.buffer.messages.push({
|
|
471
766
|
type: live_1.ClientMessageType.ClientEvent,
|
|
472
767
|
event,
|
|
473
768
|
});
|
|
474
769
|
tryFlushing();
|
|
475
770
|
}
|
|
476
771
|
function dispatch(ops) {
|
|
477
|
-
state.
|
|
772
|
+
state.buffer.storageOperations.push(...ops);
|
|
478
773
|
tryFlushing();
|
|
479
774
|
}
|
|
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
|
-
});
|
|
775
|
+
let _getInitialStatePromise = null;
|
|
776
|
+
let _getInitialStateResolver = null;
|
|
495
777
|
function getStorage() {
|
|
496
778
|
return __awaiter(this, void 0, void 0, function* () {
|
|
497
|
-
|
|
779
|
+
if (state.root) {
|
|
780
|
+
return {
|
|
781
|
+
root: state.root,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
if (_getInitialStatePromise == null) {
|
|
785
|
+
state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage });
|
|
786
|
+
tryFlushing();
|
|
787
|
+
_getInitialStatePromise = new Promise((resolve) => (_getInitialStateResolver = resolve));
|
|
788
|
+
}
|
|
789
|
+
yield _getInitialStatePromise;
|
|
498
790
|
return {
|
|
499
|
-
root:
|
|
791
|
+
root: state.root,
|
|
500
792
|
};
|
|
501
793
|
});
|
|
502
794
|
}
|
|
503
795
|
function undo() {
|
|
504
|
-
|
|
796
|
+
if (state.isBatching) {
|
|
797
|
+
throw new Error("undo is not allowed during a batch");
|
|
798
|
+
}
|
|
799
|
+
const historyItem = state.undoStack.pop();
|
|
800
|
+
if (historyItem == null) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
state.isHistoryPaused = false;
|
|
804
|
+
const result = apply(historyItem);
|
|
805
|
+
notify(result.updates);
|
|
806
|
+
state.redoStack.push(result.reverse);
|
|
807
|
+
for (const op of historyItem) {
|
|
808
|
+
if (op.type !== "presence") {
|
|
809
|
+
state.buffer.storageOperations.push(op);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
tryFlushing();
|
|
505
813
|
}
|
|
506
814
|
function redo() {
|
|
507
|
-
|
|
815
|
+
if (state.isBatching) {
|
|
816
|
+
throw new Error("redo is not allowed during a batch");
|
|
817
|
+
}
|
|
818
|
+
const historyItem = state.redoStack.pop();
|
|
819
|
+
if (historyItem == null) {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
state.isHistoryPaused = false;
|
|
823
|
+
const result = apply(historyItem);
|
|
824
|
+
notify(result.updates);
|
|
825
|
+
state.undoStack.push(result.reverse);
|
|
826
|
+
for (const op of historyItem) {
|
|
827
|
+
if (op.type !== "presence") {
|
|
828
|
+
state.buffer.storageOperations.push(op);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
tryFlushing();
|
|
832
|
+
}
|
|
833
|
+
function batch(callback) {
|
|
834
|
+
if (state.isBatching) {
|
|
835
|
+
throw new Error("batch should not be called during a batch");
|
|
836
|
+
}
|
|
837
|
+
state.isBatching = true;
|
|
838
|
+
try {
|
|
839
|
+
callback();
|
|
840
|
+
}
|
|
841
|
+
finally {
|
|
842
|
+
state.isBatching = false;
|
|
843
|
+
if (state.batch.reverseOps.length > 0) {
|
|
844
|
+
addToUndoStack(state.batch.reverseOps);
|
|
845
|
+
}
|
|
846
|
+
// Clear the redo stack because batch is always called from a local operation
|
|
847
|
+
state.redoStack = [];
|
|
848
|
+
if (state.batch.ops.length > 0) {
|
|
849
|
+
dispatch(state.batch.ops);
|
|
850
|
+
}
|
|
851
|
+
notify(state.batch.updates);
|
|
852
|
+
state.batch = {
|
|
853
|
+
ops: [],
|
|
854
|
+
reverseOps: [],
|
|
855
|
+
updates: {
|
|
856
|
+
others: [],
|
|
857
|
+
nodes: new Set(),
|
|
858
|
+
presence: false,
|
|
859
|
+
},
|
|
860
|
+
};
|
|
861
|
+
tryFlushing();
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function pauseHistory() {
|
|
865
|
+
state.pausedHistory = [];
|
|
866
|
+
state.isHistoryPaused = true;
|
|
867
|
+
}
|
|
868
|
+
function resumeHistory() {
|
|
869
|
+
state.isHistoryPaused = false;
|
|
870
|
+
if (state.pausedHistory.length > 0) {
|
|
871
|
+
addToUndoStack(state.pausedHistory);
|
|
872
|
+
}
|
|
873
|
+
state.pausedHistory = [];
|
|
508
874
|
}
|
|
509
875
|
return {
|
|
510
876
|
// Internal
|
|
@@ -516,6 +882,8 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
516
882
|
onNavigatorOnline,
|
|
517
883
|
// onWakeUp,
|
|
518
884
|
onVisibilityChange,
|
|
885
|
+
getUndoStack: () => state.undoStack,
|
|
886
|
+
getItemsCount: () => state.items.size,
|
|
519
887
|
// Core
|
|
520
888
|
connect,
|
|
521
889
|
disconnect,
|
|
@@ -524,8 +892,11 @@ function makeStateMachine(state, context, mockedEffects) {
|
|
|
524
892
|
// Presence
|
|
525
893
|
updatePresence,
|
|
526
894
|
broadcastEvent,
|
|
895
|
+
batch,
|
|
527
896
|
undo,
|
|
528
897
|
redo,
|
|
898
|
+
pauseHistory,
|
|
899
|
+
resumeHistory,
|
|
529
900
|
getStorage,
|
|
530
901
|
selectors: {
|
|
531
902
|
// Core
|
|
@@ -548,6 +919,7 @@ function defaultState(me, defaultStorageRoot) {
|
|
|
548
919
|
"my-presence": [],
|
|
549
920
|
error: [],
|
|
550
921
|
connection: [],
|
|
922
|
+
storage: [],
|
|
551
923
|
},
|
|
552
924
|
numberOfRetry: 0,
|
|
553
925
|
lastFlushTime: 0,
|
|
@@ -556,7 +928,7 @@ function defaultState(me, defaultStorageRoot) {
|
|
|
556
928
|
reconnect: 0,
|
|
557
929
|
pongTimeout: 0,
|
|
558
930
|
},
|
|
559
|
-
|
|
931
|
+
buffer: {
|
|
560
932
|
presence: me == null ? {} : me,
|
|
561
933
|
messages: [],
|
|
562
934
|
storageOperations: [],
|
|
@@ -569,12 +941,27 @@ function defaultState(me, defaultStorageRoot) {
|
|
|
569
941
|
others: makeOthers({}),
|
|
570
942
|
defaultStorageRoot,
|
|
571
943
|
idFactory: null,
|
|
944
|
+
// Storage
|
|
945
|
+
clock: 0,
|
|
946
|
+
opClock: 0,
|
|
947
|
+
items: new Map(),
|
|
948
|
+
root: undefined,
|
|
949
|
+
undoStack: [],
|
|
950
|
+
redoStack: [],
|
|
951
|
+
isHistoryPaused: false,
|
|
952
|
+
pausedHistory: [],
|
|
953
|
+
isBatching: false,
|
|
954
|
+
batch: {
|
|
955
|
+
ops: [],
|
|
956
|
+
updates: { nodes: new Set(), presence: false, others: [] },
|
|
957
|
+
reverseOps: [],
|
|
958
|
+
},
|
|
572
959
|
};
|
|
573
960
|
}
|
|
574
961
|
exports.defaultState = defaultState;
|
|
575
962
|
function createRoom(name, options) {
|
|
576
963
|
const throttleDelay = options.throttle || 100;
|
|
577
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/
|
|
964
|
+
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
|
|
578
965
|
let authEndpoint;
|
|
579
966
|
if (options.authEndpoint) {
|
|
580
967
|
authEndpoint = options.authEndpoint;
|
|
@@ -608,8 +995,13 @@ function createRoom(name, options) {
|
|
|
608
995
|
getOthers: machine.selectors.getOthers,
|
|
609
996
|
broadcastEvent: machine.broadcastEvent,
|
|
610
997
|
getStorage: machine.getStorage,
|
|
611
|
-
|
|
612
|
-
|
|
998
|
+
batch: machine.batch,
|
|
999
|
+
history: {
|
|
1000
|
+
undo: machine.undo,
|
|
1001
|
+
redo: machine.redo,
|
|
1002
|
+
pause: machine.pauseHistory,
|
|
1003
|
+
resume: machine.resumeHistory,
|
|
1004
|
+
},
|
|
613
1005
|
};
|
|
614
1006
|
return {
|
|
615
1007
|
connect: machine.connect,
|