@hocuspocus/provider 2.15.2 → 3.0.4-rc.0

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.
Files changed (70) hide show
  1. package/dist/hocuspocus-provider.cjs +931 -1532
  2. package/dist/hocuspocus-provider.cjs.map +1 -1
  3. package/dist/hocuspocus-provider.esm.js +931 -1527
  4. package/dist/hocuspocus-provider.esm.js.map +1 -1
  5. package/dist/packages/extension-database/src/Database.d.ts +1 -1
  6. package/dist/packages/extension-logger/src/Logger.d.ts +1 -1
  7. package/dist/packages/extension-redis/src/Redis.d.ts +4 -3
  8. package/dist/packages/extension-sqlite/src/SQLite.d.ts +2 -1
  9. package/dist/packages/extension-throttle/src/index.d.ts +2 -2
  10. package/dist/packages/extension-webhook/src/index.d.ts +3 -3
  11. package/dist/packages/provider/src/HocuspocusProvider.d.ts +12 -45
  12. package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +5 -9
  13. package/dist/packages/provider/src/IncomingMessage.d.ts +3 -3
  14. package/dist/packages/provider/src/MessageReceiver.d.ts +2 -4
  15. package/dist/packages/provider/src/MessageSender.d.ts +2 -2
  16. package/dist/packages/provider/src/OutgoingMessage.d.ts +2 -2
  17. package/dist/packages/provider/src/OutgoingMessages/AuthenticationMessage.d.ts +2 -1
  18. package/dist/packages/provider/src/OutgoingMessages/AwarenessMessage.d.ts +2 -1
  19. package/dist/packages/provider/src/OutgoingMessages/CloseMessage.d.ts +2 -1
  20. package/dist/packages/provider/src/OutgoingMessages/QueryAwarenessMessage.d.ts +2 -1
  21. package/dist/packages/provider/src/OutgoingMessages/StatelessMessage.d.ts +2 -1
  22. package/dist/packages/provider/src/OutgoingMessages/SyncStepOneMessage.d.ts +2 -1
  23. package/dist/packages/provider/src/OutgoingMessages/SyncStepTwoMessage.d.ts +2 -1
  24. package/dist/packages/provider/src/OutgoingMessages/UpdateMessage.d.ts +2 -1
  25. package/dist/packages/provider/src/index.d.ts +0 -2
  26. package/dist/packages/provider/src/types.d.ts +12 -12
  27. package/dist/packages/server/src/ClientConnection.d.ts +19 -10
  28. package/dist/packages/server/src/Connection.d.ts +7 -23
  29. package/dist/packages/server/src/DirectConnection.d.ts +2 -2
  30. package/dist/packages/server/src/Document.d.ts +3 -7
  31. package/dist/packages/server/src/Hocuspocus.d.ts +7 -36
  32. package/dist/packages/server/src/IncomingMessage.d.ts +3 -3
  33. package/dist/packages/server/src/MessageReceiver.d.ts +4 -6
  34. package/dist/packages/server/src/OutgoingMessage.d.ts +4 -3
  35. package/dist/packages/server/src/Server.d.ts +23 -3
  36. package/dist/packages/server/src/index.d.ts +1 -1
  37. package/dist/packages/server/src/types.d.ts +15 -29
  38. package/dist/packages/server/src/util/getParameters.d.ts +1 -1
  39. package/dist/packages/transformer/src/Prosemirror.d.ts +1 -1
  40. package/dist/packages/transformer/src/Tiptap.d.ts +3 -3
  41. package/dist/packages/transformer/src/types.d.ts +1 -1
  42. package/dist/tests/utils/newHocuspocus.d.ts +2 -2
  43. package/dist/tests/utils/newHocuspocusProvider.d.ts +2 -2
  44. package/dist/tests/utils/newHocuspocusProviderWebsocket.d.ts +4 -3
  45. package/dist/tests/utils/retryableAssertion.d.ts +1 -1
  46. package/package.json +2 -2
  47. package/src/EventEmitter.ts +3 -1
  48. package/src/HocuspocusProvider.ts +74 -200
  49. package/src/HocuspocusProviderWebsocket.ts +24 -75
  50. package/src/IncomingMessage.ts +5 -3
  51. package/src/MessageReceiver.ts +20 -19
  52. package/src/MessageSender.ts +3 -2
  53. package/src/OutgoingMessage.ts +3 -2
  54. package/src/OutgoingMessages/AuthenticationMessage.ts +2 -1
  55. package/src/OutgoingMessages/AwarenessMessage.ts +2 -1
  56. package/src/OutgoingMessages/CloseMessage.ts +2 -1
  57. package/src/OutgoingMessages/QueryAwarenessMessage.ts +2 -1
  58. package/src/OutgoingMessages/StatelessMessage.ts +2 -1
  59. package/src/OutgoingMessages/SyncStepOneMessage.ts +2 -1
  60. package/src/OutgoingMessages/SyncStepTwoMessage.ts +2 -1
  61. package/src/OutgoingMessages/UpdateMessage.ts +2 -1
  62. package/src/index.ts +0 -2
  63. package/src/types.ts +12 -12
  64. package/dist/packages/provider/src/TiptapCollabProvider.d.ts +0 -161
  65. package/dist/packages/provider/src/TiptapCollabProviderWebsocket.d.ts +0 -19
  66. package/dist/packages/server/src/Debugger.d.ts +0 -14
  67. package/dist/tests/server/getMessageLogs.d.ts +0 -1
  68. package/dist/tests/server/requiresAuthentication.d.ts +0 -1
  69. package/src/TiptapCollabProvider.ts +0 -505
  70. package/src/TiptapCollabProviderWebsocket.ts +0 -38
@@ -1,13 +1,10 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var common = require('@hocuspocus/common');
6
4
  var Y = require('yjs');
7
5
  var attempt = require('@lifeomic/attempt');
8
6
 
