@liveblocks/client 0.16.4-beta2 → 0.16.6

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/index.mjs CHANGED
@@ -1,1641 +1,807 @@
1
- import { A as AbstractCrdt, r as remove, S as ServerMessageType, m as mergeStorageUpdates, W as WebsocketCloseCodes, C as ClientMessageType, i as isTokenValid, a as isSameNodeOrChildOf, L as LiveObject, g as getTreesDiffOperations, O as OpType, b as LiveList, p as parseJson, c as isJsonArray, d as compact, e as isJsonObject, f as LiveMap, h as LiveRegister, j as findNonSerializableValue } from './shared.mjs';
2
- export { b as LiveList, f as LiveMap, L as LiveObject } from './shared.mjs';
1
+ import { A as AbstractCrdt, r as remove, S as ServerMessageType, m as mergeStorageUpdates, W as WebsocketCloseCodes, C as ClientMessageType, i as isTokenValid, a as isSameNodeOrChildOf, L as LiveObject, g as getTreesDiffOperations, O as OpType, b as LiveList, p as parseJson, c as isJsonArray, d as compact, e as isJsonObject, f as deprecateIf } from "./shared.mjs";
3
2
 
4
- /*! *****************************************************************************
5
- Copyright (c) Microsoft Corporation.
6
-
7
- Permission to use, copy, modify, and/or distribute this software for any
8
- purpose with or without fee is hereby granted.
9
-
10
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
- PERFORMANCE OF THIS SOFTWARE.
17
- ***************************************************************************** */
18
-
19
- function __rest(s, e) {
20
- var t = {};
21
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
22
- t[p] = s[p];
23
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
24
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
25
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
26
- t[p[i]] = s[p[i]];
27
- }
28
- return t;
29
- }
3
+ export { b as LiveList, h as LiveMap, L as LiveObject } from "./shared.mjs";
4
+
5
+ const BACKOFF_RETRY_DELAYS = [ 250, 500, 1e3, 2e3, 4e3, 8e3, 1e4 ], BACKOFF_RETRY_DELAYS_SLOW = [ 2e3, 3e4, 6e4, 3e5 ];
30
6
 
31
- const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];
32
- const BACKOFF_RETRY_DELAYS_SLOW = [2000, 30000, 60000, 300000];
33
- const HEARTBEAT_INTERVAL = 30000;
34
- // const WAKE_UP_CHECK_INTERVAL = 2000;
35
- const PONG_TIMEOUT = 2000;
36
7
  function isValidRoomEventType(value) {
37
- return (value === "my-presence" ||
38
- value === "others" ||
39
- value === "event" ||
40
- value === "error" ||
41
- value === "connection");
42
- }
43
- function makeIdFactory(connectionId) {
44
- let count = 0;
45
- return () => `${connectionId}:${count++}`;
8
+ return "my-presence" === value || "others" === value || "event" === value || "error" === value || "connection" === value;
46
9
  }
10
+
47
11
  function makeOthers(userMap) {
48
- const users = Object.values(userMap).map((user) => {
49
- const publicKeys = __rest(user, ["_hasReceivedInitialPresence"]);
50
- return publicKeys;
51
- });
52
- return {
53
- get count() {
54
- return users.length;
55
- },
56
- [Symbol.iterator]() {
57
- return users[Symbol.iterator]();
58
- },
59
- map(callback) {
60
- return users.map(callback);
61
- },
62
- toArray() {
63
- return users;
64
- },
65
- };
12
+ const users = Object.values(userMap).map((user => function(s, e) {
13
+ var t = {};
14
+ for (var p in s) Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0 && (t[p] = s[p]);
15
+ if (null != s && "function" == typeof Object.getOwnPropertySymbols) {
16
+ var i = 0;
17
+ for (p = Object.getOwnPropertySymbols(s); i < p.length; i++) e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]) && (t[p[i]] = s[p[i]]);
18
+ }
19
+ return t;
20
+ }(user, [ "_hasReceivedInitialPresence" ])));
21
+ return {
22
+ get count() {
23
+ return users.length;
24
+ },
25
+ [Symbol.iterator]: () => users[Symbol.iterator](),
26
+ map: callback => users.map(callback),
27
+ toArray: () => users
28
+ };
66
29
  }
