@liveblocks/core 1.0.12 → 1.1.0-beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +156 -59
- package/dist/index.js +1272 -522
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,6 +54,19 @@ var __async = (__this, __arguments, generator) => {
|
|
|
54
54
|
function makeEventSource() {
|
|
55
55
|
const _onetimeObservers = /* @__PURE__ */ new Set();
|
|
56
56
|
const _observers = /* @__PURE__ */ new Set();
|
|
57
|
+
let _buffer = null;
|
|
58
|
+
function pause() {
|
|
59
|
+
_buffer = [];
|
|
60
|
+
}
|
|
61
|
+
function unpause() {
|
|
62
|
+
if (_buffer === null) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const event of _buffer) {
|
|
66
|
+
notify(event);
|
|
67
|
+
}
|
|
68
|
+
_buffer = null;
|
|
69
|
+
}
|
|
57
70
|
function subscribe(callback) {
|
|
58
71
|
_observers.add(callback);
|
|
59
72
|
return () => _observers.delete(callback);
|
|
@@ -62,6 +75,25 @@ function makeEventSource() {
|
|
|
62
75
|
_onetimeObservers.add(callback);
|
|
63
76
|
return () => _onetimeObservers.delete(callback);
|
|
64
77
|
}
|
|
78
|
+
function waitUntil(predicate) {
|
|
79
|
+
return __async(this, null, function* () {
|
|
80
|
+
let unsub;
|
|
81
|
+
return new Promise((res) => {
|
|
82
|
+
unsub = subscribe((event) => {
|
|
83
|
+
if (predicate === void 0 || predicate(event)) {
|
|
84
|
+
res(event);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}).finally(() => unsub == null ? void 0 : unsub());
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function notifyOrBuffer(event) {
|
|
91
|
+
if (_buffer !== null) {
|
|
92
|
+
_buffer.push(event);
|
|
93
|
+
} else {
|
|
94
|
+
notify(event);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
65
97
|
function notify(event) {
|
|
66
98
|
_onetimeObservers.forEach((callback) => callback(event));
|
|
67
99
|
_onetimeObservers.clear();
|
|
@@ -71,16 +103,24 @@ function makeEventSource() {
|
|
|
71
103
|
_onetimeObservers.clear();
|
|
72
104
|
_observers.clear();
|
|
73
105
|
}
|
|
106
|
+
function count() {
|
|
107
|
+
return _onetimeObservers.size + _observers.size;
|
|
108
|
+
}
|
|
74
109
|
return {
|
|
75
110
|
// Private/internal control over event emission
|
|
76
|
-
notify,
|
|
111
|
+
notify: notifyOrBuffer,
|
|
77
112
|
subscribe,
|
|
78
113
|
subscribeOnce,
|
|
79
114
|
clear,
|
|
115
|
+
count,
|
|
116
|
+
waitUntil,
|
|
117
|
+
pause,
|
|
118
|
+
unpause,
|
|
80
119
|
// Publicly exposable subscription API
|
|
81
120
|
observable: {
|
|
82
121
|
subscribe,
|
|
83
|
-
subscribeOnce
|
|
122
|
+
subscribeOnce,
|
|
123
|
+
waitUntil
|
|
84
124
|
}
|
|
85
125
|
};
|
|
86
126
|
}
|
|
@@ -117,7 +157,7 @@ var onMessageFromPanel = eventSource.observable;
|
|
|
117
157
|
// src/devtools/index.ts
|
|
118
158
|
var VERSION = true ? (
|
|
119
159
|
/* istanbul ignore next */
|
|
120
|
-
"1.0
|
|
160
|
+
"1.1.0-beta1"
|
|
121
161
|
) : "dev";
|
|
122
162
|
var _devtoolsSetupHasRun = false;
|
|
123
163
|
function setupDevTools(getAllRooms) {
|
|
@@ -159,7 +199,7 @@ function startSyncStream(room) {
|
|
|
159
199
|
fullSync(room);
|
|
160
200
|
unsubsByRoomId.set(room.id, [
|
|
161
201
|
// When the connection status changes
|
|
162
|
-
room.events.
|
|
202
|
+
room.events.status.subscribe(() => partialSyncConnection(room)),
|
|
163
203
|
// When storage initializes, send the update
|
|
164
204
|
room.events.storageDidLoad.subscribeOnce(() => partialSyncStorage(room)),
|
|
165
205
|
// Any time storage updates, send the new storage root
|
|
@@ -173,7 +213,7 @@ function partialSyncConnection(room) {
|
|
|
173
213
|
sendToPanel({
|
|
174
214
|
msg: "room::sync::partial",
|
|
175
215
|
roomId: room.id,
|
|
176
|
-
status: room.
|
|
216
|
+
status: room.getStatus()
|
|
177
217
|
});
|
|
178
218
|
}
|
|
179
219
|
function partialSyncStorage(room) {
|
|
@@ -214,7 +254,7 @@ function fullSync(room) {
|
|
|
214
254
|
sendToPanel({
|
|
215
255
|
msg: "room::sync::full",
|
|
216
256
|
roomId: room.id,
|
|
217
|
-
status: room.
|
|
257
|
+
status: room.getStatus(),
|
|
218
258
|
storage: (_a = root == null ? void 0 : root.toTreeNode("root").payload) != null ? _a : null,
|
|
219
259
|
me,
|
|
220
260
|
others
|
|
@@ -345,6 +385,965 @@ function nn(value, errmsg = "Expected value to be non-nullable") {
|
|
|
345
385
|
return value;
|
|
346
386
|
}
|
|
347
387
|
|
|
388
|
+
// src/lib/fsm.ts
|
|
389
|
+
function distance(state1, state2) {
|
|
390
|
+
if (state1 === state2) {
|
|
391
|
+
return [0, 0];
|
|
392
|
+
}
|
|
393
|
+
const chunks1 = state1.split(".");
|
|
394
|
+
const chunks2 = state2.split(".");
|
|
395
|
+
const minLen = Math.min(chunks1.length, chunks2.length);
|
|
396
|
+
let shared = 0;
|
|
397
|
+
for (; shared < minLen; shared++) {
|
|
398
|
+
if (chunks1[shared] !== chunks2[shared]) {
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const up = chunks1.length - shared;
|
|
403
|
+
const down = chunks2.length - shared;
|
|
404
|
+
return [up, down];
|
|
405
|
+
}
|
|
406
|
+
function patterns(targetState, levels) {
|
|
407
|
+
const parts = targetState.split(".");
|
|
408
|
+
if (levels < 1 || levels > parts.length + 1) {
|
|
409
|
+
throw new Error("Invalid number of levels");
|
|
410
|
+
}
|
|
411
|
+
const result = [];
|
|
412
|
+
if (levels > parts.length) {
|
|
413
|
+
result.push("*");
|
|
414
|
+
}
|
|
415
|
+
for (let i = parts.length - levels + 1; i < parts.length; i++) {
|
|
416
|
+
const slice = parts.slice(0, i);
|
|
417
|
+
if (slice.length > 0) {
|
|
418
|
+
result.push(slice.join(".") + ".*");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
result.push(targetState);
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
var SafeContext = class {
|
|
425
|
+
constructor(initialContext) {
|
|
426
|
+
this.curr = initialContext;
|
|
427
|
+
}
|
|
428
|
+
get current() {
|
|
429
|
+
return this.curr;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Call a callback function that allows patching of the context, by
|
|
433
|
+
* calling `context.patch()`. Patching is only allowed for the duration
|
|
434
|
+
* of this window.
|
|
435
|
+
*/
|
|
436
|
+
allowPatching(callback) {
|
|
437
|
+
const self = this;
|
|
438
|
+
let allowed = true;
|
|
439
|
+
const patchableContext = __spreadProps(__spreadValues({}, this.curr), {
|
|
440
|
+
patch(patch) {
|
|
441
|
+
if (allowed) {
|
|
442
|
+
self.curr = Object.assign({}, self.curr, patch);
|
|
443
|
+
for (const pair of Object.entries(patch)) {
|
|
444
|
+
const [key, value] = pair;
|
|
445
|
+
if (key !== "patch") {
|
|
446
|
+
this[key] = value;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
} else {
|
|
450
|
+
throw new Error("Can no longer patch stale context");
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
callback(patchableContext);
|
|
455
|
+
allowed = false;
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
var nextId = 1;
|
|
460
|
+
var FSM = class {
|
|
461
|
+
/**
|
|
462
|
+
* Returns the initial state, which is defined by the first call made to
|
|
463
|
+
* .addState().
|
|
464
|
+
*/
|
|
465
|
+
get initialState() {
|
|
466
|
+
const result = this.states.values()[Symbol.iterator]().next();
|
|
467
|
+
if (result.done) {
|
|
468
|
+
throw new Error("No states defined yet");
|
|
469
|
+
} else {
|
|
470
|
+
return result.value;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
get currentState() {
|
|
474
|
+
if (this.currentStateOrNull === null) {
|
|
475
|
+
throw new Error("Not started yet");
|
|
476
|
+
}
|
|
477
|
+
return this.currentStateOrNull;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Starts the machine by entering the initial state.
|
|
481
|
+
*/
|
|
482
|
+
start() {
|
|
483
|
+
if (this.runningState !== 0 /* NOT_STARTED_YET */) {
|
|
484
|
+
throw new Error("State machine has already started");
|
|
485
|
+
}
|
|
486
|
+
this.runningState = 1 /* STARTED */;
|
|
487
|
+
this.currentStateOrNull = this.initialState;
|
|
488
|
+
this.enter(null);
|
|
489
|
+
return this;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Stops the state machine. Stopping the state machine will call exit
|
|
493
|
+
* handlers for the current state, but not enter a new state.
|
|
494
|
+
*/
|
|
495
|
+
stop() {
|
|
496
|
+
if (this.runningState !== 1 /* STARTED */) {
|
|
497
|
+
throw new Error("Cannot stop a state machine that isn't started yet");
|
|
498
|
+
}
|
|
499
|
+
this.runningState = 2 /* STOPPED */;
|
|
500
|
+
this.exit(null);
|
|
501
|
+
this.currentStateOrNull = null;
|
|
502
|
+
}
|
|
503
|
+
constructor(initialContext) {
|
|
504
|
+
this.id = nextId++;
|
|
505
|
+
this.runningState = 0 /* NOT_STARTED_YET */;
|
|
506
|
+
this.currentStateOrNull = null;
|
|
507
|
+
this.states = /* @__PURE__ */ new Set();
|
|
508
|
+
this.enterFns = /* @__PURE__ */ new Map();
|
|
509
|
+
this.cleanupStack = [];
|
|
510
|
+
this.knownEventTypes = /* @__PURE__ */ new Set();
|
|
511
|
+
this.allowedTransitions = /* @__PURE__ */ new Map();
|
|
512
|
+
this.currentContext = new SafeContext(initialContext);
|
|
513
|
+
this.eventHub = {
|
|
514
|
+
didReceiveEvent: makeEventSource(),
|
|
515
|
+
willTransition: makeEventSource(),
|
|
516
|
+
didIgnoreEvent: makeEventSource(),
|
|
517
|
+
willExitState: makeEventSource(),
|
|
518
|
+
didEnterState: makeEventSource()
|
|
519
|
+
};
|
|
520
|
+
this.events = {
|
|
521
|
+
didReceiveEvent: this.eventHub.didReceiveEvent.observable,
|
|
522
|
+
willTransition: this.eventHub.willTransition.observable,
|
|
523
|
+
didIgnoreEvent: this.eventHub.didIgnoreEvent.observable,
|
|
524
|
+
willExitState: this.eventHub.willExitState.observable,
|
|
525
|
+
didEnterState: this.eventHub.didEnterState.observable
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
get context() {
|
|
529
|
+
return this.currentContext.current;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Define an explicit finite state in the state machine.
|
|
533
|
+
*/
|
|
534
|
+
addState(state) {
|
|
535
|
+
if (this.runningState !== 0 /* NOT_STARTED_YET */) {
|
|
536
|
+
throw new Error("Already started");
|
|
537
|
+
}
|
|
538
|
+
this.states.add(state);
|
|
539
|
+
return this;
|
|
540
|
+
}
|
|
541
|
+
onEnter(nameOrPattern, enterFn) {
|
|
542
|
+
if (this.runningState !== 0 /* NOT_STARTED_YET */) {
|
|
543
|
+
throw new Error("Already started");
|
|
544
|
+
} else if (this.enterFns.has(nameOrPattern)) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
// TODO We _currently_ don't support multiple .onEnters() for the same
|
|
547
|
+
// state, but this is not a fundamental limitation. Just not
|
|
548
|
+
// implemented yet. If we wanted to, we could make this an array.
|
|
549
|
+
`enter/exit function for ${nameOrPattern} already exists`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
this.enterFns.set(nameOrPattern, enterFn);
|
|
553
|
+
return this;
|
|
554
|
+
}
|
|
555
|
+
onEnterAsync(nameOrPattern, promiseFn, onOK, onError) {
|
|
556
|
+
return this.onEnter(nameOrPattern, () => {
|
|
557
|
+
let cancelled = false;
|
|
558
|
+
void promiseFn(this.currentContext.current).then(
|
|
559
|
+
// On OK
|
|
560
|
+
(data) => {
|
|
561
|
+
if (!cancelled) {
|
|
562
|
+
this.transition({ type: "ASYNC_OK", data }, onOK);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
// On Error
|
|
566
|
+
(reason) => {
|
|
567
|
+
if (!cancelled) {
|
|
568
|
+
this.transition({ type: "ASYNC_ERROR", reason }, onError);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
);
|
|
572
|
+
return () => {
|
|
573
|
+
cancelled = true;
|
|
574
|
+
};
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
getStatesMatching(nameOrPattern) {
|
|
578
|
+
const matches = [];
|
|
579
|
+
if (nameOrPattern === "*") {
|
|
580
|
+
for (const state of this.states) {
|
|
581
|
+
matches.push(state);
|
|
582
|
+
}
|
|
583
|
+
} else if (nameOrPattern.endsWith(".*")) {
|
|
584
|
+
const prefix = nameOrPattern.slice(0, -1);
|
|
585
|
+
for (const state of this.states) {
|
|
586
|
+
if (state.startsWith(prefix)) {
|
|
587
|
+
matches.push(state);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
const name = nameOrPattern;
|
|
592
|
+
if (this.states.has(name)) {
|
|
593
|
+
matches.push(name);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (matches.length === 0) {
|
|
597
|
+
throw new Error(`No states match ${JSON.stringify(nameOrPattern)}`);
|
|
598
|
+
}
|
|
599
|
+
return matches;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Define all allowed outgoing transitions for a state.
|
|
603
|
+
*
|
|
604
|
+
* The targets for each event can be defined as a function which returns the
|
|
605
|
+
* next state to transition to. These functions can look at the `event` or
|
|
606
|
+
* `context` params to conditionally decide which next state to transition
|
|
607
|
+
* to.
|
|
608
|
+
*
|
|
609
|
+
* If you set it to `null`, then the transition will be explicitly forbidden
|
|
610
|
+
* and throw an error. If you don't define a target for a transition, then
|
|
611
|
+
* such events will get ignored.
|
|
612
|
+
*/
|
|
613
|
+
addTransitions(nameOrPattern, mapping) {
|
|
614
|
+
if (this.runningState !== 0 /* NOT_STARTED_YET */) {
|
|
615
|
+
throw new Error("Already started");
|
|
616
|
+
}
|
|
617
|
+
for (const srcState of this.getStatesMatching(nameOrPattern)) {
|
|
618
|
+
let map = this.allowedTransitions.get(srcState);
|
|
619
|
+
if (map === void 0) {
|
|
620
|
+
map = /* @__PURE__ */ new Map();
|
|
621
|
+
this.allowedTransitions.set(srcState, map);
|
|
622
|
+
}
|
|
623
|
+
for (const [type, target_] of Object.entries(mapping)) {
|
|
624
|
+
if (map.has(type)) {
|
|
625
|
+
throw new Error(
|
|
626
|
+
`Trying to set transition "${type}" on "${srcState}" (via "${nameOrPattern}"), but a transition already exists there.`
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const target = target_;
|
|
630
|
+
this.knownEventTypes.add(type);
|
|
631
|
+
if (target !== void 0) {
|
|
632
|
+
const targetFn = typeof target === "function" ? target : () => target;
|
|
633
|
+
map.set(type, targetFn);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return this;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Like `.addTransition()`, but takes an (anonymous) transition whenever the
|
|
641
|
+
* timer fires.
|
|
642
|
+
*
|
|
643
|
+
* @param stateOrPattern The state name, or state group pattern name.
|
|
644
|
+
* @param after Number of milliseconds after which to take the
|
|
645
|
+
* transition. If in the mean time, another transition
|
|
646
|
+
* is taken, the timer will get cancelled.
|
|
647
|
+
* @param target The target state to go to.
|
|
648
|
+
*/
|
|
649
|
+
addTimedTransition(stateOrPattern, after2, target) {
|
|
650
|
+
return this.onEnter(stateOrPattern, () => {
|
|
651
|
+
const ms = typeof after2 === "function" ? after2(this.currentContext.current) : after2;
|
|
652
|
+
const timeoutID = setTimeout(() => {
|
|
653
|
+
this.transition({ type: "TIMER" }, target);
|
|
654
|
+
}, ms);
|
|
655
|
+
return () => {
|
|
656
|
+
clearTimeout(timeoutID);
|
|
657
|
+
};
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
getTargetFn(eventName) {
|
|
661
|
+
var _a;
|
|
662
|
+
return (_a = this.allowedTransitions.get(this.currentState)) == null ? void 0 : _a.get(eventName);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Exits the current state, and executes any necessary cleanup functions.
|
|
666
|
+
* Call this before changing the current state to the next state.
|
|
667
|
+
*
|
|
668
|
+
* @param levels Defines how many "levels" of nesting will be exited. For
|
|
669
|
+
* example, if you transition from `foo.bar.qux` to `foo.bar.baz`, then
|
|
670
|
+
* the level is 1. But if you transition from `foo.bar.qux` to `bla.bla`,
|
|
671
|
+
* then the level is 3.
|
|
672
|
+
*/
|
|
673
|
+
exit(levels) {
|
|
674
|
+
this.eventHub.willExitState.notify(this.currentState);
|
|
675
|
+
this.currentContext.allowPatching((patchableContext) => {
|
|
676
|
+
var _a;
|
|
677
|
+
levels = levels != null ? levels : this.cleanupStack.length;
|
|
678
|
+
for (let i = 0; i < levels; i++) {
|
|
679
|
+
(_a = this.cleanupStack.pop()) == null ? void 0 : _a(patchableContext);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Enters the current state, and executes any necessary onEnter handlers.
|
|
685
|
+
* Call this directly _after_ setting the current state to the next state.
|
|
686
|
+
*/
|
|
687
|
+
enter(levels) {
|
|
688
|
+
const enterPatterns = patterns(
|
|
689
|
+
this.currentState,
|
|
690
|
+
levels != null ? levels : this.currentState.split(".").length + 1
|
|
691
|
+
);
|
|
692
|
+
this.currentContext.allowPatching((patchableContext) => {
|
|
693
|
+
for (const pattern of enterPatterns) {
|
|
694
|
+
const enterFn = this.enterFns.get(pattern);
|
|
695
|
+
const cleanupFn = enterFn == null ? void 0 : enterFn(patchableContext);
|
|
696
|
+
if (typeof cleanupFn === "function") {
|
|
697
|
+
this.cleanupStack.push(cleanupFn);
|
|
698
|
+
} else {
|
|
699
|
+
this.cleanupStack.push(null);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
this.eventHub.didEnterState.notify(this.currentState);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Sends an event to the machine, which may cause an internal state
|
|
707
|
+
* transition to happen. When that happens, will trigger side effects.
|
|
708
|
+
*/
|
|
709
|
+
send(event) {
|
|
710
|
+
const targetFn = this.getTargetFn(event.type);
|
|
711
|
+
if (targetFn !== void 0) {
|
|
712
|
+
return this.transition(event, targetFn);
|
|
713
|
+
}
|
|
714
|
+
if (!this.knownEventTypes.has(event.type)) {
|
|
715
|
+
throw new Error(`Invalid event ${JSON.stringify(event.type)}`);
|
|
716
|
+
} else {
|
|
717
|
+
this.eventHub.didIgnoreEvent.notify(event);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
transition(event, target) {
|
|
721
|
+
this.eventHub.didReceiveEvent.notify(event);
|
|
722
|
+
const oldState = this.currentState;
|
|
723
|
+
const targetFn = typeof target === "function" ? target : () => target;
|
|
724
|
+
const nextTarget = targetFn(event, this.currentContext.current);
|
|
725
|
+
let nextState;
|
|
726
|
+
let effects = void 0;
|
|
727
|
+
if (nextTarget === null) {
|
|
728
|
+
this.eventHub.didIgnoreEvent.notify(event);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (typeof nextTarget === "string") {
|
|
732
|
+
nextState = nextTarget;
|
|
733
|
+
} else {
|
|
734
|
+
nextState = nextTarget.target;
|
|
735
|
+
effects = Array.isArray(nextTarget.effect) ? nextTarget.effect : [nextTarget.effect];
|
|
736
|
+
}
|
|
737
|
+
if (!this.states.has(nextState)) {
|
|
738
|
+
throw new Error(`Invalid next state name: ${JSON.stringify(nextState)}`);
|
|
739
|
+
}
|
|
740
|
+
this.eventHub.willTransition.notify({ from: oldState, to: nextState });
|
|
741
|
+
const [up, down] = distance(this.currentState, nextState);
|
|
742
|
+
if (up > 0) {
|
|
743
|
+
this.exit(up);
|
|
744
|
+
}
|
|
745
|
+
this.currentStateOrNull = nextState;
|
|
746
|
+
if (effects !== void 0) {
|
|
747
|
+
const effectsToRun = effects;
|
|
748
|
+
this.currentContext.allowPatching((patchableContext) => {
|
|
749
|
+
for (const effect of effectsToRun) {
|
|
750
|
+
if (typeof effect === "function") {
|
|
751
|
+
effect(patchableContext, event);
|
|
752
|
+
} else {
|
|
753
|
+
patchableContext.patch(effect);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
if (down > 0) {
|
|
759
|
+
this.enter(down);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
// src/lib/utils.ts
|
|
765
|
+
function isPlainObject(blob) {
|
|
766
|
+
return blob !== null && typeof blob === "object" && Object.prototype.toString.call(blob) === "[object Object]";
|
|
767
|
+
}
|
|
768
|
+
function fromEntries(iterable) {
|
|
769
|
+
const obj = {};
|
|
770
|
+
for (const [key, val] of iterable) {
|
|
771
|
+
obj[key] = val;
|
|
772
|
+
}
|
|
773
|
+
return obj;
|
|
774
|
+
}
|
|
775
|
+
function entries(obj) {
|
|
776
|
+
return Object.entries(obj);
|
|
777
|
+
}
|
|
778
|
+
function tryParseJson(rawMessage) {
|
|
779
|
+
try {
|
|
780
|
+
return JSON.parse(rawMessage);
|
|
781
|
+
} catch (e) {
|
|
782
|
+
return void 0;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function b64decode(b64value) {
|
|
786
|
+
try {
|
|
787
|
+
const formattedValue = b64value.replace(/-/g, "+").replace(/_/g, "/");
|
|
788
|
+
const decodedValue = decodeURIComponent(
|
|
789
|
+
atob(formattedValue).split("").map(function(c) {
|
|
790
|
+
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
|
791
|
+
}).join("")
|
|
792
|
+
);
|
|
793
|
+
return decodedValue;
|
|
794
|
+
} catch (err) {
|
|
795
|
+
return atob(b64value);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function compact(items) {
|
|
799
|
+
return items.filter(
|
|
800
|
+
(item) => item !== null && item !== void 0
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
function compactObject(obj) {
|
|
804
|
+
const newObj = __spreadValues({}, obj);
|
|
805
|
+
Object.keys(obj).forEach((k) => {
|
|
806
|
+
const key = k;
|
|
807
|
+
if (newObj[key] === void 0) {
|
|
808
|
+
delete newObj[key];
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
return newObj;
|
|
812
|
+
}
|
|
813
|
+
function withTimeout(promise, millis, errmsg = "Timed out") {
|
|
814
|
+
return __async(this, null, function* () {
|
|
815
|
+
let timerID;
|
|
816
|
+
const timer$ = new Promise((_, reject) => {
|
|
817
|
+
timerID = setTimeout(() => {
|
|
818
|
+
reject(new Error(errmsg));
|
|
819
|
+
}, millis);
|
|
820
|
+
});
|
|
821
|
+
return Promise.race([promise, timer$]).finally(() => clearTimeout(timerID));
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/connection.ts
|
|
826
|
+
function newToLegacyStatus(status) {
|
|
827
|
+
switch (status) {
|
|
828
|
+
case "connecting":
|
|
829
|
+
return "connecting";
|
|
830
|
+
case "connected":
|
|
831
|
+
return "open";
|
|
832
|
+
case "reconnecting":
|
|
833
|
+
return "unavailable";
|
|
834
|
+
case "disconnected":
|
|
835
|
+
return "failed";
|
|
836
|
+
case "initial":
|
|
837
|
+
return "closed";
|
|
838
|
+
default:
|
|
839
|
+
return "closed";
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function toNewConnectionStatus(machine) {
|
|
843
|
+
const state = machine.currentState;
|
|
844
|
+
switch (state) {
|
|
845
|
+
case "@ok.connected":
|
|
846
|
+
case "@ok.awaiting-pong":
|
|
847
|
+
return "connected";
|
|
848
|
+
case "@idle.initial":
|
|
849
|
+
return "initial";
|
|
850
|
+
case "@auth.busy":
|
|
851
|
+
case "@auth.backoff":
|
|
852
|
+
case "@connecting.busy":
|
|
853
|
+
case "@connecting.backoff":
|
|
854
|
+
return machine.context.successCount > 0 ? "reconnecting" : "connecting";
|
|
855
|
+
case "@idle.failed":
|
|
856
|
+
return "disconnected";
|
|
857
|
+
default:
|
|
858
|
+
return assertNever(state, "Unknown state");
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
var BACKOFF_DELAYS = [250, 500, 1e3, 2e3, 4e3, 8e3, 1e4];
|
|
862
|
+
var RESET_DELAY = BACKOFF_DELAYS[0] - 1;
|
|
863
|
+
var BACKOFF_DELAYS_SLOW = [2e3, 3e4, 6e4, 3e5];
|
|
864
|
+
var HEARTBEAT_INTERVAL = 3e4;
|
|
865
|
+
var PONG_TIMEOUT = 2e3;
|
|
866
|
+
var AUTH_TIMEOUT = 1e4;
|
|
867
|
+
var SOCKET_CONNECT_TIMEOUT = 1e4;
|
|
868
|
+
var StopRetrying = class extends Error {
|
|
869
|
+
constructor(reason) {
|
|
870
|
+
super(reason);
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
var LiveblocksError = class extends Error {
|
|
874
|
+
constructor(message, code) {
|
|
875
|
+
super(message);
|
|
876
|
+
this.code = code;
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
function nextBackoffDelay(currentDelay, delays = BACKOFF_DELAYS) {
|
|
880
|
+
var _a;
|
|
881
|
+
return (_a = delays.find((delay) => delay > currentDelay)) != null ? _a : delays[delays.length - 1];
|
|
882
|
+
}
|
|
883
|
+
function increaseBackoffDelay(context) {
|
|
884
|
+
context.patch({ backoffDelay: nextBackoffDelay(context.backoffDelay) });
|
|
885
|
+
}
|
|
886
|
+
function increaseBackoffDelayAggressively(context) {
|
|
887
|
+
context.patch({
|
|
888
|
+
backoffDelay: nextBackoffDelay(context.backoffDelay, BACKOFF_DELAYS_SLOW)
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
function resetSuccessCount(context) {
|
|
892
|
+
context.patch({ successCount: 0 });
|
|
893
|
+
}
|
|
894
|
+
function log(level, message) {
|
|
895
|
+
const logger = level === 2 /* ERROR */ ? error : level === 1 /* WARN */ ? warn : (
|
|
896
|
+
/* black hole */
|
|
897
|
+
() => {
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
return () => {
|
|
901
|
+
logger(message);
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function sendHeartbeat(ctx) {
|
|
905
|
+
var _a;
|
|
906
|
+
(_a = ctx.socket) == null ? void 0 : _a.send("ping");
|
|
907
|
+
}
|
|
908
|
+
function enableTracing(machine) {
|
|
909
|
+
const start = (/* @__PURE__ */ new Date()).getTime();
|
|
910
|
+
function log2(...args) {
|
|
911
|
+
warn(
|
|
912
|
+
`${(((/* @__PURE__ */ new Date()).getTime() - start) / 1e3).toFixed(2)} [FSM #${machine.id}]`,
|
|
913
|
+
...args
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
const unsubs = [
|
|
917
|
+
machine.events.didReceiveEvent.subscribe((e) => log2(`Event ${e.type}`)),
|
|
918
|
+
machine.events.willTransition.subscribe(
|
|
919
|
+
({ from, to }) => log2("Transitioning", from, "\u2192", to)
|
|
920
|
+
),
|
|
921
|
+
machine.events.didIgnoreEvent.subscribe(
|
|
922
|
+
(e) => log2("Ignored event", e.type, e, "(current state won't handle it)")
|
|
923
|
+
)
|
|
924
|
+
// machine.events.willExitState.subscribe((s) => log("Exiting state", s)),
|
|
925
|
+
// machine.events.didEnterState.subscribe((s) => log("Entering state", s)),
|
|
926
|
+
];
|
|
927
|
+
return () => {
|
|
928
|
+
for (const unsub of unsubs) {
|
|
929
|
+
unsub();
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
function defineConnectivityEvents(machine) {
|
|
934
|
+
const statusDidChange = makeEventSource();
|
|
935
|
+
const didConnect = makeEventSource();
|
|
936
|
+
const didDisconnect = makeEventSource();
|
|
937
|
+
let lastStatus = null;
|
|
938
|
+
const unsubscribe = machine.events.didEnterState.subscribe(() => {
|
|
939
|
+
const currStatus = toNewConnectionStatus(machine);
|
|
940
|
+
if (currStatus !== lastStatus) {
|
|
941
|
+
statusDidChange.notify(currStatus);
|
|
942
|
+
}
|
|
943
|
+
if (lastStatus === "connected" && currStatus !== "connected") {
|
|
944
|
+
didDisconnect.notify();
|
|
945
|
+
} else if (lastStatus !== "connected" && currStatus === "connected") {
|
|
946
|
+
didConnect.notify();
|
|
947
|
+
}
|
|
948
|
+
lastStatus = currStatus;
|
|
949
|
+
});
|
|
950
|
+
return {
|
|
951
|
+
statusDidChange: statusDidChange.observable,
|
|
952
|
+
didConnect: didConnect.observable,
|
|
953
|
+
didDisconnect: didDisconnect.observable,
|
|
954
|
+
unsubscribe
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
var assign = (patch) => (ctx) => ctx.patch(patch);
|
|
958
|
+
function createConnectionStateMachine(delegates, enableDebugLogging) {
|
|
959
|
+
const onMessage = makeEventSource();
|
|
960
|
+
onMessage.pause();
|
|
961
|
+
const onLiveblocksError = makeEventSource();
|
|
962
|
+
const initialContext = {
|
|
963
|
+
successCount: 0,
|
|
964
|
+
token: null,
|
|
965
|
+
socket: null,
|
|
966
|
+
backoffDelay: RESET_DELAY
|
|
967
|
+
};
|
|
968
|
+
const machine = new FSM(initialContext).addState("@idle.initial").addState("@idle.failed").addState("@auth.busy").addState("@auth.backoff").addState("@connecting.busy").addState("@connecting.backoff").addState("@ok.connected").addState("@ok.awaiting-pong");
|
|
969
|
+
machine.addTransitions("*", {
|
|
970
|
+
RECONNECT: {
|
|
971
|
+
target: "@auth.backoff",
|
|
972
|
+
effect: [increaseBackoffDelay, resetSuccessCount]
|
|
973
|
+
},
|
|
974
|
+
DISCONNECT: "@idle.initial"
|
|
975
|
+
});
|
|
976
|
+
machine.onEnter("@idle.*", resetSuccessCount).addTransitions("@idle.*", {
|
|
977
|
+
CONNECT: (_, ctx) => (
|
|
978
|
+
// If we still have a known token, try to reconnect to the socket directly,
|
|
979
|
+
// otherwise, try to obtain a new token
|
|
980
|
+
ctx.token !== null ? "@connecting.busy" : "@auth.busy"
|
|
981
|
+
)
|
|
982
|
+
});
|
|
983
|
+
machine.addTransitions("@auth.backoff", {
|
|
984
|
+
NAVIGATOR_ONLINE: {
|
|
985
|
+
target: "@auth.busy",
|
|
986
|
+
effect: assign({ backoffDelay: RESET_DELAY })
|
|
987
|
+
}
|
|
988
|
+
}).addTimedTransition(
|
|
989
|
+
"@auth.backoff",
|
|
990
|
+
(ctx) => ctx.backoffDelay,
|
|
991
|
+
"@auth.busy"
|
|
992
|
+
).onEnterAsync(
|
|
993
|
+
"@auth.busy",
|
|
994
|
+
() => withTimeout(delegates.authenticate(), AUTH_TIMEOUT),
|
|
995
|
+
// On successful authentication
|
|
996
|
+
(okEvent) => ({
|
|
997
|
+
target: "@connecting.busy",
|
|
998
|
+
effect: assign({
|
|
999
|
+
token: okEvent.data,
|
|
1000
|
+
backoffDelay: RESET_DELAY
|
|
1001
|
+
})
|
|
1002
|
+
}),
|
|
1003
|
+
// Auth failed
|
|
1004
|
+
(failedEvent) => {
|
|
1005
|
+
if (failedEvent.reason instanceof StopRetrying) {
|
|
1006
|
+
return {
|
|
1007
|
+
target: "@idle.failed",
|
|
1008
|
+
effect: log(2 /* ERROR */, failedEvent.reason.message)
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
target: "@auth.backoff",
|
|
1013
|
+
effect: [
|
|
1014
|
+
increaseBackoffDelay,
|
|
1015
|
+
log(
|
|
1016
|
+
2 /* ERROR */,
|
|
1017
|
+
`Authentication failed: ${failedEvent.reason instanceof Error ? failedEvent.reason.message : String(failedEvent.reason)}`
|
|
1018
|
+
)
|
|
1019
|
+
]
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
1023
|
+
const onSocketError = (event) => machine.send({ type: "EXPLICIT_SOCKET_ERROR", event });
|
|
1024
|
+
const onSocketClose = (event) => machine.send({ type: "EXPLICIT_SOCKET_CLOSE", event });
|
|
1025
|
+
const onSocketMessage = (event) => event.data === "pong" ? machine.send({ type: "PONG" }) : onMessage.notify(event);
|
|
1026
|
+
function teardownSocket(socket) {
|
|
1027
|
+
if (socket) {
|
|
1028
|
+
socket.removeEventListener("error", onSocketError);
|
|
1029
|
+
socket.removeEventListener("close", onSocketClose);
|
|
1030
|
+
socket.removeEventListener("message", onSocketMessage);
|
|
1031
|
+
socket.close();
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
machine.addTransitions("@connecting.backoff", {
|
|
1035
|
+
NAVIGATOR_ONLINE: {
|
|
1036
|
+
target: "@connecting.busy",
|
|
1037
|
+
effect: assign({ backoffDelay: RESET_DELAY })
|
|
1038
|
+
}
|
|
1039
|
+
}).addTimedTransition(
|
|
1040
|
+
"@connecting.backoff",
|
|
1041
|
+
(ctx) => ctx.backoffDelay,
|
|
1042
|
+
"@connecting.busy"
|
|
1043
|
+
).onEnterAsync(
|
|
1044
|
+
"@connecting.busy",
|
|
1045
|
+
//
|
|
1046
|
+
// Use the "createSocket" delegate function (provided to the
|
|
1047
|
+
// ManagedSocket) to create the actual WebSocket connection instance.
|
|
1048
|
+
// Then, set up all the necessary event listeners, and wait for the
|
|
1049
|
+
// "open" event to occur.
|
|
1050
|
+
//
|
|
1051
|
+
// When the "open" event happens, we're ready to transition to the
|
|
1052
|
+
// OK state. This is done by resolving the Promise.
|
|
1053
|
+
//
|
|
1054
|
+
(ctx) => __async(this, null, function* () {
|
|
1055
|
+
let capturedPrematureEvent = null;
|
|
1056
|
+
const connect$ = new Promise(
|
|
1057
|
+
(resolve, rej) => {
|
|
1058
|
+
if (ctx.token === null) {
|
|
1059
|
+
throw new Error("No auth token");
|
|
1060
|
+
}
|
|
1061
|
+
const socket = delegates.createSocket(ctx.token);
|
|
1062
|
+
function reject(event) {
|
|
1063
|
+
capturedPrematureEvent = event;
|
|
1064
|
+
socket.removeEventListener("message", onSocketMessage);
|
|
1065
|
+
rej(event);
|
|
1066
|
+
}
|
|
1067
|
+
socket.addEventListener("message", onSocketMessage);
|
|
1068
|
+
socket.addEventListener("error", reject);
|
|
1069
|
+
socket.addEventListener("close", reject);
|
|
1070
|
+
socket.addEventListener("open", () => {
|
|
1071
|
+
socket.addEventListener("error", onSocketError);
|
|
1072
|
+
socket.addEventListener("close", onSocketClose);
|
|
1073
|
+
const unsub = () => {
|
|
1074
|
+
socket.removeEventListener("error", reject);
|
|
1075
|
+
socket.removeEventListener("close", reject);
|
|
1076
|
+
};
|
|
1077
|
+
resolve([socket, unsub]);
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
);
|
|
1081
|
+
return withTimeout(connect$, SOCKET_CONNECT_TIMEOUT).then(
|
|
1082
|
+
//
|
|
1083
|
+
// Part 3:
|
|
1084
|
+
// By now, our "open" event has fired, and the promise has been
|
|
1085
|
+
// resolved. Two possible scenarios:
|
|
1086
|
+
//
|
|
1087
|
+
// 1. The happy path. Most likely.
|
|
1088
|
+
// 2. Uh-oh. A premature close/error event has been observed. Let's
|
|
1089
|
+
// reject the promise after all.
|
|
1090
|
+
//
|
|
1091
|
+
// Any close/error event that will get scheduled after this point
|
|
1092
|
+
// onwards, will be caught in the OK state, and dealt with
|
|
1093
|
+
// accordingly.
|
|
1094
|
+
//
|
|
1095
|
+
([socket, unsub]) => {
|
|
1096
|
+
unsub();
|
|
1097
|
+
if (capturedPrematureEvent) {
|
|
1098
|
+
throw capturedPrematureEvent;
|
|
1099
|
+
}
|
|
1100
|
+
return socket;
|
|
1101
|
+
}
|
|
1102
|
+
);
|
|
1103
|
+
}),
|
|
1104
|
+
// Only transition to OK state after a successfully opened WebSocket connection
|
|
1105
|
+
(okEvent) => ({
|
|
1106
|
+
target: "@ok.connected",
|
|
1107
|
+
effect: assign({
|
|
1108
|
+
socket: okEvent.data,
|
|
1109
|
+
backoffDelay: RESET_DELAY
|
|
1110
|
+
})
|
|
1111
|
+
}),
|
|
1112
|
+
// If the WebSocket connection cannot be established
|
|
1113
|
+
(failure) => {
|
|
1114
|
+
const err = failure.reason;
|
|
1115
|
+
if (err instanceof StopRetrying) {
|
|
1116
|
+
return {
|
|
1117
|
+
target: "@idle.failed",
|
|
1118
|
+
effect: log(2 /* ERROR */, err.message)
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
return {
|
|
1122
|
+
target: "@auth.backoff",
|
|
1123
|
+
effect: [
|
|
1124
|
+
// Increase the backoff delay conditionally
|
|
1125
|
+
// TODO: This is ugly. DRY this up with the other code 40xx checks elsewhere.
|
|
1126
|
+
!(err instanceof Error) && err.type === "close" && err.code >= 4e3 && err.code <= 4100 ? increaseBackoffDelayAggressively : increaseBackoffDelay,
|
|
1127
|
+
// Produce a useful log message
|
|
1128
|
+
(ctx) => {
|
|
1129
|
+
if (err instanceof Error) {
|
|
1130
|
+
warn(String(err));
|
|
1131
|
+
} else {
|
|
1132
|
+
warn(
|
|
1133
|
+
err.type === "close" ? `Connection to Liveblocks websocket server closed prematurely (code: ${err.code}). Retrying in ${ctx.backoffDelay}ms.` : "Connection to Liveblocks websocket server could not be established."
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
]
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
);
|
|
1141
|
+
machine.addTimedTransition("@ok.connected", HEARTBEAT_INTERVAL, {
|
|
1142
|
+
target: "@ok.awaiting-pong",
|
|
1143
|
+
effect: sendHeartbeat
|
|
1144
|
+
}).addTransitions("@ok.connected", {
|
|
1145
|
+
WINDOW_GOT_FOCUS: { target: "@ok.awaiting-pong", effect: sendHeartbeat }
|
|
1146
|
+
});
|
|
1147
|
+
const noPongAction = {
|
|
1148
|
+
target: "@connecting.busy",
|
|
1149
|
+
// Log implicit connection loss and drop the current open socket
|
|
1150
|
+
effect: log(
|
|
1151
|
+
1 /* WARN */,
|
|
1152
|
+
"Received no pong from server, assume implicit connection loss."
|
|
1153
|
+
)
|
|
1154
|
+
};
|
|
1155
|
+
machine.onEnter("@ok.*", (ctx) => {
|
|
1156
|
+
ctx.patch({ successCount: ctx.successCount + 1 });
|
|
1157
|
+
const timerID = setTimeout(
|
|
1158
|
+
// On the next tick, start delivering all messages that have already
|
|
1159
|
+
// been received, and continue synchronous delivery of all future
|
|
1160
|
+
// incoming messages.
|
|
1161
|
+
onMessage.unpause,
|
|
1162
|
+
0
|
|
1163
|
+
);
|
|
1164
|
+
return (ctx2) => {
|
|
1165
|
+
teardownSocket(ctx2.socket);
|
|
1166
|
+
ctx2.patch({ socket: null });
|
|
1167
|
+
clearTimeout(timerID);
|
|
1168
|
+
onMessage.pause();
|
|
1169
|
+
};
|
|
1170
|
+
}).addTimedTransition("@ok.awaiting-pong", PONG_TIMEOUT, noPongAction).addTransitions("@ok.awaiting-pong", { PONG_TIMEOUT: noPongAction }).addTransitions("@ok.awaiting-pong", { PONG: "@ok.connected" }).addTransitions("@ok.*", {
|
|
1171
|
+
// When a socket receives an error, this can cause the closing of the
|
|
1172
|
+
// socket, or not. So always check to see if the socket is still OPEN or
|
|
1173
|
+
// not. When still OPEN, don't transition.
|
|
1174
|
+
EXPLICIT_SOCKET_ERROR: (_, context) => {
|
|
1175
|
+
var _a;
|
|
1176
|
+
if (((_a = context.socket) == null ? void 0 : _a.readyState) === 1) {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
return {
|
|
1180
|
+
target: "@connecting.backoff",
|
|
1181
|
+
effect: increaseBackoffDelay
|
|
1182
|
+
};
|
|
1183
|
+
},
|
|
1184
|
+
EXPLICIT_SOCKET_CLOSE: (e) => {
|
|
1185
|
+
if (e.event.code === 4999) {
|
|
1186
|
+
return {
|
|
1187
|
+
target: "@idle.failed",
|
|
1188
|
+
effect: log(
|
|
1189
|
+
1 /* WARN */,
|
|
1190
|
+
"Connection to WebSocket closed permanently. Won't retry."
|
|
1191
|
+
)
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
if (e.event.code >= 4e3 && e.event.code <= 4100) {
|
|
1195
|
+
return {
|
|
1196
|
+
target: "@connecting.backoff",
|
|
1197
|
+
effect: [
|
|
1198
|
+
increaseBackoffDelayAggressively,
|
|
1199
|
+
(ctx) => warn(
|
|
1200
|
+
`Connection to Liveblocks websocket server closed (code: ${e.event.code}). Retrying in ${ctx.backoffDelay}ms.`
|
|
1201
|
+
),
|
|
1202
|
+
(_, { event }) => {
|
|
1203
|
+
if (event.code >= 4e3 && event.code <= 4100) {
|
|
1204
|
+
const err = new LiveblocksError(event.reason, event.code);
|
|
1205
|
+
onLiveblocksError.notify(err);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
]
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
return {
|
|
1212
|
+
target: "@connecting.backoff",
|
|
1213
|
+
effect: [
|
|
1214
|
+
increaseBackoffDelay,
|
|
1215
|
+
(ctx) => warn(
|
|
1216
|
+
`Connection to Liveblocks websocket server closed (code: ${e.event.code}). Retrying in ${ctx.backoffDelay}ms.`
|
|
1217
|
+
)
|
|
1218
|
+
]
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
if (typeof document !== "undefined") {
|
|
1223
|
+
const doc = typeof document !== "undefined" ? document : void 0;
|
|
1224
|
+
const win = typeof window !== "undefined" ? window : void 0;
|
|
1225
|
+
const root = win != null ? win : doc;
|
|
1226
|
+
machine.onEnter("*", (ctx) => {
|
|
1227
|
+
function onBackOnline() {
|
|
1228
|
+
machine.send({ type: "NAVIGATOR_ONLINE" });
|
|
1229
|
+
}
|
|
1230
|
+
function onVisibilityChange() {
|
|
1231
|
+
if ((doc == null ? void 0 : doc.visibilityState) === "visible") {
|
|
1232
|
+
machine.send({ type: "WINDOW_GOT_FOCUS" });
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
win == null ? void 0 : win.addEventListener("online", onBackOnline);
|
|
1236
|
+
root == null ? void 0 : root.addEventListener("visibilitychange", onVisibilityChange);
|
|
1237
|
+
return () => {
|
|
1238
|
+
root == null ? void 0 : root.removeEventListener("visibilitychange", onVisibilityChange);
|
|
1239
|
+
win == null ? void 0 : win.removeEventListener("online", onBackOnline);
|
|
1240
|
+
teardownSocket(ctx.socket);
|
|
1241
|
+
};
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
const cleanups = [];
|
|
1245
|
+
const { statusDidChange, didConnect, didDisconnect, unsubscribe } = defineConnectivityEvents(machine);
|
|
1246
|
+
cleanups.push(unsubscribe);
|
|
1247
|
+
if (enableDebugLogging) {
|
|
1248
|
+
cleanups.push(enableTracing(machine));
|
|
1249
|
+
}
|
|
1250
|
+
machine.start();
|
|
1251
|
+
return {
|
|
1252
|
+
machine,
|
|
1253
|
+
cleanups,
|
|
1254
|
+
// Observable events that will be emitted by this machine
|
|
1255
|
+
events: {
|
|
1256
|
+
statusDidChange,
|
|
1257
|
+
didConnect,
|
|
1258
|
+
didDisconnect,
|
|
1259
|
+
onMessage: onMessage.observable,
|
|
1260
|
+
onLiveblocksError: onLiveblocksError.observable
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
var ManagedSocket = class {
|
|
1265
|
+
constructor(delegates, enableDebugLogging = false) {
|
|
1266
|
+
const { machine, events, cleanups } = createConnectionStateMachine(
|
|
1267
|
+
delegates,
|
|
1268
|
+
enableDebugLogging
|
|
1269
|
+
);
|
|
1270
|
+
this.machine = machine;
|
|
1271
|
+
this.events = events;
|
|
1272
|
+
this.cleanups = cleanups;
|
|
1273
|
+
}
|
|
1274
|
+
getLegacyStatus() {
|
|
1275
|
+
return newToLegacyStatus(this.getStatus());
|
|
1276
|
+
}
|
|
1277
|
+
getStatus() {
|
|
1278
|
+
try {
|
|
1279
|
+
return toNewConnectionStatus(this.machine);
|
|
1280
|
+
} catch (e) {
|
|
1281
|
+
return "initial";
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Returns the current auth token.
|
|
1286
|
+
*/
|
|
1287
|
+
get token() {
|
|
1288
|
+
return this.machine.context.token;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Call this method to try to connect to a WebSocket. This only has an effect
|
|
1292
|
+
* if the machine is idle at the moment, otherwise this is a no-op.
|
|
1293
|
+
*/
|
|
1294
|
+
connect() {
|
|
1295
|
+
this.machine.send({ type: "CONNECT" });
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* If idle, will try to connect. Otherwise, it will attempt to reconnect to
|
|
1299
|
+
* the socket, potentially obtaining a new token first, if needed.
|
|
1300
|
+
*/
|
|
1301
|
+
reconnect() {
|
|
1302
|
+
this.machine.send({ type: "RECONNECT" });
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Call this method to disconnect from the current WebSocket. Is going to be
|
|
1306
|
+
* a no-op if there is no active connection.
|
|
1307
|
+
*/
|
|
1308
|
+
disconnect() {
|
|
1309
|
+
this.machine.send({ type: "DISCONNECT" });
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Call this to stop the machine and run necessary cleanup functions. After
|
|
1313
|
+
* calling destroy(), you can no longer use this instance. Call this before
|
|
1314
|
+
* letting the instance get garbage collected.
|
|
1315
|
+
*/
|
|
1316
|
+
destroy() {
|
|
1317
|
+
this.machine.stop();
|
|
1318
|
+
let cleanup;
|
|
1319
|
+
while (cleanup = this.cleanups.pop()) {
|
|
1320
|
+
cleanup();
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Safely send a message to the current WebSocket connection. Will emit a log
|
|
1325
|
+
* message if this is somehow impossible.
|
|
1326
|
+
*/
|
|
1327
|
+
send(data) {
|
|
1328
|
+
var _a;
|
|
1329
|
+
const socket = (_a = this.machine.context) == null ? void 0 : _a.socket;
|
|
1330
|
+
if (socket === null) {
|
|
1331
|
+
warn("Cannot send: not connected yet", data);
|
|
1332
|
+
} else if (socket.readyState !== 1) {
|
|
1333
|
+
warn("Cannot send: WebSocket no longer open", data);
|
|
1334
|
+
} else {
|
|
1335
|
+
socket.send(data);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* NOTE: Used by the E2E app only, to simulate explicit events.
|
|
1340
|
+
* Not ideal to keep exposed :(
|
|
1341
|
+
*/
|
|
1342
|
+
_privateSendMachineEvent(event) {
|
|
1343
|
+
this.machine.send(event);
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
|
|
348
1347
|
// src/lib/position.ts
|
|
349
1348
|
var MIN_CODE = 32;
|
|
350
1349
|
var MAX_CODE = 126;
|
|
@@ -626,85 +1625,35 @@ var AbstractCrdt = class {
|
|
|
626
1625
|
*/
|
|
627
1626
|
invalidate() {
|
|
628
1627
|
if (this._cachedImmutable !== void 0 || this._cachedTreeNode !== void 0) {
|
|
629
|
-
this._cachedImmutable = void 0;
|
|
630
|
-
this._cachedTreeNode = void 0;
|
|
631
|
-
if (this.parent.type === "HasParent") {
|
|
632
|
-
this.parent.node.invalidate();
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* @internal
|
|
638
|
-
*
|
|
639
|
-
* Return an snapshot of this Live tree for use in DevTools.
|
|
640
|
-
*/
|
|
641
|
-
toTreeNode(key) {
|
|
642
|
-
if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
|
|
643
|
-
this._cachedTreeNodeKey = key;
|
|
644
|
-
this._cachedTreeNode = this._toTreeNode(key);
|
|
645
|
-
}
|
|
646
|
-
return this._cachedTreeNode;
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Return an immutable snapshot of this Live node and its children.
|
|
650
|
-
*/
|
|
651
|
-
toImmutable() {
|
|
652
|
-
if (this._cachedImmutable === void 0) {
|
|
653
|
-
this._cachedImmutable = this._toImmutable();
|
|
654
|
-
}
|
|
655
|
-
return this._cachedImmutable;
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
// src/lib/utils.ts
|
|
660
|
-
function isPlainObject(blob) {
|
|
661
|
-
return blob !== null && typeof blob === "object" && Object.prototype.toString.call(blob) === "[object Object]";
|
|
662
|
-
}
|
|
663
|
-
function fromEntries(iterable) {
|
|
664
|
-
const obj = {};
|
|
665
|
-
for (const [key, val] of iterable) {
|
|
666
|
-
obj[key] = val;
|
|
667
|
-
}
|
|
668
|
-
return obj;
|
|
669
|
-
}
|
|
670
|
-
function entries(obj) {
|
|
671
|
-
return Object.entries(obj);
|
|
672
|
-
}
|
|
673
|
-
function tryParseJson(rawMessage) {
|
|
674
|
-
try {
|
|
675
|
-
return JSON.parse(rawMessage);
|
|
676
|
-
} catch (e) {
|
|
677
|
-
return void 0;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
function b64decode(b64value) {
|
|
681
|
-
try {
|
|
682
|
-
const formattedValue = b64value.replace(/-/g, "+").replace(/_/g, "/");
|
|
683
|
-
const decodedValue = decodeURIComponent(
|
|
684
|
-
atob(formattedValue).split("").map(function(c) {
|
|
685
|
-
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
|
686
|
-
}).join("")
|
|
687
|
-
);
|
|
688
|
-
return decodedValue;
|
|
689
|
-
} catch (err) {
|
|
690
|
-
return atob(b64value);
|
|
1628
|
+
this._cachedImmutable = void 0;
|
|
1629
|
+
this._cachedTreeNode = void 0;
|
|
1630
|
+
if (this.parent.type === "HasParent") {
|
|
1631
|
+
this.parent.node.invalidate();
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
691
1634
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const key = k;
|
|
702
|
-
if (newObj[key] === void 0) {
|
|
703
|
-
delete newObj[key];
|
|
1635
|
+
/**
|
|
1636
|
+
* @internal
|
|
1637
|
+
*
|
|
1638
|
+
* Return an snapshot of this Live tree for use in DevTools.
|
|
1639
|
+
*/
|
|
1640
|
+
toTreeNode(key) {
|
|
1641
|
+
if (this._cachedTreeNode === void 0 || this._cachedTreeNodeKey !== key) {
|
|
1642
|
+
this._cachedTreeNodeKey = key;
|
|
1643
|
+
this._cachedTreeNode = this._toTreeNode(key);
|
|
704
1644
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1645
|
+
return this._cachedTreeNode;
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Return an immutable snapshot of this Live node and its children.
|
|
1649
|
+
*/
|
|
1650
|
+
toImmutable() {
|
|
1651
|
+
if (this._cachedImmutable === void 0) {
|
|
1652
|
+
this._cachedImmutable = this._toImmutable();
|
|
1653
|
+
}
|
|
1654
|
+
return this._cachedImmutable;
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
708
1657
|
|
|
709
1658
|
// src/protocol/SerializedCrdt.ts
|
|
710
1659
|
var CrdtType = /* @__PURE__ */ ((CrdtType2) => {
|
|
@@ -3239,35 +4188,12 @@ var DerivedRef = class extends ImmutableRef {
|
|
|
3239
4188
|
}
|
|
3240
4189
|
};
|
|
3241
4190
|
|
|
3242
|
-
// src/types/IWebSocket.ts
|
|
3243
|
-
var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
|
|
3244
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
|
|
3245
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["INVALID_MESSAGE_FORMAT"] = 4e3] = "INVALID_MESSAGE_FORMAT";
|
|
3246
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
|
|
3247
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
|
|
3248
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
|
|
3249
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
|
|
3250
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
|
|
3251
|
-
WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
|
|
3252
|
-
return WebsocketCloseCodes2;
|
|
3253
|
-
})(WebsocketCloseCodes || {});
|
|
3254
|
-
|
|
3255
4191
|
// src/room.ts
|
|
3256
|
-
var BACKOFF_RETRY_DELAYS = [250, 500, 1e3, 2e3, 4e3, 8e3, 1e4];
|
|
3257
|
-
var BACKOFF_RETRY_DELAYS_SLOW = [2e3, 3e4, 6e4, 3e5];
|
|
3258
|
-
var HEARTBEAT_INTERVAL = 3e4;
|
|
3259
|
-
var PONG_TIMEOUT = 2e3;
|
|
3260
4192
|
var MAX_MESSAGE_SIZE = 1024 * 1024 - 128;
|
|
3261
4193
|
function makeIdFactory(connectionId) {
|
|
3262
4194
|
let count = 0;
|
|
3263
4195
|
return () => `${connectionId}:${count++}`;
|
|
3264
4196
|
}
|
|
3265
|
-
function log(..._params) {
|
|
3266
|
-
return;
|
|
3267
|
-
}
|
|
3268
|
-
function isConnectionSelfAware(connection) {
|
|
3269
|
-
return connection.status === "open" || connection.status === "connecting";
|
|
3270
|
-
}
|
|
3271
4197
|
function userToTreeNode(key, user) {
|
|
3272
4198
|
return {
|
|
3273
4199
|
type: "User",
|
|
@@ -3277,21 +4203,27 @@ function userToTreeNode(key, user) {
|
|
|
3277
4203
|
};
|
|
3278
4204
|
}
|
|
3279
4205
|
function createRoom(options, config) {
|
|
3280
|
-
var _a;
|
|
4206
|
+
var _a, _b, _c, _d;
|
|
3281
4207
|
const initialPresence = typeof options.initialPresence === "function" ? options.initialPresence(config.roomId) : options.initialPresence;
|
|
3282
4208
|
const initialStorage = typeof options.initialStorage === "function" ? options.initialStorage(config.roomId) : options.initialStorage;
|
|
4209
|
+
const delegates = (_c = config.delegates) != null ? _c : {
|
|
4210
|
+
authenticate: makeAuthDelegateForRoom(
|
|
4211
|
+
config.roomId,
|
|
4212
|
+
config.authentication,
|
|
4213
|
+
(_a = config.polyfills) == null ? void 0 : _a.fetch
|
|
4214
|
+
),
|
|
4215
|
+
createSocket: makeCreateSocketDelegateForRoom(
|
|
4216
|
+
config.liveblocksServer,
|
|
4217
|
+
(_b = config.polyfills) == null ? void 0 : _b.WebSocket
|
|
4218
|
+
)
|
|
4219
|
+
};
|
|
4220
|
+
const managedSocket = new ManagedSocket(
|
|
4221
|
+
delegates,
|
|
4222
|
+
config.enableDebugLogging
|
|
4223
|
+
);
|
|
3283
4224
|
const context = {
|
|
3284
|
-
token: null,
|
|
3285
|
-
lastConnectionId: null,
|
|
3286
|
-
socket: null,
|
|
3287
|
-
numRetries: 0,
|
|
3288
|
-
timers: {
|
|
3289
|
-
flush: void 0,
|
|
3290
|
-
reconnect: void 0,
|
|
3291
|
-
heartbeat: void 0,
|
|
3292
|
-
pongTimeout: void 0
|
|
3293
|
-
},
|
|
3294
4225
|
buffer: {
|
|
4226
|
+
flushTimerID: void 0,
|
|
3295
4227
|
lastFlushedAt: 0,
|
|
3296
4228
|
me: (
|
|
3297
4229
|
// Queue up the initial presence message as a Full Presence™ update
|
|
@@ -3303,7 +4235,7 @@ function createRoom(options, config) {
|
|
|
3303
4235
|
messages: [],
|
|
3304
4236
|
storageOperations: []
|
|
3305
4237
|
},
|
|
3306
|
-
|
|
4238
|
+
sessionInfo: new ValueRef(null),
|
|
3307
4239
|
me: new MeRef(initialPresence),
|
|
3308
4240
|
others: new OthersRef(),
|
|
3309
4241
|
initialStorage,
|
|
@@ -3322,7 +4254,91 @@ function createRoom(options, config) {
|
|
|
3322
4254
|
opStackTraces: process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new Map() : void 0
|
|
3323
4255
|
};
|
|
3324
4256
|
const doNotBatchUpdates = (cb) => cb();
|
|
3325
|
-
const batchUpdates = (
|
|
4257
|
+
const batchUpdates = (_d = config.unstable_batchedUpdates) != null ? _d : doNotBatchUpdates;
|
|
4258
|
+
let lastToken;
|
|
4259
|
+
function onStatusDidChange(newStatus) {
|
|
4260
|
+
var _a2;
|
|
4261
|
+
const token = (_a2 = managedSocket.token) == null ? void 0 : _a2.parsed;
|
|
4262
|
+
if (token !== void 0 && token !== lastToken) {
|
|
4263
|
+
context.sessionInfo.set({
|
|
4264
|
+
id: token.actor,
|
|
4265
|
+
userInfo: token.info,
|
|
4266
|
+
userId: token.id,
|
|
4267
|
+
isReadOnly: isStorageReadOnly(token.scopes)
|
|
4268
|
+
});
|
|
4269
|
+
lastToken = token;
|
|
4270
|
+
}
|
|
4271
|
+
batchUpdates(() => {
|
|
4272
|
+
eventHub.status.notify(newStatus);
|
|
4273
|
+
eventHub.connection.notify(newToLegacyStatus(newStatus));
|
|
4274
|
+
});
|
|
4275
|
+
}
|
|
4276
|
+
let _connectionLossTimerId;
|
|
4277
|
+
let _hasLostConnection = false;
|
|
4278
|
+
function handleConnectionLossEvent(newStatus) {
|
|
4279
|
+
if (newStatus === "reconnecting") {
|
|
4280
|
+
_connectionLossTimerId = setTimeout(() => {
|
|
4281
|
+
batchUpdates(() => {
|
|
4282
|
+
eventHub.lostConnection.notify("lost");
|
|
4283
|
+
_hasLostConnection = true;
|
|
4284
|
+
context.others.clearOthers();
|
|
4285
|
+
notify({ others: [{ type: "reset" }] }, doNotBatchUpdates);
|
|
4286
|
+
});
|
|
4287
|
+
}, config.lostConnectionTimeout);
|
|
4288
|
+
} else {
|
|
4289
|
+
clearTimeout(_connectionLossTimerId);
|
|
4290
|
+
if (_hasLostConnection) {
|
|
4291
|
+
if (newStatus === "disconnected") {
|
|
4292
|
+
batchUpdates(() => {
|
|
4293
|
+
eventHub.lostConnection.notify("failed");
|
|
4294
|
+
});
|
|
4295
|
+
} else {
|
|
4296
|
+
batchUpdates(() => {
|
|
4297
|
+
eventHub.lostConnection.notify("restored");
|
|
4298
|
+
});
|
|
4299
|
+
}
|
|
4300
|
+
_hasLostConnection = false;
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
function onDidConnect() {
|
|
4305
|
+
const sessionInfo = context.sessionInfo.current;
|
|
4306
|
+
if (sessionInfo === null) {
|
|
4307
|
+
throw new Error("Unexpected missing session info");
|
|
4308
|
+
}
|
|
4309
|
+
context.buffer.me = {
|
|
4310
|
+
type: "full",
|
|
4311
|
+
data: (
|
|
4312
|
+
// Because context.me.current is a readonly object, we'll have to
|
|
4313
|
+
// make a copy here. Otherwise, type errors happen later when
|
|
4314
|
+
// "patching" my presence.
|
|
4315
|
+
__spreadValues({}, context.me.current)
|
|
4316
|
+
)
|
|
4317
|
+
};
|
|
4318
|
+
context.idFactory = makeIdFactory(sessionInfo.id);
|
|
4319
|
+
if (context.root) {
|
|
4320
|
+
context.buffer.messages.push({ type: 200 /* FETCH_STORAGE */ });
|
|
4321
|
+
}
|
|
4322
|
+
tryFlushing();
|
|
4323
|
+
}
|
|
4324
|
+
function onDidDisconnect() {
|
|
4325
|
+
clearTimeout(context.buffer.flushTimerID);
|
|
4326
|
+
}
|
|
4327
|
+
managedSocket.events.onMessage.subscribe(handleServerMessage);
|
|
4328
|
+
managedSocket.events.statusDidChange.subscribe(onStatusDidChange);
|
|
4329
|
+
managedSocket.events.statusDidChange.subscribe(handleConnectionLossEvent);
|
|
4330
|
+
managedSocket.events.didConnect.subscribe(onDidConnect);
|
|
4331
|
+
managedSocket.events.didDisconnect.subscribe(onDidDisconnect);
|
|
4332
|
+
managedSocket.events.onLiveblocksError.subscribe((err) => {
|
|
4333
|
+
batchUpdates(() => {
|
|
4334
|
+
if (process.env.NODE_ENV !== "production") {
|
|
4335
|
+
error(
|
|
4336
|
+
`Connection to websocket server closed. Reason: ${err.message} (code: ${err.code}).`
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
eventHub.error.notify(err);
|
|
4340
|
+
});
|
|
4341
|
+
});
|
|
3326
4342
|
const pool = {
|
|
3327
4343
|
roomId: config.roomId,
|
|
3328
4344
|
getNode: (id) => context.nodes.get(id),
|
|
@@ -3364,7 +4380,8 @@ function createRoom(options, config) {
|
|
|
3364
4380
|
}
|
|
3365
4381
|
},
|
|
3366
4382
|
assertStorageIsWritable: () => {
|
|
3367
|
-
|
|
4383
|
+
var _a2;
|
|
4384
|
+
if ((_a2 = context.sessionInfo.current) == null ? void 0 : _a2.isReadOnly) {
|
|
3368
4385
|
throw new Error(
|
|
3369
4386
|
"Cannot write to storage with a read only user, please ensure the user has write permissions"
|
|
3370
4387
|
);
|
|
@@ -3372,81 +4389,55 @@ function createRoom(options, config) {
|
|
|
3372
4389
|
}
|
|
3373
4390
|
};
|
|
3374
4391
|
const eventHub = {
|
|
4392
|
+
connection: makeEventSource(),
|
|
4393
|
+
// Old/deprecated API
|
|
4394
|
+
status: makeEventSource(),
|
|
4395
|
+
// New/recommended API
|
|
4396
|
+
lostConnection: makeEventSource(),
|
|
3375
4397
|
customEvent: makeEventSource(),
|
|
3376
4398
|
me: makeEventSource(),
|
|
3377
4399
|
others: makeEventSource(),
|
|
3378
4400
|
error: makeEventSource(),
|
|
3379
|
-
connection: makeEventSource(),
|
|
3380
4401
|
storage: makeEventSource(),
|
|
3381
4402
|
history: makeEventSource(),
|
|
3382
4403
|
storageDidLoad: makeEventSource(),
|
|
3383
4404
|
storageStatus: makeEventSource()
|
|
3384
4405
|
};
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
void auth().then((token) => {
|
|
3394
|
-
if (context.connection.current.status !== "authenticating") {
|
|
3395
|
-
return;
|
|
3396
|
-
}
|
|
3397
|
-
const socket = createWebSocket(token);
|
|
3398
|
-
handleAuthSuccess(token.parsed, socket);
|
|
3399
|
-
context.token = token;
|
|
3400
|
-
}).catch(
|
|
3401
|
-
(er) => authenticationFailure(
|
|
3402
|
-
er instanceof Error ? er : new Error(String(er))
|
|
3403
|
-
)
|
|
3404
|
-
);
|
|
3405
|
-
return void 0;
|
|
3406
|
-
}
|
|
3407
|
-
},
|
|
3408
|
-
send(messageOrMessages) {
|
|
3409
|
-
var _a2, _b;
|
|
3410
|
-
if (context.socket === null) {
|
|
3411
|
-
throw new Error("Can't send message if socket is null");
|
|
3412
|
-
}
|
|
3413
|
-
if (context.socket.readyState === context.socket.OPEN) {
|
|
3414
|
-
const message = JSON.stringify(messageOrMessages);
|
|
3415
|
-
if (config.unstable_fallbackToHTTP) {
|
|
3416
|
-
const size = new TextEncoder().encode(message).length;
|
|
3417
|
-
if (size > MAX_MESSAGE_SIZE && ((_a2 = context.token) == null ? void 0 : _a2.raw) && config.httpSendEndpoint) {
|
|
3418
|
-
if (isTokenExpired(context.token.parsed)) {
|
|
3419
|
-
return reconnect();
|
|
3420
|
-
}
|
|
3421
|
-
void httpSend(
|
|
3422
|
-
message,
|
|
3423
|
-
context.token.raw,
|
|
3424
|
-
config.httpSendEndpoint,
|
|
3425
|
-
(_b = config.polyfills) == null ? void 0 : _b.fetch
|
|
3426
|
-
);
|
|
3427
|
-
warn(
|
|
3428
|
-
"Message was too large for websockets and sent over HTTP instead"
|
|
3429
|
-
);
|
|
3430
|
-
return;
|
|
3431
|
-
}
|
|
4406
|
+
function sendMessages(messageOrMessages) {
|
|
4407
|
+
var _a2, _b2;
|
|
4408
|
+
const message = JSON.stringify(messageOrMessages);
|
|
4409
|
+
if (config.unstable_fallbackToHTTP) {
|
|
4410
|
+
const size = new TextEncoder().encode(message).length;
|
|
4411
|
+
if (size > MAX_MESSAGE_SIZE && ((_a2 = managedSocket.token) == null ? void 0 : _a2.raw) && config.httpSendEndpoint) {
|
|
4412
|
+
if (isTokenExpired(managedSocket.token.parsed)) {
|
|
4413
|
+
return managedSocket.reconnect();
|
|
3432
4414
|
}
|
|
3433
|
-
|
|
4415
|
+
void httpSend(
|
|
4416
|
+
message,
|
|
4417
|
+
managedSocket.token.raw,
|
|
4418
|
+
config.httpSendEndpoint,
|
|
4419
|
+
(_b2 = config.polyfills) == null ? void 0 : _b2.fetch
|
|
4420
|
+
);
|
|
4421
|
+
warn(
|
|
4422
|
+
"Message was too large for websockets and sent over HTTP instead"
|
|
4423
|
+
);
|
|
4424
|
+
return;
|
|
3434
4425
|
}
|
|
3435
|
-
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
schedulePongTimeout: () => setTimeout(pongTimeout, PONG_TIMEOUT)
|
|
3439
|
-
};
|
|
4426
|
+
}
|
|
4427
|
+
managedSocket.send(message);
|
|
4428
|
+
}
|
|
3440
4429
|
const self = new DerivedRef(
|
|
3441
|
-
context.
|
|
4430
|
+
context.sessionInfo,
|
|
3442
4431
|
context.me,
|
|
3443
|
-
(
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
4432
|
+
(info, me) => {
|
|
4433
|
+
return info !== null ? {
|
|
4434
|
+
connectionId: info.id,
|
|
4435
|
+
id: info.userId,
|
|
4436
|
+
info: info.userInfo,
|
|
4437
|
+
presence: me,
|
|
4438
|
+
isReadOnly: info.isReadOnly
|
|
4439
|
+
} : null;
|
|
4440
|
+
}
|
|
3450
4441
|
);
|
|
3451
4442
|
const selfAsTreeNode = new DerivedRef(
|
|
3452
4443
|
self,
|
|
@@ -3515,11 +4506,9 @@ function createRoom(options, config) {
|
|
|
3515
4506
|
});
|
|
3516
4507
|
}
|
|
3517
4508
|
function getConnectionId() {
|
|
3518
|
-
const
|
|
3519
|
-
if (
|
|
3520
|
-
return
|
|
3521
|
-
} else if (context.lastConnectionId !== null) {
|
|
3522
|
-
return context.lastConnectionId;
|
|
4509
|
+
const info = context.sessionInfo.current;
|
|
4510
|
+
if (info) {
|
|
4511
|
+
return info.id;
|
|
3523
4512
|
}
|
|
3524
4513
|
throw new Error(
|
|
3525
4514
|
"Internal. Tried to get connection id but connection was never open"
|
|
@@ -3642,23 +4631,6 @@ function createRoom(options, config) {
|
|
|
3642
4631
|
}
|
|
3643
4632
|
}
|
|
3644
4633
|
}
|
|
3645
|
-
function handleConnect() {
|
|
3646
|
-
var _a2, _b;
|
|
3647
|
-
if (context.connection.current.status !== "closed" && context.connection.current.status !== "unavailable") {
|
|
3648
|
-
return;
|
|
3649
|
-
}
|
|
3650
|
-
const auth = prepareAuthEndpoint(
|
|
3651
|
-
config.roomId,
|
|
3652
|
-
config.authentication,
|
|
3653
|
-
(_a2 = config.polyfills) == null ? void 0 : _a2.fetch
|
|
3654
|
-
);
|
|
3655
|
-
const createWebSocket = prepareCreateWebSocket(
|
|
3656
|
-
config.liveblocksServer,
|
|
3657
|
-
(_b = config.polyfills) == null ? void 0 : _b.WebSocket
|
|
3658
|
-
);
|
|
3659
|
-
updateConnection({ status: "authenticating" }, batchUpdates);
|
|
3660
|
-
effects.authenticateAndConnect(auth, createWebSocket);
|
|
3661
|
-
}
|
|
3662
4634
|
function updatePresence(patch, options2) {
|
|
3663
4635
|
const oldValues = {};
|
|
3664
4636
|
if (context.buffer.me === null) {
|
|
@@ -3700,40 +4672,6 @@ function createRoom(options, config) {
|
|
|
3700
4672
|
function isStorageReadOnly(scopes) {
|
|
3701
4673
|
return scopes.includes("room:read" /* Read */) && scopes.includes("room:presence:write" /* PresenceWrite */) && !scopes.includes("room:write" /* Write */);
|
|
3702
4674
|
}
|
|
3703
|
-
function handleAuthSuccess(token, socket) {
|
|
3704
|
-
socket.addEventListener("message", handleRawSocketMessage);
|
|
3705
|
-
socket.addEventListener("open", handleSocketOpen);
|
|
3706
|
-
socket.addEventListener("close", handleExplicitClose);
|
|
3707
|
-
socket.addEventListener("error", handleSocketError);
|
|
3708
|
-
updateConnection(
|
|
3709
|
-
{
|
|
3710
|
-
status: "connecting",
|
|
3711
|
-
id: token.actor,
|
|
3712
|
-
userInfo: token.info,
|
|
3713
|
-
userId: token.id,
|
|
3714
|
-
isReadOnly: isStorageReadOnly(token.scopes)
|
|
3715
|
-
},
|
|
3716
|
-
batchUpdates
|
|
3717
|
-
);
|
|
3718
|
-
context.idFactory = makeIdFactory(token.actor);
|
|
3719
|
-
context.socket = socket;
|
|
3720
|
-
}
|
|
3721
|
-
function authenticationFailure(error2) {
|
|
3722
|
-
if (process.env.NODE_ENV !== "production") {
|
|
3723
|
-
error("Call to authentication endpoint failed", error2);
|
|
3724
|
-
}
|
|
3725
|
-
context.token = null;
|
|
3726
|
-
updateConnection({ status: "unavailable" }, batchUpdates);
|
|
3727
|
-
context.numRetries++;
|
|
3728
|
-
clearTimeout(context.timers.reconnect);
|
|
3729
|
-
context.timers.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
3730
|
-
}
|
|
3731
|
-
function handleWindowGotFocus() {
|
|
3732
|
-
if (context.connection.current.status === "open") {
|
|
3733
|
-
log("Heartbeat after visibility change");
|
|
3734
|
-
heartbeat();
|
|
3735
|
-
}
|
|
3736
|
-
}
|
|
3737
4675
|
function onUpdatePresenceMessage(message) {
|
|
3738
4676
|
if (message.targetActor !== void 0) {
|
|
3739
4677
|
const oldUser = context.others.getUser(message.actor);
|
|
@@ -3783,12 +4721,6 @@ function createRoom(options, config) {
|
|
|
3783
4721
|
}
|
|
3784
4722
|
return { type: "reset" };
|
|
3785
4723
|
}
|
|
3786
|
-
function handleNavigatorBackOnline() {
|
|
3787
|
-
if (context.connection.current.status === "unavailable") {
|
|
3788
|
-
log("Try to reconnect after connectivity change");
|
|
3789
|
-
reconnect();
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
4724
|
function canUndo() {
|
|
3793
4725
|
return context.undoStack.length > 0;
|
|
3794
4726
|
}
|
|
@@ -3844,17 +4776,7 @@ function createRoom(options, config) {
|
|
|
3844
4776
|
ops: result.ops
|
|
3845
4777
|
});
|
|
3846
4778
|
notify(result.updates, batchedUpdatesWrapper);
|
|
3847
|
-
|
|
3848
|
-
}
|
|
3849
|
-
function handleRawSocketMessage(event) {
|
|
3850
|
-
if (event.data === "pong") {
|
|
3851
|
-
transition({ type: "RECEIVE_PONG" });
|
|
3852
|
-
} else {
|
|
3853
|
-
handleServerMessage(event);
|
|
3854
|
-
}
|
|
3855
|
-
}
|
|
3856
|
-
function handlePong() {
|
|
3857
|
-
clearTimeout(context.timers.pongTimeout);
|
|
4779
|
+
sendMessages(messages);
|
|
3858
4780
|
}
|
|
3859
4781
|
function handleServerMessage(event) {
|
|
3860
4782
|
if (typeof event.data !== "string") {
|
|
@@ -3908,8 +4830,8 @@ function createRoom(options, config) {
|
|
|
3908
4830
|
const unacknowledgedOps = new Map(context.unacknowledgedOps);
|
|
3909
4831
|
createOrUpdateRootFromMessage(message, doNotBatchUpdates);
|
|
3910
4832
|
applyAndSendOps(unacknowledgedOps, doNotBatchUpdates);
|
|
3911
|
-
if (
|
|
3912
|
-
|
|
4833
|
+
if (_resolveInitialStatePromise !== null) {
|
|
4834
|
+
_resolveInitialStatePromise();
|
|
3913
4835
|
}
|
|
3914
4836
|
notifyStorageStatus();
|
|
3915
4837
|
eventHub.storageDidLoad.notify();
|
|
@@ -3957,140 +4879,6 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
3957
4879
|
notify(updates, doNotBatchUpdates);
|
|
3958
4880
|
});
|
|
3959
4881
|
}
|
|
3960
|
-
function handleExplicitClose(event) {
|
|
3961
|
-
context.socket = null;
|
|
3962
|
-
clearTimeout(context.timers.flush);
|
|
3963
|
-
clearTimeout(context.timers.reconnect);
|
|
3964
|
-
clearInterval(context.timers.heartbeat);
|
|
3965
|
-
clearTimeout(context.timers.pongTimeout);
|
|
3966
|
-
context.others.clearOthers();
|
|
3967
|
-
batchUpdates(() => {
|
|
3968
|
-
notify({ others: [{ type: "reset" }] }, doNotBatchUpdates);
|
|
3969
|
-
if (event.code >= 4e3 && event.code <= 4100) {
|
|
3970
|
-
updateConnection({ status: "failed" }, doNotBatchUpdates);
|
|
3971
|
-
const error2 = new LiveblocksError(event.reason, event.code);
|
|
3972
|
-
eventHub.error.notify(error2);
|
|
3973
|
-
const delay = getRetryDelay(true);
|
|
3974
|
-
context.numRetries++;
|
|
3975
|
-
if (process.env.NODE_ENV !== "production") {
|
|
3976
|
-
error(
|
|
3977
|
-
`Connection to websocket server closed. Reason: ${error2.message} (code: ${error2.code}). Retrying in ${delay}ms.`
|
|
3978
|
-
);
|
|
3979
|
-
}
|
|
3980
|
-
updateConnection({ status: "unavailable" }, doNotBatchUpdates);
|
|
3981
|
-
clearTimeout(context.timers.reconnect);
|
|
3982
|
-
context.timers.reconnect = effects.scheduleReconnect(delay);
|
|
3983
|
-
} else if (event.code === 4999 /* CLOSE_WITHOUT_RETRY */) {
|
|
3984
|
-
updateConnection({ status: "closed" }, doNotBatchUpdates);
|
|
3985
|
-
} else {
|
|
3986
|
-
const delay = getRetryDelay();
|
|
3987
|
-
context.numRetries++;
|
|
3988
|
-
if (process.env.NODE_ENV !== "production") {
|
|
3989
|
-
warn(
|
|
3990
|
-
`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`
|
|
3991
|
-
);
|
|
3992
|
-
}
|
|
3993
|
-
updateConnection({ status: "unavailable" }, doNotBatchUpdates);
|
|
3994
|
-
clearTimeout(context.timers.reconnect);
|
|
3995
|
-
context.timers.reconnect = effects.scheduleReconnect(delay);
|
|
3996
|
-
}
|
|
3997
|
-
});
|
|
3998
|
-
}
|
|
3999
|
-
function updateConnection(connection, batchedUpdatesWrapper) {
|
|
4000
|
-
context.connection.set(connection);
|
|
4001
|
-
batchedUpdatesWrapper(() => {
|
|
4002
|
-
eventHub.connection.notify(connection.status);
|
|
4003
|
-
});
|
|
4004
|
-
}
|
|
4005
|
-
function getRetryDelay(slow = false) {
|
|
4006
|
-
if (slow) {
|
|
4007
|
-
return BACKOFF_RETRY_DELAYS_SLOW[context.numRetries < BACKOFF_RETRY_DELAYS_SLOW.length ? context.numRetries : BACKOFF_RETRY_DELAYS_SLOW.length - 1];
|
|
4008
|
-
}
|
|
4009
|
-
return BACKOFF_RETRY_DELAYS[context.numRetries < BACKOFF_RETRY_DELAYS.length ? context.numRetries : BACKOFF_RETRY_DELAYS.length - 1];
|
|
4010
|
-
}
|
|
4011
|
-
function handleSocketError() {
|
|
4012
|
-
}
|
|
4013
|
-
function handleSocketOpen() {
|
|
4014
|
-
clearInterval(context.timers.heartbeat);
|
|
4015
|
-
context.timers.heartbeat = effects.startHeartbeatInterval();
|
|
4016
|
-
if (context.connection.current.status === "connecting") {
|
|
4017
|
-
updateConnection(
|
|
4018
|
-
__spreadProps(__spreadValues({}, context.connection.current), { status: "open" }),
|
|
4019
|
-
batchUpdates
|
|
4020
|
-
);
|
|
4021
|
-
context.numRetries = 0;
|
|
4022
|
-
if (context.lastConnectionId !== void 0) {
|
|
4023
|
-
context.buffer.me = {
|
|
4024
|
-
type: "full",
|
|
4025
|
-
data: (
|
|
4026
|
-
// Because state.me.current is a readonly object, we'll have to
|
|
4027
|
-
// make a copy here. Otherwise, type errors happen later when
|
|
4028
|
-
// "patching" my presence.
|
|
4029
|
-
__spreadValues({}, context.me.current)
|
|
4030
|
-
)
|
|
4031
|
-
};
|
|
4032
|
-
tryFlushing();
|
|
4033
|
-
}
|
|
4034
|
-
context.lastConnectionId = context.connection.current.id;
|
|
4035
|
-
if (context.root) {
|
|
4036
|
-
context.buffer.messages.push({ type: 200 /* FETCH_STORAGE */ });
|
|
4037
|
-
}
|
|
4038
|
-
tryFlushing();
|
|
4039
|
-
} else {
|
|
4040
|
-
}
|
|
4041
|
-
}
|
|
4042
|
-
function heartbeat() {
|
|
4043
|
-
if (context.socket === null) {
|
|
4044
|
-
return;
|
|
4045
|
-
}
|
|
4046
|
-
clearTimeout(context.timers.pongTimeout);
|
|
4047
|
-
context.timers.pongTimeout = effects.schedulePongTimeout();
|
|
4048
|
-
if (context.socket.readyState === context.socket.OPEN) {
|
|
4049
|
-
context.socket.send("ping");
|
|
4050
|
-
}
|
|
4051
|
-
}
|
|
4052
|
-
function pongTimeout() {
|
|
4053
|
-
log("Pong timeout. Trying to reconnect.");
|
|
4054
|
-
reconnect();
|
|
4055
|
-
}
|
|
4056
|
-
function handleDisconnect() {
|
|
4057
|
-
if (context.socket) {
|
|
4058
|
-
context.socket.removeEventListener("open", handleSocketOpen);
|
|
4059
|
-
context.socket.removeEventListener("message", handleRawSocketMessage);
|
|
4060
|
-
context.socket.removeEventListener("close", handleExplicitClose);
|
|
4061
|
-
context.socket.removeEventListener("error", handleSocketError);
|
|
4062
|
-
context.socket.close();
|
|
4063
|
-
context.socket = null;
|
|
4064
|
-
}
|
|
4065
|
-
clearTimeout(context.timers.flush);
|
|
4066
|
-
clearTimeout(context.timers.reconnect);
|
|
4067
|
-
clearInterval(context.timers.heartbeat);
|
|
4068
|
-
clearTimeout(context.timers.pongTimeout);
|
|
4069
|
-
batchUpdates(() => {
|
|
4070
|
-
updateConnection({ status: "closed" }, doNotBatchUpdates);
|
|
4071
|
-
context.others.clearOthers();
|
|
4072
|
-
notify({ others: [{ type: "reset" }] }, doNotBatchUpdates);
|
|
4073
|
-
});
|
|
4074
|
-
for (const eventSource2 of Object.values(eventHub)) {
|
|
4075
|
-
eventSource2.clear();
|
|
4076
|
-
}
|
|
4077
|
-
}
|
|
4078
|
-
function reconnect() {
|
|
4079
|
-
if (context.socket) {
|
|
4080
|
-
context.socket.removeEventListener("open", handleSocketOpen);
|
|
4081
|
-
context.socket.removeEventListener("message", handleRawSocketMessage);
|
|
4082
|
-
context.socket.removeEventListener("close", handleExplicitClose);
|
|
4083
|
-
context.socket.removeEventListener("error", handleSocketError);
|
|
4084
|
-
context.socket.close();
|
|
4085
|
-
context.socket = null;
|
|
4086
|
-
}
|
|
4087
|
-
clearTimeout(context.timers.flush);
|
|
4088
|
-
clearTimeout(context.timers.reconnect);
|
|
4089
|
-
clearInterval(context.timers.heartbeat);
|
|
4090
|
-
clearTimeout(context.timers.pongTimeout);
|
|
4091
|
-
updateConnection({ status: "unavailable" }, batchUpdates);
|
|
4092
|
-
handleConnect();
|
|
4093
|
-
}
|
|
4094
4882
|
function tryFlushing() {
|
|
4095
4883
|
const storageOps = context.buffer.storageOperations;
|
|
4096
4884
|
if (storageOps.length > 0) {
|
|
@@ -4099,7 +4887,7 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4099
4887
|
}
|
|
4100
4888
|
notifyStorageStatus();
|
|
4101
4889
|
}
|
|
4102
|
-
if (
|
|
4890
|
+
if (managedSocket.getStatus() !== "connected") {
|
|
4103
4891
|
context.buffer.storageOperations = [];
|
|
4104
4892
|
return;
|
|
4105
4893
|
}
|
|
@@ -4110,16 +4898,17 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4110
4898
|
if (messagesToFlush.length === 0) {
|
|
4111
4899
|
return;
|
|
4112
4900
|
}
|
|
4113
|
-
|
|
4901
|
+
sendMessages(messagesToFlush);
|
|
4114
4902
|
context.buffer = {
|
|
4903
|
+
flushTimerID: void 0,
|
|
4115
4904
|
lastFlushedAt: now,
|
|
4116
4905
|
messages: [],
|
|
4117
4906
|
storageOperations: [],
|
|
4118
4907
|
me: null
|
|
4119
4908
|
};
|
|
4120
4909
|
} else {
|
|
4121
|
-
clearTimeout(context.
|
|
4122
|
-
context.
|
|
4910
|
+
clearTimeout(context.buffer.flushTimerID);
|
|
4911
|
+
context.buffer.flushTimerID = setTimeout(
|
|
4123
4912
|
tryFlushing,
|
|
4124
4913
|
config.throttleDelay - elapsedMillis
|
|
4125
4914
|
);
|
|
@@ -4156,7 +4945,7 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4156
4945
|
function broadcastEvent(event, options2 = {
|
|
4157
4946
|
shouldQueueEventIfNotReady: false
|
|
4158
4947
|
}) {
|
|
4159
|
-
if (
|
|
4948
|
+
if (managedSocket.getStatus() !== "connected" && !options2.shouldQueueEventIfNotReady) {
|
|
4160
4949
|
return;
|
|
4161
4950
|
}
|
|
4162
4951
|
context.buffer.messages.push({
|
|
@@ -4170,13 +4959,13 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4170
4959
|
tryFlushing();
|
|
4171
4960
|
}
|
|
4172
4961
|
let _getInitialStatePromise = null;
|
|
4173
|
-
let
|
|
4962
|
+
let _resolveInitialStatePromise = null;
|
|
4174
4963
|
function startLoadingStorage() {
|
|
4175
4964
|
if (_getInitialStatePromise === null) {
|
|
4176
4965
|
context.buffer.messages.push({ type: 200 /* FETCH_STORAGE */ });
|
|
4177
4966
|
tryFlushing();
|
|
4178
4967
|
_getInitialStatePromise = new Promise(
|
|
4179
|
-
(resolve) =>
|
|
4968
|
+
(resolve) => _resolveInitialStatePromise = resolve
|
|
4180
4969
|
);
|
|
4181
4970
|
notifyStorageStatus();
|
|
4182
4971
|
}
|
|
@@ -4293,11 +5082,6 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4293
5082
|
_addToRealUndoStack(historyOps, batchUpdates);
|
|
4294
5083
|
}
|
|
4295
5084
|
}
|
|
4296
|
-
function handleImplicitClose() {
|
|
4297
|
-
if (context.socket) {
|
|
4298
|
-
context.socket = null;
|
|
4299
|
-
}
|
|
4300
|
-
}
|
|
4301
5085
|
function getStorageStatus() {
|
|
4302
5086
|
if (_getInitialStatePromise === null) {
|
|
4303
5087
|
return "not-loaded";
|
|
@@ -4320,38 +5104,20 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4320
5104
|
(others) => others.map((other, index) => userToTreeNode(`Other ${index}`, other))
|
|
4321
5105
|
);
|
|
4322
5106
|
const events = {
|
|
5107
|
+
connection: eventHub.connection.observable,
|
|
5108
|
+
// Old/deprecated API
|
|
5109
|
+
status: eventHub.status.observable,
|
|
5110
|
+
// New/recommended API
|
|
5111
|
+
lostConnection: eventHub.lostConnection.observable,
|
|
4323
5112
|
customEvent: eventHub.customEvent.observable,
|
|
4324
5113
|
others: eventHub.others.observable,
|
|
4325
5114
|
me: eventHub.me.observable,
|
|
4326
5115
|
error: eventHub.error.observable,
|
|
4327
|
-
connection: eventHub.connection.observable,
|
|
4328
5116
|
storage: eventHub.storage.observable,
|
|
4329
5117
|
history: eventHub.history.observable,
|
|
4330
5118
|
storageDidLoad: eventHub.storageDidLoad.observable,
|
|
4331
5119
|
storageStatus: eventHub.storageStatus.observable
|
|
4332
5120
|
};
|
|
4333
|
-
function transition(event) {
|
|
4334
|
-
switch (event.type) {
|
|
4335
|
-
case "CONNECT":
|
|
4336
|
-
return handleConnect();
|
|
4337
|
-
case "DISCONNECT":
|
|
4338
|
-
return handleDisconnect();
|
|
4339
|
-
case "RECEIVE_PONG":
|
|
4340
|
-
return handlePong();
|
|
4341
|
-
case "AUTH_SUCCESS":
|
|
4342
|
-
return handleAuthSuccess(event.token, event.socket);
|
|
4343
|
-
case "WINDOW_GOT_FOCUS":
|
|
4344
|
-
return handleWindowGotFocus();
|
|
4345
|
-
case "NAVIGATOR_ONLINE":
|
|
4346
|
-
return handleNavigatorBackOnline();
|
|
4347
|
-
case "IMPLICIT_CLOSE":
|
|
4348
|
-
return handleImplicitClose();
|
|
4349
|
-
case "EXPLICIT_CLOSE":
|
|
4350
|
-
return handleExplicitClose(event.closeEvent);
|
|
4351
|
-
default:
|
|
4352
|
-
return assertNever(event, "Invalid event");
|
|
4353
|
-
}
|
|
4354
|
-
}
|
|
4355
5121
|
return {
|
|
4356
5122
|
/* NOTE: Exposing __internal here only to allow testing implementation details in unit tests */
|
|
4357
5123
|
__internal: {
|
|
@@ -4359,10 +5125,6 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4359
5125
|
return context.buffer;
|
|
4360
5126
|
},
|
|
4361
5127
|
// prettier-ignore
|
|
4362
|
-
get numRetries() {
|
|
4363
|
-
return context.numRetries;
|
|
4364
|
-
},
|
|
4365
|
-
// prettier-ignore
|
|
4366
5128
|
get undoStack() {
|
|
4367
5129
|
return context.undoStack;
|
|
4368
5130
|
},
|
|
@@ -4376,27 +5138,17 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4376
5138
|
getOthers_forDevTools: () => others_forDevTools.current,
|
|
4377
5139
|
// prettier-ignore
|
|
4378
5140
|
send: {
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
navigatorOnline: () => transition({ type: "NAVIGATOR_ONLINE" }),
|
|
4383
|
-
windowGotFocus: () => transition({ type: "WINDOW_GOT_FOCUS" }),
|
|
4384
|
-
pong: () => transition({ type: "RECEIVE_PONG" }),
|
|
4385
|
-
connect: () => transition({ type: "CONNECT" }),
|
|
4386
|
-
disconnect: () => transition({ type: "DISCONNECT" }),
|
|
4387
|
-
/**
|
|
4388
|
-
* This one looks differently from the rest, because receiving messages
|
|
4389
|
-
* is handled orthorgonally from all other possible events above,
|
|
4390
|
-
* because it does not matter what the connectivity state of the
|
|
4391
|
-
* machine is, so there won't be an explicit state machine transition
|
|
4392
|
-
* needed for this event.
|
|
4393
|
-
*/
|
|
4394
|
-
incomingMessage: handleServerMessage
|
|
5141
|
+
// These exist only for our E2E testing app
|
|
5142
|
+
explicitClose: (event) => managedSocket._privateSendMachineEvent({ type: "EXPLICIT_SOCKET_CLOSE", event }),
|
|
5143
|
+
implicitClose: () => managedSocket._privateSendMachineEvent({ type: "PONG_TIMEOUT" })
|
|
4395
5144
|
}
|
|
4396
5145
|
},
|
|
4397
5146
|
id: config.roomId,
|
|
4398
5147
|
subscribe: makeClassicSubscribeFn(events),
|
|
4399
|
-
|
|
5148
|
+
connect: () => managedSocket.connect(),
|
|
5149
|
+
reconnect: () => managedSocket.reconnect(),
|
|
5150
|
+
disconnect: () => managedSocket.disconnect(),
|
|
5151
|
+
destroy: () => managedSocket.destroy(),
|
|
4400
5152
|
// Presence
|
|
4401
5153
|
updatePresence,
|
|
4402
5154
|
broadcastEvent,
|
|
@@ -4415,8 +5167,9 @@ ${Array.from(traces).join("\n\n")}`
|
|
|
4415
5167
|
getStorageStatus,
|
|
4416
5168
|
events,
|
|
4417
5169
|
// Core
|
|
4418
|
-
|
|
4419
|
-
|
|
5170
|
+
getStatus: () => managedSocket.getStatus(),
|
|
5171
|
+
getConnectionState: () => managedSocket.getLegacyStatus(),
|
|
5172
|
+
isSelfAware: () => context.sessionInfo.current !== null,
|
|
4420
5173
|
getSelf: () => self.current,
|
|
4421
5174
|
// Presence
|
|
4422
5175
|
getPresence: () => context.me.current,
|
|
@@ -4468,6 +5221,12 @@ function makeClassicSubscribeFn(events) {
|
|
|
4468
5221
|
return events.connection.subscribe(
|
|
4469
5222
|
callback
|
|
4470
5223
|
);
|
|
5224
|
+
case "status":
|
|
5225
|
+
return events.status.subscribe(callback);
|
|
5226
|
+
case "lost-connection":
|
|
5227
|
+
return events.lostConnection.subscribe(
|
|
5228
|
+
callback
|
|
5229
|
+
);
|
|
4471
5230
|
case "history":
|
|
4472
5231
|
return events.history.subscribe(callback);
|
|
4473
5232
|
case "storage-status":
|
|
@@ -4503,20 +5262,14 @@ function makeClassicSubscribeFn(events) {
|
|
|
4503
5262
|
function isRoomEventName(value) {
|
|
4504
5263
|
return value === "my-presence" || value === "others" || value === "event" || value === "error" || value === "connection" || value === "history" || value === "storage-status";
|
|
4505
5264
|
}
|
|
4506
|
-
|
|
4507
|
-
constructor(message, code) {
|
|
4508
|
-
super(message);
|
|
4509
|
-
this.code = code;
|
|
4510
|
-
}
|
|
4511
|
-
};
|
|
4512
|
-
function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
|
|
4513
|
-
if (typeof window === "undefined" && WebSocketPolyfill === void 0) {
|
|
4514
|
-
throw new Error(
|
|
4515
|
-
"To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill."
|
|
4516
|
-
);
|
|
4517
|
-
}
|
|
4518
|
-
const ws = WebSocketPolyfill || WebSocket;
|
|
5265
|
+
function makeCreateSocketDelegateForRoom(liveblocksServer, WebSocketPolyfill) {
|
|
4519
5266
|
return (richToken) => {
|
|
5267
|
+
const ws = WebSocketPolyfill != null ? WebSocketPolyfill : typeof WebSocket === "undefined" ? void 0 : WebSocket;
|
|
5268
|
+
if (ws === void 0) {
|
|
5269
|
+
throw new StopRetrying(
|
|
5270
|
+
"To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill."
|
|
5271
|
+
);
|
|
5272
|
+
}
|
|
4520
5273
|
const token = richToken.raw;
|
|
4521
5274
|
return new ws(
|
|
4522
5275
|
`${liveblocksServer}/?token=${token}&version=${// prettier-ignore
|
|
@@ -4524,7 +5277,7 @@ function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
|
|
|
4524
5277
|
// @ts-ignore (__PACKAGE_VERSION__ will be injected by the build script)
|
|
4525
5278
|
true ? (
|
|
4526
5279
|
/* istanbul ignore next */
|
|
4527
|
-
"1.0
|
|
5280
|
+
"1.1.0-beta1"
|
|
4528
5281
|
) : "dev"}`
|
|
4529
5282
|
);
|
|
4530
5283
|
};
|
|
@@ -4543,45 +5296,44 @@ function httpSend(message, token, endpoint, fetchPolyfill) {
|
|
|
4543
5296
|
});
|
|
4544
5297
|
});
|
|
4545
5298
|
}
|
|
4546
|
-
function
|
|
5299
|
+
function makeAuthDelegateForRoom(roomId, authentication, fetchPolyfill) {
|
|
5300
|
+
const fetcher = fetchPolyfill != null ? fetchPolyfill : typeof window === "undefined" ? void 0 : window.fetch;
|
|
4547
5301
|
if (authentication.type === "public") {
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
fetch,
|
|
4556
|
-
authentication.url,
|
|
4557
|
-
{
|
|
5302
|
+
return () => __async(this, null, function* () {
|
|
5303
|
+
if (fetcher === void 0) {
|
|
5304
|
+
throw new StopRetrying(
|
|
5305
|
+
"To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill."
|
|
5306
|
+
);
|
|
5307
|
+
}
|
|
5308
|
+
return fetchAuthEndpoint(fetcher, authentication.url, {
|
|
4558
5309
|
room: roomId,
|
|
4559
5310
|
publicApiKey: authentication.publicApiKey
|
|
5311
|
+
}).then(({ token }) => parseRoomAuthToken(token));
|
|
5312
|
+
});
|
|
5313
|
+
} else if (authentication.type === "private") {
|
|
5314
|
+
return () => __async(this, null, function* () {
|
|
5315
|
+
if (fetcher === void 0) {
|
|
5316
|
+
throw new StopRetrying(
|
|
5317
|
+
"To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
|
|
5318
|
+
);
|
|
4560
5319
|
}
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
"To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
|
|
4567
|
-
);
|
|
4568
|
-
}
|
|
4569
|
-
return () => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
4570
|
-
room: roomId
|
|
4571
|
-
}).then(({ token }) => parseRoomAuthToken(token));
|
|
4572
|
-
}
|
|
4573
|
-
if (authentication.type === "custom") {
|
|
5320
|
+
return fetchAuthEndpoint(fetcher, authentication.url, {
|
|
5321
|
+
room: roomId
|
|
5322
|
+
}).then(({ token }) => parseRoomAuthToken(token));
|
|
5323
|
+
});
|
|
5324
|
+
} else if (authentication.type === "custom") {
|
|
4574
5325
|
return () => __async(this, null, function* () {
|
|
4575
5326
|
const response = yield authentication.callback(roomId);
|
|
4576
5327
|
if (!response || !response.token) {
|
|
4577
5328
|
throw new Error(
|
|
4578
|
-
'
|
|
5329
|
+
'We expect the authentication callback to return a token, but it does not. Hint: the return value should look like: { token: "..." }'
|
|
4579
5330
|
);
|
|
4580
5331
|
}
|
|
4581
5332
|
return parseRoomAuthToken(response.token);
|
|
4582
5333
|
});
|
|
5334
|
+
} else {
|
|
5335
|
+
throw new Error("Internal error. Unexpected authentication type");
|
|
4583
5336
|
}
|
|
4584
|
-
throw new Error("Internal error. Unexpected authentication type");
|
|
4585
5337
|
}
|
|
4586
5338
|
function fetchAuthEndpoint(fetch2, endpoint, body) {
|
|
4587
5339
|
return __async(this, null, function* () {
|
|
@@ -4595,22 +5347,25 @@ function fetchAuthEndpoint(fetch2, endpoint, body) {
|
|
|
4595
5347
|
body: JSON.stringify(body)
|
|
4596
5348
|
});
|
|
4597
5349
|
if (!res.ok) {
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
5350
|
+
const reason = `${(yield res.text()).trim() || "reason not provided in auth response"} (${res.status} returned by POST ${endpoint})`;
|
|
5351
|
+
if (res.status === 401 || res.status === 403) {
|
|
5352
|
+
throw new StopRetrying(`Unauthorized: ${reason}`);
|
|
5353
|
+
} else {
|
|
5354
|
+
throw new Error(`Failed to authenticate: ${reason}`);
|
|
5355
|
+
}
|
|
4601
5356
|
}
|
|
4602
5357
|
let data;
|
|
4603
5358
|
try {
|
|
4604
5359
|
data = yield res.json();
|
|
4605
5360
|
} catch (er) {
|
|
4606
|
-
throw new
|
|
5361
|
+
throw new Error(
|
|
4607
5362
|
`Expected a JSON response when doing a POST request on "${endpoint}". ${String(
|
|
4608
5363
|
er
|
|
4609
5364
|
)}`
|
|
4610
5365
|
);
|
|
4611
5366
|
}
|
|
4612
5367
|
if (!isPlainObject(data) || typeof data.token !== "string") {
|
|
4613
|
-
throw new
|
|
5368
|
+
throw new Error(
|
|
4614
5369
|
`Expected a JSON response of the form \`{ token: "..." }\` when doing a POST request on "${endpoint}", but got ${JSON.stringify(
|
|
4615
5370
|
data
|
|
4616
5371
|
)}`
|
|
@@ -4620,11 +5375,6 @@ function fetchAuthEndpoint(fetch2, endpoint, body) {
|
|
|
4620
5375
|
return { token };
|
|
4621
5376
|
});
|
|
4622
5377
|
}
|
|
4623
|
-
var AuthenticationError = class extends Error {
|
|
4624
|
-
constructor(message) {
|
|
4625
|
-
super(message);
|
|
4626
|
-
}
|
|
4627
|
-
};
|
|
4628
5378
|
|
|
4629
5379
|
// src/client.ts
|
|
4630
5380
|
var MIN_THROTTLE = 16;
|
|
@@ -4643,7 +5393,7 @@ function createClient(options) {
|
|
|
4643
5393
|
return room ? room : null;
|
|
4644
5394
|
}
|
|
4645
5395
|
function enter(roomId, options2) {
|
|
4646
|
-
var _a, _b, _c;
|
|
5396
|
+
var _a, _b, _c, _d;
|
|
4647
5397
|
const existingRoom = rooms.get(roomId);
|
|
4648
5398
|
if (existingRoom !== void 0) {
|
|
4649
5399
|
return existingRoom;
|
|
@@ -4660,7 +5410,10 @@ function createClient(options) {
|
|
|
4660
5410
|
{
|
|
4661
5411
|
roomId,
|
|
4662
5412
|
throttleDelay,
|
|
5413
|
+
lostConnectionTimeout: (_b = clientOptions.lostConnectionTimeout) != null ? _b : 5e3,
|
|
4663
5414
|
polyfills: clientOptions.polyfills,
|
|
5415
|
+
delegates: clientOptions.mockedDelegates,
|
|
5416
|
+
enableDebugLogging: clientOptions.enableDebugLogging,
|
|
4664
5417
|
unstable_batchedUpdates: options2 == null ? void 0 : options2.unstable_batchedUpdates,
|
|
4665
5418
|
liveblocksServer: getServerFromClientOptions(clientOptions),
|
|
4666
5419
|
authentication: prepareAuthentication(clientOptions, roomId),
|
|
@@ -4674,17 +5427,17 @@ function createClient(options) {
|
|
|
4674
5427
|
rooms.set(roomId, newRoom);
|
|
4675
5428
|
setupDevTools(() => Array.from(rooms.keys()));
|
|
4676
5429
|
linkDevTools(roomId, newRoom);
|
|
4677
|
-
const shouldConnect = (
|
|
5430
|
+
const shouldConnect = (_c = options2.shouldInitiallyConnect) != null ? _c : true;
|
|
4678
5431
|
if (shouldConnect) {
|
|
4679
5432
|
if (typeof atob === "undefined") {
|
|
4680
|
-
if (((
|
|
5433
|
+
if (((_d = clientOptions.polyfills) == null ? void 0 : _d.atob) === void 0) {
|
|
4681
5434
|
throw new Error(
|
|
4682
5435
|
"You need to polyfill atob to use the client in your environment. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/atob-polyfill"
|
|
4683
5436
|
);
|
|
4684
5437
|
}
|
|
4685
5438
|
global.atob = clientOptions.polyfills.atob;
|
|
4686
5439
|
}
|
|
4687
|
-
newRoom.
|
|
5440
|
+
newRoom.connect();
|
|
4688
5441
|
}
|
|
4689
5442
|
return newRoom;
|
|
4690
5443
|
}
|
|
@@ -4692,27 +5445,10 @@ function createClient(options) {
|
|
|
4692
5445
|
unlinkDevTools(roomId);
|
|
4693
5446
|
const room = rooms.get(roomId);
|
|
4694
5447
|
if (room !== void 0) {
|
|
4695
|
-
room.
|
|
5448
|
+
room.destroy();
|
|
4696
5449
|
rooms.delete(roomId);
|
|
4697
5450
|
}
|
|
4698
5451
|
}
|
|
4699
|
-
if (typeof window !== "undefined" && // istanbul ignore next: React Native environment doesn't implement window.addEventListener
|
|
4700
|
-
typeof window.addEventListener !== "undefined") {
|
|
4701
|
-
window.addEventListener("online", () => {
|
|
4702
|
-
for (const [, room] of rooms) {
|
|
4703
|
-
room.__internal.send.navigatorOnline();
|
|
4704
|
-
}
|
|
4705
|
-
});
|
|
4706
|
-
}
|
|
4707
|
-
if (typeof document !== "undefined") {
|
|
4708
|
-
document.addEventListener("visibilitychange", () => {
|
|
4709
|
-
if (document.visibilityState === "visible") {
|
|
4710
|
-
for (const [, room] of rooms) {
|
|
4711
|
-
room.__internal.send.windowGotFocus();
|
|
4712
|
-
}
|
|
4713
|
-
}
|
|
4714
|
-
});
|
|
4715
|
-
}
|
|
4716
5452
|
return {
|
|
4717
5453
|
getRoom,
|
|
4718
5454
|
enter,
|
|
@@ -5131,6 +5867,20 @@ function shallow(a, b) {
|
|
|
5131
5867
|
return shallowObj(a, b);
|
|
5132
5868
|
}
|
|
5133
5869
|
|
|
5870
|
+
// src/types/IWebSocket.ts
|
|
5871
|
+
var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
|
|
5872
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
|
|
5873
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["INVALID_MESSAGE_FORMAT"] = 4e3] = "INVALID_MESSAGE_FORMAT";
|
|
5874
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
|
|
5875
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
|
|
5876
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
|
|
5877
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
|
|
5878
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
|
|
5879
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
|
|
5880
|
+
return WebsocketCloseCodes2;
|
|
5881
|
+
})(WebsocketCloseCodes || {});
|
|
5882
|
+
|
|
5883
|
+
|
|
5134
5884
|
|
|
5135
5885
|
|
|
5136
5886
|
|
|
@@ -5166,4 +5916,4 @@ function shallow(a, b) {
|
|
|
5166
5916
|
|
|
5167
5917
|
|
|
5168
5918
|
|
|
5169
|
-
exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.OpCode = OpCode; exports.ServerMsgCode = ServerMsgCode; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.asArrayWithLegacyMethods = asArrayWithLegacyMethods; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.b64decode = b64decode; exports.createClient = createClient; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.errorIf = errorIf; exports.freeze = freeze; exports.isAppOnlyAuthToken = isAppOnlyAuthToken; exports.isAuthToken = isAuthToken; exports.isChildCrdt = isChildCrdt; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isPlainObject = isPlainObject; exports.isRoomAuthToken = isRoomAuthToken; exports.isRootCrdt = isRootCrdt; exports.legacy_patchImmutableObject = legacy_patchImmutableObject; exports.lsonToJson = lsonToJson; exports.makePosition = makePosition; exports.nn = nn; exports.patchLiveObjectKey = patchLiveObjectKey; exports.shallow = shallow; exports.throwUsageError = throwUsageError; exports.tryParseJson = tryParseJson;
|
|
5919
|
+
exports.ClientMsgCode = ClientMsgCode; exports.CrdtType = CrdtType; exports.LiveList = LiveList; exports.LiveMap = LiveMap; exports.LiveObject = LiveObject; exports.OpCode = OpCode; exports.ServerMsgCode = ServerMsgCode; exports.WebsocketCloseCodes = WebsocketCloseCodes; exports.asArrayWithLegacyMethods = asArrayWithLegacyMethods; exports.asPos = asPos; exports.assert = assert; exports.assertNever = assertNever; exports.b64decode = b64decode; exports.createClient = createClient; exports.deprecate = deprecate; exports.deprecateIf = deprecateIf; exports.errorIf = errorIf; exports.freeze = freeze; exports.isAppOnlyAuthToken = isAppOnlyAuthToken; exports.isAuthToken = isAuthToken; exports.isChildCrdt = isChildCrdt; exports.isJsonArray = isJsonArray; exports.isJsonObject = isJsonObject; exports.isJsonScalar = isJsonScalar; exports.isPlainObject = isPlainObject; exports.isRoomAuthToken = isRoomAuthToken; exports.isRootCrdt = isRootCrdt; exports.legacy_patchImmutableObject = legacy_patchImmutableObject; exports.lsonToJson = lsonToJson; exports.makePosition = makePosition; exports.nn = nn; exports.patchLiveObjectKey = patchLiveObjectKey; exports.shallow = shallow; exports.throwUsageError = throwUsageError; exports.tryParseJson = tryParseJson; exports.withTimeout = withTimeout;
|