kritzel-stencil 0.1.88 → 0.1.89
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/dist/cjs/index.cjs.js +210 -3024
- package/dist/cjs/kritzel-active-users_42.cjs.entry.js +19 -16
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/stencil.cjs.js +1 -1
- package/dist/cjs/{workspace.migrations-6IqGagXh.js → workspace.migrations-B_dbGmyV.js} +2923 -85
- package/dist/collection/classes/core/core.class.js +1 -1
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +34 -6
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +9 -9
- package/dist/collection/configs/default-asset-storage.config.js +1 -1
- package/dist/collection/configs/default-sync.config.js +1 -1
- package/dist/collection/constants/version.js +1 -1
- package/dist/collection/index.js +8 -8
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-editor.js +1 -1
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/{p-BxIC2o6G.js → p-CYnEIdQL.js} +1 -1
- package/dist/components/{p-Br9ky8bs.js → p-CaRobExg.js} +1 -1
- package/dist/esm/index.js +208 -3022
- package/dist/esm/kritzel-active-users_42.entry.js +19 -16
- package/dist/esm/loader.js +1 -1
- package/dist/esm/stencil.js +1 -1
- package/dist/esm/{workspace.migrations-Dviv45Qv.js → workspace.migrations-CvfM8aov.js} +2820 -5
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/p-7fc12473.entry.js +9 -0
- package/dist/stencil/p-CvfM8aov.js +1 -0
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/core/core.class.d.ts +1 -1
- package/dist/types/classes/{assets → providers/assets}/asset-resolver.class.d.ts +3 -3
- package/dist/types/classes/{assets → providers/assets}/http-asset-provider.class.d.ts +2 -2
- package/dist/types/classes/{assets → providers/assets}/indexeddb-asset-provider.class.d.ts +2 -2
- package/dist/types/classes/{assets → providers/assets}/presigned-asset-provider.class.d.ts +2 -2
- package/dist/types/classes/providers/{broadcast-sync-provider.class.d.ts → sync/broadcast-sync-provider.class.d.ts} +1 -1
- package/dist/types/classes/providers/{hocuspocus-sync-provider.class.d.ts → sync/hocuspocus-sync-provider.class.d.ts} +1 -1
- package/dist/types/classes/providers/{indexeddb-sync-provider.class.d.ts → sync/indexeddb-sync-provider.class.d.ts} +1 -1
- package/dist/types/classes/providers/{websocket-sync-provider.class.d.ts → sync/websocket-sync-provider.class.d.ts} +1 -1
- package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +2 -0
- package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +2 -2
- package/dist/types/components.d.ts +12 -4
- package/dist/types/constants/version.d.ts +1 -1
- package/dist/types/index.d.ts +8 -8
- package/package.json +1 -1
- package/dist/stencil/p-1620740b.entry.js +0 -9
- package/dist/stencil/p-Dviv45Qv.js +0 -1
- /package/dist/collection/classes/{assets → providers/assets}/asset-resolver.class.js +0 -0
- /package/dist/collection/classes/{assets → providers/assets}/http-asset-provider.class.js +0 -0
- /package/dist/collection/classes/{assets → providers/assets}/indexeddb-asset-provider.class.js +0 -0
- /package/dist/collection/classes/{assets → providers/assets}/presigned-asset-provider.class.js +0 -0
- /package/dist/collection/classes/providers/{broadcast-sync-provider.class.js → sync/broadcast-sync-provider.class.js} +0 -0
- /package/dist/collection/classes/providers/{hocuspocus-sync-provider.class.js → sync/hocuspocus-sync-provider.class.js} +0 -0
- /package/dist/collection/classes/providers/{indexeddb-sync-provider.class.js → sync/indexeddb-sync-provider.class.js} +0 -0
- /package/dist/collection/classes/providers/{websocket-sync-provider.class.js → sync/websocket-sync-provider.class.js} +0 -0
package/dist/cjs/index.cjs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var workspace_migrations = require('./workspace.migrations-
|
|
3
|
+
var workspace_migrations = require('./workspace.migrations-B_dbGmyV.js');
|
|
4
4
|
var Y = require('yjs');
|
|
5
5
|
var yWebsocket = require('y-websocket');
|
|
6
6
|
require('y-indexeddb');
|
|
@@ -31,7 +31,7 @@ var Y__namespace = /*#__PURE__*/_interopNamespace(Y);
|
|
|
31
31
|
* @module math
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
const floor
|
|
34
|
+
const floor = Math.floor;
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* @function
|
|
@@ -39,7 +39,7 @@ const floor$2 = Math.floor;
|
|
|
39
39
|
* @param {number} b
|
|
40
40
|
* @return {number} The smaller element of a and b
|
|
41
41
|
*/
|
|
42
|
-
const min
|
|
42
|
+
const min = (a, b) => a < b ? a : b;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* @function
|
|
@@ -47,12 +47,12 @@ const min$2 = (a, b) => a < b ? a : b;
|
|
|
47
47
|
* @param {number} b
|
|
48
48
|
* @return {number} The bigger element of a and b
|
|
49
49
|
*/
|
|
50
|
-
const max
|
|
50
|
+
const max = (a, b) => a > b ? a : b;
|
|
51
51
|
|
|
52
52
|
/* eslint-env browser */
|
|
53
53
|
|
|
54
|
-
const BIT8
|
|
55
|
-
const BITS7
|
|
54
|
+
const BIT8 = 128;
|
|
55
|
+
const BITS7 = 127;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Utility helpers for working with numbers.
|
|
@@ -61,7 +61,7 @@ const BITS7$2 = 127;
|
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
const MAX_SAFE_INTEGER
|
|
64
|
+
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Efficient schema-less binary encoding with support for variable length encoding.
|
|
@@ -95,7 +95,7 @@ const MAX_SAFE_INTEGER$2 = Number.MAX_SAFE_INTEGER;
|
|
|
95
95
|
/**
|
|
96
96
|
* A BinaryEncoder handles the encoding to an Uint8Array.
|
|
97
97
|
*/
|
|
98
|
-
|
|
98
|
+
class Encoder {
|
|
99
99
|
constructor () {
|
|
100
100
|
this.cpos = 0;
|
|
101
101
|
this.cbuf = new Uint8Array(100);
|
|
@@ -104,13 +104,13 @@ let Encoder$1 = class Encoder {
|
|
|
104
104
|
*/
|
|
105
105
|
this.bufs = [];
|
|
106
106
|
}
|
|
107
|
-
}
|
|
107
|
+
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* @function
|
|
111
111
|
* @return {Encoder}
|
|
112
112
|
*/
|
|
113
|
-
const createEncoder
|
|
113
|
+
const createEncoder = () => new Encoder();
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* The current length of the encoded data.
|
|
@@ -119,7 +119,7 @@ const createEncoder$1 = () => new Encoder$1();
|
|
|
119
119
|
* @param {Encoder} encoder
|
|
120
120
|
* @return {number}
|
|
121
121
|
*/
|
|
122
|
-
const length
|
|
122
|
+
const length = encoder => {
|
|
123
123
|
let len = encoder.cpos;
|
|
124
124
|
for (let i = 0; i < encoder.bufs.length; i++) {
|
|
125
125
|
len += encoder.bufs[i].length;
|
|
@@ -134,8 +134,8 @@ const length$2 = encoder => {
|
|
|
134
134
|
* @param {Encoder} encoder
|
|
135
135
|
* @return {Uint8Array<ArrayBuffer>} The created ArrayBuffer.
|
|
136
136
|
*/
|
|
137
|
-
const toUint8Array
|
|
138
|
-
const uint8arr = new Uint8Array(length
|
|
137
|
+
const toUint8Array = encoder => {
|
|
138
|
+
const uint8arr = new Uint8Array(length(encoder));
|
|
139
139
|
let curPos = 0;
|
|
140
140
|
for (let i = 0; i < encoder.bufs.length; i++) {
|
|
141
141
|
const d = encoder.bufs[i];
|
|
@@ -153,7 +153,7 @@ const toUint8Array$1 = encoder => {
|
|
|
153
153
|
* @param {Encoder} encoder
|
|
154
154
|
* @param {number} num The byte that is to be encoded.
|
|
155
155
|
*/
|
|
156
|
-
const write
|
|
156
|
+
const write = (encoder, num) => {
|
|
157
157
|
const bufferLen = encoder.cbuf.length;
|
|
158
158
|
if (encoder.cpos === bufferLen) {
|
|
159
159
|
encoder.bufs.push(encoder.cbuf);
|
|
@@ -170,12 +170,12 @@ const write$2 = (encoder, num) => {
|
|
|
170
170
|
* @param {Encoder} encoder
|
|
171
171
|
* @param {number} num The number that is to be encoded.
|
|
172
172
|
*/
|
|
173
|
-
const writeVarUint
|
|
174
|
-
while (num > BITS7
|
|
175
|
-
write
|
|
176
|
-
num = floor
|
|
173
|
+
const writeVarUint = (encoder, num) => {
|
|
174
|
+
while (num > BITS7) {
|
|
175
|
+
write(encoder, BIT8 | (BITS7 & num));
|
|
176
|
+
num = floor(num / 128); // shift >>> 7
|
|
177
177
|
}
|
|
178
|
-
write
|
|
178
|
+
write(encoder, BITS7 & num);
|
|
179
179
|
};
|
|
180
180
|
|
|
181
181
|
/**
|
|
@@ -185,10 +185,10 @@ const writeVarUint$2 = (encoder, num) => {
|
|
|
185
185
|
* @param {Encoder} encoder
|
|
186
186
|
* @param {Uint8Array} uint8Array
|
|
187
187
|
*/
|
|
188
|
-
const writeUint8Array
|
|
188
|
+
const writeUint8Array = (encoder, uint8Array) => {
|
|
189
189
|
const bufferLen = encoder.cbuf.length;
|
|
190
190
|
const cpos = encoder.cpos;
|
|
191
|
-
const leftCopyLen = min
|
|
191
|
+
const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
|
|
192
192
|
const rightCopyLen = uint8Array.length - leftCopyLen;
|
|
193
193
|
encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
|
|
194
194
|
encoder.cpos += leftCopyLen;
|
|
@@ -197,7 +197,7 @@ const writeUint8Array$2 = (encoder, uint8Array) => {
|
|
|
197
197
|
// Append new buffer
|
|
198
198
|
encoder.bufs.push(encoder.cbuf);
|
|
199
199
|
// must have at least size of remaining buffer
|
|
200
|
-
encoder.cbuf = new Uint8Array(max
|
|
200
|
+
encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
|
|
201
201
|
// copy array
|
|
202
202
|
encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
|
|
203
203
|
encoder.cpos = rightCopyLen;
|
|
@@ -211,9 +211,9 @@ const writeUint8Array$2 = (encoder, uint8Array) => {
|
|
|
211
211
|
* @param {Encoder} encoder
|
|
212
212
|
* @param {Uint8Array} uint8Array
|
|
213
213
|
*/
|
|
214
|
-
const writeVarUint8Array
|
|
215
|
-
writeVarUint
|
|
216
|
-
writeUint8Array
|
|
214
|
+
const writeVarUint8Array = (encoder, uint8Array) => {
|
|
215
|
+
writeVarUint(encoder, uint8Array.byteLength);
|
|
216
|
+
writeUint8Array(encoder, uint8Array);
|
|
217
217
|
};
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -227,7 +227,7 @@ const writeVarUint8Array$2 = (encoder, uint8Array) => {
|
|
|
227
227
|
* @return {Error}
|
|
228
228
|
*/
|
|
229
229
|
/* c8 ignore next */
|
|
230
|
-
const create
|
|
230
|
+
const create = s => new Error(s);
|
|
231
231
|
|
|
232
232
|
/**
|
|
233
233
|
* Efficient schema-less binary decoding with support for variable length encoding.
|
|
@@ -258,14 +258,14 @@ const create$4 = s => new Error(s);
|
|
|
258
258
|
*/
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
const errorUnexpectedEndOfArray
|
|
262
|
-
const errorIntegerOutOfRange
|
|
261
|
+
const errorUnexpectedEndOfArray = create('Unexpected end of array');
|
|
262
|
+
const errorIntegerOutOfRange = create('Integer out of Range');
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
265
|
* A Decoder handles the decoding of an Uint8Array.
|
|
266
266
|
* @template {ArrayBufferLike} [Buf=ArrayBufferLike]
|
|
267
267
|
*/
|
|
268
|
-
|
|
268
|
+
class Decoder {
|
|
269
269
|
/**
|
|
270
270
|
* @param {Uint8Array<Buf>} uint8Array Binary data to decode
|
|
271
271
|
*/
|
|
@@ -283,7 +283,7 @@ let Decoder$1 = class Decoder {
|
|
|
283
283
|
*/
|
|
284
284
|
this.pos = 0;
|
|
285
285
|
}
|
|
286
|
-
}
|
|
286
|
+
}
|
|
287
287
|
|
|
288
288
|
/**
|
|
289
289
|
* @function
|
|
@@ -291,7 +291,7 @@ let Decoder$1 = class Decoder {
|
|
|
291
291
|
* @param {Uint8Array<Buf>} uint8Array
|
|
292
292
|
* @return {Decoder<Buf>}
|
|
293
293
|
*/
|
|
294
|
-
const createDecoder
|
|
294
|
+
const createDecoder = uint8Array => new Decoder(uint8Array);
|
|
295
295
|
|
|
296
296
|
/**
|
|
297
297
|
* Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
|
|
@@ -305,7 +305,7 @@ const createDecoder$1 = uint8Array => new Decoder$1(uint8Array);
|
|
|
305
305
|
* @param {number} len The length of bytes to read
|
|
306
306
|
* @return {Uint8Array<Buf>}
|
|
307
307
|
*/
|
|
308
|
-
const readUint8Array
|
|
308
|
+
const readUint8Array = (decoder, len) => {
|
|
309
309
|
const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
|
|
310
310
|
decoder.pos += len;
|
|
311
311
|
return view
|
|
@@ -322,7 +322,7 @@ const readUint8Array$2 = (decoder, len) => {
|
|
|
322
322
|
* @param {Decoder<Buf>} decoder
|
|
323
323
|
* @return {Uint8Array<Buf>}
|
|
324
324
|
*/
|
|
325
|
-
const readVarUint8Array
|
|
325
|
+
const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder));
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Read unsigned integer (32bit) with variable length.
|
|
@@ -334,3030 +334,226 @@ const readVarUint8Array$2 = decoder => readUint8Array$2(decoder, readVarUint$2(d
|
|
|
334
334
|
* @param {Decoder} decoder
|
|
335
335
|
* @return {number} An unsigned integer.length
|
|
336
336
|
*/
|
|
337
|
-
const readVarUint
|
|
337
|
+
const readVarUint = decoder => {
|
|
338
338
|
let num = 0;
|
|
339
339
|
let mult = 1;
|
|
340
340
|
const len = decoder.arr.length;
|
|
341
341
|
while (decoder.pos < len) {
|
|
342
342
|
const r = decoder.arr[decoder.pos++];
|
|
343
343
|
// num = num | ((r & binary.BITS7) << len)
|
|
344
|
-
num = num + (r & BITS7
|
|
344
|
+
num = num + (r & BITS7) * mult; // shift $r << (7*#iterations) and add it to num
|
|
345
345
|
mult *= 128; // next iteration, shift 7 "more" to the left
|
|
346
|
-
if (r < BIT8
|
|
346
|
+
if (r < BIT8) {
|
|
347
347
|
return num
|
|
348
348
|
}
|
|
349
349
|
/* c8 ignore start */
|
|
350
|
-
if (num > MAX_SAFE_INTEGER
|
|
351
|
-
throw errorIntegerOutOfRange
|
|
352
|
-
}
|
|
353
|
-
/* c8 ignore stop */
|
|
354
|
-
}
|
|
355
|
-
throw errorUnexpectedEndOfArray$2
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* BroadcastChannel sync provider for cross-tab synchronization
|
|
360
|
-
* This is a lightweight alternative to y-webrtc for browser-tab-only sync
|
|
361
|
-
*/
|
|
362
|
-
class BroadcastSyncProvider {
|
|
363
|
-
type = 'local';
|
|
364
|
-
doc;
|
|
365
|
-
channel;
|
|
366
|
-
_synced = false;
|
|
367
|
-
constructor(docName, doc, _options) {
|
|
368
|
-
this.doc = doc;
|
|
369
|
-
this.channel = new BroadcastChannel(docName);
|
|
370
|
-
// Handle incoming messages from other tabs
|
|
371
|
-
this.channel.onmessage = (event) => {
|
|
372
|
-
this.handleMessage(event.data);
|
|
373
|
-
};
|
|
374
|
-
// Listen to document updates and broadcast them
|
|
375
|
-
this.doc.on('update', this.handleDocUpdate);
|
|
376
|
-
// Send initial sync request
|
|
377
|
-
this.broadcastSync();
|
|
378
|
-
// Mark as synced after a short delay (to receive any pending updates)
|
|
379
|
-
setTimeout(() => {
|
|
380
|
-
this._synced = true;
|
|
381
|
-
}, 100);
|
|
382
|
-
if (!_options?.quiet) {
|
|
383
|
-
console.info(`BroadcastChannel Provider initialized: ${docName}`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
handleDocUpdate = (update, origin) => {
|
|
387
|
-
// Don't broadcast updates that came from other tabs (to prevent loops)
|
|
388
|
-
if (origin !== this) {
|
|
389
|
-
const encoder = createEncoder$1();
|
|
390
|
-
writeVarUint$2(encoder, 0); // Message type: sync update
|
|
391
|
-
writeVarUint8Array$2(encoder, update);
|
|
392
|
-
this.channel.postMessage(toUint8Array$1(encoder));
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
handleMessage(message) {
|
|
396
|
-
const decoder = createDecoder$1(new Uint8Array(message));
|
|
397
|
-
const messageType = readVarUint$2(decoder);
|
|
398
|
-
switch (messageType) {
|
|
399
|
-
case 0: // Sync update
|
|
400
|
-
const update = readVarUint8Array$2(decoder);
|
|
401
|
-
Y__namespace.applyUpdate(this.doc, update, this);
|
|
402
|
-
break;
|
|
403
|
-
case 1: // Sync request
|
|
404
|
-
this.broadcastSync();
|
|
405
|
-
break;
|
|
406
|
-
case 2: // Sync response
|
|
407
|
-
const stateVector = readVarUint8Array$2(decoder);
|
|
408
|
-
const updateResponse = Y__namespace.encodeStateAsUpdate(this.doc, stateVector);
|
|
409
|
-
if (updateResponse.length > 0) {
|
|
410
|
-
const encoder = createEncoder$1();
|
|
411
|
-
writeVarUint$2(encoder, 0); // Send as regular update
|
|
412
|
-
writeVarUint8Array$2(encoder, updateResponse);
|
|
413
|
-
this.channel.postMessage(toUint8Array$1(encoder));
|
|
414
|
-
}
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
broadcastSync() {
|
|
419
|
-
// Broadcast our current state vector to request missing updates
|
|
420
|
-
const encoder = createEncoder$1();
|
|
421
|
-
writeVarUint$2(encoder, 2); // Message type: sync response
|
|
422
|
-
writeVarUint8Array$2(encoder, Y__namespace.encodeStateVector(this.doc));
|
|
423
|
-
this.channel.postMessage(toUint8Array$1(encoder));
|
|
424
|
-
}
|
|
425
|
-
async connect() {
|
|
426
|
-
// Wait for initial sync to complete
|
|
427
|
-
if (this._synced) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
return new Promise((resolve) => {
|
|
431
|
-
const checkSync = () => {
|
|
432
|
-
if (this._synced) {
|
|
433
|
-
resolve();
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
setTimeout(checkSync, 50);
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
checkSync();
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
disconnect() {
|
|
443
|
-
// BroadcastChannel doesn't have explicit disconnect
|
|
444
|
-
}
|
|
445
|
-
async reconnect() {
|
|
446
|
-
this.disconnect();
|
|
447
|
-
return this.connect();
|
|
448
|
-
}
|
|
449
|
-
destroy() {
|
|
450
|
-
this.doc.off('update', this.handleDocUpdate);
|
|
451
|
-
this.channel.close();
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* WebSocket sync provider for real-time collaboration
|
|
457
|
-
*/
|
|
458
|
-
class WebSocketSyncProvider {
|
|
459
|
-
type = 'network';
|
|
460
|
-
provider;
|
|
461
|
-
isConnected = false;
|
|
462
|
-
_quiet = false;
|
|
463
|
-
get awareness() {
|
|
464
|
-
return this.provider.awareness;
|
|
465
|
-
}
|
|
466
|
-
constructor(docName, doc, options) {
|
|
467
|
-
const url = options?.url || 'ws://localhost:1234';
|
|
468
|
-
const roomName = options?.roomName || docName;
|
|
469
|
-
this.provider = new yWebsocket.WebsocketProvider(url, roomName, doc, {
|
|
470
|
-
params: options?.params,
|
|
471
|
-
protocols: options?.protocols,
|
|
472
|
-
WebSocketPolyfill: options?.WebSocketPolyfill,
|
|
473
|
-
awareness: options?.awareness,
|
|
474
|
-
maxBackoffTime: options?.maxBackoffTime,
|
|
475
|
-
disableBc: true,
|
|
476
|
-
});
|
|
477
|
-
this._quiet = options?.quiet ?? false;
|
|
478
|
-
this.setupEventListeners();
|
|
479
|
-
if (!this._quiet) {
|
|
480
|
-
console.info(`WebSocket Provider initialized: ${url}/${roomName}`);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Static factory method for creating WebSocketSyncProvider with configuration options
|
|
485
|
-
* Returns a ProviderFactory that can be used in sync configuration
|
|
486
|
-
*/
|
|
487
|
-
static with(options) {
|
|
488
|
-
return {
|
|
489
|
-
create: (docName, doc, runtimeOptions) => {
|
|
490
|
-
const mergedOptions = runtimeOptions ? { ...options, ...runtimeOptions } : options;
|
|
491
|
-
return new WebSocketSyncProvider(docName, doc, mergedOptions);
|
|
492
|
-
},
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
setupEventListeners() {
|
|
496
|
-
this.provider.on('status', ({ status }) => {
|
|
497
|
-
if (status === 'connected') {
|
|
498
|
-
this.isConnected = true;
|
|
499
|
-
if (!this._quiet) {
|
|
500
|
-
console.info('WebSocket connected');
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
else if (status === 'disconnected') {
|
|
504
|
-
this.isConnected = false;
|
|
505
|
-
if (!this._quiet) {
|
|
506
|
-
console.info('WebSocket disconnected');
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
this.provider.on('sync', (synced) => {
|
|
511
|
-
if (synced && !this._quiet) {
|
|
512
|
-
console.info('WebSocket synced');
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
async connect() {
|
|
517
|
-
if (this.isConnected) {
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
return new Promise((resolve, reject) => {
|
|
521
|
-
const timeout = setTimeout(() => {
|
|
522
|
-
reject(new Error('WebSocket connection timeout'));
|
|
523
|
-
}, 10000); // 10 second timeout
|
|
524
|
-
const statusHandler = ({ status }) => {
|
|
525
|
-
if (status === 'connected') {
|
|
526
|
-
clearTimeout(timeout);
|
|
527
|
-
this.provider.off('status', statusHandler);
|
|
528
|
-
this.isConnected = true;
|
|
529
|
-
resolve();
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
this.provider.on('status', statusHandler);
|
|
533
|
-
// If already connected, resolve immediately
|
|
534
|
-
if (this.provider.wsconnected) {
|
|
535
|
-
clearTimeout(timeout);
|
|
536
|
-
this.provider.off('status', statusHandler);
|
|
537
|
-
this.isConnected = true;
|
|
538
|
-
resolve();
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
disconnect() {
|
|
543
|
-
if (this.provider) {
|
|
544
|
-
this.provider.disconnect();
|
|
545
|
-
}
|
|
546
|
-
this.isConnected = false;
|
|
547
|
-
}
|
|
548
|
-
async reconnect() {
|
|
549
|
-
this.disconnect();
|
|
550
|
-
return this.connect();
|
|
551
|
-
}
|
|
552
|
-
destroy() {
|
|
553
|
-
if (this.provider) {
|
|
554
|
-
this.provider.destroy();
|
|
555
|
-
}
|
|
556
|
-
this.isConnected = false;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Common Math expressions.
|
|
562
|
-
*
|
|
563
|
-
* @module math
|
|
564
|
-
*/
|
|
565
|
-
|
|
566
|
-
const floor$1 = Math.floor;
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* @function
|
|
570
|
-
* @param {number} a
|
|
571
|
-
* @param {number} b
|
|
572
|
-
* @return {number} The smaller element of a and b
|
|
573
|
-
*/
|
|
574
|
-
const min$1 = (a, b) => a < b ? a : b;
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* @function
|
|
578
|
-
* @param {number} a
|
|
579
|
-
* @param {number} b
|
|
580
|
-
* @return {number} The bigger element of a and b
|
|
581
|
-
*/
|
|
582
|
-
const max$1 = (a, b) => a > b ? a : b;
|
|
583
|
-
|
|
584
|
-
/* eslint-env browser */
|
|
585
|
-
|
|
586
|
-
const BIT8$1 = 128;
|
|
587
|
-
const BITS7$1 = 127;
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Utility helpers for working with numbers.
|
|
591
|
-
*
|
|
592
|
-
* @module number
|
|
593
|
-
*/
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const MAX_SAFE_INTEGER$1 = Number.MAX_SAFE_INTEGER;
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* @param {string} str
|
|
600
|
-
* @return {Uint8Array}
|
|
601
|
-
*/
|
|
602
|
-
const _encodeUtf8Polyfill$1 = str => {
|
|
603
|
-
const encodedString = unescape(encodeURIComponent(str));
|
|
604
|
-
const len = encodedString.length;
|
|
605
|
-
const buf = new Uint8Array(len);
|
|
606
|
-
for (let i = 0; i < len; i++) {
|
|
607
|
-
buf[i] = /** @type {number} */ (encodedString.codePointAt(i));
|
|
608
|
-
}
|
|
609
|
-
return buf
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
/* c8 ignore next */
|
|
613
|
-
const utf8TextEncoder$1 = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null);
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* @param {string} str
|
|
617
|
-
* @return {Uint8Array}
|
|
618
|
-
*/
|
|
619
|
-
const _encodeUtf8Native$1 = str => utf8TextEncoder$1.encode(str);
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* @param {string} str
|
|
623
|
-
* @return {Uint8Array}
|
|
624
|
-
*/
|
|
625
|
-
/* c8 ignore next */
|
|
626
|
-
const encodeUtf8$1 = utf8TextEncoder$1 ? _encodeUtf8Native$1 : _encodeUtf8Polyfill$1;
|
|
627
|
-
|
|
628
|
-
/* c8 ignore next */
|
|
629
|
-
let utf8TextDecoder$1 = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
630
|
-
|
|
631
|
-
/* c8 ignore start */
|
|
632
|
-
if (utf8TextDecoder$1 && utf8TextDecoder$1.decode(new Uint8Array()).length === 1) {
|
|
633
|
-
// Safari doesn't handle BOM correctly.
|
|
634
|
-
// This fixes a bug in Safari 13.0.5 where it produces a BOM the first time it is called.
|
|
635
|
-
// utf8TextDecoder.decode(new Uint8Array()).length === 1 on the first call and
|
|
636
|
-
// utf8TextDecoder.decode(new Uint8Array()).length === 1 on the second call
|
|
637
|
-
// Another issue is that from then on no BOM chars are recognized anymore
|
|
638
|
-
/* c8 ignore next */
|
|
639
|
-
utf8TextDecoder$1 = null;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Efficient schema-less binary encoding with support for variable length encoding.
|
|
644
|
-
*
|
|
645
|
-
* Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
|
|
646
|
-
*
|
|
647
|
-
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
648
|
-
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
649
|
-
* which is also used in Protocol Buffers.
|
|
650
|
-
*
|
|
651
|
-
* ```js
|
|
652
|
-
* // encoding step
|
|
653
|
-
* const encoder = encoding.createEncoder()
|
|
654
|
-
* encoding.writeVarUint(encoder, 256)
|
|
655
|
-
* encoding.writeVarString(encoder, 'Hello world!')
|
|
656
|
-
* const buf = encoding.toUint8Array(encoder)
|
|
657
|
-
* ```
|
|
658
|
-
*
|
|
659
|
-
* ```js
|
|
660
|
-
* // decoding step
|
|
661
|
-
* const decoder = decoding.createDecoder(buf)
|
|
662
|
-
* decoding.readVarUint(decoder) // => 256
|
|
663
|
-
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
664
|
-
* decoding.hasContent(decoder) // => false - all data is read
|
|
665
|
-
* ```
|
|
666
|
-
*
|
|
667
|
-
* @module encoding
|
|
668
|
-
*/
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* Write one byte to the encoder.
|
|
673
|
-
*
|
|
674
|
-
* @function
|
|
675
|
-
* @param {Encoder} encoder
|
|
676
|
-
* @param {number} num The byte that is to be encoded.
|
|
677
|
-
*/
|
|
678
|
-
const write$1 = (encoder, num) => {
|
|
679
|
-
const bufferLen = encoder.cbuf.length;
|
|
680
|
-
if (encoder.cpos === bufferLen) {
|
|
681
|
-
encoder.bufs.push(encoder.cbuf);
|
|
682
|
-
encoder.cbuf = new Uint8Array(bufferLen * 2);
|
|
683
|
-
encoder.cpos = 0;
|
|
684
|
-
}
|
|
685
|
-
encoder.cbuf[encoder.cpos++] = num;
|
|
686
|
-
};
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Write a variable length unsigned integer. Max encodable integer is 2^53.
|
|
690
|
-
*
|
|
691
|
-
* @function
|
|
692
|
-
* @param {Encoder} encoder
|
|
693
|
-
* @param {number} num The number that is to be encoded.
|
|
694
|
-
*/
|
|
695
|
-
const writeVarUint$1 = (encoder, num) => {
|
|
696
|
-
while (num > BITS7$1) {
|
|
697
|
-
write$1(encoder, BIT8$1 | (BITS7$1 & num));
|
|
698
|
-
num = floor$1(num / 128); // shift >>> 7
|
|
699
|
-
}
|
|
700
|
-
write$1(encoder, BITS7$1 & num);
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* A cache to store strings temporarily
|
|
705
|
-
*/
|
|
706
|
-
const _strBuffer$1 = new Uint8Array(30000);
|
|
707
|
-
const _maxStrBSize$1 = _strBuffer$1.length / 3;
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* Write a variable length string.
|
|
711
|
-
*
|
|
712
|
-
* @function
|
|
713
|
-
* @param {Encoder} encoder
|
|
714
|
-
* @param {String} str The string that is to be encoded.
|
|
715
|
-
*/
|
|
716
|
-
const _writeVarStringNative$1 = (encoder, str) => {
|
|
717
|
-
if (str.length < _maxStrBSize$1) {
|
|
718
|
-
// We can encode the string into the existing buffer
|
|
719
|
-
/* c8 ignore next */
|
|
720
|
-
const written = utf8TextEncoder$1.encodeInto(str, _strBuffer$1).written || 0;
|
|
721
|
-
writeVarUint$1(encoder, written);
|
|
722
|
-
for (let i = 0; i < written; i++) {
|
|
723
|
-
write$1(encoder, _strBuffer$1[i]);
|
|
724
|
-
}
|
|
725
|
-
} else {
|
|
726
|
-
writeVarUint8Array$1(encoder, encodeUtf8$1(str));
|
|
727
|
-
}
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* Write a variable length string.
|
|
732
|
-
*
|
|
733
|
-
* @function
|
|
734
|
-
* @param {Encoder} encoder
|
|
735
|
-
* @param {String} str The string that is to be encoded.
|
|
736
|
-
*/
|
|
737
|
-
const _writeVarStringPolyfill$1 = (encoder, str) => {
|
|
738
|
-
const encodedString = unescape(encodeURIComponent(str));
|
|
739
|
-
const len = encodedString.length;
|
|
740
|
-
writeVarUint$1(encoder, len);
|
|
741
|
-
for (let i = 0; i < len; i++) {
|
|
742
|
-
write$1(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* Write a variable length string.
|
|
748
|
-
*
|
|
749
|
-
* @function
|
|
750
|
-
* @param {Encoder} encoder
|
|
751
|
-
* @param {String} str The string that is to be encoded.
|
|
752
|
-
*/
|
|
753
|
-
/* c8 ignore next */
|
|
754
|
-
const writeVarString$1 = (utf8TextEncoder$1 && /** @type {any} */ (utf8TextEncoder$1).encodeInto) ? _writeVarStringNative$1 : _writeVarStringPolyfill$1;
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Append fixed-length Uint8Array to the encoder.
|
|
758
|
-
*
|
|
759
|
-
* @function
|
|
760
|
-
* @param {Encoder} encoder
|
|
761
|
-
* @param {Uint8Array} uint8Array
|
|
762
|
-
*/
|
|
763
|
-
const writeUint8Array$1 = (encoder, uint8Array) => {
|
|
764
|
-
const bufferLen = encoder.cbuf.length;
|
|
765
|
-
const cpos = encoder.cpos;
|
|
766
|
-
const leftCopyLen = min$1(bufferLen - cpos, uint8Array.length);
|
|
767
|
-
const rightCopyLen = uint8Array.length - leftCopyLen;
|
|
768
|
-
encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
|
|
769
|
-
encoder.cpos += leftCopyLen;
|
|
770
|
-
if (rightCopyLen > 0) {
|
|
771
|
-
// Still something to write, write right half..
|
|
772
|
-
// Append new buffer
|
|
773
|
-
encoder.bufs.push(encoder.cbuf);
|
|
774
|
-
// must have at least size of remaining buffer
|
|
775
|
-
encoder.cbuf = new Uint8Array(max$1(bufferLen * 2, rightCopyLen));
|
|
776
|
-
// copy array
|
|
777
|
-
encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
|
|
778
|
-
encoder.cpos = rightCopyLen;
|
|
779
|
-
}
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Append an Uint8Array to Encoder.
|
|
784
|
-
*
|
|
785
|
-
* @function
|
|
786
|
-
* @param {Encoder} encoder
|
|
787
|
-
* @param {Uint8Array} uint8Array
|
|
788
|
-
*/
|
|
789
|
-
const writeVarUint8Array$1 = (encoder, uint8Array) => {
|
|
790
|
-
writeVarUint$1(encoder, uint8Array.byteLength);
|
|
791
|
-
writeUint8Array$1(encoder, uint8Array);
|
|
792
|
-
};
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* Error helpers.
|
|
796
|
-
*
|
|
797
|
-
* @module error
|
|
798
|
-
*/
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* @param {string} s
|
|
802
|
-
* @return {Error}
|
|
803
|
-
*/
|
|
804
|
-
/* c8 ignore next */
|
|
805
|
-
const create$3 = s => new Error(s);
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
* Efficient schema-less binary decoding with support for variable length encoding.
|
|
809
|
-
*
|
|
810
|
-
* Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
|
|
811
|
-
*
|
|
812
|
-
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
813
|
-
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
814
|
-
* which is also used in Protocol Buffers.
|
|
815
|
-
*
|
|
816
|
-
* ```js
|
|
817
|
-
* // encoding step
|
|
818
|
-
* const encoder = encoding.createEncoder()
|
|
819
|
-
* encoding.writeVarUint(encoder, 256)
|
|
820
|
-
* encoding.writeVarString(encoder, 'Hello world!')
|
|
821
|
-
* const buf = encoding.toUint8Array(encoder)
|
|
822
|
-
* ```
|
|
823
|
-
*
|
|
824
|
-
* ```js
|
|
825
|
-
* // decoding step
|
|
826
|
-
* const decoder = decoding.createDecoder(buf)
|
|
827
|
-
* decoding.readVarUint(decoder) // => 256
|
|
828
|
-
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
829
|
-
* decoding.hasContent(decoder) // => false - all data is read
|
|
830
|
-
* ```
|
|
831
|
-
*
|
|
832
|
-
* @module decoding
|
|
833
|
-
*/
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const errorUnexpectedEndOfArray$1 = create$3('Unexpected end of array');
|
|
837
|
-
const errorIntegerOutOfRange$1 = create$3('Integer out of Range');
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
|
|
841
|
-
*
|
|
842
|
-
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
843
|
-
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
844
|
-
*
|
|
845
|
-
* @function
|
|
846
|
-
* @param {Decoder} decoder The decoder instance
|
|
847
|
-
* @param {number} len The length of bytes to read
|
|
848
|
-
* @return {Uint8Array}
|
|
849
|
-
*/
|
|
850
|
-
const readUint8Array$1 = (decoder, len) => {
|
|
851
|
-
const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
|
|
852
|
-
decoder.pos += len;
|
|
853
|
-
return view
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Read variable length Uint8Array.
|
|
858
|
-
*
|
|
859
|
-
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
860
|
-
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
861
|
-
*
|
|
862
|
-
* @function
|
|
863
|
-
* @param {Decoder} decoder
|
|
864
|
-
* @return {Uint8Array}
|
|
865
|
-
*/
|
|
866
|
-
const readVarUint8Array$1 = decoder => readUint8Array$1(decoder, readVarUint$1(decoder));
|
|
867
|
-
|
|
868
|
-
/**
|
|
869
|
-
* Read one byte as unsigned integer.
|
|
870
|
-
* @function
|
|
871
|
-
* @param {Decoder} decoder The decoder instance
|
|
872
|
-
* @return {number} Unsigned 8-bit integer
|
|
873
|
-
*/
|
|
874
|
-
const readUint8$1 = decoder => decoder.arr[decoder.pos++];
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* Read unsigned integer (32bit) with variable length.
|
|
878
|
-
* 1/8th of the storage is used as encoding overhead.
|
|
879
|
-
* * numbers < 2^7 is stored in one bytlength
|
|
880
|
-
* * numbers < 2^14 is stored in two bylength
|
|
881
|
-
*
|
|
882
|
-
* @function
|
|
883
|
-
* @param {Decoder} decoder
|
|
884
|
-
* @return {number} An unsigned integer.length
|
|
885
|
-
*/
|
|
886
|
-
const readVarUint$1 = decoder => {
|
|
887
|
-
let num = 0;
|
|
888
|
-
let mult = 1;
|
|
889
|
-
const len = decoder.arr.length;
|
|
890
|
-
while (decoder.pos < len) {
|
|
891
|
-
const r = decoder.arr[decoder.pos++];
|
|
892
|
-
// num = num | ((r & binary.BITS7) << len)
|
|
893
|
-
num = num + (r & BITS7$1) * mult; // shift $r << (7*#iterations) and add it to num
|
|
894
|
-
mult *= 128; // next iteration, shift 7 "more" to the left
|
|
895
|
-
if (r < BIT8$1) {
|
|
896
|
-
return num
|
|
897
|
-
}
|
|
898
|
-
/* c8 ignore start */
|
|
899
|
-
if (num > MAX_SAFE_INTEGER$1) {
|
|
900
|
-
throw errorIntegerOutOfRange$1
|
|
901
|
-
}
|
|
902
|
-
/* c8 ignore stop */
|
|
903
|
-
}
|
|
904
|
-
throw errorUnexpectedEndOfArray$1
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* We don't test this function anymore as we use native decoding/encoding by default now.
|
|
909
|
-
* Better not modify this anymore..
|
|
910
|
-
*
|
|
911
|
-
* Transforming utf8 to a string is pretty expensive. The code performs 10x better
|
|
912
|
-
* when String.fromCodePoint is fed with all characters as arguments.
|
|
913
|
-
* But most environments have a maximum number of arguments per functions.
|
|
914
|
-
* For effiency reasons we apply a maximum of 10000 characters at once.
|
|
915
|
-
*
|
|
916
|
-
* @function
|
|
917
|
-
* @param {Decoder} decoder
|
|
918
|
-
* @return {String} The read String.
|
|
919
|
-
*/
|
|
920
|
-
/* c8 ignore start */
|
|
921
|
-
const _readVarStringPolyfill$1 = decoder => {
|
|
922
|
-
let remainingLen = readVarUint$1(decoder);
|
|
923
|
-
if (remainingLen === 0) {
|
|
924
|
-
return ''
|
|
925
|
-
} else {
|
|
926
|
-
let encodedString = String.fromCodePoint(readUint8$1(decoder)); // remember to decrease remainingLen
|
|
927
|
-
if (--remainingLen < 100) { // do not create a Uint8Array for small strings
|
|
928
|
-
while (remainingLen--) {
|
|
929
|
-
encodedString += String.fromCodePoint(readUint8$1(decoder));
|
|
930
|
-
}
|
|
931
|
-
} else {
|
|
932
|
-
while (remainingLen > 0) {
|
|
933
|
-
const nextLen = remainingLen < 10000 ? remainingLen : 10000;
|
|
934
|
-
// this is dangerous, we create a fresh array view from the existing buffer
|
|
935
|
-
const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen);
|
|
936
|
-
decoder.pos += nextLen;
|
|
937
|
-
// Starting with ES5.1 we can supply a generic array-like object as arguments
|
|
938
|
-
encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes));
|
|
939
|
-
remainingLen -= nextLen;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
return decodeURIComponent(escape(encodedString))
|
|
943
|
-
}
|
|
944
|
-
};
|
|
945
|
-
/* c8 ignore stop */
|
|
946
|
-
|
|
947
|
-
/**
|
|
948
|
-
* @function
|
|
949
|
-
* @param {Decoder} decoder
|
|
950
|
-
* @return {String} The read String
|
|
951
|
-
*/
|
|
952
|
-
const _readVarStringNative$1 = decoder =>
|
|
953
|
-
/** @type any */ (utf8TextDecoder$1).decode(readVarUint8Array$1(decoder));
|
|
954
|
-
|
|
955
|
-
/**
|
|
956
|
-
* Read string of variable length
|
|
957
|
-
* * varUint is used to store the length of the string
|
|
958
|
-
*
|
|
959
|
-
* @function
|
|
960
|
-
* @param {Decoder} decoder
|
|
961
|
-
* @return {String} The read String
|
|
962
|
-
*
|
|
963
|
-
*/
|
|
964
|
-
/* c8 ignore next */
|
|
965
|
-
const readVarString$1 = utf8TextDecoder$1 ? _readVarStringNative$1 : _readVarStringPolyfill$1;
|
|
966
|
-
|
|
967
|
-
var AuthMessageType;
|
|
968
|
-
(function (AuthMessageType) {
|
|
969
|
-
AuthMessageType[AuthMessageType["Token"] = 0] = "Token";
|
|
970
|
-
AuthMessageType[AuthMessageType["PermissionDenied"] = 1] = "PermissionDenied";
|
|
971
|
-
AuthMessageType[AuthMessageType["Authenticated"] = 2] = "Authenticated";
|
|
972
|
-
})(AuthMessageType || (AuthMessageType = {}));
|
|
973
|
-
const writeAuthentication = (encoder, auth) => {
|
|
974
|
-
writeVarUint$1(encoder, AuthMessageType.Token);
|
|
975
|
-
writeVarString$1(encoder, auth);
|
|
976
|
-
};
|
|
977
|
-
const readAuthMessage = (decoder, sendToken, permissionDeniedHandler, authenticatedHandler) => {
|
|
978
|
-
switch (readVarUint$1(decoder)) {
|
|
979
|
-
case AuthMessageType.Token: {
|
|
980
|
-
sendToken();
|
|
981
|
-
break;
|
|
982
|
-
}
|
|
983
|
-
case AuthMessageType.PermissionDenied: {
|
|
984
|
-
permissionDeniedHandler(readVarString$1(decoder));
|
|
985
|
-
break;
|
|
986
|
-
}
|
|
987
|
-
case AuthMessageType.Authenticated: {
|
|
988
|
-
authenticatedHandler(readVarString$1(decoder));
|
|
989
|
-
break;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
const awarenessStatesToArray = (states) => {
|
|
995
|
-
return Array.from(states.entries()).map(([key, value]) => {
|
|
996
|
-
return {
|
|
997
|
-
clientId: key,
|
|
998
|
-
...value,
|
|
999
|
-
};
|
|
1000
|
-
});
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
/**
|
|
1004
|
-
* State of the WebSocket connection.
|
|
1005
|
-
* https://developer.mozilla.org/de/docs/Web/API/WebSocket/readyState
|
|
1006
|
-
*/
|
|
1007
|
-
var WsReadyStates;
|
|
1008
|
-
(function (WsReadyStates) {
|
|
1009
|
-
WsReadyStates[WsReadyStates["Connecting"] = 0] = "Connecting";
|
|
1010
|
-
WsReadyStates[WsReadyStates["Open"] = 1] = "Open";
|
|
1011
|
-
WsReadyStates[WsReadyStates["Closing"] = 2] = "Closing";
|
|
1012
|
-
WsReadyStates[WsReadyStates["Closed"] = 3] = "Closed";
|
|
1013
|
-
})(WsReadyStates || (WsReadyStates = {}));
|
|
1014
|
-
|
|
1015
|
-
function applyDefaults(options) {
|
|
1016
|
-
if (!options) {
|
|
1017
|
-
options = {};
|
|
1018
|
-
}
|
|
1019
|
-
return {
|
|
1020
|
-
delay: (options.delay === undefined) ? 200 : options.delay,
|
|
1021
|
-
initialDelay: (options.initialDelay === undefined) ? 0 : options.initialDelay,
|
|
1022
|
-
minDelay: (options.minDelay === undefined) ? 0 : options.minDelay,
|
|
1023
|
-
maxDelay: (options.maxDelay === undefined) ? 0 : options.maxDelay,
|
|
1024
|
-
factor: (options.factor === undefined) ? 0 : options.factor,
|
|
1025
|
-
maxAttempts: (options.maxAttempts === undefined) ? 3 : options.maxAttempts,
|
|
1026
|
-
timeout: (options.timeout === undefined) ? 0 : options.timeout,
|
|
1027
|
-
jitter: (options.jitter === true),
|
|
1028
|
-
initialJitter: (options.initialJitter === true),
|
|
1029
|
-
handleError: (options.handleError === undefined) ? null : options.handleError,
|
|
1030
|
-
handleTimeout: (options.handleTimeout === undefined) ? null : options.handleTimeout,
|
|
1031
|
-
beforeAttempt: (options.beforeAttempt === undefined) ? null : options.beforeAttempt,
|
|
1032
|
-
calculateDelay: (options.calculateDelay === undefined) ? null : options.calculateDelay
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
async function sleep(delay) {
|
|
1036
|
-
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
1037
|
-
}
|
|
1038
|
-
function defaultCalculateDelay(context, options) {
|
|
1039
|
-
let delay = options.delay;
|
|
1040
|
-
if (delay === 0) {
|
|
1041
|
-
// no delay between attempts
|
|
1042
|
-
return 0;
|
|
1043
|
-
}
|
|
1044
|
-
if (options.factor) {
|
|
1045
|
-
delay *= Math.pow(options.factor, context.attemptNum - 1);
|
|
1046
|
-
if (options.maxDelay !== 0) {
|
|
1047
|
-
delay = Math.min(delay, options.maxDelay);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (options.jitter) {
|
|
1051
|
-
// Jitter will result in a random value between `minDelay` and
|
|
1052
|
-
// calculated delay for a given attempt.
|
|
1053
|
-
// See https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
1054
|
-
// We're using the "full jitter" strategy.
|
|
1055
|
-
const min = Math.ceil(options.minDelay);
|
|
1056
|
-
const max = Math.floor(delay);
|
|
1057
|
-
delay = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
1058
|
-
}
|
|
1059
|
-
return Math.round(delay);
|
|
1060
|
-
}
|
|
1061
|
-
async function retry(attemptFunc, attemptOptions) {
|
|
1062
|
-
const options = applyDefaults(attemptOptions);
|
|
1063
|
-
for (const prop of [
|
|
1064
|
-
'delay',
|
|
1065
|
-
'initialDelay',
|
|
1066
|
-
'minDelay',
|
|
1067
|
-
'maxDelay',
|
|
1068
|
-
'maxAttempts',
|
|
1069
|
-
'timeout'
|
|
1070
|
-
]) {
|
|
1071
|
-
const value = options[prop];
|
|
1072
|
-
if (!Number.isInteger(value) || (value < 0)) {
|
|
1073
|
-
throw new Error(`Value for ${prop} must be an integer greater than or equal to 0`);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
if ((options.factor.constructor !== Number) || (options.factor < 0)) {
|
|
1077
|
-
throw new Error(`Value for factor must be a number greater than or equal to 0`);
|
|
1078
|
-
}
|
|
1079
|
-
if (options.delay < options.minDelay) {
|
|
1080
|
-
throw new Error(`delay cannot be less than minDelay (delay: ${options.delay}, minDelay: ${options.minDelay}`);
|
|
1081
|
-
}
|
|
1082
|
-
const context = {
|
|
1083
|
-
attemptNum: 0,
|
|
1084
|
-
attemptsRemaining: options.maxAttempts ? options.maxAttempts : -1,
|
|
1085
|
-
aborted: false,
|
|
1086
|
-
abort() {
|
|
1087
|
-
context.aborted = true;
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1090
|
-
const calculateDelay = options.calculateDelay || defaultCalculateDelay;
|
|
1091
|
-
async function makeAttempt() {
|
|
1092
|
-
if (options.beforeAttempt) {
|
|
1093
|
-
options.beforeAttempt(context, options);
|
|
1094
|
-
}
|
|
1095
|
-
if (context.aborted) {
|
|
1096
|
-
const err = new Error(`Attempt aborted`);
|
|
1097
|
-
err.code = 'ATTEMPT_ABORTED';
|
|
1098
|
-
throw err;
|
|
1099
|
-
}
|
|
1100
|
-
const onError = async (err) => {
|
|
1101
|
-
if (options.handleError) {
|
|
1102
|
-
await options.handleError(err, context, options);
|
|
1103
|
-
}
|
|
1104
|
-
if (context.aborted || (context.attemptsRemaining === 0)) {
|
|
1105
|
-
throw err;
|
|
1106
|
-
}
|
|
1107
|
-
// We are about to try again so increment attempt number
|
|
1108
|
-
context.attemptNum++;
|
|
1109
|
-
const delay = calculateDelay(context, options);
|
|
1110
|
-
if (delay) {
|
|
1111
|
-
await sleep(delay);
|
|
1112
|
-
}
|
|
1113
|
-
return makeAttempt();
|
|
1114
|
-
};
|
|
1115
|
-
if (context.attemptsRemaining > 0) {
|
|
1116
|
-
context.attemptsRemaining--;
|
|
1117
|
-
}
|
|
1118
|
-
if (options.timeout) {
|
|
1119
|
-
return new Promise((resolve, reject) => {
|
|
1120
|
-
const timer = setTimeout(() => {
|
|
1121
|
-
if (options.handleTimeout) {
|
|
1122
|
-
// If calling handleTimeout throws an error that is not wrapped in a promise
|
|
1123
|
-
// we want to catch the error and reject.
|
|
1124
|
-
try {
|
|
1125
|
-
resolve(options.handleTimeout(context, options));
|
|
1126
|
-
}
|
|
1127
|
-
catch (e) {
|
|
1128
|
-
reject(e);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
else {
|
|
1132
|
-
const err = new Error(`Retry timeout (attemptNum: ${context.attemptNum}, timeout: ${options.timeout})`);
|
|
1133
|
-
err.code = 'ATTEMPT_TIMEOUT';
|
|
1134
|
-
reject(err);
|
|
1135
|
-
}
|
|
1136
|
-
}, options.timeout);
|
|
1137
|
-
attemptFunc(context, options).then((result) => {
|
|
1138
|
-
clearTimeout(timer);
|
|
1139
|
-
resolve(result);
|
|
1140
|
-
}).catch((err) => {
|
|
1141
|
-
clearTimeout(timer);
|
|
1142
|
-
// Calling resolve with a Promise that rejects here will result
|
|
1143
|
-
// in an unhandled rejection. Calling `reject` with errors
|
|
1144
|
-
// does not result in an unhandled rejection
|
|
1145
|
-
onError(err).then(resolve).catch(reject);
|
|
1146
|
-
});
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
// No timeout provided so wait indefinitely for the returned promise
|
|
1151
|
-
// to be resolved.
|
|
1152
|
-
return attemptFunc(context, options).catch(onError);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
const initialDelay = options.calculateDelay
|
|
1156
|
-
? options.calculateDelay(context, options)
|
|
1157
|
-
: options.initialDelay;
|
|
1158
|
-
if (initialDelay) {
|
|
1159
|
-
await sleep(initialDelay);
|
|
1160
|
-
}
|
|
1161
|
-
if (context.attemptNum < 1 && options.initialJitter) {
|
|
1162
|
-
const delay = calculateDelay(context, options);
|
|
1163
|
-
if (delay) {
|
|
1164
|
-
await sleep(delay);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
return makeAttempt();
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
/**
|
|
1171
|
-
* Common Math expressions.
|
|
1172
|
-
*
|
|
1173
|
-
* @module math
|
|
1174
|
-
*/
|
|
1175
|
-
|
|
1176
|
-
const floor = Math.floor;
|
|
1177
|
-
|
|
1178
|
-
/**
|
|
1179
|
-
* @function
|
|
1180
|
-
* @param {number} a
|
|
1181
|
-
* @param {number} b
|
|
1182
|
-
* @return {number} The smaller element of a and b
|
|
1183
|
-
*/
|
|
1184
|
-
const min = (a, b) => a < b ? a : b;
|
|
1185
|
-
|
|
1186
|
-
/**
|
|
1187
|
-
* @function
|
|
1188
|
-
* @param {number} a
|
|
1189
|
-
* @param {number} b
|
|
1190
|
-
* @return {number} The bigger element of a and b
|
|
1191
|
-
*/
|
|
1192
|
-
const max = (a, b) => a > b ? a : b;
|
|
1193
|
-
|
|
1194
|
-
/* eslint-env browser */
|
|
1195
|
-
|
|
1196
|
-
const BIT7 = 64;
|
|
1197
|
-
const BIT8 = 128;
|
|
1198
|
-
const BITS6 = 63;
|
|
1199
|
-
const BITS7 = 127;
|
|
1200
|
-
|
|
1201
|
-
/**
|
|
1202
|
-
* Utility helpers for working with numbers.
|
|
1203
|
-
*
|
|
1204
|
-
* @module number
|
|
1205
|
-
*/
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
1209
|
-
|
|
1210
|
-
/**
|
|
1211
|
-
* Utility module to work with sets.
|
|
1212
|
-
*
|
|
1213
|
-
* @module set
|
|
1214
|
-
*/
|
|
1215
|
-
|
|
1216
|
-
const create$2 = () => new Set();
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* Utility module to work with Arrays.
|
|
1220
|
-
*
|
|
1221
|
-
* @module array
|
|
1222
|
-
*/
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
/**
|
|
1226
|
-
* Transforms something array-like to an actual Array.
|
|
1227
|
-
*
|
|
1228
|
-
* @function
|
|
1229
|
-
* @template T
|
|
1230
|
-
* @param {ArrayLike<T>|Iterable<T>} arraylike
|
|
1231
|
-
* @return {T}
|
|
1232
|
-
*/
|
|
1233
|
-
const from = Array.from;
|
|
1234
|
-
|
|
1235
|
-
/**
|
|
1236
|
-
* @param {string} str
|
|
1237
|
-
* @return {Uint8Array}
|
|
1238
|
-
*/
|
|
1239
|
-
const _encodeUtf8Polyfill = str => {
|
|
1240
|
-
const encodedString = unescape(encodeURIComponent(str));
|
|
1241
|
-
const len = encodedString.length;
|
|
1242
|
-
const buf = new Uint8Array(len);
|
|
1243
|
-
for (let i = 0; i < len; i++) {
|
|
1244
|
-
buf[i] = /** @type {number} */ (encodedString.codePointAt(i));
|
|
1245
|
-
}
|
|
1246
|
-
return buf
|
|
1247
|
-
};
|
|
1248
|
-
|
|
1249
|
-
/* c8 ignore next */
|
|
1250
|
-
const utf8TextEncoder = /** @type {TextEncoder} */ (typeof TextEncoder !== 'undefined' ? new TextEncoder() : null);
|
|
1251
|
-
|
|
1252
|
-
/**
|
|
1253
|
-
* @param {string} str
|
|
1254
|
-
* @return {Uint8Array}
|
|
1255
|
-
*/
|
|
1256
|
-
const _encodeUtf8Native = str => utf8TextEncoder.encode(str);
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* @param {string} str
|
|
1260
|
-
* @return {Uint8Array}
|
|
1261
|
-
*/
|
|
1262
|
-
/* c8 ignore next */
|
|
1263
|
-
const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill;
|
|
1264
|
-
|
|
1265
|
-
/* c8 ignore next */
|
|
1266
|
-
let utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
1267
|
-
|
|
1268
|
-
/* c8 ignore start */
|
|
1269
|
-
if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) {
|
|
1270
|
-
// Safari doesn't handle BOM correctly.
|
|
1271
|
-
// This fixes a bug in Safari 13.0.5 where it produces a BOM the first time it is called.
|
|
1272
|
-
// utf8TextDecoder.decode(new Uint8Array()).length === 1 on the first call and
|
|
1273
|
-
// utf8TextDecoder.decode(new Uint8Array()).length === 1 on the second call
|
|
1274
|
-
// Another issue is that from then on no BOM chars are recognized anymore
|
|
1275
|
-
/* c8 ignore next */
|
|
1276
|
-
utf8TextDecoder = null;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
/**
|
|
1280
|
-
* Efficient schema-less binary encoding with support for variable length encoding.
|
|
1281
|
-
*
|
|
1282
|
-
* Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
|
|
1283
|
-
*
|
|
1284
|
-
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
1285
|
-
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
1286
|
-
* which is also used in Protocol Buffers.
|
|
1287
|
-
*
|
|
1288
|
-
* ```js
|
|
1289
|
-
* // encoding step
|
|
1290
|
-
* const encoder = encoding.createEncoder()
|
|
1291
|
-
* encoding.writeVarUint(encoder, 256)
|
|
1292
|
-
* encoding.writeVarString(encoder, 'Hello world!')
|
|
1293
|
-
* const buf = encoding.toUint8Array(encoder)
|
|
1294
|
-
* ```
|
|
1295
|
-
*
|
|
1296
|
-
* ```js
|
|
1297
|
-
* // decoding step
|
|
1298
|
-
* const decoder = decoding.createDecoder(buf)
|
|
1299
|
-
* decoding.readVarUint(decoder) // => 256
|
|
1300
|
-
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
1301
|
-
* decoding.hasContent(decoder) // => false - all data is read
|
|
1302
|
-
* ```
|
|
1303
|
-
*
|
|
1304
|
-
* @module encoding
|
|
1305
|
-
*/
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
/**
|
|
1309
|
-
* A BinaryEncoder handles the encoding to an Uint8Array.
|
|
1310
|
-
*/
|
|
1311
|
-
class Encoder {
|
|
1312
|
-
constructor () {
|
|
1313
|
-
this.cpos = 0;
|
|
1314
|
-
this.cbuf = new Uint8Array(100);
|
|
1315
|
-
/**
|
|
1316
|
-
* @type {Array<Uint8Array>}
|
|
1317
|
-
*/
|
|
1318
|
-
this.bufs = [];
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
/**
|
|
1323
|
-
* @function
|
|
1324
|
-
* @return {Encoder}
|
|
1325
|
-
*/
|
|
1326
|
-
const createEncoder = () => new Encoder();
|
|
1327
|
-
|
|
1328
|
-
/**
|
|
1329
|
-
* The current length of the encoded data.
|
|
1330
|
-
*
|
|
1331
|
-
* @function
|
|
1332
|
-
* @param {Encoder} encoder
|
|
1333
|
-
* @return {number}
|
|
1334
|
-
*/
|
|
1335
|
-
const length$1 = encoder => {
|
|
1336
|
-
let len = encoder.cpos;
|
|
1337
|
-
for (let i = 0; i < encoder.bufs.length; i++) {
|
|
1338
|
-
len += encoder.bufs[i].length;
|
|
1339
|
-
}
|
|
1340
|
-
return len
|
|
1341
|
-
};
|
|
1342
|
-
|
|
1343
|
-
/**
|
|
1344
|
-
* Transform to Uint8Array.
|
|
1345
|
-
*
|
|
1346
|
-
* @function
|
|
1347
|
-
* @param {Encoder} encoder
|
|
1348
|
-
* @return {Uint8Array} The created ArrayBuffer.
|
|
1349
|
-
*/
|
|
1350
|
-
const toUint8Array = encoder => {
|
|
1351
|
-
const uint8arr = new Uint8Array(length$1(encoder));
|
|
1352
|
-
let curPos = 0;
|
|
1353
|
-
for (let i = 0; i < encoder.bufs.length; i++) {
|
|
1354
|
-
const d = encoder.bufs[i];
|
|
1355
|
-
uint8arr.set(d, curPos);
|
|
1356
|
-
curPos += d.length;
|
|
1357
|
-
}
|
|
1358
|
-
uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
|
|
1359
|
-
return uint8arr
|
|
1360
|
-
};
|
|
1361
|
-
|
|
1362
|
-
/**
|
|
1363
|
-
* Write one byte to the encoder.
|
|
1364
|
-
*
|
|
1365
|
-
* @function
|
|
1366
|
-
* @param {Encoder} encoder
|
|
1367
|
-
* @param {number} num The byte that is to be encoded.
|
|
1368
|
-
*/
|
|
1369
|
-
const write = (encoder, num) => {
|
|
1370
|
-
const bufferLen = encoder.cbuf.length;
|
|
1371
|
-
if (encoder.cpos === bufferLen) {
|
|
1372
|
-
encoder.bufs.push(encoder.cbuf);
|
|
1373
|
-
encoder.cbuf = new Uint8Array(bufferLen * 2);
|
|
1374
|
-
encoder.cpos = 0;
|
|
1375
|
-
}
|
|
1376
|
-
encoder.cbuf[encoder.cpos++] = num;
|
|
1377
|
-
};
|
|
1378
|
-
|
|
1379
|
-
/**
|
|
1380
|
-
* Write a variable length unsigned integer. Max encodable integer is 2^53.
|
|
1381
|
-
*
|
|
1382
|
-
* @function
|
|
1383
|
-
* @param {Encoder} encoder
|
|
1384
|
-
* @param {number} num The number that is to be encoded.
|
|
1385
|
-
*/
|
|
1386
|
-
const writeVarUint = (encoder, num) => {
|
|
1387
|
-
while (num > BITS7) {
|
|
1388
|
-
write(encoder, BIT8 | (BITS7 & num));
|
|
1389
|
-
num = floor(num / 128); // shift >>> 7
|
|
1390
|
-
}
|
|
1391
|
-
write(encoder, BITS7 & num);
|
|
1392
|
-
};
|
|
1393
|
-
|
|
1394
|
-
/**
|
|
1395
|
-
* A cache to store strings temporarily
|
|
1396
|
-
*/
|
|
1397
|
-
const _strBuffer = new Uint8Array(30000);
|
|
1398
|
-
const _maxStrBSize = _strBuffer.length / 3;
|
|
1399
|
-
|
|
1400
|
-
/**
|
|
1401
|
-
* Write a variable length string.
|
|
1402
|
-
*
|
|
1403
|
-
* @function
|
|
1404
|
-
* @param {Encoder} encoder
|
|
1405
|
-
* @param {String} str The string that is to be encoded.
|
|
1406
|
-
*/
|
|
1407
|
-
const _writeVarStringNative = (encoder, str) => {
|
|
1408
|
-
if (str.length < _maxStrBSize) {
|
|
1409
|
-
// We can encode the string into the existing buffer
|
|
1410
|
-
/* c8 ignore next */
|
|
1411
|
-
const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
|
|
1412
|
-
writeVarUint(encoder, written);
|
|
1413
|
-
for (let i = 0; i < written; i++) {
|
|
1414
|
-
write(encoder, _strBuffer[i]);
|
|
1415
|
-
}
|
|
1416
|
-
} else {
|
|
1417
|
-
writeVarUint8Array(encoder, encodeUtf8(str));
|
|
1418
|
-
}
|
|
1419
|
-
};
|
|
1420
|
-
|
|
1421
|
-
/**
|
|
1422
|
-
* Write a variable length string.
|
|
1423
|
-
*
|
|
1424
|
-
* @function
|
|
1425
|
-
* @param {Encoder} encoder
|
|
1426
|
-
* @param {String} str The string that is to be encoded.
|
|
1427
|
-
*/
|
|
1428
|
-
const _writeVarStringPolyfill = (encoder, str) => {
|
|
1429
|
-
const encodedString = unescape(encodeURIComponent(str));
|
|
1430
|
-
const len = encodedString.length;
|
|
1431
|
-
writeVarUint(encoder, len);
|
|
1432
|
-
for (let i = 0; i < len; i++) {
|
|
1433
|
-
write(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
|
|
1434
|
-
}
|
|
1435
|
-
};
|
|
1436
|
-
|
|
1437
|
-
/**
|
|
1438
|
-
* Write a variable length string.
|
|
1439
|
-
*
|
|
1440
|
-
* @function
|
|
1441
|
-
* @param {Encoder} encoder
|
|
1442
|
-
* @param {String} str The string that is to be encoded.
|
|
1443
|
-
*/
|
|
1444
|
-
/* c8 ignore next */
|
|
1445
|
-
const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill;
|
|
1446
|
-
|
|
1447
|
-
/**
|
|
1448
|
-
* Append fixed-length Uint8Array to the encoder.
|
|
1449
|
-
*
|
|
1450
|
-
* @function
|
|
1451
|
-
* @param {Encoder} encoder
|
|
1452
|
-
* @param {Uint8Array} uint8Array
|
|
1453
|
-
*/
|
|
1454
|
-
const writeUint8Array = (encoder, uint8Array) => {
|
|
1455
|
-
const bufferLen = encoder.cbuf.length;
|
|
1456
|
-
const cpos = encoder.cpos;
|
|
1457
|
-
const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
|
|
1458
|
-
const rightCopyLen = uint8Array.length - leftCopyLen;
|
|
1459
|
-
encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
|
|
1460
|
-
encoder.cpos += leftCopyLen;
|
|
1461
|
-
if (rightCopyLen > 0) {
|
|
1462
|
-
// Still something to write, write right half..
|
|
1463
|
-
// Append new buffer
|
|
1464
|
-
encoder.bufs.push(encoder.cbuf);
|
|
1465
|
-
// must have at least size of remaining buffer
|
|
1466
|
-
encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
|
|
1467
|
-
// copy array
|
|
1468
|
-
encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
|
|
1469
|
-
encoder.cpos = rightCopyLen;
|
|
1470
|
-
}
|
|
1471
|
-
};
|
|
1472
|
-
|
|
1473
|
-
/**
|
|
1474
|
-
* Append an Uint8Array to Encoder.
|
|
1475
|
-
*
|
|
1476
|
-
* @function
|
|
1477
|
-
* @param {Encoder} encoder
|
|
1478
|
-
* @param {Uint8Array} uint8Array
|
|
1479
|
-
*/
|
|
1480
|
-
const writeVarUint8Array = (encoder, uint8Array) => {
|
|
1481
|
-
writeVarUint(encoder, uint8Array.byteLength);
|
|
1482
|
-
writeUint8Array(encoder, uint8Array);
|
|
1483
|
-
};
|
|
1484
|
-
|
|
1485
|
-
/**
|
|
1486
|
-
* Error helpers.
|
|
1487
|
-
*
|
|
1488
|
-
* @module error
|
|
1489
|
-
*/
|
|
1490
|
-
|
|
1491
|
-
/**
|
|
1492
|
-
* @param {string} s
|
|
1493
|
-
* @return {Error}
|
|
1494
|
-
*/
|
|
1495
|
-
/* c8 ignore next */
|
|
1496
|
-
const create$1 = s => new Error(s);
|
|
1497
|
-
|
|
1498
|
-
/**
|
|
1499
|
-
* Efficient schema-less binary decoding with support for variable length encoding.
|
|
1500
|
-
*
|
|
1501
|
-
* Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
|
|
1502
|
-
*
|
|
1503
|
-
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
1504
|
-
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
1505
|
-
* which is also used in Protocol Buffers.
|
|
1506
|
-
*
|
|
1507
|
-
* ```js
|
|
1508
|
-
* // encoding step
|
|
1509
|
-
* const encoder = encoding.createEncoder()
|
|
1510
|
-
* encoding.writeVarUint(encoder, 256)
|
|
1511
|
-
* encoding.writeVarString(encoder, 'Hello world!')
|
|
1512
|
-
* const buf = encoding.toUint8Array(encoder)
|
|
1513
|
-
* ```
|
|
1514
|
-
*
|
|
1515
|
-
* ```js
|
|
1516
|
-
* // decoding step
|
|
1517
|
-
* const decoder = decoding.createDecoder(buf)
|
|
1518
|
-
* decoding.readVarUint(decoder) // => 256
|
|
1519
|
-
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
1520
|
-
* decoding.hasContent(decoder) // => false - all data is read
|
|
1521
|
-
* ```
|
|
1522
|
-
*
|
|
1523
|
-
* @module decoding
|
|
1524
|
-
*/
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
const errorUnexpectedEndOfArray = create$1('Unexpected end of array');
|
|
1528
|
-
const errorIntegerOutOfRange = create$1('Integer out of Range');
|
|
1529
|
-
|
|
1530
|
-
/**
|
|
1531
|
-
* A Decoder handles the decoding of an Uint8Array.
|
|
1532
|
-
*/
|
|
1533
|
-
class Decoder {
|
|
1534
|
-
/**
|
|
1535
|
-
* @param {Uint8Array} uint8Array Binary data to decode
|
|
1536
|
-
*/
|
|
1537
|
-
constructor (uint8Array) {
|
|
1538
|
-
/**
|
|
1539
|
-
* Decoding target.
|
|
1540
|
-
*
|
|
1541
|
-
* @type {Uint8Array}
|
|
1542
|
-
*/
|
|
1543
|
-
this.arr = uint8Array;
|
|
1544
|
-
/**
|
|
1545
|
-
* Current decoding position.
|
|
1546
|
-
*
|
|
1547
|
-
* @type {number}
|
|
1548
|
-
*/
|
|
1549
|
-
this.pos = 0;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
/**
|
|
1554
|
-
* @function
|
|
1555
|
-
* @param {Uint8Array} uint8Array
|
|
1556
|
-
* @return {Decoder}
|
|
1557
|
-
*/
|
|
1558
|
-
const createDecoder = uint8Array => new Decoder(uint8Array);
|
|
1559
|
-
|
|
1560
|
-
/**
|
|
1561
|
-
* Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
|
|
1562
|
-
*
|
|
1563
|
-
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
1564
|
-
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
1565
|
-
*
|
|
1566
|
-
* @function
|
|
1567
|
-
* @param {Decoder} decoder The decoder instance
|
|
1568
|
-
* @param {number} len The length of bytes to read
|
|
1569
|
-
* @return {Uint8Array}
|
|
1570
|
-
*/
|
|
1571
|
-
const readUint8Array = (decoder, len) => {
|
|
1572
|
-
const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
|
|
1573
|
-
decoder.pos += len;
|
|
1574
|
-
return view
|
|
1575
|
-
};
|
|
1576
|
-
|
|
1577
|
-
/**
|
|
1578
|
-
* Read variable length Uint8Array.
|
|
1579
|
-
*
|
|
1580
|
-
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
1581
|
-
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
1582
|
-
*
|
|
1583
|
-
* @function
|
|
1584
|
-
* @param {Decoder} decoder
|
|
1585
|
-
* @return {Uint8Array}
|
|
1586
|
-
*/
|
|
1587
|
-
const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder));
|
|
1588
|
-
|
|
1589
|
-
/**
|
|
1590
|
-
* Read one byte as unsigned integer.
|
|
1591
|
-
* @function
|
|
1592
|
-
* @param {Decoder} decoder The decoder instance
|
|
1593
|
-
* @return {number} Unsigned 8-bit integer
|
|
1594
|
-
*/
|
|
1595
|
-
const readUint8 = decoder => decoder.arr[decoder.pos++];
|
|
1596
|
-
|
|
1597
|
-
/**
|
|
1598
|
-
* Read unsigned integer (32bit) with variable length.
|
|
1599
|
-
* 1/8th of the storage is used as encoding overhead.
|
|
1600
|
-
* * numbers < 2^7 is stored in one bytlength
|
|
1601
|
-
* * numbers < 2^14 is stored in two bylength
|
|
1602
|
-
*
|
|
1603
|
-
* @function
|
|
1604
|
-
* @param {Decoder} decoder
|
|
1605
|
-
* @return {number} An unsigned integer.length
|
|
1606
|
-
*/
|
|
1607
|
-
const readVarUint = decoder => {
|
|
1608
|
-
let num = 0;
|
|
1609
|
-
let mult = 1;
|
|
1610
|
-
const len = decoder.arr.length;
|
|
1611
|
-
while (decoder.pos < len) {
|
|
1612
|
-
const r = decoder.arr[decoder.pos++];
|
|
1613
|
-
// num = num | ((r & binary.BITS7) << len)
|
|
1614
|
-
num = num + (r & BITS7) * mult; // shift $r << (7*#iterations) and add it to num
|
|
1615
|
-
mult *= 128; // next iteration, shift 7 "more" to the left
|
|
1616
|
-
if (r < BIT8) {
|
|
1617
|
-
return num
|
|
1618
|
-
}
|
|
1619
|
-
/* c8 ignore start */
|
|
1620
|
-
if (num > MAX_SAFE_INTEGER) {
|
|
1621
|
-
throw errorIntegerOutOfRange
|
|
1622
|
-
}
|
|
1623
|
-
/* c8 ignore stop */
|
|
1624
|
-
}
|
|
1625
|
-
throw errorUnexpectedEndOfArray
|
|
1626
|
-
};
|
|
1627
|
-
|
|
1628
|
-
/**
|
|
1629
|
-
* Read signed integer (32bit) with variable length.
|
|
1630
|
-
* 1/8th of the storage is used as encoding overhead.
|
|
1631
|
-
* * numbers < 2^7 is stored in one bytlength
|
|
1632
|
-
* * numbers < 2^14 is stored in two bylength
|
|
1633
|
-
* @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change.
|
|
1634
|
-
*
|
|
1635
|
-
* @function
|
|
1636
|
-
* @param {Decoder} decoder
|
|
1637
|
-
* @return {number} An unsigned integer.length
|
|
1638
|
-
*/
|
|
1639
|
-
const readVarInt = decoder => {
|
|
1640
|
-
let r = decoder.arr[decoder.pos++];
|
|
1641
|
-
let num = r & BITS6;
|
|
1642
|
-
let mult = 64;
|
|
1643
|
-
const sign = (r & BIT7) > 0 ? -1 : 1;
|
|
1644
|
-
if ((r & BIT8) === 0) {
|
|
1645
|
-
// don't continue reading
|
|
1646
|
-
return sign * num
|
|
1647
|
-
}
|
|
1648
|
-
const len = decoder.arr.length;
|
|
1649
|
-
while (decoder.pos < len) {
|
|
1650
|
-
r = decoder.arr[decoder.pos++];
|
|
1651
|
-
// num = num | ((r & binary.BITS7) << len)
|
|
1652
|
-
num = num + (r & BITS7) * mult;
|
|
1653
|
-
mult *= 128;
|
|
1654
|
-
if (r < BIT8) {
|
|
1655
|
-
return sign * num
|
|
1656
|
-
}
|
|
1657
|
-
/* c8 ignore start */
|
|
1658
|
-
if (num > MAX_SAFE_INTEGER) {
|
|
1659
|
-
throw errorIntegerOutOfRange
|
|
1660
|
-
}
|
|
1661
|
-
/* c8 ignore stop */
|
|
1662
|
-
}
|
|
1663
|
-
throw errorUnexpectedEndOfArray
|
|
1664
|
-
};
|
|
1665
|
-
|
|
1666
|
-
/**
|
|
1667
|
-
* We don't test this function anymore as we use native decoding/encoding by default now.
|
|
1668
|
-
* Better not modify this anymore..
|
|
1669
|
-
*
|
|
1670
|
-
* Transforming utf8 to a string is pretty expensive. The code performs 10x better
|
|
1671
|
-
* when String.fromCodePoint is fed with all characters as arguments.
|
|
1672
|
-
* But most environments have a maximum number of arguments per functions.
|
|
1673
|
-
* For effiency reasons we apply a maximum of 10000 characters at once.
|
|
1674
|
-
*
|
|
1675
|
-
* @function
|
|
1676
|
-
* @param {Decoder} decoder
|
|
1677
|
-
* @return {String} The read String.
|
|
1678
|
-
*/
|
|
1679
|
-
/* c8 ignore start */
|
|
1680
|
-
const _readVarStringPolyfill = decoder => {
|
|
1681
|
-
let remainingLen = readVarUint(decoder);
|
|
1682
|
-
if (remainingLen === 0) {
|
|
1683
|
-
return ''
|
|
1684
|
-
} else {
|
|
1685
|
-
let encodedString = String.fromCodePoint(readUint8(decoder)); // remember to decrease remainingLen
|
|
1686
|
-
if (--remainingLen < 100) { // do not create a Uint8Array for small strings
|
|
1687
|
-
while (remainingLen--) {
|
|
1688
|
-
encodedString += String.fromCodePoint(readUint8(decoder));
|
|
1689
|
-
}
|
|
1690
|
-
} else {
|
|
1691
|
-
while (remainingLen > 0) {
|
|
1692
|
-
const nextLen = remainingLen < 10000 ? remainingLen : 10000;
|
|
1693
|
-
// this is dangerous, we create a fresh array view from the existing buffer
|
|
1694
|
-
const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen);
|
|
1695
|
-
decoder.pos += nextLen;
|
|
1696
|
-
// Starting with ES5.1 we can supply a generic array-like object as arguments
|
|
1697
|
-
encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes));
|
|
1698
|
-
remainingLen -= nextLen;
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
return decodeURIComponent(escape(encodedString))
|
|
1702
|
-
}
|
|
1703
|
-
};
|
|
1704
|
-
/* c8 ignore stop */
|
|
1705
|
-
|
|
1706
|
-
/**
|
|
1707
|
-
* @function
|
|
1708
|
-
* @param {Decoder} decoder
|
|
1709
|
-
* @return {String} The read String
|
|
1710
|
-
*/
|
|
1711
|
-
const _readVarStringNative = decoder =>
|
|
1712
|
-
/** @type any */ (utf8TextDecoder).decode(readVarUint8Array(decoder));
|
|
1713
|
-
|
|
1714
|
-
/**
|
|
1715
|
-
* Read string of variable length
|
|
1716
|
-
* * varUint is used to store the length of the string
|
|
1717
|
-
*
|
|
1718
|
-
* @function
|
|
1719
|
-
* @param {Decoder} decoder
|
|
1720
|
-
* @return {String} The read String
|
|
1721
|
-
*
|
|
1722
|
-
*/
|
|
1723
|
-
/* c8 ignore next */
|
|
1724
|
-
const readVarString = utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill;
|
|
1725
|
-
|
|
1726
|
-
/**
|
|
1727
|
-
* Look ahead and read varString without incrementing position
|
|
1728
|
-
*
|
|
1729
|
-
* @function
|
|
1730
|
-
* @param {Decoder} decoder
|
|
1731
|
-
* @return {string}
|
|
1732
|
-
*/
|
|
1733
|
-
const peekVarString = decoder => {
|
|
1734
|
-
const pos = decoder.pos;
|
|
1735
|
-
const s = readVarString(decoder);
|
|
1736
|
-
decoder.pos = pos;
|
|
1737
|
-
return s
|
|
1738
|
-
};
|
|
1739
|
-
|
|
1740
|
-
/**
|
|
1741
|
-
* Utility module to work with time.
|
|
1742
|
-
*
|
|
1743
|
-
* @module time
|
|
1744
|
-
*/
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
/**
|
|
1748
|
-
* Return current unix time.
|
|
1749
|
-
*
|
|
1750
|
-
* @return {number}
|
|
1751
|
-
*/
|
|
1752
|
-
const getUnixTime = Date.now;
|
|
1753
|
-
|
|
1754
|
-
/**
|
|
1755
|
-
* Utility module to work with key-value stores.
|
|
1756
|
-
*
|
|
1757
|
-
* @module map
|
|
1758
|
-
*/
|
|
1759
|
-
|
|
1760
|
-
/**
|
|
1761
|
-
* Creates a new Map instance.
|
|
1762
|
-
*
|
|
1763
|
-
* @function
|
|
1764
|
-
* @return {Map<any, any>}
|
|
1765
|
-
*
|
|
1766
|
-
* @function
|
|
1767
|
-
*/
|
|
1768
|
-
const create = () => new Map();
|
|
1769
|
-
|
|
1770
|
-
/**
|
|
1771
|
-
* Get map property. Create T if property is undefined and set T on map.
|
|
1772
|
-
*
|
|
1773
|
-
* ```js
|
|
1774
|
-
* const listeners = map.setIfUndefined(events, 'eventName', set.create)
|
|
1775
|
-
* listeners.add(listener)
|
|
1776
|
-
* ```
|
|
1777
|
-
*
|
|
1778
|
-
* @function
|
|
1779
|
-
* @template {Map<any, any>} MAP
|
|
1780
|
-
* @template {MAP extends Map<any,infer V> ? function():V : unknown} CF
|
|
1781
|
-
* @param {MAP} map
|
|
1782
|
-
* @param {MAP extends Map<infer K,any> ? K : unknown} key
|
|
1783
|
-
* @param {CF} createT
|
|
1784
|
-
* @return {ReturnType<CF>}
|
|
1785
|
-
*/
|
|
1786
|
-
const setIfUndefined = (map, key, createT) => {
|
|
1787
|
-
let set = map.get(key);
|
|
1788
|
-
if (set === undefined) {
|
|
1789
|
-
map.set(key, set = createT());
|
|
1790
|
-
}
|
|
1791
|
-
return set
|
|
1792
|
-
};
|
|
1793
|
-
|
|
1794
|
-
/**
|
|
1795
|
-
* Observable class prototype.
|
|
1796
|
-
*
|
|
1797
|
-
* @module observable
|
|
1798
|
-
*/
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
/* c8 ignore start */
|
|
1802
|
-
/**
|
|
1803
|
-
* Handles named events.
|
|
1804
|
-
*
|
|
1805
|
-
* @deprecated
|
|
1806
|
-
* @template N
|
|
1807
|
-
*/
|
|
1808
|
-
class Observable {
|
|
1809
|
-
constructor () {
|
|
1810
|
-
/**
|
|
1811
|
-
* Some desc.
|
|
1812
|
-
* @type {Map<N, any>}
|
|
1813
|
-
*/
|
|
1814
|
-
this._observers = create();
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
/**
|
|
1818
|
-
* @param {N} name
|
|
1819
|
-
* @param {function} f
|
|
1820
|
-
*/
|
|
1821
|
-
on (name, f) {
|
|
1822
|
-
setIfUndefined(this._observers, name, create$2).add(f);
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
/**
|
|
1826
|
-
* @param {N} name
|
|
1827
|
-
* @param {function} f
|
|
1828
|
-
*/
|
|
1829
|
-
once (name, f) {
|
|
1830
|
-
/**
|
|
1831
|
-
* @param {...any} args
|
|
1832
|
-
*/
|
|
1833
|
-
const _f = (...args) => {
|
|
1834
|
-
this.off(name, _f);
|
|
1835
|
-
f(...args);
|
|
1836
|
-
};
|
|
1837
|
-
this.on(name, _f);
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
/**
|
|
1841
|
-
* @param {N} name
|
|
1842
|
-
* @param {function} f
|
|
1843
|
-
*/
|
|
1844
|
-
off (name, f) {
|
|
1845
|
-
const observers = this._observers.get(name);
|
|
1846
|
-
if (observers !== undefined) {
|
|
1847
|
-
observers.delete(f);
|
|
1848
|
-
if (observers.size === 0) {
|
|
1849
|
-
this._observers.delete(name);
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
/**
|
|
1855
|
-
* Emit a named event. All registered event listeners that listen to the
|
|
1856
|
-
* specified name will receive the event.
|
|
1857
|
-
*
|
|
1858
|
-
* @todo This should catch exceptions
|
|
1859
|
-
*
|
|
1860
|
-
* @param {N} name The event name.
|
|
1861
|
-
* @param {Array<any>} args The arguments that are applied to the event listener.
|
|
1862
|
-
*/
|
|
1863
|
-
emit (name, args) {
|
|
1864
|
-
// copy all listeners to an array first to make sure that no event is emitted to listeners that are subscribed while the event handler is called.
|
|
1865
|
-
return from((this._observers.get(name) || create()).values()).forEach(f => f(...args))
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
destroy () {
|
|
1869
|
-
this._observers = create();
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
/* c8 ignore end */
|
|
1873
|
-
|
|
1874
|
-
/**
|
|
1875
|
-
* Utility functions for working with EcmaScript objects.
|
|
1876
|
-
*
|
|
1877
|
-
* @module object
|
|
1878
|
-
*/
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
/**
|
|
1882
|
-
* @param {Object<string,any>} obj
|
|
1883
|
-
*/
|
|
1884
|
-
const keys = Object.keys;
|
|
1885
|
-
|
|
1886
|
-
/**
|
|
1887
|
-
* @deprecated use object.size instead
|
|
1888
|
-
* @param {Object<string,any>} obj
|
|
1889
|
-
* @return {number}
|
|
1890
|
-
*/
|
|
1891
|
-
const length = obj => keys(obj).length;
|
|
1892
|
-
|
|
1893
|
-
/**
|
|
1894
|
-
* Calls `Object.prototype.hasOwnProperty`.
|
|
1895
|
-
*
|
|
1896
|
-
* @param {any} obj
|
|
1897
|
-
* @param {string|symbol} key
|
|
1898
|
-
* @return {boolean}
|
|
1899
|
-
*/
|
|
1900
|
-
const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
1901
|
-
|
|
1902
|
-
/**
|
|
1903
|
-
* Common functions and function call helpers.
|
|
1904
|
-
*
|
|
1905
|
-
* @module function
|
|
1906
|
-
*/
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
/**
|
|
1910
|
-
* @template T
|
|
1911
|
-
*
|
|
1912
|
-
* @param {T} a
|
|
1913
|
-
* @param {T} b
|
|
1914
|
-
* @return {boolean}
|
|
1915
|
-
*/
|
|
1916
|
-
const equalityStrict = (a, b) => a === b;
|
|
1917
|
-
|
|
1918
|
-
/* c8 ignore start */
|
|
1919
|
-
|
|
1920
|
-
/**
|
|
1921
|
-
* @param {any} a
|
|
1922
|
-
* @param {any} b
|
|
1923
|
-
* @return {boolean}
|
|
1924
|
-
*/
|
|
1925
|
-
const equalityDeep = (a, b) => {
|
|
1926
|
-
if (a == null || b == null) {
|
|
1927
|
-
return equalityStrict(a, b)
|
|
1928
|
-
}
|
|
1929
|
-
if (a.constructor !== b.constructor) {
|
|
1930
|
-
return false
|
|
1931
|
-
}
|
|
1932
|
-
if (a === b) {
|
|
1933
|
-
return true
|
|
1934
|
-
}
|
|
1935
|
-
switch (a.constructor) {
|
|
1936
|
-
case ArrayBuffer:
|
|
1937
|
-
a = new Uint8Array(a);
|
|
1938
|
-
b = new Uint8Array(b);
|
|
1939
|
-
// eslint-disable-next-line no-fallthrough
|
|
1940
|
-
case Uint8Array: {
|
|
1941
|
-
if (a.byteLength !== b.byteLength) {
|
|
1942
|
-
return false
|
|
1943
|
-
}
|
|
1944
|
-
for (let i = 0; i < a.length; i++) {
|
|
1945
|
-
if (a[i] !== b[i]) {
|
|
1946
|
-
return false
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
break
|
|
1950
|
-
}
|
|
1951
|
-
case Set: {
|
|
1952
|
-
if (a.size !== b.size) {
|
|
1953
|
-
return false
|
|
1954
|
-
}
|
|
1955
|
-
for (const value of a) {
|
|
1956
|
-
if (!b.has(value)) {
|
|
1957
|
-
return false
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
break
|
|
1961
|
-
}
|
|
1962
|
-
case Map: {
|
|
1963
|
-
if (a.size !== b.size) {
|
|
1964
|
-
return false
|
|
1965
|
-
}
|
|
1966
|
-
for (const key of a.keys()) {
|
|
1967
|
-
if (!b.has(key) || !equalityDeep(a.get(key), b.get(key))) {
|
|
1968
|
-
return false
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
break
|
|
1972
|
-
}
|
|
1973
|
-
case Object:
|
|
1974
|
-
if (length(a) !== length(b)) {
|
|
1975
|
-
return false
|
|
1976
|
-
}
|
|
1977
|
-
for (const key in a) {
|
|
1978
|
-
if (!hasProperty(a, key) || !equalityDeep(a[key], b[key])) {
|
|
1979
|
-
return false
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
break
|
|
1983
|
-
case Array:
|
|
1984
|
-
if (a.length !== b.length) {
|
|
1985
|
-
return false
|
|
1986
|
-
}
|
|
1987
|
-
for (let i = 0; i < a.length; i++) {
|
|
1988
|
-
if (!equalityDeep(a[i], b[i])) {
|
|
1989
|
-
return false
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
break
|
|
1993
|
-
default:
|
|
1994
|
-
return false
|
|
1995
|
-
}
|
|
1996
|
-
return true
|
|
1997
|
-
};
|
|
1998
|
-
|
|
1999
|
-
/**
|
|
2000
|
-
* @module awareness-protocol
|
|
2001
|
-
*/
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
const outdatedTimeout = 30000;
|
|
2005
|
-
|
|
2006
|
-
/**
|
|
2007
|
-
* @typedef {Object} MetaClientState
|
|
2008
|
-
* @property {number} MetaClientState.clock
|
|
2009
|
-
* @property {number} MetaClientState.lastUpdated unix timestamp
|
|
2010
|
-
*/
|
|
2011
|
-
|
|
2012
|
-
/**
|
|
2013
|
-
* The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information
|
|
2014
|
-
* (cursor, username, status, ..). Each client can update its own local state and listen to state changes of
|
|
2015
|
-
* remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline.
|
|
2016
|
-
*
|
|
2017
|
-
* Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override
|
|
2018
|
-
* its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is
|
|
2019
|
-
* applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that
|
|
2020
|
-
* a remote client is offline, it may propagate a message with
|
|
2021
|
-
* `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a
|
|
2022
|
-
* message is received, and the known clock of that client equals the received clock, it will override the state with `null`.
|
|
2023
|
-
*
|
|
2024
|
-
* Before a client disconnects, it should propagate a `null` state with an updated clock.
|
|
2025
|
-
*
|
|
2026
|
-
* Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state.
|
|
2027
|
-
*
|
|
2028
|
-
* @extends {Observable<string>}
|
|
2029
|
-
*/
|
|
2030
|
-
class Awareness extends Observable {
|
|
2031
|
-
/**
|
|
2032
|
-
* @param {Y.Doc} doc
|
|
2033
|
-
*/
|
|
2034
|
-
constructor (doc) {
|
|
2035
|
-
super();
|
|
2036
|
-
this.doc = doc;
|
|
2037
|
-
/**
|
|
2038
|
-
* @type {number}
|
|
2039
|
-
*/
|
|
2040
|
-
this.clientID = doc.clientID;
|
|
2041
|
-
/**
|
|
2042
|
-
* Maps from client id to client state
|
|
2043
|
-
* @type {Map<number, Object<string, any>>}
|
|
2044
|
-
*/
|
|
2045
|
-
this.states = new Map();
|
|
2046
|
-
/**
|
|
2047
|
-
* @type {Map<number, MetaClientState>}
|
|
2048
|
-
*/
|
|
2049
|
-
this.meta = new Map();
|
|
2050
|
-
this._checkInterval = /** @type {any} */ (setInterval(() => {
|
|
2051
|
-
const now = getUnixTime();
|
|
2052
|
-
if (this.getLocalState() !== null && (outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */ (this.meta.get(this.clientID)).lastUpdated)) {
|
|
2053
|
-
// renew local clock
|
|
2054
|
-
this.setLocalState(this.getLocalState());
|
|
2055
|
-
}
|
|
2056
|
-
/**
|
|
2057
|
-
* @type {Array<number>}
|
|
2058
|
-
*/
|
|
2059
|
-
const remove = [];
|
|
2060
|
-
this.meta.forEach((meta, clientid) => {
|
|
2061
|
-
if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
|
|
2062
|
-
remove.push(clientid);
|
|
2063
|
-
}
|
|
2064
|
-
});
|
|
2065
|
-
if (remove.length > 0) {
|
|
2066
|
-
removeAwarenessStates(this, remove, 'timeout');
|
|
2067
|
-
}
|
|
2068
|
-
}, floor(outdatedTimeout / 10)));
|
|
2069
|
-
doc.on('destroy', () => {
|
|
2070
|
-
this.destroy();
|
|
2071
|
-
});
|
|
2072
|
-
this.setLocalState({});
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
destroy () {
|
|
2076
|
-
this.emit('destroy', [this]);
|
|
2077
|
-
this.setLocalState(null);
|
|
2078
|
-
super.destroy();
|
|
2079
|
-
clearInterval(this._checkInterval);
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
/**
|
|
2083
|
-
* @return {Object<string,any>|null}
|
|
2084
|
-
*/
|
|
2085
|
-
getLocalState () {
|
|
2086
|
-
return this.states.get(this.clientID) || null
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
/**
|
|
2090
|
-
* @param {Object<string,any>|null} state
|
|
2091
|
-
*/
|
|
2092
|
-
setLocalState (state) {
|
|
2093
|
-
const clientID = this.clientID;
|
|
2094
|
-
const currLocalMeta = this.meta.get(clientID);
|
|
2095
|
-
const clock = currLocalMeta === undefined ? 0 : currLocalMeta.clock + 1;
|
|
2096
|
-
const prevState = this.states.get(clientID);
|
|
2097
|
-
if (state === null) {
|
|
2098
|
-
this.states.delete(clientID);
|
|
2099
|
-
} else {
|
|
2100
|
-
this.states.set(clientID, state);
|
|
2101
|
-
}
|
|
2102
|
-
this.meta.set(clientID, {
|
|
2103
|
-
clock,
|
|
2104
|
-
lastUpdated: getUnixTime()
|
|
2105
|
-
});
|
|
2106
|
-
const added = [];
|
|
2107
|
-
const updated = [];
|
|
2108
|
-
const filteredUpdated = [];
|
|
2109
|
-
const removed = [];
|
|
2110
|
-
if (state === null) {
|
|
2111
|
-
removed.push(clientID);
|
|
2112
|
-
} else if (prevState == null) {
|
|
2113
|
-
if (state != null) {
|
|
2114
|
-
added.push(clientID);
|
|
2115
|
-
}
|
|
2116
|
-
} else {
|
|
2117
|
-
updated.push(clientID);
|
|
2118
|
-
if (!equalityDeep(prevState, state)) {
|
|
2119
|
-
filteredUpdated.push(clientID);
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
|
|
2123
|
-
this.emit('change', [{ added, updated: filteredUpdated, removed }, 'local']);
|
|
2124
|
-
}
|
|
2125
|
-
this.emit('update', [{ added, updated, removed }, 'local']);
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
/**
|
|
2129
|
-
* @param {string} field
|
|
2130
|
-
* @param {any} value
|
|
2131
|
-
*/
|
|
2132
|
-
setLocalStateField (field, value) {
|
|
2133
|
-
const state = this.getLocalState();
|
|
2134
|
-
if (state !== null) {
|
|
2135
|
-
this.setLocalState({
|
|
2136
|
-
...state,
|
|
2137
|
-
[field]: value
|
|
2138
|
-
});
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
/**
|
|
2143
|
-
* @return {Map<number,Object<string,any>>}
|
|
2144
|
-
*/
|
|
2145
|
-
getStates () {
|
|
2146
|
-
return this.states
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
/**
|
|
2151
|
-
* Mark (remote) clients as inactive and remove them from the list of active peers.
|
|
2152
|
-
* This change will be propagated to remote clients.
|
|
2153
|
-
*
|
|
2154
|
-
* @param {Awareness} awareness
|
|
2155
|
-
* @param {Array<number>} clients
|
|
2156
|
-
* @param {any} origin
|
|
2157
|
-
*/
|
|
2158
|
-
const removeAwarenessStates = (awareness, clients, origin) => {
|
|
2159
|
-
const removed = [];
|
|
2160
|
-
for (let i = 0; i < clients.length; i++) {
|
|
2161
|
-
const clientID = clients[i];
|
|
2162
|
-
if (awareness.states.has(clientID)) {
|
|
2163
|
-
awareness.states.delete(clientID);
|
|
2164
|
-
if (clientID === awareness.clientID) {
|
|
2165
|
-
const curMeta = /** @type {MetaClientState} */ (awareness.meta.get(clientID));
|
|
2166
|
-
awareness.meta.set(clientID, {
|
|
2167
|
-
clock: curMeta.clock + 1,
|
|
2168
|
-
lastUpdated: getUnixTime()
|
|
2169
|
-
});
|
|
2170
|
-
}
|
|
2171
|
-
removed.push(clientID);
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
if (removed.length > 0) {
|
|
2175
|
-
awareness.emit('change', [{ added: [], updated: [], removed }, origin]);
|
|
2176
|
-
awareness.emit('update', [{ added: [], updated: [], removed }, origin]);
|
|
2177
|
-
}
|
|
2178
|
-
};
|
|
2179
|
-
|
|
2180
|
-
/**
|
|
2181
|
-
* @param {Awareness} awareness
|
|
2182
|
-
* @param {Array<number>} clients
|
|
2183
|
-
* @return {Uint8Array}
|
|
2184
|
-
*/
|
|
2185
|
-
const encodeAwarenessUpdate = (awareness, clients, states = awareness.states) => {
|
|
2186
|
-
const len = clients.length;
|
|
2187
|
-
const encoder = createEncoder();
|
|
2188
|
-
writeVarUint(encoder, len);
|
|
2189
|
-
for (let i = 0; i < len; i++) {
|
|
2190
|
-
const clientID = clients[i];
|
|
2191
|
-
const state = states.get(clientID) || null;
|
|
2192
|
-
const clock = /** @type {MetaClientState} */ (awareness.meta.get(clientID)).clock;
|
|
2193
|
-
writeVarUint(encoder, clientID);
|
|
2194
|
-
writeVarUint(encoder, clock);
|
|
2195
|
-
writeVarString(encoder, JSON.stringify(state));
|
|
2196
|
-
}
|
|
2197
|
-
return toUint8Array(encoder)
|
|
2198
|
-
};
|
|
2199
|
-
|
|
2200
|
-
/**
|
|
2201
|
-
* @param {Awareness} awareness
|
|
2202
|
-
* @param {Uint8Array} update
|
|
2203
|
-
* @param {any} origin This will be added to the emitted change event
|
|
2204
|
-
*/
|
|
2205
|
-
const applyAwarenessUpdate = (awareness, update, origin) => {
|
|
2206
|
-
const decoder = createDecoder(update);
|
|
2207
|
-
const timestamp = getUnixTime();
|
|
2208
|
-
const added = [];
|
|
2209
|
-
const updated = [];
|
|
2210
|
-
const filteredUpdated = [];
|
|
2211
|
-
const removed = [];
|
|
2212
|
-
const len = readVarUint(decoder);
|
|
2213
|
-
for (let i = 0; i < len; i++) {
|
|
2214
|
-
const clientID = readVarUint(decoder);
|
|
2215
|
-
let clock = readVarUint(decoder);
|
|
2216
|
-
const state = JSON.parse(readVarString(decoder));
|
|
2217
|
-
const clientMeta = awareness.meta.get(clientID);
|
|
2218
|
-
const prevState = awareness.states.get(clientID);
|
|
2219
|
-
const currClock = clientMeta === undefined ? 0 : clientMeta.clock;
|
|
2220
|
-
if (currClock < clock || (currClock === clock && state === null && awareness.states.has(clientID))) {
|
|
2221
|
-
if (state === null) {
|
|
2222
|
-
// never let a remote client remove this local state
|
|
2223
|
-
if (clientID === awareness.clientID && awareness.getLocalState() != null) {
|
|
2224
|
-
// remote client removed the local state. Do not remote state. Broadcast a message indicating
|
|
2225
|
-
// that this client still exists by increasing the clock
|
|
2226
|
-
clock++;
|
|
2227
|
-
} else {
|
|
2228
|
-
awareness.states.delete(clientID);
|
|
2229
|
-
}
|
|
2230
|
-
} else {
|
|
2231
|
-
awareness.states.set(clientID, state);
|
|
2232
|
-
}
|
|
2233
|
-
awareness.meta.set(clientID, {
|
|
2234
|
-
clock,
|
|
2235
|
-
lastUpdated: timestamp
|
|
2236
|
-
});
|
|
2237
|
-
if (clientMeta === undefined && state !== null) {
|
|
2238
|
-
added.push(clientID);
|
|
2239
|
-
} else if (clientMeta !== undefined && state === null) {
|
|
2240
|
-
removed.push(clientID);
|
|
2241
|
-
} else if (state !== null) {
|
|
2242
|
-
if (!equalityDeep(state, prevState)) {
|
|
2243
|
-
filteredUpdated.push(clientID);
|
|
2244
|
-
}
|
|
2245
|
-
updated.push(clientID);
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
|
|
2250
|
-
awareness.emit('change', [{
|
|
2251
|
-
added, updated: filteredUpdated, removed
|
|
2252
|
-
}, origin]);
|
|
2253
|
-
}
|
|
2254
|
-
if (added.length > 0 || updated.length > 0 || removed.length > 0) {
|
|
2255
|
-
awareness.emit('update', [{
|
|
2256
|
-
added, updated, removed
|
|
2257
|
-
}, origin]);
|
|
2258
|
-
}
|
|
2259
|
-
};
|
|
2260
|
-
|
|
2261
|
-
class EventEmitter {
|
|
2262
|
-
constructor() {
|
|
2263
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
2264
|
-
this.callbacks = {};
|
|
2265
|
-
}
|
|
2266
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
2267
|
-
on(event, fn) {
|
|
2268
|
-
if (!this.callbacks[event]) {
|
|
2269
|
-
this.callbacks[event] = [];
|
|
2270
|
-
}
|
|
2271
|
-
this.callbacks[event].push(fn);
|
|
2272
|
-
return this;
|
|
2273
|
-
}
|
|
2274
|
-
emit(event, ...args) {
|
|
2275
|
-
const callbacks = this.callbacks[event];
|
|
2276
|
-
if (callbacks) {
|
|
2277
|
-
callbacks.forEach((callback) => callback.apply(this, args));
|
|
2278
|
-
}
|
|
2279
|
-
return this;
|
|
2280
|
-
}
|
|
2281
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
2282
|
-
off(event, fn) {
|
|
2283
|
-
const callbacks = this.callbacks[event];
|
|
2284
|
-
if (callbacks) {
|
|
2285
|
-
if (fn) {
|
|
2286
|
-
this.callbacks[event] = callbacks.filter((callback) => callback !== fn);
|
|
2287
|
-
}
|
|
2288
|
-
else {
|
|
2289
|
-
delete this.callbacks[event];
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
return this;
|
|
2293
|
-
}
|
|
2294
|
-
removeAllListeners() {
|
|
2295
|
-
this.callbacks = {};
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
class IncomingMessage {
|
|
2300
|
-
constructor(data) {
|
|
2301
|
-
this.data = data;
|
|
2302
|
-
this.encoder = createEncoder();
|
|
2303
|
-
this.decoder = createDecoder(new Uint8Array(this.data));
|
|
2304
|
-
}
|
|
2305
|
-
peekVarString() {
|
|
2306
|
-
return peekVarString(this.decoder);
|
|
2307
|
-
}
|
|
2308
|
-
readVarUint() {
|
|
2309
|
-
return readVarUint(this.decoder);
|
|
2310
|
-
}
|
|
2311
|
-
readVarString() {
|
|
2312
|
-
return readVarString(this.decoder);
|
|
2313
|
-
}
|
|
2314
|
-
readVarUint8Array() {
|
|
2315
|
-
return readVarUint8Array(this.decoder);
|
|
2316
|
-
}
|
|
2317
|
-
writeVarUint(type) {
|
|
2318
|
-
return writeVarUint(this.encoder, type);
|
|
2319
|
-
}
|
|
2320
|
-
writeVarString(string) {
|
|
2321
|
-
return writeVarString(this.encoder, string);
|
|
2322
|
-
}
|
|
2323
|
-
writeVarUint8Array(data) {
|
|
2324
|
-
return writeVarUint8Array(this.encoder, data);
|
|
2325
|
-
}
|
|
2326
|
-
length() {
|
|
2327
|
-
return length$1(this.encoder);
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
var MessageType;
|
|
2332
|
-
(function (MessageType) {
|
|
2333
|
-
MessageType[MessageType["Sync"] = 0] = "Sync";
|
|
2334
|
-
MessageType[MessageType["Awareness"] = 1] = "Awareness";
|
|
2335
|
-
MessageType[MessageType["Auth"] = 2] = "Auth";
|
|
2336
|
-
MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
|
|
2337
|
-
MessageType[MessageType["Stateless"] = 5] = "Stateless";
|
|
2338
|
-
MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
|
|
2339
|
-
MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus";
|
|
2340
|
-
})(MessageType || (MessageType = {}));
|
|
2341
|
-
var WebSocketStatus;
|
|
2342
|
-
(function (WebSocketStatus) {
|
|
2343
|
-
WebSocketStatus["Connecting"] = "connecting";
|
|
2344
|
-
WebSocketStatus["Connected"] = "connected";
|
|
2345
|
-
WebSocketStatus["Disconnected"] = "disconnected";
|
|
2346
|
-
})(WebSocketStatus || (WebSocketStatus = {}));
|
|
2347
|
-
|
|
2348
|
-
class OutgoingMessage {
|
|
2349
|
-
constructor() {
|
|
2350
|
-
this.encoder = createEncoder();
|
|
2351
|
-
}
|
|
2352
|
-
get(args) {
|
|
2353
|
-
return args.encoder;
|
|
2354
|
-
}
|
|
2355
|
-
toUint8Array() {
|
|
2356
|
-
return toUint8Array(this.encoder);
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
class CloseMessage extends OutgoingMessage {
|
|
2361
|
-
constructor() {
|
|
2362
|
-
super(...arguments);
|
|
2363
|
-
this.type = MessageType.CLOSE;
|
|
2364
|
-
this.description = "Ask the server to close the connection";
|
|
2365
|
-
}
|
|
2366
|
-
get(args) {
|
|
2367
|
-
writeVarString(this.encoder, args.documentName);
|
|
2368
|
-
writeVarUint(this.encoder, this.type);
|
|
2369
|
-
return this.encoder;
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
class HocuspocusProviderWebsocket extends EventEmitter {
|
|
2374
|
-
constructor(configuration) {
|
|
2375
|
-
super();
|
|
2376
|
-
this.messageQueue = [];
|
|
2377
|
-
this.configuration = {
|
|
2378
|
-
url: "",
|
|
2379
|
-
autoConnect: true,
|
|
2380
|
-
preserveTrailingSlash: false,
|
|
2381
|
-
// @ts-ignore
|
|
2382
|
-
document: undefined,
|
|
2383
|
-
WebSocketPolyfill: undefined,
|
|
2384
|
-
// TODO: this should depend on awareness.outdatedTime
|
|
2385
|
-
messageReconnectTimeout: 30000,
|
|
2386
|
-
// 1 second
|
|
2387
|
-
delay: 1000,
|
|
2388
|
-
// instant
|
|
2389
|
-
initialDelay: 0,
|
|
2390
|
-
// double the delay each time
|
|
2391
|
-
factor: 2,
|
|
2392
|
-
// unlimited retries
|
|
2393
|
-
maxAttempts: 0,
|
|
2394
|
-
// wait at least 1 second
|
|
2395
|
-
minDelay: 1000,
|
|
2396
|
-
// at least every 30 seconds
|
|
2397
|
-
maxDelay: 30000,
|
|
2398
|
-
// randomize
|
|
2399
|
-
jitter: true,
|
|
2400
|
-
// retry forever
|
|
2401
|
-
timeout: 0,
|
|
2402
|
-
onOpen: () => null,
|
|
2403
|
-
onConnect: () => null,
|
|
2404
|
-
onMessage: () => null,
|
|
2405
|
-
onOutgoingMessage: () => null,
|
|
2406
|
-
onStatus: () => null,
|
|
2407
|
-
onDisconnect: () => null,
|
|
2408
|
-
onClose: () => null,
|
|
2409
|
-
onDestroy: () => null,
|
|
2410
|
-
onAwarenessUpdate: () => null,
|
|
2411
|
-
onAwarenessChange: () => null,
|
|
2412
|
-
handleTimeout: null,
|
|
2413
|
-
providerMap: new Map(),
|
|
2414
|
-
};
|
|
2415
|
-
this.webSocket = null;
|
|
2416
|
-
this.webSocketHandlers = {};
|
|
2417
|
-
this.shouldConnect = true;
|
|
2418
|
-
this.status = WebSocketStatus.Disconnected;
|
|
2419
|
-
this.lastMessageReceived = 0;
|
|
2420
|
-
this.identifier = 0;
|
|
2421
|
-
this.intervals = {
|
|
2422
|
-
connectionChecker: null,
|
|
2423
|
-
};
|
|
2424
|
-
this.connectionAttempt = null;
|
|
2425
|
-
this.receivedOnOpenPayload = undefined;
|
|
2426
|
-
this.closeTries = 0;
|
|
2427
|
-
this.setConfiguration(configuration);
|
|
2428
|
-
this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill
|
|
2429
|
-
? configuration.WebSocketPolyfill
|
|
2430
|
-
: WebSocket;
|
|
2431
|
-
this.on("open", this.configuration.onOpen);
|
|
2432
|
-
this.on("open", this.onOpen.bind(this));
|
|
2433
|
-
this.on("connect", this.configuration.onConnect);
|
|
2434
|
-
this.on("message", this.configuration.onMessage);
|
|
2435
|
-
this.on("outgoingMessage", this.configuration.onOutgoingMessage);
|
|
2436
|
-
this.on("status", this.configuration.onStatus);
|
|
2437
|
-
this.on("disconnect", this.configuration.onDisconnect);
|
|
2438
|
-
this.on("close", this.configuration.onClose);
|
|
2439
|
-
this.on("destroy", this.configuration.onDestroy);
|
|
2440
|
-
this.on("awarenessUpdate", this.configuration.onAwarenessUpdate);
|
|
2441
|
-
this.on("awarenessChange", this.configuration.onAwarenessChange);
|
|
2442
|
-
this.on("close", this.onClose.bind(this));
|
|
2443
|
-
this.on("message", this.onMessage.bind(this));
|
|
2444
|
-
this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.configuration.messageReconnectTimeout / 10);
|
|
2445
|
-
if (this.shouldConnect) {
|
|
2446
|
-
this.connect();
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
async onOpen(event) {
|
|
2450
|
-
this.status = WebSocketStatus.Connected;
|
|
2451
|
-
this.emit("status", { status: WebSocketStatus.Connected });
|
|
2452
|
-
this.cancelWebsocketRetry = undefined;
|
|
2453
|
-
this.receivedOnOpenPayload = event;
|
|
2454
|
-
}
|
|
2455
|
-
attach(provider) {
|
|
2456
|
-
this.configuration.providerMap.set(provider.configuration.name, provider);
|
|
2457
|
-
if (this.status === WebSocketStatus.Disconnected && this.shouldConnect) {
|
|
2458
|
-
this.connect();
|
|
2459
|
-
}
|
|
2460
|
-
if (this.receivedOnOpenPayload &&
|
|
2461
|
-
this.status === WebSocketStatus.Connected) {
|
|
2462
|
-
provider.onOpen(this.receivedOnOpenPayload);
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
detach(provider) {
|
|
2466
|
-
if (this.configuration.providerMap.has(provider.configuration.name)) {
|
|
2467
|
-
provider.send(CloseMessage, {
|
|
2468
|
-
documentName: provider.configuration.name,
|
|
2469
|
-
});
|
|
2470
|
-
this.configuration.providerMap.delete(provider.configuration.name);
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
setConfiguration(configuration = {}) {
|
|
2474
|
-
this.configuration = { ...this.configuration, ...configuration };
|
|
2475
|
-
if (!this.configuration.autoConnect) {
|
|
2476
|
-
this.shouldConnect = false;
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
async connect() {
|
|
2480
|
-
if (this.status === WebSocketStatus.Connected) {
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
// Always cancel any previously initiated connection retryer instances
|
|
2484
|
-
if (this.cancelWebsocketRetry) {
|
|
2485
|
-
this.cancelWebsocketRetry();
|
|
2486
|
-
this.cancelWebsocketRetry = undefined;
|
|
2487
|
-
}
|
|
2488
|
-
this.receivedOnOpenPayload = undefined;
|
|
2489
|
-
this.shouldConnect = true;
|
|
2490
|
-
const abortableRetry = () => {
|
|
2491
|
-
let cancelAttempt = false;
|
|
2492
|
-
const retryPromise = retry(this.createWebSocketConnection.bind(this), {
|
|
2493
|
-
delay: this.configuration.delay,
|
|
2494
|
-
initialDelay: this.configuration.initialDelay,
|
|
2495
|
-
factor: this.configuration.factor,
|
|
2496
|
-
maxAttempts: this.configuration.maxAttempts,
|
|
2497
|
-
minDelay: this.configuration.minDelay,
|
|
2498
|
-
maxDelay: this.configuration.maxDelay,
|
|
2499
|
-
jitter: this.configuration.jitter,
|
|
2500
|
-
timeout: this.configuration.timeout,
|
|
2501
|
-
handleTimeout: this.configuration.handleTimeout,
|
|
2502
|
-
beforeAttempt: (context) => {
|
|
2503
|
-
if (!this.shouldConnect || cancelAttempt) {
|
|
2504
|
-
context.abort();
|
|
2505
|
-
}
|
|
2506
|
-
},
|
|
2507
|
-
}).catch((error) => {
|
|
2508
|
-
// If we aborted the connection attempt then don’t throw an error
|
|
2509
|
-
// ref: https://github.com/lifeomic/attempt/blob/master/src/index.ts#L136
|
|
2510
|
-
if (error && error.code !== "ATTEMPT_ABORTED") {
|
|
2511
|
-
throw error;
|
|
2512
|
-
}
|
|
2513
|
-
});
|
|
2514
|
-
return {
|
|
2515
|
-
retryPromise,
|
|
2516
|
-
cancelFunc: () => {
|
|
2517
|
-
cancelAttempt = true;
|
|
2518
|
-
},
|
|
2519
|
-
};
|
|
2520
|
-
};
|
|
2521
|
-
const { retryPromise, cancelFunc } = abortableRetry();
|
|
2522
|
-
this.cancelWebsocketRetry = cancelFunc;
|
|
2523
|
-
return retryPromise;
|
|
2524
|
-
}
|
|
2525
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
2526
|
-
attachWebSocketListeners(ws, reject) {
|
|
2527
|
-
const { identifier } = ws;
|
|
2528
|
-
const onMessageHandler = (payload) => this.emit("message", payload);
|
|
2529
|
-
const onCloseHandler = (payload) => this.emit("close", { event: payload });
|
|
2530
|
-
const onOpenHandler = (payload) => this.emit("open", payload);
|
|
2531
|
-
const onErrorHandler = (err) => {
|
|
2532
|
-
reject(err);
|
|
2533
|
-
};
|
|
2534
|
-
this.webSocketHandlers[identifier] = {
|
|
2535
|
-
message: onMessageHandler,
|
|
2536
|
-
close: onCloseHandler,
|
|
2537
|
-
open: onOpenHandler,
|
|
2538
|
-
error: onErrorHandler,
|
|
2539
|
-
};
|
|
2540
|
-
const handlers = this.webSocketHandlers[ws.identifier];
|
|
2541
|
-
Object.keys(handlers).forEach((name) => {
|
|
2542
|
-
ws.addEventListener(name, handlers[name]);
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2545
|
-
cleanupWebSocket() {
|
|
2546
|
-
if (!this.webSocket) {
|
|
2547
|
-
return;
|
|
2548
|
-
}
|
|
2549
|
-
const { identifier } = this.webSocket;
|
|
2550
|
-
const handlers = this.webSocketHandlers[identifier];
|
|
2551
|
-
Object.keys(handlers).forEach((name) => {
|
|
2552
|
-
var _a;
|
|
2553
|
-
(_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.removeEventListener(name, handlers[name]);
|
|
2554
|
-
delete this.webSocketHandlers[identifier];
|
|
2555
|
-
});
|
|
2556
|
-
this.webSocket.close();
|
|
2557
|
-
this.webSocket = null;
|
|
2558
|
-
}
|
|
2559
|
-
createWebSocketConnection() {
|
|
2560
|
-
return new Promise((resolve, reject) => {
|
|
2561
|
-
if (this.webSocket) {
|
|
2562
|
-
this.messageQueue = [];
|
|
2563
|
-
this.cleanupWebSocket();
|
|
2564
|
-
}
|
|
2565
|
-
this.lastMessageReceived = 0;
|
|
2566
|
-
this.identifier += 1;
|
|
2567
|
-
// Init the WebSocket connection
|
|
2568
|
-
const ws = new this.configuration.WebSocketPolyfill(this.url);
|
|
2569
|
-
ws.binaryType = "arraybuffer";
|
|
2570
|
-
ws.identifier = this.identifier;
|
|
2571
|
-
this.attachWebSocketListeners(ws, reject);
|
|
2572
|
-
this.webSocket = ws;
|
|
2573
|
-
// Reset the status
|
|
2574
|
-
this.status = WebSocketStatus.Connecting;
|
|
2575
|
-
this.emit("status", { status: WebSocketStatus.Connecting });
|
|
2576
|
-
// Store resolve/reject for later use
|
|
2577
|
-
this.connectionAttempt = {
|
|
2578
|
-
resolve,
|
|
2579
|
-
reject,
|
|
2580
|
-
};
|
|
2581
|
-
});
|
|
2582
|
-
}
|
|
2583
|
-
onMessage(event) {
|
|
2584
|
-
var _a;
|
|
2585
|
-
this.resolveConnectionAttempt();
|
|
2586
|
-
this.lastMessageReceived = getUnixTime();
|
|
2587
|
-
const message = new IncomingMessage(event.data);
|
|
2588
|
-
const documentName = message.peekVarString();
|
|
2589
|
-
(_a = this.configuration.providerMap.get(documentName)) === null || _a === void 0 ? void 0 : _a.onMessage(event);
|
|
2590
|
-
}
|
|
2591
|
-
resolveConnectionAttempt() {
|
|
2592
|
-
if (this.connectionAttempt) {
|
|
2593
|
-
this.connectionAttempt.resolve();
|
|
2594
|
-
this.connectionAttempt = null;
|
|
2595
|
-
this.status = WebSocketStatus.Connected;
|
|
2596
|
-
this.emit("status", { status: WebSocketStatus.Connected });
|
|
2597
|
-
this.emit("connect");
|
|
2598
|
-
this.messageQueue.forEach((message) => this.send(message));
|
|
2599
|
-
this.messageQueue = [];
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
stopConnectionAttempt() {
|
|
2603
|
-
this.connectionAttempt = null;
|
|
2604
|
-
}
|
|
2605
|
-
rejectConnectionAttempt() {
|
|
2606
|
-
var _a;
|
|
2607
|
-
(_a = this.connectionAttempt) === null || _a === void 0 ? void 0 : _a.reject();
|
|
2608
|
-
this.connectionAttempt = null;
|
|
2609
|
-
}
|
|
2610
|
-
checkConnection() {
|
|
2611
|
-
var _a;
|
|
2612
|
-
// Don’t check the connection when it’s not even established
|
|
2613
|
-
if (this.status !== WebSocketStatus.Connected) {
|
|
2614
|
-
return;
|
|
2615
|
-
}
|
|
2616
|
-
// Don’t close the connection while waiting for the first message
|
|
2617
|
-
if (!this.lastMessageReceived) {
|
|
2618
|
-
return;
|
|
2619
|
-
}
|
|
2620
|
-
// Don’t close the connection when a message was received recently
|
|
2621
|
-
if (this.configuration.messageReconnectTimeout >=
|
|
2622
|
-
getUnixTime() - this.lastMessageReceived) {
|
|
2623
|
-
return;
|
|
2624
|
-
}
|
|
2625
|
-
// No message received in a long time, not even your own
|
|
2626
|
-
// Awareness updates, which are updated every 15 seconds
|
|
2627
|
-
// if awareness is enabled.
|
|
2628
|
-
this.closeTries += 1;
|
|
2629
|
-
// https://bugs.webkit.org/show_bug.cgi?id=247943
|
|
2630
|
-
if (this.closeTries > 2) {
|
|
2631
|
-
this.onClose({
|
|
2632
|
-
event: {
|
|
2633
|
-
code: 4408,
|
|
2634
|
-
reason: "forced",
|
|
2635
|
-
},
|
|
2636
|
-
});
|
|
2637
|
-
this.closeTries = 0;
|
|
2638
|
-
}
|
|
2639
|
-
else {
|
|
2640
|
-
(_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.close();
|
|
2641
|
-
this.messageQueue = [];
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
get serverUrl() {
|
|
2645
|
-
if (this.configuration.preserveTrailingSlash) {
|
|
2646
|
-
return this.configuration.url;
|
|
2647
|
-
}
|
|
2648
|
-
// By default, ensure that the URL never ends with /
|
|
2649
|
-
let url = this.configuration.url;
|
|
2650
|
-
while (url[url.length - 1] === "/") {
|
|
2651
|
-
url = url.slice(0, url.length - 1);
|
|
2652
|
-
}
|
|
2653
|
-
return url;
|
|
2654
|
-
}
|
|
2655
|
-
get url() {
|
|
2656
|
-
return this.serverUrl;
|
|
2657
|
-
}
|
|
2658
|
-
disconnect() {
|
|
2659
|
-
this.shouldConnect = false;
|
|
2660
|
-
if (this.webSocket === null) {
|
|
2661
|
-
return;
|
|
2662
|
-
}
|
|
2663
|
-
try {
|
|
2664
|
-
this.webSocket.close();
|
|
2665
|
-
this.messageQueue = [];
|
|
2666
|
-
}
|
|
2667
|
-
catch (e) {
|
|
2668
|
-
console.error(e);
|
|
2669
|
-
}
|
|
2670
|
-
}
|
|
2671
|
-
send(message) {
|
|
2672
|
-
var _a;
|
|
2673
|
-
if (((_a = this.webSocket) === null || _a === void 0 ? void 0 : _a.readyState) === WsReadyStates.Open) {
|
|
2674
|
-
this.webSocket.send(message);
|
|
2675
|
-
}
|
|
2676
|
-
else {
|
|
2677
|
-
this.messageQueue.push(message);
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
onClose({ event }) {
|
|
2681
|
-
this.closeTries = 0;
|
|
2682
|
-
this.cleanupWebSocket();
|
|
2683
|
-
if (this.connectionAttempt) {
|
|
2684
|
-
// That connection attempt failed.
|
|
2685
|
-
this.rejectConnectionAttempt();
|
|
2686
|
-
}
|
|
2687
|
-
// Let’s update the connection status.
|
|
2688
|
-
this.status = WebSocketStatus.Disconnected;
|
|
2689
|
-
this.emit("status", { status: WebSocketStatus.Disconnected });
|
|
2690
|
-
this.emit("disconnect", { event });
|
|
2691
|
-
// trigger connect if no retry is running and we want to have a connection
|
|
2692
|
-
if (!this.cancelWebsocketRetry && this.shouldConnect) {
|
|
2693
|
-
setTimeout(() => {
|
|
2694
|
-
this.connect();
|
|
2695
|
-
}, this.configuration.delay);
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
destroy() {
|
|
2699
|
-
this.emit("destroy");
|
|
2700
|
-
clearInterval(this.intervals.connectionChecker);
|
|
2701
|
-
// If there is still a connection attempt outstanding then we should stop
|
|
2702
|
-
// it before calling disconnect, otherwise it will be rejected in the onClose
|
|
2703
|
-
// handler and trigger a retry
|
|
2704
|
-
this.stopConnectionAttempt();
|
|
2705
|
-
this.disconnect();
|
|
2706
|
-
this.removeAllListeners();
|
|
2707
|
-
this.cleanupWebSocket();
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
|
-
/**
|
|
2712
|
-
* @module sync-protocol
|
|
2713
|
-
*/
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
/**
|
|
2717
|
-
* @typedef {Map<number, number>} StateMap
|
|
2718
|
-
*/
|
|
2719
|
-
|
|
2720
|
-
/**
|
|
2721
|
-
* Core Yjs defines two message types:
|
|
2722
|
-
* • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2.
|
|
2723
|
-
* • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the client is assured that it
|
|
2724
|
-
* received all information from the remote client.
|
|
2725
|
-
*
|
|
2726
|
-
* In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection
|
|
2727
|
-
* with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both
|
|
2728
|
-
* SyncStep2 and SyncDone, it is assured that it is synced to the remote client.
|
|
2729
|
-
*
|
|
2730
|
-
* In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1.
|
|
2731
|
-
* When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies
|
|
2732
|
-
* with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the
|
|
2733
|
-
* client knows that the sync is finished. There are two reasons for this more elaborated sync model: 1. This protocol can
|
|
2734
|
-
* easily be implemented on top of http and websockets. 2. The server should only reply to requests, and not initiate them.
|
|
2735
|
-
* Therefore it is necessary that the client initiates the sync.
|
|
2736
|
-
*
|
|
2737
|
-
* Construction of a message:
|
|
2738
|
-
* [messageType : varUint, message definition..]
|
|
2739
|
-
*
|
|
2740
|
-
* Note: A message does not include information about the room name. This must to be handled by the upper layer protocol!
|
|
2741
|
-
*
|
|
2742
|
-
* stringify[messageType] stringifies a message definition (messageType is already read from the bufffer)
|
|
2743
|
-
*/
|
|
2744
|
-
|
|
2745
|
-
const messageYjsSyncStep1 = 0;
|
|
2746
|
-
const messageYjsSyncStep2 = 1;
|
|
2747
|
-
const messageYjsUpdate = 2;
|
|
2748
|
-
|
|
2749
|
-
/**
|
|
2750
|
-
* Create a sync step 1 message based on the state of the current shared document.
|
|
2751
|
-
*
|
|
2752
|
-
* @param {encoding.Encoder} encoder
|
|
2753
|
-
* @param {Y.Doc} doc
|
|
2754
|
-
*/
|
|
2755
|
-
const writeSyncStep1 = (encoder, doc) => {
|
|
2756
|
-
writeVarUint(encoder, messageYjsSyncStep1);
|
|
2757
|
-
const sv = Y__namespace.encodeStateVector(doc);
|
|
2758
|
-
writeVarUint8Array(encoder, sv);
|
|
2759
|
-
};
|
|
2760
|
-
|
|
2761
|
-
/**
|
|
2762
|
-
* @param {encoding.Encoder} encoder
|
|
2763
|
-
* @param {Y.Doc} doc
|
|
2764
|
-
* @param {Uint8Array} [encodedStateVector]
|
|
2765
|
-
*/
|
|
2766
|
-
const writeSyncStep2 = (encoder, doc, encodedStateVector) => {
|
|
2767
|
-
writeVarUint(encoder, messageYjsSyncStep2);
|
|
2768
|
-
writeVarUint8Array(encoder, Y__namespace.encodeStateAsUpdate(doc, encodedStateVector));
|
|
2769
|
-
};
|
|
2770
|
-
|
|
2771
|
-
/**
|
|
2772
|
-
* Read SyncStep1 message and reply with SyncStep2.
|
|
2773
|
-
*
|
|
2774
|
-
* @param {decoding.Decoder} decoder The reply to the received message
|
|
2775
|
-
* @param {encoding.Encoder} encoder The received message
|
|
2776
|
-
* @param {Y.Doc} doc
|
|
2777
|
-
*/
|
|
2778
|
-
const readSyncStep1 = (decoder, encoder, doc) =>
|
|
2779
|
-
writeSyncStep2(encoder, doc, readVarUint8Array(decoder));
|
|
2780
|
-
|
|
2781
|
-
/**
|
|
2782
|
-
* Read and apply Structs and then DeleteStore to a y instance.
|
|
2783
|
-
*
|
|
2784
|
-
* @param {decoding.Decoder} decoder
|
|
2785
|
-
* @param {Y.Doc} doc
|
|
2786
|
-
* @param {any} transactionOrigin
|
|
2787
|
-
*/
|
|
2788
|
-
const readSyncStep2 = (decoder, doc, transactionOrigin) => {
|
|
2789
|
-
try {
|
|
2790
|
-
Y__namespace.applyUpdate(doc, readVarUint8Array(decoder), transactionOrigin);
|
|
2791
|
-
} catch (error) {
|
|
2792
|
-
// This catches errors that are thrown by event handlers
|
|
2793
|
-
console.error('Caught error while handling a Yjs update', error);
|
|
2794
|
-
}
|
|
2795
|
-
};
|
|
2796
|
-
|
|
2797
|
-
/**
|
|
2798
|
-
* @param {encoding.Encoder} encoder
|
|
2799
|
-
* @param {Uint8Array} update
|
|
2800
|
-
*/
|
|
2801
|
-
const writeUpdate = (encoder, update) => {
|
|
2802
|
-
writeVarUint(encoder, messageYjsUpdate);
|
|
2803
|
-
writeVarUint8Array(encoder, update);
|
|
2804
|
-
};
|
|
2805
|
-
|
|
2806
|
-
/**
|
|
2807
|
-
* Read and apply Structs and then DeleteStore to a y instance.
|
|
2808
|
-
*
|
|
2809
|
-
* @param {decoding.Decoder} decoder
|
|
2810
|
-
* @param {Y.Doc} doc
|
|
2811
|
-
* @param {any} transactionOrigin
|
|
2812
|
-
*/
|
|
2813
|
-
const readUpdate = readSyncStep2;
|
|
2814
|
-
|
|
2815
|
-
/**
|
|
2816
|
-
* @param {decoding.Decoder} decoder A message received from another client
|
|
2817
|
-
* @param {encoding.Encoder} encoder The reply message. Does not need to be sent if empty.
|
|
2818
|
-
* @param {Y.Doc} doc
|
|
2819
|
-
* @param {any} transactionOrigin
|
|
2820
|
-
*/
|
|
2821
|
-
const readSyncMessage = (decoder, encoder, doc, transactionOrigin) => {
|
|
2822
|
-
const messageType = readVarUint(decoder);
|
|
2823
|
-
switch (messageType) {
|
|
2824
|
-
case messageYjsSyncStep1:
|
|
2825
|
-
readSyncStep1(decoder, encoder, doc);
|
|
2826
|
-
break
|
|
2827
|
-
case messageYjsSyncStep2:
|
|
2828
|
-
readSyncStep2(decoder, doc, transactionOrigin);
|
|
2829
|
-
break
|
|
2830
|
-
case messageYjsUpdate:
|
|
2831
|
-
readUpdate(decoder, doc, transactionOrigin);
|
|
2832
|
-
break
|
|
2833
|
-
default:
|
|
2834
|
-
throw new Error('Unknown message type')
|
|
350
|
+
if (num > MAX_SAFE_INTEGER) {
|
|
351
|
+
throw errorIntegerOutOfRange
|
|
352
|
+
}
|
|
353
|
+
/* c8 ignore stop */
|
|
2835
354
|
}
|
|
2836
|
-
|
|
355
|
+
throw errorUnexpectedEndOfArray
|
|
2837
356
|
};
|
|
2838
357
|
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
358
|
+
/**
|
|
359
|
+
* BroadcastChannel sync provider for cross-tab synchronization
|
|
360
|
+
* This is a lightweight alternative to y-webrtc for browser-tab-only sync
|
|
361
|
+
*/
|
|
362
|
+
class BroadcastSyncProvider {
|
|
363
|
+
type = 'local';
|
|
364
|
+
doc;
|
|
365
|
+
channel;
|
|
366
|
+
_synced = false;
|
|
367
|
+
constructor(docName, doc, _options) {
|
|
368
|
+
this.doc = doc;
|
|
369
|
+
this.channel = new BroadcastChannel(docName);
|
|
370
|
+
// Handle incoming messages from other tabs
|
|
371
|
+
this.channel.onmessage = (event) => {
|
|
372
|
+
this.handleMessage(event.data);
|
|
373
|
+
};
|
|
374
|
+
// Listen to document updates and broadcast them
|
|
375
|
+
this.doc.on('update', this.handleDocUpdate);
|
|
376
|
+
// Send initial sync request
|
|
377
|
+
this.broadcastSync();
|
|
378
|
+
// Mark as synced after a short delay (to receive any pending updates)
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
this._synced = true;
|
|
381
|
+
}, 100);
|
|
382
|
+
if (!_options?.quiet) {
|
|
383
|
+
console.info(`BroadcastChannel Provider initialized: ${docName}`);
|
|
384
|
+
}
|
|
2842
385
|
}
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
this.
|
|
2859
|
-
break;
|
|
2860
|
-
case MessageType.Stateless:
|
|
2861
|
-
provider.receiveStateless(readVarString(message.decoder));
|
|
386
|
+
handleDocUpdate = (update, origin) => {
|
|
387
|
+
// Don't broadcast updates that came from other tabs (to prevent loops)
|
|
388
|
+
if (origin !== this) {
|
|
389
|
+
const encoder = createEncoder();
|
|
390
|
+
writeVarUint(encoder, 0); // Message type: sync update
|
|
391
|
+
writeVarUint8Array(encoder, update);
|
|
392
|
+
this.channel.postMessage(toUint8Array(encoder));
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
handleMessage(message) {
|
|
396
|
+
const decoder = createDecoder(new Uint8Array(message));
|
|
397
|
+
const messageType = readVarUint(decoder);
|
|
398
|
+
switch (messageType) {
|
|
399
|
+
case 0: // Sync update
|
|
400
|
+
const update = readVarUint8Array(decoder);
|
|
401
|
+
Y__namespace.applyUpdate(this.doc, update, this);
|
|
2862
402
|
break;
|
|
2863
|
-
case
|
|
2864
|
-
this.
|
|
403
|
+
case 1: // Sync request
|
|
404
|
+
this.broadcastSync();
|
|
2865
405
|
break;
|
|
2866
|
-
case
|
|
2867
|
-
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
//
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
}
|
|
2875
|
-
provider.onClose();
|
|
2876
|
-
provider.configuration.onClose({ event });
|
|
2877
|
-
provider.forwardClose({ event });
|
|
406
|
+
case 2: // Sync response
|
|
407
|
+
const stateVector = readVarUint8Array(decoder);
|
|
408
|
+
const updateResponse = Y__namespace.encodeStateAsUpdate(this.doc, stateVector);
|
|
409
|
+
if (updateResponse.length > 0) {
|
|
410
|
+
const encoder = createEncoder();
|
|
411
|
+
writeVarUint(encoder, 0); // Send as regular update
|
|
412
|
+
writeVarUint8Array(encoder, updateResponse);
|
|
413
|
+
this.channel.postMessage(toUint8Array(encoder));
|
|
414
|
+
}
|
|
2878
415
|
break;
|
|
2879
|
-
default:
|
|
2880
|
-
throw new Error(`Can’t apply message of unknown type: ${type}`);
|
|
2881
|
-
}
|
|
2882
|
-
// Reply
|
|
2883
|
-
if (message.length() > emptyMessageLength + 1) {
|
|
2884
|
-
// length of documentName (considered in emptyMessageLength plus length of yjs sync type, set in applySyncMessage)
|
|
2885
|
-
// @ts-ignore
|
|
2886
|
-
provider.send(OutgoingMessage, { encoder: message.encoder });
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
applySyncMessage(provider, emitSynced) {
|
|
2890
|
-
const { message } = this;
|
|
2891
|
-
message.writeVarUint(MessageType.Sync);
|
|
2892
|
-
// Apply update
|
|
2893
|
-
const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
|
|
2894
|
-
// Synced once we receive Step2
|
|
2895
|
-
if (emitSynced && syncMessageType === messageYjsSyncStep2) {
|
|
2896
|
-
provider.synced = true;
|
|
2897
416
|
}
|
|
2898
417
|
}
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
if (!provider.awareness)
|
|
2906
|
-
return;
|
|
2907
|
-
const { message } = this;
|
|
2908
|
-
applyAwarenessUpdate(provider.awareness, message.readVarUint8Array(), provider);
|
|
2909
|
-
}
|
|
2910
|
-
applyAuthMessage(provider) {
|
|
2911
|
-
const { message } = this;
|
|
2912
|
-
readAuthMessage(message.decoder, provider.sendToken.bind(provider), provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider));
|
|
418
|
+
broadcastSync() {
|
|
419
|
+
// Broadcast our current state vector to request missing updates
|
|
420
|
+
const encoder = createEncoder();
|
|
421
|
+
writeVarUint(encoder, 2); // Message type: sync response
|
|
422
|
+
writeVarUint8Array(encoder, Y__namespace.encodeStateVector(this.doc));
|
|
423
|
+
this.channel.postMessage(toUint8Array(encoder));
|
|
2913
424
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
425
|
+
async connect() {
|
|
426
|
+
// Wait for initial sync to complete
|
|
427
|
+
if (this._synced) {
|
|
2916
428
|
return;
|
|
2917
|
-
const { message } = this;
|
|
2918
|
-
message.writeVarUint(MessageType.Awareness);
|
|
2919
|
-
message.writeVarUint8Array(encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())));
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
|
|
2923
|
-
class MessageSender {
|
|
2924
|
-
constructor(Message, args = {}) {
|
|
2925
|
-
this.message = new Message();
|
|
2926
|
-
this.encoder = this.message.get(args);
|
|
2927
|
-
}
|
|
2928
|
-
create() {
|
|
2929
|
-
return toUint8Array(this.encoder);
|
|
2930
|
-
}
|
|
2931
|
-
send(webSocket) {
|
|
2932
|
-
webSocket === null || webSocket === void 0 ? void 0 : webSocket.send(this.create());
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
|
-
|
|
2936
|
-
class AuthenticationMessage extends OutgoingMessage {
|
|
2937
|
-
constructor() {
|
|
2938
|
-
super(...arguments);
|
|
2939
|
-
this.type = MessageType.Auth;
|
|
2940
|
-
this.description = "Authentication";
|
|
2941
|
-
}
|
|
2942
|
-
get(args) {
|
|
2943
|
-
if (typeof args.token === "undefined") {
|
|
2944
|
-
throw new Error("The authentication message requires `token` as an argument.");
|
|
2945
|
-
}
|
|
2946
|
-
writeVarString(this.encoder, args.documentName);
|
|
2947
|
-
writeVarUint(this.encoder, this.type);
|
|
2948
|
-
writeAuthentication(this.encoder, args.token);
|
|
2949
|
-
return this.encoder;
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
|
-
class AwarenessMessage extends OutgoingMessage {
|
|
2954
|
-
constructor() {
|
|
2955
|
-
super(...arguments);
|
|
2956
|
-
this.type = MessageType.Awareness;
|
|
2957
|
-
this.description = "Awareness states update";
|
|
2958
|
-
}
|
|
2959
|
-
get(args) {
|
|
2960
|
-
if (typeof args.awareness === "undefined") {
|
|
2961
|
-
throw new Error("The awareness message requires awareness as an argument");
|
|
2962
|
-
}
|
|
2963
|
-
if (typeof args.clients === "undefined") {
|
|
2964
|
-
throw new Error("The awareness message requires clients as an argument");
|
|
2965
429
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
class StatelessMessage extends OutgoingMessage {
|
|
2981
|
-
constructor() {
|
|
2982
|
-
super(...arguments);
|
|
2983
|
-
this.type = MessageType.Stateless;
|
|
2984
|
-
this.description = "A stateless message";
|
|
2985
|
-
}
|
|
2986
|
-
get(args) {
|
|
2987
|
-
var _a;
|
|
2988
|
-
writeVarString(this.encoder, args.documentName);
|
|
2989
|
-
writeVarUint(this.encoder, this.type);
|
|
2990
|
-
writeVarString(this.encoder, (_a = args.payload) !== null && _a !== void 0 ? _a : "");
|
|
2991
|
-
return this.encoder;
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
class SyncStepOneMessage extends OutgoingMessage {
|
|
2996
|
-
constructor() {
|
|
2997
|
-
super(...arguments);
|
|
2998
|
-
this.type = MessageType.Sync;
|
|
2999
|
-
this.description = "First sync step";
|
|
430
|
+
return new Promise((resolve) => {
|
|
431
|
+
const checkSync = () => {
|
|
432
|
+
if (this._synced) {
|
|
433
|
+
resolve();
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
setTimeout(checkSync, 50);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
checkSync();
|
|
440
|
+
});
|
|
3000
441
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
throw new Error("The sync step one message requires document as an argument");
|
|
3004
|
-
}
|
|
3005
|
-
writeVarString(this.encoder, args.documentName);
|
|
3006
|
-
writeVarUint(this.encoder, this.type);
|
|
3007
|
-
writeSyncStep1(this.encoder, args.document);
|
|
3008
|
-
return this.encoder;
|
|
442
|
+
disconnect() {
|
|
443
|
+
// BroadcastChannel doesn't have explicit disconnect
|
|
3009
444
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
constructor() {
|
|
3014
|
-
super(...arguments);
|
|
3015
|
-
this.type = MessageType.Sync;
|
|
3016
|
-
this.description = "A document update";
|
|
445
|
+
async reconnect() {
|
|
446
|
+
this.disconnect();
|
|
447
|
+
return this.connect();
|
|
3017
448
|
}
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
writeUpdate(this.encoder, args.update);
|
|
3022
|
-
return this.encoder;
|
|
449
|
+
destroy() {
|
|
450
|
+
this.doc.off('update', this.handleDocUpdate);
|
|
451
|
+
this.channel.close();
|
|
3023
452
|
}
|
|
3024
453
|
}
|
|
3025
454
|
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
var _a, _b, _c;
|
|
3035
|
-
super();
|
|
3036
|
-
this.configuration = {
|
|
3037
|
-
name: "",
|
|
3038
|
-
// @ts-ignore
|
|
3039
|
-
document: undefined,
|
|
3040
|
-
// @ts-ignore
|
|
3041
|
-
awareness: undefined,
|
|
3042
|
-
token: null,
|
|
3043
|
-
forceSyncInterval: false,
|
|
3044
|
-
onAuthenticated: () => null,
|
|
3045
|
-
onAuthenticationFailed: () => null,
|
|
3046
|
-
onOpen: () => null,
|
|
3047
|
-
onConnect: () => null,
|
|
3048
|
-
onMessage: () => null,
|
|
3049
|
-
onOutgoingMessage: () => null,
|
|
3050
|
-
onSynced: () => null,
|
|
3051
|
-
onStatus: () => null,
|
|
3052
|
-
onDisconnect: () => null,
|
|
3053
|
-
onClose: () => null,
|
|
3054
|
-
onDestroy: () => null,
|
|
3055
|
-
onAwarenessUpdate: () => null,
|
|
3056
|
-
onAwarenessChange: () => null,
|
|
3057
|
-
onStateless: () => null,
|
|
3058
|
-
onUnsyncedChanges: () => null,
|
|
3059
|
-
};
|
|
3060
|
-
this.isSynced = false;
|
|
3061
|
-
this.unsyncedChanges = 0;
|
|
3062
|
-
this.isAuthenticated = false;
|
|
3063
|
-
this.authorizedScope = undefined;
|
|
3064
|
-
// @internal
|
|
3065
|
-
this.manageSocket = false;
|
|
3066
|
-
this._isAttached = false;
|
|
3067
|
-
this.intervals = {
|
|
3068
|
-
forceSync: null,
|
|
3069
|
-
};
|
|
3070
|
-
this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
|
|
3071
|
-
this.boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this);
|
|
3072
|
-
this.boundPageHide = this.pageHide.bind(this);
|
|
3073
|
-
this.boundOnOpen = this.onOpen.bind(this);
|
|
3074
|
-
this.boundOnClose = this.onClose.bind(this);
|
|
3075
|
-
this.forwardConnect = () => this.emit("connect");
|
|
3076
|
-
this.forwardStatus = (e) => this.emit("status", e);
|
|
3077
|
-
this.forwardClose = (e) => this.emit("close", e);
|
|
3078
|
-
this.forwardDisconnect = (e) => this.emit("disconnect", e);
|
|
3079
|
-
this.forwardDestroy = () => this.emit("destroy");
|
|
3080
|
-
this.setConfiguration(configuration);
|
|
3081
|
-
this.configuration.document = configuration.document
|
|
3082
|
-
? configuration.document
|
|
3083
|
-
: new Y__namespace.Doc();
|
|
3084
|
-
this.configuration.awareness =
|
|
3085
|
-
configuration.awareness !== undefined
|
|
3086
|
-
? configuration.awareness
|
|
3087
|
-
: new Awareness(this.document);
|
|
3088
|
-
this.on("open", this.configuration.onOpen);
|
|
3089
|
-
this.on("message", this.configuration.onMessage);
|
|
3090
|
-
this.on("outgoingMessage", this.configuration.onOutgoingMessage);
|
|
3091
|
-
this.on("synced", this.configuration.onSynced);
|
|
3092
|
-
this.on("destroy", this.configuration.onDestroy);
|
|
3093
|
-
this.on("awarenessUpdate", this.configuration.onAwarenessUpdate);
|
|
3094
|
-
this.on("awarenessChange", this.configuration.onAwarenessChange);
|
|
3095
|
-
this.on("stateless", this.configuration.onStateless);
|
|
3096
|
-
this.on("unsyncedChanges", this.configuration.onUnsyncedChanges);
|
|
3097
|
-
this.on("authenticated", this.configuration.onAuthenticated);
|
|
3098
|
-
this.on("authenticationFailed", this.configuration.onAuthenticationFailed);
|
|
3099
|
-
(_a = this.awareness) === null || _a === void 0 ? void 0 : _a.on("update", () => {
|
|
3100
|
-
this.emit("awarenessUpdate", {
|
|
3101
|
-
states: awarenessStatesToArray(this.awareness.getStates()),
|
|
3102
|
-
});
|
|
3103
|
-
});
|
|
3104
|
-
(_b = this.awareness) === null || _b === void 0 ? void 0 : _b.on("change", () => {
|
|
3105
|
-
this.emit("awarenessChange", {
|
|
3106
|
-
states: awarenessStatesToArray(this.awareness.getStates()),
|
|
3107
|
-
});
|
|
3108
|
-
});
|
|
3109
|
-
this.document.on("update", this.boundDocumentUpdateHandler);
|
|
3110
|
-
(_c = this.awareness) === null || _c === void 0 ? void 0 : _c.on("update", this.boundAwarenessUpdateHandler);
|
|
3111
|
-
this.registerEventListeners();
|
|
3112
|
-
if (this.configuration.forceSyncInterval &&
|
|
3113
|
-
typeof this.configuration.forceSyncInterval === "number") {
|
|
3114
|
-
this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval);
|
|
3115
|
-
}
|
|
3116
|
-
if (this.manageSocket) {
|
|
3117
|
-
this.attach();
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
setConfiguration(configuration = {}) {
|
|
3121
|
-
if (!configuration.websocketProvider) {
|
|
3122
|
-
this.manageSocket = true;
|
|
3123
|
-
this.configuration.websocketProvider = new HocuspocusProviderWebsocket(configuration);
|
|
3124
|
-
}
|
|
3125
|
-
this.configuration = { ...this.configuration, ...configuration };
|
|
3126
|
-
}
|
|
3127
|
-
get document() {
|
|
3128
|
-
return this.configuration.document;
|
|
3129
|
-
}
|
|
3130
|
-
get isAttached() {
|
|
3131
|
-
return this._isAttached;
|
|
3132
|
-
}
|
|
455
|
+
/**
|
|
456
|
+
* WebSocket sync provider for real-time collaboration
|
|
457
|
+
*/
|
|
458
|
+
class WebSocketSyncProvider {
|
|
459
|
+
type = 'network';
|
|
460
|
+
provider;
|
|
461
|
+
isConnected = false;
|
|
462
|
+
_quiet = false;
|
|
3133
463
|
get awareness() {
|
|
3134
|
-
return this.
|
|
3135
|
-
}
|
|
3136
|
-
get hasUnsyncedChanges() {
|
|
3137
|
-
return this.unsyncedChanges > 0;
|
|
3138
|
-
}
|
|
3139
|
-
resetUnsyncedChanges() {
|
|
3140
|
-
this.unsyncedChanges = 1;
|
|
3141
|
-
this.emit("unsyncedChanges", { number: this.unsyncedChanges });
|
|
3142
|
-
}
|
|
3143
|
-
incrementUnsyncedChanges() {
|
|
3144
|
-
this.unsyncedChanges += 1;
|
|
3145
|
-
this.emit("unsyncedChanges", { number: this.unsyncedChanges });
|
|
3146
|
-
}
|
|
3147
|
-
decrementUnsyncedChanges() {
|
|
3148
|
-
if (this.unsyncedChanges > 0) {
|
|
3149
|
-
this.unsyncedChanges -= 1;
|
|
3150
|
-
}
|
|
3151
|
-
if (this.unsyncedChanges === 0) {
|
|
3152
|
-
this.synced = true;
|
|
3153
|
-
}
|
|
3154
|
-
this.emit("unsyncedChanges", { number: this.unsyncedChanges });
|
|
464
|
+
return this.provider.awareness;
|
|
3155
465
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
466
|
+
constructor(docName, doc, options) {
|
|
467
|
+
const url = options?.url || 'ws://localhost:1234';
|
|
468
|
+
const roomName = options?.roomName || docName;
|
|
469
|
+
this.provider = new yWebsocket.WebsocketProvider(url, roomName, doc, {
|
|
470
|
+
params: options?.params,
|
|
471
|
+
protocols: options?.protocols,
|
|
472
|
+
WebSocketPolyfill: options?.WebSocketPolyfill,
|
|
473
|
+
awareness: options?.awareness,
|
|
474
|
+
maxBackoffTime: options?.maxBackoffTime,
|
|
475
|
+
disableBc: true,
|
|
3161
476
|
});
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
if (this.
|
|
3165
|
-
|
|
477
|
+
this._quiet = options?.quiet ?? false;
|
|
478
|
+
this.setupEventListeners();
|
|
479
|
+
if (!this._quiet) {
|
|
480
|
+
console.info(`WebSocket Provider initialized: ${url}/${roomName}`);
|
|
3166
481
|
}
|
|
3167
482
|
}
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
483
|
+
/**
|
|
484
|
+
* Static factory method for creating WebSocketSyncProvider with configuration options
|
|
485
|
+
* Returns a ProviderFactory that can be used in sync configuration
|
|
486
|
+
*/
|
|
487
|
+
static with(options) {
|
|
488
|
+
return {
|
|
489
|
+
create: (docName, doc, runtimeOptions) => {
|
|
490
|
+
const mergedOptions = runtimeOptions ? { ...options, ...runtimeOptions } : options;
|
|
491
|
+
return new WebSocketSyncProvider(docName, doc, mergedOptions);
|
|
492
|
+
},
|
|
493
|
+
};
|
|
3173
494
|
}
|
|
3174
|
-
|
|
3175
|
-
this.
|
|
3176
|
-
|
|
3177
|
-
|
|
495
|
+
setupEventListeners() {
|
|
496
|
+
this.provider.on('status', ({ status }) => {
|
|
497
|
+
if (status === 'connected') {
|
|
498
|
+
this.isConnected = true;
|
|
499
|
+
if (!this._quiet) {
|
|
500
|
+
console.info('WebSocket connected');
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else if (status === 'disconnected') {
|
|
504
|
+
this.isConnected = false;
|
|
505
|
+
if (!this._quiet) {
|
|
506
|
+
console.info('WebSocket disconnected');
|
|
507
|
+
}
|
|
508
|
+
}
|
|
3178
509
|
});
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
token = await this.getToken();
|
|
3184
|
-
}
|
|
3185
|
-
catch (error) {
|
|
3186
|
-
this.permissionDeniedHandler(`Failed to get token during sendToken(): ${error}`);
|
|
3187
|
-
return;
|
|
3188
|
-
}
|
|
3189
|
-
this.send(AuthenticationMessage, {
|
|
3190
|
-
token: token !== null && token !== void 0 ? token : "",
|
|
3191
|
-
documentName: this.configuration.name,
|
|
510
|
+
this.provider.on('sync', (synced) => {
|
|
511
|
+
if (synced && !this._quiet) {
|
|
512
|
+
console.info('WebSocket synced');
|
|
513
|
+
}
|
|
3192
514
|
});
|
|
3193
515
|
}
|
|
3194
|
-
|
|
3195
|
-
if (
|
|
516
|
+
async connect() {
|
|
517
|
+
if (this.isConnected) {
|
|
3196
518
|
return;
|
|
3197
519
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
const timeout = setTimeout(() => {
|
|
522
|
+
reject(new Error('WebSocket connection timeout'));
|
|
523
|
+
}, 10000); // 10 second timeout
|
|
524
|
+
const statusHandler = ({ status }) => {
|
|
525
|
+
if (status === 'connected') {
|
|
526
|
+
clearTimeout(timeout);
|
|
527
|
+
this.provider.off('status', statusHandler);
|
|
528
|
+
this.isConnected = true;
|
|
529
|
+
resolve();
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
this.provider.on('status', statusHandler);
|
|
533
|
+
// If already connected, resolve immediately
|
|
534
|
+
if (this.provider.wsconnected) {
|
|
535
|
+
clearTimeout(timeout);
|
|
536
|
+
this.provider.off('status', statusHandler);
|
|
537
|
+
this.isConnected = true;
|
|
538
|
+
resolve();
|
|
539
|
+
}
|
|
3207
540
|
});
|
|
3208
541
|
}
|
|
3209
|
-
/**
|
|
3210
|
-
* Indicates whether a first handshake with the server has been established
|
|
3211
|
-
*
|
|
3212
|
-
* Note: this does not mean all updates from the client have been persisted to the backend. For this,
|
|
3213
|
-
* use `hasUnsyncedChanges`.
|
|
3214
|
-
*/
|
|
3215
|
-
get synced() {
|
|
3216
|
-
return this.isSynced;
|
|
3217
|
-
}
|
|
3218
|
-
set synced(state) {
|
|
3219
|
-
if (this.isSynced === state) {
|
|
3220
|
-
return;
|
|
3221
|
-
}
|
|
3222
|
-
this.isSynced = state;
|
|
3223
|
-
if (state) {
|
|
3224
|
-
this.emit("synced", { state });
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
receiveStateless(payload) {
|
|
3228
|
-
this.emit("stateless", { payload });
|
|
3229
|
-
}
|
|
3230
|
-
// not needed, but provides backward compatibility with e.g. lexical/yjs
|
|
3231
|
-
async connect() {
|
|
3232
|
-
if (this.manageSocket) {
|
|
3233
|
-
return this.configuration.websocketProvider.connect();
|
|
3234
|
-
}
|
|
3235
|
-
console.warn("HocuspocusProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
|
|
3236
|
-
}
|
|
3237
542
|
disconnect() {
|
|
3238
|
-
if (this.
|
|
3239
|
-
|
|
3240
|
-
}
|
|
3241
|
-
console.warn("HocuspocusProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
|
|
3242
|
-
}
|
|
3243
|
-
async onOpen(event) {
|
|
3244
|
-
this.isAuthenticated = false;
|
|
3245
|
-
this.emit("open", { event });
|
|
3246
|
-
await this.sendToken();
|
|
3247
|
-
this.startSync();
|
|
3248
|
-
}
|
|
3249
|
-
async getToken() {
|
|
3250
|
-
if (typeof this.configuration.token === "function") {
|
|
3251
|
-
const token = await this.configuration.token();
|
|
3252
|
-
return token;
|
|
3253
|
-
}
|
|
3254
|
-
return this.configuration.token;
|
|
3255
|
-
}
|
|
3256
|
-
startSync() {
|
|
3257
|
-
this.resetUnsyncedChanges();
|
|
3258
|
-
this.send(SyncStepOneMessage, {
|
|
3259
|
-
document: this.document,
|
|
3260
|
-
documentName: this.configuration.name,
|
|
3261
|
-
});
|
|
3262
|
-
if (this.awareness && this.awareness.getLocalState() !== null) {
|
|
3263
|
-
this.send(AwarenessMessage, {
|
|
3264
|
-
awareness: this.awareness,
|
|
3265
|
-
clients: [this.document.clientID],
|
|
3266
|
-
documentName: this.configuration.name,
|
|
3267
|
-
});
|
|
543
|
+
if (this.provider) {
|
|
544
|
+
this.provider.disconnect();
|
|
3268
545
|
}
|
|
546
|
+
this.isConnected = false;
|
|
3269
547
|
}
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
const messageSender = new MessageSender(message, args);
|
|
3274
|
-
this.emit("outgoingMessage", { message: messageSender.message });
|
|
3275
|
-
messageSender.send(this.configuration.websocketProvider);
|
|
3276
|
-
}
|
|
3277
|
-
onMessage(event) {
|
|
3278
|
-
const message = new IncomingMessage(event.data);
|
|
3279
|
-
const documentName = message.readVarString();
|
|
3280
|
-
message.writeVarString(documentName);
|
|
3281
|
-
this.emit("message", { event, message: new IncomingMessage(event.data) });
|
|
3282
|
-
new MessageReceiver(message).apply(this, true);
|
|
3283
|
-
}
|
|
3284
|
-
onClose() {
|
|
3285
|
-
this.isAuthenticated = false;
|
|
3286
|
-
this.synced = false;
|
|
3287
|
-
// update awareness (all users except local left)
|
|
3288
|
-
if (this.awareness) {
|
|
3289
|
-
removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter((client) => client !== this.document.clientID), this);
|
|
3290
|
-
}
|
|
548
|
+
async reconnect() {
|
|
549
|
+
this.disconnect();
|
|
550
|
+
return this.connect();
|
|
3291
551
|
}
|
|
3292
552
|
destroy() {
|
|
3293
|
-
this.
|
|
3294
|
-
|
|
3295
|
-
clearInterval(this.intervals.forceSync);
|
|
3296
|
-
}
|
|
3297
|
-
if (this.awareness) {
|
|
3298
|
-
removeAwarenessStates(this.awareness, [this.document.clientID], "provider destroy");
|
|
3299
|
-
this.awareness.off("update", this.boundAwarenessUpdateHandler);
|
|
3300
|
-
this.awareness.destroy();
|
|
3301
|
-
}
|
|
3302
|
-
this.document.off("update", this.boundDocumentUpdateHandler);
|
|
3303
|
-
this.removeAllListeners();
|
|
3304
|
-
this.detach();
|
|
3305
|
-
if (this.manageSocket) {
|
|
3306
|
-
this.configuration.websocketProvider.destroy();
|
|
3307
|
-
}
|
|
3308
|
-
if (typeof window === "undefined" || !("removeEventListener" in window)) {
|
|
3309
|
-
return;
|
|
3310
|
-
}
|
|
3311
|
-
window.removeEventListener("pagehide", this.boundPageHide);
|
|
3312
|
-
}
|
|
3313
|
-
detach() {
|
|
3314
|
-
this.configuration.websocketProvider.off("connect", this.configuration.onConnect);
|
|
3315
|
-
this.configuration.websocketProvider.off("connect", this.forwardConnect);
|
|
3316
|
-
this.configuration.websocketProvider.off("status", this.forwardStatus);
|
|
3317
|
-
this.configuration.websocketProvider.off("status", this.configuration.onStatus);
|
|
3318
|
-
this.configuration.websocketProvider.off("open", this.boundOnOpen);
|
|
3319
|
-
this.configuration.websocketProvider.off("close", this.boundOnClose);
|
|
3320
|
-
this.configuration.websocketProvider.off("close", this.configuration.onClose);
|
|
3321
|
-
this.configuration.websocketProvider.off("close", this.forwardClose);
|
|
3322
|
-
this.configuration.websocketProvider.off("disconnect", this.configuration.onDisconnect);
|
|
3323
|
-
this.configuration.websocketProvider.off("disconnect", this.forwardDisconnect);
|
|
3324
|
-
this.configuration.websocketProvider.off("destroy", this.configuration.onDestroy);
|
|
3325
|
-
this.configuration.websocketProvider.off("destroy", this.forwardDestroy);
|
|
3326
|
-
this.configuration.websocketProvider.detach(this);
|
|
3327
|
-
this._isAttached = false;
|
|
3328
|
-
}
|
|
3329
|
-
attach() {
|
|
3330
|
-
if (this._isAttached)
|
|
3331
|
-
return;
|
|
3332
|
-
this.configuration.websocketProvider.on("connect", this.configuration.onConnect);
|
|
3333
|
-
this.configuration.websocketProvider.on("connect", this.forwardConnect);
|
|
3334
|
-
this.configuration.websocketProvider.on("status", this.configuration.onStatus);
|
|
3335
|
-
this.configuration.websocketProvider.on("status", this.forwardStatus);
|
|
3336
|
-
this.configuration.websocketProvider.on("open", this.boundOnOpen);
|
|
3337
|
-
this.configuration.websocketProvider.on("close", this.boundOnClose);
|
|
3338
|
-
this.configuration.websocketProvider.on("close", this.configuration.onClose);
|
|
3339
|
-
this.configuration.websocketProvider.on("close", this.forwardClose);
|
|
3340
|
-
this.configuration.websocketProvider.on("disconnect", this.configuration.onDisconnect);
|
|
3341
|
-
this.configuration.websocketProvider.on("disconnect", this.forwardDisconnect);
|
|
3342
|
-
this.configuration.websocketProvider.on("destroy", this.configuration.onDestroy);
|
|
3343
|
-
this.configuration.websocketProvider.on("destroy", this.forwardDestroy);
|
|
3344
|
-
this.configuration.websocketProvider.attach(this);
|
|
3345
|
-
this._isAttached = true;
|
|
3346
|
-
}
|
|
3347
|
-
permissionDeniedHandler(reason) {
|
|
3348
|
-
this.emit("authenticationFailed", { reason });
|
|
3349
|
-
this.isAuthenticated = false;
|
|
3350
|
-
}
|
|
3351
|
-
authenticatedHandler(scope) {
|
|
3352
|
-
this.isAuthenticated = true;
|
|
3353
|
-
this.authorizedScope = scope;
|
|
3354
|
-
this.emit("authenticated", { scope });
|
|
3355
|
-
}
|
|
3356
|
-
setAwarenessField(key, value) {
|
|
3357
|
-
if (!this.awareness) {
|
|
3358
|
-
throw new AwarenessError(`Cannot set awareness field "${key}" to ${JSON.stringify(value)}. You have disabled Awareness for this provider by explicitly passing awareness: null in the provider configuration.`);
|
|
553
|
+
if (this.provider) {
|
|
554
|
+
this.provider.destroy();
|
|
3359
555
|
}
|
|
3360
|
-
this.
|
|
556
|
+
this.isConnected = false;
|
|
3361
557
|
}
|
|
3362
558
|
}
|
|
3363
559
|
|
|
@@ -3476,7 +672,7 @@ class HocuspocusSyncProvider {
|
|
|
3476
672
|
if (options?.onAuthenticationFailed) {
|
|
3477
673
|
config.onAuthenticationFailed = options.onAuthenticationFailed;
|
|
3478
674
|
}
|
|
3479
|
-
this.provider = new HocuspocusProvider(config);
|
|
675
|
+
this.provider = new workspace_migrations.HocuspocusProvider(config);
|
|
3480
676
|
// Must call attach() explicitly when using shared socket
|
|
3481
677
|
this.provider.attach();
|
|
3482
678
|
if (!options?.quiet) {
|
|
@@ -3508,7 +704,7 @@ class HocuspocusSyncProvider {
|
|
|
3508
704
|
if (options?.WebSocketPolyfill) {
|
|
3509
705
|
config.WebSocketPolyfill = options.WebSocketPolyfill;
|
|
3510
706
|
}
|
|
3511
|
-
this.provider = new HocuspocusProvider(config);
|
|
707
|
+
this.provider = new workspace_migrations.HocuspocusProvider(config);
|
|
3512
708
|
if (!options?.quiet) {
|
|
3513
709
|
console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
|
|
3514
710
|
}
|
|
@@ -3567,7 +763,7 @@ class HocuspocusSyncProvider {
|
|
|
3567
763
|
if (options.onStatus) {
|
|
3568
764
|
config.onStatus = options.onStatus;
|
|
3569
765
|
}
|
|
3570
|
-
HocuspocusSyncProvider.sharedWebSocketProvider = new HocuspocusProviderWebsocket(config);
|
|
766
|
+
HocuspocusSyncProvider.sharedWebSocketProvider = new workspace_migrations.HocuspocusProviderWebsocket(config);
|
|
3571
767
|
console.info(`Shared Hocuspocus WebSocket created: ${options.url}`);
|
|
3572
768
|
return HocuspocusSyncProvider.sharedWebSocketProvider;
|
|
3573
769
|
}
|
|
@@ -3878,19 +1074,10 @@ class PresignedAssetProvider {
|
|
|
3878
1074
|
}
|
|
3879
1075
|
}
|
|
3880
1076
|
|
|
3881
|
-
/**
|
|
3882
|
-
* Default asset storage configuration. Stores bytes in IndexedDB only,
|
|
3883
|
-
* which provides offline-friendly behavior out of the box. Applications
|
|
3884
|
-
* that sync Yjs over the network should also configure a remote provider
|
|
3885
|
-
* (e.g. HttpAssetProvider) so images are available across devices.
|
|
3886
|
-
*/
|
|
3887
|
-
const DEFAULT_ASSET_STORAGE_CONFIG = {
|
|
3888
|
-
providers: [workspace_migrations.IndexedDBAssetProvider],
|
|
3889
|
-
};
|
|
3890
|
-
|
|
3891
1077
|
exports.APP_STATE_MIGRATIONS = workspace_migrations.APP_STATE_MIGRATIONS;
|
|
3892
1078
|
exports.CURRENT_APP_STATE_SCHEMA_VERSION = workspace_migrations.CURRENT_APP_STATE_SCHEMA_VERSION;
|
|
3893
1079
|
exports.CURRENT_WORKSPACE_SCHEMA_VERSION = workspace_migrations.CURRENT_WORKSPACE_SCHEMA_VERSION;
|
|
1080
|
+
exports.DEFAULT_ASSET_STORAGE_CONFIG = workspace_migrations.DEFAULT_ASSET_STORAGE_CONFIG;
|
|
3894
1081
|
exports.DEFAULT_BRUSH_CONFIG = workspace_migrations.DEFAULT_BRUSH_CONFIG;
|
|
3895
1082
|
exports.DEFAULT_LINE_TOOL_CONFIG = workspace_migrations.DEFAULT_LINE_TOOL_CONFIG;
|
|
3896
1083
|
exports.DEFAULT_TEXT_CONFIG = workspace_migrations.DEFAULT_TEXT_CONFIG;
|
|
@@ -3928,7 +1115,6 @@ exports.darkTheme = workspace_migrations.darkTheme;
|
|
|
3928
1115
|
exports.lightTheme = workspace_migrations.lightTheme;
|
|
3929
1116
|
exports.runMigrations = workspace_migrations.runMigrations;
|
|
3930
1117
|
exports.BroadcastSyncProvider = BroadcastSyncProvider;
|
|
3931
|
-
exports.DEFAULT_ASSET_STORAGE_CONFIG = DEFAULT_ASSET_STORAGE_CONFIG;
|
|
3932
1118
|
exports.HocuspocusSyncProvider = HocuspocusSyncProvider;
|
|
3933
1119
|
exports.HttpAssetProvider = HttpAssetProvider;
|
|
3934
1120
|
exports.PresignedAssetProvider = PresignedAssetProvider;
|