@fairfox/polly 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/src/background/index.js.map +7 -7
  2. package/dist/src/background/message-router.js.map +7 -7
  3. package/dist/src/elysia/index.d.ts +2 -0
  4. package/dist/src/elysia/index.js +177 -17
  5. package/dist/src/elysia/index.js.map +8 -5
  6. package/dist/src/elysia/peer-repo-plugin.d.ts +79 -0
  7. package/dist/src/elysia/signaling-server-plugin.d.ts +121 -0
  8. package/dist/src/index.d.ts +36 -0
  9. package/dist/src/index.js +1731 -2
  10. package/dist/src/index.js.map +31 -13
  11. package/dist/src/shared/adapters/index.js.map +6 -6
  12. package/dist/src/shared/lib/_client-only.d.ts +38 -0
  13. package/dist/src/shared/lib/access.d.ts +124 -0
  14. package/dist/src/shared/lib/blob-ref.d.ts +72 -0
  15. package/dist/src/shared/lib/context-helpers.js.map +7 -7
  16. package/dist/src/shared/lib/crdt-specialised.d.ts +129 -0
  17. package/dist/src/shared/lib/crdt-state.d.ts +86 -0
  18. package/dist/src/shared/lib/encryption.d.ts +117 -0
  19. package/dist/src/shared/lib/mesh-network-adapter.d.ts +130 -0
  20. package/dist/src/shared/lib/mesh-signaling-client.d.ts +85 -0
  21. package/dist/src/shared/lib/mesh-state.d.ts +102 -0
  22. package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +132 -0
  23. package/dist/src/shared/lib/message-bus.js.map +7 -7
  24. package/dist/src/shared/lib/migrate-primitive.d.ts +100 -0
  25. package/dist/src/shared/lib/pairing.d.ts +170 -0
  26. package/dist/src/shared/lib/peer-relay-adapter.d.ts +80 -0
  27. package/dist/src/shared/lib/peer-repo-server.d.ts +83 -0
  28. package/dist/src/shared/lib/peer-state.d.ts +117 -0
  29. package/dist/src/shared/lib/primitive-registry.d.ts +88 -0
  30. package/dist/src/shared/lib/resource.js.map +4 -4
  31. package/dist/src/shared/lib/revocation.d.ts +126 -0
  32. package/dist/src/shared/lib/schema-version.d.ts +129 -0
  33. package/dist/src/shared/lib/signing.d.ts +118 -0
  34. package/dist/src/shared/lib/state.js.map +4 -4
  35. package/dist/src/shared/state/app-state.js.map +5 -5
  36. package/dist/tools/init/src/cli.js.map +1 -1
  37. package/dist/tools/quality/src/index.js +177 -0
  38. package/dist/tools/quality/src/index.js.map +10 -0
  39. package/dist/tools/test/src/adapters/index.js.map +2 -2
  40. package/dist/tools/test/src/browser/harness.d.ts +80 -0
  41. package/dist/tools/test/src/browser/index.d.ts +32 -0
  42. package/dist/tools/test/src/browser/index.js +243 -0
  43. package/dist/tools/test/src/browser/index.js.map +10 -0
  44. package/dist/tools/test/src/browser/run.d.ts +26 -0
  45. package/dist/tools/test/src/index.js.map +2 -2
  46. package/dist/tools/verify/specs/tla/MeshState.cfg +21 -0
  47. package/dist/tools/verify/specs/tla/MeshState.tla +247 -0
  48. package/dist/tools/verify/specs/tla/PeerState.cfg +27 -0
  49. package/dist/tools/verify/specs/tla/PeerState.tla +238 -0
  50. package/dist/tools/verify/specs/tla/README.md +27 -3
  51. package/dist/tools/verify/src/cli.js.map +8 -8
  52. package/dist/tools/visualize/src/cli.js.map +7 -7
  53. package/package.json +47 -5
package/dist/src/index.js CHANGED
@@ -1983,10 +1983,1658 @@ var uiState = signal3({
1983
1983
  sidebarOpen: false,
1984
1984
  selectedPanel: "main"
1985
1985
  });
