@fairfox/polly 0.21.0 → 0.22.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/src/index.js CHANGED
@@ -2062,1579 +2062,6 @@ async function createBlobRef({
2062
2062
  // src/index.ts
2063
2063
  init_constraints();
2064
2064
 
2065
- // src/shared/lib/crdt-specialised.ts
2066
- import { Counter, updateText } from "@automerge/automerge-repo";
2067
- import { effect as effect3, signal as signal4 } from "@preact/signals";
2068
-
2069
- // src/shared/lib/migrate-primitive.ts
2070
- class MigrationError extends Error {
2071
- code;
2072
- key;
2073
- primitive;
2074
- constructor(message, code, key, primitive) {
2075
- super(message);
2076
- this.name = "MigrationError";
2077
- this.code = code;
2078
- this.key = key;
2079
- this.primitive = primitive;
2080
- }
2081
- }
2082
-
2083
- class MigrationRegistry {
2084
- marks = new Set;
2085
- entryKey(key, primitive) {
2086
- return `${primitive}:${key}`;
2087
- }
2088
- mark(key, primitive) {
2089
- this.marks.add(this.entryKey(key, primitive));
2090
- }
2091
- isMarked(key, primitive) {
2092
- return this.marks.has(this.entryKey(key, primitive));
2093
- }
2094
- clear() {
2095
- this.marks.clear();
2096
- }
2097
- get size() {
2098
- return this.marks.size;
2099
- }
2100
- }
2101
- var migrationRegistry = new MigrationRegistry;
2102
- async function migratePrimitive(source, destination, transform) {
2103
- if (source === destination) {
2104
- throw new MigrationError(`Cannot migrate a primitive to itself: "${source.key}" under ${source.primitive}.`, "same-primitive-instance", source.key, source.primitive);
2105
- }
2106
- if (migrationRegistry.isMarked(source.key, source.primitive)) {
2107
- throw new MigrationError(`Cannot migrate: source "${source.key}" under $${source.primitive} has already been migrated. Migrations are one-way and one-time.`, "already-migrated", source.key, source.primitive);
2108
- }
2109
- await source.loaded;
2110
- await destination.loaded;
2111
- const transformed = transform(source.value);
2112
- destination.value = transformed;
2113
- migrationRegistry.mark(source.key, source.primitive);
2114
- }
2115
-
2116
- // src/shared/lib/primitive-registry.ts
2117
- class PrimitiveCollisionError extends Error {
2118
- key;
2119
- firstPrimitive;
2120
- firstCallSite;
2121
- secondPrimitive;
2122
- secondCallSite;
2123
- constructor(key, firstPrimitive, firstCallSite, secondPrimitive, secondCallSite) {
2124
- const firstLocation = firstCallSite ? ` (at ${firstCallSite})` : "";
2125
- const secondLocation = secondCallSite ? ` (at ${secondCallSite})` : "";
2126
- super(`Polly primitive key collision: "${key}" is already registered as ` + `$${firstPrimitive}${firstLocation} and cannot also be registered ` + `as $${secondPrimitive}${secondLocation}. Pick a different key or ` + `use the same primitive in both places.`);
2127
- this.name = "PrimitiveCollisionError";
2128
- this.key = key;
2129
- this.firstPrimitive = firstPrimitive;
2130
- this.firstCallSite = firstCallSite;
2131
- this.secondPrimitive = secondPrimitive;
2132
- this.secondCallSite = secondCallSite;
2133
- }
2134
- }
2135
-
2136
- class PrimitiveRegistry {
2137
- entries = new Map;
2138
- register(key, primitive, callSite) {
2139
- const existing = this.entries.get(key);
2140
- if (existing && existing.primitive !== primitive) {
2141
- throw new PrimitiveCollisionError(key, existing.primitive, existing.callSite, primitive, callSite);
2142
- }
2143
- if (!existing) {
2144
- this.entries.set(key, { primitive, callSite });
2145
- }
2146
- }
2147
- has(key) {
2148
- return this.entries.has(key);
2149
- }
2150
- kindOf(key) {
2151
- return this.entries.get(key)?.primitive;
2152
- }
2153
- clear() {
2154
- this.entries.clear();
2155
- }
2156
- get size() {
2157
- return this.entries.size;
2158
- }
2159
- }
2160
- var primitiveRegistry = new PrimitiveRegistry;
2161
-
2162
- // src/shared/lib/schema-version.ts
2163
- var SCHEMA_VERSION_FIELD = "__schemaVersion";
2164
-
2165
- class SchemaVersionError extends Error {
2166
- code;
2167
- docVersion;
2168
- targetVersion;
2169
- opVersion;
2170
- missingVersion;
2171
- constructor(message, code, details = {}) {
2172
- super(message);
2173
- this.name = "SchemaVersionError";
2174
- this.code = code;
2175
- if (details.docVersion !== undefined)
2176
- this.docVersion = details.docVersion;
2177
- if (details.targetVersion !== undefined)
2178
- this.targetVersion = details.targetVersion;
2179
- if (details.opVersion !== undefined)
2180
- this.opVersion = details.opVersion;
2181
- if (details.missingVersion !== undefined)
2182
- this.missingVersion = details.missingVersion;
2183
- }
2184
- }
2185
- function getDocVersion(doc) {
2186
- if (typeof doc !== "object" || doc === null)
2187
- return 0;
2188
- const record = doc;
2189
- const value = record[SCHEMA_VERSION_FIELD];
2190
- return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
2191
- }
2192
- function setDocVersion(doc, version) {
2193
- doc[SCHEMA_VERSION_FIELD] = version;
2194
- }
2195
- function runMigrations(doc, targetVersion, migrations) {
2196
- const current = getDocVersion(doc);
2197
- if (current > targetVersion) {
2198
- throw new SchemaVersionError(`Document is at schema version ${current} but the application targets ${targetVersion}. Upgrade the application to continue.`, "doc-ahead-of-app", { docVersion: current, targetVersion });
2199
- }
2200
- for (let v = current + 1;v <= targetVersion; v++) {
2201
- const migration = migrations[v];
2202
- if (!migration) {
2203
- throw new SchemaVersionError(`Missing migration for schema version ${v}. Migrations must be contiguous from ${current + 1} through ${targetVersion}.`, "missing-migration", { docVersion: current, targetVersion, missingVersion: v });
2204
- }
2205
- migration(doc);
2206
- setDocVersion(doc, v);
2207
- }
2208
- }
2209
- function checkOpVersion(opVersion, docVersion) {
2210
- if (opVersion < docVersion) {
2211
- return { compatible: false, reason: "op-older-than-doc", opVersion, docVersion };
2212
- }
2213
- if (opVersion > docVersion) {
2214
- return { compatible: false, reason: "op-newer-than-doc", opVersion, docVersion };
2215
- }
2216
- return { compatible: true };
2217
- }
2218
- function assertOpVersion(opVersion, docVersion) {
2219
- const result = checkOpVersion(opVersion, docVersion);
2220
- if (result.compatible)
2221
- return;
2222
- const message = result.reason === "op-older-than-doc" ? `Incoming op was produced at schema version ${opVersion} but the document is at version ${docVersion}. The producing peer is behind.` : `Incoming op was produced at schema version ${opVersion} but the document is at version ${docVersion}. The current peer is behind and should upgrade.`;
2223
- throw new SchemaVersionError(message, result.reason, { opVersion, docVersion });
2224
- }
2225
-
2226
- // src/shared/lib/crdt-specialised.ts
2227
- function createSpecialisedPrimitive(config) {
2228
- if (migrationRegistry.isMarked(config.key, config.primitive)) {
2229
- throw new MigrationError(`Cannot construct $${config.primitive}("${config.key}"): this key has been marked as migrated. Migrations are one-way; use the destination primitive instead.`, "already-migrated", config.key, config.primitive);
2230
- }
2231
- primitiveRegistry.register(config.key, config.primitive, config.callSite);
2232
- const inner = signal4(config.initialValue);
2233
- let updating = false;
2234
- let currentHandle;
2235
- const loaded = (async () => {
2236
- const handle = await config.getHandle();
2237
- await handle.whenReady();
2238
- currentHandle = handle;
2239
- if (config.schemaVersion !== undefined) {
2240
- const targetVersion = config.schemaVersion;
2241
- const migrations = config.migrations ?? {};
2242
- handle.change((doc) => {
2243
- runMigrations(doc, targetVersion, migrations);
2244
- setDocVersion(doc, targetVersion);
2245
- });
2246
- }
2247
- updating = true;
2248
- try {
2249
- inner.value = config.extractValue(handle.doc());
2250
- } finally {
2251
- updating = false;
2252
- }
2253
- handle.on("change", (payload) => {
2254
- if (updating)
2255
- return;
2256
- updating = true;
2257
- try {
2258
- inner.value = config.extractValue(payload.doc);
2259
- } finally {
2260
- updating = false;
2261
- }
2262
- });
2263
- effect3(() => {
2264
- const value = inner.value;
2265
- if (updating)
2266
- return;
2267
- if (!currentHandle)
2268
- return;
2269
- updating = true;
2270
- try {
2271
- currentHandle.change((doc) => {
2272
- config.applyWrite(doc, value);
2273
- });
2274
- } finally {
2275
- updating = false;
2276
- }
2277
- });
2278
- })();
2279
- return {
2280
- key: config.key,
2281
- primitive: config.primitive,
2282
- get value() {
2283
- return inner.value;
2284
- },
2285
- set value(next) {
2286
- inner.value = next;
2287
- },
2288
- loaded,
2289
- get handle() {
2290
- return currentHandle;
2291
- }
2292
- };
2293
- }
2294
- function $crdtText(key, initialValue, options) {
2295
- return createSpecialisedPrimitive({
2296
- key,
2297
- primitive: options.primitive ?? "peerState",
2298
- initialValue,
2299
- getHandle: options.getHandle,
2300
- extractValue: (doc) => doc.text ?? "",
2301
- applyWrite: (doc, value) => {
2302
- if (doc.text === undefined) {
2303
- doc.text = value;
2304
- } else {
2305
- updateText(doc, ["text"], value);
2306
- }
2307
- },
2308
- schemaVersion: options.schemaVersion,
2309
- migrations: options.migrations,
2310
- access: options.access,
2311
- callSite: options.callSite
2312
- });
2313
- }
2314
- function $crdtCounter(key, initialValue, options) {
2315
- return createSpecialisedPrimitive({
2316
- key,
2317
- primitive: options.primitive ?? "peerState",
2318
- initialValue,
2319
- getHandle: options.getHandle,
2320
- extractValue: (doc) => {
2321
- const c = doc.count;
2322
- if (c === undefined)
2323
- return 0;
2324
- return c.value;
2325
- },
2326
- applyWrite: (doc, value) => {
2327
- const existing = doc.count;
2328
- if (existing === undefined) {
2329
- doc.count = new Counter(value);
2330
- } else {
2331
- const delta = value - existing.value;
2332
- if (delta !== 0) {
2333
- existing.increment(delta);
2334
- }
2335
- }
2336
- },
2337
- schemaVersion: options.schemaVersion,
2338
- migrations: options.migrations,
2339
- access: options.access,
2340
- callSite: options.callSite
2341
- });
2342
- }
2343
- function $crdtList(key, initialValue, options) {
2344
- return createSpecialisedPrimitive({
2345
- key,
2346
- primitive: options.primitive ?? "peerState",
2347
- initialValue,
2348
- getHandle: options.getHandle,
2349
- extractValue: (doc) => doc.items ? [...doc.items] : [],
2350
- applyWrite: (doc, value) => {
2351
- doc.items = [...value];
2352
- },
2353
- schemaVersion: options.schemaVersion,
2354
- migrations: options.migrations,
2355
- access: options.access,
2356
- callSite: options.callSite
2357
- });
2358
- }
2359
- // src/shared/lib/crdt-state.ts
2360
- import { effect as effect4, signal as signal5 } from "@preact/signals";
2361
- function $crdtState(options) {
2362
- if (migrationRegistry.isMarked(options.key, options.primitive)) {
2363
- throw new MigrationError(`Cannot construct $${options.primitive}("${options.key}"): this key has been marked as migrated. Migrations are one-way; use the destination primitive instead.`, "already-migrated", options.key, options.primitive);
2364
- }
2365
- primitiveRegistry.register(options.key, options.primitive, options.callSite);
2366
- const inner = signal5(options.initialValue);
2367
- let updating = false;
2368
- let currentHandle;
2369
- const loaded = (async () => {
2370
- const handle = await options.getHandle();
2371
- await handle.whenReady();
2372
- currentHandle = handle;
2373
- if (options.schemaVersion !== undefined) {
2374
- const targetVersion = options.schemaVersion;
2375
- const migrations = options.migrations ?? {};
2376
- handle.change((doc) => {
2377
- runMigrations(doc, targetVersion, migrations);
2378
- setDocVersion(doc, targetVersion);
2379
- });
2380
- }
2381
- updating = true;
2382
- try {
2383
- inner.value = cloneDoc(handle.doc());
2384
- } finally {
2385
- updating = false;
2386
- }
2387
- handle.on("change", (payload) => {
2388
- if (updating)
2389
- return;
2390
- updating = true;
2391
- try {
2392
- inner.value = cloneDoc(payload.doc);
2393
- } finally {
2394
- updating = false;
2395
- }
2396
- });
2397
- effect4(() => {
2398
- const value = inner.value;
2399
- if (updating)
2400
- return;
2401
- if (!currentHandle)
2402
- return;
2403
- updating = true;
2404
- try {
2405
- currentHandle.change((doc) => {
2406
- applyTopLevel(doc, value);
2407
- });
2408
- } finally {
2409
- updating = false;
2410
- }
2411
- });
2412
- })();
2413
- return {
2414
- key: options.key,
2415
- primitive: options.primitive,
2416
- get value() {
2417
- return inner.value;
2418
- },
2419
- set value(next) {
2420
- inner.value = next;
2421
- },
2422
- loaded,
2423
- get handle() {
2424
- return currentHandle;
2425
- }
2426
- };
2427
- }
2428
- function cloneDoc(doc) {
2429
- return JSON.parse(JSON.stringify(doc));
2430
- }
2431
- function applyTopLevel(doc, value) {
2432
- for (const key of Object.keys(value)) {
2433
- if (key === SCHEMA_VERSION_FIELD)
2434
- continue;
2435
- doc[key] = value[key];
2436
- }
2437
- }
2438
- // src/shared/lib/encryption.ts
2439
- import nacl from "tweetnacl";
2440
- var KEY_BYTES = 32;
2441
- var NONCE_BYTES = 24;
2442
- var TAG_BYTES = 16;
2443
-
2444
- class EncryptionError extends Error {
2445
- code;
2446
- constructor(message, code) {
2447
- super(message);
2448
- this.name = "EncryptionError";
2449
- this.code = code;
2450
- }
2451
- }
2452
- function generateDocumentKey() {
2453
- return nacl.randomBytes(KEY_BYTES);
2454
- }
2455
- function encrypt(payload, key) {
2456
- if (key.length !== KEY_BYTES) {
2457
- throw new EncryptionError(`secretbox key must be ${KEY_BYTES} bytes, got ${key.length}.`, "invalid-key-length");
2458
- }
2459
- const nonce = nacl.randomBytes(NONCE_BYTES);
2460
- const ciphertext = nacl.secretbox(payload, nonce, key);
2461
- const out = new Uint8Array(NONCE_BYTES + ciphertext.length);
2462
- out.set(nonce, 0);
2463
- out.set(ciphertext, NONCE_BYTES);
2464
- return out;
2465
- }
2466
- function decrypt(sealed, key) {
2467
- if (key.length !== KEY_BYTES) {
2468
- throw new EncryptionError(`secretbox key must be ${KEY_BYTES} bytes, got ${key.length}.`, "invalid-key-length");
2469
- }
2470
- if (sealed.length < NONCE_BYTES + TAG_BYTES) {
2471
- return;
2472
- }
2473
- const nonce = sealed.subarray(0, NONCE_BYTES);
2474
- const ciphertext = sealed.subarray(NONCE_BYTES);
2475
- const opened = nacl.secretbox.open(ciphertext, nonce, key);
2476
- return opened ?? undefined;
2477
- }
2478
- function decryptOrThrow(sealed, key) {
2479
- const opened = decrypt(sealed, key);
2480
- if (!opened) {
2481
- throw new EncryptionError(`Failed to decrypt sealed blob: wrong key, malformed input, or tampered ciphertext.`, "decrypt-failed");
2482
- }
2483
- return opened;
2484
- }
2485
- function sealEnvelope(payload, documentId, key) {
2486
- return {
2487
- documentId,
2488
- sealed: encrypt(payload, key)
2489
- };
2490
- }
2491
- function openEnvelope(envelope, key) {
2492
- return decryptOrThrow(envelope.sealed, key);
2493
- }
2494
- function encodeEncryptedEnvelope(envelope) {
2495
- const idBytes = new TextEncoder().encode(envelope.documentId);
2496
- const out = new Uint8Array(4 + idBytes.length + envelope.sealed.length);
2497
- const view = new DataView(out.buffer);
2498
- view.setUint32(0, idBytes.length, false);
2499
- out.set(idBytes, 4);
2500
- out.set(envelope.sealed, 4 + idBytes.length);
2501
- return out;
2502
- }
2503
- function decodeEncryptedEnvelope(bytes) {
2504
- if (bytes.length < 4) {
2505
- throw new EncryptionError(`Encrypted envelope too short: ${bytes.length} bytes.`, "envelope-malformed");
2506
- }
2507
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2508
- const idLen = view.getUint32(0, false);
2509
- if (bytes.length < 4 + idLen) {
2510
- throw new EncryptionError(`Encrypted envelope truncated: declared id length ${idLen}, total ${bytes.length}.`, "envelope-malformed");
2511
- }
2512
- const documentId = new TextDecoder().decode(bytes.subarray(4, 4 + idLen));
2513
- const sealed = bytes.slice(4 + idLen);
2514
- return { documentId, sealed };
2515
- }
2516
- // src/shared/lib/mesh-network-adapter.ts
2517
- import {
2518
- NetworkAdapter
2519
- } from "@automerge/automerge-repo";
2520
-
2521
- // src/shared/lib/signing.ts
2522
- import nacl2 from "tweetnacl";
2523
- var PUBLIC_KEY_BYTES = 32;
2524
- var SECRET_KEY_BYTES = 64;
2525
- var SIGNATURE_BYTES = 64;
2526
-
2527
- class SigningError extends Error {
2528
- code;
2529
- constructor(message, code) {
2530
- super(message);
2531
- this.name = "SigningError";
2532
- this.code = code;
2533
- }
2534
- }
2535
- function generateSigningKeyPair() {
2536
- const pair = nacl2.sign.keyPair();
2537
- return {
2538
- publicKey: pair.publicKey,
2539
- secretKey: pair.secretKey
2540
- };
2541
- }
2542
- function signingKeyPairFromSecret(secretKey) {
2543
- if (secretKey.length !== SECRET_KEY_BYTES) {
2544
- throw new SigningError(`Ed25519 secret key must be ${SECRET_KEY_BYTES} bytes, got ${secretKey.length}.`, "invalid-secret-key");
2545
- }
2546
- const pair = nacl2.sign.keyPair.fromSecretKey(secretKey);
2547
- return {
2548
- publicKey: pair.publicKey,
2549
- secretKey: pair.secretKey
2550
- };
2551
- }
2552
- function sign(payload, secretKey) {
2553
- if (secretKey.length !== SECRET_KEY_BYTES) {
2554
- throw new SigningError(`Ed25519 secret key must be ${SECRET_KEY_BYTES} bytes, got ${secretKey.length}.`, "invalid-secret-key");
2555
- }
2556
- return nacl2.sign.detached(payload, secretKey);
2557
- }
2558
- function verify(payload, signature, publicKey) {
2559
- if (publicKey.length !== PUBLIC_KEY_BYTES) {
2560
- throw new SigningError(`Ed25519 public key must be ${PUBLIC_KEY_BYTES} bytes, got ${publicKey.length}.`, "invalid-public-key");
2561
- }
2562
- if (signature.length !== SIGNATURE_BYTES) {
2563
- throw new SigningError(`Ed25519 signature must be ${SIGNATURE_BYTES} bytes, got ${signature.length}.`, "invalid-signature-length");
2564
- }
2565
- return nacl2.sign.detached.verify(payload, signature, publicKey);
2566
- }
2567
- function signEnvelope(payload, senderId, secretKey) {
2568
- const signature = sign(payload, secretKey);
2569
- return { senderId, payload, signature };
2570
- }
2571
- function openEnvelope2(envelope, publicKey) {
2572
- const ok = verify(envelope.payload, envelope.signature, publicKey);
2573
- if (!ok) {
2574
- throw new SigningError(`Signature verification failed for envelope from ${envelope.senderId}.`, "envelope-malformed");
2575
- }
2576
- return envelope.payload;
2577
- }
2578
- function encodeSignedEnvelope(envelope) {
2579
- const senderBytes = new TextEncoder().encode(envelope.senderId);
2580
- const total = 4 + senderBytes.length + SIGNATURE_BYTES + envelope.payload.length;
2581
- const out = new Uint8Array(total);
2582
- const view = new DataView(out.buffer);
2583
- view.setUint32(0, senderBytes.length, false);
2584
- out.set(senderBytes, 4);
2585
- out.set(envelope.signature, 4 + senderBytes.length);
2586
- out.set(envelope.payload, 4 + senderBytes.length + SIGNATURE_BYTES);
2587
- return out;
2588
- }
2589
- function decodeSignedEnvelope(bytes) {
2590
- if (bytes.length < 4 + SIGNATURE_BYTES) {
2591
- throw new SigningError(`Envelope too short: ${bytes.length} bytes, need at least ${4 + SIGNATURE_BYTES}.`, "envelope-malformed");
2592
- }
2593
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2594
- const senderLen = view.getUint32(0, false);
2595
- if (bytes.length < 4 + senderLen + SIGNATURE_BYTES) {
2596
- throw new SigningError(`Envelope truncated: declared sender length ${senderLen}, total ${bytes.length}.`, "envelope-malformed");
2597
- }
2598
- const senderId = new TextDecoder().decode(bytes.subarray(4, 4 + senderLen));
2599
- const signature = bytes.slice(4 + senderLen, 4 + senderLen + SIGNATURE_BYTES);
2600
- const payload = bytes.slice(4 + senderLen + SIGNATURE_BYTES);
2601
- return { senderId, payload, signature };
2602
- }
2603
-
2604
- // src/shared/lib/mesh-network-adapter.ts
2605
- var DEFAULT_MESH_KEY_ID = "polly-mesh-default";
2606
-
2607
- class MeshNetworkAdapter extends NetworkAdapter {
2608
- base;
2609
- keyring;
2610
- encryptionEnabled;
2611
- constructor(options) {
2612
- super();
2613
- this.base = options.base;
2614
- this.keyring = options.keyring;
2615
- this.encryptionEnabled = options.encryptionEnabled ?? true;
2616
- this.base.on("close", () => this.emit("close"));
2617
- this.base.on("peer-candidate", (payload) => this.emit("peer-candidate", payload));
2618
- this.base.on("peer-disconnected", (payload) => this.emit("peer-disconnected", payload));
2619
- this.base.on("message", (rawMessage) => {
2620
- const unwrapped = this.tryUnwrap(rawMessage);
2621
- if (unwrapped) {
2622
- this.emit("message", unwrapped);
2623
- }
2624
- });
2625
- }
2626
- isReady() {
2627
- return this.base.isReady();
2628
- }
2629
- whenReady() {
2630
- return this.base.whenReady();
2631
- }
2632
- connect(peerId, peerMetadata) {
2633
- this.peerId = peerId;
2634
- if (peerMetadata !== undefined) {
2635
- this.peerMetadata = peerMetadata;
2636
- }
2637
- this.base.connect(peerId, peerMetadata);
2638
- }
2639
- disconnect() {
2640
- this.base.disconnect();
2641
- }
2642
- send(message) {
2643
- const wrapped = this.wrap(message);
2644
- this.base.send(wrapped);
2645
- }
2646
- wrap(message) {
2647
- const serialised = serialiseMessage(message);
2648
- let payloadToSign;
2649
- if (this.encryptionEnabled) {
2650
- const docKey = this.keyring.documentKeys.get(DEFAULT_MESH_KEY_ID);
2651
- if (!docKey) {
2652
- throw new Error(`MeshNetworkAdapter: missing document encryption key under id "${DEFAULT_MESH_KEY_ID}". Provision the key in the keyring before sending.`);
2653
- }
2654
- const encrypted = sealEnvelope(serialised, DEFAULT_MESH_KEY_ID, docKey);
2655
- payloadToSign = encodeEncryptedEnvelope(encrypted);
2656
- } else {
2657
- payloadToSign = serialised;
2658
- }
2659
- const signed = signEnvelope(payloadToSign, message.senderId, this.keyring.identity.secretKey);
2660
- const signedBytes = encodeSignedEnvelope(signed);
2661
- return {
2662
- type: message.type,
2663
- senderId: message.senderId,
2664
- targetId: message.targetId,
2665
- data: signedBytes
2666
- };
2667
- }
2668
- tryUnwrap(message) {
2669
- if (!message.data)
2670
- return;
2671
- let signed;
2672
- try {
2673
- signed = decodeSignedEnvelope(message.data);
2674
- } catch {
2675
- return;
2676
- }
2677
- if (this.keyring.revokedPeers.has(signed.senderId)) {
2678
- return;
2679
- }
2680
- const senderKey = this.keyring.knownPeers.get(signed.senderId);
2681
- if (!senderKey) {
2682
- return;
2683
- }
2684
- let verifiedPayload;
2685
- try {
2686
- verifiedPayload = openEnvelope2(signed, senderKey);
2687
- } catch {
2688
- return;
2689
- }
2690
- if (!this.encryptionEnabled) {
2691
- return deserialiseMessage(verifiedPayload);
2692
- }
2693
- let encrypted;
2694
- try {
2695
- encrypted = decodeEncryptedEnvelope(verifiedPayload);
2696
- } catch {
2697
- return;
2698
- }
2699
- const docKey = this.keyring.documentKeys.get(encrypted.documentId);
2700
- if (!docKey) {
2701
- return;
2702
- }
2703
- let plaintext;
2704
- try {
2705
- plaintext = openEnvelope(encrypted, docKey);
2706
- } catch {
2707
- return;
2708
- }
2709
- return deserialiseMessage(plaintext);
2710
- }
2711
- }
2712
- function serialiseMessage(message) {
2713
- const headerObj = {
2714
- type: message.type,
2715
- senderId: message.senderId,
2716
- targetId: message.targetId
2717
- };
2718
- if ("documentId" in message && message.documentId !== undefined) {
2719
- headerObj["documentId"] = message.documentId;
2720
- }
2721
- if ("count" in message && message.count !== undefined) {
2722
- headerObj["count"] = message.count;
2723
- }
2724
- if ("sessionId" in message && message.sessionId !== undefined) {
2725
- headerObj["sessionId"] = message.sessionId;
2726
- }
2727
- const headerBytes = new TextEncoder().encode(JSON.stringify(headerObj));
2728
- const dataBytes = "data" in message && message.data instanceof Uint8Array ? message.data : new Uint8Array(0);
2729
- const out = new Uint8Array(4 + headerBytes.length + dataBytes.length);
2730
- const view = new DataView(out.buffer);
2731
- view.setUint32(0, headerBytes.length, false);
2732
- out.set(headerBytes, 4);
2733
- out.set(dataBytes, 4 + headerBytes.length);
2734
- return out;
2735
- }
2736
- function deserialiseMessage(bytes) {
2737
- if (bytes.length < 4) {
2738
- throw new Error("MeshNetworkAdapter: message too short to deserialise.");
2739
- }
2740
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2741
- const headerLen = view.getUint32(0, false);
2742
- if (bytes.length < 4 + headerLen) {
2743
- throw new Error("MeshNetworkAdapter: message header truncated.");
2744
- }
2745
- const header = JSON.parse(new TextDecoder().decode(bytes.subarray(4, 4 + headerLen)));
2746
- const data = bytes.slice(4 + headerLen);
2747
- return { ...header, data };
2748
- }
2749
- // src/shared/lib/mesh-signaling-client.ts
2750
- class MeshSignalingClient {
2751
- url;
2752
- peerId;
2753
- onSignal;
2754
- onError;
2755
- onOpen;
2756
- onClose;
2757
- socket;
2758
- joined = false;
2759
- constructor(options) {
2760
- this.url = options.url;
2761
- this.peerId = options.peerId;
2762
- this.onSignal = options.onSignal;
2763
- if (options.onError !== undefined)
2764
- this.onError = options.onError;
2765
- if (options.onOpen !== undefined)
2766
- this.onOpen = options.onOpen;
2767
- if (options.onClose !== undefined)
2768
- this.onClose = options.onClose;
2769
- }
2770
- async connect() {
2771
- return new Promise((resolve, reject) => {
2772
- const ws = new WebSocket(this.url);
2773
- this.socket = ws;
2774
- ws.addEventListener("open", () => {
2775
- ws.send(JSON.stringify({ type: "join", peerId: this.peerId }));
2776
- this.joined = true;
2777
- this.onOpen?.();
2778
- resolve();
2779
- });
2780
- ws.addEventListener("message", (event) => {
2781
- let msg;
2782
- try {
2783
- msg = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
2784
- } catch {
2785
- return;
2786
- }
2787
- if (msg.type === "signal" && typeof msg.peerId === "string") {
2788
- this.onSignal(msg.peerId, msg.payload);
2789
- return;
2790
- }
2791
- if (msg.type === "error" && msg.reason) {
2792
- this.onError?.(msg.reason, msg.targetPeerId);
2793
- }
2794
- });
2795
- ws.addEventListener("error", (err) => {
2796
- reject(err);
2797
- });
2798
- ws.addEventListener("close", () => {
2799
- this.joined = false;
2800
- this.onClose?.();
2801
- });
2802
- });
2803
- }
2804
- sendSignal(targetPeerId, payload) {
2805
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN || !this.joined) {
2806
- return false;
2807
- }
2808
- const msg = {
2809
- type: "signal",
2810
- peerId: this.peerId,
2811
- targetPeerId,
2812
- payload
2813
- };
2814
- this.socket.send(JSON.stringify(msg));
2815
- return true;
2816
- }
2817
- close() {
2818
- this.socket?.close();
2819
- this.socket = undefined;
2820
- this.joined = false;
2821
- }
2822
- get isConnected() {
2823
- return this.joined && this.socket?.readyState === WebSocket.OPEN;
2824
- }
2825
- }
2826
- // src/shared/lib/mesh-state.ts
2827
- var keyMapsByRepo = new WeakMap;
2828
- var defaultRepo;
2829
- function configureMeshState(repo) {
2830
- defaultRepo = repo;
2831
- keyMapsByRepo.set(repo, new Map);
2832
- }
2833
- function resetMeshState() {
2834
- defaultRepo = undefined;
2835
- }
2836
- function resolveRepo(option) {
2837
- const repo = option ?? defaultRepo;
2838
- if (!repo) {
2839
- throw new Error("Polly $meshState: no Repo configured. Call configureMeshState(repo) at startup or pass { repo } in the primitive options.");
2840
- }
2841
- return repo;
2842
- }
2843
- function getKeyMap(repo) {
2844
- let map = keyMapsByRepo.get(repo);
2845
- if (!map) {
2846
- map = new Map;
2847
- keyMapsByRepo.set(repo, map);
2848
- }
2849
- return map;
2850
- }
2851
- function buildHandleFactory(repo, key, initialDoc) {
2852
- return async () => {
2853
- const map = getKeyMap(repo);
2854
- const existingId = map.get(key);
2855
- if (existingId !== undefined) {
2856
- return repo.find(existingId);
2857
- }
2858
- const handle = repo.create(initialDoc);
2859
- map.set(key, handle.documentId);
2860
- return handle;
2861
- };
2862
- }
2863
- function $meshState(key, initialValue, options = {}) {
2864
- const repo = resolveRepo(options.repo);
2865
- return $crdtState({
2866
- key,
2867
- primitive: "meshState",
2868
- initialValue,
2869
- getHandle: buildHandleFactory(repo, key, initialValue),
2870
- schemaVersion: options.schemaVersion,
2871
- migrations: options.migrations,
2872
- access: options.access
2873
- });
2874
- }
2875
- function $meshText(key, initialValue, options = {}) {
2876
- const repo = resolveRepo(options.repo);
2877
- return $crdtText(key, initialValue, {
2878
- primitive: "meshState",
2879
- getHandle: buildHandleFactory(repo, key, { text: initialValue }),
2880
- schemaVersion: options.schemaVersion,
2881
- migrations: options.migrations,
2882
- access: options.access
2883
- });
2884
- }
2885
- function $meshCounter(key, initialValue, options = {}) {
2886
- const repo = resolveRepo(options.repo);
2887
- return $crdtCounter(key, initialValue, {
2888
- primitive: "meshState",
2889
- getHandle: buildHandleFactory(repo, key, {}),
2890
- schemaVersion: options.schemaVersion,
2891
- migrations: options.migrations,
2892
- access: options.access
2893
- });
2894
- }
2895
- function $meshList(key, initialValue, options = {}) {
2896
- const repo = resolveRepo(options.repo);
2897
- return $crdtList(key, initialValue, {
2898
- primitive: "meshState",
2899
- getHandle: buildHandleFactory(repo, key, { items: initialValue }),
2900
- schemaVersion: options.schemaVersion,
2901
- migrations: options.migrations,
2902
- access: options.access
2903
- });
2904
- }
2905
- // src/shared/lib/mesh-webrtc-adapter.ts
2906
- import {
2907
- NetworkAdapter as NetworkAdapter2
2908
- } from "@automerge/automerge-repo";
2909
- var DEFAULT_ICE_SERVERS = [
2910
- { urls: "stun:stun.l.google.com:19302" },
2911
- { urls: "stun:stun1.l.google.com:19302" }
2912
- ];
2913
-
2914
- class MeshWebRTCAdapter extends NetworkAdapter2 {
2915
- signaling;
2916
- iceServers;
2917
- dataChannelLabel;
2918
- knownPeerIds;
2919
- slots = new Map;
2920
- ready = false;
2921
- readyResolver;
2922
- constructor(options) {
2923
- super();
2924
- this.signaling = options.signaling;
2925
- this.iceServers = options.iceServers ?? DEFAULT_ICE_SERVERS;
2926
- this.dataChannelLabel = options.dataChannelLabel ?? "polly-mesh";
2927
- this.knownPeerIds = options.knownPeerIds ?? [];
2928
- }
2929
- isReady() {
2930
- return this.ready;
2931
- }
2932
- whenReady() {
2933
- if (this.ready)
2934
- return Promise.resolve();
2935
- return new Promise((resolve) => {
2936
- this.readyResolver = resolve;
2937
- });
2938
- }
2939
- connect(peerId, peerMetadata) {
2940
- this.peerId = peerId;
2941
- if (peerMetadata !== undefined) {
2942
- this.peerMetadata = peerMetadata;
2943
- }
2944
- this.ready = true;
2945
- this.readyResolver?.();
2946
- for (const remotePeerId of this.knownPeerIds) {
2947
- if (remotePeerId !== peerId && !this.slots.has(remotePeerId)) {
2948
- this.createInitiatingSlot(remotePeerId);
2949
- }
2950
- }
2951
- }
2952
- disconnect() {
2953
- for (const slot of this.slots.values()) {
2954
- slot.channel?.close();
2955
- slot.connection.close();
2956
- }
2957
- this.slots.clear();
2958
- this.signaling.close();
2959
- this.ready = false;
2960
- this.emit("close");
2961
- }
2962
- send(message) {
2963
- const targetId = message.targetId;
2964
- const bytes = this.serialiseMessage(message);
2965
- let slot = this.slots.get(targetId);
2966
- if (!slot) {
2967
- slot = this.createInitiatingSlot(targetId);
2968
- }
2969
- if (slot.channel && slot.channel.readyState === "open") {
2970
- slot.channel.send(bytes);
2971
- } else {
2972
- slot.pendingSends.push(bytes);
2973
- }
2974
- }
2975
- handleSignal(fromPeerId, rawPayload) {
2976
- const payload = rawPayload;
2977
- if (!payload || typeof payload !== "object" || !("kind" in payload)) {
2978
- return;
2979
- }
2980
- switch (payload.kind) {
2981
- case "offer":
2982
- this.handleOffer(fromPeerId, payload.sdp);
2983
- return;
2984
- case "answer":
2985
- this.handleAnswer(fromPeerId, payload.sdp);
2986
- return;
2987
- case "ice":
2988
- this.handleIceCandidate(fromPeerId, payload.candidate);
2989
- return;
2990
- }
2991
- }
2992
- createInitiatingSlot(targetId) {
2993
- const connection = new RTCPeerConnection({ iceServers: this.iceServers });
2994
- const channel = connection.createDataChannel(this.dataChannelLabel, { ordered: true });
2995
- const slot = { connection, channel, pendingSends: [] };
2996
- this.slots.set(targetId, slot);
2997
- this.wireConnection(targetId, connection);
2998
- this.wireDataChannel(targetId, channel);
2999
- this.initiateOffer(targetId, connection);
3000
- return slot;
3001
- }
3002
- async initiateOffer(targetId, connection) {
3003
- const offer = await connection.createOffer();
3004
- await connection.setLocalDescription(offer);
3005
- this.signaling.sendSignal(targetId, { kind: "offer", sdp: offer });
3006
- }
3007
- async handleOffer(fromPeerId, sdp) {
3008
- const existing = this.slots.get(fromPeerId);
3009
- if (existing) {
3010
- const localId = this.peerId;
3011
- if (localId > fromPeerId) {
3012
- return;
3013
- }
3014
- existing.channel?.close();
3015
- existing.connection.close();
3016
- this.slots.delete(fromPeerId);
3017
- }
3018
- const connection = new RTCPeerConnection({ iceServers: this.iceServers });
3019
- const slot = { connection, channel: undefined, pendingSends: [] };
3020
- this.slots.set(fromPeerId, slot);
3021
- this.wireConnection(fromPeerId, connection);
3022
- connection.ondatachannel = (event) => {
3023
- slot.channel = event.channel;
3024
- this.wireDataChannel(fromPeerId, event.channel);
3025
- };
3026
- await connection.setRemoteDescription(sdp);
3027
- const answer = await connection.createAnswer();
3028
- await connection.setLocalDescription(answer);
3029
- this.signaling.sendSignal(fromPeerId, {
3030
- kind: "answer",
3031
- sdp: answer
3032
- });
3033
- }
3034
- async handleAnswer(fromPeerId, sdp) {
3035
- const slot = this.slots.get(fromPeerId);
3036
- if (!slot)
3037
- return;
3038
- await slot.connection.setRemoteDescription(sdp);
3039
- }
3040
- async handleIceCandidate(fromPeerId, candidate) {
3041
- const slot = this.slots.get(fromPeerId);
3042
- if (!slot)
3043
- return;
3044
- try {
3045
- await slot.connection.addIceCandidate(candidate);
3046
- } catch {}
3047
- }
3048
- wireConnection(peerId, connection) {
3049
- connection.onicecandidate = (event) => {
3050
- if (event.candidate) {
3051
- this.signaling.sendSignal(peerId, {
3052
- kind: "ice",
3053
- candidate: event.candidate.toJSON()
3054
- });
3055
- }
3056
- };
3057
- connection.onconnectionstatechange = () => {
3058
- const state = connection.connectionState;
3059
- if (state === "connected") {
3060
- this.emit("peer-candidate", {
3061
- peerId,
3062
- peerMetadata: {}
3063
- });
3064
- } else if (state === "disconnected" || state === "failed" || state === "closed") {
3065
- this.slots.delete(peerId);
3066
- this.emit("peer-disconnected", { peerId });
3067
- }
3068
- };
3069
- }
3070
- wireDataChannel(peerId, channel) {
3071
- channel.onopen = () => {
3072
- const slot = this.slots.get(peerId);
3073
- if (!slot)
3074
- return;
3075
- for (const bytes of slot.pendingSends) {
3076
- channel.send(bytes);
3077
- }
3078
- slot.pendingSends = [];
3079
- };
3080
- channel.onmessage = (event) => {
3081
- const data = event.data;
3082
- if (data instanceof ArrayBuffer) {
3083
- this.dispatchMessage(new Uint8Array(data));
3084
- } else if (data instanceof Uint8Array) {
3085
- this.dispatchMessage(data);
3086
- }
3087
- };
3088
- channel.onclose = () => {
3089
- const slot = this.slots.get(peerId);
3090
- if (slot?.channel === channel) {
3091
- slot.channel = undefined;
3092
- }
3093
- };
3094
- }
3095
- dispatchMessage(bytes) {
3096
- try {
3097
- const message = this.deserialiseMessage(bytes);
3098
- this.emit("message", message);
3099
- } catch {}
3100
- }
3101
- serialiseMessage(message) {
3102
- const headerObj = {
3103
- type: message.type,
3104
- senderId: message.senderId,
3105
- targetId: message.targetId
3106
- };
3107
- if ("documentId" in message && message.documentId !== undefined) {
3108
- headerObj["documentId"] = message.documentId;
3109
- }
3110
- const headerBytes = new TextEncoder().encode(JSON.stringify(headerObj));
3111
- const dataBytes = "data" in message && message.data instanceof Uint8Array ? message.data : new Uint8Array(0);
3112
- const size = 4 + headerBytes.length + dataBytes.length;
3113
- const buffer = new ArrayBuffer(size);
3114
- const out = new Uint8Array(buffer);
3115
- const view = new DataView(buffer);
3116
- view.setUint32(0, headerBytes.length, false);
3117
- out.set(headerBytes, 4);
3118
- out.set(dataBytes, 4 + headerBytes.length);
3119
- return out;
3120
- }
3121
- deserialiseMessage(bytes) {
3122
- if (bytes.length < 4) {
3123
- throw new Error("MeshWebRTCAdapter: message too short to deserialise.");
3124
- }
3125
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3126
- const headerLen = view.getUint32(0, false);
3127
- if (bytes.length < 4 + headerLen) {
3128
- throw new Error("MeshWebRTCAdapter: message header truncated.");
3129
- }
3130
- const header = JSON.parse(new TextDecoder().decode(bytes.subarray(4, 4 + headerLen)));
3131
- const data = bytes.slice(4 + headerLen);
3132
- return { ...header, data };
3133
- }
3134
- }
3135
- // src/shared/lib/pairing.ts
3136
- var PAIRING_TOKEN_VERSION = 1;
3137
- var PAIRING_TOKEN_MAGIC = new Uint8Array([80, 80, 84, 49]);
3138
- var PAIRING_NONCE_BYTES = 16;
3139
- var DEFAULT_PAIRING_TTL_MS = 10 * 60 * 1000;
3140
-
3141
- class PairingError extends Error {
3142
- code;
3143
- constructor(message, code) {
3144
- super(message);
3145
- this.name = "PairingError";
3146
- this.code = code;
3147
- }
3148
- }
3149
- function createPairingToken(options) {
3150
- const now = options.now ? options.now() : Date.now();
3151
- const ttlMs = options.ttlMs ?? DEFAULT_PAIRING_TTL_MS;
3152
- const documentKey = options.documentKey ?? generateDocumentKey();
3153
- const nonce = randomBytes(PAIRING_NONCE_BYTES);
3154
- return {
3155
- version: PAIRING_TOKEN_VERSION,
3156
- issuerPeerId: options.issuerPeerId,
3157
- issuerPublicKey: options.identity.publicKey,
3158
- documentKey,
3159
- documentKeyId: options.documentKeyId,
3160
- expiresAt: now + ttlMs,
3161
- nonce
3162
- };
3163
- }
3164
- function createPairingTokenWithFreshIdentity(args) {
3165
- const identity = generateSigningKeyPair();
3166
- const token = createPairingToken({
3167
- identity,
3168
- issuerPeerId: args.issuerPeerId,
3169
- documentKeyId: args.documentKeyId,
3170
- ttlMs: args.ttlMs,
3171
- now: args.now
3172
- });
3173
- return { identity, token };
3174
- }
3175
- function isPairingTokenExpired(token, now) {
3176
- const t = now ? now() : Date.now();
3177
- return t >= token.expiresAt;
3178
- }
3179
- function applyPairingToken(token, keyring, options = {}) {
3180
- if (isPairingTokenExpired(token, options.now)) {
3181
- throw new PairingError(`Pairing token from ${token.issuerPeerId} expired at ${new Date(token.expiresAt).toISOString()}.`, "expired");
3182
- }
3183
- keyring.knownPeers.set(token.issuerPeerId, token.issuerPublicKey);
3184
- keyring.documentKeys.set(token.documentKeyId, token.documentKey);
3185
- }
3186
- function serialisePairingToken(token) {
3187
- validateForSerialisation(token);
3188
- const issuerBytes = new TextEncoder().encode(token.issuerPeerId);
3189
- const keyIdBytes = new TextEncoder().encode(token.documentKeyId);
3190
- const total = PAIRING_TOKEN_MAGIC.length + 1 + 4 + issuerBytes.length + PUBLIC_KEY_BYTES + KEY_BYTES + 4 + keyIdBytes.length + 8 + PAIRING_NONCE_BYTES;
3191
- const out = new Uint8Array(total);
3192
- let offset = 0;
3193
- out.set(PAIRING_TOKEN_MAGIC, offset);
3194
- offset += PAIRING_TOKEN_MAGIC.length;
3195
- out[offset] = token.version;
3196
- offset += 1;
3197
- const view = new DataView(out.buffer);
3198
- view.setUint32(offset, issuerBytes.length, false);
3199
- offset += 4;
3200
- out.set(issuerBytes, offset);
3201
- offset += issuerBytes.length;
3202
- out.set(token.issuerPublicKey, offset);
3203
- offset += PUBLIC_KEY_BYTES;
3204
- out.set(token.documentKey, offset);
3205
- offset += KEY_BYTES;
3206
- view.setUint32(offset, keyIdBytes.length, false);
3207
- offset += 4;
3208
- out.set(keyIdBytes, offset);
3209
- offset += keyIdBytes.length;
3210
- view.setBigUint64(offset, BigInt(token.expiresAt), false);
3211
- offset += 8;
3212
- out.set(token.nonce, offset);
3213
- offset += PAIRING_NONCE_BYTES;
3214
- return out;
3215
- }
3216
- function parsePairingToken(bytes) {
3217
- let offset = 0;
3218
- if (bytes.length < PAIRING_TOKEN_MAGIC.length) {
3219
- throw new PairingError(`Pairing token too short: ${bytes.length} bytes.`, "truncated");
3220
- }
3221
- for (let i = 0;i < PAIRING_TOKEN_MAGIC.length; i++) {
3222
- if (bytes[offset + i] !== PAIRING_TOKEN_MAGIC[i]) {
3223
- throw new PairingError(`Pairing token magic mismatch: not a Polly pairing token.`, "wrong-magic");
3224
- }
3225
- }
3226
- offset += PAIRING_TOKEN_MAGIC.length;
3227
- if (bytes.length < offset + 1) {
3228
- throw new PairingError("Pairing token truncated at version.", "truncated");
3229
- }
3230
- const version = bytes[offset];
3231
- offset += 1;
3232
- if (version !== PAIRING_TOKEN_VERSION) {
3233
- throw new PairingError(`Unknown pairing token version: ${version}. This Polly build supports version ${PAIRING_TOKEN_VERSION}.`, "unknown-version");
3234
- }
3235
- if (bytes.length < offset + 4) {
3236
- throw new PairingError("Pairing token truncated at issuer id length.", "truncated");
3237
- }
3238
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3239
- const issuerLen = view.getUint32(offset, false);
3240
- offset += 4;
3241
- if (bytes.length < offset + issuerLen) {
3242
- throw new PairingError("Pairing token truncated at issuer id.", "truncated");
3243
- }
3244
- const issuerPeerId = new TextDecoder().decode(bytes.subarray(offset, offset + issuerLen));
3245
- offset += issuerLen;
3246
- if (bytes.length < offset + PUBLIC_KEY_BYTES) {
3247
- throw new PairingError("Pairing token truncated at public key.", "truncated");
3248
- }
3249
- const issuerPublicKey = bytes.slice(offset, offset + PUBLIC_KEY_BYTES);
3250
- offset += PUBLIC_KEY_BYTES;
3251
- if (bytes.length < offset + KEY_BYTES) {
3252
- throw new PairingError("Pairing token truncated at document key.", "truncated");
3253
- }
3254
- const documentKey = bytes.slice(offset, offset + KEY_BYTES);
3255
- offset += KEY_BYTES;
3256
- if (bytes.length < offset + 4) {
3257
- throw new PairingError("Pairing token truncated at document key id length.", "truncated");
3258
- }
3259
- const keyIdLen = view.getUint32(offset, false);
3260
- offset += 4;
3261
- if (bytes.length < offset + keyIdLen) {
3262
- throw new PairingError("Pairing token truncated at document key id.", "truncated");
3263
- }
3264
- const documentKeyId = new TextDecoder().decode(bytes.subarray(offset, offset + keyIdLen));
3265
- offset += keyIdLen;
3266
- if (bytes.length < offset + 8) {
3267
- throw new PairingError("Pairing token truncated at expiry.", "truncated");
3268
- }
3269
- const expiresAtBig = view.getBigUint64(offset, false);
3270
- offset += 8;
3271
- const expiresAt = Number(expiresAtBig);
3272
- if (bytes.length < offset + PAIRING_NONCE_BYTES) {
3273
- throw new PairingError("Pairing token truncated at nonce.", "truncated");
3274
- }
3275
- const nonce = bytes.slice(offset, offset + PAIRING_NONCE_BYTES);
3276
- offset += PAIRING_NONCE_BYTES;
3277
- return {
3278
- version,
3279
- issuerPeerId,
3280
- issuerPublicKey,
3281
- documentKey,
3282
- documentKeyId,
3283
- expiresAt,
3284
- nonce
3285
- };
3286
- }
3287
- function encodePairingToken(token) {
3288
- const bytes = serialisePairingToken(token);
3289
- let binary = "";
3290
- for (const byte of bytes) {
3291
- binary += String.fromCharCode(byte);
3292
- }
3293
- return btoa(binary);
3294
- }
3295
- function decodePairingToken(encoded) {
3296
- let binary;
3297
- try {
3298
- binary = atob(encoded);
3299
- } catch {
3300
- throw new PairingError("Pairing token is not valid base64.", "wrong-magic");
3301
- }
3302
- const bytes = new Uint8Array(binary.length);
3303
- for (let i = 0;i < binary.length; i++) {
3304
- bytes[i] = binary.charCodeAt(i);
3305
- }
3306
- return parsePairingToken(bytes);
3307
- }
3308
- function validateForSerialisation(token) {
3309
- if (token.issuerPublicKey.length !== PUBLIC_KEY_BYTES) {
3310
- throw new PairingError(`Issuer public key must be ${PUBLIC_KEY_BYTES} bytes, got ${token.issuerPublicKey.length}.`, "invalid-public-key");
3311
- }
3312
- if (token.documentKey.length !== KEY_BYTES) {
3313
- throw new PairingError(`Document key must be ${KEY_BYTES} bytes, got ${token.documentKey.length}.`, "invalid-document-key");
3314
- }
3315
- if (token.nonce.length !== PAIRING_NONCE_BYTES) {
3316
- throw new PairingError(`Nonce must be ${PAIRING_NONCE_BYTES} bytes, got ${token.nonce.length}.`, "invalid-nonce");
3317
- }
3318
- }
3319
- function randomBytes(n) {
3320
- const out = new Uint8Array(n);
3321
- crypto.getRandomValues(out);
3322
- return out;
3323
- }
3324
- // src/shared/lib/peer-relay-adapter.ts
3325
- import { Repo } from "@automerge/automerge-repo";
3326
- import { WebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
3327
- import { signal as signal6 } from "@preact/signals";
3328
- function createPeerStateClient(options) {
3329
- if (options.sign && !options.keyring) {
3330
- throw new Error("Polly createPeerStateClient: { sign: true } requires a keyring. Pass { keyring: { identity, knownPeers, documentKeys: new Map(), revokedPeers: new Set() } } to enable signing.");
3331
- }
3332
- const adapter = new WebSocketClientAdapter(options.url, options.retryInterval);
3333
- const connectionState = signal6("connecting");
3334
- adapter.on("peer-candidate", () => {
3335
- connectionState.value = "connected";
3336
- });
3337
- adapter.on("peer-disconnected", () => {
3338
- connectionState.value = "disconnected";
3339
- });
3340
- adapter.on("close", () => {
3341
- connectionState.value = "disconnected";
3342
- });
3343
- const networkAdapter = options.sign && options.keyring ? new MeshNetworkAdapter({
3344
- base: adapter,
3345
- keyring: options.keyring,
3346
- encryptionEnabled: false
3347
- }) : adapter;
3348
- const repo = new Repo({
3349
- network: [networkAdapter],
3350
- ...options.storage !== undefined && { storage: options.storage }
3351
- });
3352
- return {
3353
- repo,
3354
- connectionState,
3355
- adapter,
3356
- signEnabled: options.sign === true,
3357
- close: async () => {
3358
- await repo.shutdown();
3359
- }
3360
- };
3361
- }
3362
- // src/shared/lib/peer-repo-server.ts
3363
- import { Repo as Repo2 } from "@automerge/automerge-repo";
3364
- import { WebSocketServerAdapter } from "@automerge/automerge-repo-network-websocket";
3365
- import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs";
3366
- import * as ws from "ws";
3367
- async function createPeerRepoServer(options) {
3368
- const wss = await (options.webSocketServer ? Promise.resolve(options.webSocketServer) : new Promise((resolve, reject) => {
3369
- const created = new ws.WebSocketServer({
3370
- port: options.port,
3371
- ...options.host !== undefined && { host: options.host }
3372
- }, () => resolve(created));
3373
- created.once("error", reject);
3374
- }));
3375
- const adapter = new WebSocketServerAdapter(wss);
3376
- const storage2 = new NodeFSStorageAdapter(options.storagePath);
3377
- const repo = new Repo2({
3378
- network: [adapter],
3379
- storage: storage2
3380
- });
3381
- await repo.storageId();
3382
- return {
3383
- repo,
3384
- webSocketServer: wss,
3385
- adapter,
3386
- storage: storage2,
3387
- close: async () => {
3388
- for (const client of wss.clients) {
3389
- try {
3390
- client.terminate();
3391
- } catch {}
3392
- }
3393
- repo.shutdown();
3394
- try {
3395
- wss.close();
3396
- } catch {}
3397
- }
3398
- };
3399
- }
3400
- // src/shared/lib/peer-state.ts
3401
- var keyMapsByRepo2 = new WeakMap;
3402
- var signingEnabledRepos = new WeakSet;
3403
- var defaultRepo2;
3404
- function configurePeerState(repo, options) {
3405
- defaultRepo2 = repo;
3406
- keyMapsByRepo2.set(repo, new Map);
3407
- if (options?.signEnabled) {
3408
- signingEnabledRepos.add(repo);
3409
- }
3410
- }
3411
- function resetPeerState() {
3412
- defaultRepo2 = undefined;
3413
- }
3414
- function resolveRepo2(option) {
3415
- const repo = option ?? defaultRepo2;
3416
- if (!repo) {
3417
- throw new Error("Polly $peerState: no Repo configured. Call configurePeerState(repo) at startup or pass { repo } in the primitive options.");
3418
- }
3419
- return repo;
3420
- }
3421
- function getKeyMap2(repo) {
3422
- let map = keyMapsByRepo2.get(repo);
3423
- if (!map) {
3424
- map = new Map;
3425
- keyMapsByRepo2.set(repo, map);
3426
- }
3427
- return map;
3428
- }
3429
- function validateSignOption(options, repo) {
3430
- if (!options.sign)
3431
- return;
3432
- if (!signingEnabledRepos.has(repo)) {
3433
- throw new Error("Polly $peerState: { sign: true } was passed to the primitive but the configured Repo does not have signing enabled. " + "Pass { sign: true, keyring: ... } to createPeerStateClient to enable signing at the transport level, " + "then call configurePeerState(client.repo, { signEnabled: true }).");
3434
- }
3435
- }
3436
- function buildHandleFactory2(repo, key, initialDoc) {
3437
- return async () => {
3438
- const map = getKeyMap2(repo);
3439
- const existingId = map.get(key);
3440
- if (existingId !== undefined) {
3441
- return repo.find(existingId);
3442
- }
3443
- const handle = repo.create(initialDoc);
3444
- map.set(key, handle.documentId);
3445
- return handle;
3446
- };
3447
- }
3448
- function $peerState(key, initialValue, options = {}) {
3449
- const repo = resolveRepo2(options.repo);
3450
- validateSignOption(options, repo);
3451
- return $crdtState({
3452
- key,
3453
- primitive: "peerState",
3454
- initialValue,
3455
- getHandle: buildHandleFactory2(repo, key, initialValue),
3456
- schemaVersion: options.schemaVersion,
3457
- migrations: options.migrations,
3458
- access: options.access
3459
- });
3460
- }
3461
- function $peerText(key, initialValue, options = {}) {
3462
- const repo = resolveRepo2(options.repo);
3463
- validateSignOption(options, repo);
3464
- return $crdtText(key, initialValue, {
3465
- primitive: "peerState",
3466
- getHandle: buildHandleFactory2(repo, key, { text: initialValue }),
3467
- schemaVersion: options.schemaVersion,
3468
- migrations: options.migrations,
3469
- access: options.access
3470
- });
3471
- }
3472
- function $peerCounter(key, initialValue, options = {}) {
3473
- const repo = resolveRepo2(options.repo);
3474
- validateSignOption(options, repo);
3475
- return $crdtCounter(key, initialValue, {
3476
- primitive: "peerState",
3477
- getHandle: buildHandleFactory2(repo, key, {}),
3478
- schemaVersion: options.schemaVersion,
3479
- migrations: options.migrations,
3480
- access: options.access
3481
- });
3482
- }
3483
- function $peerList(key, initialValue, options = {}) {
3484
- const repo = resolveRepo2(options.repo);
3485
- validateSignOption(options, repo);
3486
- return $crdtList(key, initialValue, {
3487
- primitive: "peerState",
3488
- getHandle: buildHandleFactory2(repo, key, { items: initialValue }),
3489
- schemaVersion: options.schemaVersion,
3490
- migrations: options.migrations,
3491
- access: options.access
3492
- });
3493
- }
3494
- // src/shared/lib/revocation.ts
3495
- var REVOCATION_RECORD_VERSION = 1;
3496
- var REVOCATION_MAGIC = new Uint8Array([80, 82, 86, 49]);
3497
-
3498
- class RevocationError extends Error {
3499
- code;
3500
- constructor(message, code) {
3501
- super(message);
3502
- this.name = "RevocationError";
3503
- this.code = code;
3504
- }
3505
- }
3506
- function createRevocation(options) {
3507
- const now = options.now ? options.now() : Date.now();
3508
- return {
3509
- version: REVOCATION_RECORD_VERSION,
3510
- issuerPeerId: options.issuerPeerId,
3511
- revokedPeerId: options.revokedPeerId,
3512
- issuedAt: now,
3513
- ...options.reason === undefined ? {} : { reason: options.reason }
3514
- };
3515
- }
3516
- function applyRevocation(record, keyring) {
3517
- keyring.revokedPeers.add(record.revokedPeerId);
3518
- }
3519
- function revokePeerLocally(peerId, keyring) {
3520
- keyring.revokedPeers.add(peerId);
3521
- }
3522
- function serialiseRevocationPayload(record) {
3523
- const issuerBytes = new TextEncoder().encode(record.issuerPeerId);
3524
- const revokedBytes = new TextEncoder().encode(record.revokedPeerId);
3525
- const reasonBytes = new TextEncoder().encode(record.reason ?? "");
3526
- const total = REVOCATION_MAGIC.length + 1 + 4 + issuerBytes.length + 4 + revokedBytes.length + 8 + 4 + reasonBytes.length;
3527
- const out = new Uint8Array(total);
3528
- let offset = 0;
3529
- out.set(REVOCATION_MAGIC, offset);
3530
- offset += REVOCATION_MAGIC.length;
3531
- out[offset] = record.version;
3532
- offset += 1;
3533
- const view = new DataView(out.buffer);
3534
- view.setUint32(offset, issuerBytes.length, false);
3535
- offset += 4;
3536
- out.set(issuerBytes, offset);
3537
- offset += issuerBytes.length;
3538
- view.setUint32(offset, revokedBytes.length, false);
3539
- offset += 4;
3540
- out.set(revokedBytes, offset);
3541
- offset += revokedBytes.length;
3542
- view.setBigUint64(offset, BigInt(record.issuedAt), false);
3543
- offset += 8;
3544
- view.setUint32(offset, reasonBytes.length, false);
3545
- offset += 4;
3546
- out.set(reasonBytes, offset);
3547
- return out;
3548
- }
3549
- function parseRevocationPayload(bytes) {
3550
- let offset = 0;
3551
- if (bytes.length < REVOCATION_MAGIC.length) {
3552
- throw new RevocationError("Revocation record too short for magic.", "truncated");
3553
- }
3554
- for (let i = 0;i < REVOCATION_MAGIC.length; i++) {
3555
- if (bytes[offset + i] !== REVOCATION_MAGIC[i]) {
3556
- throw new RevocationError("Revocation record magic mismatch.", "wrong-magic");
3557
- }
3558
- }
3559
- offset += REVOCATION_MAGIC.length;
3560
- if (bytes.length < offset + 1) {
3561
- throw new RevocationError("Revocation record truncated at version.", "truncated");
3562
- }
3563
- const version = bytes[offset];
3564
- offset += 1;
3565
- if (version !== REVOCATION_RECORD_VERSION) {
3566
- throw new RevocationError(`Unknown revocation record version: ${version}.`, "unknown-version");
3567
- }
3568
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3569
- if (bytes.length < offset + 4) {
3570
- throw new RevocationError("Revocation record truncated at issuer length.", "truncated");
3571
- }
3572
- const issuerLen = view.getUint32(offset, false);
3573
- offset += 4;
3574
- if (bytes.length < offset + issuerLen) {
3575
- throw new RevocationError("Revocation record truncated at issuer id.", "truncated");
3576
- }
3577
- const issuerPeerId = new TextDecoder().decode(bytes.subarray(offset, offset + issuerLen));
3578
- offset += issuerLen;
3579
- if (bytes.length < offset + 4) {
3580
- throw new RevocationError("Revocation record truncated at revoked id length.", "truncated");
3581
- }
3582
- const revokedLen = view.getUint32(offset, false);
3583
- offset += 4;
3584
- if (bytes.length < offset + revokedLen) {
3585
- throw new RevocationError("Revocation record truncated at revoked id.", "truncated");
3586
- }
3587
- const revokedPeerId = new TextDecoder().decode(bytes.subarray(offset, offset + revokedLen));
3588
- offset += revokedLen;
3589
- if (bytes.length < offset + 8) {
3590
- throw new RevocationError("Revocation record truncated at issuedAt.", "truncated");
3591
- }
3592
- const issuedAt = Number(view.getBigUint64(offset, false));
3593
- offset += 8;
3594
- if (bytes.length < offset + 4) {
3595
- throw new RevocationError("Revocation record truncated at reason length.", "truncated");
3596
- }
3597
- const reasonLen = view.getUint32(offset, false);
3598
- offset += 4;
3599
- if (bytes.length < offset + reasonLen) {
3600
- throw new RevocationError("Revocation record truncated at reason.", "truncated");
3601
- }
3602
- const reason = new TextDecoder().decode(bytes.subarray(offset, offset + reasonLen));
3603
- offset += reasonLen;
3604
- return {
3605
- version,
3606
- issuerPeerId,
3607
- revokedPeerId,
3608
- issuedAt,
3609
- ...reason ? { reason } : {}
3610
- };
3611
- }
3612
- function encodeRevocation(record, issuer) {
3613
- const payload = serialiseRevocationPayload(record);
3614
- const envelope = signEnvelope(payload, record.issuerPeerId, issuer.secretKey);
3615
- return encodeSignedEnvelope(envelope);
3616
- }
3617
- function decodeRevocation(bytes, keyring) {
3618
- const envelope = decodeSignedEnvelope(bytes);
3619
- const issuerKey = keyring.knownPeers.get(envelope.senderId);
3620
- if (!issuerKey) {
3621
- throw new RevocationError(`Revocation issuer ${envelope.senderId} is not in the local keyring.`, "unknown-issuer");
3622
- }
3623
- if (keyring.revocationAuthority !== undefined && keyring.revocationAuthority.size > 0 && !keyring.revocationAuthority.has(envelope.senderId)) {
3624
- throw new RevocationError(`Revocation issuer ${envelope.senderId} is not in the keyring's revocation authority set.`, "unauthorised-issuer");
3625
- }
3626
- let payloadBytes;
3627
- try {
3628
- payloadBytes = openEnvelope2(envelope, issuerKey);
3629
- } catch {
3630
- throw new RevocationError(`Revocation signature failed verification for issuer ${envelope.senderId}.`, "signature-invalid");
3631
- }
3632
- const record = parseRevocationPayload(payloadBytes);
3633
- if (record.issuerPeerId !== envelope.senderId) {
3634
- throw new RevocationError(`Revocation payload claims issuer ${record.issuerPeerId} but the envelope was signed by ${envelope.senderId}.`, "not-signed-by-issuer");
3635
- }
3636
- return record;
3637
- }
3638
2065
  // src/shared/lib/validation.ts
3639
2066
  function checkPrimitiveType(val, type) {
3640
2067
  if (type === "array")
@@ -3685,116 +2112,49 @@ function validatePartial(_validator) {
3685
2112
  };
3686
2113
  }
3687
2114
  export {
3688
- verify,
3689
2115
  validateShape,
3690
2116
  validatePartial,
3691
2117
  validateEnum,
3692
2118
  validateArray,
3693
- signingKeyPairFromSecret,
3694
- sign,
3695
2119
  settings,
3696
- serialisePairingToken,
3697
2120
  runInContext,
3698
- revokePeerLocally,
3699
- resetPeerState,
3700
- resetMeshState,
3701
2121
  registerConstraints,
3702
2122
  registerConstraint,
3703
2123
  readOnlyExcept,
3704
2124
  quickTest,
3705
2125
  publicAccess,
3706
- parsePairingToken,
3707
2126
  ownerAccess,
3708
2127
  or,
3709
2128
  onlyPeer,
3710
2129
  not,
3711
2130
  nobody,
3712
- migratePrimitive,
3713
2131
  isRuntimeConstraintsEnabled,
3714
- isPairingTokenExpired,
3715
2132
  isBlobRef,
3716
2133
  groupAccess,
3717
2134
  getMessageBus,
3718
- getDocVersion,
3719
- generateSigningKeyPair,
3720
- generateDocumentKey,
3721
- encrypt,
3722
- encodeRevocation,
3723
- encodePairingToken,
3724
- decryptOrThrow,
3725
- decrypt,
3726
- decodeRevocation,
3727
- decodePairingToken,
3728
2135
  createTestSuite,
3729
- createRevocation,
3730
- createPeerStateClient,
3731
- createPeerRepoServer,
3732
- createPairingTokenWithFreshIdentity,
3733
- createPairingToken,
3734
2136
  createContext,
3735
2137
  createChromeAdapters2 as createChromeAdapters,
3736
2138
  createBlobRef,
3737
- configurePeerState,
3738
- configureMeshState,
3739
2139
  computeBlobHash,
3740
2140
  clearConstraints,
3741
2141
  checkPreconditions,
3742
2142
  checkPostconditions,
3743
- checkOpVersion,
3744
- assertOpVersion,
3745
- applyRevocation,
3746
- applyPairingToken,
3747
2143
  anyone,
3748
2144
  anyOfPeers,
3749
2145
  and,
3750
2146
  TimeoutError,
3751
2147
  TestRunner,
3752
- SigningError,
3753
- SchemaVersionError,
3754
- SIGNATURE_BYTES as SIGNING_SIGNATURE_BYTES,
3755
- SECRET_KEY_BYTES as SIGNING_SECRET_KEY_BYTES,
3756
- PUBLIC_KEY_BYTES as SIGNING_PUBLIC_KEY_BYTES,
3757
- SCHEMA_VERSION_FIELD,
3758
- RevocationError,
3759
- REVOCATION_RECORD_VERSION,
3760
- REVOCATION_MAGIC,
3761
- PrimitiveCollisionError,
3762
- PairingError,
3763
- PAIRING_TOKEN_VERSION,
3764
- PAIRING_NONCE_BYTES,
3765
- MigrationError,
3766
2148
  MessageBus,
3767
- MeshWebRTCAdapter,
3768
- MeshSignalingClient,
3769
- MeshNetworkAdapter,
3770
2149
  HandlerError,
3771
2150
  ExtensionError,
3772
2151
  ErrorHandler,
3773
- EncryptionError,
3774
- TAG_BYTES as ENCRYPTION_TAG_BYTES,
3775
- NONCE_BYTES as ENCRYPTION_NONCE_BYTES,
3776
- KEY_BYTES as ENCRYPTION_KEY_BYTES,
3777
- DEFAULT_PAIRING_TTL_MS,
3778
- DEFAULT_MESH_KEY_ID,
3779
- DEFAULT_ICE_SERVERS,
3780
2152
  ConnectionError,
3781
2153
  $syncedState,
3782
2154
  $state,
3783
2155
  $sharedState,
3784
2156
  $resource,
3785
- $persistedState,
3786
- $peerText,
3787
- $peerState,
3788
- $peerList,
3789
- $peerCounter,
3790
- $meshText,
3791
- $meshState,
3792
- $meshList,
3793
- $meshCounter,
3794
- $crdtText,
3795
- $crdtState,
3796
- $crdtList,
3797
- $crdtCounter
2157
+ $persistedState
3798
2158
  };
3799
2159
 
3800
- //# debugId=DC7C621AC65ABFA764756E2164756E21
2160
+ //# debugId=0888B09580CA471964756E2164756E21