@liveblocks/client 0.16.17 → 0.17.0-test1
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.d.ts +26 -7
- package/index.js +1230 -830
- package/index.mjs +1030 -782
- package/internal.d.ts +269 -279
- package/internal.js +314 -205
- package/internal.mjs +266 -171
- package/package.json +15 -10
- package/shared.d.ts +973 -628
- package/shared.js +2566 -1326
- package/shared.mjs +1987 -1205
package/index.mjs
CHANGED
|
@@ -1,804 +1,1052 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import {
|
|
2
|
+
_ as __rest,
|
|
3
|
+
i as isLiveNode,
|
|
4
|
+
r as remove,
|
|
5
|
+
S as ServerMsgCode,
|
|
6
|
+
m as mergeStorageUpdates,
|
|
7
|
+
W as WebsocketCloseCodes,
|
|
8
|
+
C as ClientMsgCode,
|
|
9
|
+
n as nn,
|
|
10
|
+
a as isPlainObject,
|
|
11
|
+
p as parseRoomAuthToken,
|
|
12
|
+
b as isTokenExpired,
|
|
13
|
+
c as isSameNodeOrChildOf,
|
|
14
|
+
L as LiveObject,
|
|
15
|
+
g as getTreesDiffOperations,
|
|
16
|
+
O as OpSource,
|
|
17
|
+
d as OpCode,
|
|
18
|
+
e as isLiveList,
|
|
19
|
+
t as tryParseJson,
|
|
20
|
+
f as isJsonArray,
|
|
21
|
+
h as compact,
|
|
22
|
+
j as isRootCrdt,
|
|
23
|
+
k as isJsonObject,
|
|
24
|
+
l as errorIf,
|
|
25
|
+
} from "./shared.mjs";
|
|
26
|
+
export { o as LiveList, q as LiveMap, L as LiveObject } from "./shared.mjs";
|
|
27
|
+
const BACKOFF_RETRY_DELAYS = [250, 500, 1e3, 2e3, 4e3, 8e3, 1e4],
|
|
28
|
+
BACKOFF_RETRY_DELAYS_SLOW = [2e3, 3e4, 6e4, 3e5];
|
|
11
29
|
function makeOthers(userMap) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return users.length;
|
|
24
|
-
},
|
|
25
|
-
[Symbol.iterator]: () => users[Symbol.iterator](),
|
|
26
|
-
map: callback => users.map(callback),
|
|
27
|
-
toArray: () => users
|
|
28
|
-
};
|
|
30
|
+
const users = Object.values(userMap).map((user) =>
|
|
31
|
+
__rest(user, ["_hasReceivedInitialPresence"])
|
|
32
|
+
);
|
|
33
|
+
return {
|
|
34
|
+
get count() {
|
|
35
|
+
return users.length;
|
|
36
|
+
},
|
|
37
|
+
[Symbol.iterator]: () => users[Symbol.iterator](),
|
|
38
|
+
map: (callback) => users.map(callback),
|
|
39
|
+
toArray: () => users,
|
|
40
|
+
};
|
|
29
41
|
}
|
|
30
|
-
|
|
31
42
|
function makeStateMachine(state, context, mockedEffects) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
43
|
+
const effects = mockedEffects || {
|
|
44
|
+
authenticate(auth, createWebSocket) {
|
|
45
|
+
const rawToken = state.token,
|
|
46
|
+
parsedToken = null !== rawToken && parseRoomAuthToken(rawToken);
|
|
47
|
+
if (!parsedToken || isTokenExpired(parsedToken))
|
|
48
|
+
return auth(context.roomId)
|
|
49
|
+
.then(({ token: token }) => {
|
|
50
|
+
if ("authenticating" !== state.connection.state) return;
|
|
51
|
+
authenticationSuccess(
|
|
52
|
+
parseRoomAuthToken(token),
|
|
53
|
+
createWebSocket(token)
|
|
54
|
+
),
|
|
55
|
+
(state.token = token);
|
|
56
|
+
})
|
|
57
|
+
.catch((er) =>
|
|
58
|
+
(function (error) {
|
|
59
|
+
"production" !== process.env.NODE_ENV &&
|
|
60
|
+
console.error("Call to authentication endpoint failed", error);
|
|
61
|
+
(state.token = null),
|
|
62
|
+
updateConnection({ state: "unavailable" }),
|
|
63
|
+
state.numberOfRetry++,
|
|
64
|
+
(state.timeoutHandles.reconnect = effects.scheduleReconnect(
|
|
65
|
+
getRetryDelay()
|
|
66
|
+
));
|
|
67
|
+
})(er instanceof Error ? er : new Error(String(er)))
|
|
68
|
+
);
|
|
69
|
+
authenticationSuccess(parsedToken, createWebSocket(rawToken));
|
|
70
|
+
},
|
|
71
|
+
send(messageOrMessages) {
|
|
72
|
+
if (null == state.socket)
|
|
73
|
+
throw new Error("Can't send message if socket is null");
|
|
74
|
+
state.socket.send(JSON.stringify(messageOrMessages));
|
|
75
|
+
},
|
|
76
|
+
delayFlush: (delay) => setTimeout(tryFlushing, delay),
|
|
77
|
+
startHeartbeatInterval: () => setInterval(heartbeat, 3e4),
|
|
78
|
+
schedulePongTimeout: () => setTimeout(pongTimeout, 2e3),
|
|
79
|
+
scheduleReconnect: (delay) => setTimeout(connect, delay),
|
|
80
|
+
};
|
|
81
|
+
function genericSubscribe(callback) {
|
|
82
|
+
return (
|
|
83
|
+
state.listeners.storage.push(callback),
|
|
84
|
+
() => remove(state.listeners.storage, callback)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function createOrUpdateRootFromMessage(message) {
|
|
88
|
+
if (0 === message.items.length)
|
|
89
|
+
throw new Error("Internal error: cannot load storage without items");
|
|
90
|
+
state.root
|
|
91
|
+
? (function (items) {
|
|
92
|
+
if (!state.root) return;
|
|
93
|
+
const currentItems = new Map();
|
|
94
|
+
state.items.forEach((liveCrdt, id) => {
|
|
95
|
+
currentItems.set(id, liveCrdt._toSerializedCrdt());
|
|
96
|
+
});
|
|
97
|
+
const ops = getTreesDiffOperations(currentItems, new Map(items));
|
|
98
|
+
notify(apply(ops, !1).updates);
|
|
99
|
+
})(message.items)
|
|
100
|
+
: (state.root = (function (items) {
|
|
101
|
+
const [root, parentToChildren] = (function (items) {
|
|
102
|
+
const parentToChildren = new Map();
|
|
103
|
+
let root = null;
|
|
104
|
+
for (const [id, crdt] of items)
|
|
105
|
+
if (isRootCrdt(crdt)) root = [id, crdt];
|
|
106
|
+
else {
|
|
107
|
+
const tuple = [id, crdt],
|
|
108
|
+
children = parentToChildren.get(crdt.parentId);
|
|
109
|
+
null != children
|
|
110
|
+
? children.push(tuple)
|
|
111
|
+
: parentToChildren.set(crdt.parentId, [tuple]);
|
|
112
|
+
}
|
|
113
|
+
if (null == root) throw new Error("Root can't be null");
|
|
114
|
+
return [root, parentToChildren];
|
|
115
|
+
})(items);
|
|
116
|
+
return LiveObject._deserialize(root, parentToChildren, {
|
|
117
|
+
getItem: getItem,
|
|
118
|
+
addItem: addItem,
|
|
119
|
+
deleteItem: deleteItem,
|
|
120
|
+
generateId: generateId,
|
|
121
|
+
generateOpId: generateOpId,
|
|
122
|
+
dispatch: storageDispatch,
|
|
123
|
+
roomId: context.roomId,
|
|
124
|
+
});
|
|
125
|
+
})(message.items));
|
|
126
|
+
for (const key in state.defaultStorageRoot)
|
|
127
|
+
null == state.root.get(key) &&
|
|
128
|
+
state.root.set(key, state.defaultStorageRoot[key]);
|
|
129
|
+
}
|
|
130
|
+
function addItem(id, liveItem) {
|
|
131
|
+
state.items.set(id, liveItem);
|
|
132
|
+
}
|
|
133
|
+
function deleteItem(id) {
|
|
134
|
+
state.items.delete(id);
|
|
135
|
+
}
|
|
136
|
+
function getItem(id) {
|
|
137
|
+
return state.items.get(id);
|
|
138
|
+
}
|
|
139
|
+
function addToUndoStack(historyItem) {
|
|
140
|
+
state.undoStack.length >= 50 && state.undoStack.shift(),
|
|
141
|
+
state.isHistoryPaused
|
|
142
|
+
? state.pausedHistory.unshift(...historyItem)
|
|
143
|
+
: state.undoStack.push(historyItem);
|
|
144
|
+
}
|
|
145
|
+
function storageDispatch(ops, reverse, storageUpdates) {
|
|
146
|
+
state.isBatching
|
|
147
|
+
? (state.batch.ops.push(...ops),
|
|
148
|
+
storageUpdates.forEach((value, key) => {
|
|
149
|
+
state.batch.updates.storageUpdates.set(
|
|
150
|
+
key,
|
|
151
|
+
mergeStorageUpdates(
|
|
152
|
+
state.batch.updates.storageUpdates.get(key),
|
|
153
|
+
value
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
}),
|
|
157
|
+
state.batch.reverseOps.push(...reverse))
|
|
158
|
+
: (addToUndoStack(reverse),
|
|
159
|
+
(state.redoStack = []),
|
|
160
|
+
dispatch(ops),
|
|
161
|
+
notify({ storageUpdates: storageUpdates }));
|
|
162
|
+
}
|
|
163
|
+
function notify({
|
|
164
|
+
storageUpdates: storageUpdates = new Map(),
|
|
165
|
+
presence: presence = !1,
|
|
166
|
+
others: otherEvents = [],
|
|
167
|
+
}) {
|
|
168
|
+
if (otherEvents.length > 0) {
|
|
169
|
+
state.others = makeOthers(state.users);
|
|
170
|
+
for (const event of otherEvents)
|
|
171
|
+
for (const listener of state.listeners.others)
|
|
172
|
+
listener(state.others, event);
|
|
75
173
|
}
|
|
76
|
-
if (
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
if (presence)
|
|
175
|
+
for (const listener of state.listeners["my-presence"]) listener(state.me);
|
|
176
|
+
if (storageUpdates.size > 0)
|
|
177
|
+
for (const subscriber of state.listeners.storage)
|
|
178
|
+
subscriber(Array.from(storageUpdates.values()));
|
|
179
|
+
}
|
|
180
|
+
function getConnectionId() {
|
|
181
|
+
if (
|
|
182
|
+
"open" === state.connection.state ||
|
|
183
|
+
"connecting" === state.connection.state
|
|
184
|
+
)
|
|
185
|
+
return state.connection.id;
|
|
186
|
+
if (null !== state.lastConnectionId) return state.lastConnectionId;
|
|
187
|
+
throw new Error(
|
|
188
|
+
"Internal. Tried to get connection id but connection was never open"
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function generateId() {
|
|
192
|
+
return `${getConnectionId()}:${state.clock++}`;
|
|
193
|
+
}
|
|
194
|
+
function generateOpId() {
|
|
195
|
+
return `${getConnectionId()}:${state.opClock++}`;
|
|
196
|
+
}
|
|
197
|
+
function apply(item, isLocal) {
|
|
198
|
+
const result = {
|
|
199
|
+
reverse: [],
|
|
200
|
+
updates: { storageUpdates: new Map(), presence: !1 },
|
|
201
|
+
},
|
|
202
|
+
createdNodeIds = new Set();
|
|
203
|
+
for (const op of item)
|
|
204
|
+
if ("presence" === op.type) {
|
|
205
|
+
const reverse = { type: "presence", data: {} };
|
|
206
|
+
for (const key in op.data) reverse.data[key] = state.me[key];
|
|
207
|
+
if (
|
|
208
|
+
((state.me = Object.assign(Object.assign({}, state.me), op.data)),
|
|
209
|
+
null == state.buffer.presence)
|
|
210
|
+
)
|
|
211
|
+
state.buffer.presence = op.data;
|
|
212
|
+
else
|
|
213
|
+
for (const key in op.data) state.buffer.presence[key] = op.data[key];
|
|
214
|
+
result.reverse.unshift(reverse), (result.updates.presence = !0);
|
|
215
|
+
} else {
|
|
216
|
+
let source;
|
|
217
|
+
if ((op.opId || (op.opId = generateOpId()), isLocal))
|
|
218
|
+
source = OpSource.UNDOREDO_RECONNECT;
|
|
219
|
+
else {
|
|
220
|
+
source = state.offlineOperations.delete(nn(op.opId))
|
|
221
|
+
? OpSource.ACK
|
|
222
|
+
: OpSource.REMOTE;
|
|
223
|
+
}
|
|
224
|
+
const applyOpResult = applyOp(op, source);
|
|
225
|
+
if (applyOpResult.modified) {
|
|
226
|
+
const parentId =
|
|
227
|
+
"HasParent" === applyOpResult.modified.node.parent.type
|
|
228
|
+
? nn(
|
|
229
|
+
applyOpResult.modified.node.parent.node._id,
|
|
230
|
+
"Expected parent node to have an ID"
|
|
231
|
+
)
|
|
232
|
+
: void 0;
|
|
233
|
+
(parentId && createdNodeIds.has(parentId)) ||
|
|
234
|
+
(result.updates.storageUpdates.set(
|
|
235
|
+
nn(applyOpResult.modified.node._id),
|
|
236
|
+
mergeStorageUpdates(
|
|
237
|
+
result.updates.storageUpdates.get(
|
|
238
|
+
nn(applyOpResult.modified.node._id)
|
|
239
|
+
),
|
|
240
|
+
applyOpResult.modified
|
|
241
|
+
)
|
|
242
|
+
),
|
|
243
|
+
result.reverse.unshift(...applyOpResult.reverse)),
|
|
244
|
+
(op.type !== OpCode.CREATE_LIST &&
|
|
245
|
+
op.type !== OpCode.CREATE_MAP &&
|
|
246
|
+
op.type !== OpCode.CREATE_OBJECT) ||
|
|
247
|
+
createdNodeIds.add(nn(applyOpResult.modified.node._id));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
function applyOp(op, source) {
|
|
253
|
+
switch (op.type) {
|
|
254
|
+
case OpCode.DELETE_OBJECT_KEY:
|
|
255
|
+
case OpCode.UPDATE_OBJECT:
|
|
256
|
+
case OpCode.DELETE_CRDT: {
|
|
257
|
+
const item = state.items.get(op.id);
|
|
258
|
+
return null == item
|
|
259
|
+
? { modified: !1 }
|
|
260
|
+
: item._apply(op, source === OpSource.UNDOREDO_RECONNECT);
|
|
261
|
+
}
|
|
262
|
+
case OpCode.SET_PARENT_KEY: {
|
|
263
|
+
const item = state.items.get(op.id);
|
|
264
|
+
return null == item
|
|
265
|
+
? { modified: !1 }
|
|
266
|
+
: "HasParent" === item.parent.type && isLiveList(item.parent.node)
|
|
267
|
+
? item.parent.node._setChildKey(op.parentKey, item, source)
|
|
268
|
+
: { modified: !1 };
|
|
269
|
+
}
|
|
270
|
+
case OpCode.CREATE_OBJECT:
|
|
271
|
+
case OpCode.CREATE_LIST:
|
|
272
|
+
case OpCode.CREATE_MAP:
|
|
273
|
+
case OpCode.CREATE_REGISTER: {
|
|
274
|
+
if (void 0 === op.parentId) return { modified: !1 };
|
|
275
|
+
const parent = state.items.get(op.parentId);
|
|
276
|
+
return null == parent
|
|
277
|
+
? { modified: !1 }
|
|
278
|
+
: parent._attachChild(op, source);
|
|
279
|
+
}
|
|
181
280
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
281
|
+
}
|
|
282
|
+
function connect() {
|
|
283
|
+
if (
|
|
284
|
+
"closed" !== state.connection.state &&
|
|
285
|
+
"unavailable" !== state.connection.state
|
|
286
|
+
)
|
|
287
|
+
return null;
|
|
288
|
+
const auth = (function (authentication, fetchPolyfill) {
|
|
289
|
+
if ("public" === authentication.type) {
|
|
290
|
+
if ("undefined" == typeof window && null == fetchPolyfill)
|
|
291
|
+
throw new Error(
|
|
292
|
+
"To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill."
|
|
293
|
+
);
|
|
294
|
+
return (room) =>
|
|
295
|
+
fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
296
|
+
room: room,
|
|
297
|
+
publicApiKey: authentication.publicApiKey,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if ("private" === authentication.type) {
|
|
301
|
+
if ("undefined" == typeof window && null == fetchPolyfill)
|
|
302
|
+
throw new Error(
|
|
303
|
+
"To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill."
|
|
304
|
+
);
|
|
305
|
+
return (room) =>
|
|
306
|
+
fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
307
|
+
room: room,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if ("custom" === authentication.type) return authentication.callback;
|
|
311
|
+
throw new Error("Internal error. Unexpected authentication type");
|
|
312
|
+
})(context.authentication, context.fetchPolyfill),
|
|
313
|
+
createWebSocket = (function (liveblocksServer, WebSocketPolyfill) {
|
|
314
|
+
if ("undefined" == typeof window && null == WebSocketPolyfill)
|
|
315
|
+
throw new Error(
|
|
316
|
+
"To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill."
|
|
317
|
+
);
|
|
318
|
+
const ws = WebSocketPolyfill || WebSocket;
|
|
319
|
+
return (token) => new ws(`${liveblocksServer}/?token=${token}`);
|
|
320
|
+
})(context.liveblocksServer, context.WebSocketPolyfill);
|
|
321
|
+
updateConnection({ state: "authenticating" }),
|
|
322
|
+
effects.authenticate(auth, createWebSocket);
|
|
323
|
+
}
|
|
324
|
+
function authenticationSuccess(token, socket) {
|
|
325
|
+
socket.addEventListener("message", onMessage),
|
|
326
|
+
socket.addEventListener("open", onOpen),
|
|
327
|
+
socket.addEventListener("close", onClose),
|
|
328
|
+
socket.addEventListener("error", onError),
|
|
329
|
+
updateConnection({
|
|
330
|
+
state: "connecting",
|
|
331
|
+
id: token.actor,
|
|
332
|
+
userInfo: token.info,
|
|
333
|
+
userId: token.id,
|
|
334
|
+
}),
|
|
335
|
+
(state.idFactory = (function (connectionId) {
|
|
336
|
+
let count = 0;
|
|
337
|
+
return () => `${connectionId}:${count++}`;
|
|
338
|
+
})(token.actor)),
|
|
339
|
+
(state.socket = socket);
|
|
340
|
+
}
|
|
341
|
+
function onUpdatePresenceMessage(message) {
|
|
342
|
+
const user = state.users[message.actor];
|
|
343
|
+
if (
|
|
344
|
+
void 0 !== message.targetActor ||
|
|
345
|
+
null == user ||
|
|
346
|
+
user._hasReceivedInitialPresence
|
|
347
|
+
)
|
|
348
|
+
return (
|
|
349
|
+
(state.users[message.actor] =
|
|
350
|
+
null == user
|
|
351
|
+
? {
|
|
352
|
+
connectionId: message.actor,
|
|
353
|
+
presence: message.data,
|
|
354
|
+
_hasReceivedInitialPresence: !0,
|
|
355
|
+
}
|
|
356
|
+
: {
|
|
357
|
+
id: user.id,
|
|
358
|
+
info: user.info,
|
|
359
|
+
connectionId: message.actor,
|
|
360
|
+
presence: Object.assign(
|
|
361
|
+
Object.assign({}, user.presence),
|
|
362
|
+
message.data
|
|
363
|
+
),
|
|
364
|
+
_hasReceivedInitialPresence: !0,
|
|
365
|
+
}),
|
|
366
|
+
{
|
|
367
|
+
type: "update",
|
|
368
|
+
updates: message.data,
|
|
369
|
+
user: state.users[message.actor],
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
function onUserLeftMessage(message) {
|
|
374
|
+
const userLeftMessage = message,
|
|
375
|
+
user = state.users[userLeftMessage.actor];
|
|
376
|
+
return user
|
|
377
|
+
? (delete state.users[userLeftMessage.actor],
|
|
378
|
+
{ type: "leave", user: user })
|
|
379
|
+
: null;
|
|
380
|
+
}
|
|
381
|
+
function onRoomStateMessage(message) {
|
|
382
|
+
const newUsers = {};
|
|
383
|
+
for (const key in message.users) {
|
|
384
|
+
const connectionId = Number.parseInt(key),
|
|
385
|
+
user = message.users[key];
|
|
386
|
+
newUsers[connectionId] = {
|
|
387
|
+
connectionId: connectionId,
|
|
388
|
+
info: user.info,
|
|
389
|
+
id: user.id,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return (state.users = newUsers), { type: "reset" };
|
|
393
|
+
}
|
|
394
|
+
function onEvent(message) {
|
|
395
|
+
for (const listener of state.listeners.event)
|
|
396
|
+
listener({ connectionId: message.actor, event: message.event });
|
|
397
|
+
}
|
|
398
|
+
function onUserJoinedMessage(message) {
|
|
399
|
+
return (
|
|
400
|
+
(state.users[message.actor] = {
|
|
401
|
+
connectionId: message.actor,
|
|
402
|
+
info: message.info,
|
|
403
|
+
id: message.id,
|
|
404
|
+
_hasReceivedInitialPresence: !0,
|
|
405
|
+
}),
|
|
406
|
+
state.me &&
|
|
407
|
+
(state.buffer.messages.push({
|
|
408
|
+
type: ClientMsgCode.UPDATE_PRESENCE,
|
|
409
|
+
data: state.me,
|
|
410
|
+
targetActor: message.actor,
|
|
411
|
+
}),
|
|
412
|
+
tryFlushing()),
|
|
413
|
+
{ type: "enter", user: state.users[message.actor] }
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
function parseServerMessage(data) {
|
|
417
|
+
return isJsonObject(data) ? data : null;
|
|
418
|
+
}
|
|
419
|
+
function onMessage(event) {
|
|
420
|
+
if ("pong" === event.data)
|
|
421
|
+
return void clearTimeout(state.timeoutHandles.pongTimeout);
|
|
422
|
+
const messages = (function (text) {
|
|
423
|
+
const data = tryParseJson(text);
|
|
424
|
+
return void 0 === data
|
|
425
|
+
? null
|
|
426
|
+
: isJsonArray(data)
|
|
427
|
+
? compact(data.map((item) => parseServerMessage(item)))
|
|
428
|
+
: compact([parseServerMessage(data)]);
|
|
429
|
+
})(event.data);
|
|
430
|
+
if (null === messages || 0 === messages.length) return;
|
|
431
|
+
const updates = { storageUpdates: new Map(), others: [] };
|
|
432
|
+
for (const message of messages)
|
|
433
|
+
switch (message.type) {
|
|
434
|
+
case ServerMsgCode.USER_JOINED:
|
|
435
|
+
updates.others.push(onUserJoinedMessage(message));
|
|
436
|
+
break;
|
|
437
|
+
case ServerMsgCode.UPDATE_PRESENCE: {
|
|
438
|
+
const othersPresenceUpdate = onUpdatePresenceMessage(message);
|
|
439
|
+
othersPresenceUpdate && updates.others.push(othersPresenceUpdate);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
case ServerMsgCode.BROADCASTED_EVENT:
|
|
443
|
+
onEvent(message);
|
|
444
|
+
break;
|
|
445
|
+
case ServerMsgCode.USER_LEFT: {
|
|
446
|
+
const event = onUserLeftMessage(message);
|
|
447
|
+
event && updates.others.push(event);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case ServerMsgCode.ROOM_STATE:
|
|
451
|
+
updates.others.push(onRoomStateMessage(message));
|
|
452
|
+
break;
|
|
453
|
+
case ServerMsgCode.INITIAL_STORAGE_STATE: {
|
|
454
|
+
const offlineOps = new Map(state.offlineOperations);
|
|
455
|
+
createOrUpdateRootFromMessage(message),
|
|
456
|
+
applyAndSendOfflineOps(offlineOps),
|
|
457
|
+
null == _getInitialStateResolver || _getInitialStateResolver();
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
case ServerMsgCode.UPDATE_STORAGE:
|
|
461
|
+
apply(message.ops, !1).updates.storageUpdates.forEach(
|
|
462
|
+
(value, key) => {
|
|
463
|
+
updates.storageUpdates.set(
|
|
464
|
+
key,
|
|
465
|
+
mergeStorageUpdates(updates.storageUpdates.get(key), value)
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
notify(updates);
|
|
472
|
+
}
|
|
473
|
+
function onClose(event) {
|
|
474
|
+
if (
|
|
475
|
+
((state.socket = null),
|
|
476
|
+
clearTimeout(state.timeoutHandles.pongTimeout),
|
|
477
|
+
clearInterval(state.intervalHandles.heartbeat),
|
|
478
|
+
state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush),
|
|
479
|
+
clearTimeout(state.timeoutHandles.reconnect),
|
|
480
|
+
(state.users = {}),
|
|
481
|
+
notify({ others: [{ type: "reset" }] }),
|
|
482
|
+
event.code >= 4e3 && event.code <= 4100)
|
|
483
|
+
) {
|
|
484
|
+
updateConnection({ state: "failed" });
|
|
485
|
+
const error = new LiveblocksError(event.reason, event.code);
|
|
486
|
+
for (const listener of state.listeners.error) listener(error);
|
|
487
|
+
const delay = getRetryDelay(!0);
|
|
488
|
+
state.numberOfRetry++,
|
|
489
|
+
"production" !== process.env.NODE_ENV &&
|
|
490
|
+
console.error(
|
|
491
|
+
`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code}). Retrying in ${delay}ms.`
|
|
492
|
+
),
|
|
493
|
+
updateConnection({ state: "unavailable" }),
|
|
494
|
+
(state.timeoutHandles.reconnect = effects.scheduleReconnect(delay));
|
|
495
|
+
} else if (event.code === WebsocketCloseCodes.CLOSE_WITHOUT_RETRY)
|
|
496
|
+
updateConnection({ state: "closed" });
|
|
497
|
+
else {
|
|
498
|
+
const delay = getRetryDelay();
|
|
499
|
+
state.numberOfRetry++,
|
|
500
|
+
"production" !== process.env.NODE_ENV &&
|
|
501
|
+
console.warn(
|
|
502
|
+
`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`
|
|
503
|
+
),
|
|
504
|
+
updateConnection({ state: "unavailable" }),
|
|
505
|
+
(state.timeoutHandles.reconnect = effects.scheduleReconnect(delay));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function updateConnection(connection) {
|
|
509
|
+
state.connection = connection;
|
|
510
|
+
for (const listener of state.listeners.connection)
|
|
511
|
+
listener(connection.state);
|
|
512
|
+
}
|
|
513
|
+
function getRetryDelay(slow = !1) {
|
|
514
|
+
return slow
|
|
515
|
+
? BACKOFF_RETRY_DELAYS_SLOW[
|
|
516
|
+
state.numberOfRetry < BACKOFF_RETRY_DELAYS_SLOW.length
|
|
517
|
+
? state.numberOfRetry
|
|
518
|
+
: BACKOFF_RETRY_DELAYS_SLOW.length - 1
|
|
519
|
+
]
|
|
520
|
+
: BACKOFF_RETRY_DELAYS[
|
|
521
|
+
state.numberOfRetry < BACKOFF_RETRY_DELAYS.length
|
|
522
|
+
? state.numberOfRetry
|
|
523
|
+
: BACKOFF_RETRY_DELAYS.length - 1
|
|
524
|
+
];
|
|
525
|
+
}
|
|
526
|
+
function onError() {}
|
|
527
|
+
function onOpen() {
|
|
528
|
+
clearInterval(state.intervalHandles.heartbeat),
|
|
529
|
+
(state.intervalHandles.heartbeat = effects.startHeartbeatInterval()),
|
|
530
|
+
"connecting" === state.connection.state &&
|
|
531
|
+
(updateConnection(
|
|
532
|
+
Object.assign(Object.assign({}, state.connection), { state: "open" })
|
|
533
|
+
),
|
|
534
|
+
(state.numberOfRetry = 0),
|
|
535
|
+
void 0 !== state.lastConnectionId &&
|
|
536
|
+
((state.buffer.presence = state.me), tryFlushing()),
|
|
537
|
+
(state.lastConnectionId = state.connection.id),
|
|
538
|
+
state.root &&
|
|
539
|
+
state.buffer.messages.push({ type: ClientMsgCode.FETCH_STORAGE }),
|
|
540
|
+
tryFlushing());
|
|
541
|
+
}
|
|
542
|
+
function heartbeat() {
|
|
543
|
+
null != state.socket &&
|
|
544
|
+
(clearTimeout(state.timeoutHandles.pongTimeout),
|
|
545
|
+
(state.timeoutHandles.pongTimeout = effects.schedulePongTimeout()),
|
|
546
|
+
state.socket.readyState === state.socket.OPEN &&
|
|
547
|
+
state.socket.send("ping"));
|
|
548
|
+
}
|
|
549
|
+
function pongTimeout() {
|
|
550
|
+
reconnect();
|
|
551
|
+
}
|
|
552
|
+
function reconnect() {
|
|
553
|
+
state.socket &&
|
|
554
|
+
(state.socket.removeEventListener("open", onOpen),
|
|
555
|
+
state.socket.removeEventListener("message", onMessage),
|
|
556
|
+
state.socket.removeEventListener("close", onClose),
|
|
557
|
+
state.socket.removeEventListener("error", onError),
|
|
558
|
+
state.socket.close(),
|
|
559
|
+
(state.socket = null)),
|
|
560
|
+
updateConnection({ state: "unavailable" }),
|
|
561
|
+
clearTimeout(state.timeoutHandles.pongTimeout),
|
|
562
|
+
state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush),
|
|
563
|
+
clearTimeout(state.timeoutHandles.reconnect),
|
|
564
|
+
clearInterval(state.intervalHandles.heartbeat),
|
|
565
|
+
connect();
|
|
566
|
+
}
|
|
567
|
+
function applyAndSendOfflineOps(offlineOps) {
|
|
568
|
+
if (0 === offlineOps.size) return;
|
|
569
|
+
const messages = [],
|
|
570
|
+
ops = Array.from(offlineOps.values()),
|
|
571
|
+
result = apply(ops, !0);
|
|
572
|
+
messages.push({ type: ClientMsgCode.UPDATE_STORAGE, ops: ops }),
|
|
573
|
+
notify(result.updates),
|
|
574
|
+
effects.send(messages);
|
|
575
|
+
}
|
|
576
|
+
function tryFlushing() {
|
|
577
|
+
const storageOps = state.buffer.storageOperations;
|
|
578
|
+
if (
|
|
579
|
+
(storageOps.length > 0 &&
|
|
580
|
+
storageOps.forEach((op) => {
|
|
581
|
+
state.offlineOperations.set(nn(op.opId), op);
|
|
582
|
+
}),
|
|
583
|
+
null == state.socket || state.socket.readyState !== state.socket.OPEN)
|
|
584
|
+
)
|
|
585
|
+
return void (state.buffer.storageOperations = []);
|
|
586
|
+
const now = Date.now();
|
|
587
|
+
if (now - state.lastFlushTime > context.throttleDelay) {
|
|
588
|
+
const messages = (function (state) {
|
|
589
|
+
const messages = [];
|
|
590
|
+
state.buffer.presence &&
|
|
591
|
+
messages.push({
|
|
592
|
+
type: ClientMsgCode.UPDATE_PRESENCE,
|
|
593
|
+
data: state.buffer.presence,
|
|
594
|
+
});
|
|
595
|
+
for (const event of state.buffer.messages) messages.push(event);
|
|
596
|
+
state.buffer.storageOperations.length > 0 &&
|
|
597
|
+
messages.push({
|
|
598
|
+
type: ClientMsgCode.UPDATE_STORAGE,
|
|
599
|
+
ops: state.buffer.storageOperations,
|
|
600
|
+
});
|
|
601
|
+
return messages;
|
|
602
|
+
})(state);
|
|
603
|
+
if (0 === messages.length) return;
|
|
604
|
+
effects.send(messages),
|
|
605
|
+
(state.buffer = {
|
|
606
|
+
messages: [],
|
|
607
|
+
storageOperations: [],
|
|
608
|
+
presence: null,
|
|
609
|
+
}),
|
|
610
|
+
(state.lastFlushTime = now);
|
|
611
|
+
} else
|
|
612
|
+
null != state.timeoutHandles.flush &&
|
|
613
|
+
clearTimeout(state.timeoutHandles.flush),
|
|
614
|
+
(state.timeoutHandles.flush = effects.delayFlush(
|
|
615
|
+
context.throttleDelay - (now - state.lastFlushTime)
|
|
616
|
+
));
|
|
617
|
+
}
|
|
618
|
+
function getPresence() {
|
|
619
|
+
return state.me;
|
|
620
|
+
}
|
|
621
|
+
function dispatch(ops) {
|
|
622
|
+
state.buffer.storageOperations.push(...ops), tryFlushing();
|
|
623
|
+
}
|
|
624
|
+
let _getInitialStatePromise = null,
|
|
625
|
+
_getInitialStateResolver = null;
|
|
626
|
+
return {
|
|
627
|
+
onClose: onClose,
|
|
628
|
+
onMessage: onMessage,
|
|
629
|
+
authenticationSuccess: authenticationSuccess,
|
|
630
|
+
heartbeat: heartbeat,
|
|
631
|
+
onNavigatorOnline: function () {
|
|
632
|
+
"unavailable" === state.connection.state && reconnect();
|
|
633
|
+
},
|
|
634
|
+
simulateSocketClose: function () {
|
|
635
|
+
state.socket && (state.socket = null);
|
|
636
|
+
},
|
|
637
|
+
simulateSendCloseEvent: function (event) {
|
|
638
|
+
onClose(event);
|
|
639
|
+
},
|
|
640
|
+
onVisibilityChange: function (visibilityState) {
|
|
641
|
+
"visible" === visibilityState &&
|
|
642
|
+
"open" === state.connection.state &&
|
|
643
|
+
heartbeat();
|
|
644
|
+
},
|
|
645
|
+
getUndoStack: () => state.undoStack,
|
|
646
|
+
getItemsCount: () => state.items.size,
|
|
647
|
+
connect: connect,
|
|
648
|
+
disconnect: function () {
|
|
649
|
+
state.socket &&
|
|
650
|
+
(state.socket.removeEventListener("open", onOpen),
|
|
651
|
+
state.socket.removeEventListener("message", onMessage),
|
|
652
|
+
state.socket.removeEventListener("close", onClose),
|
|
653
|
+
state.socket.removeEventListener("error", onError),
|
|
654
|
+
state.socket.close(),
|
|
655
|
+
(state.socket = null)),
|
|
656
|
+
updateConnection({ state: "closed" }),
|
|
657
|
+
state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush),
|
|
658
|
+
clearTimeout(state.timeoutHandles.reconnect),
|
|
659
|
+
clearTimeout(state.timeoutHandles.pongTimeout),
|
|
660
|
+
clearInterval(state.intervalHandles.heartbeat),
|
|
661
|
+
(state.users = {}),
|
|
662
|
+
notify({ others: [{ type: "reset" }] }),
|
|
663
|
+
(function () {
|
|
664
|
+
for (const key in state.listeners) state.listeners[key] = [];
|
|
665
|
+
})();
|
|
666
|
+
},
|
|
667
|
+
subscribe: function (firstParam, listener, options) {
|
|
668
|
+
if (isLiveNode(firstParam))
|
|
669
|
+
return (function (liveValue, innerCallback, options) {
|
|
670
|
+
return genericSubscribe((updates) => {
|
|
671
|
+
const relatedUpdates = [];
|
|
672
|
+
for (const update of updates)
|
|
673
|
+
(null == options ? void 0 : options.isDeep) &&
|
|
674
|
+
isSameNodeOrChildOf(update.node, liveValue)
|
|
675
|
+
? relatedUpdates.push(update)
|
|
676
|
+
: update.node._id === liveValue._id &&
|
|
677
|
+
innerCallback(update.node);
|
|
678
|
+
(null == options ? void 0 : options.isDeep) &&
|
|
679
|
+
relatedUpdates.length > 0 &&
|
|
680
|
+
innerCallback(relatedUpdates);
|
|
681
|
+
});
|
|
682
|
+
})(firstParam, listener, options);
|
|
683
|
+
if ("function" == typeof firstParam) return genericSubscribe(firstParam);
|
|
684
|
+
if (
|
|
685
|
+
"my-presence" !== (value = firstParam) &&
|
|
686
|
+
"others" !== value &&
|
|
687
|
+
"event" !== value &&
|
|
688
|
+
"error" !== value &&
|
|
689
|
+
"connection" !== value
|
|
690
|
+
)
|
|
691
|
+
throw new Error(`"${firstParam}" is not a valid event name`);
|
|
692
|
+
var value;
|
|
693
|
+
return (
|
|
694
|
+
state.listeners[firstParam].push(listener),
|
|
695
|
+
() => {
|
|
696
|
+
const callbacks = state.listeners[firstParam];
|
|
697
|
+
remove(callbacks, listener);
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
},
|
|
701
|
+
updatePresence: function (overrides, options) {
|
|
702
|
+
const oldValues = {};
|
|
703
|
+
null == state.buffer.presence && (state.buffer.presence = {});
|
|
704
|
+
for (const key in overrides) {
|
|
705
|
+
const overrideValue = overrides[key];
|
|
706
|
+
void 0 !== overrideValue &&
|
|
707
|
+
((state.buffer.presence[key] = overrideValue),
|
|
708
|
+
(oldValues[key] = state.me[key]));
|
|
709
|
+
}
|
|
710
|
+
(state.me = Object.assign(Object.assign({}, state.me), overrides)),
|
|
711
|
+
state.isBatching
|
|
712
|
+
? ((null == options ? void 0 : options.addToHistory) &&
|
|
713
|
+
state.batch.reverseOps.push({
|
|
714
|
+
type: "presence",
|
|
715
|
+
data: oldValues,
|
|
716
|
+
}),
|
|
717
|
+
(state.batch.updates.presence = !0))
|
|
718
|
+
: (tryFlushing(),
|
|
719
|
+
(null == options ? void 0 : options.addToHistory) &&
|
|
720
|
+
addToUndoStack([{ type: "presence", data: oldValues }]),
|
|
721
|
+
notify({ presence: !0 }));
|
|
722
|
+
},
|
|
723
|
+
broadcastEvent: function (
|
|
724
|
+
event,
|
|
725
|
+
options = { shouldQueueEventIfNotReady: !1 }
|
|
726
|
+
) {
|
|
727
|
+
(null == state.socket && 0 == options.shouldQueueEventIfNotReady) ||
|
|
728
|
+
(state.buffer.messages.push({
|
|
729
|
+
type: ClientMsgCode.BROADCAST_EVENT,
|
|
730
|
+
event: event,
|
|
731
|
+
}),
|
|
732
|
+
tryFlushing());
|
|
733
|
+
},
|
|
734
|
+
batch: function (callback) {
|
|
735
|
+
if (state.isBatching)
|
|
736
|
+
throw new Error("batch should not be called during a batch");
|
|
737
|
+
state.isBatching = !0;
|
|
738
|
+
try {
|
|
739
|
+
callback();
|
|
740
|
+
} finally {
|
|
741
|
+
(state.isBatching = !1),
|
|
742
|
+
state.batch.reverseOps.length > 0 &&
|
|
743
|
+
addToUndoStack(state.batch.reverseOps),
|
|
744
|
+
state.batch.ops.length > 0 && (state.redoStack = []),
|
|
745
|
+
state.batch.ops.length > 0 && dispatch(state.batch.ops),
|
|
746
|
+
notify(state.batch.updates),
|
|
747
|
+
(state.batch = {
|
|
748
|
+
ops: [],
|
|
749
|
+
reverseOps: [],
|
|
750
|
+
updates: { others: [], storageUpdates: new Map(), presence: !1 },
|
|
751
|
+
}),
|
|
752
|
+
tryFlushing();
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
undo: function () {
|
|
756
|
+
if (state.isBatching)
|
|
757
|
+
throw new Error("undo is not allowed during a batch");
|
|
758
|
+
const historyItem = state.undoStack.pop();
|
|
759
|
+
if (null == historyItem) return;
|
|
760
|
+
state.isHistoryPaused = !1;
|
|
761
|
+
const result = apply(historyItem, !0);
|
|
762
|
+
notify(result.updates), state.redoStack.push(result.reverse);
|
|
763
|
+
for (const op of historyItem)
|
|
764
|
+
"presence" !== op.type && state.buffer.storageOperations.push(op);
|
|
765
|
+
tryFlushing();
|
|
766
|
+
},
|
|
767
|
+
redo: function () {
|
|
768
|
+
if (state.isBatching)
|
|
769
|
+
throw new Error("redo is not allowed during a batch");
|
|
770
|
+
const historyItem = state.redoStack.pop();
|
|
771
|
+
if (null == historyItem) return;
|
|
772
|
+
state.isHistoryPaused = !1;
|
|
773
|
+
const result = apply(historyItem, !0);
|
|
774
|
+
notify(result.updates), state.undoStack.push(result.reverse);
|
|
775
|
+
for (const op of historyItem)
|
|
776
|
+
"presence" !== op.type && state.buffer.storageOperations.push(op);
|
|
777
|
+
tryFlushing();
|
|
778
|
+
},
|
|
779
|
+
pauseHistory: function () {
|
|
780
|
+
(state.pausedHistory = []), (state.isHistoryPaused = !0);
|
|
781
|
+
},
|
|
782
|
+
resumeHistory: function () {
|
|
783
|
+
(state.isHistoryPaused = !1),
|
|
784
|
+
state.pausedHistory.length > 0 && addToUndoStack(state.pausedHistory),
|
|
785
|
+
(state.pausedHistory = []);
|
|
786
|
+
},
|
|
787
|
+
getStorage: function () {
|
|
788
|
+
return state.root
|
|
789
|
+
? new Promise((resolve) => resolve({ root: state.root }))
|
|
790
|
+
: (null == _getInitialStatePromise &&
|
|
791
|
+
(state.buffer.messages.push({ type: ClientMsgCode.FETCH_STORAGE }),
|
|
792
|
+
tryFlushing(),
|
|
793
|
+
(_getInitialStatePromise = new Promise(
|
|
794
|
+
(resolve) => (_getInitialStateResolver = resolve)
|
|
795
|
+
))),
|
|
796
|
+
_getInitialStatePromise.then(() => ({ root: nn(state.root) })));
|
|
797
|
+
},
|
|
798
|
+
selectors: {
|
|
799
|
+
getConnectionState: function () {
|
|
800
|
+
return state.connection.state;
|
|
801
|
+
},
|
|
802
|
+
getSelf: function () {
|
|
803
|
+
return "open" === state.connection.state ||
|
|
804
|
+
"connecting" === state.connection.state
|
|
805
|
+
? {
|
|
806
|
+
connectionId: state.connection.id,
|
|
807
|
+
id: state.connection.userId,
|
|
808
|
+
info: state.connection.userInfo,
|
|
809
|
+
presence: getPresence(),
|
|
810
|
+
}
|
|
811
|
+
: null;
|
|
812
|
+
},
|
|
813
|
+
getPresence: getPresence,
|
|
814
|
+
getOthers: function () {
|
|
815
|
+
return state.others;
|
|
816
|
+
},
|
|
817
|
+
},
|
|
312
818
|
};
|
|
313
|
-
for (const message of messages) switch (message.type) {
|
|
314
|
-
case ServerMsgCode.USER_JOINED:
|
|
315
|
-
updates.others.push(onUserJoinedMessage(message));
|
|
316
|
-
break;
|
|
317
|
-
|
|
318
|
-
case ServerMsgCode.UPDATE_PRESENCE:
|
|
319
|
-
{
|
|
320
|
-
const othersPresenceUpdate = onUpdatePresenceMessage(message);
|
|
321
|
-
othersPresenceUpdate && updates.others.push(othersPresenceUpdate);
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
case ServerMsgCode.BROADCASTED_EVENT:
|
|
326
|
-
onEvent(message);
|
|
327
|
-
break;
|
|
328
|
-
|
|
329
|
-
case ServerMsgCode.USER_LEFT:
|
|
330
|
-
{
|
|
331
|
-
const event = onUserLeftMessage(message);
|
|
332
|
-
event && updates.others.push(event);
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
case ServerMsgCode.ROOM_STATE:
|
|
337
|
-
updates.others.push(onRoomStateMessage(message));
|
|
338
|
-
break;
|
|
339
|
-
|
|
340
|
-
case ServerMsgCode.INITIAL_STORAGE_STATE:
|
|
341
|
-
{
|
|
342
|
-
const offlineOps = new Map(state.offlineOperations);
|
|
343
|
-
createOrUpdateRootFromMessage(message), applyAndSendOfflineOps(offlineOps), null == _getInitialStateResolver || _getInitialStateResolver();
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
case ServerMsgCode.UPDATE_STORAGE:
|
|
348
|
-
apply(message.ops, !1).updates.storageUpdates.forEach(((value, key) => {
|
|
349
|
-
updates.storageUpdates.set(key, mergeStorageUpdates(updates.storageUpdates.get(key), value));
|
|
350
|
-
}));
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
notify(updates);
|
|
354
|
-
}
|
|
355
|
-
function onClose(event) {
|
|
356
|
-
if (state.socket = null, clearTimeout(state.timeoutHandles.pongTimeout), clearInterval(state.intervalHandles.heartbeat),
|
|
357
|
-
state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush), clearTimeout(state.timeoutHandles.reconnect),
|
|
358
|
-
state.users = {}, notify({
|
|
359
|
-
others: [ {
|
|
360
|
-
type: "reset"
|
|
361
|
-
} ]
|
|
362
|
-
}), event.code >= 4e3 && event.code <= 4100) {
|
|
363
|
-
updateConnection({
|
|
364
|
-
state: "failed"
|
|
365
|
-
});
|
|
366
|
-
const error = new LiveblocksError(event.reason, event.code);
|
|
367
|
-
for (const listener of state.listeners.error) listener(error);
|
|
368
|
-
const delay = getRetryDelay(!0);
|
|
369
|
-
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.`),
|
|
370
|
-
updateConnection({
|
|
371
|
-
state: "unavailable"
|
|
372
|
-
}), state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
|
|
373
|
-
} else if (event.code === WebsocketCloseCodes.CLOSE_WITHOUT_RETRY) updateConnection({
|
|
374
|
-
state: "closed"
|
|
375
|
-
}); else {
|
|
376
|
-
const delay = getRetryDelay();
|
|
377
|
-
state.numberOfRetry++, "production" !== process.env.NODE_ENV && console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`),
|
|
378
|
-
updateConnection({
|
|
379
|
-
state: "unavailable"
|
|
380
|
-
}), state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
function updateConnection(connection) {
|
|
384
|
-
state.connection = connection;
|
|
385
|
-
for (const listener of state.listeners.connection) listener(connection.state);
|
|
386
|
-
}
|
|
387
|
-
function getRetryDelay(slow = !1) {
|
|
388
|
-
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];
|
|
389
|
-
}
|
|
390
|
-
function onError() {}
|
|
391
|
-
function onOpen() {
|
|
392
|
-
clearInterval(state.intervalHandles.heartbeat), state.intervalHandles.heartbeat = effects.startHeartbeatInterval(),
|
|
393
|
-
"connecting" === state.connection.state && (updateConnection(Object.assign(Object.assign({}, state.connection), {
|
|
394
|
-
state: "open"
|
|
395
|
-
})), state.numberOfRetry = 0, void 0 !== state.lastConnectionId && (state.buffer.presence = state.me,
|
|
396
|
-
tryFlushing()), state.lastConnectionId = state.connection.id, state.root && state.buffer.messages.push({
|
|
397
|
-
type: ClientMsgCode.FETCH_STORAGE
|
|
398
|
-
}), tryFlushing());
|
|
399
|
-
}
|
|
400
|
-
function heartbeat() {
|
|
401
|
-
null != state.socket && (clearTimeout(state.timeoutHandles.pongTimeout), state.timeoutHandles.pongTimeout = effects.schedulePongTimeout(),
|
|
402
|
-
state.socket.readyState === state.socket.OPEN && state.socket.send("ping"));
|
|
403
|
-
}
|
|
404
|
-
function pongTimeout() {
|
|
405
|
-
reconnect();
|
|
406
|
-
}
|
|
407
|
-
function reconnect() {
|
|
408
|
-
state.socket && (state.socket.removeEventListener("open", onOpen), state.socket.removeEventListener("message", onMessage),
|
|
409
|
-
state.socket.removeEventListener("close", onClose), state.socket.removeEventListener("error", onError),
|
|
410
|
-
state.socket.close(), state.socket = null), updateConnection({
|
|
411
|
-
state: "unavailable"
|
|
412
|
-
}), clearTimeout(state.timeoutHandles.pongTimeout), state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush),
|
|
413
|
-
clearTimeout(state.timeoutHandles.reconnect), clearInterval(state.intervalHandles.heartbeat),
|
|
414
|
-
connect();
|
|
415
|
-
}
|
|
416
|
-
function applyAndSendOfflineOps(offlineOps) {
|
|
417
|
-
if (0 === offlineOps.size) return;
|
|
418
|
-
const messages = [], ops = Array.from(offlineOps.values()), result = apply(ops, !0);
|
|
419
|
-
messages.push({
|
|
420
|
-
type: ClientMsgCode.UPDATE_STORAGE,
|
|
421
|
-
ops: ops
|
|
422
|
-
}), notify(result.updates), effects.send(messages);
|
|
423
|
-
}
|
|
424
|
-
function tryFlushing() {
|
|
425
|
-
const storageOps = state.buffer.storageOperations;
|
|
426
|
-
if (storageOps.length > 0 && storageOps.forEach((op => {
|
|
427
|
-
state.offlineOperations.set(op.opId, op);
|
|
428
|
-
})), null == state.socket || state.socket.readyState !== state.socket.OPEN) return void (state.buffer.storageOperations = []);
|
|
429
|
-
const now = Date.now();
|
|
430
|
-
if (now - state.lastFlushTime > context.throttleDelay) {
|
|
431
|
-
const messages = function(state) {
|
|
432
|
-
const messages = [];
|
|
433
|
-
state.buffer.presence && messages.push({
|
|
434
|
-
type: ClientMsgCode.UPDATE_PRESENCE,
|
|
435
|
-
data: state.buffer.presence
|
|
436
|
-
});
|
|
437
|
-
for (const event of state.buffer.messages) messages.push(event);
|
|
438
|
-
state.buffer.storageOperations.length > 0 && messages.push({
|
|
439
|
-
type: ClientMsgCode.UPDATE_STORAGE,
|
|
440
|
-
ops: state.buffer.storageOperations
|
|
441
|
-
});
|
|
442
|
-
return messages;
|
|
443
|
-
}(state);
|
|
444
|
-
if (0 === messages.length) return;
|
|
445
|
-
effects.send(messages), state.buffer = {
|
|
446
|
-
messages: [],
|
|
447
|
-
storageOperations: [],
|
|
448
|
-
presence: null
|
|
449
|
-
}, state.lastFlushTime = now;
|
|
450
|
-
} else null != state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush),
|
|
451
|
-
state.timeoutHandles.flush = effects.delayFlush(context.throttleDelay - (now - state.lastFlushTime));
|
|
452
|
-
}
|
|
453
|
-
function getPresence() {
|
|
454
|
-
return state.me;
|
|
455
|
-
}
|
|
456
|
-
function dispatch(ops) {
|
|
457
|
-
state.buffer.storageOperations.push(...ops), tryFlushing();
|
|
458
|
-
}
|
|
459
|
-
let _getInitialStatePromise = null, _getInitialStateResolver = null;
|
|
460
|
-
return {
|
|
461
|
-
onClose: onClose,
|
|
462
|
-
onMessage: onMessage,
|
|
463
|
-
authenticationSuccess: authenticationSuccess,
|
|
464
|
-
heartbeat: heartbeat,
|
|
465
|
-
onNavigatorOnline: function() {
|
|
466
|
-
"unavailable" === state.connection.state && reconnect();
|
|
467
|
-
},
|
|
468
|
-
simulateSocketClose: function() {
|
|
469
|
-
state.socket && state.socket.close();
|
|
470
|
-
},
|
|
471
|
-
simulateSendCloseEvent: function(event) {
|
|
472
|
-
state.socket && onClose(event);
|
|
473
|
-
},
|
|
474
|
-
onVisibilityChange: function(visibilityState) {
|
|
475
|
-
"visible" === visibilityState && "open" === state.connection.state && heartbeat();
|
|
476
|
-
},
|
|
477
|
-
getUndoStack: () => state.undoStack,
|
|
478
|
-
getItemsCount: () => state.items.size,
|
|
479
|
-
connect: connect,
|
|
480
|
-
disconnect: function() {
|
|
481
|
-
state.socket && (state.socket.removeEventListener("open", onOpen), state.socket.removeEventListener("message", onMessage),
|
|
482
|
-
state.socket.removeEventListener("close", onClose), state.socket.removeEventListener("error", onError),
|
|
483
|
-
state.socket.close(), state.socket = null), updateConnection({
|
|
484
|
-
state: "closed"
|
|
485
|
-
}), state.timeoutHandles.flush && clearTimeout(state.timeoutHandles.flush), clearTimeout(state.timeoutHandles.reconnect),
|
|
486
|
-
clearTimeout(state.timeoutHandles.pongTimeout), clearInterval(state.intervalHandles.heartbeat),
|
|
487
|
-
state.users = {}, notify({
|
|
488
|
-
others: [ {
|
|
489
|
-
type: "reset"
|
|
490
|
-
} ]
|
|
491
|
-
}), function() {
|
|
492
|
-
for (const key in state.listeners) state.listeners[key] = [];
|
|
493
|
-
}();
|
|
494
|
-
},
|
|
495
|
-
subscribe: function(firstParam, listener, options) {
|
|
496
|
-
if (firstParam instanceof AbstractCrdt) return function(crdt, innerCallback, options) {
|
|
497
|
-
return genericSubscribe((updates => {
|
|
498
|
-
const relatedUpdates = [];
|
|
499
|
-
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);
|
|
500
|
-
(null == options ? void 0 : options.isDeep) && relatedUpdates.length > 0 && innerCallback(relatedUpdates);
|
|
501
|
-
}));
|
|
502
|
-
}(firstParam, listener, options);
|
|
503
|
-
if ("function" == typeof firstParam) return genericSubscribe(firstParam);
|
|
504
|
-
if (!isValidRoomEventType(firstParam)) throw new Error(`"${firstParam}" is not a valid event name`);
|
|
505
|
-
return state.listeners[firstParam].push(listener), () => {
|
|
506
|
-
const callbacks = state.listeners[firstParam];
|
|
507
|
-
remove(callbacks, listener);
|
|
508
|
-
};
|
|
509
|
-
},
|
|
510
|
-
unsubscribe: function(event, callback) {
|
|
511
|
-
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"),
|
|
512
|
-
!isValidRoomEventType(event)) throw new Error(`"${event}" is not a valid event name`);
|
|
513
|
-
const callbacks = state.listeners[event];
|
|
514
|
-
remove(callbacks, callback);
|
|
515
|
-
},
|
|
516
|
-
updatePresence: function(overrides, options) {
|
|
517
|
-
const oldValues = {};
|
|
518
|
-
null == state.buffer.presence && (state.buffer.presence = {});
|
|
519
|
-
for (const key in overrides) state.buffer.presence[key] = overrides[key], oldValues[key] = state.me[key];
|
|
520
|
-
state.me = Object.assign(Object.assign({}, state.me), overrides), state.isBatching ? ((null == options ? void 0 : options.addToHistory) && state.batch.reverseOps.push({
|
|
521
|
-
type: "presence",
|
|
522
|
-
data: oldValues
|
|
523
|
-
}), state.batch.updates.presence = !0) : (tryFlushing(), (null == options ? void 0 : options.addToHistory) && addToUndoStack([ {
|
|
524
|
-
type: "presence",
|
|
525
|
-
data: oldValues
|
|
526
|
-
} ]), notify({
|
|
527
|
-
presence: !0
|
|
528
|
-
}));
|
|
529
|
-
},
|
|
530
|
-
broadcastEvent: function(event, options = {
|
|
531
|
-
shouldQueueEventIfNotReady: !1
|
|
532
|
-
}) {
|
|
533
|
-
null == state.socket && 0 == options.shouldQueueEventIfNotReady || (state.buffer.messages.push({
|
|
534
|
-
type: ClientMsgCode.BROADCAST_EVENT,
|
|
535
|
-
event: event
|
|
536
|
-
}), tryFlushing());
|
|
537
|
-
},
|
|
538
|
-
batch: function(callback) {
|
|
539
|
-
if (state.isBatching) throw new Error("batch should not be called during a batch");
|
|
540
|
-
state.isBatching = !0;
|
|
541
|
-
try {
|
|
542
|
-
callback();
|
|
543
|
-
} finally {
|
|
544
|
-
state.isBatching = !1, state.batch.reverseOps.length > 0 && addToUndoStack(state.batch.reverseOps),
|
|
545
|
-
state.batch.ops.length > 0 && (state.redoStack = []), state.batch.ops.length > 0 && dispatch(state.batch.ops),
|
|
546
|
-
notify(state.batch.updates), state.batch = {
|
|
547
|
-
ops: [],
|
|
548
|
-
reverseOps: [],
|
|
549
|
-
updates: {
|
|
550
|
-
others: [],
|
|
551
|
-
storageUpdates: new Map,
|
|
552
|
-
presence: !1
|
|
553
|
-
}
|
|
554
|
-
}, tryFlushing();
|
|
555
|
-
}
|
|
556
|
-
},
|
|
557
|
-
undo: function() {
|
|
558
|
-
if (state.isBatching) throw new Error("undo is not allowed during a batch");
|
|
559
|
-
const historyItem = state.undoStack.pop();
|
|
560
|
-
if (null == historyItem) return;
|
|
561
|
-
state.isHistoryPaused = !1;
|
|
562
|
-
const result = apply(historyItem, !0);
|
|
563
|
-
notify(result.updates), state.redoStack.push(result.reverse);
|
|
564
|
-
for (const op of historyItem) "presence" !== op.type && state.buffer.storageOperations.push(op);
|
|
565
|
-
tryFlushing();
|
|
566
|
-
},
|
|
567
|
-
redo: function() {
|
|
568
|
-
if (state.isBatching) throw new Error("redo is not allowed during a batch");
|
|
569
|
-
const historyItem = state.redoStack.pop();
|
|
570
|
-
if (null == historyItem) return;
|
|
571
|
-
state.isHistoryPaused = !1;
|
|
572
|
-
const result = apply(historyItem, !0);
|
|
573
|
-
notify(result.updates), state.undoStack.push(result.reverse);
|
|
574
|
-
for (const op of historyItem) "presence" !== op.type && state.buffer.storageOperations.push(op);
|
|
575
|
-
tryFlushing();
|
|
576
|
-
},
|
|
577
|
-
pauseHistory: function() {
|
|
578
|
-
state.pausedHistory = [], state.isHistoryPaused = !0;
|
|
579
|
-
},
|
|
580
|
-
resumeHistory: function() {
|
|
581
|
-
state.isHistoryPaused = !1, state.pausedHistory.length > 0 && addToUndoStack(state.pausedHistory),
|
|
582
|
-
state.pausedHistory = [];
|
|
583
|
-
},
|
|
584
|
-
getStorage: function() {
|
|
585
|
-
return state.root ? new Promise((resolve => resolve({
|
|
586
|
-
root: state.root
|
|
587
|
-
}))) : (null == _getInitialStatePromise && (state.buffer.messages.push({
|
|
588
|
-
type: ClientMsgCode.FETCH_STORAGE
|
|
589
|
-
}), tryFlushing(), _getInitialStatePromise = new Promise((resolve => _getInitialStateResolver = resolve))),
|
|
590
|
-
_getInitialStatePromise.then((() => ({
|
|
591
|
-
root: state.root
|
|
592
|
-
}))));
|
|
593
|
-
},
|
|
594
|
-
selectors: {
|
|
595
|
-
getConnectionState: function() {
|
|
596
|
-
return state.connection.state;
|
|
597
|
-
},
|
|
598
|
-
getSelf: function() {
|
|
599
|
-
return "open" === state.connection.state || "connecting" === state.connection.state ? {
|
|
600
|
-
connectionId: state.connection.id,
|
|
601
|
-
id: state.connection.userId,
|
|
602
|
-
info: state.connection.userInfo,
|
|
603
|
-
presence: getPresence()
|
|
604
|
-
} : null;
|
|
605
|
-
},
|
|
606
|
-
getPresence: getPresence,
|
|
607
|
-
getOthers: function() {
|
|
608
|
-
return state.others;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
};
|
|
612
819
|
}
|
|
613
|
-
|
|
614
820
|
function createRoom(options, context) {
|
|
615
|
-
|
|
616
|
-
|
|
821
|
+
var _a, _b;
|
|
822
|
+
const initialPresence =
|
|
823
|
+
null !== (_a = options.initialPresence) && void 0 !== _a
|
|
824
|
+
? _a
|
|
825
|
+
: options.defaultPresence,
|
|
826
|
+
initialStorage =
|
|
827
|
+
null !== (_b = options.initialStorage) && void 0 !== _b
|
|
828
|
+
? _b
|
|
829
|
+
: options.defaultStorageRoot,
|
|
830
|
+
machine = makeStateMachine(
|
|
831
|
+
(function (initialPresence, initialStorage) {
|
|
832
|
+
return {
|
|
833
|
+
connection: { state: "closed" },
|
|
834
|
+
token: null,
|
|
835
|
+
lastConnectionId: null,
|
|
836
|
+
socket: null,
|
|
837
|
+
listeners: {
|
|
838
|
+
event: [],
|
|
839
|
+
others: [],
|
|
840
|
+
"my-presence": [],
|
|
841
|
+
error: [],
|
|
842
|
+
connection: [],
|
|
843
|
+
storage: [],
|
|
844
|
+
},
|
|
845
|
+
numberOfRetry: 0,
|
|
846
|
+
lastFlushTime: 0,
|
|
847
|
+
timeoutHandles: { flush: null, reconnect: 0, pongTimeout: 0 },
|
|
848
|
+
buffer: {
|
|
849
|
+
presence: null == initialPresence ? {} : initialPresence,
|
|
850
|
+
messages: [],
|
|
851
|
+
storageOperations: [],
|
|
852
|
+
},
|
|
853
|
+
intervalHandles: { heartbeat: 0 },
|
|
854
|
+
me: null == initialPresence ? {} : initialPresence,
|
|
855
|
+
users: {},
|
|
856
|
+
others: makeOthers({}),
|
|
857
|
+
defaultStorageRoot: initialStorage,
|
|
858
|
+
idFactory: null,
|
|
859
|
+
clock: 0,
|
|
860
|
+
opClock: 0,
|
|
861
|
+
items: new Map(),
|
|
862
|
+
root: void 0,
|
|
863
|
+
undoStack: [],
|
|
864
|
+
redoStack: [],
|
|
865
|
+
isHistoryPaused: !1,
|
|
866
|
+
pausedHistory: [],
|
|
867
|
+
isBatching: !1,
|
|
868
|
+
batch: {
|
|
869
|
+
ops: [],
|
|
870
|
+
updates: { storageUpdates: new Map(), presence: !1, others: [] },
|
|
871
|
+
reverseOps: [],
|
|
872
|
+
},
|
|
873
|
+
offlineOperations: new Map(),
|
|
874
|
+
};
|
|
875
|
+
})(
|
|
876
|
+
"function" == typeof initialPresence
|
|
877
|
+
? initialPresence(context.roomId)
|
|
878
|
+
: initialPresence,
|
|
879
|
+
"function" == typeof initialStorage
|
|
880
|
+
? initialStorage(context.roomId)
|
|
881
|
+
: initialStorage
|
|
882
|
+
),
|
|
883
|
+
context
|
|
884
|
+
),
|
|
885
|
+
room = {
|
|
886
|
+
id: context.roomId,
|
|
887
|
+
getConnectionState: machine.selectors.getConnectionState,
|
|
888
|
+
getSelf: machine.selectors.getSelf,
|
|
889
|
+
subscribe: machine.subscribe,
|
|
890
|
+
getPresence: machine.selectors.getPresence,
|
|
891
|
+
updatePresence: machine.updatePresence,
|
|
892
|
+
getOthers: machine.selectors.getOthers,
|
|
893
|
+
broadcastEvent: machine.broadcastEvent,
|
|
894
|
+
getStorage: machine.getStorage,
|
|
895
|
+
batch: machine.batch,
|
|
896
|
+
history: {
|
|
897
|
+
undo: machine.undo,
|
|
898
|
+
redo: machine.redo,
|
|
899
|
+
pause: machine.pauseHistory,
|
|
900
|
+
resume: machine.resumeHistory,
|
|
901
|
+
},
|
|
902
|
+
__INTERNAL_DO_NOT_USE: {
|
|
903
|
+
simulateCloseWebsocket: machine.simulateSocketClose,
|
|
904
|
+
simulateSendCloseEvent: machine.simulateSendCloseEvent,
|
|
905
|
+
},
|
|
906
|
+
};
|
|
617
907
|
return {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
socket: null,
|
|
624
|
-
listeners: {
|
|
625
|
-
event: [],
|
|
626
|
-
others: [],
|
|
627
|
-
"my-presence": [],
|
|
628
|
-
error: [],
|
|
629
|
-
connection: [],
|
|
630
|
-
storage: []
|
|
631
|
-
},
|
|
632
|
-
numberOfRetry: 0,
|
|
633
|
-
lastFlushTime: 0,
|
|
634
|
-
timeoutHandles: {
|
|
635
|
-
flush: null,
|
|
636
|
-
reconnect: 0,
|
|
637
|
-
pongTimeout: 0
|
|
638
|
-
},
|
|
639
|
-
buffer: {
|
|
640
|
-
presence: null == initialPresence ? {} : initialPresence,
|
|
641
|
-
messages: [],
|
|
642
|
-
storageOperations: []
|
|
643
|
-
},
|
|
644
|
-
intervalHandles: {
|
|
645
|
-
heartbeat: 0
|
|
646
|
-
},
|
|
647
|
-
me: null == initialPresence ? {} : initialPresence,
|
|
648
|
-
users: {},
|
|
649
|
-
others: makeOthers({}),
|
|
650
|
-
defaultStorageRoot: initialStorage,
|
|
651
|
-
idFactory: null,
|
|
652
|
-
clock: 0,
|
|
653
|
-
opClock: 0,
|
|
654
|
-
items: new Map,
|
|
655
|
-
root: void 0,
|
|
656
|
-
undoStack: [],
|
|
657
|
-
redoStack: [],
|
|
658
|
-
isHistoryPaused: !1,
|
|
659
|
-
pausedHistory: [],
|
|
660
|
-
isBatching: !1,
|
|
661
|
-
batch: {
|
|
662
|
-
ops: [],
|
|
663
|
-
updates: {
|
|
664
|
-
storageUpdates: new Map,
|
|
665
|
-
presence: !1,
|
|
666
|
-
others: []
|
|
667
|
-
},
|
|
668
|
-
reverseOps: []
|
|
669
|
-
},
|
|
670
|
-
offlineOperations: new Map
|
|
908
|
+
connect: machine.connect,
|
|
909
|
+
disconnect: machine.disconnect,
|
|
910
|
+
onNavigatorOnline: machine.onNavigatorOnline,
|
|
911
|
+
onVisibilityChange: machine.onVisibilityChange,
|
|
912
|
+
room: room,
|
|
671
913
|
};
|
|
672
|
-
}("function" == typeof initialPresence ? initialPresence(context.roomId) : initialPresence, "function" == typeof initialStorage ? initialStorage(context.roomId) : initialStorage), context), room = {
|
|
673
|
-
id: context.roomId,
|
|
674
|
-
getConnectionState: machine.selectors.getConnectionState,
|
|
675
|
-
getSelf: machine.selectors.getSelf,
|
|
676
|
-
subscribe: machine.subscribe,
|
|
677
|
-
unsubscribe: machine.unsubscribe,
|
|
678
|
-
getPresence: machine.selectors.getPresence,
|
|
679
|
-
updatePresence: machine.updatePresence,
|
|
680
|
-
getOthers: machine.selectors.getOthers,
|
|
681
|
-
broadcastEvent: machine.broadcastEvent,
|
|
682
|
-
getStorage: machine.getStorage,
|
|
683
|
-
batch: machine.batch,
|
|
684
|
-
history: {
|
|
685
|
-
undo: machine.undo,
|
|
686
|
-
redo: machine.redo,
|
|
687
|
-
pause: machine.pauseHistory,
|
|
688
|
-
resume: machine.resumeHistory
|
|
689
|
-
},
|
|
690
|
-
internalDevTools: {
|
|
691
|
-
closeWebsocket: machine.simulateSocketClose,
|
|
692
|
-
sendCloseEvent: machine.simulateSendCloseEvent
|
|
693
|
-
}
|
|
694
|
-
};
|
|
695
|
-
return {
|
|
696
|
-
connect: machine.connect,
|
|
697
|
-
disconnect: machine.disconnect,
|
|
698
|
-
onNavigatorOnline: machine.onNavigatorOnline,
|
|
699
|
-
onVisibilityChange: machine.onVisibilityChange,
|
|
700
|
-
room: room
|
|
701
|
-
};
|
|
702
914
|
}
|
|
703
|
-
|
|
704
915
|
class LiveblocksError extends Error {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function parseToken(token) {
|
|
711
|
-
const tokenParts = token.split(".");
|
|
712
|
-
if (3 !== tokenParts.length) throw new Error("Authentication error. Liveblocks could not parse the response of your authentication endpoint");
|
|
713
|
-
const data = tryParseJson(b64decode(tokenParts[1]));
|
|
714
|
-
if (void 0 !== data && isJsonObject(data) && "number" == typeof data.actor && (void 0 === data.id || "string" == typeof data.id)) return {
|
|
715
|
-
actor: data.actor,
|
|
716
|
-
id: data.id,
|
|
717
|
-
info: data.info
|
|
718
|
-
};
|
|
719
|
-
throw new Error("Authentication error. Liveblocks could not parse the response of your authentication endpoint");
|
|
916
|
+
constructor(message, code) {
|
|
917
|
+
super(message), (this.code = code);
|
|
918
|
+
}
|
|
720
919
|
}
|
|
721
|
-
|
|
722
920
|
function fetchAuthEndpoint(fetch, endpoint, body) {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
921
|
+
return fetch(endpoint, {
|
|
922
|
+
method: "POST",
|
|
923
|
+
headers: { "Content-Type": "application/json" },
|
|
924
|
+
body: JSON.stringify(body),
|
|
925
|
+
})
|
|
926
|
+
.then((res) => {
|
|
927
|
+
if (!res.ok)
|
|
928
|
+
throw new AuthenticationError(
|
|
929
|
+
`Expected a status 200 but got ${res.status} when doing a POST request on "${endpoint}"`
|
|
930
|
+
);
|
|
931
|
+
return res.json().catch((er) => {
|
|
932
|
+
throw new AuthenticationError(
|
|
933
|
+
`Expected a JSON response when doing a POST request on "${endpoint}". ${er}`
|
|
934
|
+
);
|
|
935
|
+
});
|
|
936
|
+
})
|
|
937
|
+
.then((data) => {
|
|
938
|
+
if (!isPlainObject(data) || "string" != typeof data.token)
|
|
939
|
+
throw new AuthenticationError(
|
|
940
|
+
`Expected a JSON response of the form \`{ token: "..." }\` when doing a POST request on "${endpoint}", but got ${JSON.stringify(
|
|
941
|
+
data
|
|
942
|
+
)}`
|
|
943
|
+
);
|
|
944
|
+
const { token: token } = data;
|
|
945
|
+
return { token: token };
|
|
946
|
+
});
|
|
738
947
|
}
|
|
739
|
-
|
|
740
948
|
class AuthenticationError extends Error {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
949
|
+
constructor(message) {
|
|
950
|
+
super(message);
|
|
951
|
+
}
|
|
744
952
|
}
|
|
745
|
-
|
|
746
953
|
function createClient(options) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
954
|
+
const clientOptions = options,
|
|
955
|
+
throttleDelay = (function (options) {
|
|
956
|
+
if (void 0 === options.throttle) return 100;
|
|
957
|
+
if (
|
|
958
|
+
"number" != typeof options.throttle ||
|
|
959
|
+
options.throttle < 80 ||
|
|
960
|
+
options.throttle > 1e3
|
|
961
|
+
)
|
|
962
|
+
throw new Error("throttle should be a number between 80 and 1000.");
|
|
963
|
+
return options.throttle;
|
|
964
|
+
})(options),
|
|
965
|
+
rooms = new Map();
|
|
966
|
+
return (
|
|
967
|
+
"undefined" != typeof window &&
|
|
968
|
+
void 0 !== window.addEventListener &&
|
|
969
|
+
window.addEventListener("online", () => {
|
|
970
|
+
for (const [, room] of rooms) room.onNavigatorOnline();
|
|
971
|
+
}),
|
|
972
|
+
"undefined" != typeof document &&
|
|
973
|
+
document.addEventListener("visibilitychange", () => {
|
|
974
|
+
for (const [, room] of rooms)
|
|
975
|
+
room.onVisibilityChange(document.visibilityState);
|
|
976
|
+
}),
|
|
977
|
+
{
|
|
978
|
+
getRoom: function (roomId) {
|
|
979
|
+
const internalRoom = rooms.get(roomId);
|
|
980
|
+
return internalRoom ? internalRoom.room : null;
|
|
981
|
+
},
|
|
982
|
+
enter: function (roomId, options = {}) {
|
|
983
|
+
let internalRoom = rooms.get(roomId);
|
|
984
|
+
if (internalRoom) return internalRoom.room;
|
|
985
|
+
if (
|
|
986
|
+
(errorIf(
|
|
987
|
+
options.defaultPresence,
|
|
988
|
+
"Argument `defaultPresence` will be removed in @liveblocks/client 0.18. Please use `initialPresence` instead. For more info, see https://bit.ly/3Niy5aP"
|
|
989
|
+
),
|
|
990
|
+
errorIf(
|
|
991
|
+
options.defaultStorageRoot,
|
|
992
|
+
"Argument `defaultStorageRoot` will be removed in @liveblocks/client 0.18. Please use `initialStorage` instead. For more info, see https://bit.ly/3Niy5aP"
|
|
993
|
+
),
|
|
994
|
+
(internalRoom = createRoom(
|
|
995
|
+
{
|
|
996
|
+
initialPresence: options.initialPresence,
|
|
997
|
+
initialStorage: options.initialStorage,
|
|
998
|
+
defaultPresence: options.defaultPresence,
|
|
999
|
+
defaultStorageRoot: options.defaultStorageRoot,
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
roomId: roomId,
|
|
1003
|
+
throttleDelay: throttleDelay,
|
|
1004
|
+
WebSocketPolyfill: clientOptions.WebSocketPolyfill,
|
|
1005
|
+
fetchPolyfill: clientOptions.fetchPolyfill,
|
|
1006
|
+
liveblocksServer:
|
|
1007
|
+
(null == clientOptions
|
|
1008
|
+
? void 0
|
|
1009
|
+
: clientOptions.liveblocksServer) ||
|
|
1010
|
+
"wss://liveblocks.net/v6",
|
|
1011
|
+
authentication: prepareAuthentication(clientOptions),
|
|
1012
|
+
}
|
|
1013
|
+
)),
|
|
1014
|
+
rooms.set(roomId, internalRoom),
|
|
1015
|
+
!options.DO_NOT_USE_withoutConnecting)
|
|
1016
|
+
) {
|
|
1017
|
+
if ("undefined" == typeof atob) {
|
|
1018
|
+
if (null == clientOptions.atobPolyfill)
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
"You need to polyfill atob to use the client in your environment. Please follow the instructions at https://liveblocks.io/docs/errors/liveblocks-client/atob-polyfill"
|
|
1021
|
+
);
|
|
1022
|
+
global.atob = clientOptions.atobPolyfill;
|
|
1023
|
+
}
|
|
1024
|
+
internalRoom.connect();
|
|
1025
|
+
}
|
|
1026
|
+
return internalRoom.room;
|
|
1027
|
+
},
|
|
1028
|
+
leave: function (roomId) {
|
|
1029
|
+
const room = rooms.get(roomId);
|
|
1030
|
+
room && (room.disconnect(), rooms.delete(roomId));
|
|
1031
|
+
},
|
|
1032
|
+
}
|
|
1033
|
+
);
|
|
785
1034
|
}
|
|
786
|
-
|
|
787
1035
|
function prepareAuthentication(clientOptions) {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1036
|
+
if ("string" == typeof clientOptions.publicApiKey)
|
|
1037
|
+
return {
|
|
1038
|
+
type: "public",
|
|
1039
|
+
publicApiKey: clientOptions.publicApiKey,
|
|
1040
|
+
url:
|
|
1041
|
+
clientOptions.publicAuthorizeEndpoint ||
|
|
1042
|
+
"https://liveblocks.io/api/public/authorize",
|
|
1043
|
+
};
|
|
1044
|
+
if ("string" == typeof clientOptions.authEndpoint)
|
|
1045
|
+
return { type: "private", url: clientOptions.authEndpoint };
|
|
1046
|
+
if ("function" == typeof clientOptions.authEndpoint)
|
|
1047
|
+
return { type: "custom", callback: clientOptions.authEndpoint };
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
"Invalid Liveblocks client options. For more information: https://liveblocks.io/docs/api-reference/liveblocks-client#createClient"
|
|
1050
|
+
);
|
|
802
1051
|
}
|
|
803
|
-
|
|
804
1052
|
export { createClient };
|