1986
+ // src/shared/lib/access.ts
1987
+ function anyone() {
1988
+ return () => true;
1989
+ }
1990
+ function nobody() {
1991
+ return () => false;
1992
+ }
1993
+ function onlyPeer(peerId) {
1994
+ return (identity) => identity.peerId === peerId;
1995
+ }
1996
+ function anyOfPeers(peerIds) {
1997
+ const set = new Set(peerIds);
1998
+ return (identity) => set.has(identity.peerId);
1999
+ }
2000
+ function and(a, b) {
2001
+ return (identity) => a(identity) && b(identity);
2002
+ }
2003
+ function or(a, b) {
2004
+ return (identity) => a(identity) || b(identity);
2005
+ }
2006
+ function not(a) {
2007
+ return (identity) => !a(identity);
2008
+ }
2009
+ function publicAccess() {
2010
+ return {
2011
+ read: anyone(),
2012
+ write: anyone()
2013
+ };
2014
+ }
2015
+ function ownerAccess(peerId) {
2016
+ const pred = onlyPeer(peerId);
2017
+ return { read: pred, write: pred };
2018
+ }
2019
+ function groupAccess(peerIds) {
2020
+ const pred = anyOfPeers(peerIds);
2021
+ return { read: pred, write: pred };
2022
+ }
2023
+ function readOnlyExcept(writer) {
2024
+ return {
2025
+ read: anyone(),
2026
+ write: writer
2027
+ };
2028
+ }
2029
+ // src/shared/lib/blob-ref.ts
2030
+ function isBlobRef(value) {
2031
+ if (typeof value !== "object" || value === null)
2032
+ return false;
2033
+ const v = value;
2034
+ return typeof v["hash"] === "string" && /^[0-9a-f]{64}$/.test(v["hash"]) && typeof v["size"] === "number" && Number.isInteger(v["size"]) && v["size"] >= 0 && typeof v["filename"] === "string" && typeof v["mimeType"] === "string";
2035
+ }
2036
+ async function computeBlobHash(bytes) {
2037
+ const buffer = new ArrayBuffer(bytes.byteLength);
2038
+ const copy = new Uint8Array(buffer);
2039
+ copy.set(bytes);
2040
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
2041
+ const view = new Uint8Array(digest);
2042
+ let hex = "";
2043
+ for (const byte of view) {
2044
+ hex += byte.toString(16).padStart(2, "0");
2045
+ }
2046
+ return hex;
2047
+ }
2048
+ async function createBlobRef({
2049
+ bytes,
2050
+ filename,
2051
+ mimeType
2052
+ }) {
2053
+ const hash = await computeBlobHash(bytes);
2054
+ return {
2055
+ hash,
2056
+ size: bytes.byteLength,
2057
+ filename,
2058
+ mimeType
2059
+ };
2060
+ }
1986
2061
 
1987
2062
  // src/index.ts
1988
2063
  init_constraints();
1989
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
+ }
1990
3638
  // src/shared/lib/validation.ts
1991
3639
  function checkPrimitiveType(val, type) {
1992
3640
  if (type === "array")
@@ -2037,35 +3685,116 @@ function validatePartial(_validator) {
2037
3685
  };
2038
3686
  }
2039
3687
  export {
3688
+ verify,
2040
3689
  validateShape,
2041
3690
  validatePartial,
2042
3691
  validateEnum,
2043
3692
  validateArray,
3693
+ signingKeyPairFromSecret,
3694
+ sign,
2044
3695
  settings,
3696
+ serialisePairingToken,
2045
3697
  runInContext,
3698
+ revokePeerLocally,
3699
+ resetPeerState,
3700
+ resetMeshState,
2046
3701
  registerConstraints,
2047
3702
  registerConstraint,
3703
+ readOnlyExcept,
2048
3704
  quickTest,
3705
+ publicAccess,
3706
+ parsePairingToken,
3707
+ ownerAccess,
3708
+ or,
3709
+ onlyPeer,
3710
+ not,
3711
+ nobody,
3712
+ migratePrimitive,
2049
3713
  isRuntimeConstraintsEnabled,
3714
+ isPairingTokenExpired,
3715
+ isBlobRef,
3716
+ groupAccess,
2050
3717
  getMessageBus,
3718
+ getDocVersion,
3719
+ generateSigningKeyPair,
3720
+ generateDocumentKey,
3721
+ encrypt,
3722
+ encodeRevocation,
3723
+ encodePairingToken,
3724
+ decryptOrThrow,
3725
+ decrypt,
3726
+ decodeRevocation,
3727
+ decodePairingToken,
2051
3728
  createTestSuite,
3729
+ createRevocation,
3730
+ createPeerStateClient,
3731
+ createPeerRepoServer,
3732
+ createPairingTokenWithFreshIdentity,
3733
+ createPairingToken,
2052
3734
  createContext,
2053
3735
  createChromeAdapters2 as createChromeAdapters,
3736
+ createBlobRef,
3737
+ configurePeerState,
3738
+ configureMeshState,
3739
+ computeBlobHash,
2054
3740
  clearConstraints,
2055
3741
  checkPreconditions,
2056
3742
  checkPostconditions,
3743
+ checkOpVersion,
3744
+ assertOpVersion,
3745
+ applyRevocation,
3746
+ applyPairingToken,
3747
+ anyone,
3748
+ anyOfPeers,
3749
+ and,
2057
3750
  TimeoutError,
2058
3751
  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,
2059
3766
  MessageBus,
3767
+ MeshWebRTCAdapter,
3768
+ MeshSignalingClient,
3769
+ MeshNetworkAdapter,
2060
3770
  HandlerError,
2061
3771
  ExtensionError,
2062
3772
  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,
2063
3780
  ConnectionError,
2064
3781
  $syncedState,
2065
3782
  $state,
2066
3783
  $sharedState,
2067
3784
  $resource,
2068
- $persistedState
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
2069
3798
  };
2070
3799
 
2071
- //# debugId=C2AEE82F23E5AE9464756E2164756E21
3800
+ //# debugId=DC7C621AC65ABFA764756E2164756E21