@liveblocks/client 0.13.2 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/AbstractCrdt.d.ts +8 -4
- package/lib/cjs/AbstractCrdt.js +2 -2
- package/lib/cjs/LiveList.d.ts +9 -4
- package/lib/cjs/LiveList.js +58 -9
- package/lib/cjs/LiveMap.d.ts +7 -3
- package/lib/cjs/LiveMap.js +23 -5
- package/lib/cjs/LiveObject.d.ts +17 -7
- package/lib/cjs/LiveObject.js +45 -15
- package/lib/cjs/LiveRegister.d.ts +8 -4
- package/lib/cjs/LiveRegister.js +17 -4
- package/lib/cjs/client.js +50 -7
- package/lib/cjs/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/cjs/{immutable/index.js → immutable.js} +98 -21
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/live.d.ts +7 -0
- package/lib/cjs/room.d.ts +17 -9
- package/lib/cjs/room.js +203 -81
- package/lib/cjs/types.d.ts +26 -1
- package/lib/cjs/utils.d.ts +2 -1
- package/lib/cjs/utils.js +76 -1
- package/lib/esm/AbstractCrdt.d.ts +8 -4
- package/lib/esm/AbstractCrdt.js +2 -2
- package/lib/esm/LiveList.d.ts +9 -4
- package/lib/esm/LiveList.js +59 -10
- package/lib/esm/LiveMap.d.ts +7 -3
- package/lib/esm/LiveMap.js +23 -5
- package/lib/esm/LiveObject.d.ts +17 -7
- package/lib/esm/LiveObject.js +45 -15
- package/lib/esm/LiveRegister.d.ts +8 -4
- package/lib/esm/LiveRegister.js +18 -5
- package/lib/esm/client.js +50 -7
- package/lib/esm/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/esm/{immutable/index.js → immutable.js} +96 -21
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/live.d.ts +7 -0
- package/lib/esm/room.d.ts +17 -9
- package/lib/esm/room.js +203 -62
- package/lib/esm/types.d.ts +26 -1
- package/lib/esm/utils.d.ts +2 -1
- package/lib/esm/utils.js +75 -1
- package/package.json +6 -2
package/lib/esm/room.js
CHANGED
|
@@ -7,8 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { isSameNodeOrChildOf, remove } from "./utils";
|
|
11
|
-
import auth, { parseToken } from "./authentication";
|
|
10
|
+
import { getTreesDiffOperations, isSameNodeOrChildOf, remove } from "./utils";
|
|
12
11
|
import { ClientMessageType, ServerMessageType, OpType, } from "./live";
|
|
13
12
|
import { LiveMap } from "./LiveMap";
|
|
14
13
|
import { LiveObject } from "./LiveObject";
|
|
@@ -36,6 +35,9 @@ function makeOthers(presenceMap) {
|
|
|
36
35
|
get count() {
|
|
37
36
|
return array.length;
|
|
38
37
|
},
|
|
38
|
+
[Symbol.iterator]() {
|
|
39
|
+
return array[Symbol.iterator]();
|
|
40
|
+
},
|
|
39
41
|
map(callback) {
|
|
40
42
|
return array.map(callback);
|
|
41
43
|
},
|
|
@@ -50,16 +52,12 @@ function log(...params) {
|
|
|
50
52
|
}
|
|
51
53
|
export function makeStateMachine(state, context, mockedEffects) {
|
|
52
54
|
const effects = mockedEffects || {
|
|
53
|
-
authenticate() {
|
|
55
|
+
authenticate(auth, createWebSocket) {
|
|
54
56
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
57
|
try {
|
|
56
|
-
const token = yield auth(context.
|
|
58
|
+
const { token } = yield auth(context.room);
|
|
57
59
|
const parsedToken = parseToken(token);
|
|
58
|
-
const socket =
|
|
59
|
-
socket.addEventListener("message", onMessage);
|
|
60
|
-
socket.addEventListener("open", onOpen);
|
|
61
|
-
socket.addEventListener("close", onClose);
|
|
62
|
-
socket.addEventListener("error", onError);
|
|
60
|
+
const socket = createWebSocket(token);
|
|
63
61
|
authenticationSuccess(parsedToken, socket);
|
|
64
62
|
}
|
|
65
63
|
catch (er) {
|
|
@@ -107,18 +105,23 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
107
105
|
};
|
|
108
106
|
return genericSubscribe(cb);
|
|
109
107
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
108
|
+
function createOrUpdateRootFromMessage(message) {
|
|
109
|
+
if (message.items.length === 0) {
|
|
110
|
+
throw new Error("Internal error: cannot load storage without items");
|
|
111
|
+
}
|
|
112
|
+
if (state.root) {
|
|
113
|
+
updateRoot(message.items);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
state.root = load(message.items);
|
|
117
|
+
}
|
|
112
118
|
for (const key in state.defaultStorageRoot) {
|
|
113
119
|
if (state.root.get(key) == null) {
|
|
114
120
|
state.root.set(key, state.defaultStorageRoot[key]);
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
|
-
function
|
|
119
|
-
if (items.length === 0) {
|
|
120
|
-
throw new Error("Internal error: cannot load storage without items");
|
|
121
|
-
}
|
|
124
|
+
function buildRootAndParentToChildren(items) {
|
|
122
125
|
const parentToChildren = new Map();
|
|
123
126
|
let root = null;
|
|
124
127
|
for (const tuple of items) {
|
|
@@ -139,6 +142,23 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
139
142
|
if (root == null) {
|
|
140
143
|
throw new Error("Root can't be null");
|
|
141
144
|
}
|
|
145
|
+
return [root, parentToChildren];
|
|
146
|
+
}
|
|
147
|
+
function updateRoot(items) {
|
|
148
|
+
if (!state.root) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const currentItems = new Map();
|
|
152
|
+
state.items.forEach((liveCrdt, id) => {
|
|
153
|
+
currentItems.set(id, liveCrdt._toSerializedCrdt());
|
|
154
|
+
});
|
|
155
|
+
// Get operations that represent the diff between 2 states.
|
|
156
|
+
const ops = getTreesDiffOperations(currentItems, new Map(items));
|
|
157
|
+
const result = apply(ops, false);
|
|
158
|
+
notify(result.updates);
|
|
159
|
+
}
|
|
160
|
+
function load(items) {
|
|
161
|
+
const [root, parentToChildren] = buildRootAndParentToChildren(items);
|
|
142
162
|
return LiveObject._deserialize(root, parentToChildren, {
|
|
143
163
|
addItem,
|
|
144
164
|
deleteItem,
|
|
@@ -227,7 +247,10 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
227
247
|
state.connection.state === "connecting") {
|
|
228
248
|
return state.connection.id;
|
|
229
249
|
}
|
|
230
|
-
|
|
250
|
+
else if (state.lastConnectionId !== null) {
|
|
251
|
+
return state.lastConnectionId;
|
|
252
|
+
}
|
|
253
|
+
throw new Error("Internal. Tried to get connection id but connection was never open");
|
|
231
254
|
}
|
|
232
255
|
function generateId() {
|
|
233
256
|
return `${getConnectionId()}:${state.clock++}`;
|
|
@@ -235,7 +258,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
235
258
|
function generateOpId() {
|
|
236
259
|
return `${getConnectionId()}:${state.opClock++}`;
|
|
237
260
|
}
|
|
238
|
-
function apply(item) {
|
|
261
|
+
function apply(item, isLocal) {
|
|
239
262
|
const result = {
|
|
240
263
|
reverse: [],
|
|
241
264
|
updates: { nodes: new Set(), presence: false },
|
|
@@ -262,7 +285,11 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
262
285
|
result.updates.presence = true;
|
|
263
286
|
}
|
|
264
287
|
else {
|
|
265
|
-
|
|
288
|
+
// Ops applied after undo/redo don't have an opId.
|
|
289
|
+
if (isLocal && !op.opId) {
|
|
290
|
+
op.opId = generateOpId();
|
|
291
|
+
}
|
|
292
|
+
const applyOpResult = applyOp(op, isLocal);
|
|
266
293
|
if (applyOpResult.modified) {
|
|
267
294
|
result.updates.nodes.add(applyOpResult.modified);
|
|
268
295
|
result.reverse.unshift(...applyOpResult.reverse);
|
|
@@ -271,7 +298,10 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
271
298
|
}
|
|
272
299
|
return result;
|
|
273
300
|
}
|
|
274
|
-
function applyOp(op) {
|
|
301
|
+
function applyOp(op, isLocal) {
|
|
302
|
+
if (op.opId) {
|
|
303
|
+
state.offlineOperations.delete(op.opId);
|
|
304
|
+
}
|
|
275
305
|
switch (op.type) {
|
|
276
306
|
case OpType.DeleteObjectKey:
|
|
277
307
|
case OpType.UpdateObject:
|
|
@@ -280,7 +310,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
280
310
|
if (item == null) {
|
|
281
311
|
return { modified: false };
|
|
282
312
|
}
|
|
283
|
-
return item._apply(op);
|
|
313
|
+
return item._apply(op, isLocal);
|
|
284
314
|
}
|
|
285
315
|
case OpType.SetParentKey: {
|
|
286
316
|
const item = state.items.get(op.id);
|
|
@@ -308,28 +338,28 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
308
338
|
if (parent == null || getItem(op.id) != null) {
|
|
309
339
|
return { modified: false };
|
|
310
340
|
}
|
|
311
|
-
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data));
|
|
341
|
+
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal);
|
|
312
342
|
}
|
|
313
343
|
case OpType.CreateList: {
|
|
314
344
|
const parent = state.items.get(op.parentId);
|
|
315
345
|
if (parent == null || getItem(op.id) != null) {
|
|
316
346
|
return { modified: false };
|
|
317
347
|
}
|
|
318
|
-
return parent._attachChild(op.id, op.parentKey, new LiveList());
|
|
348
|
+
return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal);
|
|
319
349
|
}
|
|
320
350
|
case OpType.CreateRegister: {
|
|
321
351
|
const parent = state.items.get(op.parentId);
|
|
322
352
|
if (parent == null || getItem(op.id) != null) {
|
|
323
353
|
return { modified: false };
|
|
324
354
|
}
|
|
325
|
-
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data));
|
|
355
|
+
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal);
|
|
326
356
|
}
|
|
327
357
|
case OpType.CreateMap: {
|
|
328
358
|
const parent = state.items.get(op.parentId);
|
|
329
359
|
if (parent == null || getItem(op.id) != null) {
|
|
330
360
|
return { modified: false };
|
|
331
361
|
}
|
|
332
|
-
return parent._attachChild(op.id, op.parentKey, new LiveMap());
|
|
362
|
+
return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal);
|
|
333
363
|
}
|
|
334
364
|
}
|
|
335
365
|
return { modified: false };
|
|
@@ -376,15 +406,14 @@ See v0.13 release notes for more information.
|
|
|
376
406
|
: null;
|
|
377
407
|
}
|
|
378
408
|
function connect() {
|
|
379
|
-
if (typeof window === "undefined") {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
409
|
if (state.connection.state !== "closed" &&
|
|
383
410
|
state.connection.state !== "unavailable") {
|
|
384
411
|
return null;
|
|
385
412
|
}
|
|
413
|
+
const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
|
|
414
|
+
const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
|
|
386
415
|
updateConnection({ state: "authenticating" });
|
|
387
|
-
effects.authenticate();
|
|
416
|
+
effects.authenticate(auth, createWebSocket);
|
|
388
417
|
}
|
|
389
418
|
function updatePresence(overrides, options) {
|
|
390
419
|
const oldValues = {};
|
|
@@ -411,6 +440,10 @@ See v0.13 release notes for more information.
|
|
|
411
440
|
}
|
|
412
441
|
}
|
|
413
442
|
function authenticationSuccess(token, socket) {
|
|
443
|
+
socket.addEventListener("message", onMessage);
|
|
444
|
+
socket.addEventListener("open", onOpen);
|
|
445
|
+
socket.addEventListener("close", onClose);
|
|
446
|
+
socket.addEventListener("error", onError);
|
|
414
447
|
updateConnection({
|
|
415
448
|
state: "connecting",
|
|
416
449
|
id: token.actor,
|
|
@@ -421,7 +454,9 @@ See v0.13 release notes for more information.
|
|
|
421
454
|
state.socket = socket;
|
|
422
455
|
}
|
|
423
456
|
function authenticationFailure(error) {
|
|
424
|
-
|
|
457
|
+
if (process.env.NODE_ENV !== "production") {
|
|
458
|
+
console.error("Call to authentication endpoint failed", error);
|
|
459
|
+
}
|
|
425
460
|
updateConnection({ state: "unavailable" });
|
|
426
461
|
state.numberOfRetry++;
|
|
427
462
|
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
@@ -549,12 +584,13 @@ See v0.13 release notes for more information.
|
|
|
549
584
|
break;
|
|
550
585
|
}
|
|
551
586
|
case ServerMessageType.InitialStorageState: {
|
|
552
|
-
|
|
587
|
+
createOrUpdateRootFromMessage(subMessage);
|
|
588
|
+
applyAndSendOfflineOps();
|
|
553
589
|
_getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
|
|
554
590
|
break;
|
|
555
591
|
}
|
|
556
592
|
case ServerMessageType.UpdateStorage: {
|
|
557
|
-
const applyResult = apply(subMessage.ops);
|
|
593
|
+
const applyResult = apply(subMessage.ops, false);
|
|
558
594
|
for (const node of applyResult.updates.nodes) {
|
|
559
595
|
updates.nodes.add(node);
|
|
560
596
|
}
|
|
@@ -587,14 +623,20 @@ See v0.13 release notes for more information.
|
|
|
587
623
|
updateConnection({ state: "failed" });
|
|
588
624
|
const error = new LiveblocksError(event.reason, event.code);
|
|
589
625
|
for (const listener of state.listeners.error) {
|
|
590
|
-
|
|
626
|
+
if (process.env.NODE_ENV !== "production") {
|
|
627
|
+
console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
|
|
628
|
+
}
|
|
591
629
|
listener(error);
|
|
592
630
|
}
|
|
593
631
|
}
|
|
594
632
|
else if (event.wasClean === false) {
|
|
595
|
-
updateConnection({ state: "unavailable" });
|
|
596
633
|
state.numberOfRetry++;
|
|
597
|
-
|
|
634
|
+
const delay = getRetryDelay();
|
|
635
|
+
if (process.env.NODE_ENV !== "production") {
|
|
636
|
+
console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
|
|
637
|
+
}
|
|
638
|
+
updateConnection({ state: "unavailable" });
|
|
639
|
+
state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
|
|
598
640
|
}
|
|
599
641
|
else {
|
|
600
642
|
updateConnection({ state: "closed" });
|
|
@@ -618,6 +660,10 @@ See v0.13 release notes for more information.
|
|
|
618
660
|
if (state.connection.state === "connecting") {
|
|
619
661
|
updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
|
|
620
662
|
state.numberOfRetry = 0;
|
|
663
|
+
state.lastConnectionId = state.connection.id;
|
|
664
|
+
if (state.root) {
|
|
665
|
+
state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
|
|
666
|
+
}
|
|
621
667
|
tryFlushing();
|
|
622
668
|
}
|
|
623
669
|
else {
|
|
@@ -631,7 +677,7 @@ See v0.13 release notes for more information.
|
|
|
631
677
|
}
|
|
632
678
|
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
633
679
|
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
|
|
634
|
-
if (state.socket.readyState ===
|
|
680
|
+
if (state.socket.readyState === state.socket.OPEN) {
|
|
635
681
|
state.socket.send("ping");
|
|
636
682
|
}
|
|
637
683
|
}
|
|
@@ -657,11 +703,29 @@ See v0.13 release notes for more information.
|
|
|
657
703
|
clearInterval(state.intervalHandles.heartbeat);
|
|
658
704
|
connect();
|
|
659
705
|
}
|
|
660
|
-
function
|
|
661
|
-
if (state.
|
|
706
|
+
function applyAndSendOfflineOps() {
|
|
707
|
+
if (state.offlineOperations.size === 0) {
|
|
662
708
|
return;
|
|
663
709
|
}
|
|
664
|
-
|
|
710
|
+
const messages = [];
|
|
711
|
+
const ops = Array.from(state.offlineOperations.values());
|
|
712
|
+
const result = apply(ops, true);
|
|
713
|
+
messages.push({
|
|
714
|
+
type: ClientMessageType.UpdateStorage,
|
|
715
|
+
ops: ops,
|
|
716
|
+
});
|
|
717
|
+
notify(result.updates);
|
|
718
|
+
effects.send(messages);
|
|
719
|
+
}
|
|
720
|
+
function tryFlushing() {
|
|
721
|
+
const storageOps = state.buffer.storageOperations;
|
|
722
|
+
if (storageOps.length > 0) {
|
|
723
|
+
storageOps.forEach((op) => {
|
|
724
|
+
state.offlineOperations.set(op.opId, op);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
|
|
728
|
+
state.buffer.storageOperations = [];
|
|
665
729
|
return;
|
|
666
730
|
}
|
|
667
731
|
const now = Date.now();
|
|
@@ -736,8 +800,10 @@ See v0.13 release notes for more information.
|
|
|
736
800
|
function getOthers() {
|
|
737
801
|
return state.others;
|
|
738
802
|
}
|
|
739
|
-
function broadcastEvent(event
|
|
740
|
-
|
|
803
|
+
function broadcastEvent(event, options = {
|
|
804
|
+
shouldQueueEventIfNotReady: false,
|
|
805
|
+
}) {
|
|
806
|
+
if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
|
|
741
807
|
return;
|
|
742
808
|
}
|
|
743
809
|
state.buffer.messages.push({
|
|
@@ -779,7 +845,7 @@ See v0.13 release notes for more information.
|
|
|
779
845
|
return;
|
|
780
846
|
}
|
|
781
847
|
state.isHistoryPaused = false;
|
|
782
|
-
const result = apply(historyItem);
|
|
848
|
+
const result = apply(historyItem, true);
|
|
783
849
|
notify(result.updates);
|
|
784
850
|
state.redoStack.push(result.reverse);
|
|
785
851
|
for (const op of historyItem) {
|
|
@@ -798,7 +864,7 @@ See v0.13 release notes for more information.
|
|
|
798
864
|
return;
|
|
799
865
|
}
|
|
800
866
|
state.isHistoryPaused = false;
|
|
801
|
-
const result = apply(historyItem);
|
|
867
|
+
const result = apply(historyItem, true);
|
|
802
868
|
notify(result.updates);
|
|
803
869
|
state.undoStack.push(result.reverse);
|
|
804
870
|
for (const op of historyItem) {
|
|
@@ -850,14 +916,26 @@ See v0.13 release notes for more information.
|
|
|
850
916
|
}
|
|
851
917
|
state.pausedHistory = [];
|
|
852
918
|
}
|
|
919
|
+
function simulateSocketClose() {
|
|
920
|
+
if (state.socket) {
|
|
921
|
+
state.socket.close();
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function simulateSendCloseEvent(event) {
|
|
925
|
+
if (state.socket) {
|
|
926
|
+
onClose(event);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
853
929
|
return {
|
|
854
930
|
// Internal
|
|
855
|
-
onOpen,
|
|
856
931
|
onClose,
|
|
857
932
|
onMessage,
|
|
858
933
|
authenticationSuccess,
|
|
859
934
|
heartbeat,
|
|
860
935
|
onNavigatorOnline,
|
|
936
|
+
// Internal dev tools
|
|
937
|
+
simulateSocketClose,
|
|
938
|
+
simulateSendCloseEvent,
|
|
861
939
|
// onWakeUp,
|
|
862
940
|
onVisibilityChange,
|
|
863
941
|
getUndoStack: () => state.undoStack,
|
|
@@ -889,6 +967,7 @@ See v0.13 release notes for more information.
|
|
|
889
967
|
export function defaultState(me, defaultStorageRoot) {
|
|
890
968
|
return {
|
|
891
969
|
connection: { state: "closed" },
|
|
970
|
+
lastConnectionId: null,
|
|
892
971
|
socket: null,
|
|
893
972
|
listeners: {
|
|
894
973
|
event: [],
|
|
@@ -933,28 +1012,12 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
933
1012
|
updates: { nodes: new Set(), presence: false, others: [] },
|
|
934
1013
|
reverseOps: [],
|
|
935
1014
|
},
|
|
1015
|
+
offlineOperations: new Map(),
|
|
936
1016
|
};
|
|
937
1017
|
}
|
|
938
|
-
export function createRoom(
|
|
939
|
-
const throttleDelay = options.throttle || 100;
|
|
940
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
|
|
941
|
-
let authEndpoint;
|
|
942
|
-
if (options.authEndpoint) {
|
|
943
|
-
authEndpoint = options.authEndpoint;
|
|
944
|
-
}
|
|
945
|
-
else {
|
|
946
|
-
const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
|
|
947
|
-
"https://liveblocks.io/api/public/authorize";
|
|
948
|
-
authEndpoint = publicAuthorizeEndpoint;
|
|
949
|
-
}
|
|
1018
|
+
export function createRoom(options, context) {
|
|
950
1019
|
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
|
|
951
|
-
const machine = makeStateMachine(state,
|
|
952
|
-
throttleDelay,
|
|
953
|
-
liveblocksServer,
|
|
954
|
-
authEndpoint,
|
|
955
|
-
room: name,
|
|
956
|
-
publicApiKey: options.publicApiKey,
|
|
957
|
-
});
|
|
1020
|
+
const machine = makeStateMachine(state, context);
|
|
958
1021
|
const room = {
|
|
959
1022
|
/////////////
|
|
960
1023
|
// Core //
|
|
@@ -978,6 +1041,11 @@ export function createRoom(name, options) {
|
|
|
978
1041
|
pause: machine.pauseHistory,
|
|
979
1042
|
resume: machine.resumeHistory,
|
|
980
1043
|
},
|
|
1044
|
+
// @ts-ignore
|
|
1045
|
+
internalDevTools: {
|
|
1046
|
+
closeWebsocket: machine.simulateSocketClose,
|
|
1047
|
+
sendCloseEvent: machine.simulateSendCloseEvent,
|
|
1048
|
+
},
|
|
981
1049
|
};
|
|
982
1050
|
return {
|
|
983
1051
|
connect: machine.connect,
|
|
@@ -993,3 +1061,76 @@ class LiveblocksError extends Error {
|
|
|
993
1061
|
this.code = code;
|
|
994
1062
|
}
|
|
995
1063
|
}
|
|
1064
|
+
function parseToken(token) {
|
|
1065
|
+
const tokenParts = token.split(".");
|
|
1066
|
+
if (tokenParts.length !== 3) {
|
|
1067
|
+
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1068
|
+
}
|
|
1069
|
+
const data = JSON.parse(atob(tokenParts[1]));
|
|
1070
|
+
if (typeof data.actor !== "number") {
|
|
1071
|
+
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1072
|
+
}
|
|
1073
|
+
return data;
|
|
1074
|
+
}
|
|
1075
|
+
function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
|
|
1076
|
+
if (typeof window === "undefined" && WebSocketPolyfill == null) {
|
|
1077
|
+
throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
|
|
1078
|
+
}
|
|
1079
|
+
const ws = WebSocketPolyfill || WebSocket;
|
|
1080
|
+
return (token) => {
|
|
1081
|
+
return new ws(`${liveblocksServer}/?token=${token}`);
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function prepareAuthEndpoint(authentication, fetchPolyfill) {
|
|
1085
|
+
if (authentication.type === "public") {
|
|
1086
|
+
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1087
|
+
throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
|
|
1088
|
+
}
|
|
1089
|
+
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1090
|
+
room,
|
|
1091
|
+
publicApiKey: authentication.publicApiKey,
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
if (authentication.type === "private") {
|
|
1095
|
+
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1096
|
+
throw new Error("To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill.");
|
|
1097
|
+
}
|
|
1098
|
+
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1099
|
+
room,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
if (authentication.type === "custom") {
|
|
1103
|
+
return authentication.callback;
|
|
1104
|
+
}
|
|
1105
|
+
throw new Error("Internal error. Unexpected authentication type");
|
|
1106
|
+
}
|
|
1107
|
+
function fetchAuthEndpoint(fetch, endpoint, body) {
|
|
1108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1109
|
+
const res = yield fetch(endpoint, {
|
|
1110
|
+
method: "POST",
|
|
1111
|
+
headers: {
|
|
1112
|
+
"Content-Type": "application/json",
|
|
1113
|
+
},
|
|
1114
|
+
body: JSON.stringify(body),
|
|
1115
|
+
});
|
|
1116
|
+
if (!res.ok) {
|
|
1117
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1118
|
+
}
|
|
1119
|
+
let authResponse = null;
|
|
1120
|
+
try {
|
|
1121
|
+
authResponse = yield res.json();
|
|
1122
|
+
}
|
|
1123
|
+
catch (er) {
|
|
1124
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1125
|
+
}
|
|
1126
|
+
if (typeof authResponse.token !== "string") {
|
|
1127
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1128
|
+
}
|
|
1129
|
+
return authResponse;
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
class AuthenticationError extends Error {
|
|
1133
|
+
constructor(message) {
|
|
1134
|
+
super(message);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
package/lib/esm/types.d.ts
CHANGED
|
@@ -28,6 +28,14 @@ export declare type LiveListUpdates<TItem = any> = {
|
|
|
28
28
|
type: "LiveList";
|
|
29
29
|
node: LiveList<TItem>;
|
|
30
30
|
};
|
|
31
|
+
export declare type BroadcastOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* Whether or not event is queued if the connection is currently closed.
|
|
34
|
+
*
|
|
35
|
+
* ❗ We are not sure if we want to support this option in the future so it might be deprecated to be replaced by something else
|
|
36
|
+
*/
|
|
37
|
+
shouldQueueEventIfNotReady: boolean;
|
|
38
|
+
};
|
|
31
39
|
export declare type StorageUpdate = LiveMapUpdates | LiveObjectUpdates | LiveListUpdates;
|
|
32
40
|
export declare type StorageCallback = (updates: StorageUpdate[]) => void;
|
|
33
41
|
export declare type Client = {
|
|
@@ -65,6 +73,10 @@ export interface Others<TPresence extends Presence = Presence> {
|
|
|
65
73
|
* Number of other users in the room.
|
|
66
74
|
*/
|
|
67
75
|
readonly count: number;
|
|
76
|
+
/**
|
|
77
|
+
* Returns a new Iterator object that contains the users.
|
|
78
|
+
*/
|
|
79
|
+
[Symbol.iterator](): IterableIterator<User<TPresence>>;
|
|
68
80
|
/**
|
|
69
81
|
* Returns the array of connected users in room.
|
|
70
82
|
*/
|
|
@@ -109,6 +121,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
|
|
|
109
121
|
*/
|
|
110
122
|
export declare type ClientOptions = {
|
|
111
123
|
throttle?: number;
|
|
124
|
+
fetchPolyfill?: any;
|
|
125
|
+
WebSocketPolyfill?: any;
|
|
112
126
|
} & ({
|
|
113
127
|
publicApiKey: string;
|
|
114
128
|
authEndpoint?: never;
|
|
@@ -119,6 +133,17 @@ export declare type ClientOptions = {
|
|
|
119
133
|
export declare type AuthorizeResponse = {
|
|
120
134
|
token: string;
|
|
121
135
|
};
|
|
136
|
+
export declare type Authentication = {
|
|
137
|
+
type: "public";
|
|
138
|
+
publicApiKey: string;
|
|
139
|
+
url: string;
|
|
140
|
+
} | {
|
|
141
|
+
type: "private";
|
|
142
|
+
url: string;
|
|
143
|
+
} | {
|
|
144
|
+
type: "custom";
|
|
145
|
+
callback: (room: string) => Promise<AuthorizeResponse>;
|
|
146
|
+
};
|
|
122
147
|
declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
|
|
123
148
|
export declare type Connection = {
|
|
124
149
|
state: "closed" | "authenticating" | "unavailable" | "failed";
|
|
@@ -423,7 +448,7 @@ export declare type Room = {
|
|
|
423
448
|
* }
|
|
424
449
|
* });
|
|
425
450
|
*/
|
|
426
|
-
broadcastEvent: (event: any) => void;
|
|
451
|
+
broadcastEvent: (event: any, options?: BroadcastOptions) => void;
|
|
427
452
|
/**
|
|
428
453
|
* Get the room's storage asynchronously.
|
|
429
454
|
* The storage's root is a {@link LiveObject}.
|
package/lib/esm/utils.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { AbstractCrdt, Doc } from "./AbstractCrdt";
|
|
2
|
-
import { SerializedCrdtWithId } from "./live";
|
|
2
|
+
import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live";
|
|
3
3
|
export declare function remove<T>(array: T[], item: T): void;
|
|
4
4
|
export declare function isSameNodeOrChildOf(node: AbstractCrdt, parent: AbstractCrdt): boolean;
|
|
5
5
|
export declare function deserialize(entry: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): AbstractCrdt;
|
|
6
6
|
export declare function isCrdt(obj: any): obj is AbstractCrdt;
|
|
7
7
|
export declare function selfOrRegisterValue(obj: AbstractCrdt): any;
|
|
8
8
|
export declare function selfOrRegister(obj: any): AbstractCrdt;
|
|
9
|
+
export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[];
|
package/lib/esm/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CrdtType, } from "./live";
|
|
1
|
+
import { CrdtType, OpType, } from "./live";
|
|
2
2
|
import { LiveList } from "./LiveList";
|
|
3
3
|
import { LiveMap } from "./LiveMap";
|
|
4
4
|
import { LiveObject } from "./LiveObject";
|
|
@@ -64,3 +64,77 @@ export function selfOrRegister(obj) {
|
|
|
64
64
|
return new LiveRegister(obj);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
export function getTreesDiffOperations(currentItems, newItems) {
|
|
68
|
+
const ops = [];
|
|
69
|
+
currentItems.forEach((_, id) => {
|
|
70
|
+
if (!newItems.get(id)) {
|
|
71
|
+
// Delete crdt
|
|
72
|
+
ops.push({
|
|
73
|
+
type: OpType.DeleteCrdt,
|
|
74
|
+
id: id,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
newItems.forEach((crdt, id) => {
|
|
79
|
+
const currentCrdt = currentItems.get(id);
|
|
80
|
+
if (currentCrdt) {
|
|
81
|
+
if (crdt.type === CrdtType.Object) {
|
|
82
|
+
if (JSON.stringify(crdt.data) !==
|
|
83
|
+
JSON.stringify(currentCrdt.data)) {
|
|
84
|
+
ops.push({
|
|
85
|
+
type: OpType.UpdateObject,
|
|
86
|
+
id: id,
|
|
87
|
+
data: crdt.data,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (crdt.parentKey !== currentCrdt.parentKey) {
|
|
92
|
+
ops.push({
|
|
93
|
+
type: OpType.SetParentKey,
|
|
94
|
+
id: id,
|
|
95
|
+
parentKey: crdt.parentKey,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// new Crdt
|
|
101
|
+
switch (crdt.type) {
|
|
102
|
+
case CrdtType.Register:
|
|
103
|
+
ops.push({
|
|
104
|
+
type: OpType.CreateRegister,
|
|
105
|
+
id: id,
|
|
106
|
+
parentId: crdt.parentId,
|
|
107
|
+
parentKey: crdt.parentKey,
|
|
108
|
+
data: crdt.data,
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
case CrdtType.List:
|
|
112
|
+
ops.push({
|
|
113
|
+
type: OpType.CreateList,
|
|
114
|
+
id: id,
|
|
115
|
+
parentId: crdt.parentId,
|
|
116
|
+
parentKey: crdt.parentKey,
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
case CrdtType.Object:
|
|
120
|
+
ops.push({
|
|
121
|
+
type: OpType.CreateObject,
|
|
122
|
+
id: id,
|
|
123
|
+
parentId: crdt.parentId,
|
|
124
|
+
parentKey: crdt.parentKey,
|
|
125
|
+
data: crdt.data,
|
|
126
|
+
});
|
|
127
|
+
break;
|
|
128
|
+
case CrdtType.Map:
|
|
129
|
+
ops.push({
|
|
130
|
+
type: OpType.CreateMap,
|
|
131
|
+
id: id,
|
|
132
|
+
parentId: crdt.parentId,
|
|
133
|
+
parentKey: crdt.parentKey,
|
|
134
|
+
});
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return ops;
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liveblocks/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -29,9 +29,13 @@
|
|
|
29
29
|
"@babel/preset-env": "^7.12.16",
|
|
30
30
|
"@babel/preset-typescript": "^7.12.16",
|
|
31
31
|
"@types/jest": "^26.0.21",
|
|
32
|
+
"@types/node-fetch": "^2.6.1",
|
|
33
|
+
"@types/ws": "^8.2.2",
|
|
32
34
|
"babel-jest": "^26.6.3",
|
|
33
35
|
"jest": "^26.6.3",
|
|
34
|
-
"
|
|
36
|
+
"node-fetch": "2.6.7",
|
|
37
|
+
"typescript": "^4.4.0",
|
|
38
|
+
"ws": "^8.5.0"
|
|
35
39
|
},
|
|
36
40
|
"repository": {
|
|
37
41
|
"type": "git",
|