@abraca/dabra 0.1.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 +3237 -0
- package/dist/hocuspocus-provider.cjs.map +1 -0
- package/dist/hocuspocus-provider.esm.js +3199 -0
- package/dist/hocuspocus-provider.esm.js.map +1 -0
- package/dist/index.d.ts +784 -0
- package/package.json +42 -0
- package/src/AbracadabraProvider.ts +381 -0
- package/src/CryptoIdentityKeystore.ts +294 -0
- package/src/EventEmitter.ts +44 -0
- package/src/HocuspocusProvider.ts +603 -0
- package/src/HocuspocusProviderWebsocket.ts +533 -0
- package/src/IncomingMessage.ts +63 -0
- package/src/MessageReceiver.ts +139 -0
- package/src/MessageSender.ts +22 -0
- package/src/OfflineStore.ts +185 -0
- package/src/OutgoingMessage.ts +25 -0
- package/src/OutgoingMessages/AuthenticationMessage.ts +25 -0
- package/src/OutgoingMessages/AwarenessMessage.ts +41 -0
- package/src/OutgoingMessages/CloseMessage.ts +17 -0
- package/src/OutgoingMessages/QueryAwarenessMessage.ts +17 -0
- package/src/OutgoingMessages/StatelessMessage.ts +18 -0
- package/src/OutgoingMessages/SubdocMessage.ts +35 -0
- package/src/OutgoingMessages/SyncStepOneMessage.ts +25 -0
- package/src/OutgoingMessages/SyncStepTwoMessage.ts +25 -0
- package/src/OutgoingMessages/UpdateMessage.ts +20 -0
- package/src/index.ts +7 -0
- package/src/types.ts +144 -0
|
@@ -0,0 +1,3237 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let yjs = require("yjs");
|
|
30
|
+
yjs = __toESM(yjs);
|
|
31
|
+
let _lifeomic_attempt = require("@lifeomic/attempt");
|
|
32
|
+
let _noble_ed25519 = require("@noble/ed25519");
|
|
33
|
+
_noble_ed25519 = __toESM(_noble_ed25519);
|
|
34
|
+
|
|
35
|
+
//#region node_modules/lib0/math.js
|
|
36
|
+
/**
|
|
37
|
+
* Common Math expressions.
|
|
38
|
+
*
|
|
39
|
+
* @module math
|
|
40
|
+
*/
|
|
41
|
+
const floor = Math.floor;
|
|
42
|
+
/**
|
|
43
|
+
* @function
|
|
44
|
+
* @param {number} a
|
|
45
|
+
* @param {number} b
|
|
46
|
+
* @return {number} The smaller element of a and b
|
|
47
|
+
*/
|
|
48
|
+
const min = (a, b) => a < b ? a : b;
|
|
49
|
+
/**
|
|
50
|
+
* @function
|
|
51
|
+
* @param {number} a
|
|
52
|
+
* @param {number} b
|
|
53
|
+
* @return {number} The bigger element of a and b
|
|
54
|
+
*/
|
|
55
|
+
const max = (a, b) => a > b ? a : b;
|
|
56
|
+
const isNaN$1 = Number.isNaN;
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region node_modules/lib0/binary.js
|
|
60
|
+
const BIT7 = 64;
|
|
61
|
+
const BIT8 = 128;
|
|
62
|
+
const BIT18 = 1 << 17;
|
|
63
|
+
const BIT19 = 1 << 18;
|
|
64
|
+
const BIT20 = 1 << 19;
|
|
65
|
+
const BIT21 = 1 << 20;
|
|
66
|
+
const BIT22 = 1 << 21;
|
|
67
|
+
const BIT23 = 1 << 22;
|
|
68
|
+
const BIT24 = 1 << 23;
|
|
69
|
+
const BIT25 = 1 << 24;
|
|
70
|
+
const BIT26 = 1 << 25;
|
|
71
|
+
const BIT27 = 1 << 26;
|
|
72
|
+
const BIT28 = 1 << 27;
|
|
73
|
+
const BIT29 = 1 << 28;
|
|
74
|
+
const BIT30 = 1 << 29;
|
|
75
|
+
const BIT31 = 1 << 30;
|
|
76
|
+
const BIT32 = 1 << 31;
|
|
77
|
+
const BITS6 = 63;
|
|
78
|
+
const BITS7 = 127;
|
|
79
|
+
const BITS17 = BIT18 - 1;
|
|
80
|
+
const BITS18 = BIT19 - 1;
|
|
81
|
+
const BITS19 = BIT20 - 1;
|
|
82
|
+
const BITS20 = BIT21 - 1;
|
|
83
|
+
const BITS21 = BIT22 - 1;
|
|
84
|
+
const BITS22 = BIT23 - 1;
|
|
85
|
+
const BITS23 = BIT24 - 1;
|
|
86
|
+
const BITS24 = BIT25 - 1;
|
|
87
|
+
const BITS25 = BIT26 - 1;
|
|
88
|
+
const BITS26 = BIT27 - 1;
|
|
89
|
+
const BITS27 = BIT28 - 1;
|
|
90
|
+
const BITS28 = BIT29 - 1;
|
|
91
|
+
const BITS29 = BIT30 - 1;
|
|
92
|
+
const BITS30 = BIT31 - 1;
|
|
93
|
+
/**
|
|
94
|
+
* @type {number}
|
|
95
|
+
*/
|
|
96
|
+
const BITS31 = 2147483647;
|
|
97
|
+
/**
|
|
98
|
+
* @type {number}
|
|
99
|
+
*/
|
|
100
|
+
const BITS32 = 4294967295;
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region node_modules/lib0/number.js
|
|
104
|
+
/**
|
|
105
|
+
* Utility helpers for working with numbers.
|
|
106
|
+
*
|
|
107
|
+
* @module number
|
|
108
|
+
*/
|
|
109
|
+
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
110
|
+
const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER;
|
|
111
|
+
const LOWEST_INT32 = 1 << 31;
|
|
112
|
+
const HIGHEST_INT32 = BITS31;
|
|
113
|
+
const HIGHEST_UINT32 = BITS32;
|
|
114
|
+
/* c8 ignore next */
|
|
115
|
+
const isInteger = Number.isInteger || ((num) => typeof num === "number" && isFinite(num) && floor(num) === num);
|
|
116
|
+
const isNaN = Number.isNaN;
|
|
117
|
+
const parseInt = Number.parseInt;
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region node_modules/lib0/set.js
|
|
121
|
+
/**
|
|
122
|
+
* Utility module to work with sets.
|
|
123
|
+
*
|
|
124
|
+
* @module set
|
|
125
|
+
*/
|
|
126
|
+
const create$2 = () => /* @__PURE__ */ new Set();
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region node_modules/lib0/array.js
|
|
130
|
+
/**
|
|
131
|
+
* Transforms something array-like to an actual Array.
|
|
132
|
+
*
|
|
133
|
+
* @function
|
|
134
|
+
* @template T
|
|
135
|
+
* @param {ArrayLike<T>|Iterable<T>} arraylike
|
|
136
|
+
* @return {T}
|
|
137
|
+
*/
|
|
138
|
+
const from = Array.from;
|
|
139
|
+
const isArray$1 = Array.isArray;
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region node_modules/lib0/string.js
|
|
143
|
+
/**
|
|
144
|
+
* Utility module to work with strings.
|
|
145
|
+
*
|
|
146
|
+
* @module string
|
|
147
|
+
*/
|
|
148
|
+
const fromCharCode = String.fromCharCode;
|
|
149
|
+
const fromCodePoint = String.fromCodePoint;
|
|
150
|
+
/**
|
|
151
|
+
* The largest utf16 character.
|
|
152
|
+
* Corresponds to Uint8Array([255, 255]) or charcodeof(2x2^8)
|
|
153
|
+
*/
|
|
154
|
+
const MAX_UTF16_CHARACTER = fromCharCode(65535);
|
|
155
|
+
/**
|
|
156
|
+
* @param {string} str
|
|
157
|
+
* @return {Uint8Array<ArrayBuffer>}
|
|
158
|
+
*/
|
|
159
|
+
const _encodeUtf8Polyfill = (str) => {
|
|
160
|
+
const encodedString = unescape(encodeURIComponent(str));
|
|
161
|
+
const len = encodedString.length;
|
|
162
|
+
const buf = new Uint8Array(len);
|
|
163
|
+
for (let i = 0; i < len; i++) buf[i] = encodedString.codePointAt(i);
|
|
164
|
+
return buf;
|
|
165
|
+
};
|
|
166
|
+
/* c8 ignore next */
|
|
167
|
+
const utf8TextEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
168
|
+
/**
|
|
169
|
+
* @param {string} str
|
|
170
|
+
* @return {Uint8Array<ArrayBuffer>}
|
|
171
|
+
*/
|
|
172
|
+
const _encodeUtf8Native = (str) => utf8TextEncoder.encode(str);
|
|
173
|
+
/**
|
|
174
|
+
* @param {string} str
|
|
175
|
+
* @return {Uint8Array}
|
|
176
|
+
*/
|
|
177
|
+
/* c8 ignore next */
|
|
178
|
+
const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill;
|
|
179
|
+
/* c8 ignore next */
|
|
180
|
+
let utf8TextDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder("utf-8", {
|
|
181
|
+
fatal: true,
|
|
182
|
+
ignoreBOM: true
|
|
183
|
+
});
|
|
184
|
+
/* c8 ignore start */
|
|
185
|
+
if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1)
|
|
186
|
+
/* c8 ignore next */
|
|
187
|
+
utf8TextDecoder = null;
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region node_modules/lib0/encoding.js
|
|
191
|
+
/**
|
|
192
|
+
* Efficient schema-less binary encoding with support for variable length encoding.
|
|
193
|
+
*
|
|
194
|
+
* Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
|
|
195
|
+
*
|
|
196
|
+
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
197
|
+
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
198
|
+
* which is also used in Protocol Buffers.
|
|
199
|
+
*
|
|
200
|
+
* ```js
|
|
201
|
+
* // encoding step
|
|
202
|
+
* const encoder = encoding.createEncoder()
|
|
203
|
+
* encoding.writeVarUint(encoder, 256)
|
|
204
|
+
* encoding.writeVarString(encoder, 'Hello world!')
|
|
205
|
+
* const buf = encoding.toUint8Array(encoder)
|
|
206
|
+
* ```
|
|
207
|
+
*
|
|
208
|
+
* ```js
|
|
209
|
+
* // decoding step
|
|
210
|
+
* const decoder = decoding.createDecoder(buf)
|
|
211
|
+
* decoding.readVarUint(decoder) // => 256
|
|
212
|
+
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
213
|
+
* decoding.hasContent(decoder) // => false - all data is read
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @module encoding
|
|
217
|
+
*/
|
|
218
|
+
/**
|
|
219
|
+
* A BinaryEncoder handles the encoding to an Uint8Array.
|
|
220
|
+
*/
|
|
221
|
+
var Encoder = class {
|
|
222
|
+
constructor() {
|
|
223
|
+
this.cpos = 0;
|
|
224
|
+
this.cbuf = new Uint8Array(100);
|
|
225
|
+
/**
|
|
226
|
+
* @type {Array<Uint8Array>}
|
|
227
|
+
*/
|
|
228
|
+
this.bufs = [];
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* @function
|
|
233
|
+
* @return {Encoder}
|
|
234
|
+
*/
|
|
235
|
+
const createEncoder = () => new Encoder();
|
|
236
|
+
/**
|
|
237
|
+
* The current length of the encoded data.
|
|
238
|
+
*
|
|
239
|
+
* @function
|
|
240
|
+
* @param {Encoder} encoder
|
|
241
|
+
* @return {number}
|
|
242
|
+
*/
|
|
243
|
+
const length = (encoder) => {
|
|
244
|
+
let len = encoder.cpos;
|
|
245
|
+
for (let i = 0; i < encoder.bufs.length; i++) len += encoder.bufs[i].length;
|
|
246
|
+
return len;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Transform to Uint8Array.
|
|
250
|
+
*
|
|
251
|
+
* @function
|
|
252
|
+
* @param {Encoder} encoder
|
|
253
|
+
* @return {Uint8Array<ArrayBuffer>} The created ArrayBuffer.
|
|
254
|
+
*/
|
|
255
|
+
const toUint8Array = (encoder) => {
|
|
256
|
+
const uint8arr = new Uint8Array(length(encoder));
|
|
257
|
+
let curPos = 0;
|
|
258
|
+
for (let i = 0; i < encoder.bufs.length; i++) {
|
|
259
|
+
const d = encoder.bufs[i];
|
|
260
|
+
uint8arr.set(d, curPos);
|
|
261
|
+
curPos += d.length;
|
|
262
|
+
}
|
|
263
|
+
uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
|
|
264
|
+
return uint8arr;
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Write one byte to the encoder.
|
|
268
|
+
*
|
|
269
|
+
* @function
|
|
270
|
+
* @param {Encoder} encoder
|
|
271
|
+
* @param {number} num The byte that is to be encoded.
|
|
272
|
+
*/
|
|
273
|
+
const write = (encoder, num) => {
|
|
274
|
+
const bufferLen = encoder.cbuf.length;
|
|
275
|
+
if (encoder.cpos === bufferLen) {
|
|
276
|
+
encoder.bufs.push(encoder.cbuf);
|
|
277
|
+
encoder.cbuf = new Uint8Array(bufferLen * 2);
|
|
278
|
+
encoder.cpos = 0;
|
|
279
|
+
}
|
|
280
|
+
encoder.cbuf[encoder.cpos++] = num;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Write a variable length unsigned integer. Max encodable integer is 2^53.
|
|
284
|
+
*
|
|
285
|
+
* @function
|
|
286
|
+
* @param {Encoder} encoder
|
|
287
|
+
* @param {number} num The number that is to be encoded.
|
|
288
|
+
*/
|
|
289
|
+
const writeVarUint = (encoder, num) => {
|
|
290
|
+
while (num > BITS7) {
|
|
291
|
+
write(encoder, BIT8 | BITS7 & num);
|
|
292
|
+
num = floor(num / 128);
|
|
293
|
+
}
|
|
294
|
+
write(encoder, BITS7 & num);
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* A cache to store strings temporarily
|
|
298
|
+
*/
|
|
299
|
+
const _strBuffer = new Uint8Array(3e4);
|
|
300
|
+
const _maxStrBSize = _strBuffer.length / 3;
|
|
301
|
+
/**
|
|
302
|
+
* Write a variable length string.
|
|
303
|
+
*
|
|
304
|
+
* @function
|
|
305
|
+
* @param {Encoder} encoder
|
|
306
|
+
* @param {String} str The string that is to be encoded.
|
|
307
|
+
*/
|
|
308
|
+
const _writeVarStringNative = (encoder, str) => {
|
|
309
|
+
if (str.length < _maxStrBSize) {
|
|
310
|
+
/* c8 ignore next */
|
|
311
|
+
const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
|
|
312
|
+
writeVarUint(encoder, written);
|
|
313
|
+
for (let i = 0; i < written; i++) write(encoder, _strBuffer[i]);
|
|
314
|
+
} else writeVarUint8Array(encoder, encodeUtf8(str));
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Write a variable length string.
|
|
318
|
+
*
|
|
319
|
+
* @function
|
|
320
|
+
* @param {Encoder} encoder
|
|
321
|
+
* @param {String} str The string that is to be encoded.
|
|
322
|
+
*/
|
|
323
|
+
const _writeVarStringPolyfill = (encoder, str) => {
|
|
324
|
+
const encodedString = unescape(encodeURIComponent(str));
|
|
325
|
+
const len = encodedString.length;
|
|
326
|
+
writeVarUint(encoder, len);
|
|
327
|
+
for (let i = 0; i < len; i++) write(encoder, encodedString.codePointAt(i));
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Write a variable length string.
|
|
331
|
+
*
|
|
332
|
+
* @function
|
|
333
|
+
* @param {Encoder} encoder
|
|
334
|
+
* @param {String} str The string that is to be encoded.
|
|
335
|
+
*/
|
|
336
|
+
/* c8 ignore next */
|
|
337
|
+
const writeVarString = utf8TextEncoder && utf8TextEncoder.encodeInto ? _writeVarStringNative : _writeVarStringPolyfill;
|
|
338
|
+
/**
|
|
339
|
+
* Append fixed-length Uint8Array to the encoder.
|
|
340
|
+
*
|
|
341
|
+
* @function
|
|
342
|
+
* @param {Encoder} encoder
|
|
343
|
+
* @param {Uint8Array} uint8Array
|
|
344
|
+
*/
|
|
345
|
+
const writeUint8Array = (encoder, uint8Array) => {
|
|
346
|
+
const bufferLen = encoder.cbuf.length;
|
|
347
|
+
const cpos = encoder.cpos;
|
|
348
|
+
const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
|
|
349
|
+
const rightCopyLen = uint8Array.length - leftCopyLen;
|
|
350
|
+
encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
|
|
351
|
+
encoder.cpos += leftCopyLen;
|
|
352
|
+
if (rightCopyLen > 0) {
|
|
353
|
+
encoder.bufs.push(encoder.cbuf);
|
|
354
|
+
encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
|
|
355
|
+
encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
|
|
356
|
+
encoder.cpos = rightCopyLen;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* Append an Uint8Array to Encoder.
|
|
361
|
+
*
|
|
362
|
+
* @function
|
|
363
|
+
* @param {Encoder} encoder
|
|
364
|
+
* @param {Uint8Array} uint8Array
|
|
365
|
+
*/
|
|
366
|
+
const writeVarUint8Array = (encoder, uint8Array) => {
|
|
367
|
+
writeVarUint(encoder, uint8Array.byteLength);
|
|
368
|
+
writeUint8Array(encoder, uint8Array);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region node_modules/lib0/error.js
|
|
373
|
+
/**
|
|
374
|
+
* Error helpers.
|
|
375
|
+
*
|
|
376
|
+
* @module error
|
|
377
|
+
*/
|
|
378
|
+
/**
|
|
379
|
+
* @param {string} s
|
|
380
|
+
* @return {Error}
|
|
381
|
+
*/
|
|
382
|
+
/* c8 ignore next */
|
|
383
|
+
const create$1 = (s) => new Error(s);
|
|
384
|
+
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region node_modules/lib0/decoding.js
|
|
387
|
+
/**
|
|
388
|
+
* Efficient schema-less binary decoding with support for variable length encoding.
|
|
389
|
+
*
|
|
390
|
+
* Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
|
|
391
|
+
*
|
|
392
|
+
* Encodes numbers in little-endian order (least to most significant byte order)
|
|
393
|
+
* and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
|
|
394
|
+
* which is also used in Protocol Buffers.
|
|
395
|
+
*
|
|
396
|
+
* ```js
|
|
397
|
+
* // encoding step
|
|
398
|
+
* const encoder = encoding.createEncoder()
|
|
399
|
+
* encoding.writeVarUint(encoder, 256)
|
|
400
|
+
* encoding.writeVarString(encoder, 'Hello world!')
|
|
401
|
+
* const buf = encoding.toUint8Array(encoder)
|
|
402
|
+
* ```
|
|
403
|
+
*
|
|
404
|
+
* ```js
|
|
405
|
+
* // decoding step
|
|
406
|
+
* const decoder = decoding.createDecoder(buf)
|
|
407
|
+
* decoding.readVarUint(decoder) // => 256
|
|
408
|
+
* decoding.readVarString(decoder) // => 'Hello world!'
|
|
409
|
+
* decoding.hasContent(decoder) // => false - all data is read
|
|
410
|
+
* ```
|
|
411
|
+
*
|
|
412
|
+
* @module decoding
|
|
413
|
+
*/
|
|
414
|
+
const errorUnexpectedEndOfArray = create$1("Unexpected end of array");
|
|
415
|
+
const errorIntegerOutOfRange = create$1("Integer out of Range");
|
|
416
|
+
/**
|
|
417
|
+
* A Decoder handles the decoding of an Uint8Array.
|
|
418
|
+
* @template {ArrayBufferLike} [Buf=ArrayBufferLike]
|
|
419
|
+
*/
|
|
420
|
+
var Decoder = class {
|
|
421
|
+
/**
|
|
422
|
+
* @param {Uint8Array<Buf>} uint8Array Binary data to decode
|
|
423
|
+
*/
|
|
424
|
+
constructor(uint8Array) {
|
|
425
|
+
/**
|
|
426
|
+
* Decoding target.
|
|
427
|
+
*
|
|
428
|
+
* @type {Uint8Array<Buf>}
|
|
429
|
+
*/
|
|
430
|
+
this.arr = uint8Array;
|
|
431
|
+
/**
|
|
432
|
+
* Current decoding position.
|
|
433
|
+
*
|
|
434
|
+
* @type {number}
|
|
435
|
+
*/
|
|
436
|
+
this.pos = 0;
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* @function
|
|
441
|
+
* @template {ArrayBufferLike} Buf
|
|
442
|
+
* @param {Uint8Array<Buf>} uint8Array
|
|
443
|
+
* @return {Decoder<Buf>}
|
|
444
|
+
*/
|
|
445
|
+
const createDecoder = (uint8Array) => new Decoder(uint8Array);
|
|
446
|
+
/**
|
|
447
|
+
* Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
|
|
448
|
+
*
|
|
449
|
+
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
450
|
+
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
451
|
+
*
|
|
452
|
+
* @function
|
|
453
|
+
* @template {ArrayBufferLike} Buf
|
|
454
|
+
* @param {Decoder<Buf>} decoder The decoder instance
|
|
455
|
+
* @param {number} len The length of bytes to read
|
|
456
|
+
* @return {Uint8Array<Buf>}
|
|
457
|
+
*/
|
|
458
|
+
const readUint8Array = (decoder, len) => {
|
|
459
|
+
const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
|
|
460
|
+
decoder.pos += len;
|
|
461
|
+
return view;
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* Read variable length Uint8Array.
|
|
465
|
+
*
|
|
466
|
+
* Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
|
|
467
|
+
* Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
|
|
468
|
+
*
|
|
469
|
+
* @function
|
|
470
|
+
* @template {ArrayBufferLike} Buf
|
|
471
|
+
* @param {Decoder<Buf>} decoder
|
|
472
|
+
* @return {Uint8Array<Buf>}
|
|
473
|
+
*/
|
|
474
|
+
const readVarUint8Array = (decoder) => readUint8Array(decoder, readVarUint(decoder));
|
|
475
|
+
/**
|
|
476
|
+
* Read one byte as unsigned integer.
|
|
477
|
+
* @function
|
|
478
|
+
* @param {Decoder} decoder The decoder instance
|
|
479
|
+
* @return {number} Unsigned 8-bit integer
|
|
480
|
+
*/
|
|
481
|
+
const readUint8 = (decoder) => decoder.arr[decoder.pos++];
|
|
482
|
+
/**
|
|
483
|
+
* Read unsigned integer (32bit) with variable length.
|
|
484
|
+
* 1/8th of the storage is used as encoding overhead.
|
|
485
|
+
* * numbers < 2^7 is stored in one bytlength
|
|
486
|
+
* * numbers < 2^14 is stored in two bylength
|
|
487
|
+
*
|
|
488
|
+
* @function
|
|
489
|
+
* @param {Decoder} decoder
|
|
490
|
+
* @return {number} An unsigned integer.length
|
|
491
|
+
*/
|
|
492
|
+
const readVarUint = (decoder) => {
|
|
493
|
+
let num = 0;
|
|
494
|
+
let mult = 1;
|
|
495
|
+
const len = decoder.arr.length;
|
|
496
|
+
while (decoder.pos < len) {
|
|
497
|
+
const r = decoder.arr[decoder.pos++];
|
|
498
|
+
num = num + (r & BITS7) * mult;
|
|
499
|
+
mult *= 128;
|
|
500
|
+
if (r < BIT8) return num;
|
|
501
|
+
/* c8 ignore start */
|
|
502
|
+
if (num > MAX_SAFE_INTEGER) throw errorIntegerOutOfRange;
|
|
503
|
+
}
|
|
504
|
+
throw errorUnexpectedEndOfArray;
|
|
505
|
+
};
|
|
506
|
+
/**
|
|
507
|
+
* Read signed integer (32bit) with variable length.
|
|
508
|
+
* 1/8th of the storage is used as encoding overhead.
|
|
509
|
+
* * numbers < 2^7 is stored in one bytlength
|
|
510
|
+
* * numbers < 2^14 is stored in two bylength
|
|
511
|
+
* @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change.
|
|
512
|
+
*
|
|
513
|
+
* @function
|
|
514
|
+
* @param {Decoder} decoder
|
|
515
|
+
* @return {number} An unsigned integer.length
|
|
516
|
+
*/
|
|
517
|
+
const readVarInt = (decoder) => {
|
|
518
|
+
let r = decoder.arr[decoder.pos++];
|
|
519
|
+
let num = r & BITS6;
|
|
520
|
+
let mult = 64;
|
|
521
|
+
const sign = (r & BIT7) > 0 ? -1 : 1;
|
|
522
|
+
if ((r & BIT8) === 0) return sign * num;
|
|
523
|
+
const len = decoder.arr.length;
|
|
524
|
+
while (decoder.pos < len) {
|
|
525
|
+
r = decoder.arr[decoder.pos++];
|
|
526
|
+
num = num + (r & BITS7) * mult;
|
|
527
|
+
mult *= 128;
|
|
528
|
+
if (r < BIT8) return sign * num;
|
|
529
|
+
/* c8 ignore start */
|
|
530
|
+
if (num > MAX_SAFE_INTEGER) throw errorIntegerOutOfRange;
|
|
531
|
+
}
|
|
532
|
+
throw errorUnexpectedEndOfArray;
|
|
533
|
+
};
|
|
534
|
+
/**
|
|
535
|
+
* We don't test this function anymore as we use native decoding/encoding by default now.
|
|
536
|
+
* Better not modify this anymore..
|
|
537
|
+
*
|
|
538
|
+
* Transforming utf8 to a string is pretty expensive. The code performs 10x better
|
|
539
|
+
* when String.fromCodePoint is fed with all characters as arguments.
|
|
540
|
+
* But most environments have a maximum number of arguments per functions.
|
|
541
|
+
* For effiency reasons we apply a maximum of 10000 characters at once.
|
|
542
|
+
*
|
|
543
|
+
* @function
|
|
544
|
+
* @param {Decoder} decoder
|
|
545
|
+
* @return {String} The read String.
|
|
546
|
+
*/
|
|
547
|
+
/* c8 ignore start */
|
|
548
|
+
const _readVarStringPolyfill = (decoder) => {
|
|
549
|
+
let remainingLen = readVarUint(decoder);
|
|
550
|
+
if (remainingLen === 0) return "";
|
|
551
|
+
else {
|
|
552
|
+
let encodedString = String.fromCodePoint(readUint8(decoder));
|
|
553
|
+
if (--remainingLen < 100) while (remainingLen--) encodedString += String.fromCodePoint(readUint8(decoder));
|
|
554
|
+
else while (remainingLen > 0) {
|
|
555
|
+
const nextLen = remainingLen < 1e4 ? remainingLen : 1e4;
|
|
556
|
+
const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen);
|
|
557
|
+
decoder.pos += nextLen;
|
|
558
|
+
encodedString += String.fromCodePoint.apply(null, bytes);
|
|
559
|
+
remainingLen -= nextLen;
|
|
560
|
+
}
|
|
561
|
+
return decodeURIComponent(escape(encodedString));
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
/* c8 ignore stop */
|
|
565
|
+
/**
|
|
566
|
+
* @function
|
|
567
|
+
* @param {Decoder} decoder
|
|
568
|
+
* @return {String} The read String
|
|
569
|
+
*/
|
|
570
|
+
const _readVarStringNative = (decoder) => utf8TextDecoder.decode(readVarUint8Array(decoder));
|
|
571
|
+
/**
|
|
572
|
+
* Read string of variable length
|
|
573
|
+
* * varUint is used to store the length of the string
|
|
574
|
+
*
|
|
575
|
+
* @function
|
|
576
|
+
* @param {Decoder} decoder
|
|
577
|
+
* @return {String} The read String
|
|
578
|
+
*
|
|
579
|
+
*/
|
|
580
|
+
/* c8 ignore next */
|
|
581
|
+
const readVarString = utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill;
|
|
582
|
+
/**
|
|
583
|
+
* Look ahead and read varString without incrementing position
|
|
584
|
+
*
|
|
585
|
+
* @function
|
|
586
|
+
* @param {Decoder} decoder
|
|
587
|
+
* @return {string}
|
|
588
|
+
*/
|
|
589
|
+
const peekVarString = (decoder) => {
|
|
590
|
+
const pos = decoder.pos;
|
|
591
|
+
const s = readVarString(decoder);
|
|
592
|
+
decoder.pos = pos;
|
|
593
|
+
return s;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region packages/common/src/auth.ts
|
|
598
|
+
let AuthMessageType = /* @__PURE__ */ function(AuthMessageType) {
|
|
599
|
+
AuthMessageType[AuthMessageType["Token"] = 0] = "Token";
|
|
600
|
+
AuthMessageType[AuthMessageType["PermissionDenied"] = 1] = "PermissionDenied";
|
|
601
|
+
AuthMessageType[AuthMessageType["Authenticated"] = 2] = "Authenticated";
|
|
602
|
+
return AuthMessageType;
|
|
603
|
+
}({});
|
|
604
|
+
const writeAuthentication = (encoder, auth) => {
|
|
605
|
+
writeVarUint(encoder, AuthMessageType.Token);
|
|
606
|
+
writeVarString(encoder, auth);
|
|
607
|
+
};
|
|
608
|
+
const readAuthMessage = (decoder, sendToken, permissionDeniedHandler, authenticatedHandler) => {
|
|
609
|
+
switch (readVarUint(decoder)) {
|
|
610
|
+
case AuthMessageType.Token:
|
|
611
|
+
sendToken();
|
|
612
|
+
break;
|
|
613
|
+
case AuthMessageType.PermissionDenied:
|
|
614
|
+
permissionDeniedHandler(readVarString(decoder));
|
|
615
|
+
break;
|
|
616
|
+
case AuthMessageType.Authenticated:
|
|
617
|
+
authenticatedHandler(readVarString(decoder));
|
|
618
|
+
break;
|
|
619
|
+
default:
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region packages/common/src/awarenessStatesToArray.ts
|
|
625
|
+
const awarenessStatesToArray = (states) => {
|
|
626
|
+
return Array.from(states.entries()).map(([key, value]) => {
|
|
627
|
+
return {
|
|
628
|
+
clientId: key,
|
|
629
|
+
...value
|
|
630
|
+
};
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region packages/common/src/types.ts
|
|
636
|
+
/**
|
|
637
|
+
* State of the WebSocket connection.
|
|
638
|
+
* https://developer.mozilla.org/de/docs/Web/API/WebSocket/readyState
|
|
639
|
+
*/
|
|
640
|
+
let WsReadyStates = /* @__PURE__ */ function(WsReadyStates) {
|
|
641
|
+
WsReadyStates[WsReadyStates["Connecting"] = 0] = "Connecting";
|
|
642
|
+
WsReadyStates[WsReadyStates["Open"] = 1] = "Open";
|
|
643
|
+
WsReadyStates[WsReadyStates["Closing"] = 2] = "Closing";
|
|
644
|
+
WsReadyStates[WsReadyStates["Closed"] = 3] = "Closed";
|
|
645
|
+
return WsReadyStates;
|
|
646
|
+
}({});
|
|
647
|
+
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region node_modules/lib0/time.js
|
|
650
|
+
/**
|
|
651
|
+
* Return current unix time.
|
|
652
|
+
*
|
|
653
|
+
* @return {number}
|
|
654
|
+
*/
|
|
655
|
+
const getUnixTime = Date.now;
|
|
656
|
+
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region node_modules/lib0/map.js
|
|
659
|
+
/**
|
|
660
|
+
* Utility module to work with key-value stores.
|
|
661
|
+
*
|
|
662
|
+
* @module map
|
|
663
|
+
*/
|
|
664
|
+
/**
|
|
665
|
+
* @template K
|
|
666
|
+
* @template V
|
|
667
|
+
* @typedef {Map<K,V>} GlobalMap
|
|
668
|
+
*/
|
|
669
|
+
/**
|
|
670
|
+
* Creates a new Map instance.
|
|
671
|
+
*
|
|
672
|
+
* @function
|
|
673
|
+
* @return {Map<any, any>}
|
|
674
|
+
*
|
|
675
|
+
* @function
|
|
676
|
+
*/
|
|
677
|
+
const create = () => /* @__PURE__ */ new Map();
|
|
678
|
+
/**
|
|
679
|
+
* Get map property. Create T if property is undefined and set T on map.
|
|
680
|
+
*
|
|
681
|
+
* ```js
|
|
682
|
+
* const listeners = map.setIfUndefined(events, 'eventName', set.create)
|
|
683
|
+
* listeners.add(listener)
|
|
684
|
+
* ```
|
|
685
|
+
*
|
|
686
|
+
* @function
|
|
687
|
+
* @template {Map<any, any>} MAP
|
|
688
|
+
* @template {MAP extends Map<any,infer V> ? function():V : unknown} CF
|
|
689
|
+
* @param {MAP} map
|
|
690
|
+
* @param {MAP extends Map<infer K,any> ? K : unknown} key
|
|
691
|
+
* @param {CF} createT
|
|
692
|
+
* @return {ReturnType<CF>}
|
|
693
|
+
*/
|
|
694
|
+
const setIfUndefined = (map, key, createT) => {
|
|
695
|
+
let set = map.get(key);
|
|
696
|
+
if (set === void 0) map.set(key, set = createT());
|
|
697
|
+
return set;
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region node_modules/lib0/observable.js
|
|
702
|
+
/**
|
|
703
|
+
* Observable class prototype.
|
|
704
|
+
*
|
|
705
|
+
* @module observable
|
|
706
|
+
*/
|
|
707
|
+
/* c8 ignore start */
|
|
708
|
+
/**
|
|
709
|
+
* Handles named events.
|
|
710
|
+
*
|
|
711
|
+
* @deprecated
|
|
712
|
+
* @template N
|
|
713
|
+
*/
|
|
714
|
+
var Observable = class {
|
|
715
|
+
constructor() {
|
|
716
|
+
/**
|
|
717
|
+
* Some desc.
|
|
718
|
+
* @type {Map<N, any>}
|
|
719
|
+
*/
|
|
720
|
+
this._observers = create();
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* @param {N} name
|
|
724
|
+
* @param {function} f
|
|
725
|
+
*/
|
|
726
|
+
on(name, f) {
|
|
727
|
+
setIfUndefined(this._observers, name, create$2).add(f);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* @param {N} name
|
|
731
|
+
* @param {function} f
|
|
732
|
+
*/
|
|
733
|
+
once(name, f) {
|
|
734
|
+
/**
|
|
735
|
+
* @param {...any} args
|
|
736
|
+
*/
|
|
737
|
+
const _f = (...args) => {
|
|
738
|
+
this.off(name, _f);
|
|
739
|
+
f(...args);
|
|
740
|
+
};
|
|
741
|
+
this.on(name, _f);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* @param {N} name
|
|
745
|
+
* @param {function} f
|
|
746
|
+
*/
|
|
747
|
+
off(name, f) {
|
|
748
|
+
const observers = this._observers.get(name);
|
|
749
|
+
if (observers !== void 0) {
|
|
750
|
+
observers.delete(f);
|
|
751
|
+
if (observers.size === 0) this._observers.delete(name);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Emit a named event. All registered event listeners that listen to the
|
|
756
|
+
* specified name will receive the event.
|
|
757
|
+
*
|
|
758
|
+
* @todo This should catch exceptions
|
|
759
|
+
*
|
|
760
|
+
* @param {N} name The event name.
|
|
761
|
+
* @param {Array<any>} args The arguments that are applied to the event listener.
|
|
762
|
+
*/
|
|
763
|
+
emit(name, args) {
|
|
764
|
+
return from((this._observers.get(name) || create()).values()).forEach((f) => f(...args));
|
|
765
|
+
}
|
|
766
|
+
destroy() {
|
|
767
|
+
this._observers = create();
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
/* c8 ignore end */
|
|
771
|
+
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region node_modules/lib0/trait/equality.js
|
|
774
|
+
const EqualityTraitSymbol = Symbol("Equality");
|
|
775
|
+
|
|
776
|
+
//#endregion
|
|
777
|
+
//#region node_modules/lib0/object.js
|
|
778
|
+
/**
|
|
779
|
+
* @param {Object<string,any>} obj
|
|
780
|
+
*/
|
|
781
|
+
const keys = Object.keys;
|
|
782
|
+
/**
|
|
783
|
+
* @param {Object<string,any>} obj
|
|
784
|
+
* @return {number}
|
|
785
|
+
*/
|
|
786
|
+
const size = (obj) => keys(obj).length;
|
|
787
|
+
/**
|
|
788
|
+
* Calls `Object.prototype.hasOwnProperty`.
|
|
789
|
+
*
|
|
790
|
+
* @param {any} obj
|
|
791
|
+
* @param {string|number|symbol} key
|
|
792
|
+
* @return {boolean}
|
|
793
|
+
*/
|
|
794
|
+
const hasProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
795
|
+
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region node_modules/lib0/function.js
|
|
798
|
+
/**
|
|
799
|
+
* Common functions and function call helpers.
|
|
800
|
+
*
|
|
801
|
+
* @module function
|
|
802
|
+
*/
|
|
803
|
+
/* c8 ignore start */
|
|
804
|
+
/**
|
|
805
|
+
* @param {any} a
|
|
806
|
+
* @param {any} b
|
|
807
|
+
* @return {boolean}
|
|
808
|
+
*/
|
|
809
|
+
const equalityDeep = (a, b) => {
|
|
810
|
+
if (a === b) return true;
|
|
811
|
+
if (a == null || b == null || a.constructor !== b.constructor && (a.constructor || Object) !== (b.constructor || Object)) return false;
|
|
812
|
+
if (a[EqualityTraitSymbol] != null) return a[EqualityTraitSymbol](b);
|
|
813
|
+
switch (a.constructor) {
|
|
814
|
+
case ArrayBuffer:
|
|
815
|
+
a = new Uint8Array(a);
|
|
816
|
+
b = new Uint8Array(b);
|
|
817
|
+
case Uint8Array:
|
|
818
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
819
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
820
|
+
break;
|
|
821
|
+
case Set:
|
|
822
|
+
if (a.size !== b.size) return false;
|
|
823
|
+
for (const value of a) if (!b.has(value)) return false;
|
|
824
|
+
break;
|
|
825
|
+
case Map:
|
|
826
|
+
if (a.size !== b.size) return false;
|
|
827
|
+
for (const key of a.keys()) if (!b.has(key) || !equalityDeep(a.get(key), b.get(key))) return false;
|
|
828
|
+
break;
|
|
829
|
+
case void 0:
|
|
830
|
+
case Object:
|
|
831
|
+
if (size(a) !== size(b)) return false;
|
|
832
|
+
for (const key in a) if (!hasProperty(a, key) || !equalityDeep(a[key], b[key])) return false;
|
|
833
|
+
break;
|
|
834
|
+
case Array:
|
|
835
|
+
if (a.length !== b.length) return false;
|
|
836
|
+
for (let i = 0; i < a.length; i++) if (!equalityDeep(a[i], b[i])) return false;
|
|
837
|
+
break;
|
|
838
|
+
default: return false;
|
|
839
|
+
}
|
|
840
|
+
return true;
|
|
841
|
+
};
|
|
842
|
+
/* c8 ignore stop */
|
|
843
|
+
const isArray = isArray$1;
|
|
844
|
+
|
|
845
|
+
//#endregion
|
|
846
|
+
//#region node_modules/y-protocols/awareness.js
|
|
847
|
+
/**
|
|
848
|
+
* @module awareness-protocol
|
|
849
|
+
*/
|
|
850
|
+
const outdatedTimeout = 3e4;
|
|
851
|
+
/**
|
|
852
|
+
* @typedef {Object} MetaClientState
|
|
853
|
+
* @property {number} MetaClientState.clock
|
|
854
|
+
* @property {number} MetaClientState.lastUpdated unix timestamp
|
|
855
|
+
*/
|
|
856
|
+
/**
|
|
857
|
+
* The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information
|
|
858
|
+
* (cursor, username, status, ..). Each client can update its own local state and listen to state changes of
|
|
859
|
+
* remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline.
|
|
860
|
+
*
|
|
861
|
+
* Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override
|
|
862
|
+
* its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is
|
|
863
|
+
* applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that
|
|
864
|
+
* a remote client is offline, it may propagate a message with
|
|
865
|
+
* `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a
|
|
866
|
+
* message is received, and the known clock of that client equals the received clock, it will override the state with `null`.
|
|
867
|
+
*
|
|
868
|
+
* Before a client disconnects, it should propagate a `null` state with an updated clock.
|
|
869
|
+
*
|
|
870
|
+
* Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state.
|
|
871
|
+
*
|
|
872
|
+
* @extends {Observable<string>}
|
|
873
|
+
*/
|
|
874
|
+
var Awareness = class extends Observable {
|
|
875
|
+
/**
|
|
876
|
+
* @param {Y.Doc} doc
|
|
877
|
+
*/
|
|
878
|
+
constructor(doc) {
|
|
879
|
+
super();
|
|
880
|
+
this.doc = doc;
|
|
881
|
+
/**
|
|
882
|
+
* @type {number}
|
|
883
|
+
*/
|
|
884
|
+
this.clientID = doc.clientID;
|
|
885
|
+
/**
|
|
886
|
+
* Maps from client id to client state
|
|
887
|
+
* @type {Map<number, Object<string, any>>}
|
|
888
|
+
*/
|
|
889
|
+
this.states = /* @__PURE__ */ new Map();
|
|
890
|
+
/**
|
|
891
|
+
* @type {Map<number, MetaClientState>}
|
|
892
|
+
*/
|
|
893
|
+
this.meta = /* @__PURE__ */ new Map();
|
|
894
|
+
this._checkInterval = setInterval(() => {
|
|
895
|
+
const now = getUnixTime();
|
|
896
|
+
if (this.getLocalState() !== null && outdatedTimeout / 2 <= now - this.meta.get(this.clientID).lastUpdated) this.setLocalState(this.getLocalState());
|
|
897
|
+
/**
|
|
898
|
+
* @type {Array<number>}
|
|
899
|
+
*/
|
|
900
|
+
const remove = [];
|
|
901
|
+
this.meta.forEach((meta, clientid) => {
|
|
902
|
+
if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) remove.push(clientid);
|
|
903
|
+
});
|
|
904
|
+
if (remove.length > 0) removeAwarenessStates(this, remove, "timeout");
|
|
905
|
+
}, floor(outdatedTimeout / 10));
|
|
906
|
+
doc.on("destroy", () => {
|
|
907
|
+
this.destroy();
|
|
908
|
+
});
|
|
909
|
+
this.setLocalState({});
|
|
910
|
+
}
|
|
911
|
+
destroy() {
|
|
912
|
+
this.emit("destroy", [this]);
|
|
913
|
+
this.setLocalState(null);
|
|
914
|
+
super.destroy();
|
|
915
|
+
clearInterval(this._checkInterval);
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* @return {Object<string,any>|null}
|
|
919
|
+
*/
|
|
920
|
+
getLocalState() {
|
|
921
|
+
return this.states.get(this.clientID) || null;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* @param {Object<string,any>|null} state
|
|
925
|
+
*/
|
|
926
|
+
setLocalState(state) {
|
|
927
|
+
const clientID = this.clientID;
|
|
928
|
+
const currLocalMeta = this.meta.get(clientID);
|
|
929
|
+
const clock = currLocalMeta === void 0 ? 0 : currLocalMeta.clock + 1;
|
|
930
|
+
const prevState = this.states.get(clientID);
|
|
931
|
+
if (state === null) this.states.delete(clientID);
|
|
932
|
+
else this.states.set(clientID, state);
|
|
933
|
+
this.meta.set(clientID, {
|
|
934
|
+
clock,
|
|
935
|
+
lastUpdated: getUnixTime()
|
|
936
|
+
});
|
|
937
|
+
const added = [];
|
|
938
|
+
const updated = [];
|
|
939
|
+
const filteredUpdated = [];
|
|
940
|
+
const removed = [];
|
|
941
|
+
if (state === null) removed.push(clientID);
|
|
942
|
+
else if (prevState == null) {
|
|
943
|
+
if (state != null) added.push(clientID);
|
|
944
|
+
} else {
|
|
945
|
+
updated.push(clientID);
|
|
946
|
+
if (!equalityDeep(prevState, state)) filteredUpdated.push(clientID);
|
|
947
|
+
}
|
|
948
|
+
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) this.emit("change", [{
|
|
949
|
+
added,
|
|
950
|
+
updated: filteredUpdated,
|
|
951
|
+
removed
|
|
952
|
+
}, "local"]);
|
|
953
|
+
this.emit("update", [{
|
|
954
|
+
added,
|
|
955
|
+
updated,
|
|
956
|
+
removed
|
|
957
|
+
}, "local"]);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* @param {string} field
|
|
961
|
+
* @param {any} value
|
|
962
|
+
*/
|
|
963
|
+
setLocalStateField(field, value) {
|
|
964
|
+
const state = this.getLocalState();
|
|
965
|
+
if (state !== null) this.setLocalState({
|
|
966
|
+
...state,
|
|
967
|
+
[field]: value
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* @return {Map<number,Object<string,any>>}
|
|
972
|
+
*/
|
|
973
|
+
getStates() {
|
|
974
|
+
return this.states;
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* Mark (remote) clients as inactive and remove them from the list of active peers.
|
|
979
|
+
* This change will be propagated to remote clients.
|
|
980
|
+
*
|
|
981
|
+
* @param {Awareness} awareness
|
|
982
|
+
* @param {Array<number>} clients
|
|
983
|
+
* @param {any} origin
|
|
984
|
+
*/
|
|
985
|
+
const removeAwarenessStates = (awareness, clients, origin) => {
|
|
986
|
+
const removed = [];
|
|
987
|
+
for (let i = 0; i < clients.length; i++) {
|
|
988
|
+
const clientID = clients[i];
|
|
989
|
+
if (awareness.states.has(clientID)) {
|
|
990
|
+
awareness.states.delete(clientID);
|
|
991
|
+
if (clientID === awareness.clientID) {
|
|
992
|
+
const curMeta = awareness.meta.get(clientID);
|
|
993
|
+
awareness.meta.set(clientID, {
|
|
994
|
+
clock: curMeta.clock + 1,
|
|
995
|
+
lastUpdated: getUnixTime()
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
removed.push(clientID);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (removed.length > 0) {
|
|
1002
|
+
awareness.emit("change", [{
|
|
1003
|
+
added: [],
|
|
1004
|
+
updated: [],
|
|
1005
|
+
removed
|
|
1006
|
+
}, origin]);
|
|
1007
|
+
awareness.emit("update", [{
|
|
1008
|
+
added: [],
|
|
1009
|
+
updated: [],
|
|
1010
|
+
removed
|
|
1011
|
+
}, origin]);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
/**
|
|
1015
|
+
* @param {Awareness} awareness
|
|
1016
|
+
* @param {Array<number>} clients
|
|
1017
|
+
* @return {Uint8Array}
|
|
1018
|
+
*/
|
|
1019
|
+
const encodeAwarenessUpdate = (awareness, clients, states = awareness.states) => {
|
|
1020
|
+
const len = clients.length;
|
|
1021
|
+
const encoder = createEncoder();
|
|
1022
|
+
writeVarUint(encoder, len);
|
|
1023
|
+
for (let i = 0; i < len; i++) {
|
|
1024
|
+
const clientID = clients[i];
|
|
1025
|
+
const state = states.get(clientID) || null;
|
|
1026
|
+
const clock = awareness.meta.get(clientID).clock;
|
|
1027
|
+
writeVarUint(encoder, clientID);
|
|
1028
|
+
writeVarUint(encoder, clock);
|
|
1029
|
+
writeVarString(encoder, JSON.stringify(state));
|
|
1030
|
+
}
|
|
1031
|
+
return toUint8Array(encoder);
|
|
1032
|
+
};
|
|
1033
|
+
/**
|
|
1034
|
+
* @param {Awareness} awareness
|
|
1035
|
+
* @param {Uint8Array} update
|
|
1036
|
+
* @param {any} origin This will be added to the emitted change event
|
|
1037
|
+
*/
|
|
1038
|
+
const applyAwarenessUpdate = (awareness, update, origin) => {
|
|
1039
|
+
const decoder = createDecoder(update);
|
|
1040
|
+
const timestamp = getUnixTime();
|
|
1041
|
+
const added = [];
|
|
1042
|
+
const updated = [];
|
|
1043
|
+
const filteredUpdated = [];
|
|
1044
|
+
const removed = [];
|
|
1045
|
+
const len = readVarUint(decoder);
|
|
1046
|
+
for (let i = 0; i < len; i++) {
|
|
1047
|
+
const clientID = readVarUint(decoder);
|
|
1048
|
+
let clock = readVarUint(decoder);
|
|
1049
|
+
const state = JSON.parse(readVarString(decoder));
|
|
1050
|
+
const clientMeta = awareness.meta.get(clientID);
|
|
1051
|
+
const prevState = awareness.states.get(clientID);
|
|
1052
|
+
const currClock = clientMeta === void 0 ? 0 : clientMeta.clock;
|
|
1053
|
+
if (currClock < clock || currClock === clock && state === null && awareness.states.has(clientID)) {
|
|
1054
|
+
if (state === null) if (clientID === awareness.clientID && awareness.getLocalState() != null) clock++;
|
|
1055
|
+
else awareness.states.delete(clientID);
|
|
1056
|
+
else awareness.states.set(clientID, state);
|
|
1057
|
+
awareness.meta.set(clientID, {
|
|
1058
|
+
clock,
|
|
1059
|
+
lastUpdated: timestamp
|
|
1060
|
+
});
|
|
1061
|
+
if (clientMeta === void 0 && state !== null) added.push(clientID);
|
|
1062
|
+
else if (clientMeta !== void 0 && state === null) removed.push(clientID);
|
|
1063
|
+
else if (state !== null) {
|
|
1064
|
+
if (!equalityDeep(state, prevState)) filteredUpdated.push(clientID);
|
|
1065
|
+
updated.push(clientID);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) awareness.emit("change", [{
|
|
1070
|
+
added,
|
|
1071
|
+
updated: filteredUpdated,
|
|
1072
|
+
removed
|
|
1073
|
+
}, origin]);
|
|
1074
|
+
if (added.length > 0 || updated.length > 0 || removed.length > 0) awareness.emit("update", [{
|
|
1075
|
+
added,
|
|
1076
|
+
updated,
|
|
1077
|
+
removed
|
|
1078
|
+
}, origin]);
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
//#endregion
|
|
1082
|
+
//#region packages/provider/src/EventEmitter.ts
|
|
1083
|
+
var EventEmitter = class {
|
|
1084
|
+
constructor() {
|
|
1085
|
+
this.callbacks = {};
|
|
1086
|
+
}
|
|
1087
|
+
on(event, fn) {
|
|
1088
|
+
if (!this.callbacks[event]) this.callbacks[event] = [];
|
|
1089
|
+
this.callbacks[event].push(fn);
|
|
1090
|
+
return this;
|
|
1091
|
+
}
|
|
1092
|
+
emit(event, ...args) {
|
|
1093
|
+
const callbacks = this.callbacks[event];
|
|
1094
|
+
if (callbacks) callbacks.forEach((callback) => callback.apply(this, args));
|
|
1095
|
+
return this;
|
|
1096
|
+
}
|
|
1097
|
+
off(event, fn) {
|
|
1098
|
+
const callbacks = this.callbacks[event];
|
|
1099
|
+
if (callbacks) if (fn) this.callbacks[event] = callbacks.filter((callback) => callback !== fn);
|
|
1100
|
+
else delete this.callbacks[event];
|
|
1101
|
+
return this;
|
|
1102
|
+
}
|
|
1103
|
+
removeAllListeners() {
|
|
1104
|
+
this.callbacks = {};
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
//#endregion
|
|
1109
|
+
//#region packages/provider/src/IncomingMessage.ts
|
|
1110
|
+
var IncomingMessage = class {
|
|
1111
|
+
constructor(data) {
|
|
1112
|
+
this.data = data;
|
|
1113
|
+
this.encoder = createEncoder();
|
|
1114
|
+
this.decoder = createDecoder(new Uint8Array(this.data));
|
|
1115
|
+
}
|
|
1116
|
+
peekVarString() {
|
|
1117
|
+
return peekVarString(this.decoder);
|
|
1118
|
+
}
|
|
1119
|
+
readVarUint() {
|
|
1120
|
+
return readVarUint(this.decoder);
|
|
1121
|
+
}
|
|
1122
|
+
readVarString() {
|
|
1123
|
+
return readVarString(this.decoder);
|
|
1124
|
+
}
|
|
1125
|
+
readVarUint8Array() {
|
|
1126
|
+
return readVarUint8Array(this.decoder);
|
|
1127
|
+
}
|
|
1128
|
+
writeVarUint(type) {
|
|
1129
|
+
return writeVarUint(this.encoder, type);
|
|
1130
|
+
}
|
|
1131
|
+
writeVarString(string) {
|
|
1132
|
+
return writeVarString(this.encoder, string);
|
|
1133
|
+
}
|
|
1134
|
+
writeVarUint8Array(data) {
|
|
1135
|
+
return writeVarUint8Array(this.encoder, data);
|
|
1136
|
+
}
|
|
1137
|
+
length() {
|
|
1138
|
+
return length(this.encoder);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
//#endregion
|
|
1143
|
+
//#region packages/provider/src/types.ts
|
|
1144
|
+
let MessageType = /* @__PURE__ */ function(MessageType) {
|
|
1145
|
+
MessageType[MessageType["Sync"] = 0] = "Sync";
|
|
1146
|
+
MessageType[MessageType["Awareness"] = 1] = "Awareness";
|
|
1147
|
+
MessageType[MessageType["Auth"] = 2] = "Auth";
|
|
1148
|
+
MessageType[MessageType["QueryAwareness"] = 3] = "QueryAwareness";
|
|
1149
|
+
MessageType[MessageType["Subdoc"] = 4] = "Subdoc";
|
|
1150
|
+
MessageType[MessageType["Stateless"] = 5] = "Stateless";
|
|
1151
|
+
MessageType[MessageType["CLOSE"] = 7] = "CLOSE";
|
|
1152
|
+
MessageType[MessageType["SyncStatus"] = 8] = "SyncStatus";
|
|
1153
|
+
return MessageType;
|
|
1154
|
+
}({});
|
|
1155
|
+
let WebSocketStatus = /* @__PURE__ */ function(WebSocketStatus) {
|
|
1156
|
+
WebSocketStatus["Connecting"] = "connecting";
|
|
1157
|
+
WebSocketStatus["Connected"] = "connected";
|
|
1158
|
+
WebSocketStatus["Disconnected"] = "disconnected";
|
|
1159
|
+
return WebSocketStatus;
|
|
1160
|
+
}({});
|
|
1161
|
+
|
|
1162
|
+
//#endregion
|
|
1163
|
+
//#region packages/provider/src/OutgoingMessage.ts
|
|
1164
|
+
var OutgoingMessage = class {
|
|
1165
|
+
constructor() {
|
|
1166
|
+
this.encoder = createEncoder();
|
|
1167
|
+
}
|
|
1168
|
+
get(args) {
|
|
1169
|
+
return args.encoder;
|
|
1170
|
+
}
|
|
1171
|
+
toUint8Array() {
|
|
1172
|
+
return toUint8Array(this.encoder);
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
//#endregion
|
|
1177
|
+
//#region packages/provider/src/OutgoingMessages/CloseMessage.ts
|
|
1178
|
+
var CloseMessage = class extends OutgoingMessage {
|
|
1179
|
+
constructor(..._args) {
|
|
1180
|
+
super(..._args);
|
|
1181
|
+
this.type = MessageType.CLOSE;
|
|
1182
|
+
this.description = "Ask the server to close the connection";
|
|
1183
|
+
}
|
|
1184
|
+
get(args) {
|
|
1185
|
+
writeVarString(this.encoder, args.documentName);
|
|
1186
|
+
writeVarUint(this.encoder, this.type);
|
|
1187
|
+
return this.encoder;
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
//#endregion
|
|
1192
|
+
//#region packages/provider/src/HocuspocusProviderWebsocket.ts
|
|
1193
|
+
var HocuspocusProviderWebsocket = class extends EventEmitter {
|
|
1194
|
+
constructor(configuration) {
|
|
1195
|
+
super();
|
|
1196
|
+
this.messageQueue = [];
|
|
1197
|
+
this.configuration = {
|
|
1198
|
+
url: "",
|
|
1199
|
+
autoConnect: true,
|
|
1200
|
+
preserveTrailingSlash: false,
|
|
1201
|
+
document: void 0,
|
|
1202
|
+
WebSocketPolyfill: void 0,
|
|
1203
|
+
messageReconnectTimeout: 3e4,
|
|
1204
|
+
delay: 1e3,
|
|
1205
|
+
initialDelay: 0,
|
|
1206
|
+
factor: 2,
|
|
1207
|
+
maxAttempts: 0,
|
|
1208
|
+
minDelay: 1e3,
|
|
1209
|
+
maxDelay: 3e4,
|
|
1210
|
+
jitter: true,
|
|
1211
|
+
timeout: 0,
|
|
1212
|
+
onOpen: () => null,
|
|
1213
|
+
onConnect: () => null,
|
|
1214
|
+
onMessage: () => null,
|
|
1215
|
+
onOutgoingMessage: () => null,
|
|
1216
|
+
onStatus: () => null,
|
|
1217
|
+
onDisconnect: () => null,
|
|
1218
|
+
onClose: () => null,
|
|
1219
|
+
onDestroy: () => null,
|
|
1220
|
+
onAwarenessUpdate: () => null,
|
|
1221
|
+
onAwarenessChange: () => null,
|
|
1222
|
+
handleTimeout: null,
|
|
1223
|
+
providerMap: /* @__PURE__ */ new Map()
|
|
1224
|
+
};
|
|
1225
|
+
this.webSocket = null;
|
|
1226
|
+
this.webSocketHandlers = {};
|
|
1227
|
+
this.shouldConnect = true;
|
|
1228
|
+
this.status = WebSocketStatus.Disconnected;
|
|
1229
|
+
this.lastMessageReceived = 0;
|
|
1230
|
+
this.identifier = 0;
|
|
1231
|
+
this.intervals = { connectionChecker: null };
|
|
1232
|
+
this.connectionAttempt = null;
|
|
1233
|
+
this.receivedOnOpenPayload = void 0;
|
|
1234
|
+
this.closeTries = 0;
|
|
1235
|
+
this.setConfiguration(configuration);
|
|
1236
|
+
this.configuration.WebSocketPolyfill = configuration.WebSocketPolyfill ? configuration.WebSocketPolyfill : WebSocket;
|
|
1237
|
+
this.on("open", this.configuration.onOpen);
|
|
1238
|
+
this.on("open", this.onOpen.bind(this));
|
|
1239
|
+
this.on("connect", this.configuration.onConnect);
|
|
1240
|
+
this.on("message", this.configuration.onMessage);
|
|
1241
|
+
this.on("outgoingMessage", this.configuration.onOutgoingMessage);
|
|
1242
|
+
this.on("status", this.configuration.onStatus);
|
|
1243
|
+
this.on("disconnect", this.configuration.onDisconnect);
|
|
1244
|
+
this.on("close", this.configuration.onClose);
|
|
1245
|
+
this.on("destroy", this.configuration.onDestroy);
|
|
1246
|
+
this.on("awarenessUpdate", this.configuration.onAwarenessUpdate);
|
|
1247
|
+
this.on("awarenessChange", this.configuration.onAwarenessChange);
|
|
1248
|
+
this.on("close", this.onClose.bind(this));
|
|
1249
|
+
this.on("message", this.onMessage.bind(this));
|
|
1250
|
+
this.intervals.connectionChecker = setInterval(this.checkConnection.bind(this), this.configuration.messageReconnectTimeout / 10);
|
|
1251
|
+
if (this.shouldConnect) this.connect();
|
|
1252
|
+
}
|
|
1253
|
+
async onOpen(event) {
|
|
1254
|
+
this.status = WebSocketStatus.Connected;
|
|
1255
|
+
this.emit("status", { status: WebSocketStatus.Connected });
|
|
1256
|
+
this.cancelWebsocketRetry = void 0;
|
|
1257
|
+
this.receivedOnOpenPayload = event;
|
|
1258
|
+
}
|
|
1259
|
+
attach(provider) {
|
|
1260
|
+
this.configuration.providerMap.set(provider.configuration.name, provider);
|
|
1261
|
+
if (this.status === WebSocketStatus.Disconnected && this.shouldConnect) this.connect();
|
|
1262
|
+
if (this.receivedOnOpenPayload && this.status === WebSocketStatus.Connected) provider.onOpen(this.receivedOnOpenPayload);
|
|
1263
|
+
}
|
|
1264
|
+
detach(provider) {
|
|
1265
|
+
if (this.configuration.providerMap.has(provider.configuration.name)) {
|
|
1266
|
+
provider.send(CloseMessage, { documentName: provider.configuration.name });
|
|
1267
|
+
this.configuration.providerMap.delete(provider.configuration.name);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
setConfiguration(configuration = {}) {
|
|
1271
|
+
this.configuration = {
|
|
1272
|
+
...this.configuration,
|
|
1273
|
+
...configuration
|
|
1274
|
+
};
|
|
1275
|
+
if (!this.configuration.autoConnect) this.shouldConnect = false;
|
|
1276
|
+
}
|
|
1277
|
+
async connect() {
|
|
1278
|
+
if (this.status === WebSocketStatus.Connected) return;
|
|
1279
|
+
if (this.cancelWebsocketRetry) {
|
|
1280
|
+
this.cancelWebsocketRetry();
|
|
1281
|
+
this.cancelWebsocketRetry = void 0;
|
|
1282
|
+
}
|
|
1283
|
+
this.receivedOnOpenPayload = void 0;
|
|
1284
|
+
this.shouldConnect = true;
|
|
1285
|
+
const abortableRetry = () => {
|
|
1286
|
+
let cancelAttempt = false;
|
|
1287
|
+
return {
|
|
1288
|
+
retryPromise: (0, _lifeomic_attempt.retry)(this.createWebSocketConnection.bind(this), {
|
|
1289
|
+
delay: this.configuration.delay,
|
|
1290
|
+
initialDelay: this.configuration.initialDelay,
|
|
1291
|
+
factor: this.configuration.factor,
|
|
1292
|
+
maxAttempts: this.configuration.maxAttempts,
|
|
1293
|
+
minDelay: this.configuration.minDelay,
|
|
1294
|
+
maxDelay: this.configuration.maxDelay,
|
|
1295
|
+
jitter: this.configuration.jitter,
|
|
1296
|
+
timeout: this.configuration.timeout,
|
|
1297
|
+
handleTimeout: this.configuration.handleTimeout,
|
|
1298
|
+
beforeAttempt: (context) => {
|
|
1299
|
+
if (!this.shouldConnect || cancelAttempt) context.abort();
|
|
1300
|
+
}
|
|
1301
|
+
}).catch((error) => {
|
|
1302
|
+
if (error && error.code !== "ATTEMPT_ABORTED") throw error;
|
|
1303
|
+
}),
|
|
1304
|
+
cancelFunc: () => {
|
|
1305
|
+
cancelAttempt = true;
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
};
|
|
1309
|
+
const { retryPromise, cancelFunc } = abortableRetry();
|
|
1310
|
+
this.cancelWebsocketRetry = cancelFunc;
|
|
1311
|
+
return retryPromise;
|
|
1312
|
+
}
|
|
1313
|
+
attachWebSocketListeners(ws, reject) {
|
|
1314
|
+
const { identifier } = ws;
|
|
1315
|
+
const onMessageHandler = (payload) => this.emit("message", payload);
|
|
1316
|
+
const onCloseHandler = (payload) => this.emit("close", { event: payload });
|
|
1317
|
+
const onOpenHandler = (payload) => this.emit("open", payload);
|
|
1318
|
+
const onErrorHandler = (err) => {
|
|
1319
|
+
reject(err);
|
|
1320
|
+
};
|
|
1321
|
+
this.webSocketHandlers[identifier] = {
|
|
1322
|
+
message: onMessageHandler,
|
|
1323
|
+
close: onCloseHandler,
|
|
1324
|
+
open: onOpenHandler,
|
|
1325
|
+
error: onErrorHandler
|
|
1326
|
+
};
|
|
1327
|
+
const handlers = this.webSocketHandlers[ws.identifier];
|
|
1328
|
+
Object.keys(handlers).forEach((name) => {
|
|
1329
|
+
ws.addEventListener(name, handlers[name]);
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
cleanupWebSocket() {
|
|
1333
|
+
if (!this.webSocket) return;
|
|
1334
|
+
const { identifier } = this.webSocket;
|
|
1335
|
+
const handlers = this.webSocketHandlers[identifier];
|
|
1336
|
+
Object.keys(handlers).forEach((name) => {
|
|
1337
|
+
this.webSocket?.removeEventListener(name, handlers[name]);
|
|
1338
|
+
delete this.webSocketHandlers[identifier];
|
|
1339
|
+
});
|
|
1340
|
+
try {
|
|
1341
|
+
if (this.webSocket.readyState !== 0 && this.webSocket.readyState !== 3) this.webSocket.close();
|
|
1342
|
+
} catch (e) {}
|
|
1343
|
+
this.webSocket = null;
|
|
1344
|
+
}
|
|
1345
|
+
createWebSocketConnection() {
|
|
1346
|
+
return new Promise((resolve, reject) => {
|
|
1347
|
+
if (this.webSocket) {
|
|
1348
|
+
this.messageQueue = [];
|
|
1349
|
+
this.cleanupWebSocket();
|
|
1350
|
+
}
|
|
1351
|
+
this.lastMessageReceived = 0;
|
|
1352
|
+
this.identifier += 1;
|
|
1353
|
+
const ws = new this.configuration.WebSocketPolyfill(this.url);
|
|
1354
|
+
ws.binaryType = "arraybuffer";
|
|
1355
|
+
ws.identifier = this.identifier;
|
|
1356
|
+
this.attachWebSocketListeners(ws, reject);
|
|
1357
|
+
this.webSocket = ws;
|
|
1358
|
+
this.status = WebSocketStatus.Connecting;
|
|
1359
|
+
this.emit("status", { status: WebSocketStatus.Connecting });
|
|
1360
|
+
this.connectionAttempt = {
|
|
1361
|
+
resolve,
|
|
1362
|
+
reject
|
|
1363
|
+
};
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
onMessage(event) {
|
|
1367
|
+
this.resolveConnectionAttempt();
|
|
1368
|
+
this.lastMessageReceived = getUnixTime();
|
|
1369
|
+
const documentName = new IncomingMessage(event.data).peekVarString();
|
|
1370
|
+
this.configuration.providerMap.get(documentName)?.onMessage(event);
|
|
1371
|
+
}
|
|
1372
|
+
resolveConnectionAttempt() {
|
|
1373
|
+
if (this.connectionAttempt) {
|
|
1374
|
+
this.connectionAttempt.resolve();
|
|
1375
|
+
this.connectionAttempt = null;
|
|
1376
|
+
this.status = WebSocketStatus.Connected;
|
|
1377
|
+
this.emit("status", { status: WebSocketStatus.Connected });
|
|
1378
|
+
this.emit("connect");
|
|
1379
|
+
this.messageQueue.forEach((message) => this.send(message));
|
|
1380
|
+
this.messageQueue = [];
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
stopConnectionAttempt() {
|
|
1384
|
+
this.connectionAttempt = null;
|
|
1385
|
+
}
|
|
1386
|
+
rejectConnectionAttempt() {
|
|
1387
|
+
this.connectionAttempt?.reject();
|
|
1388
|
+
this.connectionAttempt = null;
|
|
1389
|
+
}
|
|
1390
|
+
checkConnection() {
|
|
1391
|
+
if (this.status !== WebSocketStatus.Connected) return;
|
|
1392
|
+
if (!this.lastMessageReceived) return;
|
|
1393
|
+
if (this.configuration.messageReconnectTimeout >= getUnixTime() - this.lastMessageReceived) return;
|
|
1394
|
+
this.closeTries += 1;
|
|
1395
|
+
if (this.closeTries > 2) {
|
|
1396
|
+
this.onClose({ event: {
|
|
1397
|
+
code: 4408,
|
|
1398
|
+
reason: "forced"
|
|
1399
|
+
} });
|
|
1400
|
+
this.closeTries = 0;
|
|
1401
|
+
} else {
|
|
1402
|
+
this.webSocket?.close();
|
|
1403
|
+
this.messageQueue = [];
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
get serverUrl() {
|
|
1407
|
+
if (this.configuration.preserveTrailingSlash) return this.configuration.url;
|
|
1408
|
+
let url = this.configuration.url;
|
|
1409
|
+
while (url[url.length - 1] === "/") url = url.slice(0, url.length - 1);
|
|
1410
|
+
return url;
|
|
1411
|
+
}
|
|
1412
|
+
get url() {
|
|
1413
|
+
return this.serverUrl;
|
|
1414
|
+
}
|
|
1415
|
+
disconnect() {
|
|
1416
|
+
this.shouldConnect = false;
|
|
1417
|
+
if (this.webSocket === null) return;
|
|
1418
|
+
try {
|
|
1419
|
+
this.webSocket.close();
|
|
1420
|
+
this.messageQueue = [];
|
|
1421
|
+
} catch (e) {
|
|
1422
|
+
console.error(e);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
send(message) {
|
|
1426
|
+
if (this.webSocket?.readyState === WsReadyStates.Open) this.webSocket.send(message);
|
|
1427
|
+
else this.messageQueue.push(message);
|
|
1428
|
+
}
|
|
1429
|
+
onClose({ event }) {
|
|
1430
|
+
this.closeTries = 0;
|
|
1431
|
+
this.cleanupWebSocket();
|
|
1432
|
+
if (this.connectionAttempt) this.rejectConnectionAttempt();
|
|
1433
|
+
this.status = WebSocketStatus.Disconnected;
|
|
1434
|
+
this.emit("status", { status: WebSocketStatus.Disconnected });
|
|
1435
|
+
this.emit("disconnect", { event });
|
|
1436
|
+
if (!this.cancelWebsocketRetry && this.shouldConnect) setTimeout(() => {
|
|
1437
|
+
this.connect();
|
|
1438
|
+
}, this.configuration.delay);
|
|
1439
|
+
}
|
|
1440
|
+
destroy() {
|
|
1441
|
+
this.emit("destroy");
|
|
1442
|
+
clearInterval(this.intervals.connectionChecker);
|
|
1443
|
+
this.stopConnectionAttempt();
|
|
1444
|
+
this.disconnect();
|
|
1445
|
+
this.removeAllListeners();
|
|
1446
|
+
this.cleanupWebSocket();
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
//#endregion
|
|
1451
|
+
//#region node_modules/y-protocols/sync.js
|
|
1452
|
+
/**
|
|
1453
|
+
* @module sync-protocol
|
|
1454
|
+
*/
|
|
1455
|
+
/**
|
|
1456
|
+
* @typedef {Map<number, number>} StateMap
|
|
1457
|
+
*/
|
|
1458
|
+
/**
|
|
1459
|
+
* Core Yjs defines two message types:
|
|
1460
|
+
* • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2.
|
|
1461
|
+
* • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the client is assured that it
|
|
1462
|
+
* received all information from the remote client.
|
|
1463
|
+
*
|
|
1464
|
+
* In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection
|
|
1465
|
+
* with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both
|
|
1466
|
+
* SyncStep2 and SyncDone, it is assured that it is synced to the remote client.
|
|
1467
|
+
*
|
|
1468
|
+
* In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1.
|
|
1469
|
+
* When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies
|
|
1470
|
+
* with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the
|
|
1471
|
+
* client knows that the sync is finished. There are two reasons for this more elaborated sync model: 1. This protocol can
|
|
1472
|
+
* easily be implemented on top of http and websockets. 2. The server should only reply to requests, and not initiate them.
|
|
1473
|
+
* Therefore it is necessary that the client initiates the sync.
|
|
1474
|
+
*
|
|
1475
|
+
* Construction of a message:
|
|
1476
|
+
* [messageType : varUint, message definition..]
|
|
1477
|
+
*
|
|
1478
|
+
* Note: A message does not include information about the room name. This must to be handled by the upper layer protocol!
|
|
1479
|
+
*
|
|
1480
|
+
* stringify[messageType] stringifies a message definition (messageType is already read from the bufffer)
|
|
1481
|
+
*/
|
|
1482
|
+
const messageYjsSyncStep1 = 0;
|
|
1483
|
+
const messageYjsSyncStep2 = 1;
|
|
1484
|
+
const messageYjsUpdate = 2;
|
|
1485
|
+
/**
|
|
1486
|
+
* Create a sync step 1 message based on the state of the current shared document.
|
|
1487
|
+
*
|
|
1488
|
+
* @param {encoding.Encoder} encoder
|
|
1489
|
+
* @param {Y.Doc} doc
|
|
1490
|
+
*/
|
|
1491
|
+
const writeSyncStep1 = (encoder, doc) => {
|
|
1492
|
+
writeVarUint(encoder, messageYjsSyncStep1);
|
|
1493
|
+
const sv = yjs.encodeStateVector(doc);
|
|
1494
|
+
writeVarUint8Array(encoder, sv);
|
|
1495
|
+
};
|
|
1496
|
+
/**
|
|
1497
|
+
* @param {encoding.Encoder} encoder
|
|
1498
|
+
* @param {Y.Doc} doc
|
|
1499
|
+
* @param {Uint8Array} [encodedStateVector]
|
|
1500
|
+
*/
|
|
1501
|
+
const writeSyncStep2 = (encoder, doc, encodedStateVector) => {
|
|
1502
|
+
writeVarUint(encoder, messageYjsSyncStep2);
|
|
1503
|
+
writeVarUint8Array(encoder, yjs.encodeStateAsUpdate(doc, encodedStateVector));
|
|
1504
|
+
};
|
|
1505
|
+
/**
|
|
1506
|
+
* Read SyncStep1 message and reply with SyncStep2.
|
|
1507
|
+
*
|
|
1508
|
+
* @param {decoding.Decoder} decoder The reply to the received message
|
|
1509
|
+
* @param {encoding.Encoder} encoder The received message
|
|
1510
|
+
* @param {Y.Doc} doc
|
|
1511
|
+
*/
|
|
1512
|
+
const readSyncStep1 = (decoder, encoder, doc) => writeSyncStep2(encoder, doc, readVarUint8Array(decoder));
|
|
1513
|
+
/**
|
|
1514
|
+
* Read and apply Structs and then DeleteStore to a y instance.
|
|
1515
|
+
*
|
|
1516
|
+
* @param {decoding.Decoder} decoder
|
|
1517
|
+
* @param {Y.Doc} doc
|
|
1518
|
+
* @param {any} transactionOrigin
|
|
1519
|
+
* @param {(error:Error)=>any} [errorHandler]
|
|
1520
|
+
*/
|
|
1521
|
+
const readSyncStep2 = (decoder, doc, transactionOrigin, errorHandler) => {
|
|
1522
|
+
try {
|
|
1523
|
+
yjs.applyUpdate(doc, readVarUint8Array(decoder), transactionOrigin);
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
if (errorHandler != null) errorHandler(error);
|
|
1526
|
+
console.error("Caught error while handling a Yjs update", error);
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
/**
|
|
1530
|
+
* @param {encoding.Encoder} encoder
|
|
1531
|
+
* @param {Uint8Array} update
|
|
1532
|
+
*/
|
|
1533
|
+
const writeUpdate = (encoder, update) => {
|
|
1534
|
+
writeVarUint(encoder, messageYjsUpdate);
|
|
1535
|
+
writeVarUint8Array(encoder, update);
|
|
1536
|
+
};
|
|
1537
|
+
/**
|
|
1538
|
+
* Read and apply Structs and then DeleteStore to a y instance.
|
|
1539
|
+
*
|
|
1540
|
+
* @param {decoding.Decoder} decoder
|
|
1541
|
+
* @param {Y.Doc} doc
|
|
1542
|
+
* @param {any} transactionOrigin
|
|
1543
|
+
* @param {(error:Error)=>any} [errorHandler]
|
|
1544
|
+
*/
|
|
1545
|
+
const readUpdate = readSyncStep2;
|
|
1546
|
+
/**
|
|
1547
|
+
* @param {decoding.Decoder} decoder A message received from another client
|
|
1548
|
+
* @param {encoding.Encoder} encoder The reply message. Does not need to be sent if empty.
|
|
1549
|
+
* @param {Y.Doc} doc
|
|
1550
|
+
* @param {any} transactionOrigin
|
|
1551
|
+
* @param {(error:Error)=>any} [errorHandler] Optional error handler that catches errors when reading Yjs messages.
|
|
1552
|
+
*/
|
|
1553
|
+
const readSyncMessage = (decoder, encoder, doc, transactionOrigin, errorHandler) => {
|
|
1554
|
+
const messageType = readVarUint(decoder);
|
|
1555
|
+
switch (messageType) {
|
|
1556
|
+
case messageYjsSyncStep1:
|
|
1557
|
+
readSyncStep1(decoder, encoder, doc);
|
|
1558
|
+
break;
|
|
1559
|
+
case messageYjsSyncStep2:
|
|
1560
|
+
readSyncStep2(decoder, doc, transactionOrigin, errorHandler);
|
|
1561
|
+
break;
|
|
1562
|
+
case messageYjsUpdate:
|
|
1563
|
+
readUpdate(decoder, doc, transactionOrigin, errorHandler);
|
|
1564
|
+
break;
|
|
1565
|
+
default: throw new Error("Unknown message type");
|
|
1566
|
+
}
|
|
1567
|
+
return messageType;
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
//#endregion
|
|
1571
|
+
//#region packages/provider/src/MessageReceiver.ts
|
|
1572
|
+
var MessageReceiver = class {
|
|
1573
|
+
constructor(message) {
|
|
1574
|
+
this.message = message;
|
|
1575
|
+
}
|
|
1576
|
+
apply(provider, emitSynced) {
|
|
1577
|
+
const { message } = this;
|
|
1578
|
+
const type = message.readVarUint();
|
|
1579
|
+
const emptyMessageLength = message.length();
|
|
1580
|
+
switch (type) {
|
|
1581
|
+
case MessageType.Sync:
|
|
1582
|
+
this.applySyncMessage(provider, emitSynced);
|
|
1583
|
+
break;
|
|
1584
|
+
case MessageType.Awareness:
|
|
1585
|
+
this.applyAwarenessMessage(provider);
|
|
1586
|
+
break;
|
|
1587
|
+
case MessageType.Auth:
|
|
1588
|
+
this.applyAuthMessage(provider);
|
|
1589
|
+
break;
|
|
1590
|
+
case MessageType.QueryAwareness:
|
|
1591
|
+
this.applyQueryAwarenessMessage(provider);
|
|
1592
|
+
break;
|
|
1593
|
+
case MessageType.Stateless:
|
|
1594
|
+
provider.receiveStateless(readVarString(message.decoder));
|
|
1595
|
+
break;
|
|
1596
|
+
case MessageType.SyncStatus:
|
|
1597
|
+
this.applySyncStatusMessage(provider, readVarInt(message.decoder) === 1);
|
|
1598
|
+
break;
|
|
1599
|
+
case MessageType.CLOSE:
|
|
1600
|
+
const event = {
|
|
1601
|
+
code: 1e3,
|
|
1602
|
+
reason: readVarString(message.decoder),
|
|
1603
|
+
target: provider.configuration.websocketProvider.webSocket,
|
|
1604
|
+
type: "close"
|
|
1605
|
+
};
|
|
1606
|
+
provider.onClose();
|
|
1607
|
+
provider.configuration.onClose({ event });
|
|
1608
|
+
provider.forwardClose({ event });
|
|
1609
|
+
break;
|
|
1610
|
+
default: throw new Error(`Can’t apply message of unknown type: ${type}`);
|
|
1611
|
+
}
|
|
1612
|
+
if (message.length() > emptyMessageLength + 1) provider.send(OutgoingMessage, { encoder: message.encoder });
|
|
1613
|
+
}
|
|
1614
|
+
applySyncMessage(provider, emitSynced) {
|
|
1615
|
+
const { message } = this;
|
|
1616
|
+
message.writeVarUint(MessageType.Sync);
|
|
1617
|
+
const syncMessageType = readSyncMessage(message.decoder, message.encoder, provider.document, provider);
|
|
1618
|
+
if (emitSynced && syncMessageType === messageYjsSyncStep2) provider.synced = true;
|
|
1619
|
+
}
|
|
1620
|
+
applySyncStatusMessage(provider, applied) {
|
|
1621
|
+
if (applied) provider.decrementUnsyncedChanges();
|
|
1622
|
+
}
|
|
1623
|
+
applyAwarenessMessage(provider) {
|
|
1624
|
+
if (!provider.awareness) return;
|
|
1625
|
+
const { message } = this;
|
|
1626
|
+
applyAwarenessUpdate(provider.awareness, message.readVarUint8Array(), provider);
|
|
1627
|
+
}
|
|
1628
|
+
applyAuthMessage(provider) {
|
|
1629
|
+
const { message } = this;
|
|
1630
|
+
readAuthMessage(message.decoder, provider.sendToken.bind(provider), provider.permissionDeniedHandler.bind(provider), provider.authenticatedHandler.bind(provider));
|
|
1631
|
+
}
|
|
1632
|
+
applyQueryAwarenessMessage(provider) {
|
|
1633
|
+
if (!provider.awareness) return;
|
|
1634
|
+
const { message } = this;
|
|
1635
|
+
message.writeVarUint(MessageType.Awareness);
|
|
1636
|
+
message.writeVarUint8Array(encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())));
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
//#endregion
|
|
1641
|
+
//#region packages/provider/src/MessageSender.ts
|
|
1642
|
+
var MessageSender = class {
|
|
1643
|
+
constructor(Message, args = {}) {
|
|
1644
|
+
this.message = new Message();
|
|
1645
|
+
this.encoder = this.message.get(args);
|
|
1646
|
+
}
|
|
1647
|
+
create() {
|
|
1648
|
+
return toUint8Array(this.encoder);
|
|
1649
|
+
}
|
|
1650
|
+
send(webSocket) {
|
|
1651
|
+
webSocket?.send(this.create());
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
//#endregion
|
|
1656
|
+
//#region packages/provider/src/OutgoingMessages/AuthenticationMessage.ts
|
|
1657
|
+
var AuthenticationMessage = class extends OutgoingMessage {
|
|
1658
|
+
constructor(..._args) {
|
|
1659
|
+
super(..._args);
|
|
1660
|
+
this.type = MessageType.Auth;
|
|
1661
|
+
this.description = "Authentication";
|
|
1662
|
+
}
|
|
1663
|
+
get(args) {
|
|
1664
|
+
if (typeof args.token === "undefined") throw new Error("The authentication message requires `token` as an argument.");
|
|
1665
|
+
writeVarString(this.encoder, args.documentName);
|
|
1666
|
+
writeVarUint(this.encoder, this.type);
|
|
1667
|
+
writeAuthentication(this.encoder, args.token);
|
|
1668
|
+
return this.encoder;
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
|
|
1672
|
+
//#endregion
|
|
1673
|
+
//#region packages/provider/src/OutgoingMessages/AwarenessMessage.ts
|
|
1674
|
+
var AwarenessMessage = class extends OutgoingMessage {
|
|
1675
|
+
constructor(..._args) {
|
|
1676
|
+
super(..._args);
|
|
1677
|
+
this.type = MessageType.Awareness;
|
|
1678
|
+
this.description = "Awareness states update";
|
|
1679
|
+
}
|
|
1680
|
+
get(args) {
|
|
1681
|
+
if (typeof args.awareness === "undefined") throw new Error("The awareness message requires awareness as an argument");
|
|
1682
|
+
if (typeof args.clients === "undefined") throw new Error("The awareness message requires clients as an argument");
|
|
1683
|
+
writeVarString(this.encoder, args.documentName);
|
|
1684
|
+
writeVarUint(this.encoder, this.type);
|
|
1685
|
+
let awarenessUpdate;
|
|
1686
|
+
if (args.states === void 0) awarenessUpdate = encodeAwarenessUpdate(args.awareness, args.clients);
|
|
1687
|
+
else awarenessUpdate = encodeAwarenessUpdate(args.awareness, args.clients, args.states);
|
|
1688
|
+
writeVarUint8Array(this.encoder, awarenessUpdate);
|
|
1689
|
+
return this.encoder;
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
//#endregion
|
|
1694
|
+
//#region packages/provider/src/OutgoingMessages/StatelessMessage.ts
|
|
1695
|
+
var StatelessMessage = class extends OutgoingMessage {
|
|
1696
|
+
constructor(..._args) {
|
|
1697
|
+
super(..._args);
|
|
1698
|
+
this.type = MessageType.Stateless;
|
|
1699
|
+
this.description = "A stateless message";
|
|
1700
|
+
}
|
|
1701
|
+
get(args) {
|
|
1702
|
+
writeVarString(this.encoder, args.documentName);
|
|
1703
|
+
writeVarUint(this.encoder, this.type);
|
|
1704
|
+
writeVarString(this.encoder, args.payload ?? "");
|
|
1705
|
+
return this.encoder;
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
|
|
1709
|
+
//#endregion
|
|
1710
|
+
//#region packages/provider/src/OutgoingMessages/SyncStepOneMessage.ts
|
|
1711
|
+
var SyncStepOneMessage = class extends OutgoingMessage {
|
|
1712
|
+
constructor(..._args) {
|
|
1713
|
+
super(..._args);
|
|
1714
|
+
this.type = MessageType.Sync;
|
|
1715
|
+
this.description = "First sync step";
|
|
1716
|
+
}
|
|
1717
|
+
get(args) {
|
|
1718
|
+
if (typeof args.document === "undefined") throw new Error("The sync step one message requires document as an argument");
|
|
1719
|
+
writeVarString(this.encoder, args.documentName);
|
|
1720
|
+
writeVarUint(this.encoder, this.type);
|
|
1721
|
+
writeSyncStep1(this.encoder, args.document);
|
|
1722
|
+
return this.encoder;
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
|
|
1726
|
+
//#endregion
|
|
1727
|
+
//#region packages/provider/src/OutgoingMessages/UpdateMessage.ts
|
|
1728
|
+
var UpdateMessage = class extends OutgoingMessage {
|
|
1729
|
+
constructor(..._args) {
|
|
1730
|
+
super(..._args);
|
|
1731
|
+
this.type = MessageType.Sync;
|
|
1732
|
+
this.description = "A document update";
|
|
1733
|
+
}
|
|
1734
|
+
get(args) {
|
|
1735
|
+
writeVarString(this.encoder, args.documentName);
|
|
1736
|
+
writeVarUint(this.encoder, this.type);
|
|
1737
|
+
writeUpdate(this.encoder, args.update);
|
|
1738
|
+
return this.encoder;
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
//#endregion
|
|
1743
|
+
//#region packages/provider/src/HocuspocusProvider.ts
|
|
1744
|
+
var AwarenessError = class extends Error {
|
|
1745
|
+
constructor(..._args) {
|
|
1746
|
+
super(..._args);
|
|
1747
|
+
this.code = 1001;
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
var HocuspocusProvider = class extends EventEmitter {
|
|
1751
|
+
constructor(configuration) {
|
|
1752
|
+
super();
|
|
1753
|
+
this.configuration = {
|
|
1754
|
+
name: "",
|
|
1755
|
+
document: void 0,
|
|
1756
|
+
awareness: void 0,
|
|
1757
|
+
token: null,
|
|
1758
|
+
forceSyncInterval: false,
|
|
1759
|
+
onAuthenticated: () => null,
|
|
1760
|
+
onAuthenticationFailed: () => null,
|
|
1761
|
+
onOpen: () => null,
|
|
1762
|
+
onConnect: () => null,
|
|
1763
|
+
onMessage: () => null,
|
|
1764
|
+
onOutgoingMessage: () => null,
|
|
1765
|
+
onSynced: () => null,
|
|
1766
|
+
onStatus: () => null,
|
|
1767
|
+
onDisconnect: () => null,
|
|
1768
|
+
onClose: () => null,
|
|
1769
|
+
onDestroy: () => null,
|
|
1770
|
+
onAwarenessUpdate: () => null,
|
|
1771
|
+
onAwarenessChange: () => null,
|
|
1772
|
+
onStateless: () => null,
|
|
1773
|
+
onUnsyncedChanges: () => null
|
|
1774
|
+
};
|
|
1775
|
+
this.isSynced = false;
|
|
1776
|
+
this.unsyncedChanges = 0;
|
|
1777
|
+
this.isAuthenticated = false;
|
|
1778
|
+
this.authorizedScope = void 0;
|
|
1779
|
+
this.manageSocket = false;
|
|
1780
|
+
this._isAttached = false;
|
|
1781
|
+
this.intervals = { forceSync: null };
|
|
1782
|
+
this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
|
|
1783
|
+
this.boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this);
|
|
1784
|
+
this.boundPageHide = this.pageHide.bind(this);
|
|
1785
|
+
this.boundOnOpen = this.onOpen.bind(this);
|
|
1786
|
+
this.boundOnClose = this.onClose.bind(this);
|
|
1787
|
+
this.forwardConnect = () => this.emit("connect");
|
|
1788
|
+
this.forwardStatus = (e) => this.emit("status", e);
|
|
1789
|
+
this.forwardClose = (e) => this.emit("close", e);
|
|
1790
|
+
this.forwardDisconnect = (e) => this.emit("disconnect", e);
|
|
1791
|
+
this.forwardDestroy = () => this.emit("destroy");
|
|
1792
|
+
this.setConfiguration(configuration);
|
|
1793
|
+
this.configuration.document = configuration.document ? configuration.document : new yjs.Doc();
|
|
1794
|
+
this.configuration.awareness = configuration.awareness !== void 0 ? configuration.awareness : new Awareness(this.document);
|
|
1795
|
+
this.on("open", this.configuration.onOpen);
|
|
1796
|
+
this.on("message", this.configuration.onMessage);
|
|
1797
|
+
this.on("outgoingMessage", this.configuration.onOutgoingMessage);
|
|
1798
|
+
this.on("synced", this.configuration.onSynced);
|
|
1799
|
+
this.on("destroy", this.configuration.onDestroy);
|
|
1800
|
+
this.on("awarenessUpdate", this.configuration.onAwarenessUpdate);
|
|
1801
|
+
this.on("awarenessChange", this.configuration.onAwarenessChange);
|
|
1802
|
+
this.on("stateless", this.configuration.onStateless);
|
|
1803
|
+
this.on("unsyncedChanges", this.configuration.onUnsyncedChanges);
|
|
1804
|
+
this.on("authenticated", this.configuration.onAuthenticated);
|
|
1805
|
+
this.on("authenticationFailed", this.configuration.onAuthenticationFailed);
|
|
1806
|
+
this.awareness?.on("update", () => {
|
|
1807
|
+
this.emit("awarenessUpdate", { states: awarenessStatesToArray(this.awareness.getStates()) });
|
|
1808
|
+
});
|
|
1809
|
+
this.awareness?.on("change", () => {
|
|
1810
|
+
this.emit("awarenessChange", { states: awarenessStatesToArray(this.awareness.getStates()) });
|
|
1811
|
+
});
|
|
1812
|
+
this.document.on("update", this.boundDocumentUpdateHandler);
|
|
1813
|
+
this.awareness?.on("update", this.boundAwarenessUpdateHandler);
|
|
1814
|
+
this.registerEventListeners();
|
|
1815
|
+
if (this.configuration.forceSyncInterval && typeof this.configuration.forceSyncInterval === "number") this.intervals.forceSync = setInterval(this.forceSync.bind(this), this.configuration.forceSyncInterval);
|
|
1816
|
+
if (this.manageSocket) this.attach();
|
|
1817
|
+
}
|
|
1818
|
+
setConfiguration(configuration = {}) {
|
|
1819
|
+
if (!configuration.websocketProvider) {
|
|
1820
|
+
this.manageSocket = true;
|
|
1821
|
+
this.configuration.websocketProvider = new HocuspocusProviderWebsocket(configuration);
|
|
1822
|
+
}
|
|
1823
|
+
this.configuration = {
|
|
1824
|
+
...this.configuration,
|
|
1825
|
+
...configuration
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
get document() {
|
|
1829
|
+
return this.configuration.document;
|
|
1830
|
+
}
|
|
1831
|
+
get isAttached() {
|
|
1832
|
+
return this._isAttached;
|
|
1833
|
+
}
|
|
1834
|
+
get awareness() {
|
|
1835
|
+
return this.configuration.awareness;
|
|
1836
|
+
}
|
|
1837
|
+
get hasUnsyncedChanges() {
|
|
1838
|
+
return this.unsyncedChanges > 0;
|
|
1839
|
+
}
|
|
1840
|
+
resetUnsyncedChanges() {
|
|
1841
|
+
this.unsyncedChanges = 1;
|
|
1842
|
+
this.emit("unsyncedChanges", { number: this.unsyncedChanges });
|
|
1843
|
+
}
|
|
1844
|
+
incrementUnsyncedChanges() {
|
|
1845
|
+
this.unsyncedChanges += 1;
|
|
1846
|
+
this.emit("unsyncedChanges", { number: this.unsyncedChanges });
|
|
1847
|
+
}
|
|
1848
|
+
decrementUnsyncedChanges() {
|
|
1849
|
+
if (this.unsyncedChanges > 0) this.unsyncedChanges -= 1;
|
|
1850
|
+
if (this.unsyncedChanges === 0) this.synced = true;
|
|
1851
|
+
this.emit("unsyncedChanges", { number: this.unsyncedChanges });
|
|
1852
|
+
}
|
|
1853
|
+
forceSync() {
|
|
1854
|
+
this.resetUnsyncedChanges();
|
|
1855
|
+
this.send(SyncStepOneMessage, {
|
|
1856
|
+
document: this.document,
|
|
1857
|
+
documentName: this.configuration.name
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
pageHide() {
|
|
1861
|
+
if (this.awareness) removeAwarenessStates(this.awareness, [this.document.clientID], "page hide");
|
|
1862
|
+
}
|
|
1863
|
+
registerEventListeners() {
|
|
1864
|
+
if (typeof window === "undefined" || !("addEventListener" in window)) return;
|
|
1865
|
+
window.addEventListener("pagehide", this.boundPageHide);
|
|
1866
|
+
}
|
|
1867
|
+
sendStateless(payload) {
|
|
1868
|
+
this.send(StatelessMessage, {
|
|
1869
|
+
documentName: this.configuration.name,
|
|
1870
|
+
payload
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
async sendToken() {
|
|
1874
|
+
let token;
|
|
1875
|
+
try {
|
|
1876
|
+
token = await this.getToken();
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
this.permissionDeniedHandler(`Failed to get token during sendToken(): ${error}`);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
this.send(AuthenticationMessage, {
|
|
1882
|
+
token: token ?? "",
|
|
1883
|
+
documentName: this.configuration.name
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
documentUpdateHandler(update, origin) {
|
|
1887
|
+
if (origin === this) return;
|
|
1888
|
+
this.incrementUnsyncedChanges();
|
|
1889
|
+
this.send(UpdateMessage, {
|
|
1890
|
+
update,
|
|
1891
|
+
documentName: this.configuration.name
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
awarenessUpdateHandler({ added, updated, removed }, origin) {
|
|
1895
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
1896
|
+
this.send(AwarenessMessage, {
|
|
1897
|
+
awareness: this.awareness,
|
|
1898
|
+
clients: changedClients,
|
|
1899
|
+
documentName: this.configuration.name
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Indicates whether a first handshake with the server has been established
|
|
1904
|
+
*
|
|
1905
|
+
* Note: this does not mean all updates from the client have been persisted to the backend. For this,
|
|
1906
|
+
* use `hasUnsyncedChanges`.
|
|
1907
|
+
*/
|
|
1908
|
+
get synced() {
|
|
1909
|
+
return this.isSynced;
|
|
1910
|
+
}
|
|
1911
|
+
set synced(state) {
|
|
1912
|
+
if (this.isSynced === state) return;
|
|
1913
|
+
this.isSynced = state;
|
|
1914
|
+
if (state) this.emit("synced", { state });
|
|
1915
|
+
}
|
|
1916
|
+
receiveStateless(payload) {
|
|
1917
|
+
this.emit("stateless", { payload });
|
|
1918
|
+
}
|
|
1919
|
+
async connect() {
|
|
1920
|
+
if (this.manageSocket) return this.configuration.websocketProvider.connect();
|
|
1921
|
+
console.warn("HocuspocusProvider::connect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
|
|
1922
|
+
}
|
|
1923
|
+
disconnect() {
|
|
1924
|
+
if (this.manageSocket) return this.configuration.websocketProvider.disconnect();
|
|
1925
|
+
console.warn("HocuspocusProvider::disconnect() is deprecated and does not do anything. Please connect/disconnect on the websocketProvider, or attach/deattach providers.");
|
|
1926
|
+
}
|
|
1927
|
+
async onOpen(event) {
|
|
1928
|
+
this.isAuthenticated = false;
|
|
1929
|
+
this.emit("open", { event });
|
|
1930
|
+
await this.sendToken();
|
|
1931
|
+
this.startSync();
|
|
1932
|
+
}
|
|
1933
|
+
async getToken() {
|
|
1934
|
+
if (typeof this.configuration.token === "function") return await this.configuration.token();
|
|
1935
|
+
return this.configuration.token;
|
|
1936
|
+
}
|
|
1937
|
+
startSync() {
|
|
1938
|
+
this.resetUnsyncedChanges();
|
|
1939
|
+
this.send(SyncStepOneMessage, {
|
|
1940
|
+
document: this.document,
|
|
1941
|
+
documentName: this.configuration.name
|
|
1942
|
+
});
|
|
1943
|
+
if (this.awareness && this.awareness.getLocalState() !== null) this.send(AwarenessMessage, {
|
|
1944
|
+
awareness: this.awareness,
|
|
1945
|
+
clients: [this.document.clientID],
|
|
1946
|
+
documentName: this.configuration.name
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
send(message, args) {
|
|
1950
|
+
if (!this._isAttached) return;
|
|
1951
|
+
const messageSender = new MessageSender(message, args);
|
|
1952
|
+
this.emit("outgoingMessage", { message: messageSender.message });
|
|
1953
|
+
messageSender.send(this.configuration.websocketProvider);
|
|
1954
|
+
}
|
|
1955
|
+
onMessage(event) {
|
|
1956
|
+
const message = new IncomingMessage(event.data);
|
|
1957
|
+
const documentName = message.readVarString();
|
|
1958
|
+
message.writeVarString(documentName);
|
|
1959
|
+
this.emit("message", {
|
|
1960
|
+
event,
|
|
1961
|
+
message: new IncomingMessage(event.data)
|
|
1962
|
+
});
|
|
1963
|
+
new MessageReceiver(message).apply(this, true);
|
|
1964
|
+
}
|
|
1965
|
+
onClose() {
|
|
1966
|
+
this.isAuthenticated = false;
|
|
1967
|
+
this.synced = false;
|
|
1968
|
+
if (this.awareness) removeAwarenessStates(this.awareness, Array.from(this.awareness.getStates().keys()).filter((client) => client !== this.document.clientID), this);
|
|
1969
|
+
}
|
|
1970
|
+
destroy() {
|
|
1971
|
+
this.emit("destroy");
|
|
1972
|
+
if (this.intervals.forceSync) clearInterval(this.intervals.forceSync);
|
|
1973
|
+
if (this.awareness) {
|
|
1974
|
+
removeAwarenessStates(this.awareness, [this.document.clientID], "provider destroy");
|
|
1975
|
+
this.awareness.off("update", this.boundAwarenessUpdateHandler);
|
|
1976
|
+
this.awareness.destroy();
|
|
1977
|
+
}
|
|
1978
|
+
this.document.off("update", this.boundDocumentUpdateHandler);
|
|
1979
|
+
this.removeAllListeners();
|
|
1980
|
+
this.detach();
|
|
1981
|
+
if (this.manageSocket) this.configuration.websocketProvider.destroy();
|
|
1982
|
+
if (typeof window === "undefined" || !("removeEventListener" in window)) return;
|
|
1983
|
+
window.removeEventListener("pagehide", this.boundPageHide);
|
|
1984
|
+
}
|
|
1985
|
+
detach() {
|
|
1986
|
+
this.configuration.websocketProvider.off("connect", this.configuration.onConnect);
|
|
1987
|
+
this.configuration.websocketProvider.off("connect", this.forwardConnect);
|
|
1988
|
+
this.configuration.websocketProvider.off("status", this.forwardStatus);
|
|
1989
|
+
this.configuration.websocketProvider.off("status", this.configuration.onStatus);
|
|
1990
|
+
this.configuration.websocketProvider.off("open", this.boundOnOpen);
|
|
1991
|
+
this.configuration.websocketProvider.off("close", this.boundOnClose);
|
|
1992
|
+
this.configuration.websocketProvider.off("close", this.configuration.onClose);
|
|
1993
|
+
this.configuration.websocketProvider.off("close", this.forwardClose);
|
|
1994
|
+
this.configuration.websocketProvider.off("disconnect", this.configuration.onDisconnect);
|
|
1995
|
+
this.configuration.websocketProvider.off("disconnect", this.forwardDisconnect);
|
|
1996
|
+
this.configuration.websocketProvider.off("destroy", this.configuration.onDestroy);
|
|
1997
|
+
this.configuration.websocketProvider.off("destroy", this.forwardDestroy);
|
|
1998
|
+
this.configuration.websocketProvider.detach(this);
|
|
1999
|
+
this._isAttached = false;
|
|
2000
|
+
}
|
|
2001
|
+
attach() {
|
|
2002
|
+
if (this._isAttached) return;
|
|
2003
|
+
this.configuration.websocketProvider.on("connect", this.configuration.onConnect);
|
|
2004
|
+
this.configuration.websocketProvider.on("connect", this.forwardConnect);
|
|
2005
|
+
this.configuration.websocketProvider.on("status", this.configuration.onStatus);
|
|
2006
|
+
this.configuration.websocketProvider.on("status", this.forwardStatus);
|
|
2007
|
+
this.configuration.websocketProvider.on("open", this.boundOnOpen);
|
|
2008
|
+
this.configuration.websocketProvider.on("close", this.boundOnClose);
|
|
2009
|
+
this.configuration.websocketProvider.on("close", this.configuration.onClose);
|
|
2010
|
+
this.configuration.websocketProvider.on("close", this.forwardClose);
|
|
2011
|
+
this.configuration.websocketProvider.on("disconnect", this.configuration.onDisconnect);
|
|
2012
|
+
this.configuration.websocketProvider.on("disconnect", this.forwardDisconnect);
|
|
2013
|
+
this.configuration.websocketProvider.on("destroy", this.configuration.onDestroy);
|
|
2014
|
+
this.configuration.websocketProvider.on("destroy", this.forwardDestroy);
|
|
2015
|
+
this.configuration.websocketProvider.attach(this);
|
|
2016
|
+
this._isAttached = true;
|
|
2017
|
+
}
|
|
2018
|
+
permissionDeniedHandler(reason) {
|
|
2019
|
+
this.emit("authenticationFailed", { reason });
|
|
2020
|
+
this.isAuthenticated = false;
|
|
2021
|
+
}
|
|
2022
|
+
authenticatedHandler(scope) {
|
|
2023
|
+
this.isAuthenticated = true;
|
|
2024
|
+
this.authorizedScope = scope;
|
|
2025
|
+
this.emit("authenticated", { scope });
|
|
2026
|
+
}
|
|
2027
|
+
setAwarenessField(key, value) {
|
|
2028
|
+
if (!this.awareness) 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.`);
|
|
2029
|
+
this.awareness.setLocalStateField(key, value);
|
|
2030
|
+
}
|
|
2031
|
+
};
|
|
2032
|
+
|
|
2033
|
+
//#endregion
|
|
2034
|
+
//#region packages/provider/src/OfflineStore.ts
|
|
2035
|
+
const DB_VERSION = 1;
|
|
2036
|
+
function idbAvailable() {
|
|
2037
|
+
return typeof globalThis !== "undefined" && "indexedDB" in globalThis;
|
|
2038
|
+
}
|
|
2039
|
+
function openDb$1(docId) {
|
|
2040
|
+
return new Promise((resolve, reject) => {
|
|
2041
|
+
const req = globalThis.indexedDB.open(`abracadabra:${docId}`, DB_VERSION);
|
|
2042
|
+
req.onupgradeneeded = (event) => {
|
|
2043
|
+
const db = event.target.result;
|
|
2044
|
+
if (!db.objectStoreNames.contains("updates")) db.createObjectStore("updates", { autoIncrement: true });
|
|
2045
|
+
if (!db.objectStoreNames.contains("meta")) db.createObjectStore("meta");
|
|
2046
|
+
if (!db.objectStoreNames.contains("subdoc_queue")) db.createObjectStore("subdoc_queue", { keyPath: "childId" });
|
|
2047
|
+
};
|
|
2048
|
+
req.onsuccess = () => resolve(req.result);
|
|
2049
|
+
req.onerror = () => reject(req.error);
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
function txPromise(store, request) {
|
|
2053
|
+
return new Promise((resolve, reject) => {
|
|
2054
|
+
request.onsuccess = () => resolve(request.result);
|
|
2055
|
+
request.onerror = () => reject(request.error);
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
var OfflineStore = class {
|
|
2059
|
+
constructor(docId) {
|
|
2060
|
+
this.db = null;
|
|
2061
|
+
this.docId = docId;
|
|
2062
|
+
}
|
|
2063
|
+
async getDb() {
|
|
2064
|
+
if (!idbAvailable()) return null;
|
|
2065
|
+
if (!this.db) this.db = await openDb$1(this.docId).catch(() => null);
|
|
2066
|
+
return this.db;
|
|
2067
|
+
}
|
|
2068
|
+
async persistUpdate(update) {
|
|
2069
|
+
const db = await this.getDb();
|
|
2070
|
+
if (!db) return;
|
|
2071
|
+
const store = db.transaction("updates", "readwrite").objectStore("updates");
|
|
2072
|
+
await txPromise(store, store.add(update));
|
|
2073
|
+
}
|
|
2074
|
+
async getPendingUpdates() {
|
|
2075
|
+
const db = await this.getDb();
|
|
2076
|
+
if (!db) return [];
|
|
2077
|
+
return new Promise((resolve, reject) => {
|
|
2078
|
+
const req = db.transaction("updates", "readonly").objectStore("updates").getAll();
|
|
2079
|
+
req.onsuccess = () => resolve(req.result);
|
|
2080
|
+
req.onerror = () => reject(req.error);
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
async clearPendingUpdates() {
|
|
2084
|
+
const db = await this.getDb();
|
|
2085
|
+
if (!db) return;
|
|
2086
|
+
const tx = db.transaction("updates", "readwrite");
|
|
2087
|
+
await txPromise(tx.objectStore("updates"), tx.objectStore("updates").clear());
|
|
2088
|
+
}
|
|
2089
|
+
async getStateVector() {
|
|
2090
|
+
const db = await this.getDb();
|
|
2091
|
+
if (!db) return null;
|
|
2092
|
+
const tx = db.transaction("meta", "readonly");
|
|
2093
|
+
return await txPromise(tx.objectStore("meta"), tx.objectStore("meta").get("sv")) ?? null;
|
|
2094
|
+
}
|
|
2095
|
+
async saveStateVector(sv) {
|
|
2096
|
+
const db = await this.getDb();
|
|
2097
|
+
if (!db) return;
|
|
2098
|
+
const tx = db.transaction("meta", "readwrite");
|
|
2099
|
+
await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(sv, "sv"));
|
|
2100
|
+
}
|
|
2101
|
+
async getPermissionSnapshot() {
|
|
2102
|
+
const db = await this.getDb();
|
|
2103
|
+
if (!db) return null;
|
|
2104
|
+
const tx = db.transaction("meta", "readonly");
|
|
2105
|
+
return await txPromise(tx.objectStore("meta"), tx.objectStore("meta").get("role")) ?? null;
|
|
2106
|
+
}
|
|
2107
|
+
async savePermissionSnapshot(role) {
|
|
2108
|
+
const db = await this.getDb();
|
|
2109
|
+
if (!db) return;
|
|
2110
|
+
const tx = db.transaction("meta", "readwrite");
|
|
2111
|
+
await txPromise(tx.objectStore("meta"), tx.objectStore("meta").put(role, "role"));
|
|
2112
|
+
}
|
|
2113
|
+
async queueSubdoc(entry) {
|
|
2114
|
+
const db = await this.getDb();
|
|
2115
|
+
if (!db) return;
|
|
2116
|
+
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
2117
|
+
await txPromise(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").put(entry));
|
|
2118
|
+
}
|
|
2119
|
+
async getPendingSubdocs() {
|
|
2120
|
+
const db = await this.getDb();
|
|
2121
|
+
if (!db) return [];
|
|
2122
|
+
return new Promise((resolve, reject) => {
|
|
2123
|
+
const req = db.transaction("subdoc_queue", "readonly").objectStore("subdoc_queue").getAll();
|
|
2124
|
+
req.onsuccess = () => resolve(req.result);
|
|
2125
|
+
req.onerror = () => reject(req.error);
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
async removeSubdocFromQueue(childId) {
|
|
2129
|
+
const db = await this.getDb();
|
|
2130
|
+
if (!db) return;
|
|
2131
|
+
const tx = db.transaction("subdoc_queue", "readwrite");
|
|
2132
|
+
await txPromise(tx.objectStore("subdoc_queue"), tx.objectStore("subdoc_queue").delete(childId));
|
|
2133
|
+
}
|
|
2134
|
+
destroy() {
|
|
2135
|
+
this.db?.close();
|
|
2136
|
+
this.db = null;
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
|
|
2140
|
+
//#endregion
|
|
2141
|
+
//#region packages/provider/src/OutgoingMessages/SubdocMessage.ts
|
|
2142
|
+
/**
|
|
2143
|
+
* Registers a new subdocument with the Abracadabra server.
|
|
2144
|
+
*
|
|
2145
|
+
* Wire format (consumed by handle_subdoc in sync.rs):
|
|
2146
|
+
* [ParentDocName: VarString] [MSG_SUBDOC(4): VarUint] [childDocumentName: VarString]
|
|
2147
|
+
*
|
|
2148
|
+
* The server responds with a MSG_STATELESS JSON frame:
|
|
2149
|
+
* { type: "subdoc_registered", child_id, parent_id }
|
|
2150
|
+
* which AbracadabraProvider intercepts in receiveStateless().
|
|
2151
|
+
*/
|
|
2152
|
+
var SubdocMessage = class extends OutgoingMessage {
|
|
2153
|
+
constructor(..._args) {
|
|
2154
|
+
super(..._args);
|
|
2155
|
+
this.type = MessageType.Subdoc;
|
|
2156
|
+
this.description = "SubdocRegistration";
|
|
2157
|
+
}
|
|
2158
|
+
get(args) {
|
|
2159
|
+
if (!args.documentName) throw new Error("SubdocMessage requires `documentName` (parent id).");
|
|
2160
|
+
if (!args.childDocumentName) throw new Error("SubdocMessage requires `childDocumentName`.");
|
|
2161
|
+
writeVarString(this.encoder, args.documentName);
|
|
2162
|
+
writeVarUint(this.encoder, this.type);
|
|
2163
|
+
writeVarString(this.encoder, args.childDocumentName);
|
|
2164
|
+
return this.encoder;
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
//#endregion
|
|
2169
|
+
//#region packages/provider/src/AbracadabraProvider.ts
|
|
2170
|
+
/**
|
|
2171
|
+
* AbracadabraProvider extends HocuspocusProvider with:
|
|
2172
|
+
*
|
|
2173
|
+
* 1. Subdocument lifecycle – intercepts Y.Doc subdoc events and syncs them
|
|
2174
|
+
* with the server via MSG_SUBDOC (4) frames. Child documents get their
|
|
2175
|
+
* own AbracadabraProvider instances sharing the same WebSocket connection.
|
|
2176
|
+
*
|
|
2177
|
+
* 2. Offline-first – persists CRDT updates to IndexedDB so they survive
|
|
2178
|
+
* page reloads and network outages. On reconnect, pending updates are
|
|
2179
|
+
* flushed before resuming normal sync.
|
|
2180
|
+
*
|
|
2181
|
+
* 3. Permission snapshotting – stores the resolved role locally so the UI
|
|
2182
|
+
* can gate write operations without a network round-trip. Role is
|
|
2183
|
+
* refreshed from the server on every reconnect.
|
|
2184
|
+
*/
|
|
2185
|
+
var AbracadabraProvider = class AbracadabraProvider extends HocuspocusProvider {
|
|
2186
|
+
constructor(configuration) {
|
|
2187
|
+
super(configuration);
|
|
2188
|
+
this.effectiveRole = null;
|
|
2189
|
+
this.childProviders = /* @__PURE__ */ new Map();
|
|
2190
|
+
this.boundHandleYSubdocsChange = this.handleYSubdocsChange.bind(this);
|
|
2191
|
+
this.abracadabraConfig = configuration;
|
|
2192
|
+
this.subdocLoading = configuration.subdocLoading ?? "lazy";
|
|
2193
|
+
this.offlineStore = configuration.disableOfflineStore ? null : new OfflineStore(configuration.name);
|
|
2194
|
+
this.on("subdocRegistered", configuration.onSubdocRegistered ?? (() => null));
|
|
2195
|
+
this.on("subdocLoaded", configuration.onSubdocLoaded ?? (() => null));
|
|
2196
|
+
this.document.on("subdocs", this.boundHandleYSubdocsChange);
|
|
2197
|
+
this.on("synced", () => this.flushPendingUpdates());
|
|
2198
|
+
this.restorePermissionSnapshot();
|
|
2199
|
+
}
|
|
2200
|
+
authenticatedHandler(scope) {
|
|
2201
|
+
super.authenticatedHandler(scope);
|
|
2202
|
+
this.effectiveRole = scope === "read-write" ? "editor" : "viewer";
|
|
2203
|
+
this.offlineStore?.savePermissionSnapshot(this.effectiveRole);
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Override sendToken to send an identity declaration instead of a JWT
|
|
2207
|
+
* when cryptoIdentity is configured.
|
|
2208
|
+
*/
|
|
2209
|
+
async sendToken() {
|
|
2210
|
+
const { cryptoIdentity } = this.abracadabraConfig;
|
|
2211
|
+
if (cryptoIdentity) {
|
|
2212
|
+
const id = typeof cryptoIdentity === "function" ? await cryptoIdentity() : cryptoIdentity;
|
|
2213
|
+
const json = JSON.stringify({
|
|
2214
|
+
type: "identity",
|
|
2215
|
+
username: id.username,
|
|
2216
|
+
publicKey: id.publicKey
|
|
2217
|
+
});
|
|
2218
|
+
this.send(AuthenticationMessage, {
|
|
2219
|
+
token: json,
|
|
2220
|
+
documentName: this.configuration.name
|
|
2221
|
+
});
|
|
2222
|
+
} else await super.sendToken();
|
|
2223
|
+
}
|
|
2224
|
+
/** Handle an auth_challenge message from the server. */
|
|
2225
|
+
async handleAuthChallenge(challenge, expiresAt) {
|
|
2226
|
+
const { signChallenge, cryptoIdentity } = this.abracadabraConfig;
|
|
2227
|
+
if (!signChallenge || !cryptoIdentity) {
|
|
2228
|
+
this.permissionDeniedHandler("No signChallenge callback configured");
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
if (Date.now() > expiresAt * 1e3) {
|
|
2232
|
+
this.permissionDeniedHandler("Challenge expired");
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const id = typeof cryptoIdentity === "function" ? await cryptoIdentity() : cryptoIdentity;
|
|
2236
|
+
const signature = await signChallenge(challenge);
|
|
2237
|
+
const proof = JSON.stringify({
|
|
2238
|
+
type: "proof",
|
|
2239
|
+
username: id.username,
|
|
2240
|
+
publicKey: id.publicKey,
|
|
2241
|
+
signature,
|
|
2242
|
+
challenge
|
|
2243
|
+
});
|
|
2244
|
+
this.send(AuthenticationMessage, {
|
|
2245
|
+
token: proof,
|
|
2246
|
+
documentName: this.configuration.name
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
async restorePermissionSnapshot() {
|
|
2250
|
+
if (!this.offlineStore) return;
|
|
2251
|
+
const role = await this.offlineStore.getPermissionSnapshot();
|
|
2252
|
+
if (role && !this.effectiveRole) this.effectiveRole = role;
|
|
2253
|
+
}
|
|
2254
|
+
get canWrite() {
|
|
2255
|
+
return this.effectiveRole === "owner" || this.effectiveRole === "editor";
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Called when a MSG_STATELESS frame arrives from the server.
|
|
2259
|
+
* Abracadabra uses stateless frames to deliver subdoc confirmations
|
|
2260
|
+
* ({ type: "subdoc_registered", child_id, parent_id }).
|
|
2261
|
+
*/
|
|
2262
|
+
receiveStateless(payload) {
|
|
2263
|
+
let parsed;
|
|
2264
|
+
try {
|
|
2265
|
+
parsed = JSON.parse(payload);
|
|
2266
|
+
} catch {
|
|
2267
|
+
super.receiveStateless(payload);
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
const msg = parsed;
|
|
2271
|
+
if (msg.type === "auth_challenge" && msg.challenge && msg.expiresAt) {
|
|
2272
|
+
this.handleAuthChallenge(msg.challenge, msg.expiresAt).catch(() => null);
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (msg.type === "subdoc_registered" && msg.child_id && msg.parent_id) {
|
|
2276
|
+
const event = {
|
|
2277
|
+
childId: msg.child_id,
|
|
2278
|
+
parentId: msg.parent_id
|
|
2279
|
+
};
|
|
2280
|
+
this.emit("subdocRegistered", event);
|
|
2281
|
+
this.offlineStore?.removeSubdocFromQueue(msg.child_id);
|
|
2282
|
+
if (this.subdocLoading === "eager") this.loadChild(msg.child_id);
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
super.receiveStateless(payload);
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Y.Doc emits 'subdocs' whenever subdocuments are added, removed, or loaded.
|
|
2289
|
+
* We intercept additions to register them with the Abracadabra server.
|
|
2290
|
+
*/
|
|
2291
|
+
handleYSubdocsChange({ added, removed }) {
|
|
2292
|
+
for (const subdoc of added) this.registerSubdoc(subdoc);
|
|
2293
|
+
for (const subdoc of removed) this.unloadChild(subdoc.guid);
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Send a subdoc registration frame to the server.
|
|
2297
|
+
* If offline the event is queued in IndexedDB and replayed on reconnect.
|
|
2298
|
+
*/
|
|
2299
|
+
registerSubdoc(subdoc) {
|
|
2300
|
+
const childId = subdoc.guid;
|
|
2301
|
+
if (this.isConnected) this.send(SubdocMessage, {
|
|
2302
|
+
documentName: this.configuration.name,
|
|
2303
|
+
childDocumentName: childId
|
|
2304
|
+
});
|
|
2305
|
+
else this.offlineStore?.queueSubdoc({
|
|
2306
|
+
childId,
|
|
2307
|
+
parentId: this.configuration.name,
|
|
2308
|
+
createdAt: Date.now()
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Create (or return cached) a child AbracadabraProvider for a given
|
|
2313
|
+
* child document id. The child shares the parent's WebSocket connection.
|
|
2314
|
+
*/
|
|
2315
|
+
async loadChild(childId) {
|
|
2316
|
+
if (this.childProviders.has(childId)) return this.childProviders.get(childId);
|
|
2317
|
+
const childDoc = new yjs.Doc({ guid: childId });
|
|
2318
|
+
const parentWsp = this.configuration.websocketProvider;
|
|
2319
|
+
const childProvider = new AbracadabraProvider({
|
|
2320
|
+
name: childId,
|
|
2321
|
+
document: childDoc,
|
|
2322
|
+
url: parentWsp.configuration.url,
|
|
2323
|
+
WebSocketPolyfill: parentWsp.configuration.WebSocketPolyfill,
|
|
2324
|
+
token: this.configuration.token,
|
|
2325
|
+
subdocLoading: this.subdocLoading,
|
|
2326
|
+
disableOfflineStore: this.abracadabraConfig.disableOfflineStore
|
|
2327
|
+
});
|
|
2328
|
+
this.childProviders.set(childId, childProvider);
|
|
2329
|
+
this.emit("subdocLoaded", {
|
|
2330
|
+
childId,
|
|
2331
|
+
provider: childProvider
|
|
2332
|
+
});
|
|
2333
|
+
return childProvider;
|
|
2334
|
+
}
|
|
2335
|
+
unloadChild(childId) {
|
|
2336
|
+
const provider = this.childProviders.get(childId);
|
|
2337
|
+
if (provider) {
|
|
2338
|
+
provider.destroy();
|
|
2339
|
+
this.childProviders.delete(childId);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
/** Return all currently-loaded child providers. */
|
|
2343
|
+
get children() {
|
|
2344
|
+
return this.childProviders;
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Override to persist every local update to IndexedDB before sending it
|
|
2348
|
+
* over the wire, ensuring no work is lost during connection outages.
|
|
2349
|
+
*/
|
|
2350
|
+
documentUpdateHandler(update, origin) {
|
|
2351
|
+
if (origin === this) return;
|
|
2352
|
+
this.offlineStore?.persistUpdate(update).catch(() => null);
|
|
2353
|
+
super.documentUpdateHandler(update, origin);
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* After reconnect + sync, flush any updates that were generated while
|
|
2357
|
+
* offline, then flush any queued subdoc registrations.
|
|
2358
|
+
*/
|
|
2359
|
+
async flushPendingUpdates() {
|
|
2360
|
+
if (!this.offlineStore) return;
|
|
2361
|
+
const updates = await this.offlineStore.getPendingUpdates();
|
|
2362
|
+
if (updates.length > 0) {
|
|
2363
|
+
for (const update of updates) this.send(UpdateMessage, {
|
|
2364
|
+
update,
|
|
2365
|
+
documentName: this.configuration.name
|
|
2366
|
+
});
|
|
2367
|
+
await this.offlineStore.clearPendingUpdates();
|
|
2368
|
+
}
|
|
2369
|
+
const pendingSubdocs = await this.offlineStore.getPendingSubdocs();
|
|
2370
|
+
for (const { childId } of pendingSubdocs) this.send(SubdocMessage, {
|
|
2371
|
+
documentName: this.configuration.name,
|
|
2372
|
+
childDocumentName: childId
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
get isConnected() {
|
|
2376
|
+
return this.configuration.websocketProvider.status === "connected";
|
|
2377
|
+
}
|
|
2378
|
+
destroy() {
|
|
2379
|
+
this.document.off("subdocs", this.boundHandleYSubdocsChange);
|
|
2380
|
+
for (const provider of this.childProviders.values()) provider.destroy();
|
|
2381
|
+
this.childProviders.clear();
|
|
2382
|
+
this.offlineStore?.destroy();
|
|
2383
|
+
this.offlineStore = null;
|
|
2384
|
+
super.destroy();
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
|
|
2388
|
+
//#endregion
|
|
2389
|
+
//#region node_modules/@noble/hashes/esm/utils.js
|
|
2390
|
+
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
2391
|
+
function isBytes(a) {
|
|
2392
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
2393
|
+
}
|
|
2394
|
+
/** Asserts something is positive integer. */
|
|
2395
|
+
function anumber(n) {
|
|
2396
|
+
if (!Number.isSafeInteger(n) || n < 0) throw new Error("positive integer expected, got " + n);
|
|
2397
|
+
}
|
|
2398
|
+
/** Asserts something is Uint8Array. */
|
|
2399
|
+
function abytes(b, ...lengths) {
|
|
2400
|
+
if (!isBytes(b)) throw new Error("Uint8Array expected");
|
|
2401
|
+
if (lengths.length > 0 && !lengths.includes(b.length)) throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
2402
|
+
}
|
|
2403
|
+
/** Asserts something is hash */
|
|
2404
|
+
function ahash(h) {
|
|
2405
|
+
if (typeof h !== "function" || typeof h.create !== "function") throw new Error("Hash should be wrapped by utils.createHasher");
|
|
2406
|
+
anumber(h.outputLen);
|
|
2407
|
+
anumber(h.blockLen);
|
|
2408
|
+
}
|
|
2409
|
+
/** Asserts a hash instance has not been destroyed / finished */
|
|
2410
|
+
function aexists(instance, checkFinished = true) {
|
|
2411
|
+
if (instance.destroyed) throw new Error("Hash instance has been destroyed");
|
|
2412
|
+
if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called");
|
|
2413
|
+
}
|
|
2414
|
+
/** Asserts output is properly-sized byte array */
|
|
2415
|
+
function aoutput(out, instance) {
|
|
2416
|
+
abytes(out);
|
|
2417
|
+
const min = instance.outputLen;
|
|
2418
|
+
if (out.length < min) throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
2419
|
+
}
|
|
2420
|
+
/** Zeroize a byte array. Warning: JS provides no guarantees. */
|
|
2421
|
+
function clean(...arrays) {
|
|
2422
|
+
for (let i = 0; i < arrays.length; i++) arrays[i].fill(0);
|
|
2423
|
+
}
|
|
2424
|
+
/** Create DataView of an array for easy byte-level manipulation. */
|
|
2425
|
+
function createView(arr) {
|
|
2426
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
2427
|
+
}
|
|
2428
|
+
/** The rotate right (circular right shift) operation for uint32 */
|
|
2429
|
+
function rotr(word, shift) {
|
|
2430
|
+
return word << 32 - shift | word >>> shift;
|
|
2431
|
+
}
|
|
2432
|
+
/** Is current platform little-endian? Most are. Big-Endian platform: IBM */
|
|
2433
|
+
const isLE = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
|
|
2434
|
+
const hasHexBuiltin = typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function";
|
|
2435
|
+
/**
|
|
2436
|
+
* Converts string to bytes using UTF8 encoding.
|
|
2437
|
+
* @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
|
|
2438
|
+
*/
|
|
2439
|
+
function utf8ToBytes(str) {
|
|
2440
|
+
if (typeof str !== "string") throw new Error("string expected");
|
|
2441
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
|
2445
|
+
* Warning: when Uint8Array is passed, it would NOT get copied.
|
|
2446
|
+
* Keep in mind for future mutable operations.
|
|
2447
|
+
*/
|
|
2448
|
+
function toBytes(data) {
|
|
2449
|
+
if (typeof data === "string") data = utf8ToBytes(data);
|
|
2450
|
+
abytes(data);
|
|
2451
|
+
return data;
|
|
2452
|
+
}
|
|
2453
|
+
/** For runtime check if class implements interface */
|
|
2454
|
+
var Hash = class {};
|
|
2455
|
+
/** Wraps hash function, creating an interface on top of it */
|
|
2456
|
+
function createHasher(hashCons) {
|
|
2457
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
2458
|
+
const tmp = hashCons();
|
|
2459
|
+
hashC.outputLen = tmp.outputLen;
|
|
2460
|
+
hashC.blockLen = tmp.blockLen;
|
|
2461
|
+
hashC.create = () => hashCons();
|
|
2462
|
+
return hashC;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
//#endregion
|
|
2466
|
+
//#region node_modules/@noble/hashes/esm/hmac.js
|
|
2467
|
+
/**
|
|
2468
|
+
* HMAC: RFC2104 message authentication code.
|
|
2469
|
+
* @module
|
|
2470
|
+
*/
|
|
2471
|
+
var HMAC = class extends Hash {
|
|
2472
|
+
constructor(hash, _key) {
|
|
2473
|
+
super();
|
|
2474
|
+
this.finished = false;
|
|
2475
|
+
this.destroyed = false;
|
|
2476
|
+
ahash(hash);
|
|
2477
|
+
const key = toBytes(_key);
|
|
2478
|
+
this.iHash = hash.create();
|
|
2479
|
+
if (typeof this.iHash.update !== "function") throw new Error("Expected instance of class which extends utils.Hash");
|
|
2480
|
+
this.blockLen = this.iHash.blockLen;
|
|
2481
|
+
this.outputLen = this.iHash.outputLen;
|
|
2482
|
+
const blockLen = this.blockLen;
|
|
2483
|
+
const pad = new Uint8Array(blockLen);
|
|
2484
|
+
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
|
|
2485
|
+
for (let i = 0; i < pad.length; i++) pad[i] ^= 54;
|
|
2486
|
+
this.iHash.update(pad);
|
|
2487
|
+
this.oHash = hash.create();
|
|
2488
|
+
for (let i = 0; i < pad.length; i++) pad[i] ^= 106;
|
|
2489
|
+
this.oHash.update(pad);
|
|
2490
|
+
clean(pad);
|
|
2491
|
+
}
|
|
2492
|
+
update(buf) {
|
|
2493
|
+
aexists(this);
|
|
2494
|
+
this.iHash.update(buf);
|
|
2495
|
+
return this;
|
|
2496
|
+
}
|
|
2497
|
+
digestInto(out) {
|
|
2498
|
+
aexists(this);
|
|
2499
|
+
abytes(out, this.outputLen);
|
|
2500
|
+
this.finished = true;
|
|
2501
|
+
this.iHash.digestInto(out);
|
|
2502
|
+
this.oHash.update(out);
|
|
2503
|
+
this.oHash.digestInto(out);
|
|
2504
|
+
this.destroy();
|
|
2505
|
+
}
|
|
2506
|
+
digest() {
|
|
2507
|
+
const out = new Uint8Array(this.oHash.outputLen);
|
|
2508
|
+
this.digestInto(out);
|
|
2509
|
+
return out;
|
|
2510
|
+
}
|
|
2511
|
+
_cloneInto(to) {
|
|
2512
|
+
to || (to = Object.create(Object.getPrototypeOf(this), {}));
|
|
2513
|
+
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
|
|
2514
|
+
to = to;
|
|
2515
|
+
to.finished = finished;
|
|
2516
|
+
to.destroyed = destroyed;
|
|
2517
|
+
to.blockLen = blockLen;
|
|
2518
|
+
to.outputLen = outputLen;
|
|
2519
|
+
to.oHash = oHash._cloneInto(to.oHash);
|
|
2520
|
+
to.iHash = iHash._cloneInto(to.iHash);
|
|
2521
|
+
return to;
|
|
2522
|
+
}
|
|
2523
|
+
clone() {
|
|
2524
|
+
return this._cloneInto();
|
|
2525
|
+
}
|
|
2526
|
+
destroy() {
|
|
2527
|
+
this.destroyed = true;
|
|
2528
|
+
this.oHash.destroy();
|
|
2529
|
+
this.iHash.destroy();
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
/**
|
|
2533
|
+
* HMAC: RFC2104 message authentication code.
|
|
2534
|
+
* @param hash - function that would be used e.g. sha256
|
|
2535
|
+
* @param key - message key
|
|
2536
|
+
* @param message - message data
|
|
2537
|
+
* @example
|
|
2538
|
+
* import { hmac } from '@noble/hashes/hmac';
|
|
2539
|
+
* import { sha256 } from '@noble/hashes/sha2';
|
|
2540
|
+
* const mac1 = hmac(sha256, 'key', 'message');
|
|
2541
|
+
*/
|
|
2542
|
+
const hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest();
|
|
2543
|
+
hmac.create = (hash, key) => new HMAC(hash, key);
|
|
2544
|
+
|
|
2545
|
+
//#endregion
|
|
2546
|
+
//#region node_modules/@noble/hashes/esm/hkdf.js
|
|
2547
|
+
/**
|
|
2548
|
+
* HKDF (RFC 5869): extract + expand in one step.
|
|
2549
|
+
* See https://soatok.blog/2021/11/17/understanding-hkdf/.
|
|
2550
|
+
* @module
|
|
2551
|
+
*/
|
|
2552
|
+
/**
|
|
2553
|
+
* HKDF-extract from spec. Less important part. `HKDF-Extract(IKM, salt) -> PRK`
|
|
2554
|
+
* Arguments position differs from spec (IKM is first one, since it is not optional)
|
|
2555
|
+
* @param hash - hash function that would be used (e.g. sha256)
|
|
2556
|
+
* @param ikm - input keying material, the initial key
|
|
2557
|
+
* @param salt - optional salt value (a non-secret random value)
|
|
2558
|
+
*/
|
|
2559
|
+
function extract(hash, ikm, salt) {
|
|
2560
|
+
ahash(hash);
|
|
2561
|
+
if (salt === void 0) salt = new Uint8Array(hash.outputLen);
|
|
2562
|
+
return hmac(hash, toBytes(salt), toBytes(ikm));
|
|
2563
|
+
}
|
|
2564
|
+
const HKDF_COUNTER = /* @__PURE__ */ Uint8Array.from([0]);
|
|
2565
|
+
const EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
|
|
2566
|
+
/**
|
|
2567
|
+
* HKDF-expand from the spec. The most important part. `HKDF-Expand(PRK, info, L) -> OKM`
|
|
2568
|
+
* @param hash - hash function that would be used (e.g. sha256)
|
|
2569
|
+
* @param prk - a pseudorandom key of at least HashLen octets (usually, the output from the extract step)
|
|
2570
|
+
* @param info - optional context and application specific information (can be a zero-length string)
|
|
2571
|
+
* @param length - length of output keying material in bytes
|
|
2572
|
+
*/
|
|
2573
|
+
function expand(hash, prk, info, length = 32) {
|
|
2574
|
+
ahash(hash);
|
|
2575
|
+
anumber(length);
|
|
2576
|
+
const olen = hash.outputLen;
|
|
2577
|
+
if (length > 255 * olen) throw new Error("Length should be <= 255*HashLen");
|
|
2578
|
+
const blocks = Math.ceil(length / olen);
|
|
2579
|
+
if (info === void 0) info = EMPTY_BUFFER;
|
|
2580
|
+
const okm = new Uint8Array(blocks * olen);
|
|
2581
|
+
const HMAC = hmac.create(hash, prk);
|
|
2582
|
+
const HMACTmp = HMAC._cloneInto();
|
|
2583
|
+
const T = new Uint8Array(HMAC.outputLen);
|
|
2584
|
+
for (let counter = 0; counter < blocks; counter++) {
|
|
2585
|
+
HKDF_COUNTER[0] = counter + 1;
|
|
2586
|
+
HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T).update(info).update(HKDF_COUNTER).digestInto(T);
|
|
2587
|
+
okm.set(T, olen * counter);
|
|
2588
|
+
HMAC._cloneInto(HMACTmp);
|
|
2589
|
+
}
|
|
2590
|
+
HMAC.destroy();
|
|
2591
|
+
HMACTmp.destroy();
|
|
2592
|
+
clean(T, HKDF_COUNTER);
|
|
2593
|
+
return okm.slice(0, length);
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* HKDF (RFC 5869): derive keys from an initial input.
|
|
2597
|
+
* Combines hkdf_extract + hkdf_expand in one step
|
|
2598
|
+
* @param hash - hash function that would be used (e.g. sha256)
|
|
2599
|
+
* @param ikm - input keying material, the initial key
|
|
2600
|
+
* @param salt - optional salt value (a non-secret random value)
|
|
2601
|
+
* @param info - optional context and application specific information (can be a zero-length string)
|
|
2602
|
+
* @param length - length of output keying material in bytes
|
|
2603
|
+
* @example
|
|
2604
|
+
* import { hkdf } from '@noble/hashes/hkdf';
|
|
2605
|
+
* import { sha256 } from '@noble/hashes/sha2';
|
|
2606
|
+
* import { randomBytes } from '@noble/hashes/utils';
|
|
2607
|
+
* const inputKey = randomBytes(32);
|
|
2608
|
+
* const salt = randomBytes(32);
|
|
2609
|
+
* const info = 'application-key';
|
|
2610
|
+
* const hk1 = hkdf(sha256, inputKey, salt, info, 32);
|
|
2611
|
+
*/
|
|
2612
|
+
const hkdf = (hash, ikm, salt, info, length) => expand(hash, extract(hash, ikm, salt), info, length);
|
|
2613
|
+
|
|
2614
|
+
//#endregion
|
|
2615
|
+
//#region node_modules/@noble/hashes/esm/_md.js
|
|
2616
|
+
/**
|
|
2617
|
+
* Internal Merkle-Damgard hash utils.
|
|
2618
|
+
* @module
|
|
2619
|
+
*/
|
|
2620
|
+
/** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
|
|
2621
|
+
function setBigUint64(view, byteOffset, value, isLE) {
|
|
2622
|
+
if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE);
|
|
2623
|
+
const _32n = BigInt(32);
|
|
2624
|
+
const _u32_max = BigInt(4294967295);
|
|
2625
|
+
const wh = Number(value >> _32n & _u32_max);
|
|
2626
|
+
const wl = Number(value & _u32_max);
|
|
2627
|
+
const h = isLE ? 4 : 0;
|
|
2628
|
+
const l = isLE ? 0 : 4;
|
|
2629
|
+
view.setUint32(byteOffset + h, wh, isLE);
|
|
2630
|
+
view.setUint32(byteOffset + l, wl, isLE);
|
|
2631
|
+
}
|
|
2632
|
+
/** Choice: a ? b : c */
|
|
2633
|
+
function Chi(a, b, c) {
|
|
2634
|
+
return a & b ^ ~a & c;
|
|
2635
|
+
}
|
|
2636
|
+
/** Majority function, true if any two inputs is true. */
|
|
2637
|
+
function Maj(a, b, c) {
|
|
2638
|
+
return a & b ^ a & c ^ b & c;
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Merkle-Damgard hash construction base class.
|
|
2642
|
+
* Could be used to create MD5, RIPEMD, SHA1, SHA2.
|
|
2643
|
+
*/
|
|
2644
|
+
var HashMD = class extends Hash {
|
|
2645
|
+
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
2646
|
+
super();
|
|
2647
|
+
this.finished = false;
|
|
2648
|
+
this.length = 0;
|
|
2649
|
+
this.pos = 0;
|
|
2650
|
+
this.destroyed = false;
|
|
2651
|
+
this.blockLen = blockLen;
|
|
2652
|
+
this.outputLen = outputLen;
|
|
2653
|
+
this.padOffset = padOffset;
|
|
2654
|
+
this.isLE = isLE;
|
|
2655
|
+
this.buffer = new Uint8Array(blockLen);
|
|
2656
|
+
this.view = createView(this.buffer);
|
|
2657
|
+
}
|
|
2658
|
+
update(data) {
|
|
2659
|
+
aexists(this);
|
|
2660
|
+
data = toBytes(data);
|
|
2661
|
+
abytes(data);
|
|
2662
|
+
const { view, buffer, blockLen } = this;
|
|
2663
|
+
const len = data.length;
|
|
2664
|
+
for (let pos = 0; pos < len;) {
|
|
2665
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
2666
|
+
if (take === blockLen) {
|
|
2667
|
+
const dataView = createView(data);
|
|
2668
|
+
for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
2672
|
+
this.pos += take;
|
|
2673
|
+
pos += take;
|
|
2674
|
+
if (this.pos === blockLen) {
|
|
2675
|
+
this.process(view, 0);
|
|
2676
|
+
this.pos = 0;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
this.length += data.length;
|
|
2680
|
+
this.roundClean();
|
|
2681
|
+
return this;
|
|
2682
|
+
}
|
|
2683
|
+
digestInto(out) {
|
|
2684
|
+
aexists(this);
|
|
2685
|
+
aoutput(out, this);
|
|
2686
|
+
this.finished = true;
|
|
2687
|
+
const { buffer, view, blockLen, isLE } = this;
|
|
2688
|
+
let { pos } = this;
|
|
2689
|
+
buffer[pos++] = 128;
|
|
2690
|
+
clean(this.buffer.subarray(pos));
|
|
2691
|
+
if (this.padOffset > blockLen - pos) {
|
|
2692
|
+
this.process(view, 0);
|
|
2693
|
+
pos = 0;
|
|
2694
|
+
}
|
|
2695
|
+
for (let i = pos; i < blockLen; i++) buffer[i] = 0;
|
|
2696
|
+
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
|
|
2697
|
+
this.process(view, 0);
|
|
2698
|
+
const oview = createView(out);
|
|
2699
|
+
const len = this.outputLen;
|
|
2700
|
+
if (len % 4) throw new Error("_sha2: outputLen should be aligned to 32bit");
|
|
2701
|
+
const outLen = len / 4;
|
|
2702
|
+
const state = this.get();
|
|
2703
|
+
if (outLen > state.length) throw new Error("_sha2: outputLen bigger than state");
|
|
2704
|
+
for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
|
|
2705
|
+
}
|
|
2706
|
+
digest() {
|
|
2707
|
+
const { buffer, outputLen } = this;
|
|
2708
|
+
this.digestInto(buffer);
|
|
2709
|
+
const res = buffer.slice(0, outputLen);
|
|
2710
|
+
this.destroy();
|
|
2711
|
+
return res;
|
|
2712
|
+
}
|
|
2713
|
+
_cloneInto(to) {
|
|
2714
|
+
to || (to = new this.constructor());
|
|
2715
|
+
to.set(...this.get());
|
|
2716
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
2717
|
+
to.destroyed = destroyed;
|
|
2718
|
+
to.finished = finished;
|
|
2719
|
+
to.length = length;
|
|
2720
|
+
to.pos = pos;
|
|
2721
|
+
if (length % blockLen) to.buffer.set(buffer);
|
|
2722
|
+
return to;
|
|
2723
|
+
}
|
|
2724
|
+
clone() {
|
|
2725
|
+
return this._cloneInto();
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
2728
|
+
/**
|
|
2729
|
+
* Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
|
|
2730
|
+
* Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
|
|
2731
|
+
*/
|
|
2732
|
+
/** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */
|
|
2733
|
+
const SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
2734
|
+
1779033703,
|
|
2735
|
+
3144134277,
|
|
2736
|
+
1013904242,
|
|
2737
|
+
2773480762,
|
|
2738
|
+
1359893119,
|
|
2739
|
+
2600822924,
|
|
2740
|
+
528734635,
|
|
2741
|
+
1541459225
|
|
2742
|
+
]);
|
|
2743
|
+
|
|
2744
|
+
//#endregion
|
|
2745
|
+
//#region node_modules/@noble/hashes/esm/_u64.js
|
|
2746
|
+
/**
|
|
2747
|
+
* Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.
|
|
2748
|
+
* @todo re-check https://issues.chromium.org/issues/42212588
|
|
2749
|
+
* @module
|
|
2750
|
+
*/
|
|
2751
|
+
const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
2752
|
+
const _32n = /* @__PURE__ */ BigInt(32);
|
|
2753
|
+
function fromBig(n, le = false) {
|
|
2754
|
+
if (le) return {
|
|
2755
|
+
h: Number(n & U32_MASK64),
|
|
2756
|
+
l: Number(n >> _32n & U32_MASK64)
|
|
2757
|
+
};
|
|
2758
|
+
return {
|
|
2759
|
+
h: Number(n >> _32n & U32_MASK64) | 0,
|
|
2760
|
+
l: Number(n & U32_MASK64) | 0
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
function split(lst, le = false) {
|
|
2764
|
+
const len = lst.length;
|
|
2765
|
+
let Ah = new Uint32Array(len);
|
|
2766
|
+
let Al = new Uint32Array(len);
|
|
2767
|
+
for (let i = 0; i < len; i++) {
|
|
2768
|
+
const { h, l } = fromBig(lst[i], le);
|
|
2769
|
+
[Ah[i], Al[i]] = [h, l];
|
|
2770
|
+
}
|
|
2771
|
+
return [Ah, Al];
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
//#endregion
|
|
2775
|
+
//#region node_modules/@noble/hashes/esm/sha2.js
|
|
2776
|
+
/**
|
|
2777
|
+
* SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
|
|
2778
|
+
* SHA256 is the fastest hash implementable in JS, even faster than Blake3.
|
|
2779
|
+
* Check out [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and
|
|
2780
|
+
* [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
|
|
2781
|
+
* @module
|
|
2782
|
+
*/
|
|
2783
|
+
/**
|
|
2784
|
+
* Round constants:
|
|
2785
|
+
* First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311)
|
|
2786
|
+
*/
|
|
2787
|
+
const SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
2788
|
+
1116352408,
|
|
2789
|
+
1899447441,
|
|
2790
|
+
3049323471,
|
|
2791
|
+
3921009573,
|
|
2792
|
+
961987163,
|
|
2793
|
+
1508970993,
|
|
2794
|
+
2453635748,
|
|
2795
|
+
2870763221,
|
|
2796
|
+
3624381080,
|
|
2797
|
+
310598401,
|
|
2798
|
+
607225278,
|
|
2799
|
+
1426881987,
|
|
2800
|
+
1925078388,
|
|
2801
|
+
2162078206,
|
|
2802
|
+
2614888103,
|
|
2803
|
+
3248222580,
|
|
2804
|
+
3835390401,
|
|
2805
|
+
4022224774,
|
|
2806
|
+
264347078,
|
|
2807
|
+
604807628,
|
|
2808
|
+
770255983,
|
|
2809
|
+
1249150122,
|
|
2810
|
+
1555081692,
|
|
2811
|
+
1996064986,
|
|
2812
|
+
2554220882,
|
|
2813
|
+
2821834349,
|
|
2814
|
+
2952996808,
|
|
2815
|
+
3210313671,
|
|
2816
|
+
3336571891,
|
|
2817
|
+
3584528711,
|
|
2818
|
+
113926993,
|
|
2819
|
+
338241895,
|
|
2820
|
+
666307205,
|
|
2821
|
+
773529912,
|
|
2822
|
+
1294757372,
|
|
2823
|
+
1396182291,
|
|
2824
|
+
1695183700,
|
|
2825
|
+
1986661051,
|
|
2826
|
+
2177026350,
|
|
2827
|
+
2456956037,
|
|
2828
|
+
2730485921,
|
|
2829
|
+
2820302411,
|
|
2830
|
+
3259730800,
|
|
2831
|
+
3345764771,
|
|
2832
|
+
3516065817,
|
|
2833
|
+
3600352804,
|
|
2834
|
+
4094571909,
|
|
2835
|
+
275423344,
|
|
2836
|
+
430227734,
|
|
2837
|
+
506948616,
|
|
2838
|
+
659060556,
|
|
2839
|
+
883997877,
|
|
2840
|
+
958139571,
|
|
2841
|
+
1322822218,
|
|
2842
|
+
1537002063,
|
|
2843
|
+
1747873779,
|
|
2844
|
+
1955562222,
|
|
2845
|
+
2024104815,
|
|
2846
|
+
2227730452,
|
|
2847
|
+
2361852424,
|
|
2848
|
+
2428436474,
|
|
2849
|
+
2756734187,
|
|
2850
|
+
3204031479,
|
|
2851
|
+
3329325298
|
|
2852
|
+
]);
|
|
2853
|
+
/** Reusable temporary buffer. "W" comes straight from spec. */
|
|
2854
|
+
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
2855
|
+
var SHA256 = class extends HashMD {
|
|
2856
|
+
constructor(outputLen = 32) {
|
|
2857
|
+
super(64, outputLen, 8, false);
|
|
2858
|
+
this.A = SHA256_IV[0] | 0;
|
|
2859
|
+
this.B = SHA256_IV[1] | 0;
|
|
2860
|
+
this.C = SHA256_IV[2] | 0;
|
|
2861
|
+
this.D = SHA256_IV[3] | 0;
|
|
2862
|
+
this.E = SHA256_IV[4] | 0;
|
|
2863
|
+
this.F = SHA256_IV[5] | 0;
|
|
2864
|
+
this.G = SHA256_IV[6] | 0;
|
|
2865
|
+
this.H = SHA256_IV[7] | 0;
|
|
2866
|
+
}
|
|
2867
|
+
get() {
|
|
2868
|
+
const { A, B, C, D, E, F, G, H } = this;
|
|
2869
|
+
return [
|
|
2870
|
+
A,
|
|
2871
|
+
B,
|
|
2872
|
+
C,
|
|
2873
|
+
D,
|
|
2874
|
+
E,
|
|
2875
|
+
F,
|
|
2876
|
+
G,
|
|
2877
|
+
H
|
|
2878
|
+
];
|
|
2879
|
+
}
|
|
2880
|
+
set(A, B, C, D, E, F, G, H) {
|
|
2881
|
+
this.A = A | 0;
|
|
2882
|
+
this.B = B | 0;
|
|
2883
|
+
this.C = C | 0;
|
|
2884
|
+
this.D = D | 0;
|
|
2885
|
+
this.E = E | 0;
|
|
2886
|
+
this.F = F | 0;
|
|
2887
|
+
this.G = G | 0;
|
|
2888
|
+
this.H = H | 0;
|
|
2889
|
+
}
|
|
2890
|
+
process(view, offset) {
|
|
2891
|
+
for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false);
|
|
2892
|
+
for (let i = 16; i < 64; i++) {
|
|
2893
|
+
const W15 = SHA256_W[i - 15];
|
|
2894
|
+
const W2 = SHA256_W[i - 2];
|
|
2895
|
+
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
|
2896
|
+
SHA256_W[i] = (rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10) + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
|
2897
|
+
}
|
|
2898
|
+
let { A, B, C, D, E, F, G, H } = this;
|
|
2899
|
+
for (let i = 0; i < 64; i++) {
|
|
2900
|
+
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
2901
|
+
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
|
2902
|
+
const T2 = (rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22)) + Maj(A, B, C) | 0;
|
|
2903
|
+
H = G;
|
|
2904
|
+
G = F;
|
|
2905
|
+
F = E;
|
|
2906
|
+
E = D + T1 | 0;
|
|
2907
|
+
D = C;
|
|
2908
|
+
C = B;
|
|
2909
|
+
B = A;
|
|
2910
|
+
A = T1 + T2 | 0;
|
|
2911
|
+
}
|
|
2912
|
+
A = A + this.A | 0;
|
|
2913
|
+
B = B + this.B | 0;
|
|
2914
|
+
C = C + this.C | 0;
|
|
2915
|
+
D = D + this.D | 0;
|
|
2916
|
+
E = E + this.E | 0;
|
|
2917
|
+
F = F + this.F | 0;
|
|
2918
|
+
G = G + this.G | 0;
|
|
2919
|
+
H = H + this.H | 0;
|
|
2920
|
+
this.set(A, B, C, D, E, F, G, H);
|
|
2921
|
+
}
|
|
2922
|
+
roundClean() {
|
|
2923
|
+
clean(SHA256_W);
|
|
2924
|
+
}
|
|
2925
|
+
destroy() {
|
|
2926
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
2927
|
+
clean(this.buffer);
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2930
|
+
const K512 = split([
|
|
2931
|
+
"0x428a2f98d728ae22",
|
|
2932
|
+
"0x7137449123ef65cd",
|
|
2933
|
+
"0xb5c0fbcfec4d3b2f",
|
|
2934
|
+
"0xe9b5dba58189dbbc",
|
|
2935
|
+
"0x3956c25bf348b538",
|
|
2936
|
+
"0x59f111f1b605d019",
|
|
2937
|
+
"0x923f82a4af194f9b",
|
|
2938
|
+
"0xab1c5ed5da6d8118",
|
|
2939
|
+
"0xd807aa98a3030242",
|
|
2940
|
+
"0x12835b0145706fbe",
|
|
2941
|
+
"0x243185be4ee4b28c",
|
|
2942
|
+
"0x550c7dc3d5ffb4e2",
|
|
2943
|
+
"0x72be5d74f27b896f",
|
|
2944
|
+
"0x80deb1fe3b1696b1",
|
|
2945
|
+
"0x9bdc06a725c71235",
|
|
2946
|
+
"0xc19bf174cf692694",
|
|
2947
|
+
"0xe49b69c19ef14ad2",
|
|
2948
|
+
"0xefbe4786384f25e3",
|
|
2949
|
+
"0x0fc19dc68b8cd5b5",
|
|
2950
|
+
"0x240ca1cc77ac9c65",
|
|
2951
|
+
"0x2de92c6f592b0275",
|
|
2952
|
+
"0x4a7484aa6ea6e483",
|
|
2953
|
+
"0x5cb0a9dcbd41fbd4",
|
|
2954
|
+
"0x76f988da831153b5",
|
|
2955
|
+
"0x983e5152ee66dfab",
|
|
2956
|
+
"0xa831c66d2db43210",
|
|
2957
|
+
"0xb00327c898fb213f",
|
|
2958
|
+
"0xbf597fc7beef0ee4",
|
|
2959
|
+
"0xc6e00bf33da88fc2",
|
|
2960
|
+
"0xd5a79147930aa725",
|
|
2961
|
+
"0x06ca6351e003826f",
|
|
2962
|
+
"0x142929670a0e6e70",
|
|
2963
|
+
"0x27b70a8546d22ffc",
|
|
2964
|
+
"0x2e1b21385c26c926",
|
|
2965
|
+
"0x4d2c6dfc5ac42aed",
|
|
2966
|
+
"0x53380d139d95b3df",
|
|
2967
|
+
"0x650a73548baf63de",
|
|
2968
|
+
"0x766a0abb3c77b2a8",
|
|
2969
|
+
"0x81c2c92e47edaee6",
|
|
2970
|
+
"0x92722c851482353b",
|
|
2971
|
+
"0xa2bfe8a14cf10364",
|
|
2972
|
+
"0xa81a664bbc423001",
|
|
2973
|
+
"0xc24b8b70d0f89791",
|
|
2974
|
+
"0xc76c51a30654be30",
|
|
2975
|
+
"0xd192e819d6ef5218",
|
|
2976
|
+
"0xd69906245565a910",
|
|
2977
|
+
"0xf40e35855771202a",
|
|
2978
|
+
"0x106aa07032bbd1b8",
|
|
2979
|
+
"0x19a4c116b8d2d0c8",
|
|
2980
|
+
"0x1e376c085141ab53",
|
|
2981
|
+
"0x2748774cdf8eeb99",
|
|
2982
|
+
"0x34b0bcb5e19b48a8",
|
|
2983
|
+
"0x391c0cb3c5c95a63",
|
|
2984
|
+
"0x4ed8aa4ae3418acb",
|
|
2985
|
+
"0x5b9cca4f7763e373",
|
|
2986
|
+
"0x682e6ff3d6b2b8a3",
|
|
2987
|
+
"0x748f82ee5defb2fc",
|
|
2988
|
+
"0x78a5636f43172f60",
|
|
2989
|
+
"0x84c87814a1f0ab72",
|
|
2990
|
+
"0x8cc702081a6439ec",
|
|
2991
|
+
"0x90befffa23631e28",
|
|
2992
|
+
"0xa4506cebde82bde9",
|
|
2993
|
+
"0xbef9a3f7b2c67915",
|
|
2994
|
+
"0xc67178f2e372532b",
|
|
2995
|
+
"0xca273eceea26619c",
|
|
2996
|
+
"0xd186b8c721c0c207",
|
|
2997
|
+
"0xeada7dd6cde0eb1e",
|
|
2998
|
+
"0xf57d4f7fee6ed178",
|
|
2999
|
+
"0x06f067aa72176fba",
|
|
3000
|
+
"0x0a637dc5a2c898a6",
|
|
3001
|
+
"0x113f9804bef90dae",
|
|
3002
|
+
"0x1b710b35131c471b",
|
|
3003
|
+
"0x28db77f523047d84",
|
|
3004
|
+
"0x32caab7b40c72493",
|
|
3005
|
+
"0x3c9ebe0a15c9bebc",
|
|
3006
|
+
"0x431d67c49c100d4c",
|
|
3007
|
+
"0x4cc5d4becb3e42b6",
|
|
3008
|
+
"0x597f299cfc657e2a",
|
|
3009
|
+
"0x5fcb6fab3ad6faec",
|
|
3010
|
+
"0x6c44198c4a475817"
|
|
3011
|
+
].map((n) => BigInt(n)));
|
|
3012
|
+
const SHA512_Kh = K512[0];
|
|
3013
|
+
const SHA512_Kl = K512[1];
|
|
3014
|
+
/**
|
|
3015
|
+
* SHA2-256 hash function from RFC 4634.
|
|
3016
|
+
*
|
|
3017
|
+
* It is the fastest JS hash, even faster than Blake3.
|
|
3018
|
+
* To break sha256 using birthday attack, attackers need to try 2^128 hashes.
|
|
3019
|
+
* BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
|
|
3020
|
+
*/
|
|
3021
|
+
const sha256$1 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
3022
|
+
|
|
3023
|
+
//#endregion
|
|
3024
|
+
//#region node_modules/@noble/hashes/esm/sha256.js
|
|
3025
|
+
/**
|
|
3026
|
+
* SHA2-256 a.k.a. sha256. In JS, it is the fastest hash, even faster than Blake3.
|
|
3027
|
+
*
|
|
3028
|
+
* To break sha256 using birthday attack, attackers need to try 2^128 hashes.
|
|
3029
|
+
* BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
|
|
3030
|
+
*
|
|
3031
|
+
* Check out [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
|
|
3032
|
+
* @module
|
|
3033
|
+
* @deprecated
|
|
3034
|
+
*/
|
|
3035
|
+
/** @deprecated Use import from `noble/hashes/sha2` module */
|
|
3036
|
+
const sha256 = sha256$1;
|
|
3037
|
+
|
|
3038
|
+
//#endregion
|
|
3039
|
+
//#region packages/provider/src/CryptoIdentityKeystore.ts
|
|
3040
|
+
/**
|
|
3041
|
+
* CryptoIdentityKeystore
|
|
3042
|
+
*
|
|
3043
|
+
* Per-device Ed25519 keypair, private key protected by WebAuthn PRF + AES-256-GCM.
|
|
3044
|
+
* Stored in IndexedDB under "abracadabra:identity" / "identity" / key "current".
|
|
3045
|
+
*
|
|
3046
|
+
* No private key is ever shared between devices. Each device generates its own
|
|
3047
|
+
* keypair, encrypts the private key with the PRF output from its own WebAuthn
|
|
3048
|
+
* credential, and stores the ciphertext in IndexedDB.
|
|
3049
|
+
*
|
|
3050
|
+
* Dependencies: @noble/ed25519, @noble/hashes (for HKDF)
|
|
3051
|
+
*/
|
|
3052
|
+
function toBase64url(bytes) {
|
|
3053
|
+
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
3054
|
+
}
|
|
3055
|
+
function fromBase64url(b64) {
|
|
3056
|
+
const padded = b64.replace(/-/g, "+").replace(/_/g, "/");
|
|
3057
|
+
const padLen = (4 - padded.length % 4) % 4;
|
|
3058
|
+
const padded2 = padded + "=".repeat(padLen);
|
|
3059
|
+
return Uint8Array.from(atob(padded2), (c) => c.charCodeAt(0));
|
|
3060
|
+
}
|
|
3061
|
+
const DB_NAME = "abracadabra:identity";
|
|
3062
|
+
const STORE_NAME = "identity";
|
|
3063
|
+
const RECORD_KEY = "current";
|
|
3064
|
+
const HKDF_INFO = new TextEncoder().encode("abracadabra-identity-v1");
|
|
3065
|
+
function openDb() {
|
|
3066
|
+
return new Promise((resolve, reject) => {
|
|
3067
|
+
const req = indexedDB.open(DB_NAME, 1);
|
|
3068
|
+
req.onupgradeneeded = () => {
|
|
3069
|
+
req.result.createObjectStore(STORE_NAME);
|
|
3070
|
+
};
|
|
3071
|
+
req.onsuccess = () => resolve(req.result);
|
|
3072
|
+
req.onerror = () => reject(req.error);
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
async function dbGet(db) {
|
|
3076
|
+
return new Promise((resolve, reject) => {
|
|
3077
|
+
const req = db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(RECORD_KEY);
|
|
3078
|
+
req.onsuccess = () => resolve(req.result);
|
|
3079
|
+
req.onerror = () => reject(req.error);
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
async function dbPut(db, value) {
|
|
3083
|
+
return new Promise((resolve, reject) => {
|
|
3084
|
+
const req = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(value, RECORD_KEY);
|
|
3085
|
+
req.onsuccess = () => resolve();
|
|
3086
|
+
req.onerror = () => reject(req.error);
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
async function dbDelete(db) {
|
|
3090
|
+
return new Promise((resolve, reject) => {
|
|
3091
|
+
const req = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).delete(RECORD_KEY);
|
|
3092
|
+
req.onsuccess = () => resolve();
|
|
3093
|
+
req.onerror = () => reject(req.error);
|
|
3094
|
+
});
|
|
3095
|
+
}
|
|
3096
|
+
async function deriveAesKey(prfOutput, salt) {
|
|
3097
|
+
const keyBytes = hkdf(sha256, new Uint8Array(prfOutput), salt, HKDF_INFO, 32);
|
|
3098
|
+
return crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
3099
|
+
}
|
|
3100
|
+
var CryptoIdentityKeystore = class {
|
|
3101
|
+
/**
|
|
3102
|
+
* One-time setup for a device: generates an Ed25519 keypair, creates a
|
|
3103
|
+
* WebAuthn credential with PRF extension, encrypts the private key, and
|
|
3104
|
+
* stores everything in IndexedDB.
|
|
3105
|
+
*
|
|
3106
|
+
* Returns the base64url-encoded public key. The caller must register this
|
|
3107
|
+
* key with the server via POST /auth/register (first device) or
|
|
3108
|
+
* POST /auth/keys (additional device).
|
|
3109
|
+
*
|
|
3110
|
+
* @param username - The user's account name.
|
|
3111
|
+
* @param rpId - WebAuthn relying party ID (e.g. "example.com").
|
|
3112
|
+
* @param rpName - Human-readable relying party name.
|
|
3113
|
+
*/
|
|
3114
|
+
async register(username, rpId, rpName) {
|
|
3115
|
+
const privateKey = _noble_ed25519.utils.randomPrivateKey();
|
|
3116
|
+
const publicKey = await _noble_ed25519.getPublicKeyAsync(privateKey);
|
|
3117
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
3118
|
+
const credential = await navigator.credentials.create({ publicKey: {
|
|
3119
|
+
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
3120
|
+
rp: {
|
|
3121
|
+
id: rpId,
|
|
3122
|
+
name: rpName
|
|
3123
|
+
},
|
|
3124
|
+
user: {
|
|
3125
|
+
id: new TextEncoder().encode(username),
|
|
3126
|
+
name: username,
|
|
3127
|
+
displayName: username
|
|
3128
|
+
},
|
|
3129
|
+
pubKeyCredParams: [{
|
|
3130
|
+
alg: -7,
|
|
3131
|
+
type: "public-key"
|
|
3132
|
+
}, {
|
|
3133
|
+
alg: -257,
|
|
3134
|
+
type: "public-key"
|
|
3135
|
+
}],
|
|
3136
|
+
authenticatorSelection: { userVerification: "required" },
|
|
3137
|
+
extensions: { prf: { eval: { first: salt.buffer } } }
|
|
3138
|
+
} });
|
|
3139
|
+
if (!credential) throw new Error("WebAuthn credential creation failed");
|
|
3140
|
+
const prfOutput = credential.getClientExtensionResults()?.prf?.results?.first;
|
|
3141
|
+
if (!prfOutput) throw new Error("WebAuthn PRF extension not available on this authenticator. A PRF-capable authenticator (e.g. platform authenticator with PRF support) is required.");
|
|
3142
|
+
const aesKey = await deriveAesKey(prfOutput, salt);
|
|
3143
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
3144
|
+
const encryptedPrivateKey = await crypto.subtle.encrypt({
|
|
3145
|
+
name: "AES-GCM",
|
|
3146
|
+
iv
|
|
3147
|
+
}, aesKey, privateKey);
|
|
3148
|
+
const db = await openDb();
|
|
3149
|
+
await dbPut(db, {
|
|
3150
|
+
username,
|
|
3151
|
+
publicKey: toBase64url(publicKey),
|
|
3152
|
+
encryptedPrivateKey,
|
|
3153
|
+
iv,
|
|
3154
|
+
salt,
|
|
3155
|
+
credentialId: credential.rawId
|
|
3156
|
+
});
|
|
3157
|
+
db.close();
|
|
3158
|
+
return toBase64url(publicKey);
|
|
3159
|
+
}
|
|
3160
|
+
/**
|
|
3161
|
+
* Sign a base64url-encoded challenge using the stored Ed25519 private key.
|
|
3162
|
+
*
|
|
3163
|
+
* This triggers a WebAuthn assertion (biometric / PIN prompt) to unlock the
|
|
3164
|
+
* private key via PRF → HKDF → AES-GCM decryption. The private key is
|
|
3165
|
+
* wiped from memory after signing.
|
|
3166
|
+
*
|
|
3167
|
+
* @param challengeB64 - base64url-encoded challenge bytes from the server.
|
|
3168
|
+
* @returns base64url-encoded Ed25519 signature (64 bytes).
|
|
3169
|
+
*/
|
|
3170
|
+
async sign(challengeB64) {
|
|
3171
|
+
const db = await openDb();
|
|
3172
|
+
const stored = await dbGet(db);
|
|
3173
|
+
db.close();
|
|
3174
|
+
if (!stored) throw new Error("No identity stored. Call register() first.");
|
|
3175
|
+
const assertion = await navigator.credentials.get({ publicKey: {
|
|
3176
|
+
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
3177
|
+
allowCredentials: [{
|
|
3178
|
+
id: stored.credentialId,
|
|
3179
|
+
type: "public-key"
|
|
3180
|
+
}],
|
|
3181
|
+
userVerification: "required",
|
|
3182
|
+
extensions: { prf: { eval: { first: stored.salt.buffer } } }
|
|
3183
|
+
} });
|
|
3184
|
+
if (!assertion) throw new Error("WebAuthn assertion failed");
|
|
3185
|
+
const prfOutput = assertion.getClientExtensionResults()?.prf?.results?.first;
|
|
3186
|
+
if (!prfOutput) throw new Error("PRF output not available from authenticator");
|
|
3187
|
+
const aesKey = await deriveAesKey(prfOutput, stored.salt);
|
|
3188
|
+
const privateKeyBytes = await crypto.subtle.decrypt({
|
|
3189
|
+
name: "AES-GCM",
|
|
3190
|
+
iv: stored.iv
|
|
3191
|
+
}, aesKey, stored.encryptedPrivateKey);
|
|
3192
|
+
const privateKey = new Uint8Array(privateKeyBytes);
|
|
3193
|
+
const challengeBytes = fromBase64url(challengeB64);
|
|
3194
|
+
const signature = await _noble_ed25519.signAsync(challengeBytes, privateKey);
|
|
3195
|
+
privateKey.fill(0);
|
|
3196
|
+
return toBase64url(signature);
|
|
3197
|
+
}
|
|
3198
|
+
/** Returns the stored base64url public key, or null if no identity exists. */
|
|
3199
|
+
async getPublicKey() {
|
|
3200
|
+
const db = await openDb();
|
|
3201
|
+
const stored = await dbGet(db);
|
|
3202
|
+
db.close();
|
|
3203
|
+
return stored?.publicKey ?? null;
|
|
3204
|
+
}
|
|
3205
|
+
/** Returns the stored username, or null if no identity exists. */
|
|
3206
|
+
async getUsername() {
|
|
3207
|
+
const db = await openDb();
|
|
3208
|
+
const stored = await dbGet(db);
|
|
3209
|
+
db.close();
|
|
3210
|
+
return stored?.username ?? null;
|
|
3211
|
+
}
|
|
3212
|
+
/** Returns true if an identity is stored in IndexedDB. */
|
|
3213
|
+
async hasIdentity() {
|
|
3214
|
+
const db = await openDb();
|
|
3215
|
+
const stored = await dbGet(db);
|
|
3216
|
+
db.close();
|
|
3217
|
+
return stored !== void 0;
|
|
3218
|
+
}
|
|
3219
|
+
/** Remove the stored identity from IndexedDB. */
|
|
3220
|
+
async clear() {
|
|
3221
|
+
const db = await openDb();
|
|
3222
|
+
await dbDelete(db);
|
|
3223
|
+
db.close();
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
|
|
3227
|
+
//#endregion
|
|
3228
|
+
exports.AbracadabraProvider = AbracadabraProvider;
|
|
3229
|
+
exports.AwarenessError = AwarenessError;
|
|
3230
|
+
exports.CryptoIdentityKeystore = CryptoIdentityKeystore;
|
|
3231
|
+
exports.HocuspocusProvider = HocuspocusProvider;
|
|
3232
|
+
exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket;
|
|
3233
|
+
exports.MessageType = MessageType;
|
|
3234
|
+
exports.OfflineStore = OfflineStore;
|
|
3235
|
+
exports.SubdocMessage = SubdocMessage;
|
|
3236
|
+
exports.WebSocketStatus = WebSocketStatus;
|
|
3237
|
+
//# sourceMappingURL=hocuspocus-provider.cjs.map
|