30
+
67
31
  function makeStateMachine(state, context, mockedEffects) {
68
- const effects = mockedEffects || {
69
- authenticate(auth, createWebSocket) {
70
- const token = state.token;
71
- if (token && isTokenValid(token)) {
72
- const parsedToken = parseToken(token);
73
- const socket = createWebSocket(token);
74
- authenticationSuccess(parsedToken, socket);
75
- }
76
- else {
77
- return auth(context.roomId)
78
- .then(({ token }) => {
79
- if (state.connection.state !== "authenticating") {
80
- return;
81
- }
82
- const parsedToken = parseToken(token);
83
- const socket = createWebSocket(token);
84
- authenticationSuccess(parsedToken, socket);
85
- state.token = token;
86
- })
87
- .catch((er) => authenticationFailure(er));
88
- }
89
- },
90
- send(messageOrMessages) {
91
- if (state.socket == null) {
92
- throw new Error("Can't send message if socket is null");
93
- }
94
- state.socket.send(JSON.stringify(messageOrMessages));
95
- },
96
- delayFlush(delay) {
97
- return setTimeout(tryFlushing, delay);
98
- },
99
- startHeartbeatInterval() {
100
- return setInterval(heartbeat, HEARTBEAT_INTERVAL);
101
- },
102
- schedulePongTimeout() {
103
- return setTimeout(pongTimeout, PONG_TIMEOUT);
104
- },
105
- scheduleReconnect(delay) {
106
- return setTimeout(connect, delay);
107
- },
32
+ const effects = mockedEffects || {
33
+ authenticate(auth, createWebSocket) {
34
+ const token = state.token;
35
+ if (!token || !isTokenValid(token)) return auth(context.roomId).then((({token: token}) => {
36
+ if ("authenticating" !== state.connection.state) return;
37
+ authenticationSuccess(parseToken(token), createWebSocket(token)), state.token = token;
38
+ })).catch((er => function(error) {
39
+ "production" !== process.env.NODE_ENV && console.error("Call to authentication endpoint failed", error);
40
+ state.token = null, updateConnection({
41
+ state: "unavailable"
42
+ }), state.numberOfRetry++, state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
43
+ }(er)));
44
+ authenticationSuccess(parseToken(token), createWebSocket(token));
45
+ },
46
+ send(messageOrMessages) {
47
+ if (null == state.socket) throw new Error("Can't send message if socket is null");
48
+ state.socket.send(JSON.stringify(messageOrMessages));
49
+ },
50
+ delayFlush: delay => setTimeout(tryFlushing, delay),
51
+ startHeartbeatInterval: () => setInterval(heartbeat, 3e4),
52
+ schedulePongTimeout: () => setTimeout(pongTimeout, 2e3),
53
+ scheduleReconnect: delay => setTimeout(connect, delay)
54
+ };
55
+ function genericSubscribe(callback) {
56
+ return state.listeners.storage.push(callback), () => remove(state.listeners.storage, callback);
57
+ }
58
+ function createOrUpdateRootFromMessage(message) {
59
+ if (0 === message.items.length) throw new Error("Internal error: cannot load storage without items");
60
+ state.root ? function(items) {
61
+ if (!state.root) return;
62
+ const currentItems = new Map;
63
+ state.items.forEach(((liveCrdt, id) => {
64
+ currentItems.set(id, liveCrdt._toSerializedCrdt());
65
+ }));
66
+ const ops = getTreesDiffOperations(currentItems, new Map(items));
67
+ notify(apply(ops, !1).updates);
68
+ }(message.items) : state.root = function(items) {
69
+ const [root, parentToChildren] = function(items) {
70
+ const parentToChildren = new Map;
71
+ let root = null;
72
+ for (const tuple of items) {
73
+ const parentId = tuple[1].parentId;
74
+ if (null == parentId) root = tuple; else {
75
+ const children = parentToChildren.get(parentId);
76
+ null != children ? children.push(tuple) : parentToChildren.set(parentId, [ tuple ]);
77
+ }
78
+ }
79
+ if (null == root) throw new Error("Root can't be null");
80
+ return [ root, parentToChildren ];
81
+ }(items);
82
+ return LiveObject._deserialize(root, parentToChildren, {
83
+ getItem: getItem,
84
+ addItem: addItem,
85
+ deleteItem: deleteItem,
86
+ generateId: generateId,
87
+ generateOpId: generateOpId,
88
+ dispatch: storageDispatch,
89
+ roomId: context.roomId
90
+ });
91
+ }(message.items);
92
+ for (const key in state.defaultStorageRoot) null == state.root.get(key) && state.root.set(key, state.defaultStorageRoot[key]);
93
+ }
94
+ function addItem(id, item) {
95
+ state.items.set(id, item);
96
+ }
97
+ function deleteItem(id) {
98
+ state.items.delete(id);
99
+ }
100
+ function getItem(id) {
101
+ return state.items.get(id);
102
+ }
103
+ function addToUndoStack(historyItem) {
104
+ state.undoStack.length >= 50 && state.undoStack.shift(), state.isHistoryPaused ? state.pausedHistory.unshift(...historyItem) : state.undoStack.push(historyItem);
105
+ }
106
+ function storageDispatch(ops, reverse, storageUpdates) {
107
+ state.isBatching ? (state.batch.ops.push(...ops), storageUpdates.forEach(((value, key) => {
108
+ state.batch.updates.storageUpdates.set(key, mergeStorageUpdates(state.batch.updates.storageUpdates.get(key), value));
109
+ })), state.batch.reverseOps.push(...reverse)) : (addToUndoStack(reverse), state.redoStack = [],
110
+ dispatch(ops), notify({
111
+ storageUpdates: storageUpdates
112
+ }));
113
+ }
114
+ function notify({storageUpdates: storageUpdates = new Map, presence: presence = !1, others: otherEvents = []}) {
115
+ if (otherEvents.length > 0) {
116
+ state.others = makeOthers(state.users);
117
+ for (const event of otherEvents) for (const listener of state.listeners.others) listener(state.others, event);
118
+ }
119
+ if (presence) for (const listener of state.listeners["my-presence"]) listener(state.me);
120
+ if (storageUpdates.size > 0) for (const subscriber of state.listeners.storage) subscriber(Array.from(storageUpdates.values()));
121
+ }
122
+ function getConnectionId() {
123
+ if ("open" === state.connection.state || "connecting" === state.connection.state) return state.connection.id;
124
+ if (null !== state.lastConnectionId) return state.lastConnectionId;
125
+ throw new Error("Internal. Tried to get connection id but connection was never open");
126
+ }
127
+ function generateId() {
128
+ return `${getConnectionId()}:${state.clock++}`;
129
+ }
130
+ function generateOpId() {
131
+ return `${getConnectionId()}:${state.opClock++}`;
132
+ }
133
+ function apply(item, isLocal) {
134
+ var _a;
135
+ const result = {
136
+ reverse: [],
137
+ updates: {
138
+ storageUpdates: new Map,
139
+ presence: !1
140
+ }
141
+ }, createdNodeIds = new Set;
142
+ for (const op of item) if ("presence" === op.type) {
143
+ const reverse = {
144
+ type: "presence",
145
+ data: {}
146
+ };
147
+ for (const key in op.data) reverse.data[key] = state.me[key];
148
+ if (state.me = Object.assign(Object.assign({}, state.me), op.data), null == state.buffer.presence) state.buffer.presence = op.data; else for (const key in op.data) state.buffer.presence[key] = op.data[key];
149
+ result.reverse.unshift(reverse), result.updates.presence = !0;
150
+ } else {
151
+ isLocal && !op.opId && (op.opId = generateOpId());
152
+ const applyOpResult = applyOp(op, isLocal);
153
+ if (applyOpResult.modified) {
154
+ const parentId = null === (_a = applyOpResult.modified.node._parent) || void 0 === _a ? void 0 : _a._id;
155
+ createdNodeIds.has(parentId) || (result.updates.storageUpdates.set(applyOpResult.modified.node._id, mergeStorageUpdates(result.updates.storageUpdates.get(applyOpResult.modified.node._id), applyOpResult.modified)),
156
+ result.reverse.unshift(...applyOpResult.reverse)), op.type !== OpType.CreateList && op.type !== OpType.CreateMap && op.type !== OpType.CreateObject || createdNodeIds.add(applyOpResult.modified.node._id);
157
+ }
158
+ }
159
+ return result;
160
+ }
161
+ function applyOp(op, isLocal) {
162
+ switch (op.opId && state.offlineOperations.delete(op.opId), op.type) {
163
+ case OpType.DeleteObjectKey:
164
+ case OpType.UpdateObject:
165
+ case OpType.DeleteCrdt:
166
+ {
167
+ const item = state.items.get(op.id);
168
+ return null == item ? {
169
+ modified: !1
170
+ } : item._apply(op, isLocal);
171
+ }
172
+
173
+ case OpType.SetParentKey:
174
+ {
175
+ const item = state.items.get(op.id);
176
+ if (null == item) return {
177
+ modified: !1
108
178
  };
109
- function genericSubscribe(callback) {
110
- state.listeners.storage.push(callback);
111
- return () => remove(state.listeners.storage, callback);
112
- }
113
- function crdtSubscribe(crdt, innerCallback, options) {
114
- const cb = (updates) => {
115
- const relatedUpdates = [];
116
- for (const update of updates) {
117
- if ((options === null || options === void 0 ? void 0 : options.isDeep) && isSameNodeOrChildOf(update.node, crdt)) {
118
- relatedUpdates.push(update);
119
- }
120
- else if (update.node._id === crdt._id) {
121
- innerCallback(update.node);
122
- }
123
- }
124
- if ((options === null || options === void 0 ? void 0 : options.isDeep) && relatedUpdates.length > 0) {
125
- innerCallback(relatedUpdates);
126
- }
127
- };
128
- return genericSubscribe(cb);
129
- }
130
- function createOrUpdateRootFromMessage(message) {
131
- if (message.items.length === 0) {
132
- throw new Error("Internal error: cannot load storage without items");
133
- }
134
- if (state.root) {
135
- updateRoot(message.items);
136
- }
137
- else {
138
- state.root = load(message.items);
139
- }
140
- for (const key in state.defaultStorageRoot) {
141
- if (state.root.get(key) == null) {
142
- state.root.set(key, state.defaultStorageRoot[key]);
143
- }
144
- }
145
- }
146
- function buildRootAndParentToChildren(items) {
147
- const parentToChildren = new Map();
148
- let root = null;
149
- for (const tuple of items) {
150
- const parentId = tuple[1].parentId;
151
- if (parentId == null) {
152
- root = tuple;
153
- }
154
- else {
155
- const children = parentToChildren.get(parentId);
156
- if (children != null) {
157
- children.push(tuple);
158
- }
159
- else {
160
- parentToChildren.set(parentId, [tuple]);
161
- }
162
- }
163
- }
164
- if (root == null) {
165
- throw new Error("Root can't be null");
166
- }
167
- return [root, parentToChildren];
168
- }
169
- function updateRoot(items) {
170
- if (!state.root) {
171
- return;
172
- }
173
- const currentItems = new Map();
174
- state.items.forEach((liveCrdt, id) => {
175
- currentItems.set(id, liveCrdt._toSerializedCrdt());
176
- });
177
- // Get operations that represent the diff between 2 states.
178
- const ops = getTreesDiffOperations(currentItems, new Map(items));
179
- const result = apply(ops, false);
180
- notify(result.updates);
181
- }
182
- function load(items) {
183
- const [root, parentToChildren] = buildRootAndParentToChildren(items);
184
- return LiveObject._deserialize(root, parentToChildren, {
185
- getItem,
186
- addItem,
187
- deleteItem,
188
- generateId,
189
- generateOpId,
190
- dispatch: storageDispatch,
191
- roomId: context.roomId,
192
- });
193
- }
194
- function addItem(id, item) {
195
- state.items.set(id, item);
196
- }
197
- function deleteItem(id) {
198
- state.items.delete(id);
199
- }
200
- function getItem(id) {
201
- return state.items.get(id);
202
- }
203
- function addToUndoStack(historyItem) {
204
- // If undo stack is too large, we remove the older item
205
- if (state.undoStack.length >= 50) {
206
- state.undoStack.shift();
207
- }
208
- if (state.isHistoryPaused) {
209
- state.pausedHistory.unshift(...historyItem);
210
- }
211
- else {
212
- state.undoStack.push(historyItem);
213
- }
214
- }
215
- function storageDispatch(ops, reverse, storageUpdates) {
216
- if (state.isBatching) {
217
- state.batch.ops.push(...ops);
218
- storageUpdates.forEach((value, key) => {
219
- state.batch.updates.storageUpdates.set(key, mergeStorageUpdates(state.batch.updates.storageUpdates.get(key), // FIXME
220
- value));
221
- });
222
- state.batch.reverseOps.push(...reverse);
223
- }
224
- else {
225
- addToUndoStack(reverse);
226
- state.redoStack = [];
227
- dispatch(ops);
228
- notify({ storageUpdates: storageUpdates });
229
- }
230
- }
231
- function notify({ storageUpdates = new Map(), presence = false, others: otherEvents = [], }) {
232
- if (otherEvents.length > 0) {
233
- state.others = makeOthers(state.users);
234
- for (const event of otherEvents) {
235
- for (const listener of state.listeners.others) {
236
- listener(state.others, event);
237
- }
238
- }
239
- }
240
- if (presence) {
241
- for (const listener of state.listeners["my-presence"]) {
242
- listener(state.me);
243
- }
244
- }
245
- if (storageUpdates.size > 0) {
246
- for (const subscriber of state.listeners.storage) {
247
- subscriber(Array.from(storageUpdates.values()));
248
- }
249
- }
250
- }
251
- function getConnectionId() {
252
- if (state.connection.state === "open" ||
253
- state.connection.state === "connecting") {
254
- return state.connection.id;
255
- }
256
- else if (state.lastConnectionId !== null) {
257
- return state.lastConnectionId;
258
- }
259
- throw new Error("Internal. Tried to get connection id but connection was never open");
260
- }
261
- function generateId() {
262
- return `${getConnectionId()}:${state.clock++}`;
263
- }
264
- function generateOpId() {
265
- return `${getConnectionId()}:${state.opClock++}`;
266
- }
267
- function apply(item, isLocal) {
268
- var _a;
269
- const result = {
270
- reverse: [],
271
- updates: {
272
- storageUpdates: new Map(),
273
- presence: false,
274
- },
275
- };
276
- const createdNodeIds = new Set();
277
- for (const op of item) {
278
- if (op.type === "presence") {
279
- const reverse = {
280
- type: "presence",
281
- data: {},
282
- };
283
- for (const key in op.data) {
284
- reverse.data[key] = state.me[key];
285
- }
286
- state.me = Object.assign(Object.assign({}, state.me), op.data);
287
- if (state.buffer.presence == null) {
288
- state.buffer.presence = op.data;
289
- }
290
- else {
291
- for (const key in op.data) {
292
- state.buffer.presence[key] = op.data[key];
293
- }
294
- }
295
- result.reverse.unshift(reverse);
296
- result.updates.presence = true;
297
- }
298
- else {
299
- // Ops applied after undo/redo don't have an opId.
300
- if (isLocal && !op.opId) {
301
- op.opId = generateOpId();
302
- }
303
- const applyOpResult = applyOp(op, isLocal);
304
- if (applyOpResult.modified) {
305
- const parentId = (_a = applyOpResult.modified.node._parent) === null || _a === void 0 ? void 0 : _a._id;
306
- // If the parent was created in the same batch, we don't want to notify
307
- // storage updates for the children.
308
- if (!createdNodeIds.has(parentId)) {
309
- result.updates.storageUpdates.set(applyOpResult.modified.node._id, mergeStorageUpdates(result.updates.storageUpdates.get(applyOpResult.modified.node._id), // FIXME
310
- applyOpResult.modified));
311
- result.reverse.unshift(...applyOpResult.reverse);
312
- }
313
- if (op.type === OpType.CreateList ||
314
- op.type === OpType.CreateMap ||
315
- op.type === OpType.CreateObject) {
316
- createdNodeIds.add(applyOpResult.modified.node._id);
317
- }
318
- }
319
- }
320
- }
321
- return result;
322
- }
323
- function applyOp(op, isLocal) {
324
- if (op.opId) {
325
- state.offlineOperations.delete(op.opId);
326
- }
327
- switch (op.type) {
328
- case OpType.DeleteObjectKey:
329
- case OpType.UpdateObject:
330
- case OpType.DeleteCrdt: {
331
- const item = state.items.get(op.id);
332
- if (item == null) {
333
- return { modified: false };
334
- }
335
- return item._apply(op, isLocal);
336
- }
337
- case OpType.SetParentKey: {
338
- const item = state.items.get(op.id);
339
- if (item == null) {
340
- return { modified: false };
341
- }
342
- if (item._parent instanceof LiveList) {
343
- const previousKey = item._parentKey;
344
- if (previousKey === op.parentKey) {
345
- return { modified: false };
346
- }
347
- else {
348
- return item._parent._setChildKey(op.parentKey, item, previousKey);
349
- }
350
- }
351
- return { modified: false };
352
- }
353
- case OpType.CreateObject:
354
- case OpType.CreateList:
355
- case OpType.CreateMap:
356
- case OpType.CreateRegister: {
357
- const parent = state.items.get(op.parentId);
358
- if (parent == null) {
359
- return { modified: false };
360
- }
361
- return parent._attachChild(op, isLocal);
362
- }
363
- }
364
- }
365
- function subscribe(firstParam, listener, options) {
366
- if (firstParam instanceof AbstractCrdt) {
367
- return crdtSubscribe(firstParam, listener, options);
368
- }
369
- else if (typeof firstParam === "function") {
370
- return genericSubscribe(firstParam);
371
- }
372
- else if (!isValidRoomEventType(firstParam)) {
373
- throw new Error(`"${firstParam}" is not a valid event name`);
374
- }
375
- state.listeners[firstParam].push(listener);
376
- return () => {
377
- const callbacks = state.listeners[firstParam];
378
- remove(callbacks, listener);
379
- };
380
- }
381
- function unsubscribe(event, callback) {
382
- console.warn(`unsubscribe is depreacted and will be removed in a future version.
383
- use the callback returned by subscribe instead.
384
- See v0.13 release notes for more information.
385
- `);
386
- if (!isValidRoomEventType(event)) {
387
- throw new Error(`"${event}" is not a valid event name`);
388
- }
389
- const callbacks = state.listeners[event];
390
- remove(callbacks, callback);
391
- }
392
- function getConnectionState() {
393
- return state.connection.state;
394
- }
395
- function getSelf() {
396
- return state.connection.state === "open" ||
397
- state.connection.state === "connecting"
398
- ? {
399
- connectionId: state.connection.id,
400
- id: state.connection.userId,
401
- info: state.connection.userInfo,
402
- presence: getPresence(),
403
- }
404
- : null;
405
- }
406
- function connect() {
407
- if (state.connection.state !== "closed" &&
408
- state.connection.state !== "unavailable") {
409
- return null;
410
- }
411
- const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
412
- const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
413
- updateConnection({ state: "authenticating" });
414
- effects.authenticate(auth, createWebSocket);
415
- }
416
- function updatePresence(overrides, options) {
417
- const oldValues = {};
418
- if (state.buffer.presence == null) {
419
- state.buffer.presence = {};
420
- }
421
- for (const key in overrides) {
422
- state.buffer.presence[key] = overrides[key];
423
- oldValues[key] = state.me[key];
424
- }
425
- state.me = Object.assign(Object.assign({}, state.me), overrides);
426
- if (state.isBatching) {
427
- if (options === null || options === void 0 ? void 0 : options.addToHistory) {
428
- state.batch.reverseOps.push({ type: "presence", data: oldValues });
429
- }
430
- state.batch.updates.presence = true;
431
- }
432
- else {
433
- tryFlushing();
434
- if (options === null || options === void 0 ? void 0 : options.addToHistory) {
435
- addToUndoStack([{ type: "presence", data: oldValues }]);
436
- }
437
- notify({ presence: true });
438
- }
439
- }
440
- function authenticationSuccess(token, socket) {
441
- socket.addEventListener("message", onMessage);
442
- socket.addEventListener("open", onOpen);
443
- socket.addEventListener("close", onClose);
444
- socket.addEventListener("error", onError);
445
- updateConnection({
446
- state: "connecting",
447
- id: token.actor,
448
- userInfo: token.info,
449
- userId: token.id,
450
- });
451
- state.idFactory = makeIdFactory(token.actor);
452
- state.socket = socket;
453
- }
454
- function authenticationFailure(error) {
455
- if (process.env.NODE_ENV !== "production") {
456
- console.error("Call to authentication endpoint failed", error);
457
- }
458
- state.token = null;
459
- updateConnection({ state: "unavailable" });
460
- state.numberOfRetry++;
461
- state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
462
- }
463
- function onVisibilityChange(visibilityState) {
464
- if (visibilityState === "visible" && state.connection.state === "open") {
465
- heartbeat();
466
- }
467
- }
468
- function onUpdatePresenceMessage(message) {
469
- const user = state.users[message.actor];
470
- // If the other user initial presence hasn't been received yet, we discard the presence update.
471
- // The initial presence update message contains the property "targetActor".
472
- if (message.targetActor === undefined &&
473
- user != null &&
474
- !user._hasReceivedInitialPresence) {
475
- return undefined;
476
- }
477
- if (user == null) {
478
- state.users[message.actor] = {
479
- connectionId: message.actor,
480
- presence: message.data,
481
- _hasReceivedInitialPresence: true,
482
- };
483
- }
484
- else {
485
- state.users[message.actor] = {
486
- id: user.id,
487
- info: user.info,
488
- connectionId: message.actor,
489
- presence: Object.assign(Object.assign({}, user.presence), message.data),
490
- _hasReceivedInitialPresence: true,
491
- };
492
- }
493
- return {
494
- type: "update",
495
- updates: message.data,
496
- user: state.users[message.actor],
497
- };
498
- }
499
- function onUserLeftMessage(message) {
500
- const userLeftMessage = message;
501
- const user = state.users[userLeftMessage.actor];
502
- if (user) {
503
- delete state.users[userLeftMessage.actor];
504
- return { type: "leave", user };
505
- }
506
- return null;
507
- }
508
- function onRoomStateMessage(message) {
509
- const newUsers = {};
510
- for (const key in message.users) {
511
- const connectionId = Number.parseInt(key);
512
- const user = message.users[key];
513
- newUsers[connectionId] = {
514
- connectionId,
515
- info: user.info,
516
- id: user.id,
517
- };
518
- }
519
- state.users = newUsers;
520
- return { type: "reset" };
521
- }
522
- function onNavigatorOnline() {
523
- if (state.connection.state === "unavailable") {
524
- reconnect();
525
- }
526
- }
527
- function onEvent(message) {
528
- for (const listener of state.listeners.event) {
529
- listener({ connectionId: message.actor, event: message.event });
530
- }
531
- }
532
- function onUserJoinedMessage(message) {
533
- state.users[message.actor] = {
534
- connectionId: message.actor,
535
- info: message.info,
536
- id: message.id,
537
- _hasReceivedInitialPresence: true,
538
- };
539
- if (state.me) {
540
- // Send current presence to new user
541
- // TODO: Consider storing it on the backend
542
- state.buffer.messages.push({
543
- type: ClientMessageType.UpdatePresence,
544
- data: state.me,
545
- targetActor: message.actor,
546
- });
547
- tryFlushing();
548
- }
549
- return { type: "enter", user: state.users[message.actor] };
550
- }
551
- function parseServerMessage(data) {
552
- if (!isJsonObject(data)) {
553
- return null;
554
- }
555
- return data;
556
- // ^^^^^^^^^^^^^^^^ FIXME: Properly validate incoming external data instead!
557
- }
558
- function parseServerMessages(text) {
559
- const data = parseJson(text);
560
- if (data === undefined) {
561
- return null;
562
- }
563
- else if (isJsonArray(data)) {
564
- return compact(data.map((item) => parseServerMessage(item)));
565
- }
566
- else {
567
- return compact([parseServerMessage(data)]);
568
- }
569
- }
570
- function onMessage(event) {
571
- if (event.data === "pong") {
572
- clearTimeout(state.timeoutHandles.pongTimeout);
573
- return;
574
- }
575
- const messages = parseServerMessages(event.data);
576
- if (messages === null || messages.length === 0) {
577
- // Unknown incoming message... ignore it
578
- return;
579
- }
580
- const updates = {
581
- storageUpdates: new Map(),
582
- others: [],
583
- };
584
- for (const message of messages) {
585
- switch (message.type) {
586
- case ServerMessageType.UserJoined: {
587
- updates.others.push(onUserJoinedMessage(message));
588
- break;
589
- }
590
- case ServerMessageType.UpdatePresence: {
591
- const othersPresenceUpdate = onUpdatePresenceMessage(message);
592
- if (othersPresenceUpdate) {
593
- updates.others.push(othersPresenceUpdate);
594
- }
595
- break;
596
- }
597
- case ServerMessageType.Event: {
598
- onEvent(message);
599
- break;
600
- }
601
- case ServerMessageType.UserLeft: {
602
- const event = onUserLeftMessage(message);
603
- if (event) {
604
- updates.others.push(event);
605
- }
606
- break;
607
- }
608
- case ServerMessageType.RoomState: {
609
- updates.others.push(onRoomStateMessage(message));
610
- break;
611
- }
612
- case ServerMessageType.InitialStorageState: {
613
- // createOrUpdateRootFromMessage function could add ops to offlineOperations.
614
- // Client shouldn't resend these ops as part of the offline ops sending after reconnect.
615
- const offlineOps = new Map(state.offlineOperations);
616
- createOrUpdateRootFromMessage(message);
617
- applyAndSendOfflineOps(offlineOps);
618
- _getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
619
- break;
620
- }
621
- case ServerMessageType.UpdateStorage: {
622
- const applyResult = apply(message.ops, false);
623
- applyResult.updates.storageUpdates.forEach((value, key) => {
624
- updates.storageUpdates.set(key, mergeStorageUpdates(updates.storageUpdates.get(key), // FIXME
625
- value));
626
- });
627
- break;
628
- }
629
- }
630
- }
631
- notify(updates);
632
- }
633
- // function onWakeUp() {
634
- // // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example)
635
- // // The client will not know that the server has probably close the connection even if the readyState is Open
636
- // // One way to detect this kind of pause is to ensure that a setInterval is not taking more than the delay it was configured with
637
- // if (state.connection.state === "open") {
638
- // log("Try to reconnect after laptop wake up");
639
- // reconnect();
640
- // }
641
- // }
642
- function onClose(event) {
643
- state.socket = null;
644
- clearTimeout(state.timeoutHandles.pongTimeout);
645
- clearInterval(state.intervalHandles.heartbeat);
646
- if (state.timeoutHandles.flush) {
647
- clearTimeout(state.timeoutHandles.flush);
648
- }
649
- clearTimeout(state.timeoutHandles.reconnect);
650
- state.users = {};
651
- notify({ others: [{ type: "reset" }] });
652
- if (event.code >= 4000 && event.code <= 4100) {
653
- updateConnection({ state: "failed" });
654
- const error = new LiveblocksError(event.reason, event.code);
655
- for (const listener of state.listeners.error) {
656
- listener(error);
657
- }
658
- const delay = getRetryDelay(true);
659
- state.numberOfRetry++;
660
- if (process.env.NODE_ENV !== "production") {
661
- console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code}). Retrying in ${delay}ms.`);
662
- }
663
- updateConnection({ state: "unavailable" });
664
- state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
665
- }
666
- else if (event.code === WebsocketCloseCodes.CLOSE_WITHOUT_RETRY) {
667
- updateConnection({ state: "closed" });
668
- }
669
- else {
670
- const delay = getRetryDelay();
671
- state.numberOfRetry++;
672
- if (process.env.NODE_ENV !== "production") {
673
- console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
674
- }
675
- updateConnection({ state: "unavailable" });
676
- state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
677
- }
678
- }
679
- function updateConnection(connection) {
680
- state.connection = connection;
681
- for (const listener of state.listeners.connection) {
682
- listener(connection.state);
683
- }
684
- }
685
- function getRetryDelay(slow = false) {
686
- if (slow) {
687
- return BACKOFF_RETRY_DELAYS_SLOW[state.numberOfRetry < BACKOFF_RETRY_DELAYS_SLOW.length
688
- ? state.numberOfRetry
689
- : BACKOFF_RETRY_DELAYS_SLOW.length - 1];
690
- }
691
- return BACKOFF_RETRY_DELAYS[state.numberOfRetry < BACKOFF_RETRY_DELAYS.length
692
- ? state.numberOfRetry
693
- : BACKOFF_RETRY_DELAYS.length - 1];
694
- }
695
- function onError() { }
696
- function onOpen() {
697
- clearInterval(state.intervalHandles.heartbeat);
698
- state.intervalHandles.heartbeat = effects.startHeartbeatInterval();
699
- if (state.connection.state === "connecting") {
700
- updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
701
- state.numberOfRetry = 0;
702
- // Re-broadcast the user presence during a reconnect.
703
- if (state.lastConnectionId !== undefined) {
704
- state.buffer.presence = state.me;
705
- tryFlushing();
706
- }
707
- state.lastConnectionId = state.connection.id;
708
- if (state.root) {
709
- state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
710
- }
711
- tryFlushing();
712
- }
713
- }
714
- function heartbeat() {
715
- if (state.socket == null) {
716
- // Should never happen, because we clear the pong timeout when the connection is dropped explictly
717
- return;
718
- }
719
- clearTimeout(state.timeoutHandles.pongTimeout);
720
- state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
721
- if (state.socket.readyState === state.socket.OPEN) {
722
- state.socket.send("ping");
723
- }
724
- }
725
- function pongTimeout() {
726
- reconnect();
727
- }
728
- function reconnect() {
729
- if (state.socket) {
730
- state.socket.removeEventListener("open", onOpen);
731
- state.socket.removeEventListener("message", onMessage);
732
- state.socket.removeEventListener("close", onClose);
733
- state.socket.removeEventListener("error", onError);
734
- state.socket.close();
735
- state.socket = null;
736
- }
737
- updateConnection({ state: "unavailable" });
738
- clearTimeout(state.timeoutHandles.pongTimeout);
739
- if (state.timeoutHandles.flush) {
740
- clearTimeout(state.timeoutHandles.flush);
741
- }
742
- clearTimeout(state.timeoutHandles.reconnect);
743
- clearInterval(state.intervalHandles.heartbeat);
744
- connect();
745
- }
746
- function applyAndSendOfflineOps(offlineOps) {
747
- if (offlineOps.size === 0) {
748
- return;
749
- }
750
- const messages = [];
751
- const ops = Array.from(offlineOps.values());
752
- const result = apply(ops, true);
753
- messages.push({
754
- type: ClientMessageType.UpdateStorage,
755
- ops: ops,
756
- });
757
- notify(result.updates);
758
- effects.send(messages);
759
- }
760
- function tryFlushing() {
761
- const storageOps = state.buffer.storageOperations;
762
- if (storageOps.length > 0) {
763
- storageOps.forEach((op) => {
764
- state.offlineOperations.set(op.opId, op);
765
- });
766
- }
767
- if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
768
- state.buffer.storageOperations = [];
769
- return;
770
- }
771
- const now = Date.now();
772
- const elapsedTime = now - state.lastFlushTime;
773
- if (elapsedTime > context.throttleDelay) {
774
- const messages = flushDataToMessages(state);
775
- if (messages.length === 0) {
776
- return;
777
- }
778
- effects.send(messages);
779
- state.buffer = {
780
- messages: [],
781
- storageOperations: [],
782
- presence: null,
783
- };
784
- state.lastFlushTime = now;
785
- }
786
- else {
787
- if (state.timeoutHandles.flush != null) {
788
- clearTimeout(state.timeoutHandles.flush);
789
- }
790
- state.timeoutHandles.flush = effects.delayFlush(context.throttleDelay - (now - state.lastFlushTime));
791
- }
792
- }
793
- function flushDataToMessages(state) {
794
- const messages = [];
795
- if (state.buffer.presence) {
796
- messages.push({
797
- type: ClientMessageType.UpdatePresence,
798
- data: state.buffer.presence,
799
- });
800
- }
801
- for (const event of state.buffer.messages) {
802
- messages.push(event);
803
- }
804
- if (state.buffer.storageOperations.length > 0) {
805
- messages.push({
806
- type: ClientMessageType.UpdateStorage,
807
- ops: state.buffer.storageOperations,
808
- });
809
- }
810
- return messages;
811
- }
812
- function disconnect() {
813
- if (state.socket) {
814
- state.socket.removeEventListener("open", onOpen);
815
- state.socket.removeEventListener("message", onMessage);
816
- state.socket.removeEventListener("close", onClose);
817
- state.socket.removeEventListener("error", onError);
818
- state.socket.close();
819
- state.socket = null;
820
- }
821
- updateConnection({ state: "closed" });
822
- if (state.timeoutHandles.flush) {
823
- clearTimeout(state.timeoutHandles.flush);
824
- }
825
- clearTimeout(state.timeoutHandles.reconnect);
826
- clearTimeout(state.timeoutHandles.pongTimeout);
827
- clearInterval(state.intervalHandles.heartbeat);
828
- state.users = {};
829
- notify({ others: [{ type: "reset" }] });
830
- clearListeners();
831
- }
832
- function clearListeners() {
833
- for (const key in state.listeners) {
834
- state.listeners[key] = [];
835
- }
836
- }
837
- function getPresence() {
838
- return state.me;
839
- }
840
- function getOthers() {
841
- return state.others;
842
- }
843
- function broadcastEvent(event, options = {
844
- shouldQueueEventIfNotReady: false,
845
- }) {
846
- if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
847
- return;
848
- }
849
- state.buffer.messages.push({
850
- type: ClientMessageType.ClientEvent,
851
- event,
852
- });
853
- tryFlushing();
854
- }
855
- function dispatch(ops) {
856
- state.buffer.storageOperations.push(...ops);
857
- tryFlushing();
858
- }
859
- let _getInitialStatePromise = null;
860
- let _getInitialStateResolver = null;
861
- function getStorage() {
862
- if (state.root) {
863
- return new Promise((resolve) => resolve({
864
- root: state.root,
865
- }));
866
- }
867
- if (_getInitialStatePromise == null) {
868
- state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
869
- tryFlushing();
870
- _getInitialStatePromise = new Promise((resolve) => (_getInitialStateResolver = resolve));
871
- }
872
- return _getInitialStatePromise.then(() => {
873
- return {
874
- root: state.root,
875
- };
876
- });
877
- }
878
- function undo() {
879
- if (state.isBatching) {
880
- throw new Error("undo is not allowed during a batch");
881
- }
882
- const historyItem = state.undoStack.pop();
883
- if (historyItem == null) {
884
- return;
885
- }
886
- state.isHistoryPaused = false;
887
- const result = apply(historyItem, true);
888
- notify(result.updates);
889
- state.redoStack.push(result.reverse);
890
- for (const op of historyItem) {
891
- if (op.type !== "presence") {
892
- state.buffer.storageOperations.push(op);
893
- }
894
- }
895
- tryFlushing();
896
- }
897
- function redo() {
898
- if (state.isBatching) {
899
- throw new Error("redo is not allowed during a batch");
900
- }
901
- const historyItem = state.redoStack.pop();
902
- if (historyItem == null) {
903
- return;
904
- }
905
- state.isHistoryPaused = false;
906
- const result = apply(historyItem, true);
907
- notify(result.updates);
908
- state.undoStack.push(result.reverse);
909
- for (const op of historyItem) {
910
- if (op.type !== "presence") {
911
- state.buffer.storageOperations.push(op);
912
- }
913
- }
914
- tryFlushing();
915
- }
916
- function batch(callback) {
917
- if (state.isBatching) {
918
- throw new Error("batch should not be called during a batch");
919
- }
920
- state.isBatching = true;
921
- try {
922
- callback();
923
- }
924
- finally {
925
- state.isBatching = false;
926
- if (state.batch.reverseOps.length > 0) {
927
- addToUndoStack(state.batch.reverseOps);
928
- }
929
- if (state.batch.ops.length > 0) {
930
- // Only clear the redo stack if something has changed during a batch
931
- // Clear the redo stack because batch is always called from a local operation
932
- state.redoStack = [];
933
- }
934
- if (state.batch.ops.length > 0) {
935
- dispatch(state.batch.ops);
936
- }
937
- notify(state.batch.updates);
938
- state.batch = {
939
- ops: [],
940
- reverseOps: [],
941
- updates: {
942
- others: [],
943
- storageUpdates: new Map(),
944
- presence: false,
945
- },
946
- };
947
- tryFlushing();
948
- }
949
- }
950
- function pauseHistory() {
951
- state.pausedHistory = [];
952
- state.isHistoryPaused = true;
953
- }
954
- function resumeHistory() {
955
- state.isHistoryPaused = false;
956
- if (state.pausedHistory.length > 0) {
957
- addToUndoStack(state.pausedHistory);
958
- }
959
- state.pausedHistory = [];
960
- }
961
- function simulateSocketClose() {
962
- if (state.socket) {
963
- state.socket.close();
964
- }
965
- }
966
- function simulateSendCloseEvent(event) {
967
- if (state.socket) {
968
- onClose(event);
969
- }
179
+ if (item._parent instanceof LiveList) {
180
+ const previousKey = item._parentKey;
181
+ return previousKey === op.parentKey ? {
182
+ modified: !1
183
+ } : item._parent._setChildKey(op.parentKey, item, previousKey);
970
184
  }
971
185
  return {
972
- // Internal
973
- onClose,
974
- onMessage,
975
- authenticationSuccess,
976
- heartbeat,
977
- onNavigatorOnline,
978
- // Internal dev tools
979
- simulateSocketClose,
980
- simulateSendCloseEvent,
981
- // onWakeUp,
982
- onVisibilityChange,
983
- getUndoStack: () => state.undoStack,
984
- getItemsCount: () => state.items.size,
985
- // Core
986
- connect,
987
- disconnect,
988
- subscribe,
989
- unsubscribe,
990
- // Presence
991
- updatePresence,
992
- broadcastEvent,
993
- batch,
994
- undo,
995
- redo,
996
- pauseHistory,
997
- resumeHistory,
998
- getStorage,
999
- selectors: {
1000
- // Core
1001
- getConnectionState,
1002
- getSelf,
1003
- // Presence
1004
- getPresence,
1005
- getOthers,
1006
- },
1007
- };
1008
- }
1009
- function defaultState(me, defaultStorageRoot) {
1010
- return {
1011
- connection: { state: "closed" },
1012
- token: null,
1013
- lastConnectionId: null,
1014
- socket: null,
1015
- listeners: {
1016
- event: [],
1017
- others: [],
1018
- "my-presence": [],
1019
- error: [],
1020
- connection: [],
1021
- storage: [],
1022
- },
1023
- numberOfRetry: 0,
1024
- lastFlushTime: 0,
1025
- timeoutHandles: {
1026
- flush: null,
1027
- reconnect: 0,
1028
- pongTimeout: 0,
1029
- },
1030
- buffer: {
1031
- presence: me == null ? {} : me,
1032
- messages: [],
1033
- storageOperations: [],
1034
- },
1035
- intervalHandles: {
1036
- heartbeat: 0,
1037
- },
1038
- me: me == null ? {} : me,
1039
- users: {},
1040
- others: makeOthers({}),
1041
- defaultStorageRoot,
1042
- idFactory: null,
1043
- // Storage
1044
- clock: 0,
1045
- opClock: 0,
1046
- items: new Map(),
1047
- root: undefined,
1048
- undoStack: [],
1049
- redoStack: [],
1050
- isHistoryPaused: false,
1051
- pausedHistory: [],
1052
- isBatching: false,
1053
- batch: {
1054
- ops: [],
1055
- updates: {
1056
- storageUpdates: new Map(),
1057
- presence: false,
1058
- others: [],
1059
- },
1060
- reverseOps: [],
1061
- },
1062
- offlineOperations: new Map(),
186
+ modified: !1
1063
187
  };
188
+ }
189
+
190
+ case OpType.CreateObject:
191
+ case OpType.CreateList:
192
+ case OpType.CreateMap:
193
+ case OpType.CreateRegister:
194
+ {
195
+ const parent = state.items.get(op.parentId);
196
+ return null == parent ? {
197
+ modified: !1
198
+ } : parent._attachChild(op, isLocal);
199
+ }
200
+ }
201
+ }
202
+ function connect() {
203
+ if ("closed" !== state.connection.state && "unavailable" !== state.connection.state) return null;
204
+ const auth = function(authentication, fetchPolyfill) {
205
+ if ("public" === authentication.type) {
206
+ if ("undefined" == typeof window && null == fetchPolyfill) throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
207
+ return room => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
208
+ room: room,
209
+ publicApiKey: authentication.publicApiKey
210
+ });
211
+ }
212
+ if ("private" === authentication.type) {
213
+ if ("undefined" == typeof window && null == fetchPolyfill) 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.");
214
+ return room => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
215
+ room: room
216
+ });
217
+ }
218
+ if ("custom" === authentication.type) return authentication.callback;
219
+ throw new Error("Internal error. Unexpected authentication type");
220
+ }(context.authentication, context.fetchPolyfill), createWebSocket = function(liveblocksServer, WebSocketPolyfill) {
221
+ if ("undefined" == typeof window && null == WebSocketPolyfill) throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
222
+ const ws = WebSocketPolyfill || WebSocket;
223
+ return token => new ws(`${liveblocksServer}/?token=${token}`);
224
+ }(context.liveblocksServer, context.WebSocketPolyfill);
225
+ updateConnection({
226
+ state: "authenticating"
227
+ }), effects.authenticate(auth, createWebSocket);
228
+ }
229
+ function authenticationSuccess(token, socket) {
230
+ socket.addEventListener("message", onMessage), socket.addEventListener("open", onOpen),
231
+ socket.addEventListener("close", onClose), socket.addEventListener("error", onError),
232
+ updateConnection({
233
+ state: "connecting",
234
+ id: token.actor,
235
+ userInfo: token.info,
236
+ userId: token.id
237
+ }), state.idFactory = function(connectionId) {
238
+ let count = 0;
239
+ return () => `${connectionId}:${count++}`;
240
+ }(token.actor), state.socket = socket;
241
+ }
242
+ function onUpdatePresenceMessage(message) {
243
+ const user = state.users[message.actor];
244
+ if (void 0 !== message.targetActor || null == user || user._hasReceivedInitialPresence) return state.users[message.actor] = null == user ? {
245
+ connectionId: message.actor,
246
+ presence: message.data,
247
+ _hasReceivedInitialPresence: !0
248
+ } : {
249
+ id: user.id,
250
+ info: user.info,
251
+ connectionId: message.actor,
252
+ presence: Object.assign(Object.assign({}, user.presence), message.data),
253
+ _hasReceivedInitialPresence: !0
254
+ }, {
255
+ type: "update",
256
+ updates: message.data,
257
+ user: state.users[message.actor]
258
+ };
259
+ }
260
+ function onUserLeftMessage(message) {
261
+ const userLeftMessage = message, user = state.users[userLeftMessage.actor];
262
+ return user ? (delete state.users[userLeftMessage.actor], {
263
+ type: "leave",
264
+ user: user
265
+ }) : null;
266
+ }
267
+ function onRoomStateMessage(message) {
268
+ const newUsers = {};
269
+ for (const key in message.users) {
270
+ const connectionId = Number.parseInt(key), user = message.users[key];
271
+ newUsers[connectionId] = {
272
+ connectionId: connectionId,
273
+ info: user.info,
274
+ id: user.id
275
+ };
276
+ }
277
+ return state.users = newUsers, {
278
+ type: "reset"
279
+ };
280
+ }
281
+ function onEvent(message) {
282
+ for (const listener of state.listeners.event) listener({
283
+ connectionId: message.actor,
284
+ event: message.event
285
+ });
286
+ }
287
+ function onUserJoinedMessage(message) {
288
+ return state.users[message.actor] = {
289
+ connectionId: message.actor,
290
+ info: message.info,
291
+ id: message.id,
292
+ _hasReceivedInitialPresence: !0
293
+ }, state.me && (state.buffer.messages.push({
294
+ type: ClientMessageType.UpdatePresence,
295
+ data: state.me,
296
+ targetActor: message.actor
297
+ }), tryFlushing()), {
298
+ type: "enter",
299
+ user: state.users[message.actor]
300
+ };
301
+ }
302
+ function parseServerMessage(data) {
303
+ return isJsonObject(data) ? data : null;
304
+ }
305
+ function onMessage(event) {
306
+ if ("pong" === event.data) return void clearTimeout(state.timeoutHandles.pongTimeout);
307
+ const messages = function(text) {
308
+ const data = parseJson(text);
309
+ return void 0 === data ? null : isJsonArray(data) ? compact(data.map((item => parseServerMessage(item)))) : compact([ parseServerMessage(data) ]);
310
+ }(event.data);
311
+ if (null === messages || 0 === messages.length) return;
312
+ const updates = {
313
+ storageUpdates: new Map,
314
+ others: []
315
+ };
316
+ for (const message of messages) switch (message.type) {
317
+ case ServerMessageType.UserJoined:
318
+ updates.others.push(onUserJoinedMessage(message));
319
+ break;
320
+
321
+ case ServerMessageType.UpdatePresence:
322
+ {
323
+ const othersPresenceUpdate = onUpdatePresenceMessage(message);
324
+ othersPresenceUpdate && updates.others.push(othersPresenceUpdate);
325
+ break;
326
+ }
327
+
328
+ case ServerMessageType.Event:
329
+ onEvent(message);
330
+ break;
331
+
332
+ case ServerMessageType.UserLeft:
333
+ {
334
+ const event = onUserLeftMessage(message);
335
+ event && updates.others.push(event);
336
+ break;
337
+ }
338
+
339
+ case ServerMessageType.RoomState:
340
+ updates.others.push(onRoomStateMessage(message));
341
+ break;
342
+
343
+ case ServerMessageType.InitialStorageState:
344
+ {
345
+ const offlineOps = new Map(state.offlineOperations);
346
+ createOrUpdateRootFromMessage(message), applyAndSendOfflineOps(offlineOps), null == _getInitialStateResolver || _getInitialStateResolver();
347
+ break;
348
+ }
349
+
350
+ case ServerMessageType.UpdateStorage:
351
+ apply(message.ops, !1).updates.storageUpdates.forEach(((value, key) => {
352
+ updates.storageUpdates.set(key, mergeStorageUpdates(updates.storageUpdates.get(key), value));
353
+ }));
354
+ break;
355
+ }
356
+ notify(updates);
357
+ }
358
+ function onClose(event) {
359
+ if (state.socket = null, clearTimeout(state.timeoutHandles.pongTimeout), clearInterval(state.intervalHandles.heartbeat),
360
+ state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush), clearTimeout(state.timeoutHandles.reconnect),
361
+ state.users = {}, notify({
362
+ others: [ {
363
+ type: "reset"
364
+ } ]
365
+ }), event.code >= 4e3 && event.code <= 4100) {
366
+ updateConnection({
367
+ state: "failed"
368
+ });
369
+ const error = new LiveblocksError(event.reason, event.code);
370
+ for (const listener of state.listeners.error) listener(error);
371
+ const delay = getRetryDelay(!0);
372
+ state.numberOfRetry++, "production" !== process.env.NODE_ENV && console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code}). Retrying in ${delay}ms.`),
373
+ updateConnection({
374
+ state: "unavailable"
375
+ }), state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
376
+ } else if (event.code === WebsocketCloseCodes.CLOSE_WITHOUT_RETRY) updateConnection({
377
+ state: "closed"
378
+ }); else {
379
+ const delay = getRetryDelay();
380
+ state.numberOfRetry++, "production" !== process.env.NODE_ENV && console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`),
381
+ updateConnection({
382
+ state: "unavailable"
383
+ }), state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
384
+ }
385
+ }
386
+ function updateConnection(connection) {
387
+ state.connection = connection;
388
+ for (const listener of state.listeners.connection) listener(connection.state);
389
+ }
390
+ function getRetryDelay(slow = !1) {
391
+ return slow ? BACKOFF_RETRY_DELAYS_SLOW[state.numberOfRetry < BACKOFF_RETRY_DELAYS_SLOW.length ? state.numberOfRetry : BACKOFF_RETRY_DELAYS_SLOW.length - 1] : BACKOFF_RETRY_DELAYS[state.numberOfRetry < BACKOFF_RETRY_DELAYS.length ? state.numberOfRetry : BACKOFF_RETRY_DELAYS.length - 1];
392
+ }
393
+ function onError() {}
394
+ function onOpen() {
395
+ clearInterval(state.intervalHandles.heartbeat), state.intervalHandles.heartbeat = effects.startHeartbeatInterval(),
396
+ "connecting" === state.connection.state && (updateConnection(Object.assign(Object.assign({}, state.connection), {
397
+ state: "open"
398
+ })), state.numberOfRetry = 0, void 0 !== state.lastConnectionId && (state.buffer.presence = state.me,
399
+ tryFlushing()), state.lastConnectionId = state.connection.id, state.root && state.buffer.messages.push({
400
+ type: ClientMessageType.FetchStorage
401
+ }), tryFlushing());
402
+ }
403
+ function heartbeat() {
404
+ null != state.socket && (clearTimeout(state.timeoutHandles.pongTimeout), state.timeoutHandles.pongTimeout = effects.schedulePongTimeout(),
405
+ state.socket.readyState === state.socket.OPEN && state.socket.send("ping"));
406
+ }
407
+ function pongTimeout() {
408
+ reconnect();
409
+ }
410
+ function reconnect() {
411
+ state.socket && (state.socket.removeEventListener("open", onOpen), state.socket.removeEventListener("message", onMessage),
412
+ state.socket.removeEventListener("close", onClose), state.socket.removeEventListener("error", onError),
413
+ state.socket.close(), state.socket = null), updateConnection({
414
+ state: "unavailable"
415
+ }), clearTimeout(state.timeoutHandles.pongTimeout), state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush),
416
+ clearTimeout(state.timeoutHandles.reconnect), clearInterval(state.intervalHandles.heartbeat),
417
+ connect();
418
+ }
419
+ function applyAndSendOfflineOps(offlineOps) {
420
+ if (0 === offlineOps.size) return;
421
+ const messages = [], ops = Array.from(offlineOps.values()), result = apply(ops, !0);
422
+ messages.push({
423
+ type: ClientMessageType.UpdateStorage,
424
+ ops: ops
425
+ }), notify(result.updates), effects.send(messages);
426
+ }
427
+ function tryFlushing() {
428
+ const storageOps = state.buffer.storageOperations;
429
+ if (storageOps.length > 0 && storageOps.forEach((op => {
430
+ state.offlineOperations.set(op.opId, op);
431
+ })), null == state.socket || state.socket.readyState !== state.socket.OPEN) return void (state.buffer.storageOperations = []);
432
+ const now = Date.now();
433
+ if (now - state.lastFlushTime > context.throttleDelay) {
434
+ const messages = function(state) {
435
+ const messages = [];
436
+ state.buffer.presence && messages.push({
437
+ type: ClientMessageType.UpdatePresence,
438
+ data: state.buffer.presence
439
+ });
440
+ for (const event of state.buffer.messages) messages.push(event);
441
+ state.buffer.storageOperations.length > 0 && messages.push({
442
+ type: ClientMessageType.UpdateStorage,
443
+ ops: state.buffer.storageOperations
444
+ });
445
+ return messages;
446
+ }(state);
447
+ if (0 === messages.length) return;
448
+ effects.send(messages), state.buffer = {
449
+ messages: [],
450
+ storageOperations: [],
451
+ presence: null
452
+ }, state.lastFlushTime = now;
453
+ } else null != state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush),
454
+ state.timeoutHandles.flush = effects.delayFlush(context.throttleDelay - (now - state.lastFlushTime));
455
+ }
456
+ function getPresence() {
457
+ return state.me;
458
+ }
459
+ function dispatch(ops) {
460
+ state.buffer.storageOperations.push(...ops), tryFlushing();
461
+ }
462
+ let _getInitialStatePromise = null, _getInitialStateResolver = null;
463
+ return {
464
+ onClose: onClose,
465
+ onMessage: onMessage,
466
+ authenticationSuccess: authenticationSuccess,
467
+ heartbeat: heartbeat,
468
+ onNavigatorOnline: function() {
469
+ "unavailable" === state.connection.state && reconnect();
470
+ },
471
+ simulateSocketClose: function() {
472
+ state.socket && state.socket.close();
473
+ },
474
+ simulateSendCloseEvent: function(event) {
475
+ state.socket && onClose(event);
476
+ },
477
+ onVisibilityChange: function(visibilityState) {
478
+ "visible" === visibilityState && "open" === state.connection.state && heartbeat();
479
+ },
480
+ getUndoStack: () => state.undoStack,
481
+ getItemsCount: () => state.items.size,
482
+ connect: connect,
483
+ disconnect: function() {
484
+ state.socket && (state.socket.removeEventListener("open", onOpen), state.socket.removeEventListener("message", onMessage),
485
+ state.socket.removeEventListener("close", onClose), state.socket.removeEventListener("error", onError),
486
+ state.socket.close(), state.socket = null), updateConnection({
487
+ state: "closed"
488
+ }), state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush), clearTimeout(state.timeoutHandles.reconnect),
489
+ clearTimeout(state.timeoutHandles.pongTimeout), clearInterval(state.intervalHandles.heartbeat),
490
+ state.users = {}, notify({
491
+ others: [ {
492
+ type: "reset"
493
+ } ]
494
+ }), function() {
495
+ for (const key in state.listeners) state.listeners[key] = [];
496
+ }();
497
+ },
498
+ subscribe: function(firstParam, listener, options) {
499
+ if (firstParam instanceof AbstractCrdt) return function(crdt, innerCallback, options) {
500
+ return genericSubscribe((updates => {
501
+ const relatedUpdates = [];
502
+ for (const update of updates) (null == options ? void 0 : options.isDeep) && isSameNodeOrChildOf(update.node, crdt) ? relatedUpdates.push(update) : update.node._id === crdt._id && innerCallback(update.node);
503
+ (null == options ? void 0 : options.isDeep) && relatedUpdates.length > 0 && innerCallback(relatedUpdates);
504
+ }));
505
+ }(firstParam, listener, options);
506
+ if ("function" == typeof firstParam) return genericSubscribe(firstParam);
507
+ if (!isValidRoomEventType(firstParam)) throw new Error(`"${firstParam}" is not a valid event name`);
508
+ return state.listeners[firstParam].push(listener), () => {
509
+ const callbacks = state.listeners[firstParam];
510
+ remove(callbacks, listener);
511
+ };
512
+ },
513
+ unsubscribe: function(event, callback) {
514
+ if (console.warn("unsubscribe is depreacted and will be removed in a future version.\nuse the callback returned by subscribe instead.\nSee v0.13 release notes for more information.\n"),
515
+ !isValidRoomEventType(event)) throw new Error(`"${event}" is not a valid event name`);
516
+ const callbacks = state.listeners[event];
517
+ remove(callbacks, callback);
518
+ },
519
+ updatePresence: function(overrides, options) {
520
+ const oldValues = {};
521
+ null == state.buffer.presence && (state.buffer.presence = {});
522
+ for (const key in overrides) state.buffer.presence[key] = overrides[key], oldValues[key] = state.me[key];
523
+ state.me = Object.assign(Object.assign({}, state.me), overrides), state.isBatching ? ((null == options ? void 0 : options.addToHistory) && state.batch.reverseOps.push({
524
+ type: "presence",
525
+ data: oldValues
526
+ }), state.batch.updates.presence = !0) : (tryFlushing(), (null == options ? void 0 : options.addToHistory) && addToUndoStack([ {
527
+ type: "presence",
528
+ data: oldValues
529
+ } ]), notify({
530
+ presence: !0
531
+ }));
532
+ },
533
+ broadcastEvent: function(event, options = {
534
+ shouldQueueEventIfNotReady: !1
535
+ }) {
536
+ null == state.socket && 0 == options.shouldQueueEventIfNotReady || (state.buffer.messages.push({
537
+ type: ClientMessageType.ClientEvent,
538
+ event: event
539
+ }), tryFlushing());
540
+ },
541
+ batch: function(callback) {
542
+ if (state.isBatching) throw new Error("batch should not be called during a batch");
543
+ state.isBatching = !0;
544
+ try {
545
+ callback();
546
+ } finally {
547
+ state.isBatching = !1, state.batch.reverseOps.length > 0 && addToUndoStack(state.batch.reverseOps),
548
+ state.batch.ops.length > 0 && (state.redoStack = []), state.batch.ops.length > 0 && dispatch(state.batch.ops),
549
+ notify(state.batch.updates), state.batch = {
550
+ ops: [],
551
+ reverseOps: [],
552
+ updates: {
553
+ others: [],
554
+ storageUpdates: new Map,
555
+ presence: !1
556
+ }
557
+ }, tryFlushing();
558
+ }
559
+ },
560
+ undo: function() {
561
+ if (state.isBatching) throw new Error("undo is not allowed during a batch");
562
+ const historyItem = state.undoStack.pop();
563
+ if (null == historyItem) return;
564
+ state.isHistoryPaused = !1;
565
+ const result = apply(historyItem, !0);
566
+ notify(result.updates), state.redoStack.push(result.reverse);
567
+ for (const op of historyItem) "presence" !== op.type && state.buffer.storageOperations.push(op);
568
+ tryFlushing();
569
+ },
570
+ redo: function() {
571
+ if (state.isBatching) throw new Error("redo is not allowed during a batch");
572
+ const historyItem = state.redoStack.pop();
573
+ if (null == historyItem) return;
574
+ state.isHistoryPaused = !1;
575
+ const result = apply(historyItem, !0);
576
+ notify(result.updates), state.undoStack.push(result.reverse);
577
+ for (const op of historyItem) "presence" !== op.type && state.buffer.storageOperations.push(op);
578
+ tryFlushing();
579
+ },
580
+ pauseHistory: function() {
581
+ state.pausedHistory = [], state.isHistoryPaused = !0;
582
+ },
583
+ resumeHistory: function() {
584
+ state.isHistoryPaused = !1, state.pausedHistory.length > 0 && addToUndoStack(state.pausedHistory),
585
+ state.pausedHistory = [];
586
+ },
587
+ getStorage: function() {
588
+ return state.root ? new Promise((resolve => resolve({
589
+ root: state.root
590
+ }))) : (null == _getInitialStatePromise && (state.buffer.messages.push({
591
+ type: ClientMessageType.FetchStorage
592
+ }), tryFlushing(), _getInitialStatePromise = new Promise((resolve => _getInitialStateResolver = resolve))),
593
+ _getInitialStatePromise.then((() => ({
594
+ root: state.root
595
+ }))));
596
+ },
597
+ selectors: {
598
+ getConnectionState: function() {
599
+ return state.connection.state;
600
+ },
601
+ getSelf: function() {
602
+ return "open" === state.connection.state || "connecting" === state.connection.state ? {
603
+ connectionId: state.connection.id,
604
+ id: state.connection.userId,
605
+ info: state.connection.userInfo,
606
+ presence: getPresence()
607
+ } : null;
608
+ },
609
+ getPresence: getPresence,
610
+ getOthers: function() {
611
+ return state.others;
612
+ }
613
+ }
614
+ };
1064
615
  }
616
+
1065
617
  function createRoom(options, context) {
1066
- const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
1067
- const machine = makeStateMachine(state, context);
1068
- const room = {
1069
- id: context.roomId,
1070
- /////////////
1071
- // Core //
1072
- /////////////
1073
- getConnectionState: machine.selectors.getConnectionState,
1074
- getSelf: machine.selectors.getSelf,
1075
- // FIXME: There's a type issue here. The types of subscribe and
1076
- // machine.subscribe are incompatible somewhere.
1077
- // TODO: Figure out exactly what's wrong here!
1078
- subscribe: machine.subscribe,
1079
- unsubscribe: machine.unsubscribe,
1080
- //////////////
1081
- // Presence //
1082
- //////////////
1083
- getPresence: machine.selectors.getPresence,
1084
- updatePresence: machine.updatePresence,
1085
- getOthers: machine.selectors.getOthers,
1086
- broadcastEvent: machine.broadcastEvent,
1087
- getStorage: machine.getStorage,
1088
- batch: machine.batch,
1089
- history: {
1090
- undo: machine.undo,
1091
- redo: machine.redo,
1092
- pause: machine.pauseHistory,
1093
- resume: machine.resumeHistory,
1094
- },
1095
- // @ts-ignore
1096
- internalDevTools: {
1097
- closeWebsocket: machine.simulateSocketClose,
1098
- sendCloseEvent: machine.simulateSendCloseEvent,
1099
- },
1100
- };
1101
- return {
1102
- connect: machine.connect,
1103
- disconnect: machine.disconnect,
1104
- onNavigatorOnline: machine.onNavigatorOnline,
1105
- onVisibilityChange: machine.onVisibilityChange,
1106
- room,
1107
- };
618
+ var _a, _b;
619
+ const initialPresence = null !== (_a = options.initialPresence) && void 0 !== _a ? _a : options.defaultPresence, initialStorage = null !== (_b = options.initialStorage) && void 0 !== _b ? _b : options.defaultStorageRoot, machine = makeStateMachine(function(initialPresence, initialStorage) {
620
+ return {
621
+ connection: {
622
+ state: "closed"
623
+ },
624
+ token: null,
625
+ lastConnectionId: null,
626
+ socket: null,
627
+ listeners: {
628
+ event: [],
629
+ others: [],
630
+ "my-presence": [],
631
+ error: [],
632
+ connection: [],
633
+ storage: []
634
+ },
635
+ numberOfRetry: 0,
636
+ lastFlushTime: 0,
637
+ timeoutHandles: {
638
+ flush: null,
639
+ reconnect: 0,
640
+ pongTimeout: 0
641
+ },
642
+ buffer: {
643
+ presence: null == initialPresence ? {} : initialPresence,
644
+ messages: [],
645
+ storageOperations: []
646
+ },
647
+ intervalHandles: {
648
+ heartbeat: 0
649
+ },
650
+ me: null == initialPresence ? {} : initialPresence,
651
+ users: {},
652
+ others: makeOthers({}),
653
+ defaultStorageRoot: initialStorage,
654
+ idFactory: null,
655
+ clock: 0,
656
+ opClock: 0,
657
+ items: new Map,
658
+ root: void 0,
659
+ undoStack: [],
660
+ redoStack: [],
661
+ isHistoryPaused: !1,
662
+ pausedHistory: [],
663
+ isBatching: !1,
664
+ batch: {
665
+ ops: [],
666
+ updates: {
667
+ storageUpdates: new Map,
668
+ presence: !1,
669
+ others: []
670
+ },
671
+ reverseOps: []
672
+ },
673
+ offlineOperations: new Map
674
+ };
675
+ }("function" == typeof initialPresence ? initialPresence(context.roomId) : initialPresence, "function" == typeof initialStorage ? initialStorage(context.roomId) : initialStorage), context), room = {
676
+ id: context.roomId,
677
+ getConnectionState: machine.selectors.getConnectionState,
678
+ getSelf: machine.selectors.getSelf,
679
+ subscribe: machine.subscribe,
680
+ unsubscribe: machine.unsubscribe,
681
+ getPresence: machine.selectors.getPresence,
682
+ updatePresence: machine.updatePresence,
683
+ getOthers: machine.selectors.getOthers,
684
+ broadcastEvent: machine.broadcastEvent,
685
+ getStorage: machine.getStorage,
686
+ batch: machine.batch,
687
+ history: {
688
+ undo: machine.undo,
689
+ redo: machine.redo,
690
+ pause: machine.pauseHistory,
691
+ resume: machine.resumeHistory
692
+ },
693
+ internalDevTools: {
694
+ closeWebsocket: machine.simulateSocketClose,
695
+ sendCloseEvent: machine.simulateSendCloseEvent
696
+ }
697
+ };
698
+ return {
699
+ connect: machine.connect,
700
+ disconnect: machine.disconnect,
701
+ onNavigatorOnline: machine.onNavigatorOnline,
702
+ onVisibilityChange: machine.onVisibilityChange,
703
+ room: room
704
+ };
1108
705
  }
706
+
1109
707
  class LiveblocksError extends Error {
1110
- constructor(message, code) {
1111
- super(message);
1112
- this.code = code;
1113
- }
708
+ constructor(message, code) {
709
+ super(message), this.code = code;
710
+ }
1114
711
  }
712
+
1115
713
  function parseToken(token) {
1116
- const tokenParts = token.split(".");
1117
- if (tokenParts.length !== 3) {
1118
- throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1119
- }
1120
- const data = parseJson(atob(tokenParts[1]));
1121
- if (data !== undefined &&
1122
- isJsonObject(data) &&
1123
- typeof data.actor === "number" &&
1124
- (data.id === undefined || typeof data.id === "string")) {
1125
- return {
1126
- actor: data.actor,
1127
- id: data.id,
1128
- info: data.info,
1129
- };
1130
- }
1131
- throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
1132
- }
1133
- function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
1134
- if (typeof window === "undefined" && WebSocketPolyfill == null) {
1135
- throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
1136
- }
1137
- const ws = WebSocketPolyfill || WebSocket;
1138
- return (token) => {
1139
- return new ws(`${liveblocksServer}/?token=${token}`);
1140
- };
1141
- }
1142
- function prepareAuthEndpoint(authentication, fetchPolyfill) {
1143
- if (authentication.type === "public") {
1144
- if (typeof window === "undefined" && fetchPolyfill == null) {
1145
- throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
1146
- }
1147
- return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1148
- room,
1149
- publicApiKey: authentication.publicApiKey,
1150
- });
1151
- }
1152
- if (authentication.type === "private") {
1153
- if (typeof window === "undefined" && fetchPolyfill == null) {
1154
- 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.");
1155
- }
1156
- return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
1157
- room,
1158
- });
1159
- }
1160
- if (authentication.type === "custom") {
1161
- return authentication.callback;
1162
- }
1163
- throw new Error("Internal error. Unexpected authentication type");
714
+ const tokenParts = token.split(".");
715
+ if (3 !== tokenParts.length) throw new Error("Authentication error. Liveblocks could not parse the response of your authentication endpoint");
716
+ const data = parseJson(atob(tokenParts[1]));
717
+ if (void 0 !== data && isJsonObject(data) && "number" == typeof data.actor && (void 0 === data.id || "string" == typeof data.id)) return {
718
+ actor: data.actor,
719
+ id: data.id,
720
+ info: data.info
721
+ };
722
+ throw new Error("Authentication error. Liveblocks could not parse the response of your authentication endpoint");
1164
723
  }
724
+
1165
725
  function fetchAuthEndpoint(fetch, endpoint, body) {
1166
- return fetch(endpoint, {
1167
- method: "POST",
1168
- headers: {
1169
- "Content-Type": "application/json",
1170
- },
1171
- body: JSON.stringify(body),
1172
- })
1173
- .then((res) => {
1174
- if (!res.ok) {
1175
- throw new AuthenticationError(`Expected a status 200 but got ${res.status} when doing a POST request on "${endpoint}"`);
1176
- }
1177
- return res.json().catch((er) => {
1178
- throw new AuthenticationError(`Expected a json when doing a POST request on "${endpoint}". ${er}`);
1179
- });
1180
- })
1181
- .then((authResponse) => {
1182
- if (typeof authResponse.token !== "string") {
1183
- throw new AuthenticationError(`Expected a json with a string token when doing a POST request on "${endpoint}", but got ${JSON.stringify(authResponse)}`);
1184
- }
1185
- return authResponse;
1186
- });
726
+ return fetch(endpoint, {
727
+ method: "POST",
728
+ headers: {
729
+ "Content-Type": "application/json"
730
+ },
731
+ body: JSON.stringify(body)
732
+ }).then((res => {
733
+ if (!res.ok) throw new AuthenticationError(`Expected a status 200 but got ${res.status} when doing a POST request on "${endpoint}"`);
734
+ return res.json().catch((er => {
735
+ throw new AuthenticationError(`Expected a json when doing a POST request on "${endpoint}". ${er}`);
736
+ }));
737
+ })).then((authResponse => {
738
+ if ("string" != typeof authResponse.token) throw new AuthenticationError(`Expected a json with a string token when doing a POST request on "${endpoint}", but got ${JSON.stringify(authResponse)}`);
739
+ return authResponse;
740
+ }));
1187
741
  }
742
+
1188
743
  class AuthenticationError extends Error {
1189
- constructor(message) {
1190
- super(message);
1191
- }
744
+ constructor(message) {
745
+ super(message);
746
+ }
1192
747
  }
1193
748
 
1194
- /**
1195
- * Create a client that will be responsible to communicate with liveblocks servers.
1196
- *
1197
- * @example
1198
- * const client = createClient({
1199
- * authEndpoint: "/api/auth"
1200
- * });
1201
- *
1202
- * // It's also possible to use a function to call your authentication endpoint.
1203
- * // Useful to add additional headers or use an API wrapper (like Firebase functions)
1204
- * const client = createClient({
1205
- * authEndpoint: async (room) => {
1206
- * const response = await fetch("/api/auth", {
1207
- * method: "POST",
1208
- * headers: {
1209
- * Authentication: "token",
1210
- * "Content-Type": "application/json"
1211
- * },
1212
- * body: JSON.stringify({ room })
1213
- * });
1214
- *
1215
- * return await response.json();
1216
- * }
1217
- * });
1218
- */
1219
749
  function createClient(options) {
1220
- const clientOptions = options;
1221
- const throttleDelay = getThrottleDelayFromOptions(options);
1222
- const rooms = new Map();
1223
- function getRoom(roomId) {
1224
- const internalRoom = rooms.get(roomId);
1225
- return internalRoom ? internalRoom.room : null;
1226
- }
1227
- function enter(roomId, options = {}) {
1228
- let internalRoom = rooms.get(roomId);
1229
- if (internalRoom) {
1230
- return internalRoom.room;
1231
- }
1232
- internalRoom = createRoom({
1233
- defaultPresence: options.defaultPresence,
1234
- defaultStorageRoot: options.defaultStorageRoot,
1235
- }, {
1236
- roomId,
1237
- throttleDelay,
1238
- WebSocketPolyfill: clientOptions.WebSocketPolyfill,
1239
- fetchPolyfill: clientOptions.fetchPolyfill,
1240
- liveblocksServer: clientOptions.liveblocksServer || "wss://liveblocks.net/v5",
1241
- authentication: prepareAuthentication(clientOptions),
1242
- });
1243
- rooms.set(roomId, internalRoom);
1244
- if (!options.DO_NOT_USE_withoutConnecting) {
1245
- internalRoom.connect();
1246
- }
1247
- return internalRoom.room;
1248
- }
1249
- function leave(roomId) {
1250
- const room = rooms.get(roomId);
1251
- if (room) {
1252
- room.disconnect();
1253
- rooms.delete(roomId);
1254
- }
1255
- }
1256
- if (typeof window !== "undefined") {
1257
- // TODO: Expose a way to clear these
1258
- window.addEventListener("online", () => {
1259
- for (const [, room] of rooms) {
1260
- room.onNavigatorOnline();
1261
- }
1262
- });
1263
- }
1264
- if (typeof document !== "undefined") {
1265
- document.addEventListener("visibilitychange", () => {
1266
- for (const [, room] of rooms) {
1267
- room.onVisibilityChange(document.visibilityState);
1268
- }
1269
- });
1270
- }
1271
- return {
1272
- getRoom,
1273
- enter,
1274
- leave,
1275
- };
1276
- }
1277
- function getThrottleDelayFromOptions(options) {
1278
- if (options.throttle === undefined) {
1279
- return 100;
1280
- }
1281
- if (typeof options.throttle !== "number" ||
1282
- options.throttle < 80 ||
1283
- options.throttle > 1000) {
1284
- throw new Error("throttle should be a number between 80 and 1000.");
1285
- }
1286
- return options.throttle;
1287
- }
1288
- function prepareAuthentication(clientOptions) {
1289
- // TODO: throw descriptive errors for invalid options
1290
- if (typeof clientOptions.publicApiKey === "string") {
1291
- return {
1292
- type: "public",
1293
- publicApiKey: clientOptions.publicApiKey,
1294
- url: clientOptions.publicAuthorizeEndpoint ||
1295
- "https://liveblocks.io/api/public/authorize",
1296
- };
1297
- }
1298
- else if (typeof clientOptions.authEndpoint === "string") {
1299
- return {
1300
- type: "private",
1301
- url: clientOptions.authEndpoint,
1302
- };
1303
- }
1304
- else if (typeof clientOptions.authEndpoint === "function") {
1305
- return {
1306
- type: "custom",
1307
- callback: clientOptions.authEndpoint,
1308
- };
1309
- }
1310
- throw new Error("Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient");
750
+ const clientOptions = options, throttleDelay = function(options) {
751
+ if (void 0 === options.throttle) return 100;
752
+ if ("number" != typeof options.throttle || options.throttle < 80 || options.throttle > 1e3) throw new Error("throttle should be a number between 80 and 1000.");
753
+ return options.throttle;
754
+ }(options), rooms = new Map;
755
+ return "undefined" != typeof window && window.addEventListener("online", (() => {
756
+ for (const [, room] of rooms) room.onNavigatorOnline();
757
+ })), "undefined" != typeof document && document.addEventListener("visibilitychange", (() => {
758
+ for (const [, room] of rooms) room.onVisibilityChange(document.visibilityState);
759
+ })), {
760
+ getRoom: function(roomId) {
761
+ const internalRoom = rooms.get(roomId);
762
+ return internalRoom ? internalRoom.room : null;
763
+ },
764
+ enter: function(roomId, options = {}) {
765
+ let internalRoom = rooms.get(roomId);
766
+ return internalRoom || (deprecateIf(options.defaultPresence, "Argument `defaultPresence` will be removed in @liveblocks/client 0.18. Please use `initialPresence` instead. For more info, see https://bit.ly/3Niy5aP", "defaultPresence"),
767
+ deprecateIf(options.defaultStorageRoot, "Argument `defaultStorageRoot` will be removed in @liveblocks/client 0.18. Please use `initialStorage` instead. For more info, see https://bit.ly/3Niy5aP", "defaultStorageRoot"),
768
+ internalRoom = createRoom({
769
+ initialPresence: options.initialPresence,
770
+ initialStorage: options.initialStorage,
771
+ defaultPresence: options.defaultPresence,
772
+ defaultStorageRoot: options.defaultStorageRoot
773
+ }, {
774
+ roomId: roomId,
775
+ throttleDelay: throttleDelay,
776
+ WebSocketPolyfill: clientOptions.WebSocketPolyfill,
777
+ fetchPolyfill: clientOptions.fetchPolyfill,
778
+ liveblocksServer: clientOptions.liveblocksServer || "wss://liveblocks.net/v5",
779
+ authentication: prepareAuthentication(clientOptions)
780
+ }), rooms.set(roomId, internalRoom), options.DO_NOT_USE_withoutConnecting || internalRoom.connect()),
781
+ internalRoom.room;
782
+ },
783
+ leave: function(roomId) {
784
+ const room = rooms.get(roomId);
785
+ room && (room.disconnect(), rooms.delete(roomId));
786
+ }
787
+ };
1311
788
  }
1312
789
 
1313
- function lsonObjectToJson(obj) {
1314
- const result = {};
1315
- for (const key in obj) {
1316
- const val = obj[key];
1317
- if (val !== undefined) {
1318
- result[key] = lsonToJson(val);
1319
- }
1320
- }
1321
- return result;
1322
- }
1323
- function liveObjectToJson(liveObject) {
1324
- return lsonObjectToJson(liveObject.toObject());
1325
- }
1326
- function liveMapToJson(map) {
1327
- const result = {};
1328
- for (const [key, value] of map.entries()) {
1329
- result[key] = lsonToJson(value);
1330
- }
1331
- return result;
1332
- }
1333
- function lsonListToJson(value) {
1334
- return value.map(lsonToJson);
1335
- }
1336
- function liveListToJson(value) {
1337
- return lsonListToJson(value.toArray());
1338
- }
1339
- function lsonToJson(value) {
1340
- // ^^^^^^^^^^^^
1341
- // FIXME: Remove me later. This requires the
1342
- // addition of a concrete LiveStructure type first.
1343
- // Check for LiveStructure datastructures first
1344
- if (value instanceof LiveObject) {
1345
- return liveObjectToJson(value);
1346
- }
1347
- else if (value instanceof LiveList) {
1348
- return liveListToJson(value);
1349
- }
1350
- else if (value instanceof LiveMap) {
1351
- return liveMapToJson(value);
1352
- }
1353
- else if (value instanceof LiveRegister) {
1354
- return value.data;
1355
- }
1356
- else if (value instanceof AbstractCrdt) {
1357
- // This code path should never be taken
1358
- throw new Error("Unhandled subclass of AbstractCrdt encountered");
1359
- }
1360
- // Then for composite Lson values
1361
- if (Array.isArray(value)) {
1362
- return lsonListToJson(value);
1363
- }
1364
- else if (isPlainObject(value)) {
1365
- return lsonObjectToJson(value);
1366
- }
1367
- // Finally, if value is an LsonScalar, then it's also a valid JsonScalar
1368
- return value;
1369
- }
1370
- function isPlainObject(obj) {
1371
- return (obj !== null && Object.prototype.toString.call(obj) === "[object Object]");
1372
- }
1373
- function anyToCrdt(obj) {
1374
- // ^^^ AbstractCrdt?
1375
- if (obj == null) {
1376
- return obj;
1377
- }
1378
- if (Array.isArray(obj)) {
1379
- return new LiveList(obj.map(anyToCrdt));
1380
- }
1381
- if (isPlainObject(obj)) {
1382
- const init = {};
1383
- for (const key in obj) {
1384
- init[key] = anyToCrdt(obj[key]);
1385
- }
1386
- return new LiveObject(init);
1387
- }
1388
- return obj;
1389
- }
1390
- function patchLiveList(liveList, prev, next) {
1391
- let i = 0;
1392
- let prevEnd = prev.length - 1;
1393
- let nextEnd = next.length - 1;
1394
- let prevNode = prev[0];
1395
- let nextNode = next[0];
1396
- /**
1397
- * For A,B,C => A,B,C,D
1398
- * i = 3, prevEnd = 2, nextEnd = 3
1399
- *
1400
- * For A,B,C => B,C
1401
- * i = 2, prevEnd = 2, nextEnd = 1
1402
- *
1403
- * For B,C => A,B,C
1404
- * i = 0, pre
1405
- */
1406
- outer: {
1407
- while (prevNode === nextNode) {
1408
- ++i;
1409
- if (i > prevEnd || i > nextEnd) {
1410
- break outer;
1411
- }
1412
- prevNode = prev[i];
1413
- nextNode = next[i];
1414
- }
1415
- prevNode = prev[prevEnd];
1416
- nextNode = next[nextEnd];
1417
- while (prevNode === nextNode) {
1418
- prevEnd--;
1419
- nextEnd--;
1420
- if (i > prevEnd || i > nextEnd) {
1421
- break outer;
1422
- }
1423
- prevNode = prev[prevEnd];
1424
- nextNode = next[nextEnd];
1425
- }
1426
- }
1427
- if (i > prevEnd) {
1428
- if (i <= nextEnd) {
1429
- while (i <= nextEnd) {
1430
- liveList.insert(anyToCrdt(next[i]), i);
1431
- i++;
1432
- }
1433
- }
1434
- }
1435
- else if (i > nextEnd) {
1436
- let localI = i;
1437
- while (localI <= prevEnd) {
1438
- liveList.delete(i);
1439
- localI++;
1440
- }
1441
- }
1442
- else {
1443
- while (i <= prevEnd && i <= nextEnd) {
1444
- prevNode = prev[i];
1445
- nextNode = next[i];
1446
- const liveListNode = liveList.get(i);
1447
- if (liveListNode instanceof LiveObject &&
1448
- isPlainObject(prevNode) &&
1449
- isPlainObject(nextNode)) {
1450
- patchLiveObject(liveListNode, prevNode, nextNode);
1451
- }
1452
- else {
1453
- liveList.set(i, anyToCrdt(nextNode));
1454
- }
1455
- i++;
1456
- }
1457
- while (i <= nextEnd) {
1458
- liveList.insert(anyToCrdt(next[i]), i);
1459
- i++;
1460
- }
1461
- let localI = i;
1462
- while (localI <= prevEnd) {
1463
- liveList.delete(i);
1464
- localI++;
1465
- }
1466
- }
1467
- }
1468
- function patchLiveObjectKey(liveObject, key, prev, next) {
1469
- if (process.env.NODE_ENV !== "production") {
1470
- const nonSerializableValue = findNonSerializableValue(next);
1471
- if (nonSerializableValue) {
1472
- console.error(`New state path: '${nonSerializableValue.path}' value: '${nonSerializableValue.value}' is not serializable.\nOnly serializable value can be synced with Liveblocks.`);
1473
- return;
1474
- }
1475
- }
1476
- const value = liveObject.get(key);
1477
- if (next === undefined) {
1478
- liveObject.delete(key);
1479
- }
1480
- else if (value === undefined) {
1481
- liveObject.set(key, anyToCrdt(next));
1482
- }
1483
- else if (prev === next) {
1484
- return;
1485
- }
1486
- else if (value instanceof LiveList &&
1487
- Array.isArray(prev) &&
1488
- Array.isArray(next)) {
1489
- patchLiveList(value, prev, next);
1490
- }
1491
- else if (value instanceof LiveObject &&
1492
- isPlainObject(prev) &&
1493
- isPlainObject(next)) {
1494
- patchLiveObject(value, prev, next);
1495
- }
1496
- else {
1497
- liveObject.set(key, anyToCrdt(next));
1498
- }
1499
- }
1500
- function patchLiveObject(root, prev, next) {
1501
- const updates = {};
1502
- for (const key in next) {
1503
- patchLiveObjectKey(root, key, prev[key], next[key]);
1504
- }
1505
- for (const key in prev) {
1506
- if (next[key] === undefined) {
1507
- root.delete(key);
1508
- }
1509
- }
1510
- if (Object.keys(updates).length > 0) {
1511
- root.update(updates);
1512
- }
1513
- }
1514
- function getParentsPath(node) {
1515
- const path = [];
1516
- while (node._parentKey != null && node._parent != null) {
1517
- if (node._parent instanceof LiveList) {
1518
- path.push(node._parent._indexOfPosition(node._parentKey));
1519
- }
1520
- else {
1521
- path.push(node._parentKey);
1522
- }
1523
- node = node._parent;
1524
- }
1525
- return path;
1526
- }
1527
- function patchImmutableObject(state, updates) {
1528
- return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
1529
- }
1530
- function patchImmutableObjectWithUpdate(state, update) {
1531
- const path = getParentsPath(update.node);
1532
- return patchImmutableNode(state, path, update);
1533
- }
1534
- function patchImmutableNode(state, path, update) {
1535
- var _a, _b, _c, _d;
1536
- const pathItem = path.pop();
1537
- if (pathItem === undefined) {
1538
- switch (update.type) {
1539
- case "LiveObject": {
1540
- if (typeof state !== "object") {
1541
- throw new Error("Internal: received update on LiveObject but state was not an object");
1542
- }
1543
- const newState = Object.assign({}, state);
1544
- for (const key in update.updates) {
1545
- if (((_a = update.updates[key]) === null || _a === void 0 ? void 0 : _a.type) === "update") {
1546
- const val = update.node.get(key);
1547
- if (val !== undefined) {
1548
- newState[key] = lsonToJson(val);
1549
- }
1550
- }
1551
- else if (((_b = update.updates[key]) === null || _b === void 0 ? void 0 : _b.type) === "delete") {
1552
- delete newState[key];
1553
- }
1554
- }
1555
- return newState;
1556
- }
1557
- case "LiveList": {
1558
- if (Array.isArray(state) === false) {
1559
- throw new Error("Internal: received update on LiveList but state was not an array");
1560
- }
1561
- let newState = state.map((x) => x);
1562
- for (const listUpdate of update.updates) {
1563
- if (listUpdate.type === "set") {
1564
- newState = newState.map((item, index) => index === listUpdate.index ? listUpdate.item : item);
1565
- }
1566
- else if (listUpdate.type === "insert") {
1567
- if (listUpdate.index === newState.length) {
1568
- newState.push(lsonToJson(listUpdate.item));
1569
- }
1570
- else {
1571
- newState = [
1572
- ...newState.slice(0, listUpdate.index),
1573
- lsonToJson(listUpdate.item),
1574
- ...newState.slice(listUpdate.index),
1575
- ];
1576
- }
1577
- }
1578
- else if (listUpdate.type === "delete") {
1579
- newState.splice(listUpdate.index, 1);
1580
- }
1581
- else if (listUpdate.type === "move") {
1582
- if (listUpdate.previousIndex > listUpdate.index) {
1583
- newState = [
1584
- ...newState.slice(0, listUpdate.index),
1585
- lsonToJson(listUpdate.item),
1586
- ...newState.slice(listUpdate.index, listUpdate.previousIndex),
1587
- ...newState.slice(listUpdate.previousIndex + 1),
1588
- ];
1589
- }
1590
- else {
1591
- newState = [
1592
- ...newState.slice(0, listUpdate.previousIndex),
1593
- ...newState.slice(listUpdate.previousIndex + 1, listUpdate.index + 1),
1594
- lsonToJson(listUpdate.item),
1595
- ...newState.slice(listUpdate.index + 1),
1596
- ];
1597
- }
1598
- }
1599
- }
1600
- return newState;
1601
- }
1602
- case "LiveMap": {
1603
- if (typeof state !== "object") {
1604
- throw new Error("Internal: received update on LiveMap but state was not an object");
1605
- }
1606
- const newState = Object.assign({}, state);
1607
- for (const key in update.updates) {
1608
- if (((_c = update.updates[key]) === null || _c === void 0 ? void 0 : _c.type) === "update") {
1609
- newState[key] = lsonToJson(update.node.get(key));
1610
- }
1611
- else if (((_d = update.updates[key]) === null || _d === void 0 ? void 0 : _d.type) === "delete") {
1612
- delete newState[key];
1613
- }
1614
- }
1615
- return newState;
1616
- }
1617
- }
1618
- }
1619
- if (Array.isArray(state)) {
1620
- const newArray = [...state];
1621
- newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
1622
- return newArray;
1623
- }
1624
- else {
1625
- return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
1626
- }
790
+ function prepareAuthentication(clientOptions) {
791
+ if ("string" == typeof clientOptions.publicApiKey) return {
792
+ type: "public",
793
+ publicApiKey: clientOptions.publicApiKey,
794
+ url: clientOptions.publicAuthorizeEndpoint || "https://liveblocks.io/api/public/authorize"
795
+ };
796
+ if ("string" == typeof clientOptions.authEndpoint) return {
797
+ type: "private",
798
+ url: clientOptions.authEndpoint
799
+ };
800
+ if ("function" == typeof clientOptions.authEndpoint) return {
801
+ type: "custom",
802
+ callback: clientOptions.authEndpoint
803
+ };
804
+ throw new Error("Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient");
1627
805
  }
1628
806
 
1629
- /**
1630
- * @internal
1631
- */
1632
- const internals = {
1633
- liveObjectToJson,
1634
- lsonToJson,
1635
- patchLiveList,
1636
- patchImmutableObject,
1637
- patchLiveObject,
1638
- patchLiveObjectKey,
1639
- };
1640
-
1641
- export { createClient, internals };
807
+ export { createClient };