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