@liveblocks/client 0.15.0-alpha.2 → 0.15.0

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.
Files changed (62) hide show
  1. package/lib/esm/index.js +2991 -5
  2. package/lib/esm/index.mjs +2991 -0
  3. package/lib/esm/internal.js +149 -0
  4. package/lib/esm/internal.mjs +149 -0
  5. package/lib/{esm/types.d.ts → index.d.ts} +255 -50
  6. package/lib/index.js +4237 -0
  7. package/lib/{esm/live.d.ts → internal.d.ts} +46 -34
  8. package/lib/internal.js +193 -0
  9. package/package.json +36 -9
  10. package/lib/cjs/AbstractCrdt.d.ts +0 -67
  11. package/lib/cjs/AbstractCrdt.js +0 -95
  12. package/lib/cjs/LiveList.d.ts +0 -144
  13. package/lib/cjs/LiveList.js +0 -527
  14. package/lib/cjs/LiveMap.d.ts +0 -91
  15. package/lib/cjs/LiveMap.js +0 -322
  16. package/lib/cjs/LiveObject.d.ts +0 -80
  17. package/lib/cjs/LiveObject.js +0 -453
  18. package/lib/cjs/LiveRegister.d.ts +0 -29
  19. package/lib/cjs/LiveRegister.js +0 -88
  20. package/lib/cjs/authentication.d.ts +0 -3
  21. package/lib/cjs/authentication.js +0 -71
  22. package/lib/cjs/client.d.ts +0 -27
  23. package/lib/cjs/client.js +0 -80
  24. package/lib/cjs/immutable.d.ts +0 -9
  25. package/lib/cjs/immutable.js +0 -291
  26. package/lib/cjs/index.d.ts +0 -6
  27. package/lib/cjs/index.js +0 -18
  28. package/lib/cjs/live.d.ts +0 -181
  29. package/lib/cjs/live.js +0 -49
  30. package/lib/cjs/position.d.ts +0 -6
  31. package/lib/cjs/position.js +0 -113
  32. package/lib/cjs/room.d.ts +0 -159
  33. package/lib/cjs/room.js +0 -1094
  34. package/lib/cjs/types.d.ts +0 -489
  35. package/lib/cjs/types.js +0 -2
  36. package/lib/cjs/utils.d.ts +0 -11
  37. package/lib/cjs/utils.js +0 -175
  38. package/lib/esm/AbstractCrdt.d.ts +0 -67
  39. package/lib/esm/AbstractCrdt.js +0 -91
  40. package/lib/esm/LiveList.d.ts +0 -144
  41. package/lib/esm/LiveList.js +0 -523
  42. package/lib/esm/LiveMap.d.ts +0 -91
  43. package/lib/esm/LiveMap.js +0 -318
  44. package/lib/esm/LiveObject.d.ts +0 -80
  45. package/lib/esm/LiveObject.js +0 -449
  46. package/lib/esm/LiveRegister.d.ts +0 -29
  47. package/lib/esm/LiveRegister.js +0 -84
  48. package/lib/esm/authentication.d.ts +0 -3
  49. package/lib/esm/authentication.js +0 -66
  50. package/lib/esm/client.d.ts +0 -27
  51. package/lib/esm/client.js +0 -76
  52. package/lib/esm/immutable.d.ts +0 -9
  53. package/lib/esm/immutable.js +0 -282
  54. package/lib/esm/index.d.ts +0 -6
  55. package/lib/esm/live.js +0 -46
  56. package/lib/esm/position.d.ts +0 -6
  57. package/lib/esm/position.js +0 -106
  58. package/lib/esm/room.d.ts +0 -159
  59. package/lib/esm/room.js +0 -1069
  60. package/lib/esm/types.js +0 -1
  61. package/lib/esm/utils.d.ts +0 -11
  62. package/lib/esm/utils.js +0 -164
