@liveblocks/client 0.15.0-alpha.3 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -10
- package/lib/esm/index.js +2991 -5
- package/lib/esm/index.mjs +2991 -0
- package/lib/esm/internal.js +149 -0
- package/lib/esm/internal.mjs +149 -0
- package/lib/{esm/types.d.ts → index.d.ts} +253 -61
- package/lib/index.js +4237 -0
- package/lib/{esm/live.d.ts → internal.d.ts} +46 -34
- package/lib/internal.js +193 -0
- package/package.json +32 -10
- package/lib/cjs/AbstractCrdt.d.ts +0 -68
- package/lib/cjs/AbstractCrdt.js +0 -95
- package/lib/cjs/LiveList.d.ts +0 -144
- package/lib/cjs/LiveList.js +0 -530
- package/lib/cjs/LiveMap.d.ts +0 -91
- package/lib/cjs/LiveMap.js +0 -325
- package/lib/cjs/LiveObject.d.ts +0 -80
- package/lib/cjs/LiveObject.js +0 -485
- package/lib/cjs/LiveRegister.d.ts +0 -29
- package/lib/cjs/LiveRegister.js +0 -88
- package/lib/cjs/client.d.ts +0 -27
- package/lib/cjs/client.js +0 -123
- package/lib/cjs/immutable.d.ts +0 -9
- package/lib/cjs/immutable.js +0 -299
- package/lib/cjs/index.d.ts +0 -6
- package/lib/cjs/index.js +0 -18
- package/lib/cjs/live.d.ts +0 -181
- package/lib/cjs/live.js +0 -49
- package/lib/cjs/position.d.ts +0 -6
- package/lib/cjs/position.js +0 -113
- package/lib/cjs/room.d.ts +0 -159
- package/lib/cjs/room.js +0 -1129
- package/lib/cjs/types.d.ts +0 -502
- package/lib/cjs/types.js +0 -2
- package/lib/cjs/utils.d.ts +0 -15
- package/lib/cjs/utils.js +0 -225
- package/lib/esm/AbstractCrdt.d.ts +0 -68
- package/lib/esm/AbstractCrdt.js +0 -91
- package/lib/esm/LiveList.d.ts +0 -144
- package/lib/esm/LiveList.js +0 -526
- package/lib/esm/LiveMap.d.ts +0 -91
- package/lib/esm/LiveMap.js +0 -321
- package/lib/esm/LiveObject.d.ts +0 -80
- package/lib/esm/LiveObject.js +0 -481
- package/lib/esm/LiveRegister.d.ts +0 -29
- package/lib/esm/LiveRegister.js +0 -84
- package/lib/esm/client.d.ts +0 -27
- package/lib/esm/client.js +0 -119
- package/lib/esm/immutable.d.ts +0 -9
- package/lib/esm/immutable.js +0 -290
- package/lib/esm/index.d.ts +0 -6
- package/lib/esm/live.js +0 -46
- package/lib/esm/position.d.ts +0 -6
- package/lib/esm/position.js +0 -106
- package/lib/esm/room.d.ts +0 -159
- package/lib/esm/room.js +0 -1123
- package/lib/esm/types.js +0 -1
- package/lib/esm/utils.d.ts +0 -15
- package/lib/esm/utils.js +0 -213
package/lib/esm/room.js
DELETED
|
@@ -1,1123 +0,0 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { getTreesDiffOperations, isSameNodeOrChildOf, remove, mergeStorageUpdates, } from "./utils";
|
|
11
|
-
import { ClientMessageType, ServerMessageType, OpType, } from "./live";
|
|
12
|
-
import { LiveMap } from "./LiveMap";
|
|
13
|
-
import { LiveObject } from "./LiveObject";
|
|
14
|
-
import { LiveList } from "./LiveList";
|
|
15
|
-
import { AbstractCrdt } from "./AbstractCrdt";
|
|
16
|
-
import { LiveRegister } from "./LiveRegister";
|
|
17
|
-
const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];
|
|
18
|
-
const HEARTBEAT_INTERVAL = 30000;
|
|
19
|
-
// const WAKE_UP_CHECK_INTERVAL = 2000;
|
|
20
|
-
const PONG_TIMEOUT = 2000;
|
|
21
|
-
function isValidRoomEventType(value) {
|
|
22
|
-
return (value === "my-presence" ||
|
|
23
|
-
value === "others" ||
|
|
24
|
-
value === "event" ||
|
|
25
|
-
value === "error" ||
|
|
26
|
-
value === "connection");
|
|
27
|
-
}
|
|
28
|
-
function makeIdFactory(connectionId) {
|
|
29
|
-
let count = 0;
|
|
30
|
-
return () => `${connectionId}:${count++}`;
|
|
31
|
-
}
|
|
32
|
-
function makeOthers(presenceMap) {
|
|
33
|
-
const array = Object.values(presenceMap);
|
|
34
|
-
return {
|
|
35
|
-
get count() {
|
|
36
|
-
return array.length;
|
|
37
|
-
},
|
|
38
|
-
[Symbol.iterator]() {
|
|
39
|
-
return array[Symbol.iterator]();
|
|
40
|
-
},
|
|
41
|
-
map(callback) {
|
|
42
|
-
return array.map(callback);
|
|
43
|
-
},
|
|
44
|
-
toArray() {
|
|
45
|
-
return array;
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function log(...params) {
|
|
50
|
-
return;
|
|
51
|
-
console.log(...params, new Date().toString());
|
|
52
|
-
}
|
|
53
|
-
export function makeStateMachine(state, context, mockedEffects) {
|
|
54
|
-
const effects = mockedEffects || {
|
|
55
|
-
authenticate(auth, createWebSocket) {
|
|
56
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
-
try {
|
|
58
|
-
const { token } = yield auth(context.room);
|
|
59
|
-
const parsedToken = parseToken(token);
|
|
60
|
-
const socket = createWebSocket(token);
|
|
61
|
-
authenticationSuccess(parsedToken, socket);
|
|
62
|
-
}
|
|
63
|
-
catch (er) {
|
|
64
|
-
authenticationFailure(er);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
},
|
|
68
|
-
send(messageOrMessages) {
|
|
69
|
-
if (state.socket == null) {
|
|
70
|
-
throw new Error("Can't send message if socket is null");
|
|
71
|
-
}
|
|
72
|
-
state.socket.send(JSON.stringify(messageOrMessages));
|
|
73
|
-
},
|
|
74
|
-
delayFlush(delay) {
|
|
75
|
-
return setTimeout(tryFlushing, delay);
|
|
76
|
-
},
|
|
77
|
-
startHeartbeatInterval() {
|
|
78
|
-
return setInterval(heartbeat, HEARTBEAT_INTERVAL);
|
|
79
|
-
},
|
|
80
|
-
schedulePongTimeout() {
|
|
81
|
-
return setTimeout(pongTimeout, PONG_TIMEOUT);
|
|
82
|
-
},
|
|
83
|
-
scheduleReconnect(delay) {
|
|
84
|
-
return setTimeout(connect, delay);
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
function genericSubscribe(callback) {
|
|
88
|
-
state.listeners.storage.push(callback);
|
|
89
|
-
return () => remove(state.listeners.storage, callback);
|
|
90
|
-
}
|
|
91
|
-
function crdtSubscribe(crdt, innerCallback, options) {
|
|
92
|
-
const cb = (updates) => {
|
|
93
|
-
const relatedUpdates = [];
|
|
94
|
-
for (const update of updates) {
|
|
95
|
-
if ((options === null || options === void 0 ? void 0 : options.isDeep) && isSameNodeOrChildOf(update.node, crdt)) {
|
|
96
|
-
relatedUpdates.push(update);
|
|
97
|
-
}
|
|
98
|
-
else if (update.node._id === crdt._id) {
|
|
99
|
-
innerCallback(update.node);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if ((options === null || options === void 0 ? void 0 : options.isDeep) && relatedUpdates.length > 0) {
|
|
103
|
-
innerCallback(relatedUpdates);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
return genericSubscribe(cb);
|
|
107
|
-
}
|
|
108
|
-
function createOrUpdateRootFromMessage(message) {
|
|
109
|
-
if (message.items.length === 0) {
|
|
110
|
-
throw new Error("Internal error: cannot load storage without items");
|
|
111
|
-
}
|
|
112
|
-
if (state.root) {
|
|
113
|
-
updateRoot(message.items);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
state.root = load(message.items);
|
|
117
|
-
}
|
|
118
|
-
for (const key in state.defaultStorageRoot) {
|
|
119
|
-
if (state.root.get(key) == null) {
|
|
120
|
-
state.root.set(key, state.defaultStorageRoot[key]);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
function buildRootAndParentToChildren(items) {
|
|
125
|
-
const parentToChildren = new Map();
|
|
126
|
-
let root = null;
|
|
127
|
-
for (const tuple of items) {
|
|
128
|
-
const parentId = tuple[1].parentId;
|
|
129
|
-
if (parentId == null) {
|
|
130
|
-
root = tuple;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
const children = parentToChildren.get(parentId);
|
|
134
|
-
if (children != null) {
|
|
135
|
-
children.push(tuple);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
parentToChildren.set(parentId, [tuple]);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (root == null) {
|
|
143
|
-
throw new Error("Root can't be null");
|
|
144
|
-
}
|
|
145
|
-
return [root, parentToChildren];
|
|
146
|
-
}
|
|
147
|
-
function updateRoot(items) {
|
|
148
|
-
if (!state.root) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const currentItems = new Map();
|
|
152
|
-
state.items.forEach((liveCrdt, id) => {
|
|
153
|
-
currentItems.set(id, liveCrdt._toSerializedCrdt());
|
|
154
|
-
});
|
|
155
|
-
// Get operations that represent the diff between 2 states.
|
|
156
|
-
const ops = getTreesDiffOperations(currentItems, new Map(items));
|
|
157
|
-
const result = apply(ops, false);
|
|
158
|
-
notify(result.updates);
|
|
159
|
-
}
|
|
160
|
-
function load(items) {
|
|
161
|
-
const [root, parentToChildren] = buildRootAndParentToChildren(items);
|
|
162
|
-
return LiveObject._deserialize(root, parentToChildren, {
|
|
163
|
-
getItem,
|
|
164
|
-
addItem,
|
|
165
|
-
deleteItem,
|
|
166
|
-
generateId,
|
|
167
|
-
generateOpId,
|
|
168
|
-
dispatch: storageDispatch,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
function addItem(id, item) {
|
|
172
|
-
state.items.set(id, item);
|
|
173
|
-
}
|
|
174
|
-
function deleteItem(id) {
|
|
175
|
-
state.items.delete(id);
|
|
176
|
-
}
|
|
177
|
-
function getItem(id) {
|
|
178
|
-
return state.items.get(id);
|
|
179
|
-
}
|
|
180
|
-
function addToUndoStack(historyItem) {
|
|
181
|
-
// If undo stack is too large, we remove the older item
|
|
182
|
-
if (state.undoStack.length >= 50) {
|
|
183
|
-
state.undoStack.shift();
|
|
184
|
-
}
|
|
185
|
-
if (state.isHistoryPaused) {
|
|
186
|
-
state.pausedHistory.unshift(...historyItem);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
state.undoStack.push(historyItem);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function storageDispatch(ops, reverse, storageUpdates) {
|
|
193
|
-
if (state.isBatching) {
|
|
194
|
-
state.batch.ops.push(...ops);
|
|
195
|
-
storageUpdates.forEach((value, key) => {
|
|
196
|
-
state.batch.updates.storageUpdates.set(key, mergeStorageUpdates(state.batch.updates.storageUpdates.get(key), value));
|
|
197
|
-
});
|
|
198
|
-
state.batch.reverseOps.push(...reverse);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
addToUndoStack(reverse);
|
|
202
|
-
state.redoStack = [];
|
|
203
|
-
dispatch(ops);
|
|
204
|
-
notify({ storageUpdates: storageUpdates });
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
function notify({ storageUpdates = new Map(), presence = false, others = [], }) {
|
|
208
|
-
if (others.length > 0) {
|
|
209
|
-
state.others = makeOthers(state.users);
|
|
210
|
-
for (const event of others) {
|
|
211
|
-
for (const listener of state.listeners["others"]) {
|
|
212
|
-
listener(state.others, event);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (presence) {
|
|
217
|
-
for (const listener of state.listeners["my-presence"]) {
|
|
218
|
-
listener(state.me);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (storageUpdates.size > 0) {
|
|
222
|
-
for (const subscriber of state.listeners.storage) {
|
|
223
|
-
subscriber(Array.from(storageUpdates.values()));
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
function getConnectionId() {
|
|
228
|
-
if (state.connection.state === "open" ||
|
|
229
|
-
state.connection.state === "connecting") {
|
|
230
|
-
return state.connection.id;
|
|
231
|
-
}
|
|
232
|
-
else if (state.lastConnectionId !== null) {
|
|
233
|
-
return state.lastConnectionId;
|
|
234
|
-
}
|
|
235
|
-
throw new Error("Internal. Tried to get connection id but connection was never open");
|
|
236
|
-
}
|
|
237
|
-
function generateId() {
|
|
238
|
-
return `${getConnectionId()}:${state.clock++}`;
|
|
239
|
-
}
|
|
240
|
-
function generateOpId() {
|
|
241
|
-
return `${getConnectionId()}:${state.opClock++}`;
|
|
242
|
-
}
|
|
243
|
-
function apply(item, isLocal) {
|
|
244
|
-
const result = {
|
|
245
|
-
reverse: [],
|
|
246
|
-
updates: {
|
|
247
|
-
storageUpdates: new Map(),
|
|
248
|
-
presence: false,
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
for (const op of item) {
|
|
252
|
-
if (op.type === "presence") {
|
|
253
|
-
const reverse = {
|
|
254
|
-
type: "presence",
|
|
255
|
-
data: {},
|
|
256
|
-
};
|
|
257
|
-
for (const key in op.data) {
|
|
258
|
-
reverse.data[key] = state.me[key];
|
|
259
|
-
}
|
|
260
|
-
state.me = Object.assign(Object.assign({}, state.me), op.data);
|
|
261
|
-
if (state.buffer.presence == null) {
|
|
262
|
-
state.buffer.presence = op.data;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
for (const key in op.data) {
|
|
266
|
-
state.buffer.presence[key] = op.data;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
result.reverse.unshift(reverse);
|
|
270
|
-
result.updates.presence = true;
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
// Ops applied after undo/redo don't have an opId.
|
|
274
|
-
if (isLocal && !op.opId) {
|
|
275
|
-
op.opId = generateOpId();
|
|
276
|
-
}
|
|
277
|
-
const applyOpResult = applyOp(op, isLocal);
|
|
278
|
-
if (applyOpResult.modified) {
|
|
279
|
-
result.updates.storageUpdates.set(applyOpResult.modified.node._id, mergeStorageUpdates(result.updates.storageUpdates.get(applyOpResult.modified.node._id), applyOpResult.modified));
|
|
280
|
-
result.reverse.unshift(...applyOpResult.reverse);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return result;
|
|
285
|
-
}
|
|
286
|
-
function applyOp(op, isLocal) {
|
|
287
|
-
if (op.opId) {
|
|
288
|
-
state.offlineOperations.delete(op.opId);
|
|
289
|
-
}
|
|
290
|
-
switch (op.type) {
|
|
291
|
-
case OpType.DeleteObjectKey:
|
|
292
|
-
case OpType.UpdateObject:
|
|
293
|
-
case OpType.DeleteCrdt: {
|
|
294
|
-
const item = state.items.get(op.id);
|
|
295
|
-
if (item == null) {
|
|
296
|
-
return { modified: false };
|
|
297
|
-
}
|
|
298
|
-
return item._apply(op, isLocal);
|
|
299
|
-
}
|
|
300
|
-
case OpType.SetParentKey: {
|
|
301
|
-
const item = state.items.get(op.id);
|
|
302
|
-
if (item == null) {
|
|
303
|
-
return { modified: false };
|
|
304
|
-
}
|
|
305
|
-
if (item._parent instanceof LiveList) {
|
|
306
|
-
const previousKey = item._parentKey;
|
|
307
|
-
if (previousKey === op.parentKey) {
|
|
308
|
-
return { modified: false };
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
return item._parent._setChildKey(op.parentKey, item, previousKey);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return { modified: false };
|
|
315
|
-
}
|
|
316
|
-
case OpType.CreateObject: {
|
|
317
|
-
const parent = state.items.get(op.parentId);
|
|
318
|
-
if (parent == null) {
|
|
319
|
-
return { modified: false };
|
|
320
|
-
}
|
|
321
|
-
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), op.opId, isLocal);
|
|
322
|
-
}
|
|
323
|
-
case OpType.CreateList: {
|
|
324
|
-
const parent = state.items.get(op.parentId);
|
|
325
|
-
if (parent == null) {
|
|
326
|
-
return { modified: false };
|
|
327
|
-
}
|
|
328
|
-
return parent._attachChild(op.id, op.parentKey, new LiveList(), op.opId, isLocal);
|
|
329
|
-
}
|
|
330
|
-
case OpType.CreateRegister: {
|
|
331
|
-
const parent = state.items.get(op.parentId);
|
|
332
|
-
if (parent == null) {
|
|
333
|
-
return { modified: false };
|
|
334
|
-
}
|
|
335
|
-
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), op.opId, isLocal);
|
|
336
|
-
}
|
|
337
|
-
case OpType.CreateMap: {
|
|
338
|
-
const parent = state.items.get(op.parentId);
|
|
339
|
-
if (parent == null) {
|
|
340
|
-
return { modified: false };
|
|
341
|
-
}
|
|
342
|
-
return parent._attachChild(op.id, op.parentKey, new LiveMap(), op.opId, isLocal);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return { modified: false };
|
|
346
|
-
}
|
|
347
|
-
function subscribe(firstParam, listener, options) {
|
|
348
|
-
if (firstParam instanceof AbstractCrdt) {
|
|
349
|
-
return crdtSubscribe(firstParam, listener, options);
|
|
350
|
-
}
|
|
351
|
-
else if (typeof firstParam === "function") {
|
|
352
|
-
return genericSubscribe(firstParam);
|
|
353
|
-
}
|
|
354
|
-
else if (!isValidRoomEventType(firstParam)) {
|
|
355
|
-
throw new Error(`"${firstParam}" is not a valid event name`);
|
|
356
|
-
}
|
|
357
|
-
state.listeners[firstParam].push(listener);
|
|
358
|
-
return () => {
|
|
359
|
-
const callbacks = state.listeners[firstParam];
|
|
360
|
-
remove(callbacks, listener);
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
function unsubscribe(event, callback) {
|
|
364
|
-
console.warn(`unsubscribe is depreacted and will be removed in a future version.
|
|
365
|
-
use the callback returned by subscribe instead.
|
|
366
|
-
See v0.13 release notes for more information.
|
|
367
|
-
`);
|
|
368
|
-
if (!isValidRoomEventType(event)) {
|
|
369
|
-
throw new Error(`"${event}" is not a valid event name`);
|
|
370
|
-
}
|
|
371
|
-
const callbacks = state.listeners[event];
|
|
372
|
-
remove(callbacks, callback);
|
|
373
|
-
}
|
|
374
|
-
function getConnectionState() {
|
|
375
|
-
return state.connection.state;
|
|
376
|
-
}
|
|
377
|
-
function getSelf() {
|
|
378
|
-
return state.connection.state === "open" ||
|
|
379
|
-
state.connection.state === "connecting"
|
|
380
|
-
? {
|
|
381
|
-
connectionId: state.connection.id,
|
|
382
|
-
id: state.connection.userId,
|
|
383
|
-
info: state.connection.userInfo,
|
|
384
|
-
presence: getPresence(),
|
|
385
|
-
}
|
|
386
|
-
: null;
|
|
387
|
-
}
|
|
388
|
-
function connect() {
|
|
389
|
-
if (state.connection.state !== "closed" &&
|
|
390
|
-
state.connection.state !== "unavailable") {
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
const auth = prepareAuthEndpoint(context.authentication, context.fetchPolyfill);
|
|
394
|
-
const createWebSocket = prepareCreateWebSocket(context.liveblocksServer, context.WebSocketPolyfill);
|
|
395
|
-
updateConnection({ state: "authenticating" });
|
|
396
|
-
effects.authenticate(auth, createWebSocket);
|
|
397
|
-
}
|
|
398
|
-
function updatePresence(overrides, options) {
|
|
399
|
-
const oldValues = {};
|
|
400
|
-
if (state.buffer.presence == null) {
|
|
401
|
-
state.buffer.presence = {};
|
|
402
|
-
}
|
|
403
|
-
for (const key in overrides) {
|
|
404
|
-
state.buffer.presence[key] = overrides[key];
|
|
405
|
-
oldValues[key] = state.me[key];
|
|
406
|
-
}
|
|
407
|
-
state.me = Object.assign(Object.assign({}, state.me), overrides);
|
|
408
|
-
if (state.isBatching) {
|
|
409
|
-
if (options === null || options === void 0 ? void 0 : options.addToHistory) {
|
|
410
|
-
state.batch.reverseOps.push({ type: "presence", data: oldValues });
|
|
411
|
-
}
|
|
412
|
-
state.batch.updates.presence = true;
|
|
413
|
-
}
|
|
414
|
-
else {
|
|
415
|
-
tryFlushing();
|
|
416
|
-
if (options === null || options === void 0 ? void 0 : options.addToHistory) {
|
|
417
|
-
addToUndoStack([{ type: "presence", data: oldValues }]);
|
|
418
|
-
}
|
|
419
|
-
notify({ presence: true });
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
function authenticationSuccess(token, socket) {
|
|
423
|
-
socket.addEventListener("message", onMessage);
|
|
424
|
-
socket.addEventListener("open", onOpen);
|
|
425
|
-
socket.addEventListener("close", onClose);
|
|
426
|
-
socket.addEventListener("error", onError);
|
|
427
|
-
updateConnection({
|
|
428
|
-
state: "connecting",
|
|
429
|
-
id: token.actor,
|
|
430
|
-
userInfo: token.info,
|
|
431
|
-
userId: token.id,
|
|
432
|
-
});
|
|
433
|
-
state.idFactory = makeIdFactory(token.actor);
|
|
434
|
-
state.socket = socket;
|
|
435
|
-
}
|
|
436
|
-
function authenticationFailure(error) {
|
|
437
|
-
if (process.env.NODE_ENV !== "production") {
|
|
438
|
-
console.error("Call to authentication endpoint failed", error);
|
|
439
|
-
}
|
|
440
|
-
updateConnection({ state: "unavailable" });
|
|
441
|
-
state.numberOfRetry++;
|
|
442
|
-
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
443
|
-
}
|
|
444
|
-
function onVisibilityChange(visibilityState) {
|
|
445
|
-
if (visibilityState === "visible" && state.connection.state === "open") {
|
|
446
|
-
log("Heartbeat after visibility change");
|
|
447
|
-
heartbeat();
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
function onUpdatePresenceMessage(message) {
|
|
451
|
-
const user = state.users[message.actor];
|
|
452
|
-
if (user == null) {
|
|
453
|
-
state.users[message.actor] = {
|
|
454
|
-
connectionId: message.actor,
|
|
455
|
-
presence: message.data,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
state.users[message.actor] = {
|
|
460
|
-
id: user.id,
|
|
461
|
-
info: user.info,
|
|
462
|
-
connectionId: message.actor,
|
|
463
|
-
presence: Object.assign(Object.assign({}, user.presence), message.data),
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
return {
|
|
467
|
-
type: "update",
|
|
468
|
-
updates: message.data,
|
|
469
|
-
user: state.users[message.actor],
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
function onUserLeftMessage(message) {
|
|
473
|
-
const userLeftMessage = message;
|
|
474
|
-
const user = state.users[userLeftMessage.actor];
|
|
475
|
-
if (user) {
|
|
476
|
-
delete state.users[userLeftMessage.actor];
|
|
477
|
-
return { type: "leave", user };
|
|
478
|
-
}
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
function onRoomStateMessage(message) {
|
|
482
|
-
const newUsers = {};
|
|
483
|
-
for (const key in message.users) {
|
|
484
|
-
const connectionId = Number.parseInt(key);
|
|
485
|
-
const user = message.users[key];
|
|
486
|
-
newUsers[connectionId] = {
|
|
487
|
-
connectionId,
|
|
488
|
-
info: user.info,
|
|
489
|
-
id: user.id,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
state.users = newUsers;
|
|
493
|
-
return { type: "reset" };
|
|
494
|
-
}
|
|
495
|
-
function onNavigatorOnline() {
|
|
496
|
-
if (state.connection.state === "unavailable") {
|
|
497
|
-
log("Try to reconnect after connectivity change");
|
|
498
|
-
reconnect();
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
function onEvent(message) {
|
|
502
|
-
for (const listener of state.listeners.event) {
|
|
503
|
-
listener({ connectionId: message.actor, event: message.event });
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
function onUserJoinedMessage(message) {
|
|
507
|
-
state.users[message.actor] = {
|
|
508
|
-
connectionId: message.actor,
|
|
509
|
-
info: message.info,
|
|
510
|
-
id: message.id,
|
|
511
|
-
};
|
|
512
|
-
if (state.me) {
|
|
513
|
-
// Send current presence to new user
|
|
514
|
-
// TODO: Consider storing it on the backend
|
|
515
|
-
state.buffer.messages.push({
|
|
516
|
-
type: ClientMessageType.UpdatePresence,
|
|
517
|
-
data: state.me,
|
|
518
|
-
targetActor: message.actor,
|
|
519
|
-
});
|
|
520
|
-
tryFlushing();
|
|
521
|
-
}
|
|
522
|
-
return { type: "enter", user: state.users[message.actor] };
|
|
523
|
-
}
|
|
524
|
-
function onMessage(event) {
|
|
525
|
-
if (event.data === "pong") {
|
|
526
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
const message = JSON.parse(event.data);
|
|
530
|
-
let subMessages = [];
|
|
531
|
-
if (Array.isArray(message)) {
|
|
532
|
-
subMessages = message;
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
subMessages.push(message);
|
|
536
|
-
}
|
|
537
|
-
const updates = {
|
|
538
|
-
storageUpdates: new Map(),
|
|
539
|
-
others: [],
|
|
540
|
-
};
|
|
541
|
-
for (const subMessage of subMessages) {
|
|
542
|
-
switch (subMessage.type) {
|
|
543
|
-
case ServerMessageType.UserJoined: {
|
|
544
|
-
updates.others.push(onUserJoinedMessage(message));
|
|
545
|
-
break;
|
|
546
|
-
}
|
|
547
|
-
case ServerMessageType.UpdatePresence: {
|
|
548
|
-
updates.others.push(onUpdatePresenceMessage(subMessage));
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
case ServerMessageType.Event: {
|
|
552
|
-
onEvent(subMessage);
|
|
553
|
-
break;
|
|
554
|
-
}
|
|
555
|
-
case ServerMessageType.UserLeft: {
|
|
556
|
-
const event = onUserLeftMessage(subMessage);
|
|
557
|
-
if (event) {
|
|
558
|
-
updates.others.push(event);
|
|
559
|
-
}
|
|
560
|
-
break;
|
|
561
|
-
}
|
|
562
|
-
case ServerMessageType.RoomState: {
|
|
563
|
-
updates.others.push(onRoomStateMessage(subMessage));
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
case ServerMessageType.InitialStorageState: {
|
|
567
|
-
createOrUpdateRootFromMessage(subMessage);
|
|
568
|
-
applyAndSendOfflineOps();
|
|
569
|
-
_getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
case ServerMessageType.UpdateStorage: {
|
|
573
|
-
const applyResult = apply(subMessage.ops, false);
|
|
574
|
-
applyResult.updates.storageUpdates.forEach((value, key) => {
|
|
575
|
-
updates.storageUpdates.set(key, mergeStorageUpdates(updates.storageUpdates.get(key), value));
|
|
576
|
-
});
|
|
577
|
-
break;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
notify(updates);
|
|
582
|
-
}
|
|
583
|
-
// function onWakeUp() {
|
|
584
|
-
// // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example)
|
|
585
|
-
// // The client will not know that the server has probably close the connection even if the readyState is Open
|
|
586
|
-
// // 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
|
|
587
|
-
// if (state.connection.state === "open") {
|
|
588
|
-
// log("Try to reconnect after laptop wake up");
|
|
589
|
-
// reconnect();
|
|
590
|
-
// }
|
|
591
|
-
// }
|
|
592
|
-
function onClose(event) {
|
|
593
|
-
state.socket = null;
|
|
594
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
595
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
596
|
-
if (state.timeoutHandles.flush) {
|
|
597
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
598
|
-
}
|
|
599
|
-
clearTimeout(state.timeoutHandles.reconnect);
|
|
600
|
-
state.users = {};
|
|
601
|
-
notify({ others: [{ type: "reset" }] });
|
|
602
|
-
if (event.code >= 4000 && event.code <= 4100) {
|
|
603
|
-
updateConnection({ state: "failed" });
|
|
604
|
-
const error = new LiveblocksError(event.reason, event.code);
|
|
605
|
-
for (const listener of state.listeners.error) {
|
|
606
|
-
if (process.env.NODE_ENV !== "production") {
|
|
607
|
-
console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
|
|
608
|
-
}
|
|
609
|
-
listener(error);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
else if (event.wasClean === false) {
|
|
613
|
-
state.numberOfRetry++;
|
|
614
|
-
const delay = getRetryDelay();
|
|
615
|
-
if (process.env.NODE_ENV !== "production") {
|
|
616
|
-
console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
|
|
617
|
-
}
|
|
618
|
-
updateConnection({ state: "unavailable" });
|
|
619
|
-
state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
updateConnection({ state: "closed" });
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
function updateConnection(connection) {
|
|
626
|
-
state.connection = connection;
|
|
627
|
-
for (const listener of state.listeners.connection) {
|
|
628
|
-
listener(connection.state);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
function getRetryDelay() {
|
|
632
|
-
return BACKOFF_RETRY_DELAYS[state.numberOfRetry < BACKOFF_RETRY_DELAYS.length
|
|
633
|
-
? state.numberOfRetry
|
|
634
|
-
: BACKOFF_RETRY_DELAYS.length - 1];
|
|
635
|
-
}
|
|
636
|
-
function onError() { }
|
|
637
|
-
function onOpen() {
|
|
638
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
639
|
-
state.intervalHandles.heartbeat = effects.startHeartbeatInterval();
|
|
640
|
-
if (state.connection.state === "connecting") {
|
|
641
|
-
updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
|
|
642
|
-
state.numberOfRetry = 0;
|
|
643
|
-
state.lastConnectionId = state.connection.id;
|
|
644
|
-
if (state.root) {
|
|
645
|
-
state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
|
|
646
|
-
}
|
|
647
|
-
tryFlushing();
|
|
648
|
-
}
|
|
649
|
-
else {
|
|
650
|
-
// TODO
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
function heartbeat() {
|
|
654
|
-
if (state.socket == null) {
|
|
655
|
-
// Should never happen, because we clear the pong timeout when the connection is dropped explictly
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
659
|
-
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
|
|
660
|
-
if (state.socket.readyState === state.socket.OPEN) {
|
|
661
|
-
state.socket.send("ping");
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
function pongTimeout() {
|
|
665
|
-
log("Pong timeout. Trying to reconnect.");
|
|
666
|
-
reconnect();
|
|
667
|
-
}
|
|
668
|
-
function reconnect() {
|
|
669
|
-
if (state.socket) {
|
|
670
|
-
state.socket.removeEventListener("open", onOpen);
|
|
671
|
-
state.socket.removeEventListener("message", onMessage);
|
|
672
|
-
state.socket.removeEventListener("close", onClose);
|
|
673
|
-
state.socket.removeEventListener("error", onError);
|
|
674
|
-
state.socket.close();
|
|
675
|
-
state.socket = null;
|
|
676
|
-
}
|
|
677
|
-
updateConnection({ state: "unavailable" });
|
|
678
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
679
|
-
if (state.timeoutHandles.flush) {
|
|
680
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
681
|
-
}
|
|
682
|
-
clearTimeout(state.timeoutHandles.reconnect);
|
|
683
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
684
|
-
connect();
|
|
685
|
-
}
|
|
686
|
-
function applyAndSendOfflineOps() {
|
|
687
|
-
if (state.offlineOperations.size === 0) {
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
const messages = [];
|
|
691
|
-
const ops = Array.from(state.offlineOperations.values());
|
|
692
|
-
const result = apply(ops, true);
|
|
693
|
-
messages.push({
|
|
694
|
-
type: ClientMessageType.UpdateStorage,
|
|
695
|
-
ops: ops,
|
|
696
|
-
});
|
|
697
|
-
notify(result.updates);
|
|
698
|
-
effects.send(messages);
|
|
699
|
-
}
|
|
700
|
-
function tryFlushing() {
|
|
701
|
-
const storageOps = state.buffer.storageOperations;
|
|
702
|
-
if (storageOps.length > 0) {
|
|
703
|
-
storageOps.forEach((op) => {
|
|
704
|
-
state.offlineOperations.set(op.opId, op);
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
if (state.socket == null || state.socket.readyState !== state.socket.OPEN) {
|
|
708
|
-
state.buffer.storageOperations = [];
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
const now = Date.now();
|
|
712
|
-
const elapsedTime = now - state.lastFlushTime;
|
|
713
|
-
if (elapsedTime > context.throttleDelay) {
|
|
714
|
-
const messages = flushDataToMessages(state);
|
|
715
|
-
if (messages.length === 0) {
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
effects.send(messages);
|
|
719
|
-
state.buffer = {
|
|
720
|
-
messages: [],
|
|
721
|
-
storageOperations: [],
|
|
722
|
-
presence: null,
|
|
723
|
-
};
|
|
724
|
-
state.lastFlushTime = now;
|
|
725
|
-
}
|
|
726
|
-
else {
|
|
727
|
-
if (state.timeoutHandles.flush != null) {
|
|
728
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
729
|
-
}
|
|
730
|
-
state.timeoutHandles.flush = effects.delayFlush(context.throttleDelay - (now - state.lastFlushTime));
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
function flushDataToMessages(state) {
|
|
734
|
-
const messages = [];
|
|
735
|
-
if (state.buffer.presence) {
|
|
736
|
-
messages.push({
|
|
737
|
-
type: ClientMessageType.UpdatePresence,
|
|
738
|
-
data: state.buffer.presence,
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
for (const event of state.buffer.messages) {
|
|
742
|
-
messages.push(event);
|
|
743
|
-
}
|
|
744
|
-
if (state.buffer.storageOperations.length > 0) {
|
|
745
|
-
messages.push({
|
|
746
|
-
type: ClientMessageType.UpdateStorage,
|
|
747
|
-
ops: state.buffer.storageOperations,
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
return messages;
|
|
751
|
-
}
|
|
752
|
-
function disconnect() {
|
|
753
|
-
if (state.socket) {
|
|
754
|
-
state.socket.removeEventListener("open", onOpen);
|
|
755
|
-
state.socket.removeEventListener("message", onMessage);
|
|
756
|
-
state.socket.removeEventListener("close", onClose);
|
|
757
|
-
state.socket.removeEventListener("error", onError);
|
|
758
|
-
state.socket.close();
|
|
759
|
-
state.socket = null;
|
|
760
|
-
}
|
|
761
|
-
updateConnection({ state: "closed" });
|
|
762
|
-
if (state.timeoutHandles.flush) {
|
|
763
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
764
|
-
}
|
|
765
|
-
clearTimeout(state.timeoutHandles.reconnect);
|
|
766
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
767
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
768
|
-
state.users = {};
|
|
769
|
-
notify({ others: [{ type: "reset" }] });
|
|
770
|
-
clearListeners();
|
|
771
|
-
}
|
|
772
|
-
function clearListeners() {
|
|
773
|
-
for (const key in state.listeners) {
|
|
774
|
-
state.listeners[key] = [];
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
function getPresence() {
|
|
778
|
-
return state.me;
|
|
779
|
-
}
|
|
780
|
-
function getOthers() {
|
|
781
|
-
return state.others;
|
|
782
|
-
}
|
|
783
|
-
function broadcastEvent(event, options = {
|
|
784
|
-
shouldQueueEventIfNotReady: false,
|
|
785
|
-
}) {
|
|
786
|
-
if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
state.buffer.messages.push({
|
|
790
|
-
type: ClientMessageType.ClientEvent,
|
|
791
|
-
event,
|
|
792
|
-
});
|
|
793
|
-
tryFlushing();
|
|
794
|
-
}
|
|
795
|
-
function dispatch(ops) {
|
|
796
|
-
state.buffer.storageOperations.push(...ops);
|
|
797
|
-
tryFlushing();
|
|
798
|
-
}
|
|
799
|
-
let _getInitialStatePromise = null;
|
|
800
|
-
let _getInitialStateResolver = null;
|
|
801
|
-
function getStorage() {
|
|
802
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
803
|
-
if (state.root) {
|
|
804
|
-
return {
|
|
805
|
-
root: state.root,
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
if (_getInitialStatePromise == null) {
|
|
809
|
-
state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
|
|
810
|
-
tryFlushing();
|
|
811
|
-
_getInitialStatePromise = new Promise((resolve) => (_getInitialStateResolver = resolve));
|
|
812
|
-
}
|
|
813
|
-
yield _getInitialStatePromise;
|
|
814
|
-
return {
|
|
815
|
-
root: state.root,
|
|
816
|
-
};
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
function undo() {
|
|
820
|
-
if (state.isBatching) {
|
|
821
|
-
throw new Error("undo is not allowed during a batch");
|
|
822
|
-
}
|
|
823
|
-
const historyItem = state.undoStack.pop();
|
|
824
|
-
if (historyItem == null) {
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
827
|
-
state.isHistoryPaused = false;
|
|
828
|
-
const result = apply(historyItem, true);
|
|
829
|
-
notify(result.updates);
|
|
830
|
-
state.redoStack.push(result.reverse);
|
|
831
|
-
for (const op of historyItem) {
|
|
832
|
-
if (op.type !== "presence") {
|
|
833
|
-
state.buffer.storageOperations.push(op);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
tryFlushing();
|
|
837
|
-
}
|
|
838
|
-
function redo() {
|
|
839
|
-
if (state.isBatching) {
|
|
840
|
-
throw new Error("redo is not allowed during a batch");
|
|
841
|
-
}
|
|
842
|
-
const historyItem = state.redoStack.pop();
|
|
843
|
-
if (historyItem == null) {
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
state.isHistoryPaused = false;
|
|
847
|
-
const result = apply(historyItem, true);
|
|
848
|
-
notify(result.updates);
|
|
849
|
-
state.undoStack.push(result.reverse);
|
|
850
|
-
for (const op of historyItem) {
|
|
851
|
-
if (op.type !== "presence") {
|
|
852
|
-
state.buffer.storageOperations.push(op);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
tryFlushing();
|
|
856
|
-
}
|
|
857
|
-
function batch(callback) {
|
|
858
|
-
if (state.isBatching) {
|
|
859
|
-
throw new Error("batch should not be called during a batch");
|
|
860
|
-
}
|
|
861
|
-
state.isBatching = true;
|
|
862
|
-
try {
|
|
863
|
-
callback();
|
|
864
|
-
}
|
|
865
|
-
finally {
|
|
866
|
-
state.isBatching = false;
|
|
867
|
-
if (state.batch.reverseOps.length > 0) {
|
|
868
|
-
addToUndoStack(state.batch.reverseOps);
|
|
869
|
-
}
|
|
870
|
-
if (state.batch.ops.length > 0) {
|
|
871
|
-
// Only clear the redo stack if something has changed during a batch
|
|
872
|
-
// Clear the redo stack because batch is always called from a local operation
|
|
873
|
-
state.redoStack = [];
|
|
874
|
-
}
|
|
875
|
-
if (state.batch.ops.length > 0) {
|
|
876
|
-
dispatch(state.batch.ops);
|
|
877
|
-
}
|
|
878
|
-
notify(state.batch.updates);
|
|
879
|
-
state.batch = {
|
|
880
|
-
ops: [],
|
|
881
|
-
reverseOps: [],
|
|
882
|
-
updates: {
|
|
883
|
-
others: [],
|
|
884
|
-
storageUpdates: new Map(),
|
|
885
|
-
presence: false,
|
|
886
|
-
},
|
|
887
|
-
};
|
|
888
|
-
tryFlushing();
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
function pauseHistory() {
|
|
892
|
-
state.pausedHistory = [];
|
|
893
|
-
state.isHistoryPaused = true;
|
|
894
|
-
}
|
|
895
|
-
function resumeHistory() {
|
|
896
|
-
state.isHistoryPaused = false;
|
|
897
|
-
if (state.pausedHistory.length > 0) {
|
|
898
|
-
addToUndoStack(state.pausedHistory);
|
|
899
|
-
}
|
|
900
|
-
state.pausedHistory = [];
|
|
901
|
-
}
|
|
902
|
-
function simulateSocketClose() {
|
|
903
|
-
if (state.socket) {
|
|
904
|
-
state.socket.close();
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
function simulateSendCloseEvent(event) {
|
|
908
|
-
if (state.socket) {
|
|
909
|
-
onClose(event);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
return {
|
|
913
|
-
// Internal
|
|
914
|
-
onClose,
|
|
915
|
-
onMessage,
|
|
916
|
-
authenticationSuccess,
|
|
917
|
-
heartbeat,
|
|
918
|
-
onNavigatorOnline,
|
|
919
|
-
// Internal dev tools
|
|
920
|
-
simulateSocketClose,
|
|
921
|
-
simulateSendCloseEvent,
|
|
922
|
-
// onWakeUp,
|
|
923
|
-
onVisibilityChange,
|
|
924
|
-
getUndoStack: () => state.undoStack,
|
|
925
|
-
getItemsCount: () => state.items.size,
|
|
926
|
-
// Core
|
|
927
|
-
connect,
|
|
928
|
-
disconnect,
|
|
929
|
-
subscribe,
|
|
930
|
-
unsubscribe,
|
|
931
|
-
// Presence
|
|
932
|
-
updatePresence,
|
|
933
|
-
broadcastEvent,
|
|
934
|
-
batch,
|
|
935
|
-
undo,
|
|
936
|
-
redo,
|
|
937
|
-
pauseHistory,
|
|
938
|
-
resumeHistory,
|
|
939
|
-
getStorage,
|
|
940
|
-
selectors: {
|
|
941
|
-
// Core
|
|
942
|
-
getConnectionState,
|
|
943
|
-
getSelf,
|
|
944
|
-
// Presence
|
|
945
|
-
getPresence,
|
|
946
|
-
getOthers,
|
|
947
|
-
},
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
export function defaultState(me, defaultStorageRoot) {
|
|
951
|
-
return {
|
|
952
|
-
connection: { state: "closed" },
|
|
953
|
-
lastConnectionId: null,
|
|
954
|
-
socket: null,
|
|
955
|
-
listeners: {
|
|
956
|
-
event: [],
|
|
957
|
-
others: [],
|
|
958
|
-
"my-presence": [],
|
|
959
|
-
error: [],
|
|
960
|
-
connection: [],
|
|
961
|
-
storage: [],
|
|
962
|
-
},
|
|
963
|
-
numberOfRetry: 0,
|
|
964
|
-
lastFlushTime: 0,
|
|
965
|
-
timeoutHandles: {
|
|
966
|
-
flush: null,
|
|
967
|
-
reconnect: 0,
|
|
968
|
-
pongTimeout: 0,
|
|
969
|
-
},
|
|
970
|
-
buffer: {
|
|
971
|
-
presence: me == null ? {} : me,
|
|
972
|
-
messages: [],
|
|
973
|
-
storageOperations: [],
|
|
974
|
-
},
|
|
975
|
-
intervalHandles: {
|
|
976
|
-
heartbeat: 0,
|
|
977
|
-
},
|
|
978
|
-
me: me == null ? {} : me,
|
|
979
|
-
users: {},
|
|
980
|
-
others: makeOthers({}),
|
|
981
|
-
defaultStorageRoot,
|
|
982
|
-
idFactory: null,
|
|
983
|
-
// Storage
|
|
984
|
-
clock: 0,
|
|
985
|
-
opClock: 0,
|
|
986
|
-
items: new Map(),
|
|
987
|
-
root: undefined,
|
|
988
|
-
undoStack: [],
|
|
989
|
-
redoStack: [],
|
|
990
|
-
isHistoryPaused: false,
|
|
991
|
-
pausedHistory: [],
|
|
992
|
-
isBatching: false,
|
|
993
|
-
batch: {
|
|
994
|
-
ops: [],
|
|
995
|
-
updates: {
|
|
996
|
-
storageUpdates: new Map(),
|
|
997
|
-
presence: false,
|
|
998
|
-
others: [],
|
|
999
|
-
},
|
|
1000
|
-
reverseOps: [],
|
|
1001
|
-
},
|
|
1002
|
-
offlineOperations: new Map(),
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
export function createRoom(options, context) {
|
|
1006
|
-
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
|
|
1007
|
-
const machine = makeStateMachine(state, context);
|
|
1008
|
-
const room = {
|
|
1009
|
-
/////////////
|
|
1010
|
-
// Core //
|
|
1011
|
-
/////////////
|
|
1012
|
-
getConnectionState: machine.selectors.getConnectionState,
|
|
1013
|
-
getSelf: machine.selectors.getSelf,
|
|
1014
|
-
subscribe: machine.subscribe,
|
|
1015
|
-
unsubscribe: machine.unsubscribe,
|
|
1016
|
-
//////////////
|
|
1017
|
-
// Presence //
|
|
1018
|
-
//////////////
|
|
1019
|
-
getPresence: machine.selectors.getPresence,
|
|
1020
|
-
updatePresence: machine.updatePresence,
|
|
1021
|
-
getOthers: machine.selectors.getOthers,
|
|
1022
|
-
broadcastEvent: machine.broadcastEvent,
|
|
1023
|
-
getStorage: machine.getStorage,
|
|
1024
|
-
batch: machine.batch,
|
|
1025
|
-
history: {
|
|
1026
|
-
undo: machine.undo,
|
|
1027
|
-
redo: machine.redo,
|
|
1028
|
-
pause: machine.pauseHistory,
|
|
1029
|
-
resume: machine.resumeHistory,
|
|
1030
|
-
},
|
|
1031
|
-
// @ts-ignore
|
|
1032
|
-
internalDevTools: {
|
|
1033
|
-
closeWebsocket: machine.simulateSocketClose,
|
|
1034
|
-
sendCloseEvent: machine.simulateSendCloseEvent,
|
|
1035
|
-
},
|
|
1036
|
-
};
|
|
1037
|
-
return {
|
|
1038
|
-
connect: machine.connect,
|
|
1039
|
-
disconnect: machine.disconnect,
|
|
1040
|
-
onNavigatorOnline: machine.onNavigatorOnline,
|
|
1041
|
-
onVisibilityChange: machine.onVisibilityChange,
|
|
1042
|
-
room,
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
class LiveblocksError extends Error {
|
|
1046
|
-
constructor(message, code) {
|
|
1047
|
-
super(message);
|
|
1048
|
-
this.code = code;
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
function parseToken(token) {
|
|
1052
|
-
const tokenParts = token.split(".");
|
|
1053
|
-
if (tokenParts.length !== 3) {
|
|
1054
|
-
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1055
|
-
}
|
|
1056
|
-
const data = JSON.parse(atob(tokenParts[1]));
|
|
1057
|
-
if (typeof data.actor !== "number") {
|
|
1058
|
-
throw new Error(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
1059
|
-
}
|
|
1060
|
-
return data;
|
|
1061
|
-
}
|
|
1062
|
-
function prepareCreateWebSocket(liveblocksServer, WebSocketPolyfill) {
|
|
1063
|
-
if (typeof window === "undefined" && WebSocketPolyfill == null) {
|
|
1064
|
-
throw new Error("To use Liveblocks client in a non-dom environment, you need to provide a WebSocket polyfill.");
|
|
1065
|
-
}
|
|
1066
|
-
const ws = WebSocketPolyfill || WebSocket;
|
|
1067
|
-
return (token) => {
|
|
1068
|
-
return new ws(`${liveblocksServer}/?token=${token}`);
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
function prepareAuthEndpoint(authentication, fetchPolyfill) {
|
|
1072
|
-
if (authentication.type === "public") {
|
|
1073
|
-
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1074
|
-
throw new Error("To use Liveblocks client in a non-dom environment with a publicApiKey, you need to provide a fetch polyfill.");
|
|
1075
|
-
}
|
|
1076
|
-
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1077
|
-
room,
|
|
1078
|
-
publicApiKey: authentication.publicApiKey,
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
if (authentication.type === "private") {
|
|
1082
|
-
if (typeof window === "undefined" && fetchPolyfill == null) {
|
|
1083
|
-
throw new Error("To use Liveblocks client in a non-dom environment with a url as auth endpoint, you need to provide a fetch polyfill.");
|
|
1084
|
-
}
|
|
1085
|
-
return (room) => fetchAuthEndpoint(fetchPolyfill || fetch, authentication.url, {
|
|
1086
|
-
room,
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
|
-
if (authentication.type === "custom") {
|
|
1090
|
-
return authentication.callback;
|
|
1091
|
-
}
|
|
1092
|
-
throw new Error("Internal error. Unexpected authentication type");
|
|
1093
|
-
}
|
|
1094
|
-
function fetchAuthEndpoint(fetch, endpoint, body) {
|
|
1095
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1096
|
-
const res = yield fetch(endpoint, {
|
|
1097
|
-
method: "POST",
|
|
1098
|
-
headers: {
|
|
1099
|
-
"Content-Type": "application/json",
|
|
1100
|
-
},
|
|
1101
|
-
body: JSON.stringify(body),
|
|
1102
|
-
});
|
|
1103
|
-
if (!res.ok) {
|
|
1104
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1105
|
-
}
|
|
1106
|
-
let authResponse = null;
|
|
1107
|
-
try {
|
|
1108
|
-
authResponse = yield res.json();
|
|
1109
|
-
}
|
|
1110
|
-
catch (er) {
|
|
1111
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1112
|
-
}
|
|
1113
|
-
if (typeof authResponse.token !== "string") {
|
|
1114
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
1115
|
-
}
|
|
1116
|
-
return authResponse;
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
class AuthenticationError extends Error {
|
|
1120
|
-
constructor(message) {
|
|
1121
|
-
super(message);
|
|
1122
|
-
}
|
|
1123
|
-
}
|