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