package/lib/cjs/room.js DELETED
@@ -1,1094 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
- return new (P || (P = Promise))(function (resolve, reject) {
24
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
- step((generator = generator.apply(thisArg, _arguments || [])).next());
28
- });
29
- };
30
- Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.createRoom = exports.defaultState = exports.makeStateMachine = void 0;
32
- const utils_1 = require("./utils");
33
- const authentication_1 = __importStar(require("./authentication"));
34
- const live_1 = require("./live");
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");
40
- const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];
41
- const HEARTBEAT_INTERVAL = 30000;
42
- // const WAKE_UP_CHECK_INTERVAL = 2000;
43
- const PONG_TIMEOUT = 2000;
44
- function isValidRoomEventType(value) {
45
- return (value === "my-presence" ||
46
- value === "others" ||
47
- value === "event" ||
48
- value === "error" ||
49
- value === "connection");
50
- }
51
- function makeIdFactory(connectionId) {
52
- let count = 0;
53
- return () => `${connectionId}:${count++}`;
54
- }
55
- function makeOthers(presenceMap) {
56
- const array = Object.values(presenceMap);
57
- return {
58
- get count() {
59
- return array.length;
60
- },
61
- [Symbol.iterator]() {
62
- return array[Symbol.iterator]();
63
- },
64
- map(callback) {
65
- return array.map(callback);
66
- },
67
- toArray() {
68
- return array;
69
- },
70
- };
71
- }
72
- function log(...params) {
73
- return;
74
- console.log(...params, new Date().toString());
75
- }
76
- function makeStateMachine(state, context, mockedEffects) {
77
- const effects = mockedEffects || {
78
- authenticate() {
79
- return __awaiter(this, void 0, void 0, function* () {
80
- try {
81
- const token = yield (0, authentication_1.default)(context.authEndpoint, context.room, context.publicApiKey);
82
- const parsedToken = (0, authentication_1.parseToken)(token);
83
- const socket = new WebSocket(`${context.liveblocksServer}/?token=${token}`);
84
- socket.addEventListener("message", onMessage);
85
- socket.addEventListener("open", onOpen);
86
- socket.addEventListener("close", onClose);
87
- socket.addEventListener("error", onError);
88
- authenticationSuccess(parsedToken, socket);
89
- }
90
- catch (er) {
91
- authenticationFailure(er);
92
- }
93
- });
94
- },
95
- send(messageOrMessages) {
96
- if (state.socket == null) {
97
- throw new Error("Can't send message if socket is null");
98
- }
99
- state.socket.send(JSON.stringify(messageOrMessages));
100
- },
101
- delayFlush(delay) {
102
- return setTimeout(tryFlushing, delay);
103
- },
104
- startHeartbeatInterval() {
105
- return setInterval(heartbeat, HEARTBEAT_INTERVAL);
106
- },
107
- schedulePongTimeout() {
108
- return setTimeout(pongTimeout, PONG_TIMEOUT);
109
- },
110
- scheduleReconnect(delay) {
111
- return setTimeout(connect, delay);
112
- },
113
- };
114
- function genericSubscribe(callback) {
115
- state.listeners.storage.push(callback);
116
- return () => (0, utils_1.remove)(state.listeners.storage, callback);
117
- }
118
- function crdtSubscribe(crdt, innerCallback, options) {
119
- const cb = (updates) => {
120
- const relatedUpdates = [];
121
- for (const update of updates) {
122
- if ((options === null || options === void 0 ? void 0 : options.isDeep) && (0, utils_1.isSameNodeOrChildOf)(update.node, crdt)) {
123
- relatedUpdates.push(update);
124
- }
125
- else if (update.node._id === crdt._id) {
126
- innerCallback(update.node);
127
- }
128
- }
129
- if ((options === null || options === void 0 ? void 0 : options.isDeep) && relatedUpdates.length > 0) {
130
- innerCallback(relatedUpdates);
131
- }
132
- };
133
- return genericSubscribe(cb);
134
- }
135
- function createOrUpdateRootFromMessage(message) {
136
- if (message.items.length === 0) {
137
- throw new Error("Internal error: cannot load storage without items");
138
- }
139
- if (state.root) {
140
- updateRoot(message.items);
141
- }
142
- else {
143
- state.root = load(message.items);
144
- }
145
- for (const key in state.defaultStorageRoot) {
146
- if (state.root.get(key) == null) {
147
- state.root.set(key, state.defaultStorageRoot[key]);
148
- }
149
- }
150
- }
151
- function buildRootAndParentToChildren(items) {
152
- const parentToChildren = new Map();
153
- let root = null;
154
- for (const tuple of items) {
155
- const parentId = tuple[1].parentId;
156
- if (parentId == null) {
157
- root = tuple;
158
- }
159
- else {
160
- const children = parentToChildren.get(parentId);
161
- if (children != null) {
162
- children.push(tuple);
163
- }
164
- else {
165
- parentToChildren.set(parentId, [tuple]);
166
- }
167
- }
168
- }
169
- if (root == null) {
170
- throw new Error("Root can't be null");
171
- }
172
- return [root, parentToChildren];
173
- }
174
- function updateRoot(items) {
175
- if (!state.root) {
176
- return;
177
- }
178
- const currentItems = new Map();
179
- state.items.forEach((liveCrdt, id) => {
180
- currentItems.set(id, liveCrdt._toSerializedCrdt());
181
- });
182
- // Get operations that represent the diff between 2 states.
183
- const ops = (0, utils_1.getTreesDiffOperations)(currentItems, new Map(items));
184
- const result = apply(ops, false);
185
- notify(result.updates);
186
- }
187
- function load(items) {
188
- const [root, parentToChildren] = buildRootAndParentToChildren(items);
189
- return LiveObject_1.LiveObject._deserialize(root, parentToChildren, {
190
- addItem,
191
- deleteItem,
192
- generateId,
193
- generateOpId,
194
- dispatch: storageDispatch,
195
- });
196
- }
197
- function addItem(id, item) {
198
- state.items.set(id, item);
199
- }
200
- function deleteItem(id) {
201
- state.items.delete(id);
202
- }
203
- function getItem(id) {
204
- return state.items.get(id);
205
- }
206
- function addToUndoStack(historyItem) {
207
- // If undo stack is too large, we remove the older item
208
- if (state.undoStack.length >= 50) {
209
- state.undoStack.shift();
210
- }
211
- if (state.isHistoryPaused) {
212
- state.pausedHistory.unshift(...historyItem);
213
- }
214
- else {
215
- state.undoStack.push(historyItem);
216
- }
217
- }
218
- function storageDispatch(ops, reverse, storageUpdates) {
219
- if (state.isBatching) {
220
- state.batch.ops.push(...ops);
221
- storageUpdates.forEach((value, key) => {
222
- state.batch.updates.storageUpdates.set(key, (0, utils_1.mergeStorageUpdates)(state.batch.updates.storageUpdates.get(key), value));
223
- });
224
- state.batch.reverseOps.push(...reverse);
225
- }
226
- else {
227
- addToUndoStack(reverse);
228
- state.redoStack = [];
229
- dispatch(ops);
230
- notify({ storageUpdates: storageUpdates });
231
- }
232
- }
233
- function notify({ storageUpdates = new Map(), presence = false, others = [], }) {
234
- if (others.length > 0) {
235
- state.others = makeOthers(state.users);
236
- for (const event of others) {
237
- for (const listener of state.listeners["others"]) {
238
- listener(state.others, event);
239
- }
240
- }
241
- }
242
- if (presence) {
243
- for (const listener of state.listeners["my-presence"]) {
244
- listener(state.me);
245
- }
246
- }
247
- if (storageUpdates.size > 0) {
248
- for (const subscriber of state.listeners.storage) {
249
- subscriber(Array.from(storageUpdates.values()));
250
- }
251
- }
252
- }
253
- function getConnectionId() {
254
- if (state.connection.state === "open" ||
255
- state.connection.state === "connecting") {
256
- return state.connection.id;
257
- }
258
- else if (state.lastConnectionId !== null) {
259
- return state.lastConnectionId;
260
- }
261
- throw new Error("Internal. Tried to get connection id but connection was never open");
262
- }
263
- function generateId() {
264
- return `${getConnectionId()}:${state.clock++}`;
265
- }
266
- function generateOpId() {
267
- return `${getConnectionId()}:${state.opClock++}`;
268
- }
269
- function apply(item, isLocal) {
270
- const result = {
271
- reverse: [],
272
- updates: {
273
- storageUpdates: new Map(),
274
- presence: false,
275
- },
276
- };
277
- for (const op of item) {
278
- if (op.type === "presence") {
279
- const reverse = {
280
- type: "presence",
281
- data: {},
282
- };
283
- for (const key in op.data) {
284
- reverse.data[key] = state.me[key];
285
- }
286
- state.me = Object.assign(Object.assign({}, state.me), op.data);
287
- if (state.buffer.presence == null) {
288
- state.buffer.presence = op.data;
289
- }
290
- else {
291
- for (const key in op.data) {
292
- state.buffer.presence[key] = op.data;
293
- }
294
- }
295
- result.reverse.unshift(reverse);
296
- result.updates.presence = true;
297
- }
298
- else {
299
- // Ops applied after undo/redo don't have an opId.
300
- if (isLocal && !op.opId) {
301
- op.opId = generateOpId();
302
- }
303
- const applyOpResult = applyOp(op, isLocal);
304
- if (applyOpResult.modified) {
305
- result.updates.storageUpdates.set(applyOpResult.modified.node._id, (0, utils_1.mergeStorageUpdates)(result.updates.storageUpdates.get(applyOpResult.modified.node._id), applyOpResult.modified));
306
- result.reverse.unshift(...applyOpResult.reverse);
307
- }
308
- }
309
- }
310
- return result;
311
- }
312
- function applyOp(op, isLocal) {
313
- if (op.opId) {
314
- state.offlineOperations.delete(op.opId);
315
- }
316
- switch (op.type) {
317
- case live_1.OpType.DeleteObjectKey:
318
- case live_1.OpType.UpdateObject:
319
- case live_1.OpType.DeleteCrdt: {
320
- const item = state.items.get(op.id);
321
- if (item == null) {
322
- return { modified: false };
323
- }
324
- return item._apply(op, isLocal);
325
- }
326
- case live_1.OpType.SetParentKey: {
327
- const item = state.items.get(op.id);
328
- if (item == null) {
329
- return { modified: false };
330
- }
331
- if (item._parent instanceof LiveList_1.LiveList) {
332
- const previousKey = item._parentKey;
333
- if (previousKey === op.parentKey) {
334
- return { modified: false };
335
- }
336
- else {
337
- return item._parent._setChildKey(op.parentKey, item, previousKey);
338
- }
339
- }
340
- return { modified: false };
341
- }
342
- case live_1.OpType.CreateObject: {
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 LiveObject_1.LiveObject(op.data), isLocal);
348
- }
349
- case live_1.OpType.CreateList: {
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 LiveList_1.LiveList(), isLocal);
355
- }
356
- case live_1.OpType.CreateRegister: {
357
- const parent = state.items.get(op.parentId);
358
- if (parent == null || getItem(op.id) != null) {
359
- return { modified: false };
360
- }
361
- return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data), isLocal);
362
- }
363
- case live_1.OpType.CreateMap: {
364
- const parent = state.items.get(op.parentId);
365
- if (parent == null || getItem(op.id) != null) {
366
- return { modified: false };
367
- }
368
- return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap(), isLocal);
369
- }
370
- }
371
- return { modified: false };
372
- }
373
- function subscribe(firstParam, listener, options) {
374
- if (firstParam instanceof AbstractCrdt_1.AbstractCrdt) {
375
- return crdtSubscribe(firstParam, listener, options);
376
- }
377
- else if (typeof firstParam === "function") {
378
- return genericSubscribe(firstParam);
379
- }
380
- else if (!isValidRoomEventType(firstParam)) {
381
- throw new Error(`"${firstParam}" is not a valid event name`);
382
- }
383
- state.listeners[firstParam].push(listener);
384
- return () => {
385
- const callbacks = state.listeners[firstParam];
386
- (0, utils_1.remove)(callbacks, listener);
387
- };
388
- }
389
- function unsubscribe(event, callback) {
390
- console.warn(`unsubscribe is depreacted and will be removed in a future version.
391
- use the callback returned by subscribe instead.
392
- See v0.13 release notes for more information.
393
- `);
394
- if (!isValidRoomEventType(event)) {
395
- throw new Error(`"${event}" is not a valid event name`);
396
- }
397
- const callbacks = state.listeners[event];
398
- (0, utils_1.remove)(callbacks, callback);
399
- }
400
- function getConnectionState() {
401
- return state.connection.state;
402
- }
403
- function getSelf() {
404
- return state.connection.state === "open" ||
405
- state.connection.state === "connecting"
406
- ? {
407
- connectionId: state.connection.id,
408
- id: state.connection.userId,
409
- info: state.connection.userInfo,
410
- presence: getPresence(),
411
- }
412
- : null;
413
- }
414
- function connect() {
415
- if (typeof window === "undefined") {
416
- return;
417
- }
418
- if (state.connection.state !== "closed" &&
419
- state.connection.state !== "unavailable") {
420
- return null;
421
- }
422
- updateConnection({ state: "authenticating" });
423
- effects.authenticate();
424
- }
425
- function updatePresence(overrides, options) {
426
- const oldValues = {};
427
- if (state.buffer.presence == null) {
428
- state.buffer.presence = {};
429
- }
430
- for (const key in overrides) {
431
- state.buffer.presence[key] = overrides[key];
432
- oldValues[key] = state.me[key];
433
- }
434
- state.me = Object.assign(Object.assign({}, state.me), overrides);
435
- if (state.isBatching) {
436
- if (options === null || options === void 0 ? void 0 : options.addToHistory) {
437
- state.batch.reverseOps.push({ type: "presence", data: oldValues });
438
- }
439
- state.batch.updates.presence = true;
440
- }
441
- else {
442
- tryFlushing();
443
- if (options === null || options === void 0 ? void 0 : options.addToHistory) {
444
- addToUndoStack([{ type: "presence", data: oldValues }]);
445
- }
446
- notify({ presence: true });
447
- }
448
- }
449
- function authenticationSuccess(token, socket) {
450
- updateConnection({
451
- state: "connecting",
452
- id: token.actor,
453
- userInfo: token.info,
454
- userId: token.id,
455
- });
456
- state.idFactory = makeIdFactory(token.actor);
457
- state.socket = socket;
458
- }
459
- function authenticationFailure(error) {
460
- if (process.env.NODE_ENV !== "production") {
461
- console.error("Call to authentication endpoint failed", error);
462
- }
463
- updateConnection({ state: "unavailable" });
464
- state.numberOfRetry++;
465
- state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
466
- }
467
- function onVisibilityChange(visibilityState) {
468
- if (visibilityState === "visible" && state.connection.state === "open") {
469
- log("Heartbeat after visibility change");
470
- heartbeat();
471
- }
472
- }
473
- function onUpdatePresenceMessage(message) {
474
- const user = state.users[message.actor];
475
- if (user == null) {
476
- state.users[message.actor] = {
477
- connectionId: message.actor,
478
- presence: message.data,
479
- };
480
- }
481
- else {
482
- state.users[message.actor] = {
483
- id: user.id,
484
- info: user.info,
485
- connectionId: message.actor,
486
- presence: Object.assign(Object.assign({}, user.presence), message.data),
487
- };
488
- }
489
- return {
490
- type: "update",
491
- updates: message.data,
492
- user: state.users[message.actor],
493
- };
494
- }
495
- function onUserLeftMessage(message) {
496
- const userLeftMessage = message;
497
- const user = state.users[userLeftMessage.actor];
498
- if (user) {
499
- delete state.users[userLeftMessage.actor];
500
- return { type: "leave", user };
501
- }
502
- return null;
503
- }
504
- function onRoomStateMessage(message) {
505
- const newUsers = {};
506
- for (const key in message.users) {
507
- const connectionId = Number.parseInt(key);
508
- const user = message.users[key];
509
- newUsers[connectionId] = {
510
- connectionId,
511
- info: user.info,
512
- id: user.id,
513
- };
514
- }
515
- state.users = newUsers;
516
- return { type: "reset" };
517
- }
518
- function onNavigatorOnline() {
519
- if (state.connection.state === "unavailable") {
520
- log("Try to reconnect after connectivity change");
521
- reconnect();
522
- }
523
- }
524
- function onEvent(message) {
525
- for (const listener of state.listeners.event) {
526
- listener({ connectionId: message.actor, event: message.event });
527
- }
528
- }
529
- function onUserJoinedMessage(message) {
530
- state.users[message.actor] = {
531
- connectionId: message.actor,
532
- info: message.info,
533
- id: message.id,
534
- };
535
- if (state.me) {
536
- // Send current presence to new user
537
- // TODO: Consider storing it on the backend
538
- state.buffer.messages.push({
539
- type: live_1.ClientMessageType.UpdatePresence,
540
- data: state.me,
541
- targetActor: message.actor,
542
- });
543
- tryFlushing();
544
- }
545
- return { type: "enter", user: state.users[message.actor] };
546
- }
547
- function onMessage(event) {
548
- if (event.data === "pong") {
549
- clearTimeout(state.timeoutHandles.pongTimeout);
550
- return;
551
- }
552
- const message = JSON.parse(event.data);
553
- let subMessages = [];
554
- if (Array.isArray(message)) {
555
- subMessages = message;
556
- }
557
- else {
558
- subMessages.push(message);
559
- }
560
- const updates = {
561
- storageUpdates: new Map(),
562
- others: [],
563
- };
564
- for (const subMessage of subMessages) {
565
- switch (subMessage.type) {
566
- case live_1.ServerMessageType.UserJoined: {
567
- updates.others.push(onUserJoinedMessage(message));
568
- break;
569
- }
570
- case live_1.ServerMessageType.UpdatePresence: {
571
- updates.others.push(onUpdatePresenceMessage(subMessage));
572
- break;
573
- }
574
- case live_1.ServerMessageType.Event: {
575
- onEvent(subMessage);
576
- break;
577
- }
578
- case live_1.ServerMessageType.UserLeft: {
579
- const event = onUserLeftMessage(subMessage);
580
- if (event) {
581
- updates.others.push(event);
582
- }
583
- break;
584
- }
585
- case live_1.ServerMessageType.RoomState: {
586
- updates.others.push(onRoomStateMessage(subMessage));
587
- break;
588
- }
589
- case live_1.ServerMessageType.InitialStorageState: {
590
- createOrUpdateRootFromMessage(subMessage);
591
- applyAndSendOfflineOps();
592
- _getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
593
- break;
594
- }
595
- case live_1.ServerMessageType.UpdateStorage: {
596
- const applyResult = apply(subMessage.ops, false);
597
- applyResult.updates.storageUpdates.forEach((value, key) => {
598
- updates.storageUpdates.set(key, (0, utils_1.mergeStorageUpdates)(updates.storageUpdates.get(key), value));
599
- });
600
- break;
601
- }
602
- }
603
- }
604
- notify(updates);
605
- }
606
- // function onWakeUp() {
607
- // // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example)
608
- // // The client will not know that the server has probably close the connection even if the readyState is Open
609
- // // One way to detect this kind of pause is to ensure that a setInterval is not taking more than the delay it was configured with
610
- // if (state.connection.state === "open") {
611
- // log("Try to reconnect after laptop wake up");
612
- // reconnect();
613
- // }
614
- // }
615
- function onClose(event) {
616
- state.socket = null;
617
- clearTimeout(state.timeoutHandles.pongTimeout);
618
- clearInterval(state.intervalHandles.heartbeat);
619
- if (state.timeoutHandles.flush) {
620
- clearTimeout(state.timeoutHandles.flush);
621
- }
622
- clearTimeout(state.timeoutHandles.reconnect);
623
- state.users = {};
624
- notify({ others: [{ type: "reset" }] });
625
- if (event.code >= 4000 && event.code <= 4100) {
626
- updateConnection({ state: "failed" });
627
- const error = new LiveblocksError(event.reason, event.code);
628
- for (const listener of state.listeners.error) {
629
- if (process.env.NODE_ENV !== "production") {
630
- console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
631
- }
632
- listener(error);
633
- }
634
- }
635
- else if (event.wasClean === false) {
636
- state.numberOfRetry++;
637
- const delay = getRetryDelay();
638
- if (process.env.NODE_ENV !== "production") {
639
- console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
640
- }
641
- updateConnection({ state: "unavailable" });
642
- state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
643
- }
644
- else {
645
- updateConnection({ state: "closed" });
646
- }
647
- }
648
- function updateConnection(connection) {
649
- state.connection = connection;
650
- for (const listener of state.listeners.connection) {
651
- listener(connection.state);
652
- }
653
- }
654
- function getRetryDelay() {
655
- return BACKOFF_RETRY_DELAYS[state.numberOfRetry < BACKOFF_RETRY_DELAYS.length
656
- ? state.numberOfRetry
657
- : BACKOFF_RETRY_DELAYS.length - 1];
658
- }
659
- function onError() { }
660
- function onOpen() {
661
- clearInterval(state.intervalHandles.heartbeat);
662
- state.intervalHandles.heartbeat = effects.startHeartbeatInterval();
663
- if (state.connection.state === "connecting") {
664
- updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
665
- state.numberOfRetry = 0;
666
- state.lastConnectionId = state.connection.id;
667
- if (state.root) {
668
- state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage });
669
- }
670
- tryFlushing();
671
- }
672
- else {
673
- // TODO
674
- }
675
- }
676
- function heartbeat() {
677
- if (state.socket == null) {
678
- // Should never happen, because we clear the pong timeout when the connection is dropped explictly
679
- return;
680
- }
681
- clearTimeout(state.timeoutHandles.pongTimeout);
682
- state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
683
- if (state.socket.readyState === WebSocket.OPEN) {
684
- state.socket.send("ping");
685
- }
686
- }
687
- function pongTimeout() {
688
- log("Pong timeout. Trying to reconnect.");
689
- reconnect();
690
- }
691
- function reconnect() {
692
- if (state.socket) {
693
- state.socket.removeEventListener("open", onOpen);
694
- state.socket.removeEventListener("message", onMessage);
695
- state.socket.removeEventListener("close", onClose);
696
- state.socket.removeEventListener("error", onError);
697
- state.socket.close();
698
- state.socket = null;
699
- }
700
- updateConnection({ state: "unavailable" });
701
- clearTimeout(state.timeoutHandles.pongTimeout);
702
- if (state.timeoutHandles.flush) {
703
- clearTimeout(state.timeoutHandles.flush);
704
- }
705
- clearTimeout(state.timeoutHandles.reconnect);
706
- clearInterval(state.intervalHandles.heartbeat);
707
- connect();
708
- }
709
- function applyAndSendOfflineOps() {
710
- if (state.offlineOperations.size === 0) {
711
- return;
712
- }
713
- const messages = [];
714
- const ops = Array.from(state.offlineOperations.values());
715
- const result = apply(ops, true);
716
- messages.push({
717
- type: live_1.ClientMessageType.UpdateStorage,
718
- ops: ops,
719
- });
720
- notify(result.updates);
721
- effects.send(messages);
722
- }
723
- function tryFlushing() {
724
- const storageOps = state.buffer.storageOperations;
725
- if (storageOps.length > 0) {
726
- storageOps.forEach((op) => {
727
- state.offlineOperations.set(op.opId, op);
728
- });
729
- }
730
- if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
731
- state.buffer.storageOperations = [];
732
- return;
733
- }
734
- const now = Date.now();
735
- const elapsedTime = now - state.lastFlushTime;
736
- if (elapsedTime > context.throttleDelay) {
737
- const messages = flushDataToMessages(state);
738
- if (messages.length === 0) {
739
- return;
740
- }
741
- effects.send(messages);
742
- state.buffer = {
743
- messages: [],
744
- storageOperations: [],
745
- presence: null,
746
- };
747
- state.lastFlushTime = now;
748
- }
749
- else {
750
- if (state.timeoutHandles.flush != null) {
751
- clearTimeout(state.timeoutHandles.flush);
752
- }
753
- state.timeoutHandles.flush = effects.delayFlush(context.throttleDelay - (now - state.lastFlushTime));
754
- }
755
- }
756
- function flushDataToMessages(state) {
757
- const messages = [];
758
- if (state.buffer.presence) {
759
- messages.push({
760
- type: live_1.ClientMessageType.UpdatePresence,
761
- data: state.buffer.presence,
762
- });
763
- }
764
- for (const event of state.buffer.messages) {
765
- messages.push(event);
766
- }
767
- if (state.buffer.storageOperations.length > 0) {
768
- messages.push({
769
- type: live_1.ClientMessageType.UpdateStorage,
770
- ops: state.buffer.storageOperations,
771
- });
772
- }
773
- return messages;
774
- }
775
- function disconnect() {
776
- if (state.socket) {
777
- state.socket.removeEventListener("open", onOpen);
778
- state.socket.removeEventListener("message", onMessage);
779
- state.socket.removeEventListener("close", onClose);
780
- state.socket.removeEventListener("error", onError);
781
- state.socket.close();
782
- state.socket = null;
783
- }
784
- updateConnection({ state: "closed" });
785
- if (state.timeoutHandles.flush) {
786
- clearTimeout(state.timeoutHandles.flush);
787
- }
788
- clearTimeout(state.timeoutHandles.reconnect);
789
- clearTimeout(state.timeoutHandles.pongTimeout);
790
- clearInterval(state.intervalHandles.heartbeat);
791
- state.users = {};
792
- notify({ others: [{ type: "reset" }] });
793
- clearListeners();
794
- }
795
- function clearListeners() {
796
- for (const key in state.listeners) {
797
- state.listeners[key] = [];
798
- }
799
- }
800
- function getPresence() {
801
- return state.me;
802
- }
803
- function getOthers() {
804
- return state.others;
805
- }
806
- function broadcastEvent(event, options = {
807
- shouldQueueEventIfNotReady: false,
808
- }) {
809
- if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
810
- return;
811
- }
812
- state.buffer.messages.push({
813
- type: live_1.ClientMessageType.ClientEvent,
814
- event,
815
- });
816
- tryFlushing();
817
- }
818
- function dispatch(ops) {
819
- state.buffer.storageOperations.push(...ops);
820
- tryFlushing();
821
- }
822
- let _getInitialStatePromise = null;
823
- let _getInitialStateResolver = null;
824
- function getStorage() {
825
- return __awaiter(this, void 0, void 0, function* () {
826
- if (state.root) {
827
- return {
828
- root: state.root,
829
- };
830
- }
831
- if (_getInitialStatePromise == null) {
832
- state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage });
833
- tryFlushing();
834
- _getInitialStatePromise = new Promise((resolve) => (_getInitialStateResolver = resolve));
835
- }
836
- yield _getInitialStatePromise;
837
- return {
838
- root: state.root,
839
- };
840
- });
841
- }
842
- function undo() {
843
- if (state.isBatching) {
844
- throw new Error("undo is not allowed during a batch");
845
- }
846
- const historyItem = state.undoStack.pop();
847
- if (historyItem == null) {
848
- return;
849
- }
850
- state.isHistoryPaused = false;
851
- const result = apply(historyItem, true);
852
- notify(result.updates);
853
- state.redoStack.push(result.reverse);
854
- for (const op of historyItem) {
855
- if (op.type !== "presence") {
856
- state.buffer.storageOperations.push(op);
857
- }
858
- }
859
- tryFlushing();
860
- }
861
- function redo() {
862
- if (state.isBatching) {
863
- throw new Error("redo is not allowed during a batch");
864
- }
865
- const historyItem = state.redoStack.pop();
866
- if (historyItem == null) {
867
- return;
868
- }
869
- state.isHistoryPaused = false;
870
- const result = apply(historyItem, true);
871
- notify(result.updates);
872
- state.undoStack.push(result.reverse);
873
- for (const op of historyItem) {
874
- if (op.type !== "presence") {
875
- state.buffer.storageOperations.push(op);
876
- }
877
- }
878
- tryFlushing();
879
- }
880
- function batch(callback) {
881
- if (state.isBatching) {
882
- throw new Error("batch should not be called during a batch");
883
- }
884
- state.isBatching = true;
885
- try {
886
- callback();
887
- }
888
- finally {
889
- state.isBatching = false;
890
- if (state.batch.reverseOps.length > 0) {
891
- addToUndoStack(state.batch.reverseOps);
892
- }
893
- if (state.batch.ops.length > 0) {
894
- // Only clear the redo stack if something has changed during a batch
895
- // Clear the redo stack because batch is always called from a local operation
896
- state.redoStack = [];
897
- }
898
- if (state.batch.ops.length > 0) {
899
- dispatch(state.batch.ops);
900
- }
901
- notify(state.batch.updates);
902
- state.batch = {
903
- ops: [],
904
- reverseOps: [],
905
- updates: {
906
- others: [],
907
- storageUpdates: new Map(),
908
- presence: false,
909
- },
910
- };
911
- tryFlushing();
912
- }
913
- }
914
- function pauseHistory() {
915
- state.pausedHistory = [];
916
- state.isHistoryPaused = true;
917
- }
918
- function resumeHistory() {
919
- state.isHistoryPaused = false;
920
- if (state.pausedHistory.length > 0) {
921
- addToUndoStack(state.pausedHistory);
922
- }
923
- state.pausedHistory = [];
924
- }
925
- function simulateSocketClose() {
926
- if (state.socket) {
927
- state.socket.close();
928
- }
929
- }
930
- function simulateSendCloseEvent(event) {
931
- if (state.socket) {
932
- onClose(event);
933
- }
934
- }
935
- return {
936
- // Internal
937
- onOpen,
938
- onClose,
939
- onMessage,
940
- authenticationSuccess,
941
- heartbeat,
942
- onNavigatorOnline,
943
- // Internal dev tools
944
- simulateSocketClose,
945
- simulateSendCloseEvent,
946
- // onWakeUp,
947
- onVisibilityChange,
948
- getUndoStack: () => state.undoStack,
949
- getItemsCount: () => state.items.size,
950
- // Core
951
- connect,
952
- disconnect,
953
- subscribe,
954
- unsubscribe,
955
- // Presence
956
- updatePresence,
957
- broadcastEvent,
958
- batch,
959
- undo,
960
- redo,
961
- pauseHistory,
962
- resumeHistory,
963
- getStorage,
964
- selectors: {
965
- // Core
966
- getConnectionState,
967
- getSelf,
968
- // Presence
969
- getPresence,
970
- getOthers,
971
- },
972
- };
973
- }
974
- exports.makeStateMachine = makeStateMachine;
975
- function defaultState(me, defaultStorageRoot) {
976
- return {
977
- connection: { state: "closed" },
978
- lastConnectionId: null,
979
- socket: null,
980
- listeners: {
981
- event: [],
982
- others: [],
983
- "my-presence": [],
984
- error: [],
985
- connection: [],
986
- storage: [],
987
- },
988
- numberOfRetry: 0,
989
- lastFlushTime: 0,
990
- timeoutHandles: {
991
- flush: null,
992
- reconnect: 0,
993
- pongTimeout: 0,
994
- },
995
- buffer: {
996
- presence: me == null ? {} : me,
997
- messages: [],
998
- storageOperations: [],
999
- },
1000
- intervalHandles: {
1001
- heartbeat: 0,
1002
- },
1003
- me: me == null ? {} : me,
1004
- users: {},
1005
- others: makeOthers({}),
1006
- defaultStorageRoot,
1007
- idFactory: null,
1008
- // Storage
1009
- clock: 0,
1010
- opClock: 0,
1011
- items: new Map(),
1012
- root: undefined,
1013
- undoStack: [],
1014
- redoStack: [],
1015
- isHistoryPaused: false,
1016
- pausedHistory: [],
1017
- isBatching: false,
1018
- batch: {
1019
- ops: [],
1020
- updates: {
1021
- storageUpdates: new Map(),
1022
- presence: false,
1023
- others: [],
1024
- },
1025
- reverseOps: [],
1026
- },
1027
- offlineOperations: new Map(),
1028
- };
1029
- }
1030
- exports.defaultState = defaultState;
1031
- function createRoom(name, options) {
1032
- const throttleDelay = options.throttle || 100;
1033
- const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
1034
- let authEndpoint;
1035
- if (options.authEndpoint) {
1036
- authEndpoint = options.authEndpoint;
1037
- }
1038
- else {
1039
- const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
1040
- "https://liveblocks.io/api/public/authorize";
1041
- authEndpoint = publicAuthorizeEndpoint;
1042
- }
1043
- const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
1044
- const machine = makeStateMachine(state, {
1045
- throttleDelay,
1046
- liveblocksServer,
1047
- authEndpoint,
1048
- room: name,
1049
- publicApiKey: options.publicApiKey,
1050
- });
1051
- const room = {
1052
- /////////////
1053
- // Core //
1054
- /////////////
1055
- getConnectionState: machine.selectors.getConnectionState,
1056
- getSelf: machine.selectors.getSelf,
1057
- subscribe: machine.subscribe,
1058
- unsubscribe: machine.unsubscribe,
1059
- //////////////
1060
- // Presence //
1061
- //////////////
1062
- getPresence: machine.selectors.getPresence,
1063
- updatePresence: machine.updatePresence,
1064
- getOthers: machine.selectors.getOthers,
1065
- broadcastEvent: machine.broadcastEvent,
1066
- getStorage: machine.getStorage,
1067
- batch: machine.batch,
1068
- history: {
1069
- undo: machine.undo,
1070
- redo: machine.redo,
1071
- pause: machine.pauseHistory,
1072
- resume: machine.resumeHistory,
1073
- },
1074
- // @ts-ignore
1075
- internalDevTools: {
1076
- closeWebsocket: machine.simulateSocketClose,
1077
- sendCloseEvent: machine.simulateSendCloseEvent,
1078
- },
1079
- };
1080
- return {
1081
- connect: machine.connect,
1082
- disconnect: machine.disconnect,
1083
- onNavigatorOnline: machine.onNavigatorOnline,
1084
- onVisibilityChange: machine.onVisibilityChange,
1085
- room,
1086
- };
1087
- }
1088
- exports.createRoom = createRoom;
1089
- class LiveblocksError extends Error {
1090
- constructor(message, code) {
1091
- super(message);
1092
- this.code = code;
1093
- }
1094
- }