@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.
- package/dist/hocuspocus-provider.cjs +931 -1532
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +931 -1527
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/extension-database/src/Database.d.ts +1 -1
- package/dist/packages/extension-logger/src/Logger.d.ts +1 -1
- package/dist/packages/extension-redis/src/Redis.d.ts +4 -3
- package/dist/packages/extension-sqlite/src/SQLite.d.ts +2 -1
- package/dist/packages/extension-throttle/src/index.d.ts +2 -2
- package/dist/packages/extension-webhook/src/index.d.ts +3 -3
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +12 -45
- package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +5 -9
- package/dist/packages/provider/src/IncomingMessage.d.ts +3 -3
- package/dist/packages/provider/src/MessageReceiver.d.ts +2 -4
- package/dist/packages/provider/src/MessageSender.d.ts +2 -2
- package/dist/packages/provider/src/OutgoingMessage.d.ts +2 -2
- package/dist/packages/provider/src/OutgoingMessages/AuthenticationMessage.d.ts +2 -1
- package/dist/packages/provider/src/OutgoingMessages/AwarenessMessage.d.ts +2 -1
- package/dist/packages/provider/src/OutgoingMessages/CloseMessage.d.ts +2 -1
- package/dist/packages/provider/src/OutgoingMessages/QueryAwarenessMessage.d.ts +2 -1
- package/dist/packages/provider/src/OutgoingMessages/StatelessMessage.d.ts +2 -1
- package/dist/packages/provider/src/OutgoingMessages/SyncStepOneMessage.d.ts +2 -1
- package/dist/packages/provider/src/OutgoingMessages/SyncStepTwoMessage.d.ts +2 -1
- package/dist/packages/provider/src/OutgoingMessages/UpdateMessage.d.ts +2 -1
- package/dist/packages/provider/src/index.d.ts +0 -2
- package/dist/packages/provider/src/types.d.ts +12 -12
- package/dist/packages/server/src/ClientConnection.d.ts +19 -10
- package/dist/packages/server/src/Connection.d.ts +7 -23
- package/dist/packages/server/src/DirectConnection.d.ts +2 -2
- package/dist/packages/server/src/Document.d.ts +3 -7
- package/dist/packages/server/src/Hocuspocus.d.ts +7 -36
- package/dist/packages/server/src/IncomingMessage.d.ts +3 -3
- package/dist/packages/server/src/MessageReceiver.d.ts +4 -6
- package/dist/packages/server/src/OutgoingMessage.d.ts +4 -3
- package/dist/packages/server/src/Server.d.ts +23 -3
- package/dist/packages/server/src/index.d.ts +1 -1
- package/dist/packages/server/src/types.d.ts +15 -29
- package/dist/packages/server/src/util/getParameters.d.ts +1 -1
- package/dist/packages/transformer/src/Prosemirror.d.ts +1 -1
- package/dist/packages/transformer/src/Tiptap.d.ts +3 -3
- package/dist/packages/transformer/src/types.d.ts +1 -1
- package/dist/tests/utils/newHocuspocus.d.ts +2 -2
- package/dist/tests/utils/newHocuspocusProvider.d.ts +2 -2
- package/dist/tests/utils/newHocuspocusProviderWebsocket.d.ts +4 -3
- package/dist/tests/utils/retryableAssertion.d.ts +1 -1
- package/package.json +2 -2
- package/src/EventEmitter.ts +3 -1
- package/src/HocuspocusProvider.ts +74 -200
- package/src/HocuspocusProviderWebsocket.ts +24 -75
- package/src/IncomingMessage.ts +5 -3
- package/src/MessageReceiver.ts +20 -19
- package/src/MessageSender.ts +3 -2
- package/src/OutgoingMessage.ts +3 -2
- package/src/OutgoingMessages/AuthenticationMessage.ts +2 -1
- package/src/OutgoingMessages/AwarenessMessage.ts +2 -1
- package/src/OutgoingMessages/CloseMessage.ts +2 -1
- package/src/OutgoingMessages/QueryAwarenessMessage.ts +2 -1
- package/src/OutgoingMessages/StatelessMessage.ts +2 -1
- package/src/OutgoingMessages/SyncStepOneMessage.ts +2 -1
- package/src/OutgoingMessages/SyncStepTwoMessage.ts +2 -1
- package/src/OutgoingMessages/UpdateMessage.ts +2 -1
- package/src/index.ts +0 -2
- package/src/types.ts +12 -12
- package/dist/packages/provider/src/TiptapCollabProvider.d.ts +0 -161
- package/dist/packages/provider/src/TiptapCollabProviderWebsocket.d.ts +0 -19
- package/dist/packages/server/src/Debugger.d.ts +0 -14
- package/dist/tests/server/getMessageLogs.d.ts +0 -1
- package/dist/tests/server/requiresAuthentication.d.ts +0 -1
- package/src/TiptapCollabProvider.ts +0 -505
- package/src/TiptapCollabProviderWebsocket.ts +0 -38
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { WsReadyStates,
|
|
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
|
-
*
|
|
6
|
+
* Common Math expressions.
|
|
7
7
|
*
|
|
8
|
-
* @module
|
|
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
|
-
* @
|
|
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
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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$
|
|
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
|
-
*
|
|
146
|
+
* Efficient schema-less binary encoding with support for variable length encoding.
|
|
146
147
|
*
|
|
147
|
-
*
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
* @
|
|
189
|
+
* @function
|
|
190
|
+
* @return {Encoder}
|
|
193
191
|
*/
|
|
194
|
-
|
|
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
|
-
*
|
|
195
|
+
* The current length of the encoded data.
|
|
196
|
+
*
|
|
197
|
+
* @function
|
|
198
|
+
* @param {Encoder} encoder
|
|
199
|
+
* @return {number}
|
|
209
200
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
220
|
-
const
|
|
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
|
-
*
|
|
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
|
-
|
|
229
|
-
const
|
|
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
|
-
*
|
|
246
|
+
* Write a variable length unsigned integer. Max encodable integer is 2^53.
|
|
233
247
|
*
|
|
234
|
-
* @
|
|
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
|
-
*
|
|
261
|
+
* A cache to store strings temporarily
|
|
239
262
|
*/
|
|
240
|
-
const
|
|
263
|
+
const _strBuffer = new Uint8Array(30000);
|
|
264
|
+
const _maxStrBSize = _strBuffer.length / 3;
|
|
241
265
|
|
|
242
266
|
/**
|
|
243
|
-
*
|
|
267
|
+
* Write a variable length string.
|
|
244
268
|
*
|
|
245
|
-
* @
|
|
246
|
-
* @param {
|
|
247
|
-
* @param {
|
|
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
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
268
|
-
* @param {
|
|
269
|
-
* @
|
|
290
|
+
* @function
|
|
291
|
+
* @param {Encoder} encoder
|
|
292
|
+
* @param {String} str The string that is to be encoded.
|
|
270
293
|
*/
|
|
271
|
-
const
|
|
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
|
-
*
|
|
304
|
+
* Write a variable length string.
|
|
275
305
|
*
|
|
276
|
-
* @
|
|
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
|
-
*
|
|
314
|
+
* Append fixed-length Uint8Array to the encoder.
|
|
281
315
|
*
|
|
282
|
-
* @
|
|
283
|
-
* @param {
|
|
284
|
-
* @
|
|
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
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
*
|
|
352
|
+
* Error helpers.
|
|
486
353
|
*
|
|
487
|
-
* @module
|
|
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
|
-
*
|
|
516
|
-
*
|
|
517
|
-
* @module number
|
|
358
|
+
* @param {string} s
|
|
359
|
+
* @return {Error}
|
|
518
360
|
*/
|
|
519
|
-
|
|
520
|
-
const
|
|
361
|
+
/* c8 ignore next */
|
|
362
|
+
const create$1 = s => new Error(s);
|
|
521
363
|
|
|
522
364
|
/**
|
|
523
|
-
* Efficient schema-less binary
|
|
365
|
+
* Efficient schema-less binary decoding with support for variable length encoding.
|
|
524
366
|
*
|
|
525
|
-
* Use [lib0/
|
|
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
|
|
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
|
|
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
|
|
607
|
+
* Utility module to work with time.
|
|
983
608
|
*
|
|
984
|
-
* @module
|
|
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
|
-
*
|
|
614
|
+
* Return current unix time.
|
|
994
615
|
*
|
|
995
|
-
* @
|
|
996
|
-
* @param {number} byteOffset
|
|
997
|
-
* @param {number} length
|
|
616
|
+
* @return {number}
|
|
998
617
|
*/
|
|
999
|
-
const
|
|
618
|
+
const getUnixTime = Date.now;
|
|
1000
619
|
|
|
1001
620
|
/**
|
|
1002
|
-
*
|
|
621
|
+
* Utility module to work with key-value stores.
|
|
1003
622
|
*
|
|
1004
|
-
* @
|
|
623
|
+
* @module map
|
|
1005
624
|
*/
|
|
1006
|
-
const createUint8ArrayFromArrayBuffer = buffer => new Uint8Array(buffer);
|
|
1007
625
|
|
|
1008
|
-
/* c8 ignore start */
|
|
1009
626
|
/**
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
627
|
+
* Creates a new Map instance.
|
|
628
|
+
*
|
|
629
|
+
* @function
|
|
630
|
+
* @return {Map<any, any>}
|
|
631
|
+
*
|
|
632
|
+
* @function
|
|
1012
633
|
*/
|
|
1013
|
-
const
|
|
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
|
-
*
|
|
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
|
|
1185
|
-
*
|
|
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
|
-
* @
|
|
1194
|
-
* @
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
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
|
|
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$
|
|
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
|
|
731
|
+
return from((this._observers.get(name) || create()).values()).forEach(f => f(...args))
|
|
1296
732
|
}
|
|
1297
733
|
|
|
1298
734
|
destroy () {
|
|
1299
|
-
this._observers = create
|
|
735
|
+
this._observers = create();
|
|
1300
736
|
}
|
|
1301
737
|
}
|
|
1302
738
|
/* c8 ignore end */
|
|
1303
739
|
|
|
1304
740
|
/**
|
|
1305
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
755
|
+
* @template R
|
|
756
|
+
* @param {Object<string,any>} obj
|
|
757
|
+
* @param {function(any,string):R} f
|
|
758
|
+
* @return {Array<R>}
|
|
1333
759
|
*/
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
2211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2505
|
-
|
|
2506
|
-
|
|
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
|
|
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
|
|
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 }
|
|
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
|
-
}
|
|
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
|
-
|
|
2594
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
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
|
|
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(
|
|
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.
|
|
2707
|
-
this.
|
|
2708
|
-
|
|
2559
|
+
this.configuration.websocketProvider.detach(this);
|
|
2560
|
+
this.isAttached = false;
|
|
2561
|
+
}
|
|
2562
|
+
attach() {
|
|
2563
|
+
if (this.isAttached)
|
|
2709
2564
|
return;
|
|
2710
|
-
|
|
2711
|
-
|
|
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
|
-
|
|
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
|