@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.js +826 -1893
- package/index.mjs +779 -1613
- package/internal.d.ts +29 -44
- package/internal.js +171 -31
- package/internal.mjs +148 -1
- package/package.json +20 -7
- package/shared.d.ts +39 -9
- package/shared.js +1275 -2386
- package/shared.mjs +1183 -1887
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
708
|
+
constructor(message, code) {
|
|
709
|
+
super(message), this.code = code;
|
|
710
|
+
}
|
|
1114
711
|
}
|
|
712
|
+
|
|
1115
713
|
function parseToken(token) {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
-
|
|
1190
|
-
|
|
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
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
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
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
}
|
|
1323
|
-
function
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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 };
|