@liveblocks/client 0.14.0 → 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/client.js +50 -7
- package/lib/cjs/immutable.d.ts +9 -0
- package/lib/cjs/immutable.js +291 -0
- package/lib/cjs/room.d.ts +8 -8
- package/lib/cjs/room.js +88 -54
- package/lib/cjs/types.d.ts +13 -0
- package/lib/esm/client.js +50 -7
- package/lib/esm/immutable.d.ts +9 -0
- package/lib/esm/immutable.js +282 -0
- package/lib/esm/room.d.ts +8 -8
- package/lib/esm/room.js +87 -34
- package/lib/esm/types.d.ts +13 -0
- package/package.json +7 -3
package/lib/cjs/client.js
CHANGED
|
@@ -29,11 +29,7 @@ const room_1 = require("./room");
|
|
|
29
29
|
*/
|
|
30
30
|
function createClient(options) {
|
|
31
31
|
const clientOptions = options;
|
|
32
|
-
|
|
33
|
-
if (clientOptions.throttle < 80 || clientOptions.throttle > 1000) {
|
|
34
|
-
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");
|
|
35
|
-
}
|
|
36
|
-
}
|
|
32
|
+
const throttleDelay = getThrottleDelayFromOptions(options);
|
|
37
33
|
const rooms = new Map();
|
|
38
34
|
function getRoom(roomId) {
|
|
39
35
|
const internalRoom = rooms.get(roomId);
|
|
@@ -44,9 +40,21 @@ function createClient(options) {
|
|
|
44
40
|
if (internalRoom) {
|
|
45
41
|
return internalRoom.room;
|
|
46
42
|
}
|
|
47
|
-
internalRoom = (0, room_1.createRoom)(
|
|
43
|
+
internalRoom = (0, room_1.createRoom)({
|
|
44
|
+
defaultPresence: options.defaultPresence,
|
|
45
|
+
defaultStorageRoot: options.defaultStorageRoot,
|
|
46
|
+
}, {
|
|
47
|
+
room: roomId,
|
|
48
|
+
throttleDelay,
|
|
49
|
+
WebSocketPolyfill: clientOptions.WebSocketPolyfill,
|
|
50
|
+
fetchPolyfill: clientOptions.fetchPolyfill,
|
|
51
|
+
liveblocksServer: clientOptions.liveblocksServer || "wss://liveblocks.net/v5",
|
|
52
|
+
authentication: prepareAuthentication(clientOptions),
|
|
53
|
+
});
|
|
48
54
|
rooms.set(roomId, internalRoom);
|
|
49
|
-
|
|
55
|
+
if (!options.DO_NOT_USE_withoutConnecting) {
|
|
56
|
+
internalRoom.connect();
|
|
57
|
+
}
|
|
50
58
|
return internalRoom.room;
|
|
51
59
|
}
|
|
52
60
|
function leave(roomId) {
|
|
@@ -78,3 +86,38 @@ function createClient(options) {
|
|
|
78
86
|
};
|
|
79
87
|
}
|
|
80
88
|
exports.createClient = createClient;
|
|
89
|
+
function getThrottleDelayFromOptions(options) {
|
|
90
|
+
if (options.throttle === undefined) {
|
|
91
|
+
return 100;
|
|
92
|
+
}
|
|
93
|
+
if (typeof options.throttle !== "number" ||
|
|
94
|
+
options.throttle < 80 ||
|
|
95
|
+
options.throttle > 1000) {
|
|
96
|
+
throw new Error("throttle should be a number between 80 and 1000.");
|
|
97
|
+
}
|
|
98
|
+
return options.throttle;
|
|
99
|
+
}
|
|
100
|
+
function prepareAuthentication(clientOptions) {
|
|
101
|
+
// TODO: throw descriptive errors for invalid options
|
|
102
|
+
if (typeof clientOptions.publicApiKey === "string") {
|
|
103
|
+
return {
|
|
104
|
+
type: "public",
|
|
105
|
+
publicApiKey: clientOptions.publicApiKey,
|
|
106
|
+
url: clientOptions.publicAuthorizeEndpoint ||
|
|
107
|
+
"https://liveblocks.io/api/public/authorize",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
else if (typeof clientOptions.authEndpoint === "string") {
|
|
111
|
+
return {
|
|
112
|
+
type: "private",
|
|
113
|
+
url: clientOptions.authEndpoint,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
else if (typeof clientOptions.authEndpoint === "function") {
|
|
117
|
+
return {
|
|
118
|
+
type: "custom",
|
|
119
|
+
callback: clientOptions.authEndpoint,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
throw new Error("Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient");
|
|
123
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveObject } from "./LiveObject";
|
|
3
|
+
import { StorageUpdate } from "./types";
|
|
4
|
+
export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
|
|
5
|
+
export declare function liveNodeToJson(value: any): any;
|
|
6
|
+
export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
|
|
7
|
+
export declare function patchLiveObjectKey<T>(liveObject: LiveObject<T>, key: keyof T, prev: any, next: any): void;
|
|
8
|
+
export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
|
|
9
|
+
export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.patchImmutableObject = exports.patchLiveObject = exports.patchLiveObjectKey = exports.patchLiveList = exports.liveNodeToJson = exports.liveObjectToJson = void 0;
|
|
4
|
+
const LiveList_1 = require("./LiveList");
|
|
5
|
+
const LiveMap_1 = require("./LiveMap");
|
|
6
|
+
const LiveObject_1 = require("./LiveObject");
|
|
7
|
+
const LiveRegister_1 = require("./LiveRegister");
|
|
8
|
+
function liveObjectToJson(liveObject) {
|
|
9
|
+
const result = {};
|
|
10
|
+
const obj = liveObject.toObject();
|
|
11
|
+
for (const key in obj) {
|
|
12
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
exports.liveObjectToJson = liveObjectToJson;
|
|
17
|
+
function liveMapToJson(map) {
|
|
18
|
+
const result = {};
|
|
19
|
+
const obj = Object.fromEntries(map);
|
|
20
|
+
for (const key in obj) {
|
|
21
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
function liveListToJson(value) {
|
|
26
|
+
return value.toArray().map(liveNodeToJson);
|
|
27
|
+
}
|
|
28
|
+
function liveNodeToJson(value) {
|
|
29
|
+
if (value instanceof LiveObject_1.LiveObject) {
|
|
30
|
+
return liveObjectToJson(value);
|
|
31
|
+
}
|
|
32
|
+
else if (value instanceof LiveList_1.LiveList) {
|
|
33
|
+
return liveListToJson(value);
|
|
34
|
+
}
|
|
35
|
+
else if (value instanceof LiveMap_1.LiveMap) {
|
|
36
|
+
return liveMapToJson(value);
|
|
37
|
+
}
|
|
38
|
+
else if (value instanceof LiveRegister_1.LiveRegister) {
|
|
39
|
+
return value.data;
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
exports.liveNodeToJson = liveNodeToJson;
|
|
44
|
+
function isPlainObject(obj) {
|
|
45
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
46
|
+
}
|
|
47
|
+
function anyToCrdt(obj) {
|
|
48
|
+
if (obj == null) {
|
|
49
|
+
return obj;
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(obj)) {
|
|
52
|
+
return new LiveList_1.LiveList(obj.map(anyToCrdt));
|
|
53
|
+
}
|
|
54
|
+
if (isPlainObject(obj)) {
|
|
55
|
+
const init = {};
|
|
56
|
+
for (const key in obj) {
|
|
57
|
+
init[key] = anyToCrdt(obj[key]);
|
|
58
|
+
}
|
|
59
|
+
return new LiveObject_1.LiveObject(init);
|
|
60
|
+
}
|
|
61
|
+
return obj;
|
|
62
|
+
}
|
|
63
|
+
function patchLiveList(liveList, prev, next) {
|
|
64
|
+
let i = 0;
|
|
65
|
+
let prevEnd = prev.length - 1;
|
|
66
|
+
let nextEnd = next.length - 1;
|
|
67
|
+
let prevNode = prev[0];
|
|
68
|
+
let nextNode = next[0];
|
|
69
|
+
/**
|
|
70
|
+
* For A,B,C => A,B,C,D
|
|
71
|
+
* i = 3, prevEnd = 2, nextEnd = 3
|
|
72
|
+
*
|
|
73
|
+
* For A,B,C => B,C
|
|
74
|
+
* i = 2, prevEnd = 2, nextEnd = 1
|
|
75
|
+
*
|
|
76
|
+
* For B,C => A,B,C
|
|
77
|
+
* i = 0, pre
|
|
78
|
+
*/
|
|
79
|
+
outer: {
|
|
80
|
+
while (prevNode === nextNode) {
|
|
81
|
+
++i;
|
|
82
|
+
if (i > prevEnd || i > nextEnd) {
|
|
83
|
+
break outer;
|
|
84
|
+
}
|
|
85
|
+
prevNode = prev[i];
|
|
86
|
+
nextNode = next[i];
|
|
87
|
+
}
|
|
88
|
+
prevNode = prev[prevEnd];
|
|
89
|
+
nextNode = next[nextEnd];
|
|
90
|
+
while (prevNode === nextNode) {
|
|
91
|
+
prevEnd--;
|
|
92
|
+
nextEnd--;
|
|
93
|
+
if (i > prevEnd || i > nextEnd) {
|
|
94
|
+
break outer;
|
|
95
|
+
}
|
|
96
|
+
prevNode = prev[prevEnd];
|
|
97
|
+
nextNode = next[nextEnd];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (i > prevEnd) {
|
|
101
|
+
if (i <= nextEnd) {
|
|
102
|
+
while (i <= nextEnd) {
|
|
103
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (i > nextEnd) {
|
|
109
|
+
let localI = i;
|
|
110
|
+
while (localI <= prevEnd) {
|
|
111
|
+
liveList.delete(i);
|
|
112
|
+
localI++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
while (i <= prevEnd && i <= nextEnd) {
|
|
117
|
+
prevNode = prev[i];
|
|
118
|
+
nextNode = next[i];
|
|
119
|
+
const liveListNode = liveList.get(i);
|
|
120
|
+
if (liveListNode instanceof LiveObject_1.LiveObject &&
|
|
121
|
+
isPlainObject(prevNode) &&
|
|
122
|
+
isPlainObject(nextNode)) {
|
|
123
|
+
patchLiveObject(liveListNode, prevNode, nextNode);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
liveList.delete(i);
|
|
127
|
+
liveList.insert(anyToCrdt(nextNode), i);
|
|
128
|
+
}
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
while (i <= nextEnd) {
|
|
132
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
while (i <= prevEnd) {
|
|
136
|
+
liveList.delete(i);
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.patchLiveList = patchLiveList;
|
|
142
|
+
function patchLiveObjectKey(liveObject, key, prev, next) {
|
|
143
|
+
const value = liveObject.get(key);
|
|
144
|
+
if (next === undefined) {
|
|
145
|
+
liveObject.delete(key);
|
|
146
|
+
}
|
|
147
|
+
else if (value === undefined) {
|
|
148
|
+
liveObject.set(key, anyToCrdt(next));
|
|
149
|
+
}
|
|
150
|
+
else if (prev === next) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
else if (value instanceof LiveList_1.LiveList &&
|
|
154
|
+
Array.isArray(prev) &&
|
|
155
|
+
Array.isArray(next)) {
|
|
156
|
+
patchLiveList(value, prev, next);
|
|
157
|
+
}
|
|
158
|
+
else if (value instanceof LiveObject_1.LiveObject &&
|
|
159
|
+
isPlainObject(prev) &&
|
|
160
|
+
isPlainObject(next)) {
|
|
161
|
+
patchLiveObject(value, prev, next);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
liveObject.set(key, anyToCrdt(next));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.patchLiveObjectKey = patchLiveObjectKey;
|
|
168
|
+
function patchLiveObject(root, prev, next) {
|
|
169
|
+
const updates = {};
|
|
170
|
+
for (const key in next) {
|
|
171
|
+
patchLiveObjectKey(root, key, prev[key], next[key]);
|
|
172
|
+
}
|
|
173
|
+
for (const key in prev) {
|
|
174
|
+
if (next[key] === undefined) {
|
|
175
|
+
root.delete(key);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (Object.keys(updates).length > 0) {
|
|
179
|
+
root.update(updates);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.patchLiveObject = patchLiveObject;
|
|
183
|
+
function getParentsPath(node) {
|
|
184
|
+
const path = [];
|
|
185
|
+
while (node._parentKey != null && node._parent != null) {
|
|
186
|
+
if (node._parent instanceof LiveList_1.LiveList) {
|
|
187
|
+
path.push(node._parent._indexOfPosition(node._parentKey));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
path.push(node._parentKey);
|
|
191
|
+
}
|
|
192
|
+
node = node._parent;
|
|
193
|
+
}
|
|
194
|
+
return path;
|
|
195
|
+
}
|
|
196
|
+
function patchImmutableObject(state, updates) {
|
|
197
|
+
return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
|
|
198
|
+
}
|
|
199
|
+
exports.patchImmutableObject = patchImmutableObject;
|
|
200
|
+
function patchImmutableObjectWithUpdate(state, update) {
|
|
201
|
+
const path = getParentsPath(update.node);
|
|
202
|
+
return patchImmutableNode(state, path, update);
|
|
203
|
+
}
|
|
204
|
+
function patchImmutableNode(state, path, update) {
|
|
205
|
+
var _a, _b, _c, _d;
|
|
206
|
+
const pathItem = path.pop();
|
|
207
|
+
if (pathItem === undefined) {
|
|
208
|
+
switch (update.type) {
|
|
209
|
+
case "LiveObject": {
|
|
210
|
+
if (typeof state !== "object") {
|
|
211
|
+
throw new Error("Internal: received update on LiveObject but state was not an object");
|
|
212
|
+
}
|
|
213
|
+
let newState = Object.assign({}, state);
|
|
214
|
+
for (const key in update.updates) {
|
|
215
|
+
if (((_a = update.updates[key]) === null || _a === void 0 ? void 0 : _a.type) === "update") {
|
|
216
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
217
|
+
}
|
|
218
|
+
else if (((_b = update.updates[key]) === null || _b === void 0 ? void 0 : _b.type) === "delete") {
|
|
219
|
+
delete newState[key];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return newState;
|
|
223
|
+
}
|
|
224
|
+
case "LiveList": {
|
|
225
|
+
if (Array.isArray(state) === false) {
|
|
226
|
+
throw new Error("Internal: received update on LiveList but state was not an array");
|
|
227
|
+
}
|
|
228
|
+
let newState = state.map((x) => x);
|
|
229
|
+
for (const listUpdate of update.updates) {
|
|
230
|
+
if (listUpdate.type === "insert") {
|
|
231
|
+
if (listUpdate.index === newState.length) {
|
|
232
|
+
newState.push(liveNodeToJson(listUpdate.item));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
newState = [
|
|
236
|
+
...newState.slice(0, listUpdate.index),
|
|
237
|
+
liveNodeToJson(listUpdate.item),
|
|
238
|
+
...newState.slice(listUpdate.index),
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (listUpdate.type === "delete") {
|
|
243
|
+
newState.splice(listUpdate.index, 1);
|
|
244
|
+
}
|
|
245
|
+
else if (listUpdate.type === "move") {
|
|
246
|
+
if (listUpdate.previousIndex > listUpdate.index) {
|
|
247
|
+
newState = [
|
|
248
|
+
...newState.slice(0, listUpdate.index),
|
|
249
|
+
liveNodeToJson(listUpdate.item),
|
|
250
|
+
...newState.slice(listUpdate.index, listUpdate.previousIndex),
|
|
251
|
+
...newState.slice(listUpdate.previousIndex + 1),
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
newState = [
|
|
256
|
+
...newState.slice(0, listUpdate.previousIndex),
|
|
257
|
+
...newState.slice(listUpdate.previousIndex + 1, listUpdate.index + 1),
|
|
258
|
+
liveNodeToJson(listUpdate.item),
|
|
259
|
+
...newState.slice(listUpdate.index + 1),
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return newState;
|
|
265
|
+
}
|
|
266
|
+
case "LiveMap": {
|
|
267
|
+
if (typeof state !== "object") {
|
|
268
|
+
throw new Error("Internal: received update on LiveMap but state was not an object");
|
|
269
|
+
}
|
|
270
|
+
let newState = Object.assign({}, state);
|
|
271
|
+
for (const key in update.updates) {
|
|
272
|
+
if (((_c = update.updates[key]) === null || _c === void 0 ? void 0 : _c.type) === "update") {
|
|
273
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
274
|
+
}
|
|
275
|
+
else if (((_d = update.updates[key]) === null || _d === void 0 ? void 0 : _d.type) === "delete") {
|
|
276
|
+
delete newState[key];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return newState;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (Array.isArray(state)) {
|
|
284
|
+
const newArray = [...state];
|
|
285
|
+
newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
|
|
286
|
+
return newArray;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
|
|
290
|
+
}
|
|
291
|
+
}
|
package/lib/cjs/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/cjs/room.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
-
}) : (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
o[k2] = m[k];
|
|
8
|
-
}));
|
|
9
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
-
}) : function(o, v) {
|
|
12
|
-
o["default"] = v;
|
|
13
|
-
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
-
if (mod && mod.__esModule) return mod;
|
|
16
|
-
var result = {};
|
|
17
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
-
__setModuleDefault(result, mod);
|
|
19
|
-
return result;
|
|
20
|
-
};
|
|
21
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
22
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
23
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -30,7 +11,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
30
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
12
|
exports.createRoom = exports.defaultState = exports.makeStateMachine = void 0;
|
|
32
13
|
const utils_1 = require("./utils");
|
|
33
|
-
const authentication_1 = __importStar(require("./authentication"));
|
|
34
14
|
const live_1 = require("./live");
|
|
35
15
|
const LiveMap_1 = require("./LiveMap");
|
|
36
16
|
const LiveObject_1 = require("./LiveObject");
|
|
@@ -75,16 +55,12 @@ function log(...params) {
|
|
|
75
55
|
}
|
|
76
56
|
function makeStateMachine(state, context, mockedEffects) {
|
|
77
57
|
const effects = mockedEffects || {
|
|
78
|
-
authenticate() {
|
|
58
|
+
authenticate(auth, createWebSocket) {
|
|
79
59
|
return __awaiter(this, void 0, void 0, function* () {
|
|
80
60
|
try {
|
|
81
|
-
const token = yield (
|
|
82
|
-
const parsedToken =
|
|
83
|
-
const socket =
|
|
84
|
-
socket.addEventListener("message", onMessage);
|
|
85
|
-
socket.addEventListener("open", onOpen);
|
|
86
|
-
socket.addEventListener("close", onClose);
|
|
87
|
-
socket.addEventListener("error", onError);
|
|
61
|
+
const { token } = yield auth(context.room);
|
|
62
|
+
const parsedToken = parseToken(token);
|
|
63
|
+
const socket = createWebSocket(token);
|
|
88
64
|
authenticationSuccess(parsedToken, socket);
|
|
89
65
|
}
|
|
90
66
|
catch (er) {
|
|
@@ -433,15 +409,14 @@ See v0.13 release notes for more information.
|
|
|
433
409
|
: null;
|
|
434
410
|
}
|
|
435
411
|
function connect() {
|
|
436
|
-
if (typeof window === "undefined") {
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
412
|
if (state.connection.state !== "closed" &&
|
|
440
413
|
state.connection.state !== "unavailable") {
|
|
441
414
|
return null;
|
|
442
415
|
}
|
|
416
|
+
const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
|
|
417
|
+
const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
|
|
443
418
|
updateConnection({ state: "authenticating" });
|
|
444
|
-
effects.authenticate();
|
|
419
|
+
effects.authenticate(auth, createWebSocket);
|
|
445
420
|
}
|
|
446
421
|
function updatePresence(overrides, options) {
|
|
447
422
|
const oldValues = {};
|
|
@@ -468,6 +443,10 @@ See v0.13 release notes for more information.
|
|
|
468
443
|
}
|
|
469
444
|
}
|
|
470
445
|
function authenticationSuccess(token, socket) {
|
|
446
|
+
socket.addEventListener("message", onMessage);
|
|
447
|
+
socket.addEventListener("open", onOpen);
|
|
448
|
+
socket.addEventListener("close", onClose);
|
|
449
|
+
socket.addEventListener("error", onError);
|
|
471
450
|
updateConnection({
|
|
472
451
|
state: "connecting",
|
|
473
452
|
id: token.actor,
|
|
@@ -701,7 +680,7 @@ See v0.13 release notes for more information.
|
|
|
701
680
|
}
|
|
702
681
|
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
703
682
|
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
|
|
704
|
-
if (state.socket.readyState ===
|
|
683
|
+
if (state.socket.readyState === state.socket.OPEN) {
|
|
705
684
|
state.socket.send("ping");
|
|
706
685
|
}
|
|
707
686
|
}
|
|
@@ -748,7 +727,7 @@ See v0.13 release notes for more information.
|
|
|
748
727
|
state.offlineOperations.set(op.opId, op);
|
|
749
728
|
});
|
|
750
729
|
}
|
|
751
|
-
if (state.socket == null || state.socket.readyState !==
|
|
730
|
+
if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
|
|
752
731
|
state.buffer.storageOperations = [];
|
|
753
732
|
return;
|
|
754
733
|
}
|
|
@@ -952,7 +931,6 @@ See v0.13 release notes for more information.
|
|
|
952
931
|
}
|
|
953
932
|
return {
|
|
954
933
|
// Internal
|
|
955
|
-
onOpen,
|
|
956
934
|
onClose,
|
|
957
935
|
onMessage,
|
|
958
936
|
authenticationSuccess,
|
|
@@ -1042,26 +1020,9 @@ function defaultState(me, defaultStorageRoot) {
|
|
|
1042
1020
|
};
|
|
1043
1021
|
}
|
|
1044
1022
|
exports.defaultState = defaultState;
|
|
1045
|
-
function createRoom(
|
|
1046
|
-
const throttleDelay = options.throttle || 100;
|
|
1047
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
|
|
1048
|
-
let authEndpoint;
|
|
1049
|
-
if (options.authEndpoint) {
|
|
1050
|
-
authEndpoint = options.authEndpoint;
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
|
|
1054
|
-
"https://liveblocks.io/api/public/authorize";
|
|
1055
|
-
authEndpoint = publicAuthorizeEndpoint;
|
|
1056
|
-
}
|
|
1023
|
+
function createRoom(options, context) {
|
|
1057
1024
|
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
|
|
1058
|
-
const machine = makeStateMachine(state,
|
|
1059
|
-
throttleDelay,
|
|
1060
|
-
liveblocksServer,
|
|
1061
|
-
authEndpoint,
|
|
1062
|
-
room: name,
|
|
1063
|
-
publicApiKey: options.publicApiKey,
|
|
1064
|
-
});
|
|
1025
|
+
const machine = makeStateMachine(state, context);
|
|
1065
1026
|
const room = {
|
|
1066
1027
|
/////////////
|
|
1067
1028
|
// Core //
|
|
@@ -1106,3 +1067,76 @@ class LiveblocksError extends Error {
|
|
|
1106
1067
|
this.code = code;
|
|
1107
1068
|
}
|
|
1108
1069
|
}
|
|
1070
|
+
function parseToken(token) {
|
|
1071
|
+
const tokenParts = token.split(".");
|
|
1072
|
+
if (tokenParts.length !== 3) {
|
|
1073
|
+
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1074
|
+
}
|
|
1075
|
+
const data = JSON.parse(atob(tokenParts[1]));
|
|
1076
|
+
if (typeof data.actor !== "number") {
|
|
1077
|
+
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1078
|
+
}
|
|
1079
|
+
return data;
|
|
1080
|
+
}
|
|
1081
|
+
function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
|
|
1082
|
+
if (typeof window === "undefined" && WebSocketPolyfill == null) {
|
|
1083
|
+
throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
|
|
1084
|
+
}
|
|
1085
|
+
const ws = WebSocketPolyfill || WebSocket;
|
|
1086
|
+
return (token) => {
|
|
1087
|
+
return new ws(`${liveblocksServer}/?token=${token}`);
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function prepareAuthEndpoint(authentication, fetchPolyfill) {
|
|
1091
|
+
if (authentication.type === "public") {
|
|
1092
|
+
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1093
|
+
throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
|
|
1094
|
+
}
|
|
1095
|
+
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1096
|
+
room,
|
|
1097
|
+
publicApiKey: authentication.publicApiKey,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
if (authentication.type === "private") {
|
|
1101
|
+
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1102
|
+
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.");
|
|
1103
|
+
}
|
|
1104
|
+
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1105
|
+
room,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
if (authentication.type === "custom") {
|
|
1109
|
+
return authentication.callback;
|
|
1110
|
+
}
|
|
1111
|
+
throw new Error("Internal error. Unexpected authentication type");
|
|
1112
|
+
}
|
|
1113
|
+
function fetchAuthEndpoint(fetch, endpoint, body) {
|
|
1114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1115
|
+
const res = yield fetch(endpoint, {
|
|
1116
|
+
method: "POST",
|
|
1117
|
+
headers: {
|
|
1118
|
+
"Content-Type": "application/json",
|
|
1119
|
+
},
|
|
1120
|
+
body: JSON.stringify(body),
|
|
1121
|
+
});
|
|
1122
|
+
if (!res.ok) {
|
|
1123
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1124
|
+
}
|
|
1125
|
+
let authResponse = null;
|
|
1126
|
+
try {
|
|
1127
|
+
authResponse = yield res.json();
|
|
1128
|
+
}
|
|
1129
|
+
catch (er) {
|
|
1130
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1131
|
+
}
|
|
1132
|
+
if (typeof authResponse.token !== "string") {
|
|
1133
|
+
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1134
|
+
}
|
|
1135
|
+
return authResponse;
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
class AuthenticationError extends Error {
|
|
1139
|
+
constructor(message) {
|
|
1140
|
+
super(message);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
package/lib/cjs/types.d.ts
CHANGED
|
@@ -121,6 +121,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
|
|
|
121
121
|
*/
|
|
122
122
|
export declare type ClientOptions = {
|
|
123
123
|
throttle?: number;
|
|
124
|
+
fetchPolyfill?: any;
|
|
125
|
+
WebSocketPolyfill?: any;
|
|
124
126
|
} & ({
|
|
125
127
|
publicApiKey: string;
|
|
126
128
|
authEndpoint?: never;
|
|
@@ -131,6 +133,17 @@ export declare type ClientOptions = {
|
|
|
131
133
|
export declare type AuthorizeResponse = {
|
|
132
134
|
token: string;
|
|
133
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
|
+
};
|
|
134
147
|
declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
|
|
135
148
|
export declare type Connection = {
|
|
136
149
|
state: "closed" | "authenticating" | "unavailable" | "failed";
|
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
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveObject } from "./LiveObject";
|
|
3
|
+
import { StorageUpdate } from "./types";
|
|
4
|
+
export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
|
|
5
|
+
export declare function liveNodeToJson(value: any): any;
|
|
6
|
+
export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
|
|
7
|
+
export declare function patchLiveObjectKey<T>(liveObject: LiveObject<T>, key: keyof T, prev: any, next: any): void;
|
|
8
|
+
export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
|
|
9
|
+
export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveMap } from "./LiveMap";
|
|
3
|
+
import { LiveObject } from "./LiveObject";
|
|
4
|
+
import { LiveRegister } from "./LiveRegister";
|
|
5
|
+
export function liveObjectToJson(liveObject) {
|
|
6
|
+
const result = {};
|
|
7
|
+
const obj = liveObject.toObject();
|
|
8
|
+
for (const key in obj) {
|
|
9
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
function liveMapToJson(map) {
|
|
14
|
+
const result = {};
|
|
15
|
+
const obj = Object.fromEntries(map);
|
|
16
|
+
for (const key in obj) {
|
|
17
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
function liveListToJson(value) {
|
|
22
|
+
return value.toArray().map(liveNodeToJson);
|
|
23
|
+
}
|
|
24
|
+
export function liveNodeToJson(value) {
|
|
25
|
+
if (value instanceof LiveObject) {
|
|
26
|
+
return liveObjectToJson(value);
|
|
27
|
+
}
|
|
28
|
+
else if (value instanceof LiveList) {
|
|
29
|
+
return liveListToJson(value);
|
|
30
|
+
}
|
|
31
|
+
else if (value instanceof LiveMap) {
|
|
32
|
+
return liveMapToJson(value);
|
|
33
|
+
}
|
|
34
|
+
else if (value instanceof LiveRegister) {
|
|
35
|
+
return value.data;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function isPlainObject(obj) {
|
|
40
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
41
|
+
}
|
|
42
|
+
function anyToCrdt(obj) {
|
|
43
|
+
if (obj == null) {
|
|
44
|
+
return obj;
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(obj)) {
|
|
47
|
+
return new LiveList(obj.map(anyToCrdt));
|
|
48
|
+
}
|
|
49
|
+
if (isPlainObject(obj)) {
|
|
50
|
+
const init = {};
|
|
51
|
+
for (const key in obj) {
|
|
52
|
+
init[key] = anyToCrdt(obj[key]);
|
|
53
|
+
}
|
|
54
|
+
return new LiveObject(init);
|
|
55
|
+
}
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
58
|
+
export function patchLiveList(liveList, prev, next) {
|
|
59
|
+
let i = 0;
|
|
60
|
+
let prevEnd = prev.length - 1;
|
|
61
|
+
let nextEnd = next.length - 1;
|
|
62
|
+
let prevNode = prev[0];
|
|
63
|
+
let nextNode = next[0];
|
|
64
|
+
/**
|
|
65
|
+
* For A,B,C => A,B,C,D
|
|
66
|
+
* i = 3, prevEnd = 2, nextEnd = 3
|
|
67
|
+
*
|
|
68
|
+
* For A,B,C => B,C
|
|
69
|
+
* i = 2, prevEnd = 2, nextEnd = 1
|
|
70
|
+
*
|
|
71
|
+
* For B,C => A,B,C
|
|
72
|
+
* i = 0, pre
|
|
73
|
+
*/
|
|
74
|
+
outer: {
|
|
75
|
+
while (prevNode === nextNode) {
|
|
76
|
+
++i;
|
|
77
|
+
if (i > prevEnd || i > nextEnd) {
|
|
78
|
+
break outer;
|
|
79
|
+
}
|
|
80
|
+
prevNode = prev[i];
|
|
81
|
+
nextNode = next[i];
|
|
82
|
+
}
|
|
83
|
+
prevNode = prev[prevEnd];
|
|
84
|
+
nextNode = next[nextEnd];
|
|
85
|
+
while (prevNode === nextNode) {
|
|
86
|
+
prevEnd--;
|
|
87
|
+
nextEnd--;
|
|
88
|
+
if (i > prevEnd || i > nextEnd) {
|
|
89
|
+
break outer;
|
|
90
|
+
}
|
|
91
|
+
prevNode = prev[prevEnd];
|
|
92
|
+
nextNode = next[nextEnd];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (i > prevEnd) {
|
|
96
|
+
if (i <= nextEnd) {
|
|
97
|
+
while (i <= nextEnd) {
|
|
98
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (i > nextEnd) {
|
|
104
|
+
let localI = i;
|
|
105
|
+
while (localI <= prevEnd) {
|
|
106
|
+
liveList.delete(i);
|
|
107
|
+
localI++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
while (i <= prevEnd && i <= nextEnd) {
|
|
112
|
+
prevNode = prev[i];
|
|
113
|
+
nextNode = next[i];
|
|
114
|
+
const liveListNode = liveList.get(i);
|
|
115
|
+
if (liveListNode instanceof LiveObject &&
|
|
116
|
+
isPlainObject(prevNode) &&
|
|
117
|
+
isPlainObject(nextNode)) {
|
|
118
|
+
patchLiveObject(liveListNode, prevNode, nextNode);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
liveList.delete(i);
|
|
122
|
+
liveList.insert(anyToCrdt(nextNode), i);
|
|
123
|
+
}
|
|
124
|
+
i++;
|
|
125
|
+
}
|
|
126
|
+
while (i <= nextEnd) {
|
|
127
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
128
|
+
i++;
|
|
129
|
+
}
|
|
130
|
+
while (i <= prevEnd) {
|
|
131
|
+
liveList.delete(i);
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function patchLiveObjectKey(liveObject, key, prev, next) {
|
|
137
|
+
const value = liveObject.get(key);
|
|
138
|
+
if (next === undefined) {
|
|
139
|
+
liveObject.delete(key);
|
|
140
|
+
}
|
|
141
|
+
else if (value === undefined) {
|
|
142
|
+
liveObject.set(key, anyToCrdt(next));
|
|
143
|
+
}
|
|
144
|
+
else if (prev === next) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
else if (value instanceof LiveList &&
|
|
148
|
+
Array.isArray(prev) &&
|
|
149
|
+
Array.isArray(next)) {
|
|
150
|
+
patchLiveList(value, prev, next);
|
|
151
|
+
}
|
|
152
|
+
else if (value instanceof LiveObject &&
|
|
153
|
+
isPlainObject(prev) &&
|
|
154
|
+
isPlainObject(next)) {
|
|
155
|
+
patchLiveObject(value, prev, next);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
liveObject.set(key, anyToCrdt(next));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function patchLiveObject(root, prev, next) {
|
|
162
|
+
const updates = {};
|
|
163
|
+
for (const key in next) {
|
|
164
|
+
patchLiveObjectKey(root, key, prev[key], next[key]);
|
|
165
|
+
}
|
|
166
|
+
for (const key in prev) {
|
|
167
|
+
if (next[key] === undefined) {
|
|
168
|
+
root.delete(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (Object.keys(updates).length > 0) {
|
|
172
|
+
root.update(updates);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function getParentsPath(node) {
|
|
176
|
+
const path = [];
|
|
177
|
+
while (node._parentKey != null && node._parent != null) {
|
|
178
|
+
if (node._parent instanceof LiveList) {
|
|
179
|
+
path.push(node._parent._indexOfPosition(node._parentKey));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
path.push(node._parentKey);
|
|
183
|
+
}
|
|
184
|
+
node = node._parent;
|
|
185
|
+
}
|
|
186
|
+
return path;
|
|
187
|
+
}
|
|
188
|
+
export function patchImmutableObject(state, updates) {
|
|
189
|
+
return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
|
|
190
|
+
}
|
|
191
|
+
function patchImmutableObjectWithUpdate(state, update) {
|
|
192
|
+
const path = getParentsPath(update.node);
|
|
193
|
+
return patchImmutableNode(state, path, update);
|
|
194
|
+
}
|
|
195
|
+
function patchImmutableNode(state, path, update) {
|
|
196
|
+
var _a, _b, _c, _d;
|
|
197
|
+
const pathItem = path.pop();
|
|
198
|
+
if (pathItem === undefined) {
|
|
199
|
+
switch (update.type) {
|
|
200
|
+
case "LiveObject": {
|
|
201
|
+
if (typeof state !== "object") {
|
|
202
|
+
throw new Error("Internal: received update on LiveObject but state was not an object");
|
|
203
|
+
}
|
|
204
|
+
let newState = Object.assign({}, state);
|
|
205
|
+
for (const key in update.updates) {
|
|
206
|
+
if (((_a = update.updates[key]) === null || _a === void 0 ? void 0 : _a.type) === "update") {
|
|
207
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
208
|
+
}
|
|
209
|
+
else if (((_b = update.updates[key]) === null || _b === void 0 ? void 0 : _b.type) === "delete") {
|
|
210
|
+
delete newState[key];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return newState;
|
|
214
|
+
}
|
|
215
|
+
case "LiveList": {
|
|
216
|
+
if (Array.isArray(state) === false) {
|
|
217
|
+
throw new Error("Internal: received update on LiveList but state was not an array");
|
|
218
|
+
}
|
|
219
|
+
let newState = state.map((x) => x);
|
|
220
|
+
for (const listUpdate of update.updates) {
|
|
221
|
+
if (listUpdate.type === "insert") {
|
|
222
|
+
if (listUpdate.index === newState.length) {
|
|
223
|
+
newState.push(liveNodeToJson(listUpdate.item));
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
newState = [
|
|
227
|
+
...newState.slice(0, listUpdate.index),
|
|
228
|
+
liveNodeToJson(listUpdate.item),
|
|
229
|
+
...newState.slice(listUpdate.index),
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (listUpdate.type === "delete") {
|
|
234
|
+
newState.splice(listUpdate.index, 1);
|
|
235
|
+
}
|
|
236
|
+
else if (listUpdate.type === "move") {
|
|
237
|
+
if (listUpdate.previousIndex > listUpdate.index) {
|
|
238
|
+
newState = [
|
|
239
|
+
...newState.slice(0, listUpdate.index),
|
|
240
|
+
liveNodeToJson(listUpdate.item),
|
|
241
|
+
...newState.slice(listUpdate.index, listUpdate.previousIndex),
|
|
242
|
+
...newState.slice(listUpdate.previousIndex + 1),
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
newState = [
|
|
247
|
+
...newState.slice(0, listUpdate.previousIndex),
|
|
248
|
+
...newState.slice(listUpdate.previousIndex + 1, listUpdate.index + 1),
|
|
249
|
+
liveNodeToJson(listUpdate.item),
|
|
250
|
+
...newState.slice(listUpdate.index + 1),
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return newState;
|
|
256
|
+
}
|
|
257
|
+
case "LiveMap": {
|
|
258
|
+
if (typeof state !== "object") {
|
|
259
|
+
throw new Error("Internal: received update on LiveMap but state was not an object");
|
|
260
|
+
}
|
|
261
|
+
let newState = Object.assign({}, state);
|
|
262
|
+
for (const key in update.updates) {
|
|
263
|
+
if (((_c = update.updates[key]) === null || _c === void 0 ? void 0 : _c.type) === "update") {
|
|
264
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
265
|
+
}
|
|
266
|
+
else if (((_d = update.updates[key]) === null || _d === void 0 ? void 0 : _d.type) === "delete") {
|
|
267
|
+
delete newState[key];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return newState;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(state)) {
|
|
275
|
+
const newArray = [...state];
|
|
276
|
+
newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
|
|
277
|
+
return newArray;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
|
|
281
|
+
}
|
|
282
|
+
}
|
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 } 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) {
|
|
@@ -411,15 +406,14 @@ See v0.13 release notes for more information.
|
|
|
411
406
|
: null;
|
|
412
407
|
}
|
|
413
408
|
function connect() {
|
|
414
|
-
if (typeof window === "undefined") {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
409
|
if (state.connection.state !== "closed" &&
|
|
418
410
|
state.connection.state !== "unavailable") {
|
|
419
411
|
return null;
|
|
420
412
|
}
|
|
413
|
+
const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
|
|
414
|
+
const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
|
|
421
415
|
updateConnection({ state: "authenticating" });
|
|
422
|
-
effects.authenticate();
|
|
416
|
+
effects.authenticate(auth, createWebSocket);
|
|
423
417
|
}
|
|
424
418
|
function updatePresence(overrides, options) {
|
|
425
419
|
const oldValues = {};
|
|
@@ -446,6 +440,10 @@ See v0.13 release notes for more information.
|
|
|
446
440
|
}
|
|
447
441
|
}
|
|
448
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);
|
|
449
447
|
updateConnection({
|
|
450
448
|
state: "connecting",
|
|
451
449
|
id: token.actor,
|
|
@@ -679,7 +677,7 @@ See v0.13 release notes for more information.
|
|
|
679
677
|
}
|
|
680
678
|
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
681
679
|
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
|
|
682
|
-
if (state.socket.readyState ===
|
|
680
|
+
if (state.socket.readyState === state.socket.OPEN) {
|
|
683
681
|
state.socket.send("ping");
|
|
684
682
|
}
|
|
685
683
|
}
|
|
@@ -726,7 +724,7 @@ See v0.13 release notes for more information.
|
|
|
726
724
|
state.offlineOperations.set(op.opId, op);
|
|
727
725
|
});
|
|
728
726
|
}
|
|
729
|
-
if (state.socket == null || state.socket.readyState !==
|
|
727
|
+
if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
|
|
730
728
|
state.buffer.storageOperations = [];
|
|
731
729
|
return;
|
|
732
730
|
}
|
|
@@ -930,7 +928,6 @@ See v0.13 release notes for more information.
|
|
|
930
928
|
}
|
|
931
929
|
return {
|
|
932
930
|
// Internal
|
|
933
|
-
onOpen,
|
|
934
931
|
onClose,
|
|
935
932
|
onMessage,
|
|
936
933
|
authenticationSuccess,
|
|
@@ -1018,26 +1015,9 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
1018
1015
|
offlineOperations: new Map(),
|
|
1019
1016
|
};
|
|
1020
1017
|
}
|
|
1021
|
-
export function createRoom(
|
|
1022
|
-
const throttleDelay = options.throttle || 100;
|
|
1023
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
|
|
1024
|
-
let authEndpoint;
|
|
1025
|
-
if (options.authEndpoint) {
|
|
1026
|
-
authEndpoint = options.authEndpoint;
|
|
1027
|
-
}
|
|
1028
|
-
else {
|
|
1029
|
-
const publicAuthorizeEndpoint = options.publicAuthorizeEndpoint ||
|
|
1030
|
-
"https://liveblocks.io/api/public/authorize";
|
|
1031
|
-
authEndpoint = publicAuthorizeEndpoint;
|
|
1032
|
-
}
|
|
1018
|
+
export function createRoom(options, context) {
|
|
1033
1019
|
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
|
|
1034
|
-
const machine = makeStateMachine(state,
|
|
1035
|
-
throttleDelay,
|
|
1036
|
-
liveblocksServer,
|
|
1037
|
-
authEndpoint,
|
|
1038
|
-
room: name,
|
|
1039
|
-
publicApiKey: options.publicApiKey,
|
|
1040
|
-
});
|
|
1020
|
+
const machine = makeStateMachine(state, context);
|
|
1041
1021
|
const room = {
|
|
1042
1022
|
/////////////
|
|
1043
1023
|
// Core //
|
|
@@ -1081,3 +1061,76 @@ class LiveblocksError extends Error {
|
|
|
1081
1061
|
this.code = code;
|
|
1082
1062
|
}
|
|
1083
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
|
@@ -121,6 +121,8 @@ export declare type AuthEndpoint = string | AuthEndpointCallback;
|
|
|
121
121
|
*/
|
|
122
122
|
export declare type ClientOptions = {
|
|
123
123
|
throttle?: number;
|
|
124
|
+
fetchPolyfill?: any;
|
|
125
|
+
WebSocketPolyfill?: any;
|
|
124
126
|
} & ({
|
|
125
127
|
publicApiKey: string;
|
|
126
128
|
authEndpoint?: never;
|
|
@@ -131,6 +133,17 @@ export declare type ClientOptions = {
|
|
|
131
133
|
export declare type AuthorizeResponse = {
|
|
132
134
|
token: string;
|
|
133
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
|
+
};
|
|
134
147
|
declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
|
|
135
148
|
export declare type Connection = {
|
|
136
149
|
state: "closed" | "authenticating" | "unavailable" | "failed";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liveblocks/client",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -29,13 +29,17 @@
|
|
|
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",
|
|
38
42
|
"url": "https://github.com/liveblocks/liveblocks.git",
|
|
39
43
|
"directory": "packages/liveblocks"
|
|
40
44
|
}
|
|
41
|
-
}
|
|
45
|
+
}
|