@liveblocks/react 0.9.0 → 0.12.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +22 -41
- package/lib/index.js +154 -1462
- package/lib/index.js.map +1 -1
- package/package.json +2 -4
package/lib/index.js
CHANGED
|
@@ -1,1397 +1,59 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
2
|
|
|
3
|
+
var client = require('@liveblocks/client');
|
|
3
4
|
var React = require('react');
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
return getNextPosition(before);
|
|
57
|
-
}
|
|
58
|
-
// Insert at the start
|
|
59
|
-
if (before == null && after != null) {
|
|
60
|
-
return getPreviousPosition(after);
|
|
61
|
-
}
|
|
62
|
-
return pos(makePositionFromCodes(posCodes(before), posCodes(after)));
|
|
63
|
-
}
|
|
64
|
-
function getPreviousPosition(after) {
|
|
65
|
-
const result = [];
|
|
66
|
-
const afterCodes = posCodes(after);
|
|
67
|
-
for (let i = 0; i < afterCodes.length; i++) {
|
|
68
|
-
const code = afterCodes[i];
|
|
69
|
-
if (code <= min + 1) {
|
|
70
|
-
result.push(min);
|
|
71
|
-
if (afterCodes.length - 1 === i) {
|
|
72
|
-
result.push(max - 1);
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
result.push(code - 1);
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return pos(result);
|
|
82
|
-
}
|
|
83
|
-
function getNextPosition(before) {
|
|
84
|
-
const result = [];
|
|
85
|
-
const beforeCodes = posCodes(before);
|
|
86
|
-
for (let i = 0; i < beforeCodes.length; i++) {
|
|
87
|
-
const code = beforeCodes[i];
|
|
88
|
-
if (code === max - 1) {
|
|
89
|
-
result.push(code);
|
|
90
|
-
if (beforeCodes.length - 1 === i) {
|
|
91
|
-
result.push(min + 1);
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
result.push(code + 1);
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return pos(result);
|
|
101
|
-
}
|
|
102
|
-
function makePositionFromCodes(before, after) {
|
|
103
|
-
let index = 0;
|
|
104
|
-
const result = [];
|
|
105
|
-
while (true) {
|
|
106
|
-
const beforeDigit = before[index] || min;
|
|
107
|
-
const afterDigit = after[index] || max;
|
|
108
|
-
if (beforeDigit > afterDigit) {
|
|
109
|
-
throw new Error(`Impossible to generate position between ${before} and ${after}`);
|
|
110
|
-
}
|
|
111
|
-
if (beforeDigit === afterDigit) {
|
|
112
|
-
result.push(beforeDigit);
|
|
113
|
-
index++;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
if (afterDigit - beforeDigit === 1) {
|
|
117
|
-
result.push(beforeDigit);
|
|
118
|
-
result.push(...makePositionFromCodes(before.slice(index + 1), []));
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
const mid = beforeDigit + Math.floor((afterDigit - beforeDigit) / 2);
|
|
122
|
-
result.push(mid);
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
return result;
|
|
126
|
-
}
|
|
127
|
-
function posCodes(str) {
|
|
128
|
-
const codes = [];
|
|
129
|
-
for (let i = 0; i < str.length; i++) {
|
|
130
|
-
codes.push(str.charCodeAt(i));
|
|
131
|
-
}
|
|
132
|
-
return codes;
|
|
133
|
-
}
|
|
134
|
-
function pos(codes) {
|
|
135
|
-
return String.fromCharCode(...codes);
|
|
136
|
-
}
|
|
137
|
-
function compare(itemA, itemB) {
|
|
138
|
-
const aCodes = posCodes(itemA.position);
|
|
139
|
-
const bCodes = posCodes(itemB.position);
|
|
140
|
-
const maxLength = Math.max(aCodes.length, bCodes.length);
|
|
141
|
-
for (let i = 0; i < maxLength; i++) {
|
|
142
|
-
const a = aCodes[i] == null ? min : aCodes[i];
|
|
143
|
-
const b = bCodes[i] == null ? min : bCodes[i];
|
|
144
|
-
if (a === b) {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
return a - b;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
throw new Error(`Impossible to compare similar position "${itemA.position}" and "${itemB.position}"`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const RECORD = Symbol("liveblocks.record");
|
|
155
|
-
const LIST = Symbol("liveblocks.list");
|
|
156
|
-
function createRecord(id, data) {
|
|
157
|
-
return Object.assign({ id, $$type: RECORD }, data);
|
|
158
|
-
}
|
|
159
|
-
function createList(id, items = []) {
|
|
160
|
-
return {
|
|
161
|
-
id,
|
|
162
|
-
$$type: LIST,
|
|
163
|
-
length: items.length,
|
|
164
|
-
toArray: () => items,
|
|
165
|
-
map: (callback) => items.map(callback),
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function noop() { }
|
|
169
|
-
class Doc {
|
|
170
|
-
constructor(root, _cache, _emit) {
|
|
171
|
-
this.root = root;
|
|
172
|
-
this._cache = _cache;
|
|
173
|
-
this._emit = _emit;
|
|
174
|
-
}
|
|
175
|
-
static empty(id = "root", emit = noop) {
|
|
176
|
-
const root = {
|
|
177
|
-
id,
|
|
178
|
-
$$type: RECORD,
|
|
179
|
-
};
|
|
180
|
-
return new Doc(root, { links: new Map(), listCache: new Map() }, emit);
|
|
181
|
-
}
|
|
182
|
-
static createFromRoot(data, id = "root", emit = noop) {
|
|
183
|
-
let doc = Doc.empty(id, emit);
|
|
184
|
-
doc = doc.updateRecord(doc.root.id, data);
|
|
185
|
-
return doc;
|
|
186
|
-
}
|
|
187
|
-
static load(root, emit = noop) {
|
|
188
|
-
let doc = Doc.empty(root.id, emit);
|
|
189
|
-
return doc.dispatch({
|
|
190
|
-
type: OpType.RecordUpdate,
|
|
191
|
-
id: root.id,
|
|
192
|
-
data: root.data,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
get data() {
|
|
196
|
-
return this.root;
|
|
197
|
-
}
|
|
198
|
-
dispatch(op, shouldEmit = false) {
|
|
199
|
-
if (shouldEmit) {
|
|
200
|
-
this._emit(op);
|
|
201
|
-
}
|
|
202
|
-
if (op.id === this.root.id) {
|
|
203
|
-
const node = dispatch(this.root, op, this._cache, []);
|
|
204
|
-
return new Doc(node, this._cache, this._emit);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
const links = getAllLinks(op.id, this.root.id, this._cache.links);
|
|
208
|
-
const node = dispatch(this.root, op, this._cache, links);
|
|
209
|
-
return new Doc(node, this._cache, this._emit);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
getChild(id) {
|
|
213
|
-
if (id === this.root.id) {
|
|
214
|
-
return this.root;
|
|
215
|
-
}
|
|
216
|
-
const allLinks = getAllLinks(id, this.root.id, this._cache.links);
|
|
217
|
-
return getChildDeep(this.root, id, allLinks, this._cache);
|
|
218
|
-
}
|
|
219
|
-
updateRecord(id, overrides) {
|
|
220
|
-
const currentRecord = this.getChild(id);
|
|
221
|
-
if (currentRecord == null) {
|
|
222
|
-
throw new Error(`Record with id "${id}" does not exist`);
|
|
223
|
-
}
|
|
224
|
-
let data = {};
|
|
225
|
-
for (const key in overrides) {
|
|
226
|
-
const value = overrides[key];
|
|
227
|
-
data[key] = serialize(value);
|
|
228
|
-
}
|
|
229
|
-
const op = {
|
|
230
|
-
id: currentRecord.id,
|
|
231
|
-
type: OpType.RecordUpdate,
|
|
232
|
-
data,
|
|
233
|
-
};
|
|
234
|
-
return this.dispatch(op, true);
|
|
235
|
-
}
|
|
236
|
-
pushItem(id, item) {
|
|
237
|
-
const list = this.getChild(id);
|
|
238
|
-
if (list == null) {
|
|
239
|
-
throw new Error(`List with id "${id}" does not exist`);
|
|
240
|
-
}
|
|
241
|
-
if (list.$$type !== LIST) {
|
|
242
|
-
throw new Error(`Node with id "${id}" is not a list`);
|
|
243
|
-
}
|
|
244
|
-
if (!isRecord(item)) {
|
|
245
|
-
throw new Error("List can't only have Record as children");
|
|
246
|
-
}
|
|
247
|
-
const data = serialize(item);
|
|
248
|
-
if (list.length === 0) {
|
|
249
|
-
return this.dispatch({
|
|
250
|
-
type: OpType.ListInsert,
|
|
251
|
-
id: list.id,
|
|
252
|
-
position: makePosition(),
|
|
253
|
-
data,
|
|
254
|
-
}, true);
|
|
255
|
-
}
|
|
256
|
-
const items = sortedListItems(getListItems(this._cache, id));
|
|
257
|
-
const [tailPosition] = items[items.length - 1];
|
|
258
|
-
const position = makePosition(tailPosition);
|
|
259
|
-
const operation = {
|
|
260
|
-
type: OpType.ListInsert,
|
|
261
|
-
id: list.id,
|
|
262
|
-
position,
|
|
263
|
-
data,
|
|
264
|
-
};
|
|
265
|
-
return this.dispatch(operation, true);
|
|
266
|
-
}
|
|
267
|
-
moveItem(id, index, targetIndex) {
|
|
268
|
-
const list = this.getChild(id);
|
|
269
|
-
if (list == null) {
|
|
270
|
-
throw new Error(`List with id "${id}" does not exist`);
|
|
271
|
-
}
|
|
272
|
-
if (list.$$type !== LIST) {
|
|
273
|
-
throw new Error(`Node with id "${id}" is not a list`);
|
|
274
|
-
}
|
|
275
|
-
const items = sortedListItems(getListItems(this._cache, id));
|
|
276
|
-
if (targetIndex < 0) {
|
|
277
|
-
throw new Error("targetIndex cannot be less than 0");
|
|
278
|
-
}
|
|
279
|
-
if (targetIndex >= items.length) {
|
|
280
|
-
throw new Error("targetIndex cannot be greater or equal than the list length");
|
|
281
|
-
}
|
|
282
|
-
if (index < 0) {
|
|
283
|
-
throw new Error("index cannot be less than 0");
|
|
284
|
-
}
|
|
285
|
-
if (index >= items.length) {
|
|
286
|
-
throw new Error("index cannot be greater or equal than the list length");
|
|
287
|
-
}
|
|
288
|
-
if (index === targetIndex) {
|
|
289
|
-
return this;
|
|
290
|
-
}
|
|
291
|
-
let beforePosition = null;
|
|
292
|
-
let afterPosition = null;
|
|
293
|
-
if (index < targetIndex) {
|
|
294
|
-
afterPosition =
|
|
295
|
-
targetIndex === items.length - 1
|
|
296
|
-
? undefined
|
|
297
|
-
: items[targetIndex + 1][0];
|
|
298
|
-
beforePosition = items[targetIndex][0];
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
afterPosition = items[targetIndex][0];
|
|
302
|
-
beforePosition =
|
|
303
|
-
targetIndex === 0 ? undefined : items[targetIndex - 1][0];
|
|
304
|
-
}
|
|
305
|
-
const position = makePosition(beforePosition, afterPosition);
|
|
306
|
-
const [, item] = items[index];
|
|
307
|
-
return this.dispatch({
|
|
308
|
-
type: OpType.ListMove,
|
|
309
|
-
id: list.id,
|
|
310
|
-
itemId: item.id,
|
|
311
|
-
position,
|
|
312
|
-
}, true);
|
|
313
|
-
}
|
|
314
|
-
deleteItem(id, index) {
|
|
315
|
-
const list = this.getChild(id);
|
|
316
|
-
if (list == null) {
|
|
317
|
-
throw new Error(`List with id "${id}" does not exist`);
|
|
318
|
-
}
|
|
319
|
-
if (list.$$type !== LIST) {
|
|
320
|
-
throw new Error(`Node with id "${id}" is not a list`);
|
|
321
|
-
}
|
|
322
|
-
const items = sortedListItems(getListItems(this._cache, id));
|
|
323
|
-
const [, item] = items[index];
|
|
324
|
-
return this.dispatch({
|
|
325
|
-
type: OpType.ListRemove,
|
|
326
|
-
id: list.id,
|
|
327
|
-
itemId: item.id,
|
|
328
|
-
}, true);
|
|
329
|
-
}
|
|
330
|
-
deleteItemById(id, itemId) {
|
|
331
|
-
const list = this.getChild(id);
|
|
332
|
-
if (list == null) {
|
|
333
|
-
throw new Error(`List with id "${id}" does not exist`);
|
|
334
|
-
}
|
|
335
|
-
if (list.$$type !== LIST) {
|
|
336
|
-
throw new Error(`Node with id "${id}" is not a list`);
|
|
337
|
-
}
|
|
338
|
-
const itemsMap = getListItems(this._cache, id);
|
|
339
|
-
let item = null;
|
|
340
|
-
for (const [, crdt] of itemsMap) {
|
|
341
|
-
if (crdt.id === itemId) {
|
|
342
|
-
item = crdt;
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (item == null) {
|
|
347
|
-
throw new Error(`List with id "${id}" does not have an item with id "${itemId}"`);
|
|
348
|
-
}
|
|
349
|
-
return this.dispatch({
|
|
350
|
-
type: OpType.ListRemove,
|
|
351
|
-
id: list.id,
|
|
352
|
-
itemId: item.id,
|
|
353
|
-
}, true);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function getAllLinks(id, rootId, links) {
|
|
357
|
-
let currentId = id;
|
|
358
|
-
const result = [];
|
|
359
|
-
do {
|
|
360
|
-
const link = links.get(currentId);
|
|
361
|
-
if (link == null) {
|
|
362
|
-
throw new Error(`Can't find link for id "${currentId}"`);
|
|
363
|
-
}
|
|
364
|
-
currentId = link.parentId;
|
|
365
|
-
result.push(link);
|
|
366
|
-
} while (currentId !== rootId);
|
|
367
|
-
return result;
|
|
368
|
-
}
|
|
369
|
-
function deserializeList(serialized, cache) {
|
|
370
|
-
const listItems = new Map();
|
|
371
|
-
for (const position in serialized.data) {
|
|
372
|
-
const item = deserialize(serialized.data[position], cache);
|
|
373
|
-
if (!isRecord(item)) {
|
|
374
|
-
throw new Error("TODO");
|
|
375
|
-
}
|
|
376
|
-
listItems.set(position, item);
|
|
377
|
-
cache.links.set(item.id, { parentId: serialized.id, parentKey: position });
|
|
378
|
-
}
|
|
379
|
-
cache.listCache.set(serialized.id, listItems);
|
|
380
|
-
return createList(serialized.id, listItemsToArray(listItems));
|
|
381
|
-
}
|
|
382
|
-
function getListItems(cache, listId) {
|
|
383
|
-
const items = cache.listCache.get(listId);
|
|
384
|
-
if (items == null) {
|
|
385
|
-
throw new Error(`Can't find list cache for id "${listId}"`);
|
|
386
|
-
}
|
|
387
|
-
return items;
|
|
388
|
-
}
|
|
389
|
-
function deserializeRecord(serialized, cache) {
|
|
390
|
-
const result = {
|
|
391
|
-
id: serialized.id,
|
|
392
|
-
$$type: RECORD,
|
|
393
|
-
};
|
|
394
|
-
for (const key in serialized.data) {
|
|
395
|
-
const item = deserialize(serialized.data[key], cache);
|
|
396
|
-
if (isCrdt(item)) {
|
|
397
|
-
cache.links.set(item.id, {
|
|
398
|
-
parentId: serialized.id,
|
|
399
|
-
parentKey: key,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
result[key] = item;
|
|
403
|
-
}
|
|
404
|
-
return result;
|
|
405
|
-
}
|
|
406
|
-
function deserialize(serialized, cache) {
|
|
407
|
-
switch (serialized.type) {
|
|
408
|
-
case CrdtType.Register: {
|
|
409
|
-
return serialized.data;
|
|
410
|
-
}
|
|
411
|
-
case CrdtType.Record: {
|
|
412
|
-
return deserializeRecord(serialized, cache);
|
|
413
|
-
}
|
|
414
|
-
case CrdtType.List: {
|
|
415
|
-
return deserializeList(serialized, cache);
|
|
416
|
-
}
|
|
417
|
-
default: {
|
|
418
|
-
throw new Error("TODO");
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
function dispatchOnRecord(record, op, cache, links) {
|
|
423
|
-
if (links.length === 0) {
|
|
424
|
-
if (record.id !== op.id) {
|
|
425
|
-
throw new Error("TODO");
|
|
426
|
-
}
|
|
427
|
-
switch (op.type) {
|
|
428
|
-
case OpType.RecordUpdate: {
|
|
429
|
-
return updateRecord(record, op, cache);
|
|
430
|
-
}
|
|
431
|
-
default: {
|
|
432
|
-
console.warn("Unsupported operation");
|
|
433
|
-
return record;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
const currentLink = links.pop();
|
|
438
|
-
const child = record[currentLink.parentKey];
|
|
439
|
-
const newNode = dispatch(child, op, cache, links);
|
|
440
|
-
return Object.assign(Object.assign({}, record), { [currentLink.parentKey]: newNode });
|
|
441
|
-
}
|
|
442
|
-
function dispatchOnList(list, op, cache, links) {
|
|
443
|
-
if (links.length === 0) {
|
|
444
|
-
if (list.id !== op.id) {
|
|
445
|
-
throw new Error("TODO");
|
|
446
|
-
}
|
|
447
|
-
switch (op.type) {
|
|
448
|
-
case OpType.ListInsert: {
|
|
449
|
-
return listInsert(list, op, cache);
|
|
450
|
-
}
|
|
451
|
-
case OpType.ListMove: {
|
|
452
|
-
return listMove(list, op, cache);
|
|
453
|
-
}
|
|
454
|
-
case OpType.ListRemove: {
|
|
455
|
-
return listDelete(list, op, cache);
|
|
456
|
-
}
|
|
457
|
-
default: {
|
|
458
|
-
console.warn("Unsupported operation");
|
|
459
|
-
return list;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const currentLink = links.pop();
|
|
464
|
-
const position = currentLink.parentKey;
|
|
465
|
-
const items = getListItems(cache, list.id);
|
|
466
|
-
const item = items.get(position);
|
|
467
|
-
if (item == null) {
|
|
468
|
-
throw new Error("TODO");
|
|
469
|
-
}
|
|
470
|
-
const newItem = dispatch(item, op, cache, links);
|
|
471
|
-
items.set(position, newItem);
|
|
472
|
-
return createList(list.id, listItemsToArray(items));
|
|
473
|
-
}
|
|
474
|
-
function dispatch(node, op, cache, links) {
|
|
475
|
-
switch (node.$$type) {
|
|
476
|
-
case RECORD:
|
|
477
|
-
return dispatchOnRecord(node, op, cache, links);
|
|
478
|
-
case LIST:
|
|
479
|
-
return dispatchOnList(node, op, cache, links);
|
|
480
|
-
default: {
|
|
481
|
-
throw new Error("Unknown CRDT");
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
function updateRecord(node, op, cache) {
|
|
486
|
-
const result = Object.assign({}, node);
|
|
487
|
-
for (const key in op.data) {
|
|
488
|
-
const value = op.data[key];
|
|
489
|
-
const item = deserialize(value, cache);
|
|
490
|
-
if (isCrdt(item)) {
|
|
491
|
-
cache.links.set(item.id, { parentId: node.id, parentKey: key });
|
|
492
|
-
}
|
|
493
|
-
result[key] = item;
|
|
494
|
-
}
|
|
495
|
-
return result;
|
|
496
|
-
}
|
|
497
|
-
function listInsert(list, op, cache) {
|
|
498
|
-
const items = getListItems(cache, list.id);
|
|
499
|
-
const item = deserialize(op.data, cache);
|
|
500
|
-
if (isCrdt(item)) {
|
|
501
|
-
items.set(op.position, item);
|
|
502
|
-
cache.links.set(item.id, { parentId: list.id, parentKey: op.position });
|
|
503
|
-
}
|
|
504
|
-
return createList(list.id, listItemsToArray(items));
|
|
505
|
-
}
|
|
506
|
-
function listMove(list, op, cache) {
|
|
507
|
-
const items = getListItems(cache, list.id);
|
|
508
|
-
const link = getLinkOrThrow(cache, op.itemId);
|
|
509
|
-
const item = items.get(link.parentKey);
|
|
510
|
-
if (item == null) {
|
|
511
|
-
throw new Error("TODO");
|
|
512
|
-
}
|
|
513
|
-
// Delete old position cache entry
|
|
514
|
-
items.delete(link.parentKey);
|
|
515
|
-
// Insert new position in cache
|
|
516
|
-
items.set(op.position, item);
|
|
517
|
-
// Update link
|
|
518
|
-
cache.links.set(op.itemId, { parentId: list.id, parentKey: op.position });
|
|
519
|
-
return createList(list.id, listItemsToArray(items));
|
|
520
|
-
}
|
|
521
|
-
function getLinkOrThrow(cache, id) {
|
|
522
|
-
const link = cache.links.get(id);
|
|
523
|
-
if (link == null) {
|
|
524
|
-
throw new Error(`Can't find link with id "${id}"`);
|
|
525
|
-
}
|
|
526
|
-
return link;
|
|
527
|
-
}
|
|
528
|
-
function listDelete(list, op, cache) {
|
|
529
|
-
const items = getListItems(cache, list.id);
|
|
530
|
-
const link = getLinkOrThrow(cache, op.itemId);
|
|
531
|
-
items.delete(link.parentKey);
|
|
532
|
-
cache.links.delete(op.itemId);
|
|
533
|
-
return createList(list.id, listItemsToArray(items));
|
|
534
|
-
}
|
|
535
|
-
function listItemsToArray(items) {
|
|
536
|
-
return sortedListItems(items).map((entry) => entry[1]);
|
|
537
|
-
}
|
|
538
|
-
function sortedListItems(items) {
|
|
539
|
-
return Array.from(items.entries()).sort((entryA, entryB) => compare({ position: entryA[0] }, { position: entryB[0] }));
|
|
540
|
-
}
|
|
541
|
-
function getChildDeep(node, id, links, cache) {
|
|
542
|
-
let currentNode = node;
|
|
543
|
-
while (currentNode.id !== id) {
|
|
544
|
-
const link = links.pop();
|
|
545
|
-
if (link == null || link.parentId !== currentNode.id) {
|
|
546
|
-
throw new Error("TODO");
|
|
547
|
-
}
|
|
548
|
-
if (currentNode.$$type === RECORD) {
|
|
549
|
-
currentNode = currentNode[link.parentKey];
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
const listItems = getListItems(cache, currentNode.id);
|
|
553
|
-
const item = listItems.get(link.parentKey);
|
|
554
|
-
if (item == null) {
|
|
555
|
-
throw new Error("TODO");
|
|
556
|
-
}
|
|
557
|
-
currentNode = item;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
return currentNode;
|
|
561
|
-
}
|
|
562
|
-
function isRecord(value) {
|
|
563
|
-
return value != null && typeof value === "object" && value.$$type === RECORD;
|
|
564
|
-
}
|
|
565
|
-
function isList(value) {
|
|
566
|
-
return value != null && typeof value === "object" && value.$$type === LIST;
|
|
567
|
-
}
|
|
568
|
-
function isCrdt(value) {
|
|
569
|
-
return isRecord(value) || isList(value);
|
|
570
|
-
}
|
|
571
|
-
function serializeRecord(record) {
|
|
572
|
-
const serializedData = {};
|
|
573
|
-
for (const key in record) {
|
|
574
|
-
if (key !== "id" && key !== "$$type") {
|
|
575
|
-
const value = record[key]; // TODO: Find out why typescript does not like that
|
|
576
|
-
serializedData[key] = serialize(value);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
return {
|
|
580
|
-
id: record.id,
|
|
581
|
-
type: CrdtType.Record,
|
|
582
|
-
data: serializedData,
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
function serializeList(list) {
|
|
586
|
-
return {
|
|
587
|
-
id: list.id,
|
|
588
|
-
type: CrdtType.List,
|
|
589
|
-
data: {},
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
function serialize(value) {
|
|
593
|
-
if (isRecord(value)) {
|
|
594
|
-
return serializeRecord(value);
|
|
595
|
-
}
|
|
596
|
-
else if (isList(value)) {
|
|
597
|
-
return serializeList(value);
|
|
598
|
-
}
|
|
599
|
-
else {
|
|
600
|
-
return { type: CrdtType.Register, data: value };
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
var LiveStorageState;
|
|
605
|
-
(function (LiveStorageState) {
|
|
606
|
-
LiveStorageState[LiveStorageState["NotInitialized"] = 0] = "NotInitialized";
|
|
607
|
-
LiveStorageState[LiveStorageState["Loading"] = 1] = "Loading";
|
|
608
|
-
LiveStorageState[LiveStorageState["Loaded"] = 2] = "Loaded";
|
|
609
|
-
})(LiveStorageState || (LiveStorageState = {}));
|
|
610
|
-
|
|
611
|
-
function remove(array, item) {
|
|
612
|
-
for (let i = 0; i < array.length; i++) {
|
|
613
|
-
if (array[i] === item) {
|
|
614
|
-
array.splice(i, 1);
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
621
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
622
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
623
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
624
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
625
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
626
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
627
|
-
});
|
|
628
|
-
};
|
|
629
|
-
function fetchAuthorize(endpoint, room) {
|
|
630
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
631
|
-
const res = yield fetch(endpoint, {
|
|
632
|
-
method: "POST",
|
|
633
|
-
headers: {
|
|
634
|
-
"Content-Type": "application/json",
|
|
635
|
-
},
|
|
636
|
-
body: JSON.stringify({
|
|
637
|
-
room,
|
|
638
|
-
}),
|
|
639
|
-
});
|
|
640
|
-
if (!res.ok) {
|
|
641
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
642
|
-
}
|
|
643
|
-
let authResponse = null;
|
|
644
|
-
try {
|
|
645
|
-
authResponse = yield res.json();
|
|
646
|
-
}
|
|
647
|
-
catch (er) {
|
|
648
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
649
|
-
}
|
|
650
|
-
if (typeof authResponse.token !== "string") {
|
|
651
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication "${endpoint}"`);
|
|
652
|
-
}
|
|
653
|
-
return authResponse.token;
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
function auth(endpoint, room) {
|
|
657
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
658
|
-
if (typeof endpoint === "string") {
|
|
659
|
-
return fetchAuthorize(endpoint, room);
|
|
660
|
-
}
|
|
661
|
-
if (typeof endpoint === "function") {
|
|
662
|
-
return endpoint(room);
|
|
663
|
-
}
|
|
664
|
-
throw new Error("Authentication error. Liveblocks could not parse the response of your authentication endpoint");
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
class AuthenticationError extends Error {
|
|
668
|
-
constructor(message) {
|
|
669
|
-
super(message);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
function parseToken(token) {
|
|
673
|
-
const tokenParts = token.split(".");
|
|
674
|
-
if (tokenParts.length !== 3) {
|
|
675
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
676
|
-
}
|
|
677
|
-
const data = JSON.parse(atob(tokenParts[1]));
|
|
678
|
-
if (typeof data.actor !== "number") {
|
|
679
|
-
throw new AuthenticationError(`Authentication error. Liveblocks could not parse the response of your authentication endpoint`);
|
|
680
|
-
}
|
|
681
|
-
return data;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
685
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
686
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
687
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
688
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
689
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
690
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
691
|
-
});
|
|
692
|
-
};
|
|
693
|
-
const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];
|
|
694
|
-
const HEARTBEAT_INTERVAL = 30000;
|
|
695
|
-
// const WAKE_UP_CHECK_INTERVAL = 2000;
|
|
696
|
-
const PONG_TIMEOUT = 2000;
|
|
697
|
-
function isValidRoomEventType(value) {
|
|
698
|
-
return (value === "storage" ||
|
|
699
|
-
value === "my-presence" ||
|
|
700
|
-
value === "others" ||
|
|
701
|
-
value === "event" ||
|
|
702
|
-
value === "error" ||
|
|
703
|
-
value === "connection");
|
|
704
|
-
}
|
|
705
|
-
function makeIdFactory(connectionId) {
|
|
706
|
-
let count = 0;
|
|
707
|
-
return () => `${connectionId}:${count++}`;
|
|
708
|
-
}
|
|
709
|
-
function makeOthers(presenceMap) {
|
|
710
|
-
const array = Object.values(presenceMap);
|
|
711
|
-
return {
|
|
712
|
-
get count() {
|
|
713
|
-
return array.length;
|
|
714
|
-
},
|
|
715
|
-
map(callback) {
|
|
716
|
-
return array.map(callback);
|
|
717
|
-
},
|
|
718
|
-
toArray() {
|
|
719
|
-
return array;
|
|
720
|
-
},
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
function makeStateMachine(state, context, mockedEffects) {
|
|
724
|
-
const effects = mockedEffects || {
|
|
725
|
-
authenticate() {
|
|
726
|
-
return __awaiter$1(this, void 0, void 0, function* () {
|
|
727
|
-
try {
|
|
728
|
-
const token = yield auth(context.authEndpoint, context.room);
|
|
729
|
-
const parsedToken = parseToken(token);
|
|
730
|
-
const socket = new WebSocket(`${context.liveblocksServer}/?token=${token}`);
|
|
731
|
-
socket.addEventListener("message", onMessage);
|
|
732
|
-
socket.addEventListener("open", onOpen);
|
|
733
|
-
socket.addEventListener("close", onClose);
|
|
734
|
-
socket.addEventListener("error", onError);
|
|
735
|
-
authenticationSuccess(parsedToken, socket);
|
|
736
|
-
}
|
|
737
|
-
catch (er) {
|
|
738
|
-
authenticationFailure(er);
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
},
|
|
742
|
-
send(messageOrMessages) {
|
|
743
|
-
if (state.socket == null) {
|
|
744
|
-
throw new Error("Can't send message if socket is null");
|
|
745
|
-
}
|
|
746
|
-
state.socket.send(JSON.stringify(messageOrMessages));
|
|
747
|
-
},
|
|
748
|
-
delayFlush(delay) {
|
|
749
|
-
return setTimeout(tryFlushing, delay);
|
|
750
|
-
},
|
|
751
|
-
startHeartbeatInterval() {
|
|
752
|
-
return setInterval(heartbeat, HEARTBEAT_INTERVAL);
|
|
753
|
-
},
|
|
754
|
-
schedulePongTimeout() {
|
|
755
|
-
return setTimeout(pongTimeout, PONG_TIMEOUT);
|
|
756
|
-
},
|
|
757
|
-
scheduleReconnect(delay) {
|
|
758
|
-
return setTimeout(connect, delay);
|
|
759
|
-
},
|
|
760
|
-
};
|
|
761
|
-
function subscribe(type, listener) {
|
|
762
|
-
if (!isValidRoomEventType(type)) {
|
|
763
|
-
throw new Error(`"${type}" is not a valid event name`);
|
|
764
|
-
}
|
|
765
|
-
state.listeners[type].push(listener);
|
|
766
|
-
}
|
|
767
|
-
function unsubscribe(event, callback) {
|
|
768
|
-
if (!isValidRoomEventType(event)) {
|
|
769
|
-
throw new Error(`"${event}" is not a valid event name`);
|
|
770
|
-
}
|
|
771
|
-
const callbacks = state.listeners[event];
|
|
772
|
-
remove(callbacks, callback);
|
|
773
|
-
}
|
|
774
|
-
function getConnectionState() {
|
|
775
|
-
return state.connection.state;
|
|
776
|
-
}
|
|
777
|
-
function getCurrentUser() {
|
|
778
|
-
return state.connection.state === "open" ||
|
|
779
|
-
state.connection.state === "connecting"
|
|
780
|
-
? {
|
|
781
|
-
connectionId: state.connection.id,
|
|
782
|
-
id: state.connection.userId,
|
|
783
|
-
info: state.connection.userInfo,
|
|
784
|
-
presence: getPresence(),
|
|
785
|
-
}
|
|
786
|
-
: null;
|
|
787
|
-
}
|
|
788
|
-
function connect() {
|
|
789
|
-
if (typeof window === "undefined") {
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
if (state.connection.state !== "closed" &&
|
|
793
|
-
state.connection.state !== "unavailable") {
|
|
794
|
-
return null;
|
|
795
|
-
}
|
|
796
|
-
updateConnection({ state: "authenticating" });
|
|
797
|
-
effects.authenticate();
|
|
798
|
-
}
|
|
799
|
-
function updatePresence(overrides) {
|
|
800
|
-
const newPresence = Object.assign(Object.assign({}, state.me), overrides);
|
|
801
|
-
if (state.flushData.presence == null) {
|
|
802
|
-
state.flushData.presence = overrides;
|
|
803
|
-
}
|
|
804
|
-
else {
|
|
805
|
-
for (const key in overrides) {
|
|
806
|
-
state.flushData.presence[key] = overrides[key];
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
state.me = newPresence;
|
|
810
|
-
tryFlushing();
|
|
811
|
-
for (const listener of state.listeners["my-presence"]) {
|
|
812
|
-
listener(state.me);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
function authenticationSuccess(token, socket) {
|
|
816
|
-
updateConnection({
|
|
817
|
-
state: "connecting",
|
|
818
|
-
id: token.actor,
|
|
819
|
-
userInfo: token.info,
|
|
820
|
-
userId: token.id,
|
|
821
|
-
});
|
|
822
|
-
state.idFactory = makeIdFactory(token.actor);
|
|
823
|
-
state.socket = socket;
|
|
824
|
-
}
|
|
825
|
-
function authenticationFailure(error) {
|
|
826
|
-
console.error(error);
|
|
827
|
-
updateConnection({ state: "unavailable" });
|
|
828
|
-
state.numberOfRetry++;
|
|
829
|
-
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
830
|
-
}
|
|
831
|
-
function onVisibilityChange(visibilityState) {
|
|
832
|
-
if (visibilityState === "visible" && state.connection.state === "open") {
|
|
833
|
-
heartbeat();
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
function onUpdatePresenceMessage(message) {
|
|
837
|
-
const user = state.users[message.actor];
|
|
838
|
-
if (user == null) {
|
|
839
|
-
state.users[message.actor] = {
|
|
840
|
-
connectionId: message.actor,
|
|
841
|
-
presence: message.data,
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
state.users[message.actor] = {
|
|
846
|
-
id: user.id,
|
|
847
|
-
info: user.info,
|
|
848
|
-
connectionId: message.actor,
|
|
849
|
-
presence: Object.assign(Object.assign({}, user.presence), message.data),
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
updateUsers({
|
|
853
|
-
type: "update",
|
|
854
|
-
updates: message.data,
|
|
855
|
-
user: state.users[message.actor],
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
function updateUsers(event) {
|
|
859
|
-
state.others = makeOthers(state.users);
|
|
860
|
-
for (const listener of state.listeners["others"]) {
|
|
861
|
-
listener(state.others, event);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
function onUserLeftMessage(message) {
|
|
865
|
-
const userLeftMessage = message;
|
|
866
|
-
const user = state.users[userLeftMessage.actor];
|
|
867
|
-
if (user) {
|
|
868
|
-
delete state.users[userLeftMessage.actor];
|
|
869
|
-
updateUsers({ type: "leave", user });
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
function onRoomStateMessage(message) {
|
|
873
|
-
const newUsers = {};
|
|
874
|
-
for (const key in message.users) {
|
|
875
|
-
const connectionId = Number.parseInt(key);
|
|
876
|
-
const user = message.users[key];
|
|
877
|
-
newUsers[connectionId] = {
|
|
878
|
-
connectionId,
|
|
879
|
-
info: user.info,
|
|
880
|
-
id: user.id,
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
state.users = newUsers;
|
|
884
|
-
updateUsers({ type: "reset" });
|
|
885
|
-
}
|
|
886
|
-
function onNavigatorOnline() {
|
|
887
|
-
if (state.connection.state === "unavailable") {
|
|
888
|
-
reconnect();
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
function onEvent(message) {
|
|
892
|
-
for (const listener of state.listeners.event) {
|
|
893
|
-
listener({ connectionId: message.actor, event: message.event });
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
function onUserJoinedMessage(message) {
|
|
897
|
-
state.users[message.actor] = {
|
|
898
|
-
connectionId: message.actor,
|
|
899
|
-
info: message.info,
|
|
900
|
-
id: message.id,
|
|
901
|
-
};
|
|
902
|
-
updateUsers({ type: "enter", user: state.users[message.actor] });
|
|
903
|
-
if (state.me) {
|
|
904
|
-
// Send current presence to new user
|
|
905
|
-
// TODO: Consider storing it on the backend
|
|
906
|
-
state.flushData.messages.push({
|
|
907
|
-
type: ClientMessageType.UpdatePresence,
|
|
908
|
-
data: state.me,
|
|
909
|
-
targetActor: message.actor,
|
|
910
|
-
});
|
|
911
|
-
tryFlushing();
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
function onMessage(event) {
|
|
915
|
-
if (event.data === "pong") {
|
|
916
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
const message = JSON.parse(event.data);
|
|
920
|
-
switch (message.type) {
|
|
921
|
-
case ServerMessageType.InitialStorageState: {
|
|
922
|
-
onInitialStorageState(message);
|
|
923
|
-
break;
|
|
924
|
-
}
|
|
925
|
-
case ServerMessageType.UpdateStorage: {
|
|
926
|
-
onStorageUpdates(message);
|
|
927
|
-
break;
|
|
928
|
-
}
|
|
929
|
-
case ServerMessageType.UserJoined: {
|
|
930
|
-
onUserJoinedMessage(message);
|
|
931
|
-
break;
|
|
932
|
-
}
|
|
933
|
-
case ServerMessageType.UpdatePresence: {
|
|
934
|
-
onUpdatePresenceMessage(message);
|
|
935
|
-
break;
|
|
936
|
-
}
|
|
937
|
-
case ServerMessageType.Event: {
|
|
938
|
-
onEvent(message);
|
|
939
|
-
break;
|
|
940
|
-
}
|
|
941
|
-
case ServerMessageType.UserLeft: {
|
|
942
|
-
onUserLeftMessage(message);
|
|
943
|
-
break;
|
|
944
|
-
}
|
|
945
|
-
case ServerMessageType.RoomState: {
|
|
946
|
-
onRoomStateMessage(message);
|
|
947
|
-
break;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
// function onWakeUp() {
|
|
952
|
-
// // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example)
|
|
953
|
-
// // The client will not know that the server has probably close the connection even if the readyState is Open
|
|
954
|
-
// // 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
|
|
955
|
-
// if (state.connection.state === "open") {
|
|
956
|
-
// log("Try to reconnect after laptop wake up");
|
|
957
|
-
// reconnect();
|
|
958
|
-
// }
|
|
959
|
-
// }
|
|
960
|
-
function onClose(event) {
|
|
961
|
-
state.socket = null;
|
|
962
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
963
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
964
|
-
if (state.timeoutHandles.flush) {
|
|
965
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
966
|
-
}
|
|
967
|
-
clearTimeout(state.timeoutHandles.reconnect);
|
|
968
|
-
state.users = {};
|
|
969
|
-
updateUsers({ type: "reset" });
|
|
970
|
-
if (event.code >= 4000 && event.code <= 4100) {
|
|
971
|
-
updateConnection({ state: "failed" });
|
|
972
|
-
const error = new LiveblocksError(event.reason, event.code);
|
|
973
|
-
for (const listener of state.listeners.error) {
|
|
974
|
-
listener(error);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
else if (event.wasClean === false) {
|
|
978
|
-
updateConnection({ state: "unavailable" });
|
|
979
|
-
state.numberOfRetry++;
|
|
980
|
-
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
981
|
-
}
|
|
982
|
-
else {
|
|
983
|
-
updateConnection({ state: "closed" });
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
function updateConnection(connection) {
|
|
987
|
-
state.connection = connection;
|
|
988
|
-
for (const listener of state.listeners.connection) {
|
|
989
|
-
listener(connection.state);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
function getRetryDelay() {
|
|
993
|
-
return BACKOFF_RETRY_DELAYS[state.numberOfRetry < BACKOFF_RETRY_DELAYS.length
|
|
994
|
-
? state.numberOfRetry
|
|
995
|
-
: BACKOFF_RETRY_DELAYS.length - 1];
|
|
996
|
-
}
|
|
997
|
-
function onError() { }
|
|
998
|
-
function onOpen() {
|
|
999
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
1000
|
-
state.intervalHandles.heartbeat = effects.startHeartbeatInterval();
|
|
1001
|
-
if (state.connection.state === "connecting") {
|
|
1002
|
-
updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
|
|
1003
|
-
state.numberOfRetry = 0;
|
|
1004
|
-
tryFlushing();
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
function heartbeat() {
|
|
1008
|
-
if (state.socket == null) {
|
|
1009
|
-
// Should never happen, because we clear the pong timeout when the connection is dropped explictly
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
1013
|
-
state.timeoutHandles.pongTimeout = effects.schedulePongTimeout();
|
|
1014
|
-
if (state.socket.readyState === WebSocket.OPEN) {
|
|
1015
|
-
state.socket.send("ping");
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
function pongTimeout() {
|
|
1019
|
-
reconnect();
|
|
1020
|
-
}
|
|
1021
|
-
function reconnect() {
|
|
1022
|
-
if (state.socket) {
|
|
1023
|
-
state.socket.removeEventListener("open", onOpen);
|
|
1024
|
-
state.socket.removeEventListener("message", onMessage);
|
|
1025
|
-
state.socket.removeEventListener("close", onClose);
|
|
1026
|
-
state.socket.removeEventListener("error", onError);
|
|
1027
|
-
state.socket.close();
|
|
1028
|
-
state.socket = null;
|
|
1029
|
-
}
|
|
1030
|
-
updateConnection({ state: "unavailable" });
|
|
1031
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
1032
|
-
if (state.timeoutHandles.flush) {
|
|
1033
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
1034
|
-
}
|
|
1035
|
-
clearTimeout(state.timeoutHandles.reconnect);
|
|
1036
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
1037
|
-
connect();
|
|
1038
|
-
}
|
|
1039
|
-
function tryFlushing() {
|
|
1040
|
-
if (state.socket == null) {
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
if (state.socket.readyState !== WebSocket.OPEN) {
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
const now = Date.now();
|
|
1047
|
-
const elapsedTime = now - state.lastFlushTime;
|
|
1048
|
-
if (elapsedTime > context.throttleDelay) {
|
|
1049
|
-
const messages = flushDataToMessages(state);
|
|
1050
|
-
if (messages.length === 0) {
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
effects.send(messages);
|
|
1054
|
-
state.flushData = {
|
|
1055
|
-
messages: [],
|
|
1056
|
-
storageOperations: [],
|
|
1057
|
-
presence: null,
|
|
1058
|
-
};
|
|
1059
|
-
state.lastFlushTime = now;
|
|
1060
|
-
}
|
|
1061
|
-
else {
|
|
1062
|
-
if (state.timeoutHandles.flush != null) {
|
|
1063
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
1064
|
-
}
|
|
1065
|
-
state.timeoutHandles.flush = effects.delayFlush(context.throttleDelay - (now - state.lastFlushTime));
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
function flushDataToMessages(state) {
|
|
1069
|
-
const messages = [];
|
|
1070
|
-
if (state.flushData.presence) {
|
|
1071
|
-
messages.push({
|
|
1072
|
-
type: ClientMessageType.UpdatePresence,
|
|
1073
|
-
data: state.flushData.presence,
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
1076
|
-
for (const event of state.flushData.messages) {
|
|
1077
|
-
messages.push(event);
|
|
1078
|
-
}
|
|
1079
|
-
if (state.flushData.storageOperations.length > 0) {
|
|
1080
|
-
messages.push({
|
|
1081
|
-
type: ClientMessageType.UpdateStorage,
|
|
1082
|
-
ops: state.flushData.storageOperations,
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
return messages;
|
|
1086
|
-
}
|
|
1087
|
-
function disconnect() {
|
|
1088
|
-
if (state.socket) {
|
|
1089
|
-
state.socket.removeEventListener("open", onOpen);
|
|
1090
|
-
state.socket.removeEventListener("message", onMessage);
|
|
1091
|
-
state.socket.removeEventListener("close", onClose);
|
|
1092
|
-
state.socket.removeEventListener("error", onError);
|
|
1093
|
-
state.socket.close();
|
|
1094
|
-
state.socket = null;
|
|
1095
|
-
}
|
|
1096
|
-
updateConnection({ state: "closed" });
|
|
1097
|
-
if (state.timeoutHandles.flush) {
|
|
1098
|
-
clearTimeout(state.timeoutHandles.flush);
|
|
1099
|
-
}
|
|
1100
|
-
clearTimeout(state.timeoutHandles.reconnect);
|
|
1101
|
-
clearTimeout(state.timeoutHandles.pongTimeout);
|
|
1102
|
-
clearInterval(state.intervalHandles.heartbeat);
|
|
1103
|
-
state.users = {};
|
|
1104
|
-
updateUsers({ type: "reset" });
|
|
1105
|
-
clearListeners();
|
|
1106
|
-
}
|
|
1107
|
-
function clearListeners() {
|
|
1108
|
-
for (const key in state.listeners) {
|
|
1109
|
-
state.listeners[key] = [];
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
function getPresence() {
|
|
1113
|
-
return state.me;
|
|
1114
|
-
}
|
|
1115
|
-
function getOthers() {
|
|
1116
|
-
return state.others;
|
|
1117
|
-
}
|
|
1118
|
-
function broadcastEvent(event) {
|
|
1119
|
-
if (state.socket == null) {
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
state.flushData.messages.push({
|
|
1123
|
-
type: ClientMessageType.ClientEvent,
|
|
1124
|
-
event,
|
|
1125
|
-
});
|
|
1126
|
-
tryFlushing();
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* STORAGE
|
|
1130
|
-
*/
|
|
1131
|
-
function onStorageUpdates(message) {
|
|
1132
|
-
if (state.doc == null) {
|
|
1133
|
-
// TODO: Cache updates in case they are coming while root is queried
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
updateDoc(message.ops.reduce((doc, op) => doc.dispatch(op), state.doc));
|
|
1137
|
-
}
|
|
1138
|
-
function updateDoc(doc) {
|
|
1139
|
-
state.doc = doc;
|
|
1140
|
-
if (doc) {
|
|
1141
|
-
for (const listener of state.listeners.storage) {
|
|
1142
|
-
listener(getStorage());
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
function getStorage() {
|
|
1147
|
-
if (state.storageState === LiveStorageState.Loaded) {
|
|
1148
|
-
return {
|
|
1149
|
-
state: state.storageState,
|
|
1150
|
-
root: state.doc.root,
|
|
1151
|
-
};
|
|
1152
|
-
}
|
|
1153
|
-
return {
|
|
1154
|
-
state: state.storageState,
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
function onInitialStorageState(message) {
|
|
1158
|
-
state.storageState = LiveStorageState.Loaded;
|
|
1159
|
-
if (message.root == null) {
|
|
1160
|
-
const rootId = makeId();
|
|
1161
|
-
state.doc = Doc.empty(rootId, (op) => dispatch(op));
|
|
1162
|
-
updateDoc(state.doc.updateRecord(rootId, state.initialStorageFactory({
|
|
1163
|
-
createRecord: (data) => createRecord$1(data),
|
|
1164
|
-
createList: () => createList$1(),
|
|
1165
|
-
})));
|
|
1166
|
-
}
|
|
1167
|
-
else {
|
|
1168
|
-
updateDoc(Doc.load(message.root, (op) => dispatch(op)));
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
function makeId() {
|
|
1172
|
-
if (state.idFactory == null) {
|
|
1173
|
-
throw new Error("Can't generate id. Id factory is missing.");
|
|
1174
|
-
}
|
|
1175
|
-
return state.idFactory();
|
|
1176
|
-
}
|
|
1177
|
-
function dispatch(op) {
|
|
1178
|
-
state.flushData.storageOperations.push(op);
|
|
1179
|
-
tryFlushing();
|
|
1180
|
-
}
|
|
1181
|
-
function createRecord$1(data) {
|
|
1182
|
-
return createRecord(makeId(), data);
|
|
1183
|
-
}
|
|
1184
|
-
function createList$1() {
|
|
1185
|
-
return createList(makeId());
|
|
1186
|
-
}
|
|
1187
|
-
function fetchStorage(initialStorageFactory) {
|
|
1188
|
-
state.initialStorageFactory = initialStorageFactory;
|
|
1189
|
-
state.storageState = LiveStorageState.Loading;
|
|
1190
|
-
state.flushData.messages.push({ type: ClientMessageType.FetchStorage });
|
|
1191
|
-
tryFlushing();
|
|
1192
|
-
}
|
|
1193
|
-
function updateRecord(record, overrides) {
|
|
1194
|
-
updateDoc(state.doc.updateRecord(record.id, overrides));
|
|
1195
|
-
}
|
|
1196
|
-
function pushItem(list, item) {
|
|
1197
|
-
updateDoc(state.doc.pushItem(list.id, item));
|
|
1198
|
-
}
|
|
1199
|
-
function deleteItem(list, index) {
|
|
1200
|
-
updateDoc(state.doc.deleteItem(list.id, index));
|
|
1201
|
-
}
|
|
1202
|
-
function deleteItemById(list, itemId) {
|
|
1203
|
-
updateDoc(state.doc.deleteItemById(list.id, itemId));
|
|
1204
|
-
}
|
|
1205
|
-
function moveItem(list, index, targetIndex) {
|
|
1206
|
-
updateDoc(state.doc.moveItem(list.id, index, targetIndex));
|
|
1207
|
-
}
|
|
1208
|
-
return {
|
|
1209
|
-
// Internal
|
|
1210
|
-
onOpen,
|
|
1211
|
-
onClose,
|
|
1212
|
-
onMessage,
|
|
1213
|
-
authenticationSuccess,
|
|
1214
|
-
heartbeat,
|
|
1215
|
-
onNavigatorOnline,
|
|
1216
|
-
// onWakeUp,
|
|
1217
|
-
onVisibilityChange,
|
|
1218
|
-
// Core
|
|
1219
|
-
connect,
|
|
1220
|
-
disconnect,
|
|
1221
|
-
subscribe,
|
|
1222
|
-
unsubscribe,
|
|
1223
|
-
// Presence
|
|
1224
|
-
updatePresence,
|
|
1225
|
-
broadcastEvent,
|
|
1226
|
-
// Storage
|
|
1227
|
-
fetchStorage,
|
|
1228
|
-
createRecord: createRecord$1,
|
|
1229
|
-
updateRecord,
|
|
1230
|
-
createList: createList$1,
|
|
1231
|
-
pushItem,
|
|
1232
|
-
deleteItem,
|
|
1233
|
-
deleteItemById,
|
|
1234
|
-
moveItem,
|
|
1235
|
-
selectors: {
|
|
1236
|
-
// Core
|
|
1237
|
-
getConnectionState,
|
|
1238
|
-
getCurrentUser,
|
|
1239
|
-
// Presence
|
|
1240
|
-
getPresence,
|
|
1241
|
-
getOthers,
|
|
1242
|
-
// Storage
|
|
1243
|
-
getStorage,
|
|
1244
|
-
},
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
|
-
function defaultState(me) {
|
|
1248
|
-
return {
|
|
1249
|
-
connection: { state: "closed" },
|
|
1250
|
-
socket: null,
|
|
1251
|
-
listeners: {
|
|
1252
|
-
storage: [],
|
|
1253
|
-
event: [],
|
|
1254
|
-
others: [],
|
|
1255
|
-
"my-presence": [],
|
|
1256
|
-
error: [],
|
|
1257
|
-
connection: [],
|
|
1258
|
-
},
|
|
1259
|
-
numberOfRetry: 0,
|
|
1260
|
-
lastFlushTime: 0,
|
|
1261
|
-
timeoutHandles: {
|
|
1262
|
-
flush: null,
|
|
1263
|
-
reconnect: 0,
|
|
1264
|
-
pongTimeout: 0,
|
|
1265
|
-
},
|
|
1266
|
-
flushData: {
|
|
1267
|
-
presence: me == null ? {} : me,
|
|
1268
|
-
messages: [],
|
|
1269
|
-
storageOperations: [],
|
|
1270
|
-
},
|
|
1271
|
-
intervalHandles: {
|
|
1272
|
-
heartbeat: 0,
|
|
1273
|
-
},
|
|
1274
|
-
me: me == null ? {} : me,
|
|
1275
|
-
users: {},
|
|
1276
|
-
others: makeOthers({}),
|
|
1277
|
-
storageState: LiveStorageState.NotInitialized,
|
|
1278
|
-
initialStorageFactory: null,
|
|
1279
|
-
doc: null,
|
|
1280
|
-
idFactory: null,
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
function createRoom(name, options) {
|
|
1284
|
-
const throttleDelay = options.throttle || 100;
|
|
1285
|
-
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net";
|
|
1286
|
-
const authEndpoint = options.authEndpoint;
|
|
1287
|
-
const state = defaultState(options.initialPresence);
|
|
1288
|
-
const machine = makeStateMachine(state, {
|
|
1289
|
-
throttleDelay,
|
|
1290
|
-
liveblocksServer,
|
|
1291
|
-
authEndpoint,
|
|
1292
|
-
room: name,
|
|
1293
|
-
});
|
|
1294
|
-
const room = {
|
|
1295
|
-
/////////////
|
|
1296
|
-
// Core //
|
|
1297
|
-
/////////////
|
|
1298
|
-
getConnectionState: machine.selectors.getConnectionState,
|
|
1299
|
-
getCurrentUser: machine.selectors.getCurrentUser,
|
|
1300
|
-
subscribe: machine.subscribe,
|
|
1301
|
-
unsubscribe: machine.unsubscribe,
|
|
1302
|
-
/////////////
|
|
1303
|
-
// Storage //
|
|
1304
|
-
/////////////
|
|
1305
|
-
getStorage: machine.selectors.getStorage,
|
|
1306
|
-
fetchStorage: machine.fetchStorage,
|
|
1307
|
-
createRecord: machine.createRecord,
|
|
1308
|
-
createList: machine.createList,
|
|
1309
|
-
updateRecord: machine.updateRecord,
|
|
1310
|
-
pushItem: machine.pushItem,
|
|
1311
|
-
deleteItem: machine.deleteItem,
|
|
1312
|
-
deleteItemById: machine.deleteItemById,
|
|
1313
|
-
moveItem: machine.moveItem,
|
|
1314
|
-
//////////////
|
|
1315
|
-
// Presence //
|
|
1316
|
-
//////////////
|
|
1317
|
-
getPresence: machine.selectors.getPresence,
|
|
1318
|
-
updatePresence: machine.updatePresence,
|
|
1319
|
-
getOthers: machine.selectors.getOthers,
|
|
1320
|
-
broadcastEvent: machine.broadcastEvent,
|
|
1321
|
-
};
|
|
1322
|
-
return {
|
|
1323
|
-
connect: machine.connect,
|
|
1324
|
-
disconnect: machine.disconnect,
|
|
1325
|
-
onNavigatorOnline: machine.onNavigatorOnline,
|
|
1326
|
-
onVisibilityChange: machine.onVisibilityChange,
|
|
1327
|
-
room,
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
|
-
class LiveblocksError extends Error {
|
|
1331
|
-
constructor(message, code) {
|
|
1332
|
-
super(message);
|
|
1333
|
-
this.code = code;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
/**
|
|
1338
|
-
* Create a client that will be responsible to communicate with liveblocks servers.
|
|
1339
|
-
*
|
|
1340
|
-
* ### Example
|
|
1341
|
-
* ```
|
|
1342
|
-
* const client = createClient({
|
|
1343
|
-
* authEndpoint: "/api/auth"
|
|
1344
|
-
* })
|
|
1345
|
-
* ```
|
|
1346
|
-
*/
|
|
1347
|
-
function createClient(options) {
|
|
1348
|
-
if (typeof options.throttle === "number") {
|
|
1349
|
-
if (options.throttle < 80 || options.throttle > 1000) {
|
|
1350
|
-
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
const rooms = new Map();
|
|
1354
|
-
function getRoom(roomId) {
|
|
1355
|
-
const internalRoom = rooms.get(roomId);
|
|
1356
|
-
return internalRoom ? internalRoom.room : null;
|
|
1357
|
-
}
|
|
1358
|
-
function enter(roomId, initialPresence) {
|
|
1359
|
-
let internalRoom = rooms.get(roomId);
|
|
1360
|
-
if (internalRoom) {
|
|
1361
|
-
return internalRoom.room;
|
|
1362
|
-
}
|
|
1363
|
-
internalRoom = createRoom(roomId, Object.assign(Object.assign({}, options), { initialPresence }));
|
|
1364
|
-
rooms.set(roomId, internalRoom);
|
|
1365
|
-
internalRoom.connect();
|
|
1366
|
-
return internalRoom.room;
|
|
1367
|
-
}
|
|
1368
|
-
function leave(roomId) {
|
|
1369
|
-
let room = rooms.get(roomId);
|
|
1370
|
-
if (room) {
|
|
1371
|
-
room.disconnect();
|
|
1372
|
-
rooms.delete(roomId);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
if (typeof window !== "undefined") {
|
|
1376
|
-
// TODO: Expose a way to clear these
|
|
1377
|
-
window.addEventListener("online", () => {
|
|
1378
|
-
for (const [, room] of rooms) {
|
|
1379
|
-
room.onNavigatorOnline();
|
|
1380
|
-
}
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1383
|
-
if (typeof document !== "undefined") {
|
|
1384
|
-
document.addEventListener("visibilitychange", () => {
|
|
1385
|
-
for (const [, room] of rooms) {
|
|
1386
|
-
room.onVisibilityChange(document.visibilityState);
|
|
1387
|
-
}
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
return {
|
|
1391
|
-
getRoom,
|
|
1392
|
-
enter,
|
|
1393
|
-
leave,
|
|
1394
|
-
};
|
|
6
|
+
/*! *****************************************************************************
|
|
7
|
+
Copyright (c) Microsoft Corporation.
|
|
8
|
+
|
|
9
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
10
|
+
purpose with or without fee is hereby granted.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
13
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
14
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
15
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
16
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
17
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
18
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
19
|
+
***************************************************************************** */
|
|
20
|
+
|
|
21
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
22
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
23
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
24
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
25
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
26
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
27
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function __generator(thisArg, body) {
|
|
32
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
33
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
34
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
35
|
+
function step(op) {
|
|
36
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
37
|
+
while (_) try {
|
|
38
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
39
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
40
|
+
switch (op[0]) {
|
|
41
|
+
case 0: case 1: t = op; break;
|
|
42
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
43
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
44
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
45
|
+
default:
|
|
46
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
47
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
48
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
49
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
50
|
+
if (t[2]) _.ops.pop();
|
|
51
|
+
_.trys.pop(); continue;
|
|
52
|
+
}
|
|
53
|
+
op = body.call(thisArg, _);
|
|
54
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
55
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
56
|
+
}
|
|
1395
57
|
}
|
|
1396
58
|
|
|
1397
59
|
var ClientContext = React.createContext(null);
|
|
@@ -1418,7 +80,7 @@ function useClient() {
|
|
|
1418
80
|
* That means that you can't have 2 RoomProvider with the same room id in your react tree.
|
|
1419
81
|
*/
|
|
1420
82
|
function RoomProvider(_a) {
|
|
1421
|
-
var id = _a.id, children = _a.children, defaultPresence = _a.defaultPresence;
|
|
83
|
+
var id = _a.id, children = _a.children, defaultPresence = _a.defaultPresence, defaultStorageRoot = _a.defaultStorageRoot;
|
|
1422
84
|
var client = useClient();
|
|
1423
85
|
React.useEffect(function () {
|
|
1424
86
|
return function () {
|
|
@@ -1426,7 +88,10 @@ function RoomProvider(_a) {
|
|
|
1426
88
|
};
|
|
1427
89
|
}, [client, id]);
|
|
1428
90
|
var room = client.getRoom(id) ||
|
|
1429
|
-
client.enter(id,
|
|
91
|
+
client.enter(id, {
|
|
92
|
+
defaultPresence: defaultPresence ? defaultPresence() : undefined,
|
|
93
|
+
defaultStorageRoot: defaultStorageRoot,
|
|
94
|
+
});
|
|
1430
95
|
return React.createElement(RoomContext.Provider, { value: room }, children);
|
|
1431
96
|
}
|
|
1432
97
|
/**
|
|
@@ -1444,8 +109,7 @@ function useRoom() {
|
|
|
1444
109
|
* It is different from the setState function returned by the useState hook from React.
|
|
1445
110
|
* You don't need to pass the full presence object to update it.
|
|
1446
111
|
*
|
|
1447
|
-
*
|
|
1448
|
-
* ``` typescript
|
|
112
|
+
* @example
|
|
1449
113
|
* import { useMyPresence } from "@liveblocks/react";
|
|
1450
114
|
*
|
|
1451
115
|
* const [myPresence, updateMyPresence] = useMyPresence();
|
|
@@ -1453,7 +117,6 @@ function useRoom() {
|
|
|
1453
117
|
* updateMyPresence({ y: 0 });
|
|
1454
118
|
*
|
|
1455
119
|
* // At the next render, "myPresence" will be equal to "{ x: 0, y: 0 }"
|
|
1456
|
-
* ```
|
|
1457
120
|
*/
|
|
1458
121
|
function useMyPresence() {
|
|
1459
122
|
var room = useRoom();
|
|
@@ -1475,8 +138,7 @@ function useMyPresence() {
|
|
|
1475
138
|
* useUpdateMyPresence is similar to useMyPresence but it only returns the function to update the current user presence.
|
|
1476
139
|
* If you don't use the current user presence in your component, but you need to update it (e.g. live cursor), it's better to use useUpdateMyPresence to avoid unnecessary renders.
|
|
1477
140
|
*
|
|
1478
|
-
*
|
|
1479
|
-
* ``` typescript
|
|
141
|
+
* @example
|
|
1480
142
|
* import { useUpdateMyPresence } from "@liveblocks/react";
|
|
1481
143
|
*
|
|
1482
144
|
* const updateMyPresence = useUpdateMyPresence();
|
|
@@ -1484,7 +146,6 @@ function useMyPresence() {
|
|
|
1484
146
|
* updateMyPresence({ y: 0 });
|
|
1485
147
|
*
|
|
1486
148
|
* // At the next render, the presence of the current user will be equal to "{ x: 0, y: 0 }"
|
|
1487
|
-
* ```
|
|
1488
149
|
*/
|
|
1489
150
|
function useUpdateMyPresence() {
|
|
1490
151
|
var room = useRoom();
|
|
@@ -1495,8 +156,7 @@ function useUpdateMyPresence() {
|
|
|
1495
156
|
/**
|
|
1496
157
|
* Returns an object that lets you get information about all the the users currently connected in the room.
|
|
1497
158
|
*
|
|
1498
|
-
*
|
|
1499
|
-
* ``` typescript
|
|
159
|
+
* @example
|
|
1500
160
|
* import { useOthers } from "@liveblocks/react";
|
|
1501
161
|
*
|
|
1502
162
|
* const others = useOthers();
|
|
@@ -1510,7 +170,6 @@ function useUpdateMyPresence() {
|
|
|
1510
170
|
* return <Cursor key={connectionId} cursor={presence.cursor} />
|
|
1511
171
|
* })
|
|
1512
172
|
* }
|
|
1513
|
-
* ```
|
|
1514
173
|
*/
|
|
1515
174
|
function useOthers() {
|
|
1516
175
|
var room = useRoom();
|
|
@@ -1529,14 +188,12 @@ function useOthers() {
|
|
|
1529
188
|
/**
|
|
1530
189
|
* Returns a callback that lets you broadcast custom events to other users in the room
|
|
1531
190
|
*
|
|
1532
|
-
*
|
|
1533
|
-
* ``` typescript
|
|
191
|
+
* @example
|
|
1534
192
|
* import { useBroadcastEvent } from "@liveblocks/react";
|
|
1535
193
|
*
|
|
1536
194
|
* const broadcast = useBroadcastEvent();
|
|
1537
195
|
*
|
|
1538
196
|
* broadcast({ type: "CUSTOM_EVENT", data: { x: 0, y: 0 } });
|
|
1539
|
-
* ```
|
|
1540
197
|
*/
|
|
1541
198
|
function useBroadcastEvent() {
|
|
1542
199
|
var room = useRoom();
|
|
@@ -1547,14 +204,12 @@ function useBroadcastEvent() {
|
|
|
1547
204
|
/**
|
|
1548
205
|
* useErrorListener is a react hook that lets you react to potential room connection errors.
|
|
1549
206
|
*
|
|
1550
|
-
*
|
|
1551
|
-
* ``` typescript
|
|
207
|
+
* @example
|
|
1552
208
|
* import { useErrorListener } from "@liveblocks/react";
|
|
1553
209
|
*
|
|
1554
210
|
* useErrorListener(er => {
|
|
1555
211
|
* console.error(er);
|
|
1556
212
|
* })
|
|
1557
|
-
* ```
|
|
1558
213
|
*/
|
|
1559
214
|
function useErrorListener(callback) {
|
|
1560
215
|
var room = useRoom();
|
|
@@ -1573,8 +228,7 @@ function useErrorListener(callback) {
|
|
|
1573
228
|
/**
|
|
1574
229
|
* useEventListener is a react hook that lets you react to event broadcasted by other users in the room.
|
|
1575
230
|
*
|
|
1576
|
-
*
|
|
1577
|
-
* ``` typescript
|
|
231
|
+
* @example
|
|
1578
232
|
* import { useEventListener } from "@liveblocks/react";
|
|
1579
233
|
*
|
|
1580
234
|
* useEventListener(({ connectionId, event }) => {
|
|
@@ -1582,7 +236,6 @@ function useErrorListener(callback) {
|
|
|
1582
236
|
* // Do something
|
|
1583
237
|
* }
|
|
1584
238
|
* });
|
|
1585
|
-
* ```
|
|
1586
239
|
*/
|
|
1587
240
|
function useEventListener(callback) {
|
|
1588
241
|
var room = useRoom();
|
|
@@ -1601,16 +254,14 @@ function useEventListener(callback) {
|
|
|
1601
254
|
}, [room]);
|
|
1602
255
|
}
|
|
1603
256
|
/**
|
|
1604
|
-
*
|
|
257
|
+
* Gets the current user once it is connected to the room.
|
|
1605
258
|
*
|
|
1606
|
-
*
|
|
1607
|
-
*
|
|
1608
|
-
* import { useCurrentUser } from "@liveblocks/react";
|
|
259
|
+
* @example
|
|
260
|
+
* import { useSelf } from "@liveblocks/react";
|
|
1609
261
|
*
|
|
1610
|
-
* const user =
|
|
1611
|
-
* ```
|
|
262
|
+
* const user = useSelf();
|
|
1612
263
|
*/
|
|
1613
|
-
function
|
|
264
|
+
function useSelf() {
|
|
1614
265
|
var room = useRoom();
|
|
1615
266
|
var _a = React.useState(0), update = _a[1];
|
|
1616
267
|
React.useEffect(function () {
|
|
@@ -1624,74 +275,115 @@ function useCurrentUser() {
|
|
|
1624
275
|
room.unsubscribe("connection", onChange);
|
|
1625
276
|
};
|
|
1626
277
|
}, [room]);
|
|
1627
|
-
return room.
|
|
278
|
+
return room.getSelf();
|
|
1628
279
|
}
|
|
1629
|
-
function useStorage(
|
|
280
|
+
function useStorage() {
|
|
1630
281
|
var room = useRoom();
|
|
1631
|
-
var
|
|
1632
|
-
var _a = React.useState(0), update = _a[1];
|
|
282
|
+
var _a = React.useState(null), root = _a[0], setState = _a[1];
|
|
1633
283
|
React.useEffect(function () {
|
|
1634
|
-
function
|
|
1635
|
-
|
|
284
|
+
function fetchStorage() {
|
|
285
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
286
|
+
var storage;
|
|
287
|
+
return __generator(this, function (_a) {
|
|
288
|
+
switch (_a.label) {
|
|
289
|
+
case 0: return [4 /*yield*/, room.getStorage()];
|
|
290
|
+
case 1:
|
|
291
|
+
storage = _a.sent();
|
|
292
|
+
setState(storage.root);
|
|
293
|
+
return [2 /*return*/];
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
1636
297
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
return function () {
|
|
1640
|
-
room.unsubscribe("storage", onStorageChange);
|
|
1641
|
-
};
|
|
298
|
+
fetchStorage();
|
|
299
|
+
return function () { };
|
|
1642
300
|
}, [room]);
|
|
1643
|
-
|
|
1644
|
-
? storage.root
|
|
1645
|
-
: null;
|
|
1646
|
-
var actions = useStorageActions();
|
|
1647
|
-
return [root, actions];
|
|
301
|
+
return [root];
|
|
1648
302
|
}
|
|
1649
|
-
function
|
|
1650
|
-
var
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
303
|
+
function useMap(key) {
|
|
304
|
+
var _a;
|
|
305
|
+
var root = useStorage()[0];
|
|
306
|
+
var _b = React.useState(0), setCount = _b[1];
|
|
307
|
+
React.useEffect(function () {
|
|
308
|
+
if (root == null) {
|
|
309
|
+
return;
|
|
1654
310
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
311
|
+
var map = root.get(key);
|
|
312
|
+
if (map == null) {
|
|
313
|
+
map = new client.LiveMap();
|
|
314
|
+
root.set(key, map);
|
|
315
|
+
}
|
|
316
|
+
function onChange() {
|
|
317
|
+
setCount(function (x) { return x + 1; });
|
|
318
|
+
}
|
|
319
|
+
map.subscribe(onChange);
|
|
320
|
+
setCount(function (x) { return x + 1; });
|
|
321
|
+
return function () {
|
|
322
|
+
return map.unsubscribe(onChange);
|
|
323
|
+
};
|
|
324
|
+
}, [root]);
|
|
325
|
+
return (_a = root === null || root === void 0 ? void 0 : root.get(key)) !== null && _a !== void 0 ? _a : null;
|
|
326
|
+
}
|
|
327
|
+
function useList(key) {
|
|
328
|
+
var _a;
|
|
329
|
+
var root = useStorage()[0];
|
|
330
|
+
var _b = React.useState(0), setCount = _b[1];
|
|
331
|
+
React.useEffect(function () {
|
|
332
|
+
if (root == null) {
|
|
333
|
+
return;
|
|
1657
334
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
335
|
+
var list = root.get(key);
|
|
336
|
+
if (list == null) {
|
|
337
|
+
list = new client.LiveList();
|
|
338
|
+
root.set(key, list);
|
|
1660
339
|
}
|
|
1661
|
-
function
|
|
1662
|
-
|
|
340
|
+
function onChange() {
|
|
341
|
+
setCount(function (x) { return x + 1; });
|
|
1663
342
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
343
|
+
list.subscribe(onChange);
|
|
344
|
+
setCount(function (x) { return x + 1; });
|
|
345
|
+
return function () {
|
|
346
|
+
return list.unsubscribe(onChange);
|
|
347
|
+
};
|
|
348
|
+
}, [root]);
|
|
349
|
+
return (_a = root === null || root === void 0 ? void 0 : root.get(key)) !== null && _a !== void 0 ? _a : null;
|
|
350
|
+
}
|
|
351
|
+
function useObject(key) {
|
|
352
|
+
var _a;
|
|
353
|
+
var root = useStorage()[0];
|
|
354
|
+
var _b = React.useState(0), setCount = _b[1];
|
|
355
|
+
React.useEffect(function () {
|
|
356
|
+
if (root == null) {
|
|
357
|
+
return;
|
|
1666
358
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
359
|
+
var obj = root.get(key);
|
|
360
|
+
if (obj == null) {
|
|
361
|
+
obj = new client.LiveObject();
|
|
362
|
+
root.set(key, obj);
|
|
1669
363
|
}
|
|
1670
|
-
function
|
|
1671
|
-
|
|
364
|
+
function onChange() {
|
|
365
|
+
setCount(function (x) { return x + 1; });
|
|
1672
366
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
moveItem: moveItem,
|
|
1678
|
-
deleteItem: deleteItem,
|
|
1679
|
-
deleteItemById: deleteItemById,
|
|
1680
|
-
pushItem: pushItem,
|
|
367
|
+
obj.subscribe(onChange);
|
|
368
|
+
setCount(function (x) { return x + 1; });
|
|
369
|
+
return function () {
|
|
370
|
+
return obj.unsubscribe(onChange);
|
|
1681
371
|
};
|
|
1682
|
-
}, [
|
|
372
|
+
}, [root]);
|
|
373
|
+
return (_a = root === null || root === void 0 ? void 0 : root.get(key)) !== null && _a !== void 0 ? _a : null;
|
|
1683
374
|
}
|
|
1684
375
|
|
|
1685
376
|
exports.LiveblocksProvider = LiveblocksProvider;
|
|
1686
377
|
exports.RoomProvider = RoomProvider;
|
|
1687
|
-
exports.createClient = createClient;
|
|
1688
378
|
exports.useBroadcastEvent = useBroadcastEvent;
|
|
1689
|
-
exports.useCurrentUser = useCurrentUser;
|
|
1690
379
|
exports.useErrorListener = useErrorListener;
|
|
1691
380
|
exports.useEventListener = useEventListener;
|
|
381
|
+
exports.useList = useList;
|
|
382
|
+
exports.useMap = useMap;
|
|
1692
383
|
exports.useMyPresence = useMyPresence;
|
|
384
|
+
exports.useObject = useObject;
|
|
1693
385
|
exports.useOthers = useOthers;
|
|
386
|
+
exports.useSelf = useSelf;
|
|
1694
387
|
exports.useStorage = useStorage;
|
|
1695
|
-
exports.useStorageActions = useStorageActions;
|
|
1696
388
|
exports.useUpdateMyPresence = useUpdateMyPresence;
|
|
1697
389
|
//# sourceMappingURL=index.js.map
|