9
- function _interopNamespace(e) {
10
- if (e && e.__esModule) return e;
7
+ function _interopNamespaceDefault(e) {
11
8
  var n = Object.create(null);
12
9
  if (e) {
13
10
  Object.keys(e).forEach(function (k) {
@@ -20,51 +17,51 @@ function _interopNamespace(e) {
20
17
  }
21
18
  });
22
19
  }
23
- n["default"] = e;
20
+ n.default = e;
24
21
  return Object.freeze(n);
25
22
  }
26
23
 
27
- var Y__namespace = /*#__PURE__*/_interopNamespace(Y);
24
+ var Y__namespace = /*#__PURE__*/_interopNamespaceDefault(Y);
28
25
 
29
26
  /**
30
- * Utility module to work with key-value stores.
27
+ * Common Math expressions.
31
28
  *
32
- * @module map
29
+ * @module math
33
30
  */
34
31
 
32
+ const floor = Math.floor;
33
+
35
34
  /**
36
- * Creates a new Map instance.
37
- *
38
35
  * @function
39
- * @return {Map<any, any>}
40
- *
36
+ * @param {number} a
37
+ * @param {number} b
38
+ * @return {number} The smaller element of a and b
39
+ */
40
+ const min = (a, b) => a < b ? a : b;
41
+
42
+ /**
41
43
  * @function
44
+ * @param {number} a
45
+ * @param {number} b
46
+ * @return {number} The bigger element of a and b
42
47
  */
43
- const create$2 = () => new Map();
48
+ const max = (a, b) => a > b ? a : b;
49
+
50
+ /* eslint-env browser */
51
+
52
+ const BIT7 = 64;
53
+ const BIT8 = 128;
54
+ const BITS6 = 63;
55
+ const BITS7 = 127;
44
56
 
45
57
  /**
46
- * Get map property. Create T if property is undefined and set T on map.
47
- *
48
- * ```js
49
- * const listeners = map.setIfUndefined(events, 'eventName', set.create)
50
- * listeners.add(listener)
51
- * ```
58
+ * Utility helpers for working with numbers.
52
59
  *
53
- * @function
54
- * @template V,K
55
- * @template {Map<K,V>} MAP
56
- * @param {MAP} map
57
- * @param {K} key
58
- * @param {function():V} createT
59
- * @return {V}
60
+ * @module number
60
61
  */
61
- const setIfUndefined = (map, key, createT) => {
62
- let set = map.get(key);
63
- if (set === undefined) {
64
- map.set(key, set = createT());
65
- }
66
- return set
67
- };
62
+
63
+
64
+ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
68
65
 
69
66
  /**
70
67
  * Utility module to work with sets.
@@ -72,7 +69,7 @@ const setIfUndefined = (map, key, createT) => {
72
69
  * @module set
73
70
  */
74
71
 
75
- const create$1 = () => new Set();
72
+ const create$2 = () => new Set();
76
73
 
77
74
  /**
78
75
  * Utility module to work with Arrays.
@@ -80,6 +77,7 @@ const create$1 = () => new Set();
80
77
  * @module array
81
78
  */
82
79
 
80
+
83
81
  /**
84
82
  * Transforms something array-like to an actual Array.
85
83
  *
@@ -166,387 +164,228 @@ if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) {
166
164
  }
167
165
 
168
166
  /**
169
- * Often used conditions.
167
+ * Efficient schema-less binary encoding with support for variable length encoding.
170
168
  *
171
- * @module conditions
172
- */
173
-
174
- /**
175
- * @template T
176
- * @param {T|null|undefined} v
177
- * @return {T|null}
169
+ * Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
170
+ *
171
+ * Encodes numbers in little-endian order (least to most significant byte order)
172
+ * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
173
+ * which is also used in Protocol Buffers.
174
+ *
175
+ * ```js
176
+ * // encoding step
177
+ * const encoder = encoding.createEncoder()
178
+ * encoding.writeVarUint(encoder, 256)
179
+ * encoding.writeVarString(encoder, 'Hello world!')
180
+ * const buf = encoding.toUint8Array(encoder)
181
+ * ```
182
+ *
183
+ * ```js
184
+ * // decoding step
185
+ * const decoder = decoding.createDecoder(buf)
186
+ * decoding.readVarUint(decoder) // => 256
187
+ * decoding.readVarString(decoder) // => 'Hello world!'
188
+ * decoding.hasContent(decoder) // => false - all data is read
189
+ * ```
190
+ *
191
+ * @module encoding
178
192
  */
179
- /* c8 ignore next */
180
- const undefinedToNull = v => v === undefined ? null : v;
181
193
 
182
- /* eslint-env browser */
183
194
 
184
195
  /**
185
- * Isomorphic variable storage.
186
- *
187
- * Uses LocalStorage in the browser and falls back to in-memory storage.
188
- *
189
- * @module storage
196
+ * A BinaryEncoder handles the encoding to an Uint8Array.
190
197
  */
191
-
192
- /* c8 ignore start */
193
- class VarStoragePolyfill {
198
+ class Encoder {
194
199
  constructor () {
195
- this.map = new Map();
196
- }
197
-
198
- /**
199
- * @param {string} key
200
- * @param {any} newValue
201
- */
202
- setItem (key, newValue) {
203
- this.map.set(key, newValue);
204
- }
205
-
206
- /**
207
- * @param {string} key
208
- */
209
- getItem (key) {
210
- return this.map.get(key)
200
+ this.cpos = 0;
201
+ this.cbuf = new Uint8Array(100);
202
+ /**
203
+ * @type {Array<Uint8Array>}
204
+ */
205
+ this.bufs = [];
211
206
  }
212
207
  }
213
- /* c8 ignore stop */
214
208
 
215
209
  /**
216
- * @type {any}
210
+ * @function
211
+ * @return {Encoder}
217
212
  */
218
- let _localStorage = new VarStoragePolyfill();
219
- let usePolyfill = true;
220
-
221
- /* c8 ignore start */
222
- try {
223
- // if the same-origin rule is violated, accessing localStorage might thrown an error
224
- if (typeof localStorage !== 'undefined' && localStorage) {
225
- _localStorage = localStorage;
226
- usePolyfill = false;
227
- }
228
- } catch (e) { }
229
- /* c8 ignore stop */
213
+ const createEncoder = () => new Encoder();
230
214
 
231
215
  /**
232
- * This is basically localStorage in browser, or a polyfill in nodejs
216
+ * The current length of the encoded data.
217
+ *
218
+ * @function
219
+ * @param {Encoder} encoder
220
+ * @return {number}
233
221
  */
234
- /* c8 ignore next */
235
- const varStorage = _localStorage;
222
+ const length$1 = encoder => {
223
+ let len = encoder.cpos;
224
+ for (let i = 0; i < encoder.bufs.length; i++) {
225
+ len += encoder.bufs[i].length;
226
+ }
227
+ return len
228
+ };
236
229
 
237
230
  /**
238
- * A polyfill for `addEventListener('storage', event => {..})` that does nothing if the polyfill is being used.
231
+ * Transform to Uint8Array.
239
232
  *
240
- * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler
241
233
  * @function
234
+ * @param {Encoder} encoder
235
+ * @return {Uint8Array} The created ArrayBuffer.
242
236
  */
243
- /* c8 ignore next */
244
- const onChange = eventHandler => usePolyfill || addEventListener('storage', /** @type {any} */ (eventHandler));
237
+ const toUint8Array = encoder => {
238
+ const uint8arr = new Uint8Array(length$1(encoder));
239
+ let curPos = 0;
240
+ for (let i = 0; i < encoder.bufs.length; i++) {
241
+ const d = encoder.bufs[i];
242
+ uint8arr.set(d, curPos);
243
+ curPos += d.length;
244
+ }
245
+ uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
246
+ return uint8arr
247
+ };
245
248
 
246
249
  /**
247
- * A polyfill for `removeEventListener('storage', event => {..})` that does nothing if the polyfill is being used.
250
+ * Write one byte to the encoder.
248
251
  *
249
- * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler
250
252
  * @function
253
+ * @param {Encoder} encoder
254
+ * @param {number} num The byte that is to be encoded.
251
255
  */
252
- /* c8 ignore next */
253
- const offChange = eventHandler => usePolyfill || removeEventListener('storage', /** @type {any} */ (eventHandler));
256
+ const write = (encoder, num) => {
257
+ const bufferLen = encoder.cbuf.length;
258
+ if (encoder.cpos === bufferLen) {
259
+ encoder.bufs.push(encoder.cbuf);
260
+ encoder.cbuf = new Uint8Array(bufferLen * 2);
261
+ encoder.cpos = 0;
262
+ }
263
+ encoder.cbuf[encoder.cpos++] = num;
264
+ };
254
265
 
255
266
  /**
256
- * Utility functions for working with EcmaScript objects.
267
+ * Write a variable length unsigned integer. Max encodable integer is 2^53.
257
268
  *
258
- * @module object
269
+ * @function
270
+ * @param {Encoder} encoder
271
+ * @param {number} num The number that is to be encoded.
259
272
  */
273
+ const writeVarUint = (encoder, num) => {
274
+ while (num > BITS7) {
275
+ write(encoder, BIT8 | (BITS7 & num));
276
+ num = floor(num / 128); // shift >>> 7
277
+ }
278
+ write(encoder, BITS7 & num);
279
+ };
260
280
 
261
281
  /**
262
- * @param {Object<string,any>} obj
282
+ * A cache to store strings temporarily
263
283
  */
264
- const keys = Object.keys;
284
+ const _strBuffer = new Uint8Array(30000);
285
+ const _maxStrBSize = _strBuffer.length / 3;
265
286
 
266
287
  /**
267
- * @todo implement mapToArray & map
288
+ * Write a variable length string.
268
289
  *
269
- * @template R
270
- * @param {Object<string,any>} obj
271
- * @param {function(any,string):R} f
272
- * @return {Array<R>}
290
+ * @function
291
+ * @param {Encoder} encoder
292
+ * @param {String} str The string that is to be encoded.
273
293
  */
274
- const map = (obj, f) => {
275
- const results = [];
276
- for (const key in obj) {
277
- results.push(f(obj[key], key));
294
+ const _writeVarStringNative = (encoder, str) => {
295
+ if (str.length < _maxStrBSize) {
296
+ // We can encode the string into the existing buffer
297
+ /* c8 ignore next */
298
+ const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
299
+ writeVarUint(encoder, written);
300
+ for (let i = 0; i < written; i++) {
301
+ write(encoder, _strBuffer[i]);
302
+ }
303
+ } else {
304
+ writeVarUint8Array(encoder, encodeUtf8(str));
278
305
  }
279
- return results
280
306
  };
281
307
 
282
308
  /**
283
- * @param {Object<string,any>} obj
284
- * @return {number}
285
- */
286
- const length$1 = obj => keys(obj).length;
287
-
288
- /**
289
- * Calls `Object.prototype.hasOwnProperty`.
309
+ * Write a variable length string.
290
310
  *
291
- * @param {any} obj
292
- * @param {string|symbol} key
293
- * @return {boolean}
311
+ * @function
312
+ * @param {Encoder} encoder
313
+ * @param {String} str The string that is to be encoded.
294
314
  */
295
- const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
315
+ const _writeVarStringPolyfill = (encoder, str) => {
316
+ const encodedString = unescape(encodeURIComponent(str));
317
+ const len = encodedString.length;
318
+ writeVarUint(encoder, len);
319
+ for (let i = 0; i < len; i++) {
320
+ write(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
321
+ }
322
+ };
296
323
 
297
324
  /**
298
- * Common functions and function call helpers.
325
+ * Write a variable length string.
299
326
  *
300
- * @module function
327
+ * @function
328
+ * @param {Encoder} encoder
329
+ * @param {String} str The string that is to be encoded.
301
330
  */
331
+ /* c8 ignore next */
332
+ const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill;
302
333
 
303
334
  /**
304
- * @template T
335
+ * Append fixed-length Uint8Array to the encoder.
305
336
  *
306
- * @param {T} a
307
- * @param {T} b
308
- * @return {boolean}
309
- */
310
- const equalityStrict = (a, b) => a === b;
311
-
312
- /* c8 ignore start */
313
-
314
- /**
315
- * @param {any} a
316
- * @param {any} b
317
- * @return {boolean}
337
+ * @function
338
+ * @param {Encoder} encoder
339
+ * @param {Uint8Array} uint8Array
318
340
  */
319
- const equalityDeep = (a, b) => {
320
- if (a == null || b == null) {
321
- return equalityStrict(a, b)
322
- }
323
- if (a.constructor !== b.constructor) {
324
- return false
325
- }
326
- if (a === b) {
327
- return true
328
- }
329
- switch (a.constructor) {
330
- case ArrayBuffer:
331
- a = new Uint8Array(a);
332
- b = new Uint8Array(b);
333
- // eslint-disable-next-line no-fallthrough
334
- case Uint8Array: {
335
- if (a.byteLength !== b.byteLength) {
336
- return false
337
- }
338
- for (let i = 0; i < a.length; i++) {
339
- if (a[i] !== b[i]) {
340
- return false
341
- }
342
- }
343
- break
344
- }
345
- case Set: {
346
- if (a.size !== b.size) {
347
- return false
348
- }
349
- for (const value of a) {
350
- if (!b.has(value)) {
351
- return false
352
- }
353
- }
354
- break
355
- }
356
- case Map: {
357
- if (a.size !== b.size) {
358
- return false
359
- }
360
- for (const key of a.keys()) {
361
- if (!b.has(key) || !equalityDeep(a.get(key), b.get(key))) {
362
- return false
363
- }
364
- }
365
- break
366
- }
367
- case Object:
368
- if (length$1(a) !== length$1(b)) {
369
- return false
370
- }
371
- for (const key in a) {
372
- if (!hasProperty(a, key) || !equalityDeep(a[key], b[key])) {
373
- return false
374
- }
375
- }
376
- break
377
- case Array:
378
- if (a.length !== b.length) {
379
- return false
380
- }
381
- for (let i = 0; i < a.length; i++) {
382
- if (!equalityDeep(a[i], b[i])) {
383
- return false
384
- }
385
- }
386
- break
387
- default:
388
- return false
341
+ const writeUint8Array = (encoder, uint8Array) => {
342
+ const bufferLen = encoder.cbuf.length;
343
+ const cpos = encoder.cpos;
344
+ const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
345
+ const rightCopyLen = uint8Array.length - leftCopyLen;
346
+ encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
347
+ encoder.cpos += leftCopyLen;
348
+ if (rightCopyLen > 0) {
349
+ // Still something to write, write right half..
350
+ // Append new buffer
351
+ encoder.bufs.push(encoder.cbuf);
352
+ // must have at least size of remaining buffer
353
+ encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
354
+ // copy array
355
+ encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
356
+ encoder.cpos = rightCopyLen;
389
357
  }
390
- return true
391
358
  };
392
359
 
393
360
  /**
394
- * @template V
395
- * @template {V} OPTS
396
- *
397
- * @param {V} value
398
- * @param {Array<OPTS>} options
399
- */
400
- // @ts-ignore
401
- const isOneOf = (value, options) => options.includes(value);
402
-
403
- /**
404
- * Isomorphic module to work access the environment (query params, env variables).
361
+ * Append an Uint8Array to Encoder.
405
362
  *
406
- * @module map
407
- */
408
-
409
- /* c8 ignore next 2 */
410
- // @ts-ignore
411
- const isNode = typeof process !== 'undefined' && process.release && /node|io\.js/.test(process.release.name) && Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
412
-
413
- /* c8 ignore next */
414
- const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && !isNode;
415
- /* c8 ignore next 3 */
416
- typeof navigator !== 'undefined'
417
- ? /Mac/.test(navigator.platform)
418
- : false;
419
-
420
- /**
421
- * @type {Map<string,string>}
363
+ * @function
364
+ * @param {Encoder} encoder
365
+ * @param {Uint8Array} uint8Array
422
366
  */
423
- let params;
424
-
425
- /* c8 ignore start */
426
- const computeParams = () => {
427
- if (params === undefined) {
428
- if (isNode) {
429
- params = create$2();
430
- const pargs = process.argv;
431
- let currParamName = null;
432
- for (let i = 0; i < pargs.length; i++) {
433
- const parg = pargs[i];
434
- if (parg[0] === '-') {
435
- if (currParamName !== null) {
436
- params.set(currParamName, '');
437
- }
438
- currParamName = parg;
439
- } else {
440
- if (currParamName !== null) {
441
- params.set(currParamName, parg);
442
- currParamName = null;
443
- }
444
- }
445
- }
446
- if (currParamName !== null) {
447
- params.set(currParamName, '');
448
- }
449
- // in ReactNative for example this would not be true (unless connected to the Remote Debugger)
450
- } else if (typeof location === 'object') {
451
- params = create$2(); // eslint-disable-next-line no-undef
452
- (location.search || '?').slice(1).split('&').forEach((kv) => {
453
- if (kv.length !== 0) {
454
- const [key, value] = kv.split('=');
455
- params.set(`--${fromCamelCase(key, '-')}`, value);
456
- params.set(`-${fromCamelCase(key, '-')}`, value);
457
- }
458
- });
459
- } else {
460
- params = create$2();
461
- }
462
- }
463
- return params
367
+ const writeVarUint8Array = (encoder, uint8Array) => {
368
+ writeVarUint(encoder, uint8Array.byteLength);
369
+ writeUint8Array(encoder, uint8Array);
464
370
  };
465
- /* c8 ignore stop */
466
-
467
- /**
468
- * @param {string} name
469
- * @return {boolean}
470
- */
471
- /* c8 ignore next */
472
- const hasParam = (name) => computeParams().has(name);
473
-
474
- /**
475
- * @param {string} name
476
- * @return {string|null}
477
- */
478
- /* c8 ignore next 4 */
479
- const getVariable = (name) =>
480
- isNode
481
- ? undefinedToNull(process.env[name.toUpperCase()])
482
- : undefinedToNull(varStorage.getItem(name));
483
-
484
- /**
485
- * @param {string} name
486
- * @return {boolean}
487
- */
488
- /* c8 ignore next 2 */
489
- const hasConf = (name) =>
490
- hasParam('--' + name) || getVariable(name) !== null;
491
-
492
- /* c8 ignore next */
493
- hasConf('production');
494
-
495
- /* c8 ignore next 2 */
496
- const forceColor = isNode &&
497
- isOneOf(process.env.FORCE_COLOR, ['true', '1', '2']);
498
-
499
- /* c8 ignore start */
500
- !hasParam('no-colors') &&
501
- (!isNode || process.stdout.isTTY || forceColor) && (
502
- !isNode || hasParam('color') || forceColor ||
503
- getVariable('COLORTERM') !== null ||
504
- (getVariable('TERM') || '').includes('color')
505
- );
506
- /* c8 ignore stop */
507
371
 
508
372
  /**
509
- * Common Math expressions.
373
+ * Error helpers.
510
374
  *
511
- * @module math
512
- */
513
-
514
- const floor = Math.floor;
515
-
516
- /**
517
- * @function
518
- * @param {number} a
519
- * @param {number} b
520
- * @return {number} The smaller element of a and b
521
- */
522
- const min = (a, b) => a < b ? a : b;
523
-
524
- /**
525
- * @function
526
- * @param {number} a
527
- * @param {number} b
528
- * @return {number} The bigger element of a and b
375
+ * @module error
529
376
  */
530
- const max = (a, b) => a > b ? a : b;
531
-
532
- /* eslint-env browser */
533
- const BIT7 = 64;
534
- const BIT8 = 128;
535
- const BITS6 = 63;
536
- const BITS7 = 127;
537
377
 
538
378
  /**
539
- * Utility helpers for working with numbers.
540
- *
541
- * @module number
379
+ * @param {string} s
380
+ * @return {Error}
542
381
  */
543
-
544
- const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
382
+ /* c8 ignore next */
383
+ const create$1 = s => new Error(s);
545
384
 
546
385
  /**
547
- * Efficient schema-less binary encoding with support for variable length encoding.
386
+ * Efficient schema-less binary decoding with support for variable length encoding.
548
387
  *
549
- * Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
388
+ * Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
550
389
  *
551
390
  * Encodes numbers in little-endian order (least to most significant byte order)
552
391
  * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
@@ -568,232 +407,15 @@ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
568
407
  * decoding.hasContent(decoder) // => false - all data is read
569
408
  * ```
570
409
  *
571
- * @module encoding
410
+ * @module decoding
572
411
  */
573
412
 
413
+
414
+ const errorUnexpectedEndOfArray = create$1('Unexpected end of array');
415
+ const errorIntegerOutOfRange = create$1('Integer out of Range');
416
+
574
417
  /**
575
- * A BinaryEncoder handles the encoding to an Uint8Array.
576
- */
577
- class Encoder {
578
- constructor () {
579
- this.cpos = 0;
580
- this.cbuf = new Uint8Array(100);
581
- /**
582
- * @type {Array<Uint8Array>}
583
- */
584
- this.bufs = [];
585
- }
586
- }
587
-
588
- /**
589
- * @function
590
- * @return {Encoder}
591
- */
592
- const createEncoder = () => new Encoder();
593
-
594
- /**
595
- * The current length of the encoded data.
596
- *
597
- * @function
598
- * @param {Encoder} encoder
599
- * @return {number}
600
- */
601
- const length = encoder => {
602
- let len = encoder.cpos;
603
- for (let i = 0; i < encoder.bufs.length; i++) {
604
- len += encoder.bufs[i].length;
605
- }
606
- return len
607
- };
608
-
609
- /**
610
- * Transform to Uint8Array.
611
- *
612
- * @function
613
- * @param {Encoder} encoder
614
- * @return {Uint8Array} The created ArrayBuffer.
615
- */
616
- const toUint8Array = encoder => {
617
- const uint8arr = new Uint8Array(length(encoder));
618
- let curPos = 0;
619
- for (let i = 0; i < encoder.bufs.length; i++) {
620
- const d = encoder.bufs[i];
621
- uint8arr.set(d, curPos);
622
- curPos += d.length;
623
- }
624
- uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
625
- return uint8arr
626
- };
627
-
628
- /**
629
- * Write one byte to the encoder.
630
- *
631
- * @function
632
- * @param {Encoder} encoder
633
- * @param {number} num The byte that is to be encoded.
634
- */
635
- const write = (encoder, num) => {
636
- const bufferLen = encoder.cbuf.length;
637
- if (encoder.cpos === bufferLen) {
638
- encoder.bufs.push(encoder.cbuf);
639
- encoder.cbuf = new Uint8Array(bufferLen * 2);
640
- encoder.cpos = 0;
641
- }
642
- encoder.cbuf[encoder.cpos++] = num;
643
- };
644
-
645
- /**
646
- * Write a variable length unsigned integer. Max encodable integer is 2^53.
647
- *
648
- * @function
649
- * @param {Encoder} encoder
650
- * @param {number} num The number that is to be encoded.
651
- */
652
- const writeVarUint = (encoder, num) => {
653
- while (num > BITS7) {
654
- write(encoder, BIT8 | (BITS7 & num));
655
- num = floor(num / 128); // shift >>> 7
656
- }
657
- write(encoder, BITS7 & num);
658
- };
659
-
660
- /**
661
- * A cache to store strings temporarily
662
- */
663
- const _strBuffer = new Uint8Array(30000);
664
- const _maxStrBSize = _strBuffer.length / 3;
665
-
666
- /**
667
- * Write a variable length string.
668
- *
669
- * @function
670
- * @param {Encoder} encoder
671
- * @param {String} str The string that is to be encoded.
672
- */
673
- const _writeVarStringNative = (encoder, str) => {
674
- if (str.length < _maxStrBSize) {
675
- // We can encode the string into the existing buffer
676
- /* c8 ignore next */
677
- const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
678
- writeVarUint(encoder, written);
679
- for (let i = 0; i < written; i++) {
680
- write(encoder, _strBuffer[i]);
681
- }
682
- } else {
683
- writeVarUint8Array(encoder, encodeUtf8(str));
684
- }
685
- };
686
-
687
- /**
688
- * Write a variable length string.
689
- *
690
- * @function
691
- * @param {Encoder} encoder
692
- * @param {String} str The string that is to be encoded.
693
- */
694
- const _writeVarStringPolyfill = (encoder, str) => {
695
- const encodedString = unescape(encodeURIComponent(str));
696
- const len = encodedString.length;
697
- writeVarUint(encoder, len);
698
- for (let i = 0; i < len; i++) {
699
- write(encoder, /** @type {number} */ (encodedString.codePointAt(i)));
700
- }
701
- };
702
-
703
- /**
704
- * Write a variable length string.
705
- *
706
- * @function
707
- * @param {Encoder} encoder
708
- * @param {String} str The string that is to be encoded.
709
- */
710
- /* c8 ignore next */
711
- const writeVarString = (utf8TextEncoder && /** @type {any} */ (utf8TextEncoder).encodeInto) ? _writeVarStringNative : _writeVarStringPolyfill;
712
-
713
- /**
714
- * Append fixed-length Uint8Array to the encoder.
715
- *
716
- * @function
717
- * @param {Encoder} encoder
718
- * @param {Uint8Array} uint8Array
719
- */
720
- const writeUint8Array = (encoder, uint8Array) => {
721
- const bufferLen = encoder.cbuf.length;
722
- const cpos = encoder.cpos;
723
- const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
724
- const rightCopyLen = uint8Array.length - leftCopyLen;
725
- encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
726
- encoder.cpos += leftCopyLen;
727
- if (rightCopyLen > 0) {
728
- // Still something to write, write right half..
729
- // Append new buffer
730
- encoder.bufs.push(encoder.cbuf);
731
- // must have at least size of remaining buffer
732
- encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
733
- // copy array
734
- encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
735
- encoder.cpos = rightCopyLen;
736
- }
737
- };
738
-
739
- /**
740
- * Append an Uint8Array to Encoder.
741
- *
742
- * @function
743
- * @param {Encoder} encoder
744
- * @param {Uint8Array} uint8Array
745
- */
746
- const writeVarUint8Array = (encoder, uint8Array) => {
747
- writeVarUint(encoder, uint8Array.byteLength);
748
- writeUint8Array(encoder, uint8Array);
749
- };
750
-
751
- /**
752
- * Error helpers.
753
- *
754
- * @module error
755
- */
756
-
757
- /**
758
- * @param {string} s
759
- * @return {Error}
760
- */
761
- /* c8 ignore next */
762
- const create = s => new Error(s);
763
-
764
- /**
765
- * Efficient schema-less binary decoding with support for variable length encoding.
766
- *
767
- * Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
768
- *
769
- * Encodes numbers in little-endian order (least to most significant byte order)
770
- * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
771
- * which is also used in Protocol Buffers.
772
- *
773
- * ```js
774
- * // encoding step
775
- * const encoder = encoding.createEncoder()
776
- * encoding.writeVarUint(encoder, 256)
777
- * encoding.writeVarString(encoder, 'Hello world!')
778
- * const buf = encoding.toUint8Array(encoder)
779
- * ```
780
- *
781
- * ```js
782
- * // decoding step
783
- * const decoder = decoding.createDecoder(buf)
784
- * decoding.readVarUint(decoder) // => 256
785
- * decoding.readVarString(decoder) // => 'Hello world!'
786
- * decoding.hasContent(decoder) // => false - all data is read
787
- * ```
788
- *
789
- * @module decoding
790
- */
791
-
792
- const errorUnexpectedEndOfArray = create('Unexpected end of array');
793
- const errorIntegerOutOfRange = create('Integer out of Range');
794
-
795
- /**
796
- * A Decoder handles the decoding of an Uint8Array.
418
+ * A Decoder handles the decoding of an Uint8Array.
797
419
  */
798
420
  class Decoder {
799
421
  /**
@@ -1003,255 +625,66 @@ const peekVarString = decoder => {
1003
625
  };
1004
626
 
1005
627
  /**
1006
- * Utility functions to work with buffers (Uint8Array).
628
+ * Utility module to work with time.
1007
629
  *
1008
- * @module buffer
630
+ * @module time
1009
631
  */
1010
632
 
1011
- /**
1012
- * @param {number} len
1013
- */
1014
- const createUint8ArrayFromLen = len => new Uint8Array(len);
1015
633
 
1016
634
  /**
1017
- * Create Uint8Array with initial content from buffer
635
+ * Return current unix time.
1018
636
  *
1019
- * @param {ArrayBuffer} buffer
1020
- * @param {number} byteOffset
1021
- * @param {number} length
637
+ * @return {number}
1022
638
  */
1023
- const createUint8ArrayViewFromArrayBuffer = (buffer, byteOffset, length) => new Uint8Array(buffer, byteOffset, length);
639
+ const getUnixTime = Date.now;
1024
640
 
1025
641
  /**
1026
- * Create Uint8Array with initial content from buffer
642
+ * Utility module to work with key-value stores.
1027
643
  *
1028
- * @param {ArrayBuffer} buffer
644
+ * @module map
1029
645
  */
1030
- const createUint8ArrayFromArrayBuffer = buffer => new Uint8Array(buffer);
1031
646
 
1032
- /* c8 ignore start */
1033
647
  /**
1034
- * @param {Uint8Array} bytes
1035
- * @return {string}
648
+ * Creates a new Map instance.
649
+ *
650
+ * @function
651
+ * @return {Map<any, any>}
652
+ *
653
+ * @function
1036
654
  */
1037
- const toBase64Browser = bytes => {
1038
- let s = '';
1039
- for (let i = 0; i < bytes.byteLength; i++) {
1040
- s += fromCharCode(bytes[i]);
1041
- }
1042
- // eslint-disable-next-line no-undef
1043
- return btoa(s)
1044
- };
1045
- /* c8 ignore stop */
655
+ const create = () => new Map();
1046
656
 
1047
657
  /**
1048
- * @param {Uint8Array} bytes
1049
- * @return {string}
1050
- */
1051
- const toBase64Node = bytes => Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64');
1052
-
1053
- /* c8 ignore start */
1054
- /**
1055
- * @param {string} s
1056
- * @return {Uint8Array}
1057
- */
1058
- const fromBase64Browser = s => {
1059
- // eslint-disable-next-line no-undef
1060
- const a = atob(s);
1061
- const bytes = createUint8ArrayFromLen(a.length);
1062
- for (let i = 0; i < a.length; i++) {
1063
- bytes[i] = a.charCodeAt(i);
1064
- }
1065
- return bytes
1066
- };
1067
- /* c8 ignore stop */
1068
-
1069
- /**
1070
- * @param {string} s
1071
- */
1072
- const fromBase64Node = s => {
1073
- const buf = Buffer.from(s, 'base64');
1074
- return createUint8ArrayViewFromArrayBuffer(buf.buffer, buf.byteOffset, buf.byteLength)
1075
- };
1076
-
1077
- /* c8 ignore next */
1078
- const toBase64 = isBrowser ? toBase64Browser : toBase64Node;
1079
-
1080
- /* c8 ignore next */
1081
- const fromBase64 = isBrowser ? fromBase64Browser : fromBase64Node;
1082
-
1083
- /* eslint-env browser */
1084
-
1085
- /**
1086
- * @typedef {Object} Channel
1087
- * @property {Set<function(any, any):any>} Channel.subs
1088
- * @property {any} Channel.bc
1089
- */
1090
-
1091
- /**
1092
- * @type {Map<string, Channel>}
1093
- */
1094
- const channels = new Map();
1095
-
1096
- /* c8 ignore start */
1097
- class LocalStoragePolyfill {
1098
- /**
1099
- * @param {string} room
1100
- */
1101
- constructor (room) {
1102
- this.room = room;
1103
- /**
1104
- * @type {null|function({data:ArrayBuffer}):void}
1105
- */
1106
- this.onmessage = null;
1107
- /**
1108
- * @param {any} e
1109
- */
1110
- this._onChange = e => e.key === room && this.onmessage !== null && this.onmessage({ data: fromBase64(e.newValue || '') });
1111
- onChange(this._onChange);
1112
- }
1113
-
1114
- /**
1115
- * @param {ArrayBuffer} buf
1116
- */
1117
- postMessage (buf) {
1118
- varStorage.setItem(this.room, toBase64(createUint8ArrayFromArrayBuffer(buf)));
1119
- }
1120
-
1121
- close () {
1122
- offChange(this._onChange);
1123
- }
1124
- }
1125
- /* c8 ignore stop */
1126
-
1127
- // Use BroadcastChannel or Polyfill
1128
- /* c8 ignore next */
1129
- const BC = typeof BroadcastChannel === 'undefined' ? LocalStoragePolyfill : BroadcastChannel;
1130
-
1131
- /**
1132
- * @param {string} room
1133
- * @return {Channel}
1134
- */
1135
- const getChannel = room =>
1136
- setIfUndefined(channels, room, () => {
1137
- const subs = create$1();
1138
- const bc = new BC(room);
1139
- /**
1140
- * @param {{data:ArrayBuffer}} e
1141
- */
1142
- /* c8 ignore next */
1143
- bc.onmessage = e => subs.forEach(sub => sub(e.data, 'broadcastchannel'));
1144
- return {
1145
- bc, subs
1146
- }
1147
- });
1148
-
1149
- /**
1150
- * Subscribe to global `publish` events.
1151
- *
1152
- * @function
1153
- * @param {string} room
1154
- * @param {function(any, any):any} f
1155
- */
1156
- const subscribe = (room, f) => {
1157
- getChannel(room).subs.add(f);
1158
- return f
1159
- };
1160
-
1161
- /**
1162
- * Unsubscribe from `publish` global events.
1163
- *
1164
- * @function
1165
- * @param {string} room
1166
- * @param {function(any, any):any} f
1167
- */
1168
- const unsubscribe = (room, f) => {
1169
- const channel = getChannel(room);
1170
- const unsubscribed = channel.subs.delete(f);
1171
- if (unsubscribed && channel.subs.size === 0) {
1172
- channel.bc.close();
1173
- channels.delete(room);
1174
- }
1175
- return unsubscribed
1176
- };
1177
-
1178
- /**
1179
- * Publish data to all subscribers (including subscribers on this tab)
1180
- *
1181
- * @function
1182
- * @param {string} room
1183
- * @param {any} data
1184
- * @param {any} [origin]
1185
- */
1186
- const publish = (room, data, origin = null) => {
1187
- const c = getChannel(room);
1188
- c.bc.postMessage(data);
1189
- c.subs.forEach(sub => sub(data, origin));
1190
- };
1191
-
1192
- /**
1193
- * Mutual exclude for JavaScript.
1194
- *
1195
- * @module mutex
1196
- */
1197
-
1198
- /**
1199
- * @callback mutex
1200
- * @param {function():void} cb Only executed when this mutex is not in the current stack
1201
- * @param {function():void} [elseCb] Executed when this mutex is in the current stack
1202
- */
1203
-
1204
- /**
1205
- * Creates a mutual exclude function with the following property:
658
+ * Get map property. Create T if property is undefined and set T on map.
1206
659
  *
1207
660
  * ```js
1208
- * const mutex = createMutex()
1209
- * mutex(() => {
1210
- * // This function is immediately executed
1211
- * mutex(() => {
1212
- * // This function is not executed, as the mutex is already active.
1213
- * })
1214
- * })
661
+ * const listeners = map.setIfUndefined(events, 'eventName', set.create)
662
+ * listeners.add(listener)
1215
663
  * ```
1216
664
  *
1217
- * @return {mutex} A mutual exclude function
1218
- * @public
1219
- */
1220
- const createMutex = () => {
1221
- let token = true;
1222
- return (f, g) => {
1223
- if (token) {
1224
- token = false;
1225
- try {
1226
- f();
1227
- } finally {
1228
- token = true;
1229
- }
1230
- } else if (g !== undefined) {
1231
- g();
1232
- }
665
+ * @function
666
+ * @template {Map<any, any>} MAP
667
+ * @template {MAP extends Map<any,infer V> ? function():V : unknown} CF
668
+ * @param {MAP} map
669
+ * @param {MAP extends Map<infer K,any> ? K : unknown} key
670
+ * @param {CF} createT
671
+ * @return {ReturnType<CF>}
672
+ */
673
+ const setIfUndefined = (map, key, createT) => {
674
+ let set = map.get(key);
675
+ if (set === undefined) {
676
+ map.set(key, set = createT());
1233
677
  }
678
+ return set
1234
679
  };
1235
680
 
1236
- /**
1237
- * Utility module to work with time.
1238
- *
1239
- * @module time
1240
- */
1241
-
1242
- /**
1243
- * Return current unix time.
1244
- *
1245
- * @return {number}
1246
- */
1247
- const getUnixTime = Date.now;
1248
-
1249
681
  /**
1250
682
  * Observable class prototype.
1251
683
  *
1252
684
  * @module observable
1253
685
  */
1254
686
 
687
+
1255
688
  /* c8 ignore start */
1256
689
  /**
1257
690
  * Handles named events.
@@ -1265,7 +698,7 @@ class Observable {
1265
698
  * Some desc.
1266
699
  * @type {Map<N, any>}
1267
700
  */
1268
- this._observers = create$2();
701
+ this._observers = create();
1269
702
  }
1270
703
 
1271
704
  /**
@@ -1273,7 +706,7 @@ class Observable {
1273
706
  * @param {function} f
1274
707
  */
1275
708
  on (name, f) {
1276
- setIfUndefined(this._observers, name, create$1).add(f);
709
+ setIfUndefined(this._observers, name, create$2).add(f);
1277
710
  }
1278
711
 
1279
712
  /**
@@ -1316,105 +749,257 @@ class Observable {
1316
749
  */
1317
750
  emit (name, args) {
1318
751
  // 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.
1319
- return from((this._observers.get(name) || create$2()).values()).forEach(f => f(...args))
752
+ return from((this._observers.get(name) || create()).values()).forEach(f => f(...args))
1320
753
  }
1321
754
 
1322
755
  destroy () {
1323
- this._observers = create$2();
756
+ this._observers = create();
1324
757
  }
1325
758
  }
1326
759
  /* c8 ignore end */
1327
760
 
1328
761
  /**
1329
- * @module awareness-protocol
762
+ * Utility functions for working with EcmaScript objects.
763
+ *
764
+ * @module object
1330
765
  */
1331
766
 
1332
- const outdatedTimeout = 30000;
1333
767
 
1334
768
  /**
1335
- * @typedef {Object} MetaClientState
1336
- * @property {number} MetaClientState.clock
1337
- * @property {number} MetaClientState.lastUpdated unix timestamp
769
+ * @param {Object<string,any>} obj
1338
770
  */
771
+ const keys = Object.keys;
1339
772
 
1340
773
  /**
1341
- * The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information
1342
- * (cursor, username, status, ..). Each client can update its own local state and listen to state changes of
1343
- * remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline.
1344
- *
1345
- * Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override
1346
- * its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is
1347
- * applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that
1348
- * a remote client is offline, it may propagate a message with
1349
- * `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a
1350
- * message is received, and the known clock of that client equals the received clock, it will override the state with `null`.
1351
- *
1352
- * Before a client disconnects, it should propagate a `null` state with an updated clock.
1353
- *
1354
- * Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state.
774
+ * @todo implement mapToArray & map
1355
775
  *
1356
- * @extends {Observable<string>}
776
+ * @template R
777
+ * @param {Object<string,any>} obj
778
+ * @param {function(any,string):R} f
779
+ * @return {Array<R>}
1357
780
  */
1358
- class Awareness extends Observable {
1359
- /**
1360
- * @param {Y.Doc} doc
1361
- */
1362
- constructor (doc) {
1363
- super();
1364
- this.doc = doc;
1365
- /**
1366
- * @type {number}
1367
- */
1368
- this.clientID = doc.clientID;
1369
- /**
1370
- * Maps from client id to client state
1371
- * @type {Map<number, Object<string, any>>}
1372
- */
1373
- this.states = new Map();
1374
- /**
1375
- * @type {Map<number, MetaClientState>}
1376
- */
1377
- this.meta = new Map();
1378
- this._checkInterval = /** @type {any} */ (setInterval(() => {
1379
- const now = getUnixTime();
1380
- if (this.getLocalState() !== null && (outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */ (this.meta.get(this.clientID)).lastUpdated)) {
1381
- // renew local clock
1382
- this.setLocalState(this.getLocalState());
1383
- }
1384
- /**
1385
- * @type {Array<number>}
1386
- */
1387
- const remove = [];
1388
- this.meta.forEach((meta, clientid) => {
1389
- if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
1390
- remove.push(clientid);
1391
- }
1392
- });
1393
- if (remove.length > 0) {
1394
- removeAwarenessStates(this, remove, 'timeout');
1395
- }
1396
- }, floor(outdatedTimeout / 10)));
1397
- doc.on('destroy', () => {
1398
- this.destroy();
1399
- });
1400
- this.setLocalState({});
1401
- }
1402
-
1403
- destroy () {
1404
- this.emit('destroy', [this]);
1405
- this.setLocalState(null);
1406
- super.destroy();
1407
- clearInterval(this._checkInterval);
781
+ const map = (obj, f) => {
782
+ const results = [];
783
+ for (const key in obj) {
784
+ results.push(f(obj[key], key));
1408
785
  }
786
+ return results
787
+ };
1409
788
 
1410
- /**
1411
- * @return {Object<string,any>|null}
1412
- */
1413
- getLocalState () {
1414
- return this.states.get(this.clientID) || null
1415
- }
789
+ /**
790
+ * @deprecated use object.size instead
791
+ * @param {Object<string,any>} obj
792
+ * @return {number}
793
+ */
794
+ const length = obj => keys(obj).length;
1416
795
 
1417
- /**
796
+ /**
797
+ * Calls `Object.prototype.hasOwnProperty`.
798
+ *
799
+ * @param {any} obj
800
+ * @param {string|symbol} key
801
+ * @return {boolean}
802
+ */
803
+ const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
804
+
805
+ /**
806
+ * Common functions and function call helpers.
807
+ *
808
+ * @module function
809
+ */
810
+
811
+
812
+ /**
813
+ * @template T
814
+ *
815
+ * @param {T} a
816
+ * @param {T} b
817
+ * @return {boolean}
818
+ */
819
+ const equalityStrict = (a, b) => a === b;
820
+
821
+ /* c8 ignore start */
822
+
823
+ /**
824
+ * @param {any} a
825
+ * @param {any} b
826
+ * @return {boolean}
827
+ */
828
+ const equalityDeep = (a, b) => {
829
+ if (a == null || b == null) {
830
+ return equalityStrict(a, b)
831
+ }
832
+ if (a.constructor !== b.constructor) {
833
+ return false
834
+ }
835
+ if (a === b) {
836
+ return true
837
+ }
838
+ switch (a.constructor) {
839
+ case ArrayBuffer:
840
+ a = new Uint8Array(a);
841
+ b = new Uint8Array(b);
842
+ // eslint-disable-next-line no-fallthrough
843
+ case Uint8Array: {
844
+ if (a.byteLength !== b.byteLength) {
845
+ return false
846
+ }
847
+ for (let i = 0; i < a.length; i++) {
848
+ if (a[i] !== b[i]) {
849
+ return false
850
+ }
851
+ }
852
+ break
853
+ }
854
+ case Set: {
855
+ if (a.size !== b.size) {
856
+ return false
857
+ }
858
+ for (const value of a) {
859
+ if (!b.has(value)) {
860
+ return false
861
+ }
862
+ }
863
+ break
864
+ }
865
+ case Map: {
866
+ if (a.size !== b.size) {
867
+ return false
868
+ }
869
+ for (const key of a.keys()) {
870
+ if (!b.has(key) || !equalityDeep(a.get(key), b.get(key))) {
871
+ return false
872
+ }
873
+ }
874
+ break
875
+ }
876
+ case Object:
877
+ if (length(a) !== length(b)) {
878
+ return false
879
+ }
880
+ for (const key in a) {
881
+ if (!hasProperty(a, key) || !equalityDeep(a[key], b[key])) {
882
+ return false
883
+ }
884
+ }
885
+ break
886
+ case Array:
887
+ if (a.length !== b.length) {
888
+ return false
889
+ }
890
+ for (let i = 0; i < a.length; i++) {
891
+ if (!equalityDeep(a[i], b[i])) {
892
+ return false
893
+ }
894
+ }
895
+ break
896
+ default:
897
+ return false
898
+ }
899
+ return true
900
+ };
901
+
902
+ /**
903
+ * @template V
904
+ * @template {V} OPTS
905
+ *
906
+ * @param {V} value
907
+ * @param {Array<OPTS>} options
908
+ */
909
+ // @ts-ignore
910
+ const isOneOf = (value, options) => options.includes(value);
911
+
912
+ /**
913
+ * @module awareness-protocol
914
+ */
915
+
916
+
917
+ const outdatedTimeout = 30000;
918
+
919
+ /**
920
+ * @typedef {Object} MetaClientState
921
+ * @property {number} MetaClientState.clock
922
+ * @property {number} MetaClientState.lastUpdated unix timestamp
923
+ */
924
+
925
+ /**
926
+ * The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information
927
+ * (cursor, username, status, ..). Each client can update its own local state and listen to state changes of
928
+ * remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline.
929
+ *
930
+ * Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override
931
+ * its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is
932
+ * applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that
933
+ * a remote client is offline, it may propagate a message with
934
+ * `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a
935
+ * message is received, and the known clock of that client equals the received clock, it will override the state with `null`.
936
+ *
937
+ * Before a client disconnects, it should propagate a `null` state with an updated clock.
938
+ *
939
+ * Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state.
940
+ *
941
+ * @extends {Observable<string>}
942
+ */
943
+ class Awareness extends Observable {
944
+ /**
945
+ * @param {Y.Doc} doc
946
+ */
947
+ constructor (doc) {
948
+ super();
949
+ this.doc = doc;
950
+ /**
951
+ * @type {number}
952
+ */
953
+ this.clientID = doc.clientID;
954
+ /**
955
+ * Maps from client id to client state
956
+ * @type {Map<number, Object<string, any>>}
957
+ */
958
+ this.states = new Map();
959
+ /**
960
+ * @type {Map<number, MetaClientState>}
961
+ */
962
+ this.meta = new Map();
963
+ this._checkInterval = /** @type {any} */ (setInterval(() => {
964
+ const now = getUnixTime();
965
+ if (this.getLocalState() !== null && (outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */ (this.meta.get(this.clientID)).lastUpdated)) {
966
+ // renew local clock
967
+ this.setLocalState(this.getLocalState());
968
+ }
969
+ /**
970
+ * @type {Array<number>}
971
+ */
972
+ const remove = [];
973
+ this.meta.forEach((meta, clientid) => {
974
+ if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
975
+ remove.push(clientid);
976
+ }
977
+ });
978
+ if (remove.length > 0) {
979
+ removeAwarenessStates(this, remove, 'timeout');
980
+ }
981
+ }, floor(outdatedTimeout / 10)));
982
+ doc.on('destroy', () => {
983
+ this.destroy();
984
+ });
985
+ this.setLocalState({});
986
+ }
987
+
988
+ destroy () {
989
+ this.emit('destroy', [this]);
990
+ this.setLocalState(null);
991
+ super.destroy();
992
+ clearInterval(this._checkInterval);
993
+ }
994
+
995
+ /**
996
+ * @return {Object<string,any>|null}
997
+ */
998
+ getLocalState () {
999
+ return this.states.get(this.clientID) || null
1000
+ }
1001
+
1002
+ /**
1418
1003
  * @param {Object<string,any>|null} state
1419
1004
  */
1420
1005
  setLocalState (state) {
@@ -1588,8 +1173,10 @@ const applyAwarenessUpdate = (awareness, update, origin) => {
1588
1173
 
1589
1174
  class EventEmitter {
1590
1175
  constructor() {
1176
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
1591
1177
  this.callbacks = {};
1592
1178
  }
1179
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
1593
1180
  on(event, fn) {
1594
1181
  if (!this.callbacks[event]) {
1595
1182
  this.callbacks[event] = [];
@@ -1604,6 +1191,7 @@ class EventEmitter {
1604
1191
  }
1605
1192
  return this;
1606
1193
  }
1194
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
1607
1195
  off(event, fn) {
1608
1196
  const callbacks = this.callbacks[event];
1609
1197
  if (callbacks) {
@@ -1627,6 +1215,7 @@ class EventEmitter {
1627
1215
  * @module url
1628
1216
  */
1629
1217
 
1218
+
1630
1219
  /**
1631
1220
  * @param {Object<string,string>} params
1632
1221
  * @return {string}
@@ -1679,7 +1268,32 @@ class IncomingMessage {
1679
1268
  return writeVarUint8Array(this.encoder, data);
1680
1269
  }
1681
1270
  length() {
1682
- return length(this.encoder);
1271
+ return length$1(this.encoder);
1272
+ }
1273
+ }
1274
+
1275
+ class OutgoingMessage {
1276
+ constructor() {
1277
+ this.encoder = createEncoder();
1278
+ }
1279
+ get(args) {
1280
+ return args.encoder;
1281
+ }
1282
+ toUint8Array() {
1283
+ return toUint8Array(this.encoder);
1284
+ }
1285
+ }
1286
+
1287
+ class CloseMessage extends OutgoingMessage {
1288
+ constructor() {
1289
+ super(...arguments);
1290
+ this.type = exports.MessageType.CLOSE;
1291
+ this.description = 'Ask the server to close the connection';
1292
+ }
1293
+ get(args) {
1294
+ writeVarString(this.encoder, args.documentName);
1295
+ writeVarUint(this.encoder, this.type);
1296
+ return this.encoder;
1683
1297
  }
1684
1298
  }
1685
1299
 
@@ -1724,7 +1338,6 @@ class HocuspocusProviderWebsocket extends EventEmitter {
1724
1338
  onDestroy: () => null,
1725
1339
  onAwarenessUpdate: () => null,
1726
1340
  onAwarenessChange: () => null,
1727
- quiet: false,
1728
1341
  providerMap: new Map(),
1729
1342
  };
1730
1343
  this.webSocket = null;
@@ -1734,7 +1347,6 @@ class HocuspocusProviderWebsocket extends EventEmitter {
1734
1347
  this.lastMessageReceived = 0;
1735
1348
  this.identifier = 0;
1736
1349
  this.intervals = {
1737
- forceSync: null,
1738
1350
  connectionChecker: null,
1739
1351
  };
1740
1352
  this.connectionAttempt = null;
@@ -1769,27 +1381,26 @@ class HocuspocusProviderWebsocket extends EventEmitter {
1769
1381
  this.connect();
1770
1382
  }
1771
1383
  async onOpen(event) {
1384
+ this.cancelWebsocketRetry = undefined;
1772
1385
  this.receivedOnOpenPayload = event;
1773
1386
  }
1774
1387
  async onStatus(data) {
1775
1388
  this.receivedOnStatusPayload = data;
1776
1389
  }
1777
1390
  attach(provider) {
1778
- let connectPromise;
1779
1391
  this.configuration.providerMap.set(provider.configuration.name, provider);
1780
1392
  if (this.status === exports.WebSocketStatus.Disconnected && this.shouldConnect) {
1781
- connectPromise = this.connect();
1393
+ this.connect();
1782
1394
  }
1783
1395
  if (this.receivedOnOpenPayload) {
1784
1396
  provider.onOpen(this.receivedOnOpenPayload);
1785
1397
  }
1786
- if (this.receivedOnStatusPayload) {
1787
- provider.onStatus(this.receivedOnStatusPayload);
1788
- }
1789
- return connectPromise;
1790
1398
  }
1791
1399
  detach(provider) {
1792
- this.configuration.providerMap.delete(provider.configuration.name);
1400
+ if (this.configuration.providerMap.has(provider.configuration.name)) {
1401
+ provider.send(CloseMessage, { documentName: provider.configuration.name });
1402
+ this.configuration.providerMap.delete(provider.configuration.name);
1403
+ }
1793
1404
  }
1794
1405
  setConfiguration(configuration = {}) {
1795
1406
  this.configuration = { ...this.configuration, ...configuration };
@@ -1840,6 +1451,7 @@ class HocuspocusProviderWebsocket extends EventEmitter {
1840
1451
  this.cancelWebsocketRetry = cancelFunc;
1841
1452
  return retryPromise;
1842
1453
  }
1454
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
1843
1455
  attachWebSocketListeners(ws, reject) {
1844
1456
  const { identifier } = ws;
1845
1457
  const onMessageHandler = (payload) => this.emit('message', payload);
@@ -1958,7 +1570,7 @@ class HocuspocusProviderWebsocket extends EventEmitter {
1958
1570
  this.messageQueue = [];
1959
1571
  }
1960
1572
  }
1961
- // Ensure that the URL always ends with /
1573
+ // Ensure that the URL never ends with /
1962
1574
  get serverUrl() {
1963
1575
  while (this.configuration.url[this.configuration.url.length - 1] === '/') {
1964
1576
  return this.configuration.url.slice(0, this.configuration.url.length - 1);
@@ -1978,8 +1590,8 @@ class HocuspocusProviderWebsocket extends EventEmitter {
1978
1590
  this.webSocket.close();
1979
1591
  this.messageQueue = [];
1980
1592
  }
1981
- catch {
1982
- //
1593
+ catch (e) {
1594
+ console.error(e);
1983
1595
  }
1984
1596
  }
1985
1597
  send(message) {
@@ -1994,56 +1606,23 @@ class HocuspocusProviderWebsocket extends EventEmitter {
1994
1606
  onClose({ event }) {
1995
1607
  this.closeTries = 0;
1996
1608
  this.cleanupWebSocket();
1997
- if (this.status === exports.WebSocketStatus.Connected) {
1998
- this.status = exports.WebSocketStatus.Disconnected;
1999
- this.emit('status', { status: exports.WebSocketStatus.Disconnected });
2000
- this.emit('disconnect', { event });
2001
- }
2002
- if (event.code === common.Unauthorized.code) {
2003
- if (event.reason === common.Unauthorized.reason) {
2004
- console.warn('[HocuspocusProvider] An authentication token is required, but you didn’t send one. Try adding a `token` to your HocuspocusProvider configuration. Won’t try again.');
2005
- }
2006
- else {
2007
- console.warn(`[HocuspocusProvider] Connection closed with status Unauthorized: ${event.reason}`);
2008
- }
2009
- this.shouldConnect = false;
2010
- }
2011
- if (event.code === common.Forbidden.code) {
2012
- if (!this.configuration.quiet) {
2013
- console.warn('[HocuspocusProvider] The provided authentication token isn’t allowed to connect to this server. Will try again.');
2014
- return; // TODO REMOVE ME
2015
- }
2016
- }
2017
- if (event.code === common.MessageTooBig.code) {
2018
- console.warn(`[HocuspocusProvider] Connection closed with status MessageTooBig: ${event.reason}`);
2019
- this.shouldConnect = false;
2020
- }
2021
1609
  if (this.connectionAttempt) {
2022
1610
  // That connection attempt failed.
2023
1611
  this.rejectConnectionAttempt();
2024
1612
  }
2025
- else if (this.shouldConnect) {
2026
- // The connection was closed by the server. Let’s just try again.
2027
- this.connect();
2028
- }
2029
- // If we’ll reconnect, we’re done for now.
2030
- if (this.shouldConnect) {
2031
- return;
2032
- }
2033
- // The status is set correctly already.
2034
- if (this.status === exports.WebSocketStatus.Disconnected) {
2035
- return;
2036
- }
2037
1613
  // Let’s update the connection status.
2038
1614
  this.status = exports.WebSocketStatus.Disconnected;
2039
1615
  this.emit('status', { status: exports.WebSocketStatus.Disconnected });
2040
1616
  this.emit('disconnect', { event });
1617
+ // trigger connect if no retry is running and we want to have a connection
1618
+ if (!this.cancelWebsocketRetry && this.shouldConnect) {
1619
+ setTimeout(() => {
1620
+ this.connect();
1621
+ }, this.configuration.delay);
1622
+ }
2041
1623
  }
2042
1624
  destroy() {
2043
1625
  this.emit('destroy');
2044
- if (this.intervals.forceSync) {
2045
- clearInterval(this.intervals.forceSync);
2046
- }
2047
1626
  clearInterval(this.intervals.connectionChecker);
2048
1627
  // If there is still a connection attempt outstanding then we should stop
2049
1628
  // it before calling disconnect, otherwise it will be rejected in the onClose
@@ -2059,6 +1638,7 @@ class HocuspocusProviderWebsocket extends EventEmitter {
2059
1638
  * @module sync-protocol
2060
1639
  */
2061
1640
 
1641
+
2062
1642
  /**
2063
1643
  * @typedef {Map<number, number>} StateMap
2064
1644
  */
@@ -2182,27 +1762,10 @@ const readSyncMessage = (decoder, encoder, doc, transactionOrigin) => {
2182
1762
  return messageType
2183
1763
  };
2184
1764
 
2185
- class OutgoingMessage {
2186
- constructor() {
2187
- this.encoder = createEncoder();
2188
- }
2189
- get(args) {
2190
- return args.encoder;
2191
- }
2192
- toUint8Array() {
2193
- return toUint8Array(this.encoder);
2194
- }
2195
- }
2196
-
2197
1765
  class MessageReceiver {
2198
1766
  constructor(message) {
2199
- this.broadcasted = false;
2200
1767
  this.message = message;
2201
1768
  }
2202
- setBroadcasted(value) {
2203
- this.broadcasted = value;
2204
- return this;
2205
- }
2206
1769
  apply(provider, emitSynced) {
2207
1770
  const { message } = this;
2208
1771
  const type = message.readVarUint();
@@ -2226,21 +1789,26 @@ class MessageReceiver {
2226
1789
  case exports.MessageType.SyncStatus:
2227
1790
  this.applySyncStatusMessage(provider, readVarInt(message.decoder) === 1);
2228
1791
  break;
1792
+ case exports.MessageType.CLOSE:
1793
+ // eslint-disable-next-line no-case-declarations
1794
+ const event = {
1795
+ code: 1000,
1796
+ reason: readVarString(message.decoder),
1797
+ // @ts-ignore
1798
+ target: provider.configuration.websocketProvider.webSocket,
1799
+ type: 'close',
1800
+ };
1801
+ provider.onClose();
1802
+ provider.configuration.onClose({ event });
1803
+ provider.forwardClose(event);
1804
+ break;
2229
1805
  default:
2230
1806
  throw new Error(`Can’t apply message of unknown type: ${type}`);
2231
1807
  }
2232
1808
  // Reply
2233
1809
  if (message.length() > emptyMessageLength + 1) { // length of documentName (considered in emptyMessageLength plus length of yjs sync type, set in applySyncMessage)
2234
- if (this.broadcasted) {
2235
- // TODO: Some weird TypeScript error
2236
- // @ts-ignore
2237
- provider.broadcast(OutgoingMessage, { encoder: message.encoder });
2238
- }
2239
- else {
2240
- // TODO: Some weird TypeScript error
2241
- // @ts-ignore
2242
- provider.send(OutgoingMessage, { encoder: message.encoder });
2243
- }
1810
+ // @ts-ignore
1811
+ provider.send(OutgoingMessage, { encoder: message.encoder });
2244
1812
  }
2245
1813
  }
2246
1814
  applySyncMessage(provider, emitSynced) {
@@ -2277,6 +1845,366 @@ class MessageReceiver {
2277
1845
  }
2278
1846
  }
2279
1847
 
1848
+ /**
1849
+ * Often used conditions.
1850
+ *
1851
+ * @module conditions
1852
+ */
1853
+
1854
+ /**
1855
+ * @template T
1856
+ * @param {T|null|undefined} v
1857
+ * @return {T|null}
1858
+ */
1859
+ /* c8 ignore next */
1860
+ const undefinedToNull = v => v === undefined ? null : v;
1861
+
1862
+ /* eslint-env browser */
1863
+
1864
+ /**
1865
+ * Isomorphic variable storage.
1866
+ *
1867
+ * Uses LocalStorage in the browser and falls back to in-memory storage.
1868
+ *
1869
+ * @module storage
1870
+ */
1871
+
1872
+ /* c8 ignore start */
1873
+ class VarStoragePolyfill {
1874
+ constructor () {
1875
+ this.map = new Map();
1876
+ }
1877
+
1878
+ /**
1879
+ * @param {string} key
1880
+ * @param {any} newValue
1881
+ */
1882
+ setItem (key, newValue) {
1883
+ this.map.set(key, newValue);
1884
+ }
1885
+
1886
+ /**
1887
+ * @param {string} key
1888
+ */
1889
+ getItem (key) {
1890
+ return this.map.get(key)
1891
+ }
1892
+ }
1893
+ /* c8 ignore stop */
1894
+
1895
+ /**
1896
+ * @type {any}
1897
+ */
1898
+ let _localStorage = new VarStoragePolyfill();
1899
+ let usePolyfill = true;
1900
+
1901
+ /* c8 ignore start */
1902
+ try {
1903
+ // if the same-origin rule is violated, accessing localStorage might thrown an error
1904
+ if (typeof localStorage !== 'undefined' && localStorage) {
1905
+ _localStorage = localStorage;
1906
+ usePolyfill = false;
1907
+ }
1908
+ } catch (e) { }
1909
+ /* c8 ignore stop */
1910
+
1911
+ /**
1912
+ * This is basically localStorage in browser, or a polyfill in nodejs
1913
+ */
1914
+ /* c8 ignore next */
1915
+ const varStorage = _localStorage;
1916
+
1917
+ /**
1918
+ * A polyfill for `addEventListener('storage', event => {..})` that does nothing if the polyfill is being used.
1919
+ *
1920
+ * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler
1921
+ * @function
1922
+ */
1923
+ /* c8 ignore next */
1924
+ const onChange = eventHandler => usePolyfill || addEventListener('storage', /** @type {any} */ (eventHandler));
1925
+
1926
+ /**
1927
+ * A polyfill for `removeEventListener('storage', event => {..})` that does nothing if the polyfill is being used.
1928
+ *
1929
+ * @param {function({ key: string, newValue: string, oldValue: string }): void} eventHandler
1930
+ * @function
1931
+ */
1932
+ /* c8 ignore next */
1933
+ const offChange = eventHandler => usePolyfill || removeEventListener('storage', /** @type {any} */ (eventHandler));
1934
+
1935
+ /**
1936
+ * Isomorphic module to work access the environment (query params, env variables).
1937
+ *
1938
+ * @module environment
1939
+ */
1940
+
1941
+
1942
+ /* c8 ignore next 2 */
1943
+ // @ts-ignore
1944
+ const isNode = typeof process !== 'undefined' && process.release && /node|io\.js/.test(process.release.name) && Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
1945
+
1946
+ /* c8 ignore next */
1947
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && !isNode;
1948
+
1949
+ /**
1950
+ * @type {Map<string,string>}
1951
+ */
1952
+ let params;
1953
+
1954
+ /* c8 ignore start */
1955
+ const computeParams = () => {
1956
+ if (params === undefined) {
1957
+ if (isNode) {
1958
+ params = create();
1959
+ const pargs = process.argv;
1960
+ let currParamName = null;
1961
+ for (let i = 0; i < pargs.length; i++) {
1962
+ const parg = pargs[i];
1963
+ if (parg[0] === '-') {
1964
+ if (currParamName !== null) {
1965
+ params.set(currParamName, '');
1966
+ }
1967
+ currParamName = parg;
1968
+ } else {
1969
+ if (currParamName !== null) {
1970
+ params.set(currParamName, parg);
1971
+ currParamName = null;
1972
+ }
1973
+ }
1974
+ }
1975
+ if (currParamName !== null) {
1976
+ params.set(currParamName, '');
1977
+ }
1978
+ // in ReactNative for example this would not be true (unless connected to the Remote Debugger)
1979
+ } else if (typeof location === 'object') {
1980
+ params = create(); // eslint-disable-next-line no-undef
1981
+ (location.search || '?').slice(1).split('&').forEach((kv) => {
1982
+ if (kv.length !== 0) {
1983
+ const [key, value] = kv.split('=');
1984
+ params.set(`--${fromCamelCase(key, '-')}`, value);
1985
+ params.set(`-${fromCamelCase(key, '-')}`, value);
1986
+ }
1987
+ });
1988
+ } else {
1989
+ params = create();
1990
+ }
1991
+ }
1992
+ return params
1993
+ };
1994
+ /* c8 ignore stop */
1995
+
1996
+ /**
1997
+ * @param {string} name
1998
+ * @return {boolean}
1999
+ */
2000
+ /* c8 ignore next */
2001
+ const hasParam = (name) => computeParams().has(name);
2002
+
2003
+ /**
2004
+ * @param {string} name
2005
+ * @return {string|null}
2006
+ */
2007
+ /* c8 ignore next 4 */
2008
+ const getVariable = (name) =>
2009
+ isNode
2010
+ ? undefinedToNull(process.env[name.toUpperCase().replaceAll('-', '_')])
2011
+ : undefinedToNull(varStorage.getItem(name));
2012
+
2013
+ /**
2014
+ * @param {string} name
2015
+ * @return {boolean}
2016
+ */
2017
+ /* c8 ignore next 2 */
2018
+ const hasConf = (name) =>
2019
+ hasParam('--' + name) || getVariable(name) !== null;
2020
+
2021
+ /* c8 ignore next */
2022
+ hasConf('production');
2023
+
2024
+ /* c8 ignore next 2 */
2025
+ const forceColor = isNode &&
2026
+ isOneOf(process.env.FORCE_COLOR, ['true', '1', '2']);
2027
+
2028
+ /* c8 ignore start */
2029
+ /**
2030
+ * Color is enabled by default if the terminal supports it.
2031
+ *
2032
+ * Explicitly enable color using `--color` parameter
2033
+ * Disable color using `--no-color` parameter or using `NO_COLOR=1` environment variable.
2034
+ * `FORCE_COLOR=1` enables color and takes precedence over all.
2035
+ */
2036
+ forceColor || (
2037
+ !hasParam('--no-colors') && // @todo deprecate --no-colors
2038
+ !hasConf('no-color') &&
2039
+ (!isNode || process.stdout.isTTY) && (
2040
+ !isNode ||
2041
+ hasParam('--color') ||
2042
+ getVariable('COLORTERM') !== null ||
2043
+ (getVariable('TERM') || '').includes('color')
2044
+ )
2045
+ );
2046
+ /* c8 ignore stop */
2047
+
2048
+ /**
2049
+ * Utility functions to work with buffers (Uint8Array).
2050
+ *
2051
+ * @module buffer
2052
+ */
2053
+
2054
+
2055
+ /**
2056
+ * @param {number} len
2057
+ */
2058
+ const createUint8ArrayFromLen = len => new Uint8Array(len);
2059
+
2060
+ /**
2061
+ * Create Uint8Array with initial content from buffer
2062
+ *
2063
+ * @param {ArrayBuffer} buffer
2064
+ * @param {number} byteOffset
2065
+ * @param {number} length
2066
+ */
2067
+ const createUint8ArrayViewFromArrayBuffer = (buffer, byteOffset, length) => new Uint8Array(buffer, byteOffset, length);
2068
+
2069
+ /**
2070
+ * Create Uint8Array with initial content from buffer
2071
+ *
2072
+ * @param {ArrayBuffer} buffer
2073
+ */
2074
+ const createUint8ArrayFromArrayBuffer = buffer => new Uint8Array(buffer);
2075
+
2076
+ /* c8 ignore start */
2077
+ /**
2078
+ * @param {Uint8Array} bytes
2079
+ * @return {string}
2080
+ */
2081
+ const toBase64Browser = bytes => {
2082
+ let s = '';
2083
+ for (let i = 0; i < bytes.byteLength; i++) {
2084
+ s += fromCharCode(bytes[i]);
2085
+ }
2086
+ // eslint-disable-next-line no-undef
2087
+ return btoa(s)
2088
+ };
2089
+ /* c8 ignore stop */
2090
+
2091
+ /**
2092
+ * @param {Uint8Array} bytes
2093
+ * @return {string}
2094
+ */
2095
+ const toBase64Node = bytes => Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64');
2096
+
2097
+ /* c8 ignore start */
2098
+ /**
2099
+ * @param {string} s
2100
+ * @return {Uint8Array}
2101
+ */
2102
+ const fromBase64Browser = s => {
2103
+ // eslint-disable-next-line no-undef
2104
+ const a = atob(s);
2105
+ const bytes = createUint8ArrayFromLen(a.length);
2106
+ for (let i = 0; i < a.length; i++) {
2107
+ bytes[i] = a.charCodeAt(i);
2108
+ }
2109
+ return bytes
2110
+ };
2111
+ /* c8 ignore stop */
2112
+
2113
+ /**
2114
+ * @param {string} s
2115
+ */
2116
+ const fromBase64Node = s => {
2117
+ const buf = Buffer.from(s, 'base64');
2118
+ return createUint8ArrayViewFromArrayBuffer(buf.buffer, buf.byteOffset, buf.byteLength)
2119
+ };
2120
+
2121
+ /* c8 ignore next */
2122
+ const toBase64 = isBrowser ? toBase64Browser : toBase64Node;
2123
+
2124
+ /* c8 ignore next */
2125
+ const fromBase64 = isBrowser ? fromBase64Browser : fromBase64Node;
2126
+
2127
+ /* eslint-env browser */
2128
+
2129
+
2130
+ /**
2131
+ * @typedef {Object} Channel
2132
+ * @property {Set<function(any, any):any>} Channel.subs
2133
+ * @property {any} Channel.bc
2134
+ */
2135
+
2136
+ /**
2137
+ * @type {Map<string, Channel>}
2138
+ */
2139
+ const channels = new Map();
2140
+
2141
+ /* c8 ignore start */
2142
+ class LocalStoragePolyfill {
2143
+ /**
2144
+ * @param {string} room
2145
+ */
2146
+ constructor (room) {
2147
+ this.room = room;
2148
+ /**
2149
+ * @type {null|function({data:ArrayBuffer}):void}
2150
+ */
2151
+ this.onmessage = null;
2152
+ /**
2153
+ * @param {any} e
2154
+ */
2155
+ this._onChange = e => e.key === room && this.onmessage !== null && this.onmessage({ data: fromBase64(e.newValue || '') });
2156
+ onChange(this._onChange);
2157
+ }
2158
+
2159
+ /**
2160
+ * @param {ArrayBuffer} buf
2161
+ */
2162
+ postMessage (buf) {
2163
+ varStorage.setItem(this.room, toBase64(createUint8ArrayFromArrayBuffer(buf)));
2164
+ }
2165
+
2166
+ close () {
2167
+ offChange(this._onChange);
2168
+ }
2169
+ }
2170
+ /* c8 ignore stop */
2171
+
2172
+ // Use BroadcastChannel or Polyfill
2173
+ /* c8 ignore next */
2174
+ const BC = typeof BroadcastChannel === 'undefined' ? LocalStoragePolyfill : BroadcastChannel;
2175
+
2176
+ /**
2177
+ * @param {string} room
2178
+ * @return {Channel}
2179
+ */
2180
+ const getChannel = room =>
2181
+ setIfUndefined(channels, room, () => {
2182
+ const subs = create$2();
2183
+ const bc = new BC(room);
2184
+ /**
2185
+ * @param {{data:ArrayBuffer}} e
2186
+ */
2187
+ /* c8 ignore next */
2188
+ bc.onmessage = e => subs.forEach(sub => sub(e.data, 'broadcastchannel'));
2189
+ return {
2190
+ bc, subs
2191
+ }
2192
+ });
2193
+
2194
+ /**
2195
+ * Publish data to all subscribers (including subscribers on this tab)
2196
+ *
2197
+ * @function
2198
+ * @param {string} room
2199
+ * @param {any} data
2200
+ * @param {any} [origin]
2201
+ */
2202
+ const publish = (room, data, origin = null) => {
2203
+ const c = getChannel(room);
2204
+ c.bc.postMessage(data);
2205
+ c.subs.forEach(sub => sub(data, origin));
2206
+ };
2207
+
2280
2208
  class MessageSender {
2281
2209
  constructor(Message, args = {}) {
2282
2210
  this.message = new Message();
@@ -2337,32 +2265,6 @@ class AwarenessMessage extends OutgoingMessage {
2337
2265
  }
2338
2266
  }
2339
2267
 
2340
- class CloseMessage extends OutgoingMessage {
2341
- constructor() {
2342
- super(...arguments);
2343
- this.type = exports.MessageType.CLOSE;
2344
- this.description = 'Ask the server to close the connection';
2345
- }
2346
- get(args) {
2347
- writeVarString(this.encoder, args.documentName);
2348
- writeVarUint(this.encoder, this.type);
2349
- return this.encoder;
2350
- }
2351
- }
2352
-
2353
- class QueryAwarenessMessage extends OutgoingMessage {
2354
- constructor() {
2355
- super(...arguments);
2356
- this.type = exports.MessageType.QueryAwareness;
2357
- this.description = 'Queries awareness states';
2358
- }
2359
- get(args) {
2360
- writeVarString(this.encoder, args.documentName);
2361
- writeVarUint(this.encoder, this.type);
2362
- return this.encoder;
2363
- }
2364
- }
2365
-
2366
2268
  class StatelessMessage extends OutgoingMessage {
2367
2269
  constructor() {
2368
2270
  super(...arguments);
@@ -2395,23 +2297,6 @@ class SyncStepOneMessage extends OutgoingMessage {
2395
2297
  }
2396
2298
  }
2397
2299
 
2398
- class SyncStepTwoMessage extends OutgoingMessage {
2399
- constructor() {
2400
- super(...arguments);
2401
- this.type = exports.MessageType.Sync;
2402
- this.description = 'Second sync step';
2403
- }
2404
- get(args) {
2405
- if (typeof args.document === 'undefined') {
2406
- throw new Error('The sync step two message requires document as an argument');
2407
- }
2408
- writeVarString(this.encoder, args.documentName);
2409
- writeVarUint(this.encoder, this.type);
2410
- writeSyncStep2(this.encoder, args.document);
2411
- return this.encoder;
2412
- }
2413
- }
2414
-
2415
2300
  class UpdateMessage extends OutgoingMessage {
2416
2301
  constructor() {
2417
2302
  super(...arguments);
@@ -2443,8 +2328,6 @@ class HocuspocusProvider extends EventEmitter {
2443
2328
  // @ts-ignore
2444
2329
  awareness: undefined,
2445
2330
  token: null,
2446
- parameters: {},
2447
- broadcast: true,
2448
2331
  forceSyncInterval: false,
2449
2332
  onAuthenticated: () => null,
2450
2333
  onAuthenticationFailed: () => null,
@@ -2452,7 +2335,6 @@ class HocuspocusProvider extends EventEmitter {
2452
2335
  onConnect: () => null,
2453
2336
  onMessage: () => null,
2454
2337
  onOutgoingMessage: () => null,
2455
- onStatus: () => null,
2456
2338
  onSynced: () => null,
2457
2339
  onDisconnect: () => null,
2458
2340
  onClose: () => null,
@@ -2460,30 +2342,24 @@ class HocuspocusProvider extends EventEmitter {
2460
2342
  onAwarenessUpdate: () => null,
2461
2343
  onAwarenessChange: () => null,
2462
2344
  onStateless: () => null,
2463
- quiet: false,
2464
- connect: true,
2465
- preserveConnection: true,
2466
2345
  };
2467
- this.subscribedToBroadcastChannel = false;
2468
2346
  this.isSynced = false;
2469
2347
  this.unsyncedChanges = 0;
2470
- this.status = exports.WebSocketStatus.Disconnected;
2471
2348
  this.isAuthenticated = false;
2472
2349
  this.authorizedScope = undefined;
2473
- this.mux = createMutex();
2350
+ // @internal
2351
+ this.manageSocket = false;
2352
+ this.isAttached = false;
2474
2353
  this.intervals = {
2475
2354
  forceSync: null,
2476
2355
  };
2477
- this.isConnected = true;
2478
2356
  this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
2479
2357
  this.boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this);
2480
- this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
2481
2358
  this.boundPageHide = this.pageHide.bind(this);
2482
2359
  this.boundOnOpen = this.onOpen.bind(this);
2483
2360
  this.boundOnClose = this.onClose.bind(this);
2484
- this.boundOnStatus = this.onStatus.bind(this);
2485
2361
  this.forwardConnect = (e) => this.emit('connect', e);
2486
- this.forwardOpen = (e) => this.emit('open', e);
2362
+ // forwardOpen = (e: any) => this.emit('open', e)
2487
2363
  this.forwardClose = (e) => this.emit('close', e);
2488
2364
  this.forwardDisconnect = (e) => this.emit('disconnect', e);
2489
2365
  this.forwardDestroy = (e) => this.emit('destroy', e);
@@ -2500,18 +2376,6 @@ class HocuspocusProvider extends EventEmitter {
2500
2376
  this.on('stateless', this.configuration.onStateless);
2501
2377
  this.on('authenticated', this.configuration.onAuthenticated);
2502
2378
  this.on('authenticationFailed', this.configuration.onAuthenticationFailed);
2503
- this.configuration.websocketProvider.on('connect', this.configuration.onConnect);
2504
- this.configuration.websocketProvider.on('connect', this.forwardConnect);
2505
- this.configuration.websocketProvider.on('open', this.boundOnOpen);
2506
- this.configuration.websocketProvider.on('open', this.forwardOpen);
2507
- this.configuration.websocketProvider.on('close', this.boundOnClose);
2508
- this.configuration.websocketProvider.on('close', this.configuration.onClose);
2509
- this.configuration.websocketProvider.on('close', this.forwardClose);
2510
- this.configuration.websocketProvider.on('status', this.boundOnStatus);
2511
- this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect);
2512
- this.configuration.websocketProvider.on('disconnect', this.forwardDisconnect);
2513
- this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy);
2514
- this.configuration.websocketProvider.on('destroy', this.forwardDestroy);
2515
2379
  (_a = this.awareness) === null || _a === void 0 ? void 0 : _a.on('update', () => {
2516
2380
  this.emit('awarenessUpdate', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
2517
2381
  });
@@ -2525,20 +2389,16 @@ class HocuspocusProvider extends EventEmitter {
2525
2389
  && typeof this.configuration.forceSyncInterval === 'number') {
2526
2390
  this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval);
2527
2391
  }
2528
- this.configuration.websocketProvider.attach(this);
2529
- }
2530
- onStatus({ status }) {
2531
- this.status = status;
2532
- this.configuration.onStatus({ status });
2533
- this.emit('status', { status });
2392
+ if (this.manageSocket) {
2393
+ this.attach();
2394
+ }
2534
2395
  }
2535
2396
  setConfiguration(configuration = {}) {
2536
- if (!configuration.websocketProvider && configuration.url) {
2397
+ if (!configuration.websocketProvider) {
2537
2398
  const websocketProviderConfig = configuration;
2399
+ this.manageSocket = true;
2538
2400
  this.configuration.websocketProvider = new HocuspocusProviderWebsocket({
2539
2401
  url: websocketProviderConfig.url,
2540
- connect: websocketProviderConfig.connect,
2541
- parameters: websocketProviderConfig.parameters,
2542
2402
  });
2543
2403
  }
2544
2404
  this.configuration = { ...this.configuration, ...configuration };
@@ -2561,7 +2421,9 @@ class HocuspocusProvider extends EventEmitter {
2561
2421
  this.emit('unsyncedChanges', this.unsyncedChanges);
2562
2422
  }
2563
2423
  decrementUnsyncedChanges() {
2564
- this.unsyncedChanges -= 1;
2424
+ if (this.unsyncedChanges > 0) {
2425
+ this.unsyncedChanges -= 1;
2426
+ }
2565
2427
  if (this.unsyncedChanges === 0) {
2566
2428
  this.synced = true;
2567
2429
  }
@@ -2590,7 +2452,7 @@ class HocuspocusProvider extends EventEmitter {
2590
2452
  return;
2591
2453
  }
2592
2454
  this.incrementUnsyncedChanges();
2593
- this.send(UpdateMessage, { update, documentName: this.configuration.name }, true);
2455
+ this.send(UpdateMessage, { update, documentName: this.configuration.name });
2594
2456
  }
2595
2457
  awarenessUpdateHandler({ added, updated, removed }, origin) {
2596
2458
  const changedClients = added.concat(updated).concat(removed);
@@ -2598,7 +2460,7 @@ class HocuspocusProvider extends EventEmitter {
2598
2460
  awareness: this.awareness,
2599
2461
  clients: changedClients,
2600
2462
  documentName: this.configuration.name,
2601
- }, true);
2463
+ });
2602
2464
  }
2603
2465
  /**
2604
2466
  * Indicates whether a first handshake with the server has been established
@@ -2614,34 +2476,22 @@ class HocuspocusProvider extends EventEmitter {
2614
2476
  return;
2615
2477
  }
2616
2478
  this.isSynced = state;
2617
- this.emit('synced', { state });
2618
- this.emit('sync', { state });
2479
+ if (state) {
2480
+ this.emit('synced', { state });
2481
+ }
2619
2482
  }
2620
2483
  receiveStateless(payload) {
2621
2484
  this.emit('stateless', { payload });
2622
2485
  }
2623
- get isAuthenticationRequired() {
2624
- return !!this.configuration.token && !this.isAuthenticated;
2625
- }
2626
2486
  // not needed, but provides backward compatibility with e.g. lexical/yjs
2627
2487
  async connect() {
2628
- if (this.configuration.broadcast) {
2629
- this.subscribeToBroadcastChannel();
2630
- }
2631
- this.configuration.websocketProvider.shouldConnect = true;
2632
- return this.configuration.websocketProvider.attach(this);
2488
+ console.warn('HocuspocusProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.');
2633
2489
  }
2634
2490
  disconnect() {
2635
- this.disconnectBroadcastChannel();
2636
- this.configuration.websocketProvider.detach(this);
2637
- this.isConnected = false;
2638
- if (!this.configuration.preserveConnection) {
2639
- this.configuration.websocketProvider.disconnect();
2640
- }
2491
+ console.warn('HocuspocusProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.');
2641
2492
  }
2642
2493
  async onOpen(event) {
2643
2494
  this.isAuthenticated = false;
2644
- this.isConnected = true;
2645
2495
  this.emit('open', { event });
2646
2496
  let token;
2647
2497
  try {
@@ -2651,12 +2501,10 @@ class HocuspocusProvider extends EventEmitter {
2651
2501
  this.permissionDeniedHandler(`Failed to get token: ${error}`);
2652
2502
  return;
2653
2503
  }
2654
- if (this.isAuthenticationRequired) {
2655
- this.send(AuthenticationMessage, {
2656
- token,
2657
- documentName: this.configuration.name,
2658
- });
2659
- }
2504
+ this.send(AuthenticationMessage, {
2505
+ token: token !== null && token !== void 0 ? token : '',
2506
+ documentName: this.configuration.name,
2507
+ });
2660
2508
  this.startSync();
2661
2509
  }
2662
2510
  async getToken() {
@@ -2677,13 +2525,7 @@ class HocuspocusProvider extends EventEmitter {
2677
2525
  });
2678
2526
  }
2679
2527
  }
2680
- send(message, args, broadcast = false) {
2681
- if (!this.isConnected) {
2682
- return;
2683
- }
2684
- if (broadcast) {
2685
- this.mux(() => { this.broadcast(message, args); });
2686
- }
2528
+ send(message, args) {
2687
2529
  const messageSender = new MessageSender(message, args);
2688
2530
  this.emit('outgoingMessage', { message: messageSender.message });
2689
2531
  messageSender.send(this.configuration.websocketProvider);
@@ -2695,7 +2537,7 @@ class HocuspocusProvider extends EventEmitter {
2695
2537
  this.emit('message', { event, message: new IncomingMessage(event.data) });
2696
2538
  new MessageReceiver(message).apply(this, true);
2697
2539
  }
2698
- onClose(event) {
2540
+ onClose() {
2699
2541
  this.isAuthenticated = false;
2700
2542
  this.synced = false;
2701
2543
  // update awareness (all users except local left)
@@ -2715,92 +2557,54 @@ class HocuspocusProvider extends EventEmitter {
2715
2557
  }
2716
2558
  this.document.off('update', this.boundDocumentUpdateHandler);
2717
2559
  this.removeAllListeners();
2560
+ this.detach();
2561
+ if (this.manageSocket) {
2562
+ this.configuration.websocketProvider.destroy();
2563
+ }
2564
+ if (typeof window === 'undefined' || !('removeEventListener' in window)) {
2565
+ return;
2566
+ }
2567
+ window.removeEventListener('pagehide', this.boundPageHide);
2568
+ }
2569
+ detach() {
2718
2570
  this.configuration.websocketProvider.off('connect', this.configuration.onConnect);
2719
2571
  this.configuration.websocketProvider.off('connect', this.forwardConnect);
2720
2572
  this.configuration.websocketProvider.off('open', this.boundOnOpen);
2721
- this.configuration.websocketProvider.off('open', this.forwardOpen);
2722
2573
  this.configuration.websocketProvider.off('close', this.boundOnClose);
2723
2574
  this.configuration.websocketProvider.off('close', this.configuration.onClose);
2724
2575
  this.configuration.websocketProvider.off('close', this.forwardClose);
2725
- this.configuration.websocketProvider.off('status', this.boundOnStatus);
2726
2576
  this.configuration.websocketProvider.off('disconnect', this.configuration.onDisconnect);
2727
2577
  this.configuration.websocketProvider.off('disconnect', this.forwardDisconnect);
2728
2578
  this.configuration.websocketProvider.off('destroy', this.configuration.onDestroy);
2729
2579
  this.configuration.websocketProvider.off('destroy', this.forwardDestroy);
2730
- this.send(CloseMessage, { documentName: this.configuration.name });
2731
- this.disconnect();
2732
- if (typeof window === 'undefined' || !('removeEventListener' in window)) {
2580
+ this.configuration.websocketProvider.detach(this);
2581
+ this.isAttached = false;
2582
+ }
2583
+ attach() {
2584
+ if (this.isAttached)
2733
2585
  return;
2734
- }
2735
- window.removeEventListener('pagehide', this.boundPageHide);
2586
+ this.configuration.websocketProvider.on('connect', this.configuration.onConnect);
2587
+ this.configuration.websocketProvider.on('connect', this.forwardConnect);
2588
+ this.configuration.websocketProvider.on('open', this.boundOnOpen);
2589
+ this.configuration.websocketProvider.on('close', this.boundOnClose);
2590
+ this.configuration.websocketProvider.on('close', this.configuration.onClose);
2591
+ this.configuration.websocketProvider.on('close', this.forwardClose);
2592
+ this.configuration.websocketProvider.on('disconnect', this.configuration.onDisconnect);
2593
+ this.configuration.websocketProvider.on('disconnect', this.forwardDisconnect);
2594
+ this.configuration.websocketProvider.on('destroy', this.configuration.onDestroy);
2595
+ this.configuration.websocketProvider.on('destroy', this.forwardDestroy);
2596
+ this.configuration.websocketProvider.attach(this);
2597
+ this.isAttached = true;
2736
2598
  }
2737
2599
  permissionDeniedHandler(reason) {
2738
2600
  this.emit('authenticationFailed', { reason });
2739
2601
  this.isAuthenticated = false;
2740
- this.disconnect();
2741
- this.status = exports.WebSocketStatus.Disconnected;
2742
2602
  }
2743
2603
  authenticatedHandler(scope) {
2744
2604
  this.isAuthenticated = true;
2745
2605
  this.authorizedScope = scope;
2746
2606
  this.emit('authenticated');
2747
2607
  }
2748
- get broadcastChannel() {
2749
- return `${this.configuration.name}`;
2750
- }
2751
- broadcastChannelSubscriber(data) {
2752
- this.mux(() => {
2753
- const message = new IncomingMessage(data);
2754
- const documentName = message.readVarString();
2755
- message.writeVarString(documentName);
2756
- new MessageReceiver(message)
2757
- .setBroadcasted(true)
2758
- .apply(this, false);
2759
- });
2760
- }
2761
- subscribeToBroadcastChannel() {
2762
- if (!this.subscribedToBroadcastChannel) {
2763
- subscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2764
- this.subscribedToBroadcastChannel = true;
2765
- }
2766
- this.mux(() => {
2767
- this.broadcast(SyncStepOneMessage, { document: this.document, documentName: this.configuration.name });
2768
- this.broadcast(SyncStepTwoMessage, { document: this.document, documentName: this.configuration.name });
2769
- this.broadcast(QueryAwarenessMessage, { document: this.document, documentName: this.configuration.name });
2770
- if (this.awareness) {
2771
- this.broadcast(AwarenessMessage, {
2772
- awareness: this.awareness,
2773
- clients: [this.document.clientID],
2774
- document: this.document,
2775
- documentName: this.configuration.name,
2776
- });
2777
- }
2778
- });
2779
- }
2780
- disconnectBroadcastChannel() {
2781
- // broadcast message with local awareness state set to null (indicating disconnect)
2782
- if (this.awareness) {
2783
- this.send(AwarenessMessage, {
2784
- awareness: this.awareness,
2785
- clients: [this.document.clientID],
2786
- states: new Map(),
2787
- documentName: this.configuration.name,
2788
- }, true);
2789
- }
2790
- if (this.subscribedToBroadcastChannel) {
2791
- unsubscribe(this.broadcastChannel, this.boundBroadcastChannelSubscriber);
2792
- this.subscribedToBroadcastChannel = false;
2793
- }
2794
- }
2795
- broadcast(Message, args) {
2796
- if (!this.configuration.broadcast) {
2797
- return;
2798
- }
2799
- if (!this.subscribedToBroadcastChannel) {
2800
- return;
2801
- }
2802
- new MessageSender(Message, args).broadcast(this.broadcastChannel);
2803
- }
2804
2608
  setAwarenessField(key, value) {
2805
2609
  if (!this.awareness) {
2806
2610
  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.`);
@@ -2809,412 +2613,7 @@ class HocuspocusProvider extends EventEmitter {
2809
2613
  }
2810
2614
  }
2811
2615
 
2812
- /* eslint-env browser */
2813
- const getRandomValues = crypto.getRandomValues.bind(crypto);
2814
-
2815
- /**
2816
- * Isomorphic module for true random numbers / buffers / uuids.
2817
- *
2818
- * Attention: falls back to Math.random if the browser does not support crypto.
2819
- *
2820
- * @module random
2821
- */
2822
-
2823
- const uint32 = () => getRandomValues(new Uint32Array(1))[0];
2824
-
2825
- // @ts-ignore
2826
- const uuidv4Template = [1e7] + -1e3 + -4e3 + -8e3 + -1e11;
2827
-
2828
- /**
2829
- * @return {string}
2830
- */
2831
- const uuidv4 = () => uuidv4Template.replace(/[018]/g, /** @param {number} c */ c =>
2832
- (c ^ uint32() & 15 >> c / 4).toString(16)
2833
- );
2834
-
2835
- class TiptapCollabProviderWebsocket extends HocuspocusProviderWebsocket {
2836
- constructor(configuration) {
2837
- var _a;
2838
- let url = (_a = configuration.baseUrl) !== null && _a !== void 0 ? _a : `wss://${configuration.appId}.collab.tiptap.cloud`;
2839
- if (configuration.shardKey) {
2840
- url += url.includes('?') ? '&' : '?';
2841
- url += `shard=${configuration.shardKey}`;
2842
- }
2843
- super({ ...configuration, url });
2844
- }
2845
- }
2846
-
2847
- const defaultDeleteCommentOptions = {
2848
- deleteContent: false,
2849
- deleteThread: false,
2850
- };
2851
- const defaultGetThreadsOptions = {
2852
- types: ['unarchived'],
2853
- };
2854
- const defaultDeleteThreadOptions = {
2855
- deleteComments: false,
2856
- force: false,
2857
- };
2858
- class TiptapCollabProvider extends HocuspocusProvider {
2859
- constructor(configuration) {
2860
- if (!configuration.websocketProvider) {
2861
- configuration.websocketProvider = new TiptapCollabProviderWebsocket({ appId: configuration.appId, baseUrl: configuration.baseUrl });
2862
- }
2863
- if (!configuration.token) {
2864
- configuration.token = 'notoken'; // need to send a token anyway (which will be ignored)
2865
- }
2866
- super(configuration);
2867
- this.tiptapCollabConfigurationPrefix = '__tiptapcollab__';
2868
- if (configuration.user) {
2869
- this.userData = new Y__namespace.PermanentUserData(this.document, this.document.getMap('__tiptapcollab__users'));
2870
- this.userData.setUserMapping(this.document, this.document.clientID, configuration.user);
2871
- }
2872
- }
2873
- /**
2874
- * note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
2875
- */
2876
- createVersion(name) {
2877
- return this.sendStateless(JSON.stringify({ action: 'version.create', name }));
2878
- }
2879
- /**
2880
- * note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
2881
- */
2882
- revertToVersion(targetVersion) {
2883
- return this.sendStateless(JSON.stringify({ action: 'document.revert', version: targetVersion }));
2884
- }
2885
- /**
2886
- * note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
2887
- *
2888
- * The server will reply with a stateless message (THistoryVersionPreviewEvent)
2889
- */
2890
- previewVersion(targetVersion) {
2891
- return this.sendStateless(JSON.stringify({ action: 'version.preview', version: targetVersion }));
2892
- }
2893
- /**
2894
- * note: this will only work if your server loaded @hocuspocus-pro/extension-history, or if you are on a Tiptap business plan.
2895
- */
2896
- getVersions() {
2897
- return this.configuration.document.getArray(`${this.tiptapCollabConfigurationPrefix}versions`).toArray();
2898
- }
2899
- watchVersions(callback) {
2900
- return this.configuration.document.getArray('__tiptapcollab__versions').observe(callback);
2901
- }
2902
- unwatchVersions(callback) {
2903
- return this.configuration.document.getArray('__tiptapcollab__versions').unobserve(callback);
2904
- }
2905
- isAutoVersioning() {
2906
- return !!this.configuration.document.getMap(`${this.tiptapCollabConfigurationPrefix}config`).get('autoVersioning');
2907
- }
2908
- enableAutoVersioning() {
2909
- return this.configuration.document.getMap(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 1);
2910
- }
2911
- disableAutoVersioning() {
2912
- return this.configuration.document.getMap(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0);
2913
- }
2914
- /**
2915
- * Returns all users in the document as Y.Map objects
2916
- * @returns An array of Y.Map objects
2917
- */
2918
- getYThreads() {
2919
- return this.configuration.document.getArray(`${this.tiptapCollabConfigurationPrefix}threads`);
2920
- }
2921
- /**
2922
- * Finds all threads in the document and returns them as JSON objects
2923
- * @options Options to control the output of the threads (e.g. include deleted threads)
2924
- * @returns An array of threads as JSON objects
2925
- */
2926
- getThreads(options) {
2927
- const { types } = { ...defaultGetThreadsOptions, ...options };
2928
- const threads = this.getYThreads().toJSON();
2929
- if ((types === null || types === void 0 ? void 0 : types.includes('archived')) && (types === null || types === void 0 ? void 0 : types.includes('unarchived'))) {
2930
- return threads;
2931
- }
2932
- return threads.filter(currentThead => {
2933
- if ((types === null || types === void 0 ? void 0 : types.includes('archived')) && currentThead.deletedAt) {
2934
- return true;
2935
- }
2936
- if ((types === null || types === void 0 ? void 0 : types.includes('unarchived')) && !currentThead.deletedAt) {
2937
- return true;
2938
- }
2939
- return false;
2940
- });
2941
- }
2942
- /**
2943
- * Find the index of a thread by its id
2944
- * @param id The thread id
2945
- * @returns The index of the thread or null if not found
2946
- */
2947
- getThreadIndex(id) {
2948
- let index = null;
2949
- let i = 0;
2950
- // eslint-disable-next-line no-restricted-syntax
2951
- for (const thread of this.getThreads({ types: ['archived', 'unarchived'] })) {
2952
- if (thread.id === id) {
2953
- index = i;
2954
- break;
2955
- }
2956
- i += 1;
2957
- }
2958
- return index;
2959
- }
2960
- /**
2961
- * Gets a single thread by its id
2962
- * @param id The thread id
2963
- * @returns The thread as a JSON object or null if not found
2964
- */
2965
- getThread(id) {
2966
- const index = this.getThreadIndex(id);
2967
- if (index === null) {
2968
- return null;
2969
- }
2970
- return this.getYThreads().get(index).toJSON();
2971
- }
2972
- /**
2973
- * Gets a single thread by its id as a Y.Map object
2974
- * @param id The thread id
2975
- * @returns The thread as a Y.Map object or null if not found
2976
- */
2977
- getYThread(id) {
2978
- const index = this.getThreadIndex(id);
2979
- if (index === null) {
2980
- return null;
2981
- }
2982
- return this.getYThreads().get(index);
2983
- }
2984
- /**
2985
- * Create a new thread
2986
- * @param data The thread data
2987
- * @returns The created thread
2988
- */
2989
- createThread(data) {
2990
- let createdThread = {};
2991
- this.document.transact(() => {
2992
- const thread = new Y__namespace.Map();
2993
- thread.set('id', uuidv4());
2994
- thread.set('createdAt', (new Date()).toISOString());
2995
- thread.set('comments', new Y__namespace.Array());
2996
- thread.set('deletedComments', new Y__namespace.Array());
2997
- thread.set('deletedAt', null);
2998
- this.getYThreads().push([thread]);
2999
- createdThread = this.updateThread(String(thread.get('id')), data);
3000
- });
3001
- return createdThread;
3002
- }
3003
- /**
3004
- * Update a specific thread
3005
- * @param id The thread id
3006
- * @param data New data for the thread
3007
- * @returns The updated thread or null if the thread is not found
3008
- */
3009
- updateThread(id, data) {
3010
- let updatedThread = {};
3011
- this.document.transact(() => {
3012
- const thread = this.getYThread(id);
3013
- if (thread === null) {
3014
- return null;
3015
- }
3016
- thread.set('updatedAt', (new Date()).toISOString());
3017
- if (data.data) {
3018
- thread.set('data', data.data);
3019
- }
3020
- if (data.resolvedAt || data.resolvedAt === null) {
3021
- thread.set('resolvedAt', data.resolvedAt);
3022
- }
3023
- updatedThread = thread.toJSON();
3024
- });
3025
- return updatedThread;
3026
- }
3027
- /**
3028
- * Handle the deletion of a thread. By default, the thread and it's comments are not deleted, but marked as deleted
3029
- * via the `deletedAt` property. Forceful deletion can be enabled by setting the `force` option to `true`.
3030
- *
3031
- * If you only want to delete the comments of a thread, you can set the `deleteComments` option to `true`.
3032
- * @param id The thread id
3033
- * @param options A set of options that control how the thread is deleted
3034
- * @returns The deleted thread or null if the thread is not found
3035
- */
3036
- deleteThread(id, options) {
3037
- const { deleteComments, force } = { ...defaultDeleteThreadOptions, ...options };
3038
- const index = this.getThreadIndex(id);
3039
- if (index === null) {
3040
- return null;
3041
- }
3042
- if (force) {
3043
- this.getYThreads().delete(index, 1);
3044
- return;
3045
- }
3046
- const thread = this.getYThreads().get(index);
3047
- thread.set('deletedAt', (new Date()).toISOString());
3048
- if (deleteComments) {
3049
- thread.set('comments', new Y__namespace.Array());
3050
- thread.set('deletedComments', new Y__namespace.Array());
3051
- }
3052
- return thread.toJSON();
3053
- }
3054
- /**
3055
- * Tries to restore a deleted thread
3056
- * @param id The thread id
3057
- * @returns The restored thread or null if the thread is not found
3058
- */
3059
- restoreThread(id) {
3060
- const index = this.getThreadIndex(id);
3061
- if (index === null) {
3062
- return null;
3063
- }
3064
- const thread = this.getYThreads().get(index);
3065
- thread.set('deletedAt', null);
3066
- return thread.toJSON();
3067
- }
3068
- /**
3069
- * Returns comments from a thread, either deleted or not
3070
- * @param threadId The thread id
3071
- * @param includeDeleted If you want to include deleted comments, defaults to `false`
3072
- * @returns The comments or null if the thread is not found
3073
- */
3074
- getThreadComments(threadId, includeDeleted) {
3075
- var _a, _b, _c;
3076
- const index = this.getThreadIndex(threadId);
3077
- if (index === null) {
3078
- return null;
3079
- }
3080
- const comments = !includeDeleted ? (_a = this.getThread(threadId)) === null || _a === void 0 ? void 0 : _a.comments : [...(((_b = this.getThread(threadId)) === null || _b === void 0 ? void 0 : _b.comments) || []), ...(((_c = this.getThread(threadId)) === null || _c === void 0 ? void 0 : _c.deletedComments) || [])].sort((a, b) => {
3081
- return a.createdAt.localeCompare(b.createdAt);
3082
- });
3083
- return comments !== null && comments !== void 0 ? comments : [];
3084
- }
3085
- /**
3086
- * Get a single comment from a specific thread
3087
- * @param threadId The thread id
3088
- * @param commentId The comment id
3089
- * @param includeDeleted If you want to include deleted comments in the search
3090
- * @returns The comment or null if not found
3091
- */
3092
- getThreadComment(threadId, commentId, includeDeleted) {
3093
- var _a;
3094
- const index = this.getThreadIndex(threadId);
3095
- if (index === null) {
3096
- return null;
3097
- }
3098
- const comments = this.getThreadComments(threadId, includeDeleted);
3099
- return (_a = comments === null || comments === void 0 ? void 0 : comments.find(comment => comment.id === commentId)) !== null && _a !== void 0 ? _a : null;
3100
- }
3101
- /**
3102
- * Adds a comment to a thread
3103
- * @param threadId The thread id
3104
- * @param data The comment data
3105
- * @returns The updated thread or null if the thread is not found
3106
- * @example addComment('123', { content: 'Hello world', data: { author: 'Maria Doe' } })
3107
- */
3108
- addComment(threadId, data) {
3109
- let updatedThread = {};
3110
- this.document.transact(() => {
3111
- const thread = this.getYThread(threadId);
3112
- if (thread === null)
3113
- return null;
3114
- const commentMap = new Y__namespace.Map();
3115
- commentMap.set('id', uuidv4());
3116
- commentMap.set('createdAt', (new Date()).toISOString());
3117
- thread.get('comments').push([commentMap]);
3118
- this.updateComment(threadId, String(commentMap.get('id')), data);
3119
- updatedThread = thread.toJSON();
3120
- });
3121
- return updatedThread;
3122
- }
3123
- /**
3124
- * Update a comment in a thread
3125
- * @param threadId The thread id
3126
- * @param commentId The comment id
3127
- * @param data The new comment data
3128
- * @returns The updated thread or null if the thread or comment is not found
3129
- * @example updateComment('123', { content: 'The new content', data: { attachments: ['file1.jpg'] }})
3130
- */
3131
- updateComment(threadId, commentId, data) {
3132
- let updatedThread = {};
3133
- this.document.transact(() => {
3134
- const thread = this.getYThread(threadId);
3135
- if (thread === null)
3136
- return null;
3137
- let comment = null;
3138
- // eslint-disable-next-line no-restricted-syntax
3139
- for (const c of thread.get('comments')) {
3140
- if (c.get('id') === commentId) {
3141
- comment = c;
3142
- break;
3143
- }
3144
- }
3145
- if (comment === null)
3146
- return null;
3147
- comment.set('updatedAt', (new Date()).toISOString());
3148
- if (data.data) {
3149
- comment.set('data', data.data);
3150
- }
3151
- if (data.content) {
3152
- comment.set('content', data.content);
3153
- }
3154
- updatedThread = thread.toJSON();
3155
- });
3156
- return updatedThread;
3157
- }
3158
- /**
3159
- * Deletes a comment from a thread
3160
- * @param threadId The thread id
3161
- * @param commentId The comment id
3162
- * @param options A set of options that control how the comment is deleted
3163
- * @returns The updated thread or null if the thread or comment is not found
3164
- */
3165
- deleteComment(threadId, commentId, options) {
3166
- const { deleteContent, deleteThread } = { ...defaultDeleteCommentOptions, ...options };
3167
- const thread = this.getYThread(threadId);
3168
- if (thread === null)
3169
- return null;
3170
- let commentIndex = 0;
3171
- // eslint-disable-next-line no-restricted-syntax
3172
- for (const c of thread.get('comments')) {
3173
- if (c.get('id') === commentId) {
3174
- break;
3175
- }
3176
- commentIndex += 1;
3177
- }
3178
- // if the first comment of a thread is deleted we also
3179
- // delete the thread itself as the source comment is gone
3180
- if (commentIndex === 0 && (deleteThread || this.configuration.deleteThreadOnFirstCommentDelete)) {
3181
- this.deleteThread(threadId);
3182
- return;
3183
- }
3184
- const comment = thread.get('comments').get(commentIndex);
3185
- const newComment = new Y__namespace.Map();
3186
- newComment.set('id', comment.get('id'));
3187
- newComment.set('createdAt', comment.get('createdAt'));
3188
- newComment.set('updatedAt', (new Date()).toISOString());
3189
- newComment.set('deletedAt', (new Date()).toISOString());
3190
- newComment.set('data', comment.get('data'));
3191
- newComment.set('content', deleteContent ? null : comment.get('content'));
3192
- if (!thread.get('deletedComments')) {
3193
- thread.set('deletedComments', new Y__namespace.Array());
3194
- }
3195
- thread.get('deletedComments').push([newComment]);
3196
- thread.get('comments').delete(commentIndex);
3197
- return thread.toJSON();
3198
- }
3199
- /**
3200
- * Start watching threads for changes
3201
- * @param callback The callback function to be called when a thread changes
3202
- */
3203
- watchThreads(callback) {
3204
- this.getYThreads().observeDeep(callback);
3205
- }
3206
- /**
3207
- * Stop watching threads for changes
3208
- * @param callback The callback function to be removed
3209
- */
3210
- unwatchThreads(callback) {
3211
- this.getYThreads().unobserveDeep(callback);
3212
- }
3213
- }
3214
-
3215
2616
  exports.AwarenessError = AwarenessError;
3216
2617
  exports.HocuspocusProvider = HocuspocusProvider;
3217
2618
  exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket;
3218
- exports.TiptapCollabProvider = TiptapCollabProvider;
3219
- exports.TiptapCollabProviderWebsocket = TiptapCollabProviderWebsocket;
3220
2619
  //# sourceMappingURL=hocuspocus-provider.cjs.map