@liveblocks/client 0.15.0-alpha.2 → 0.15.0-alpha.3
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 +2 -1
- package/lib/cjs/LiveList.d.ts +1 -1
- package/lib/cjs/LiveList.js +4 -1
- package/lib/cjs/LiveMap.d.ts +1 -1
- package/lib/cjs/LiveMap.js +4 -1
- package/lib/cjs/LiveObject.d.ts +1 -1
- package/lib/cjs/LiveObject.js +35 -3
- package/lib/cjs/LiveRegister.d.ts +1 -1
- package/lib/cjs/LiveRegister.js +1 -1
- package/lib/cjs/client.js +50 -7
- package/lib/cjs/immutable.js +8 -0
- package/lib/cjs/room.d.ts +8 -8
- package/lib/cjs/room.js +97 -62
- package/lib/cjs/types.d.ts +13 -0
- package/lib/cjs/utils.d.ts +4 -0
- package/lib/cjs/utils.js +51 -1
- package/lib/esm/AbstractCrdt.d.ts +2 -1
- package/lib/esm/LiveList.d.ts +1 -1
- package/lib/esm/LiveList.js +4 -1
- package/lib/esm/LiveMap.d.ts +1 -1
- package/lib/esm/LiveMap.js +4 -1
- package/lib/esm/LiveObject.d.ts +1 -1
- package/lib/esm/LiveObject.js +35 -3
- package/lib/esm/LiveRegister.d.ts +1 -1
- package/lib/esm/LiveRegister.js +1 -1
- package/lib/esm/client.js +50 -7
- package/lib/esm/immutable.js +8 -0
- package/lib/esm/room.d.ts +8 -8
- package/lib/esm/room.js +96 -42
- package/lib/esm/types.d.ts +13 -0
- package/lib/esm/utils.d.ts +4 -0
- package/lib/esm/utils.js +49 -0
- package/package.json +8 -3
- package/lib/cjs/authentication.d.ts +0 -3
- package/lib/cjs/authentication.js +0 -71
- package/lib/esm/authentication.d.ts +0 -3
- package/lib/esm/authentication.js +0 -66
package/lib/cjs/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mergeStorageUpdates = exports.getTreesDiffOperations = exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0;
|
|
3
|
+
exports.findNonSerializableValue = exports.mergeStorageUpdates = exports.getTreesDiffOperations = exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0;
|
|
4
4
|
const live_1 = require("./live");
|
|
5
5
|
const LiveList_1 = require("./LiveList");
|
|
6
6
|
const LiveMap_1 = require("./LiveMap");
|
|
@@ -173,3 +173,53 @@ function mergeStorageUpdates(first, second) {
|
|
|
173
173
|
return second;
|
|
174
174
|
}
|
|
175
175
|
exports.mergeStorageUpdates = mergeStorageUpdates;
|
|
176
|
+
function isPlain(value) {
|
|
177
|
+
const type = typeof value;
|
|
178
|
+
return (type === "undefined" ||
|
|
179
|
+
value === null ||
|
|
180
|
+
type === "string" ||
|
|
181
|
+
type === "boolean" ||
|
|
182
|
+
type === "number" ||
|
|
183
|
+
Array.isArray(value) ||
|
|
184
|
+
isPlainObject(value));
|
|
185
|
+
}
|
|
186
|
+
function isPlainObject(value) {
|
|
187
|
+
if (typeof value !== "object" || value === null)
|
|
188
|
+
return false;
|
|
189
|
+
let proto = Object.getPrototypeOf(value);
|
|
190
|
+
if (proto === null)
|
|
191
|
+
return true;
|
|
192
|
+
let baseProto = proto;
|
|
193
|
+
while (Object.getPrototypeOf(baseProto) !== null) {
|
|
194
|
+
baseProto = Object.getPrototypeOf(baseProto);
|
|
195
|
+
}
|
|
196
|
+
return proto === baseProto;
|
|
197
|
+
}
|
|
198
|
+
function findNonSerializableValue(value, path = "") {
|
|
199
|
+
if (!isPlain) {
|
|
200
|
+
return {
|
|
201
|
+
path: path || "root",
|
|
202
|
+
value: value,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (typeof value !== "object" || value === null) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
209
|
+
const nestedPath = path ? path + "." + key : key;
|
|
210
|
+
if (!isPlain(nestedValue)) {
|
|
211
|
+
return {
|
|
212
|
+
path: nestedPath,
|
|
213
|
+
value: nestedValue,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (typeof nestedValue === "object") {
|
|
217
|
+
const nonSerializableNestedValue = findNonSerializableValue(nestedValue, nestedPath);
|
|
218
|
+
if (nonSerializableNestedValue) {
|
|
219
|
+
return nonSerializableNestedValue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
exports.findNonSerializableValue = findNonSerializableValue;
|
|
@@ -9,6 +9,7 @@ export declare type ApplyResult = {
|
|
|
9
9
|
export interface Doc {
|
|
10
10
|
generateId: () => string;
|
|
11
11
|
generateOpId: () => string;
|
|
12
|
+
getItem: (id: string) => AbstractCrdt | undefined;
|
|
12
13
|
addItem: (id: string, item: AbstractCrdt) => void;
|
|
13
14
|
deleteItem: (id: string) => void;
|
|
14
15
|
dispatch: (ops: Op[], reverseOps: Op[], storageUpdates: Map<string, StorageUpdate>) => void;
|
|
@@ -46,7 +47,7 @@ export declare abstract class AbstractCrdt {
|
|
|
46
47
|
/**
|
|
47
48
|
* INTERNAL
|
|
48
49
|
*/
|
|
49
|
-
abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
|
|
50
|
+
abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
|
|
50
51
|
/**
|
|
51
52
|
* INTERNAL
|
|
52
53
|
*/
|
package/lib/esm/LiveList.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare class LiveList<T> extends AbstractCrdt {
|
|
|
26
26
|
/**
|
|
27
27
|
* INTERNAL
|
|
28
28
|
*/
|
|
29
|
-
_attachChild(id: string, key: string, child: AbstractCrdt, isLocal: boolean): ApplyResult;
|
|
29
|
+
_attachChild(id: string, key: string, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
|
|
30
30
|
/**
|
|
31
31
|
* INTERNAL
|
|
32
32
|
*/
|
package/lib/esm/LiveList.js
CHANGED
|
@@ -97,11 +97,14 @@ export class LiveList extends AbstractCrdt {
|
|
|
97
97
|
/**
|
|
98
98
|
* INTERNAL
|
|
99
99
|
*/
|
|
100
|
-
_attachChild(id, key, child, isLocal) {
|
|
100
|
+
_attachChild(id, key, child, opId, isLocal) {
|
|
101
101
|
var _a;
|
|
102
102
|
if (this._doc == null) {
|
|
103
103
|
throw new Error("Can't attach child if doc is not present");
|
|
104
104
|
}
|
|
105
|
+
if (this._doc.getItem(id) !== undefined) {
|
|
106
|
+
return { modified: false };
|
|
107
|
+
}
|
|
105
108
|
child._attach(id, this._doc);
|
|
106
109
|
child._setParentLink(this, key);
|
|
107
110
|
const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key);
|
package/lib/esm/LiveMap.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare class LiveMap<TKey extends string, TValue> extends AbstractCrdt {
|
|
|
23
23
|
/**
|
|
24
24
|
* INTERNAL
|
|
25
25
|
*/
|
|
26
|
-
_attachChild(id: string, key: TKey, child: AbstractCrdt, isLocal: boolean): ApplyResult;
|
|
26
|
+
_attachChild(id: string, key: TKey, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
|
|
27
27
|
/**
|
|
28
28
|
* INTERNAL
|
|
29
29
|
*/
|
package/lib/esm/LiveMap.js
CHANGED
|
@@ -97,10 +97,13 @@ export class LiveMap extends AbstractCrdt {
|
|
|
97
97
|
/**
|
|
98
98
|
* INTERNAL
|
|
99
99
|
*/
|
|
100
|
-
_attachChild(id, key, child, isLocal) {
|
|
100
|
+
_attachChild(id, key, child, opId, isLocal) {
|
|
101
101
|
if (this._doc == null) {
|
|
102
102
|
throw new Error("Can't attach child if doc is not present");
|
|
103
103
|
}
|
|
104
|
+
if (this._doc.getItem(id) !== undefined) {
|
|
105
|
+
return { modified: false };
|
|
106
|
+
}
|
|
104
107
|
const previousValue = __classPrivateFieldGet(this, _LiveMap_map, "f").get(key);
|
|
105
108
|
let reverse;
|
|
106
109
|
if (previousValue) {
|
package/lib/esm/LiveObject.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare class LiveObject<T extends Record<string, any> = Record<string, a
|
|
|
27
27
|
/**
|
|
28
28
|
* INTERNAL
|
|
29
29
|
*/
|
|
30
|
-
_attachChild(id: string, key: keyof T, child: AbstractCrdt, isLocal: boolean): ApplyResult;
|
|
30
|
+
_attachChild(id: string, key: keyof T, child: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
|
|
31
31
|
/**
|
|
32
32
|
* INTERNAL
|
|
33
33
|
*/
|
package/lib/esm/LiveObject.js
CHANGED
|
@@ -103,10 +103,32 @@ export class LiveObject extends AbstractCrdt {
|
|
|
103
103
|
/**
|
|
104
104
|
* INTERNAL
|
|
105
105
|
*/
|
|
106
|
-
_attachChild(id, key, child, isLocal) {
|
|
106
|
+
_attachChild(id, key, child, opId, isLocal) {
|
|
107
107
|
if (this._doc == null) {
|
|
108
108
|
throw new Error("Can't attach child if doc is not present");
|
|
109
109
|
}
|
|
110
|
+
if (this._doc.getItem(id) !== undefined) {
|
|
111
|
+
if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === opId) {
|
|
112
|
+
// Acknowlegment from local operation
|
|
113
|
+
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").delete(key);
|
|
114
|
+
}
|
|
115
|
+
return { modified: false };
|
|
116
|
+
}
|
|
117
|
+
if (isLocal) {
|
|
118
|
+
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
|
|
119
|
+
}
|
|
120
|
+
else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === undefined) {
|
|
121
|
+
// Remote operation with no local change => apply operation
|
|
122
|
+
}
|
|
123
|
+
else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === opId) {
|
|
124
|
+
// Acknowlegment from local operation
|
|
125
|
+
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").delete(key);
|
|
126
|
+
return { modified: false };
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Conflict, ignore remote operation
|
|
130
|
+
return { modified: false };
|
|
131
|
+
}
|
|
110
132
|
const previousValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
|
|
111
133
|
let reverse;
|
|
112
134
|
if (isCrdt(previousValue)) {
|
|
@@ -311,7 +333,6 @@ export class LiveObject extends AbstractCrdt {
|
|
|
311
333
|
};
|
|
312
334
|
const updateDelta = {};
|
|
313
335
|
for (const key in overrides) {
|
|
314
|
-
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
|
|
315
336
|
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
|
|
316
337
|
if (oldValue instanceof AbstractCrdt) {
|
|
317
338
|
reverseOps.push(...oldValue._serialize(this._id, key));
|
|
@@ -327,10 +348,16 @@ export class LiveObject extends AbstractCrdt {
|
|
|
327
348
|
if (newValue instanceof AbstractCrdt) {
|
|
328
349
|
newValue._setParentLink(this, key);
|
|
329
350
|
newValue._attach(this._doc.generateId(), this._doc);
|
|
330
|
-
|
|
351
|
+
const newAttachChildOps = newValue._serialize(this._id, key, this._doc);
|
|
352
|
+
const createCrdtOp = newAttachChildOps.find((op) => op.parentId === this._id);
|
|
353
|
+
if (createCrdtOp) {
|
|
354
|
+
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, createCrdtOp.opId);
|
|
355
|
+
}
|
|
356
|
+
ops.push(...newAttachChildOps);
|
|
331
357
|
}
|
|
332
358
|
else {
|
|
333
359
|
updatedProps[key] = newValue;
|
|
360
|
+
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
|
|
334
361
|
}
|
|
335
362
|
__classPrivateFieldGet(this, _LiveObject_map, "f").set(key, newValue);
|
|
336
363
|
updateDelta[key] = { type: "update" };
|
|
@@ -422,6 +449,11 @@ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _
|
|
|
422
449
|
if (__classPrivateFieldGet(this, _LiveObject_map, "f").has(key) === false) {
|
|
423
450
|
return { modified: false };
|
|
424
451
|
}
|
|
452
|
+
// If a local operation exists on the same key
|
|
453
|
+
// prevent flickering by not applying delete op.
|
|
454
|
+
if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) !== undefined) {
|
|
455
|
+
return { modified: false };
|
|
456
|
+
}
|
|
425
457
|
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
|
|
426
458
|
let reverse = [];
|
|
427
459
|
if (isCrdt(oldValue)) {
|
|
@@ -19,7 +19,7 @@ export declare class LiveRegister<TValue = any> extends AbstractCrdt {
|
|
|
19
19
|
* INTERNAL
|
|
20
20
|
*/
|
|
21
21
|
_toSerializedCrdt(): SerializedCrdt;
|
|
22
|
-
_attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult;
|
|
22
|
+
_attachChild(id: string, key: string, crdt: AbstractCrdt, opId: string, isLocal: boolean): ApplyResult;
|
|
23
23
|
_detachChild(crdt: AbstractCrdt): ApplyResult;
|
|
24
24
|
_apply(op: Op, isLocal: boolean): ApplyResult;
|
|
25
25
|
/**
|
package/lib/esm/LiveRegister.js
CHANGED
package/lib/esm/client.js
CHANGED
|
@@ -26,11 +26,7 @@ import { createRoom } from "./room";
|
|
|
26
26
|
*/
|
|
27
27
|
export function createClient(options) {
|
|
28
28
|
const clientOptions = options;
|
|
29
|
-
|
|
30
|
-
if (clientOptions.throttle < 80 || clientOptions.throttle > 1000) {
|
|
31
|
-
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");
|
|
32
|
-
}
|
|
33
|
-
}
|
|
29
|
+
const throttleDelay = getThrottleDelayFromOptions(options);
|
|
34
30
|
const rooms = new Map();
|
|
35
31
|
function getRoom(roomId) {
|
|
36
32
|
const internalRoom = rooms.get(roomId);
|
|
@@ -41,9 +37,21 @@ export function createClient(options) {
|
|
|
41
37
|
if (internalRoom) {
|
|
42
38
|
return internalRoom.room;
|
|
43
39
|
}
|
|
44
|
-
internalRoom = createRoom(
|
|
40
|
+
internalRoom = createRoom({
|
|
41
|
+
defaultPresence: options.defaultPresence,
|
|
42
|
+
defaultStorageRoot: options.defaultStorageRoot,
|
|
43
|
+
}, {
|
|
44
|
+
room: roomId,
|
|
45
|
+
throttleDelay,
|
|
46
|
+
WebSocketPolyfill: clientOptions.WebSocketPolyfill,
|
|
47
|
+
fetchPolyfill: clientOptions.fetchPolyfill,
|
|
48
|
+
liveblocksServer: clientOptions.liveblocksServer || "wss://liveblocks.net/v5",
|
|
49
|
+
authentication: prepareAuthentication(clientOptions),
|
|
50
|
+
});
|
|
45
51
|
rooms.set(roomId, internalRoom);
|
|
46
|
-
|
|
52
|
+
if (!options.DO_NOT_USE_withoutConnecting) {
|
|
53
|
+
internalRoom.connect();
|
|
54
|
+
}
|
|
47
55
|
return internalRoom.room;
|
|
48
56
|
}
|
|
49
57
|
function leave(roomId) {
|
|
@@ -74,3 +82,38 @@ export function createClient(options) {
|
|
|
74
82
|
leave,
|
|
75
83
|
};
|
|
76
84
|
}
|
|
85
|
+
function getThrottleDelayFromOptions(options) {
|
|
86
|
+
if (options.throttle === undefined) {
|
|
87
|
+
return 100;
|
|
88
|
+
}
|
|
89
|
+
if (typeof options.throttle !== "number" ||
|
|
90
|
+
options.throttle < 80 ||
|
|
91
|
+
options.throttle > 1000) {
|
|
92
|
+
throw new Error("throttle should be a number between 80 and 1000.");
|
|
93
|
+
}
|
|
94
|
+
return options.throttle;
|
|
95
|
+
}
|
|
96
|
+
function prepareAuthentication(clientOptions) {
|
|
97
|
+
// TODO: throw descriptive errors for invalid options
|
|
98
|
+
if (typeof clientOptions.publicApiKey === "string") {
|
|
99
|
+
return {
|
|
100
|
+
type: "public",
|
|
101
|
+
publicApiKey: clientOptions.publicApiKey,
|
|
102
|
+
url: clientOptions.publicAuthorizeEndpoint ||
|
|
103
|
+
"https://liveblocks.io/api/public/authorize",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
else if (typeof clientOptions.authEndpoint === "string") {
|
|
107
|
+
return {
|
|
108
|
+
type: "private",
|
|
109
|
+
url: clientOptions.authEndpoint,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
else if (typeof clientOptions.authEndpoint === "function") {
|
|
113
|
+
return {
|
|
114
|
+
type: "custom",
|
|
115
|
+
callback: clientOptions.authEndpoint,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
throw new Error("Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient");
|
|
119
|
+
}
|
package/lib/esm/immutable.js
CHANGED
|
@@ -2,6 +2,7 @@ import { LiveList } from "./LiveList";
|
|
|
2
2
|
import { LiveMap } from "./LiveMap";
|
|
3
3
|
import { LiveObject } from "./LiveObject";
|
|
4
4
|
import { LiveRegister } from "./LiveRegister";
|
|
5
|
+
import { findNonSerializableValue } from "./utils";
|
|
5
6
|
export function liveObjectToJson(liveObject) {
|
|
6
7
|
const result = {};
|
|
7
8
|
const obj = liveObject.toObject();
|
|
@@ -134,6 +135,13 @@ export function patchLiveList(liveList, prev, next) {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
export function patchLiveObjectKey(liveObject, key, prev, next) {
|
|
138
|
+
if (process.env.NODE_ENV !== "production") {
|
|
139
|
+
const nonSerializableValue = findNonSerializableValue(next);
|
|
140
|
+
if (nonSerializableValue) {
|
|
141
|
+
console.error(`New state path: '${nonSerializableValue.path}' value: '${nonSerializableValue.value}' is not serializable.\nOnly serializable value can be synced with Liveblocks.`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
137
145
|
const value = liveObject.get(key);
|
|
138
146
|
if (next === undefined) {
|
|
139
147
|
liveObject.delete(key);
|
package/lib/esm/room.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Others, Presence,
|
|
1
|
+
import { Others, Presence, Room, MyPresenceCallback, OthersEventCallback, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate, BroadcastOptions, AuthorizeResponse, Authentication } from "./types";
|
|
2
2
|
import { ClientMessage, Op } from "./live";
|
|
3
3
|
import { LiveMap } from "./LiveMap";
|
|
4
4
|
import { LiveObject } from "./LiveObject";
|
|
@@ -66,7 +66,7 @@ export declare type State = {
|
|
|
66
66
|
offlineOperations: Map<string, Op>;
|
|
67
67
|
};
|
|
68
68
|
export declare type Effects = {
|
|
69
|
-
authenticate(): void;
|
|
69
|
+
authenticate(auth: (room: string) => Promise<AuthorizeResponse>, createWebSocket: (token: string) => WebSocket): void;
|
|
70
70
|
send(messages: ClientMessage[]): void;
|
|
71
71
|
delayFlush(delay: number): number;
|
|
72
72
|
startHeartbeatInterval(): number;
|
|
@@ -75,13 +75,13 @@ export declare type Effects = {
|
|
|
75
75
|
};
|
|
76
76
|
declare type Context = {
|
|
77
77
|
room: string;
|
|
78
|
-
authEndpoint: AuthEndpoint;
|
|
79
|
-
liveblocksServer: string;
|
|
80
78
|
throttleDelay: number;
|
|
81
|
-
|
|
79
|
+
fetchPolyfill?: typeof fetch;
|
|
80
|
+
WebSocketPolyfill?: typeof WebSocket;
|
|
81
|
+
authentication: Authentication;
|
|
82
|
+
liveblocksServer: string;
|
|
82
83
|
};
|
|
83
84
|
export declare function makeStateMachine(state: State, context: Context, mockedEffects?: Effects): {
|
|
84
|
-
onOpen: () => void;
|
|
85
85
|
onClose: (event: {
|
|
86
86
|
code: number;
|
|
87
87
|
wasClean: boolean;
|
|
@@ -152,8 +152,8 @@ export declare type InternalRoom = {
|
|
|
152
152
|
onNavigatorOnline: () => void;
|
|
153
153
|
onVisibilityChange: (visibilityState: VisibilityState) => void;
|
|
154
154
|
};
|
|
155
|
-
export declare function createRoom(
|
|
155
|
+
export declare function createRoom(options: {
|
|
156
156
|
defaultPresence?: Presence;
|
|
157
157
|
defaultStorageRoot?: Record<string, any>;
|
|
158
|
-
}): InternalRoom;
|
|
158
|
+
}, context: Context): InternalRoom;
|
|
159
159
|
export {};
|
package/lib/esm/room.js
CHANGED
|
@@ -8,7 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { getTreesDiffOperations, isSameNodeOrChildOf, remove, mergeStorageUpdates, } from "./utils";
|
|
11
|
-
import auth, { parseToken } from "./authentication";
|
|
12
11
|
import { ClientMessageType, ServerMessageType, OpType, } from "./live";
|
|
13
12
|
import { LiveMap } from "./LiveMap";
|
|
14
13
|
import { LiveObject } from "./LiveObject";
|
|
@@ -53,16 +52,12 @@ function log(...params) {
|
|
|
53
52
|
}
|
|
54
53
|
export function makeStateMachine(state, context, mockedEffects) {
|
|
55
54
|
const effects = mockedEffects || {
|
|
56
|
-
authenticate() {
|
|
55
|
+
authenticate(auth, createWebSocket) {
|
|
57
56
|
return __awaiter(this, void 0, void 0, function* () {
|
|
58
57
|
try {
|
|
59
|
-
const token = yield auth(context.
|
|
58
|
+
const { token } = yield auth(context.room);
|
|
60
59
|
const parsedToken = parseToken(token);
|
|
61
|
-
const socket =
|
|
62
|
-
socket.addEventListener("message", onMessage);
|
|
63
|
-
socket.addEventListener("open", onOpen);
|
|
64
|
-
socket.addEventListener("close", onClose);
|
|
65
|
-
socket.addEventListener("error", onError);
|
|
60
|
+
const socket = createWebSocket(token);
|
|
66
61
|
authenticationSuccess(parsedToken, socket);
|
|
67
62
|
}
|
|
68
63
|
catch (er) {
|
|
@@ -165,6 +160,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
165
160
|
function load(items) {
|
|
166
161
|
const [root, parentToChildren] = buildRootAndParentToChildren(items);
|
|
167
162
|
return LiveObject._deserialize(root, parentToChildren, {
|
|
163
|
+
getItem,
|
|
168
164
|
addItem,
|
|
169
165
|
deleteItem,
|
|
170
166
|
generateId,
|
|
@@ -319,31 +315,31 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
319
315
|
}
|
|
320
316
|
case OpType.CreateObject: {
|
|
321
317
|
const parent = state.items.get(op.parentId);
|
|
322
|
-
if (parent == null
|
|
318
|
+
if (parent == null) {
|
|
323
319
|
return { modified: false };
|
|
324
320
|
}
|
|
325
|
-
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal);
|
|
321
|
+
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), op.opId, isLocal);
|
|
326
322
|
}
|
|
327
323
|
case OpType.CreateList: {
|
|
328
324
|
const parent = state.items.get(op.parentId);
|
|
329
|
-
if (parent == null
|
|
325
|
+
if (parent == null) {
|
|
330
326
|
return { modified: false };
|
|
331
327
|
}
|
|
332
|
-
return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal);
|
|
328
|
+
return parent._attachChild(op.id, op.parentKey, new LiveList(), op.opId, isLocal);
|
|
333
329
|
}
|
|
334
330
|
case OpType.CreateRegister: {
|
|
335
331
|
const parent = state.items.get(op.parentId);
|
|
336
|
-
if (parent == null
|
|
332
|
+
if (parent == null) {
|
|
337
333
|
return { modified: false };
|
|
338
334
|
}
|
|
339
|
-
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal);
|
|
335
|
+
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), op.opId, isLocal);
|
|
340
336
|
}
|
|
341
337
|
case OpType.CreateMap: {
|
|
342
338
|
const parent = state.items.get(op.parentId);
|
|
343
|
-
if (parent == null
|
|
339
|
+
if (parent == null) {
|
|
344
340
|
return { modified: false };
|
|
345
341
|
}
|
|
346
|
-
return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal);
|
|
342
|
+
return parent._attachChild(op.id, op.parentKey, new LiveMap(), op.opId, isLocal);
|
|
347
343
|
}
|
|
348
344
|
}
|
|
349
345
|
return { modified: false };
|
|
@@ -390,15 +386,14 @@ See v0.13 release notes for more information.
|
|
|
390
386
|
: null;
|
|
391
387
|
}
|
|
392
388
|
function connect() {
|
|
393
|
-
if (typeof window === "undefined") {
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
389
|
if (state.connection.state !== "closed" &&
|
|
397
390
|
state.connection.state !== "unavailable") {
|
|
398
391
|
return null;
|
|
399
392
|
}
|
|
393
|
+
const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
|
|
394
|
+
const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
|
|
400
395
|
updateConnection({ state: "authenticating" });
|
|
401
|
-
effects.authenticate();
|
|
396
|
+
effects.authenticate(auth, createWebSocket);
|
|
402
397
|
}
|
|
403
398
|
function updatePresence(overrides, options) {
|
|
404
399
|
const oldValues = {};
|
|
@@ -425,6 +420,10 @@ See v0.13 release notes for more information.
|
|
|
425
420
|
}
|
|
426
421
|
}
|
|
427
422
|
function authenticationSuccess(token, socket) {
|
|
423
|
+
socket.addEventListener("message", onMessage);
|
|
424
|
+
socket.addEventListener("open", onOpen);
|
|
425
|
+
socket.addEventListener("close", onClose);
|
|
426
|
+
socket.addEventListener("error", onError);
|
|
428
427
|
updateConnection({
|
|
429
428
|
state: "connecting",
|
|
430
429
|
id: token.actor,
|
|
@@ -658,7 +657,7 @@ See v0.13 release notes for more information.
|
|
|
658
657
|
}
|
|
659
658
|
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
660
659
|
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
|
|
661
|
-
if (state.socket.readyState ===
|
|
660
|
+
if (state.socket.readyState === state.socket.OPEN) {
|
|
662
661
|
state.socket.send("ping");
|
|
663
662
|
}
|
|
664
663
|
}
|
|
@@ -705,7 +704,7 @@ See v0.13 release notes for more information.
|
|
|
705
704
|
state.offlineOperations.set(op.opId, op);
|
|
706
705
|
});
|
|
707
706
|
}
|
|
708
|
-
if (state.socket == null || state.socket.readyState !==
|
|
707
|
+
if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
|
|
709
708
|
state.buffer.storageOperations = [];
|
|
710
709
|
return;
|
|
711
710
|
}
|
|
@@ -912,7 +911,6 @@ See v0.13 release notes for more information.
|
|
|
912
911
|
}
|
|
913
912
|
return {
|
|
914
913
|
// Internal
|
|
915
|
-
onOpen,
|
|
916
914
|
onClose,
|
|
917
915
|
onMessage,
|
|
918
916
|
authenticationSuccess,
|
|
@@ -1004,26 +1002,9 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
1004
1002
|
offlineOperations: new Map(),
|
|
1005
1003
|
};
|
|
1006
1004
|
}
|
|
1007
|
-
export function createRoom(
|
|
1008
|
-
const throttleDelay = options.throttle || 100;
|
|
1009
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
|
|
1010
|
-
let authEndpoint;
|
|
1011
|
-
if (options.authEndpoint) {
|
|
1012
|
-
authEndpoint = options.authEndpoint;
|
|
1013
|
-
}
|
|
1014
|
-
else {
|
|
1015
|
-
const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
|
|
1016
|
-
"https://liveblocks.io/api/public/authorize";
|
|
1017
|
-
authEndpoint = publicAuthorizeEndpoint;
|
|
1018
|
-
}
|
|
1005
|
+
export function createRoom(options, context) {
|
|
1019
1006
|
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
|
|
1020
|
-
const machine = makeStateMachine(state,
|
|
1021
|
-
throttleDelay,
|
|
1022
|
-
liveblocksServer,
|
|
1023
|
-
authEndpoint,
|
|
1024
|
-
room: name,
|
|
1025
|
-
publicApiKey: options.publicApiKey,
|
|
1026
|
-
});
|
|
1007
|
+
const machine = makeStateMachine(state, context);
|
|
1027
1008
|
const room = {
|
|
1028
1009
|
/////////////
|
|
1029
1010
|
// Core //
|
|
@@ -1067,3 +1048,76 @@ class LiveblocksError extends Error {
|
|
|
1067
1048
|
this.code = code;
|
|
1068
1049
|
}
|
|
1069
1050
|
}
|
|
1051
|
+
function parseToken(token) {
|
|
1052
|
+
const tokenParts = token.split(".");
|
|
1053
|
+
if (tokenParts.length !== 3) {
|
|
1054
|
+
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1055
|
+
}
|
|
1056
|
+
const data = JSON.parse(atob(tokenParts[1]));
|
|
1057
|
+
if (typeof data.actor !== "number") {
|
|
1058
|
+
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1059
|
+
}
|
|
1060
|
+
return data;
|
|
1061
|
+
}
|
|
1062
|
+
function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
|
|
1063
|
+
if (typeof window === "undefined" && WebSocketPolyfill == null) {
|
|
1064
|
+
throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
|
|
1065
|
+
}
|
|
1066
|
+
const ws = WebSocketPolyfill || WebSocket;
|
|
1067
|
+
return (token) => {
|
|
1068
|
+
return new ws(`${liveblocksServer}/?token=${token}`);
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function prepareAuthEndpoint(authentication, fetchPolyfill) {
|
|
1072
|
+
if (authentication.type === "public") {
|
|
1073
|
+
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1074
|
+
throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
|
|
1075
|
+
}
|
|
1076
|
+
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1077
|
+
room,
|
|
1078
|
+
publicApiKey: authentication.publicApiKey,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
if (authentication.type === "private") {
|
|
1082
|
+
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1083
|
+
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.");
|
|
1084
|
+
}
|
|
1085
|
+
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1086
|
+
room,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
if (authentication.type === "custom") {
|
|
1090
|
+
return authentication.callback;
|
|
1091
|
+
}
|
|
1092
|
+
throw new Error("Internal error. Unexpected authentication type");
|
|
1093
|
+
}
|
|
1094
|
+
function fetchAuthEndpoint(fetch, endpoint, body) {
|
|
1095
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1096
|
+
const res = yield fetch(endpoint, {
|
|
1097
|
+
method: "POST",
|
|
1098
|
+
headers: {
|
|
1099
|
+
"Content-Type": "application/json",
|
|
1100
|
+
},
|
|
1101
|
+
body: JSON.stringify(body),
|
|
1102
|
+
});
|
|
1103
|
+
if (!res.ok) {
|
|
1104
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1105
|
+
}
|
|
1106
|
+
let authResponse = null;
|
|
1107
|
+
try {
|
|
1108
|
+
authResponse = yield res.json();
|
|
1109
|
+
}
|
|
1110
|
+
catch (er) {
|
|
1111
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1112
|
+
}
|
|
1113
|
+
if (typeof authResponse.token !== "string") {
|
|
1114
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1115
|
+
}
|
|
1116
|
+
return authResponse;
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
class AuthenticationError extends Error {
|
|
1120
|
+
constructor(message) {
|
|
1121
|
+
super(message);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
package/lib/esm/types.d.ts
CHANGED
|
@@ -146,6 +146,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
|
|
|
146
146
|
*/
|
|
147
147
|
export declare type ClientOptions = {
|
|
148
148
|
throttle?: number;
|
|
149
|
+
fetchPolyfill?: any;
|
|
150
|
+
WebSocketPolyfill?: any;
|
|
149
151
|
} & ({
|
|
150
152
|
publicApiKey: string;
|
|
151
153
|
authEndpoint?: never;
|
|
@@ -156,6 +158,17 @@ export declare type ClientOptions = {
|
|
|
156
158
|
export declare type AuthorizeResponse = {
|
|
157
159
|
token: string;
|
|
158
160
|
};
|
|
161
|
+
export declare type Authentication = {
|
|
162
|
+
type: "public";
|
|
163
|
+
publicApiKey: string;
|
|
164
|
+
url: string;
|
|
165
|
+
} | {
|
|
166
|
+
type: "private";
|
|
167
|
+
url: string;
|
|
168
|
+
} | {
|
|
169
|
+
type: "custom";
|
|
170
|
+
callback: (room: string) => Promise<AuthorizeResponse>;
|
|
171
|
+
};
|
|
159
172
|
declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
|
|
160
173
|
export declare type Connection = {
|
|
161
174
|
state: "closed" | "authenticating" | "unavailable" | "failed";
|
package/lib/esm/utils.d.ts
CHANGED
|
@@ -9,3 +9,7 @@ export declare function selfOrRegisterValue(obj: AbstractCrdt): any;
|
|
|
9
9
|
export declare function selfOrRegister(obj: any): AbstractCrdt;
|
|
10
10
|
export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[];
|
|
11
11
|
export declare function mergeStorageUpdates(first: StorageUpdate | undefined, second: StorageUpdate): StorageUpdate;
|
|
12
|
+
export declare function findNonSerializableValue(value: any, path?: string): {
|
|
13
|
+
path: string;
|
|
14
|
+
value: any;
|
|
15
|
+
} | false;
|