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