@fairfox/polly 0.23.0 → 0.25.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 (116) hide show
  1. package/README.md +55 -1
  2. package/dist/cli/polly.js +21 -1
  3. package/dist/cli/polly.js.map +3 -3
  4. package/dist/src/actions/error.d.ts +26 -0
  5. package/dist/src/actions/event-delegation.d.ts +48 -0
  6. package/dist/src/actions/form.d.ts +72 -0
  7. package/dist/src/actions/index.d.ts +13 -0
  8. package/dist/src/actions/index.js +525 -0
  9. package/dist/src/actions/index.js.map +15 -0
  10. package/dist/src/actions/overlay.d.ts +26 -0
  11. package/dist/src/actions/registry.d.ts +25 -0
  12. package/dist/src/actions/store.d.ts +26 -0
  13. package/dist/src/actions/testing.d.ts +26 -0
  14. package/dist/src/background/index.js +26 -1
  15. package/dist/src/background/index.js.map +2 -2
  16. package/dist/src/background/message-router.js +26 -1
  17. package/dist/src/background/message-router.js.map +2 -2
  18. package/dist/src/client/index.js +27 -2
  19. package/dist/src/client/index.js.map +3 -3
  20. package/dist/src/elysia/index.js +27 -2
  21. package/dist/src/elysia/index.js.map +3 -3
  22. package/dist/src/elysia/peer-repo-plugin.d.ts +1 -1
  23. package/dist/src/index.js +26 -1
  24. package/dist/src/index.js.map +2 -2
  25. package/dist/src/mesh-node.d.ts +89 -0
  26. package/dist/src/mesh-node.js +619 -0
  27. package/dist/src/mesh-node.js.map +14 -0
  28. package/dist/src/mesh.d.ts +10 -0
  29. package/dist/src/mesh.js +951 -24
  30. package/dist/src/mesh.js.map +17 -9
  31. package/dist/src/peer.d.ts +1 -0
  32. package/dist/src/peer.js +130 -84
  33. package/dist/src/peer.js.map +11 -10
  34. package/dist/src/polly-ui/ActionForm.d.ts +21 -0
  35. package/dist/src/polly-ui/ActionInput.d.ts +41 -0
  36. package/dist/src/polly-ui/ConfirmDialog.d.ts +24 -0
  37. package/dist/src/polly-ui/Layout.d.ts +51 -0
  38. package/dist/src/polly-ui/Modal.d.ts +52 -0
  39. package/dist/src/polly-ui/OverlayRoot.d.ts +10 -0
  40. package/dist/src/polly-ui/TextInput.d.ts +31 -0
  41. package/dist/src/polly-ui/Toast.d.ts +19 -0
  42. package/dist/src/polly-ui/index.css +319 -0
  43. package/dist/src/polly-ui/index.d.ts +17 -0
  44. package/dist/src/polly-ui/index.js +953 -0
  45. package/dist/src/polly-ui/index.js.map +22 -0
  46. package/dist/src/polly-ui/internal/focus-trap.d.ts +10 -0
  47. package/dist/src/polly-ui/internal/input-base.d.ts +18 -0
  48. package/dist/src/polly-ui/internal/scroll-lock.d.ts +9 -0
  49. package/dist/src/polly-ui/styles.css +70 -0
  50. package/dist/src/polly-ui/theme.css +163 -0
  51. package/dist/src/shared/adapters/index.js +26 -1
  52. package/dist/src/shared/adapters/index.js.map +2 -2
  53. package/dist/src/shared/lib/blob-cache.d.ts +58 -0
  54. package/dist/src/shared/lib/blob-store-impl.d.ts +33 -0
  55. package/dist/src/shared/lib/blob-store.d.ts +87 -0
  56. package/dist/src/shared/lib/blob-transfer.d.ts +58 -0
  57. package/dist/src/shared/lib/context-helpers.js +26 -1
  58. package/dist/src/shared/lib/context-helpers.js.map +2 -2
  59. package/dist/src/shared/lib/crdt-specialised.d.ts +1 -1
  60. package/dist/src/shared/lib/crdt-state.d.ts +1 -1
  61. package/dist/src/shared/lib/errors.js +26 -1
  62. package/dist/src/shared/lib/errors.js.map +2 -2
  63. package/dist/src/shared/lib/keyring-storage.d.ts +57 -0
  64. package/dist/src/shared/lib/mesh-client.d.ts +91 -0
  65. package/dist/src/shared/lib/mesh-network-adapter.d.ts +1 -1
  66. package/dist/src/shared/lib/mesh-signaling-client.d.ts +6 -0
  67. package/dist/src/shared/lib/mesh-state.d.ts +1 -1
  68. package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +20 -1
  69. package/dist/src/shared/lib/message-bus.js +26 -1
  70. package/dist/src/shared/lib/message-bus.js.map +2 -2
  71. package/dist/src/shared/lib/peer-relay-adapter.d.ts +1 -1
  72. package/dist/src/shared/lib/peer-repo-server.d.ts +1 -1
  73. package/dist/src/shared/lib/peer-state.d.ts +1 -1
  74. package/dist/src/shared/lib/resource.js +26 -1
  75. package/dist/src/shared/lib/resource.js.map +2 -2
  76. package/dist/src/shared/lib/state.js +26 -1
  77. package/dist/src/shared/lib/state.js.map +2 -2
  78. package/dist/src/shared/lib/test-helpers.js +26 -1
  79. package/dist/src/shared/lib/test-helpers.js.map +2 -2
  80. package/dist/src/shared/lib/wasm-init.d.ts +17 -0
  81. package/dist/src/shared/state/app-state.js +26 -1
  82. package/dist/src/shared/state/app-state.js.map +2 -2
  83. package/dist/src/shared/types/messages.js +26 -1
  84. package/dist/src/shared/types/messages.js.map +2 -2
  85. package/dist/tools/quality/src/cli.js +647 -28
  86. package/dist/tools/quality/src/cli.js.map +11 -5
  87. package/dist/tools/quality/src/css/check-layout.d.ts +19 -0
  88. package/dist/tools/quality/src/css/check-quality.d.ts +24 -0
  89. package/dist/tools/quality/src/css/check-unused.d.ts +20 -0
  90. package/dist/tools/quality/src/css/check-vars.d.ts +22 -0
  91. package/dist/tools/quality/src/css/shared.d.ts +33 -0
  92. package/dist/tools/quality/src/index.d.ts +37 -0
  93. package/dist/tools/quality/src/index.js +735 -0
  94. package/dist/tools/quality/src/index.js.map +16 -0
  95. package/dist/tools/quality/src/logger.d.ts +26 -0
  96. package/dist/tools/quality/src/no-as-casting.d.ts +44 -0
  97. package/dist/tools/test/src/adapters/index.js +26 -1
  98. package/dist/tools/test/src/adapters/index.js.map +2 -2
  99. package/dist/tools/test/src/browser/index.js +26 -1
  100. package/dist/tools/test/src/browser/index.js.map +2 -2
  101. package/dist/tools/test/src/browser/run.js +238 -0
  102. package/dist/tools/test/src/browser/run.js.map +11 -0
  103. package/dist/tools/test/src/index.js +26 -1
  104. package/dist/tools/test/src/index.js.map +2 -2
  105. package/dist/tools/test/src/test-utils.js +26 -1
  106. package/dist/tools/test/src/test-utils.js.map +2 -2
  107. package/dist/tools/test/src/visual/compare.d.ts +23 -0
  108. package/dist/tools/test/src/visual/harness.d.ts +53 -0
  109. package/dist/tools/test/src/visual/index.d.ts +12 -0
  110. package/dist/tools/test/src/visual/index.js +13968 -0
  111. package/dist/tools/test/src/visual/index.js.map +41 -0
  112. package/dist/tools/verify/src/cli.js +3 -3
  113. package/dist/tools/verify/src/cli.js.map +1 -1
  114. package/dist/tools/verify/src/config.js +26 -1
  115. package/dist/tools/verify/src/config.js.map +2 -2
  116. package/package.json +42 -3
@@ -0,0 +1,619 @@
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __toCommonJS = (from) => {
33
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
34
+ if (entry)
35
+ return entry;
36
+ entry = __defProp({}, "__esModule", { value: true });
37
+ if (from && typeof from === "object" || typeof from === "function") {
38
+ for (var key of __getOwnPropNames(from))
39
+ if (!__hasOwnProp.call(entry, key))
40
+ __defProp(entry, key, {
41
+ get: __accessProp.bind(from, key),
42
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
43
+ });
44
+ }
45
+ __moduleCache.set(from, entry);
46
+ return entry;
47
+ };
48
+ var __moduleCache;
49
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
50
+ var __returnValue = (v) => v;
51
+ function __exportSetter(name, newValue) {
52
+ this[name] = __returnValue.bind(null, newValue);
53
+ }
54
+ var __export = (target, all) => {
55
+ for (var name in all)
56
+ __defProp(target, name, {
57
+ get: all[name],
58
+ enumerable: true,
59
+ configurable: true,
60
+ set: __exportSetter.bind(all, name)
61
+ });
62
+ };
63
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
64
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
65
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
66
+ }) : x)(function(x) {
67
+ if (typeof require !== "undefined")
68
+ return require.apply(this, arguments);
69
+ throw Error('Dynamic require of "' + x + '" is not supported');
70
+ });
71
+
72
+ // src/shared/lib/encryption.ts
73
+ var exports_encryption = {};
74
+ __export(exports_encryption, {
75
+ sealEnvelope: () => sealEnvelope,
76
+ openEnvelope: () => openEnvelope,
77
+ generateDocumentKey: () => generateDocumentKey,
78
+ encrypt: () => encrypt,
79
+ encodeEncryptedEnvelope: () => encodeEncryptedEnvelope,
80
+ decryptOrThrow: () => decryptOrThrow,
81
+ decrypt: () => decrypt,
82
+ decodeEncryptedEnvelope: () => decodeEncryptedEnvelope,
83
+ TAG_BYTES: () => TAG_BYTES,
84
+ NONCE_BYTES: () => NONCE_BYTES,
85
+ KEY_BYTES: () => KEY_BYTES,
86
+ EncryptionError: () => EncryptionError
87
+ });
88
+ import nacl from "tweetnacl";
89
+ function generateDocumentKey() {
90
+ return nacl.randomBytes(KEY_BYTES);
91
+ }
92
+ function encrypt(payload, key) {
93
+ if (key.length !== KEY_BYTES) {
94
+ throw new EncryptionError(`secretbox key must be ${KEY_BYTES} bytes, got ${key.length}.`, "invalid-key-length");
95
+ }
96
+ const nonce = nacl.randomBytes(NONCE_BYTES);
97
+ const ciphertext = nacl.secretbox(payload, nonce, key);
98
+ const out = new Uint8Array(NONCE_BYTES + ciphertext.length);
99
+ out.set(nonce, 0);
100
+ out.set(ciphertext, NONCE_BYTES);
101
+ return out;
102
+ }
103
+ function decrypt(sealed, key) {
104
+ if (key.length !== KEY_BYTES) {
105
+ throw new EncryptionError(`secretbox key must be ${KEY_BYTES} bytes, got ${key.length}.`, "invalid-key-length");
106
+ }
107
+ if (sealed.length < NONCE_BYTES + TAG_BYTES) {
108
+ return;
109
+ }
110
+ const nonce = sealed.subarray(0, NONCE_BYTES);
111
+ const ciphertext = sealed.subarray(NONCE_BYTES);
112
+ const opened = nacl.secretbox.open(ciphertext, nonce, key);
113
+ return opened ?? undefined;
114
+ }
115
+ function decryptOrThrow(sealed, key) {
116
+ const opened = decrypt(sealed, key);
117
+ if (!opened) {
118
+ throw new EncryptionError(`Failed to decrypt sealed blob: wrong key, malformed input, or tampered ciphertext.`, "decrypt-failed");
119
+ }
120
+ return opened;
121
+ }
122
+ function sealEnvelope(payload, documentId, key) {
123
+ return {
124
+ documentId,
125
+ sealed: encrypt(payload, key)
126
+ };
127
+ }
128
+ function openEnvelope(envelope, key) {
129
+ return decryptOrThrow(envelope.sealed, key);
130
+ }
131
+ function encodeEncryptedEnvelope(envelope) {
132
+ const idBytes = new TextEncoder().encode(envelope.documentId);
133
+ const out = new Uint8Array(4 + idBytes.length + envelope.sealed.length);
134
+ const view = new DataView(out.buffer);
135
+ view.setUint32(0, idBytes.length, false);
136
+ out.set(idBytes, 4);
137
+ out.set(envelope.sealed, 4 + idBytes.length);
138
+ return out;
139
+ }
140
+ function decodeEncryptedEnvelope(bytes) {
141
+ if (bytes.length < 4) {
142
+ throw new EncryptionError(`Encrypted envelope too short: ${bytes.length} bytes.`, "envelope-malformed");
143
+ }
144
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
145
+ const idLen = view.getUint32(0, false);
146
+ if (bytes.length < 4 + idLen) {
147
+ throw new EncryptionError(`Encrypted envelope truncated: declared id length ${idLen}, total ${bytes.length}.`, "envelope-malformed");
148
+ }
149
+ const documentId = new TextDecoder().decode(bytes.subarray(4, 4 + idLen));
150
+ const sealed = bytes.slice(4 + idLen);
151
+ return { documentId, sealed };
152
+ }
153
+ var KEY_BYTES = 32, NONCE_BYTES = 24, TAG_BYTES = 16, EncryptionError;
154
+ var init_encryption = __esm(() => {
155
+ EncryptionError = class EncryptionError extends Error {
156
+ code;
157
+ constructor(message, code) {
158
+ super(message);
159
+ this.name = "EncryptionError";
160
+ this.code = code;
161
+ }
162
+ };
163
+ });
164
+
165
+ // src/mesh-node.ts
166
+ var {readFile, rename, writeFile} = (() => ({}));
167
+ var {createInterface} = (() => ({}));
168
+
169
+ // src/shared/lib/keyring-storage.ts
170
+ function memoryKeyringStorage() {
171
+ let stored = null;
172
+ return {
173
+ load: async () => stored,
174
+ save: async (keyring) => {
175
+ stored = keyring;
176
+ }
177
+ };
178
+ }
179
+ function serialiseKeyring(keyring) {
180
+ const payload = {
181
+ version: 1,
182
+ identity: {
183
+ publicKey: bytesToBase64(keyring.identity.publicKey),
184
+ secretKey: bytesToBase64(keyring.identity.secretKey)
185
+ },
186
+ knownPeers: mapToBase64Record(keyring.knownPeers),
187
+ documentKeys: mapToBase64Record(keyring.documentKeys),
188
+ revokedPeers: [...keyring.revokedPeers]
189
+ };
190
+ if (keyring.revocationAuthority && keyring.revocationAuthority.size > 0) {
191
+ payload.revocationAuthority = [...keyring.revocationAuthority];
192
+ }
193
+ return JSON.stringify(payload, null, 2);
194
+ }
195
+ function deserialiseKeyring(text) {
196
+ let raw;
197
+ try {
198
+ raw = JSON.parse(text);
199
+ } catch (err) {
200
+ throw new Error(`KeyringStorage: keyring payload is not valid JSON: ${err.message}`);
201
+ }
202
+ if (!raw || typeof raw !== "object") {
203
+ throw new Error("KeyringStorage: keyring payload is not an object");
204
+ }
205
+ const r = raw;
206
+ if (r.version !== 1) {
207
+ throw new Error(`KeyringStorage: unsupported keyring version: ${String(r.version)}`);
208
+ }
209
+ if (!r.identity || typeof r.identity !== "object") {
210
+ throw new Error("KeyringStorage: keyring payload is missing identity");
211
+ }
212
+ const identity = {
213
+ publicKey: base64ToBytes(r.identity.publicKey),
214
+ secretKey: base64ToBytes(r.identity.secretKey)
215
+ };
216
+ const keyring = {
217
+ identity,
218
+ knownPeers: base64RecordToMap(r.knownPeers ?? {}),
219
+ documentKeys: base64RecordToMap(r.documentKeys ?? {}),
220
+ revokedPeers: new Set(r.revokedPeers ?? [])
221
+ };
222
+ if (r.revocationAuthority && r.revocationAuthority.length > 0) {
223
+ keyring.revocationAuthority = new Set(r.revocationAuthority);
224
+ }
225
+ return keyring;
226
+ }
227
+ function mapToBase64Record(map) {
228
+ const out = {};
229
+ for (const [key, value] of map) {
230
+ out[key] = bytesToBase64(value);
231
+ }
232
+ return out;
233
+ }
234
+ function base64RecordToMap(record) {
235
+ const out = new Map;
236
+ for (const [key, value] of Object.entries(record)) {
237
+ out.set(key, base64ToBytes(value));
238
+ }
239
+ return out;
240
+ }
241
+ function bytesToBase64(bytes) {
242
+ let binary = "";
243
+ for (const byte of bytes) {
244
+ binary += String.fromCharCode(byte);
245
+ }
246
+ return btoa(binary);
247
+ }
248
+ function base64ToBytes(b64) {
249
+ const binary = atob(b64);
250
+ const bytes = new Uint8Array(binary.length);
251
+ for (let i = 0;i < binary.length; i++) {
252
+ bytes[i] = binary.charCodeAt(i);
253
+ }
254
+ return bytes;
255
+ }
256
+
257
+ // src/shared/lib/pairing.ts
258
+ init_encryption();
259
+
260
+ // src/shared/lib/signing.ts
261
+ import nacl2 from "tweetnacl";
262
+ var PUBLIC_KEY_BYTES = 32;
263
+ var SECRET_KEY_BYTES = 64;
264
+ var SIGNATURE_BYTES = 64;
265
+
266
+ class SigningError extends Error {
267
+ code;
268
+ constructor(message, code) {
269
+ super(message);
270
+ this.name = "SigningError";
271
+ this.code = code;
272
+ }
273
+ }
274
+ function generateSigningKeyPair() {
275
+ const pair = nacl2.sign.keyPair();
276
+ return {
277
+ publicKey: pair.publicKey,
278
+ secretKey: pair.secretKey
279
+ };
280
+ }
281
+ function signingKeyPairFromSecret(secretKey) {
282
+ if (secretKey.length !== SECRET_KEY_BYTES) {
283
+ throw new SigningError(`Ed25519 secret key must be ${SECRET_KEY_BYTES} bytes, got ${secretKey.length}.`, "invalid-secret-key");
284
+ }
285
+ const pair = nacl2.sign.keyPair.fromSecretKey(secretKey);
286
+ return {
287
+ publicKey: pair.publicKey,
288
+ secretKey: pair.secretKey
289
+ };
290
+ }
291
+ function sign(payload, secretKey) {
292
+ if (secretKey.length !== SECRET_KEY_BYTES) {
293
+ throw new SigningError(`Ed25519 secret key must be ${SECRET_KEY_BYTES} bytes, got ${secretKey.length}.`, "invalid-secret-key");
294
+ }
295
+ return nacl2.sign.detached(payload, secretKey);
296
+ }
297
+ function verify(payload, signature, publicKey) {
298
+ if (publicKey.length !== PUBLIC_KEY_BYTES) {
299
+ throw new SigningError(`Ed25519 public key must be ${PUBLIC_KEY_BYTES} bytes, got ${publicKey.length}.`, "invalid-public-key");
300
+ }
301
+ if (signature.length !== SIGNATURE_BYTES) {
302
+ throw new SigningError(`Ed25519 signature must be ${SIGNATURE_BYTES} bytes, got ${signature.length}.`, "invalid-signature-length");
303
+ }
304
+ return nacl2.sign.detached.verify(payload, signature, publicKey);
305
+ }
306
+ function signEnvelope(payload, senderId, secretKey) {
307
+ const signature = sign(payload, secretKey);
308
+ return { senderId, payload, signature };
309
+ }
310
+ function openEnvelope2(envelope, publicKey) {
311
+ const ok = verify(envelope.payload, envelope.signature, publicKey);
312
+ if (!ok) {
313
+ throw new SigningError(`Signature verification failed for envelope from ${envelope.senderId}.`, "envelope-malformed");
314
+ }
315
+ return envelope.payload;
316
+ }
317
+ function encodeSignedEnvelope(envelope) {
318
+ const senderBytes = new TextEncoder().encode(envelope.senderId);
319
+ const total = 4 + senderBytes.length + SIGNATURE_BYTES + envelope.payload.length;
320
+ const out = new Uint8Array(total);
321
+ const view = new DataView(out.buffer);
322
+ view.setUint32(0, senderBytes.length, false);
323
+ out.set(senderBytes, 4);
324
+ out.set(envelope.signature, 4 + senderBytes.length);
325
+ out.set(envelope.payload, 4 + senderBytes.length + SIGNATURE_BYTES);
326
+ return out;
327
+ }
328
+ function decodeSignedEnvelope(bytes) {
329
+ if (bytes.length < 4 + SIGNATURE_BYTES) {
330
+ throw new SigningError(`Envelope too short: ${bytes.length} bytes, need at least ${4 + SIGNATURE_BYTES}.`, "envelope-malformed");
331
+ }
332
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
333
+ const senderLen = view.getUint32(0, false);
334
+ if (bytes.length < 4 + senderLen + SIGNATURE_BYTES) {
335
+ throw new SigningError(`Envelope truncated: declared sender length ${senderLen}, total ${bytes.length}.`, "envelope-malformed");
336
+ }
337
+ const senderId = new TextDecoder().decode(bytes.subarray(4, 4 + senderLen));
338
+ const signature = bytes.slice(4 + senderLen, 4 + senderLen + SIGNATURE_BYTES);
339
+ const payload = bytes.slice(4 + senderLen + SIGNATURE_BYTES);
340
+ return { senderId, payload, signature };
341
+ }
342
+
343
+ // src/shared/lib/pairing.ts
344
+ var PAIRING_TOKEN_VERSION = 1;
345
+ var PAIRING_TOKEN_MAGIC = new Uint8Array([80, 80, 84, 49]);
346
+ var PAIRING_NONCE_BYTES = 16;
347
+ var DEFAULT_PAIRING_TTL_MS = 10 * 60 * 1000;
348
+
349
+ class PairingError extends Error {
350
+ code;
351
+ constructor(message, code) {
352
+ super(message);
353
+ this.name = "PairingError";
354
+ this.code = code;
355
+ }
356
+ }
357
+ function createPairingToken(options) {
358
+ const now = options.now ? options.now() : Date.now();
359
+ const ttlMs = options.ttlMs ?? DEFAULT_PAIRING_TTL_MS;
360
+ const documentKey = options.documentKey ?? generateDocumentKey();
361
+ const nonce = randomBytes(PAIRING_NONCE_BYTES);
362
+ return {
363
+ version: PAIRING_TOKEN_VERSION,
364
+ issuerPeerId: options.issuerPeerId,
365
+ issuerPublicKey: options.identity.publicKey,
366
+ documentKey,
367
+ documentKeyId: options.documentKeyId,
368
+ expiresAt: now + ttlMs,
369
+ nonce
370
+ };
371
+ }
372
+ function createPairingTokenWithFreshIdentity(args) {
373
+ const identity = generateSigningKeyPair();
374
+ const token = createPairingToken({
375
+ identity,
376
+ issuerPeerId: args.issuerPeerId,
377
+ documentKeyId: args.documentKeyId,
378
+ ttlMs: args.ttlMs,
379
+ now: args.now
380
+ });
381
+ return { identity, token };
382
+ }
383
+ function isPairingTokenExpired(token, now) {
384
+ const t = now ? now() : Date.now();
385
+ return t >= token.expiresAt;
386
+ }
387
+ function applyPairingToken(token, keyring, options = {}) {
388
+ if (isPairingTokenExpired(token, options.now)) {
389
+ throw new PairingError(`Pairing token from ${token.issuerPeerId} expired at ${new Date(token.expiresAt).toISOString()}.`, "expired");
390
+ }
391
+ keyring.knownPeers.set(token.issuerPeerId, token.issuerPublicKey);
392
+ keyring.documentKeys.set(token.documentKeyId, token.documentKey);
393
+ }
394
+ function serialisePairingToken(token) {
395
+ validateForSerialisation(token);
396
+ const issuerBytes = new TextEncoder().encode(token.issuerPeerId);
397
+ const keyIdBytes = new TextEncoder().encode(token.documentKeyId);
398
+ const total = PAIRING_TOKEN_MAGIC.length + 1 + 4 + issuerBytes.length + PUBLIC_KEY_BYTES + KEY_BYTES + 4 + keyIdBytes.length + 8 + PAIRING_NONCE_BYTES;
399
+ const out = new Uint8Array(total);
400
+ let offset = 0;
401
+ out.set(PAIRING_TOKEN_MAGIC, offset);
402
+ offset += PAIRING_TOKEN_MAGIC.length;
403
+ out[offset] = token.version;
404
+ offset += 1;
405
+ const view = new DataView(out.buffer);
406
+ view.setUint32(offset, issuerBytes.length, false);
407
+ offset += 4;
408
+ out.set(issuerBytes, offset);
409
+ offset += issuerBytes.length;
410
+ out.set(token.issuerPublicKey, offset);
411
+ offset += PUBLIC_KEY_BYTES;
412
+ out.set(token.documentKey, offset);
413
+ offset += KEY_BYTES;
414
+ view.setUint32(offset, keyIdBytes.length, false);
415
+ offset += 4;
416
+ out.set(keyIdBytes, offset);
417
+ offset += keyIdBytes.length;
418
+ view.setBigUint64(offset, BigInt(token.expiresAt), false);
419
+ offset += 8;
420
+ out.set(token.nonce, offset);
421
+ offset += PAIRING_NONCE_BYTES;
422
+ return out;
423
+ }
424
+ function parsePairingToken(bytes) {
425
+ let offset = 0;
426
+ if (bytes.length < PAIRING_TOKEN_MAGIC.length) {
427
+ throw new PairingError(`Pairing token too short: ${bytes.length} bytes.`, "truncated");
428
+ }
429
+ for (let i = 0;i < PAIRING_TOKEN_MAGIC.length; i++) {
430
+ if (bytes[offset + i] !== PAIRING_TOKEN_MAGIC[i]) {
431
+ throw new PairingError(`Pairing token magic mismatch: not a Polly pairing token.`, "wrong-magic");
432
+ }
433
+ }
434
+ offset += PAIRING_TOKEN_MAGIC.length;
435
+ if (bytes.length < offset + 1) {
436
+ throw new PairingError("Pairing token truncated at version.", "truncated");
437
+ }
438
+ const version = bytes[offset];
439
+ offset += 1;
440
+ if (version !== PAIRING_TOKEN_VERSION) {
441
+ throw new PairingError(`Unknown pairing token version: ${version}. This Polly build supports version ${PAIRING_TOKEN_VERSION}.`, "unknown-version");
442
+ }
443
+ if (bytes.length < offset + 4) {
444
+ throw new PairingError("Pairing token truncated at issuer id length.", "truncated");
445
+ }
446
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
447
+ const issuerLen = view.getUint32(offset, false);
448
+ offset += 4;
449
+ if (bytes.length < offset + issuerLen) {
450
+ throw new PairingError("Pairing token truncated at issuer id.", "truncated");
451
+ }
452
+ const issuerPeerId = new TextDecoder().decode(bytes.subarray(offset, offset + issuerLen));
453
+ offset += issuerLen;
454
+ if (bytes.length < offset + PUBLIC_KEY_BYTES) {
455
+ throw new PairingError("Pairing token truncated at public key.", "truncated");
456
+ }
457
+ const issuerPublicKey = bytes.slice(offset, offset + PUBLIC_KEY_BYTES);
458
+ offset += PUBLIC_KEY_BYTES;
459
+ if (bytes.length < offset + KEY_BYTES) {
460
+ throw new PairingError("Pairing token truncated at document key.", "truncated");
461
+ }
462
+ const documentKey = bytes.slice(offset, offset + KEY_BYTES);
463
+ offset += KEY_BYTES;
464
+ if (bytes.length < offset + 4) {
465
+ throw new PairingError("Pairing token truncated at document key id length.", "truncated");
466
+ }
467
+ const keyIdLen = view.getUint32(offset, false);
468
+ offset += 4;
469
+ if (bytes.length < offset + keyIdLen) {
470
+ throw new PairingError("Pairing token truncated at document key id.", "truncated");
471
+ }
472
+ const documentKeyId = new TextDecoder().decode(bytes.subarray(offset, offset + keyIdLen));
473
+ offset += keyIdLen;
474
+ if (bytes.length < offset + 8) {
475
+ throw new PairingError("Pairing token truncated at expiry.", "truncated");
476
+ }
477
+ const expiresAtBig = view.getBigUint64(offset, false);
478
+ offset += 8;
479
+ const expiresAt = Number(expiresAtBig);
480
+ if (bytes.length < offset + PAIRING_NONCE_BYTES) {
481
+ throw new PairingError("Pairing token truncated at nonce.", "truncated");
482
+ }
483
+ const nonce = bytes.slice(offset, offset + PAIRING_NONCE_BYTES);
484
+ offset += PAIRING_NONCE_BYTES;
485
+ return {
486
+ version,
487
+ issuerPeerId,
488
+ issuerPublicKey,
489
+ documentKey,
490
+ documentKeyId,
491
+ expiresAt,
492
+ nonce
493
+ };
494
+ }
495
+ function encodePairingToken(token) {
496
+ const bytes = serialisePairingToken(token);
497
+ let binary = "";
498
+ for (const byte of bytes) {
499
+ binary += String.fromCharCode(byte);
500
+ }
501
+ return btoa(binary);
502
+ }
503
+ function decodePairingToken(encoded) {
504
+ let binary;
505
+ try {
506
+ binary = atob(encoded);
507
+ } catch {
508
+ throw new PairingError("Pairing token is not valid base64.", "wrong-magic");
509
+ }
510
+ const bytes = new Uint8Array(binary.length);
511
+ for (let i = 0;i < binary.length; i++) {
512
+ bytes[i] = binary.charCodeAt(i);
513
+ }
514
+ return parsePairingToken(bytes);
515
+ }
516
+ function validateForSerialisation(token) {
517
+ if (token.issuerPublicKey.length !== PUBLIC_KEY_BYTES) {
518
+ throw new PairingError(`Issuer public key must be ${PUBLIC_KEY_BYTES} bytes, got ${token.issuerPublicKey.length}.`, "invalid-public-key");
519
+ }
520
+ if (token.documentKey.length !== KEY_BYTES) {
521
+ throw new PairingError(`Document key must be ${KEY_BYTES} bytes, got ${token.documentKey.length}.`, "invalid-document-key");
522
+ }
523
+ if (token.nonce.length !== PAIRING_NONCE_BYTES) {
524
+ throw new PairingError(`Nonce must be ${PAIRING_NONCE_BYTES} bytes, got ${token.nonce.length}.`, "invalid-nonce");
525
+ }
526
+ }
527
+ function randomBytes(n) {
528
+ const out = new Uint8Array(n);
529
+ crypto.getRandomValues(out);
530
+ return out;
531
+ }
532
+
533
+ // src/mesh-node.ts
534
+ function fileKeyringStorage(path) {
535
+ return {
536
+ load: async () => {
537
+ try {
538
+ const text = await readFile(path, "utf-8");
539
+ return deserialiseKeyring(text);
540
+ } catch (err) {
541
+ if (isFileNotFound(err))
542
+ return null;
543
+ throw err;
544
+ }
545
+ },
546
+ save: async (keyring) => {
547
+ const text = serialiseKeyring(keyring);
548
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
549
+ await writeFile(tmp, text, { mode: 384 });
550
+ await rename(tmp, path);
551
+ }
552
+ };
553
+ }
554
+ async function bootstrapCliKeyring(options) {
555
+ const existing = await options.storage.load();
556
+ if (existing !== null)
557
+ return existing;
558
+ const identity = generateSigningKeyPair();
559
+ const keyring = {
560
+ identity,
561
+ knownPeers: new Map,
562
+ documentKeys: new Map,
563
+ revokedPeers: new Set
564
+ };
565
+ const promptStream = options.promptStream ?? process.stderr;
566
+ const fingerprint = fingerprintPublicKey(identity.publicKey);
567
+ promptStream.write([
568
+ "",
569
+ "Polly mesh-state CLI bootstrap",
570
+ "──────────────────────────────",
571
+ `Fingerprint: ${fingerprint}`,
572
+ "",
573
+ "Authorise this peer on a trusted device (open the pairing UI, enter",
574
+ "the fingerprint above, copy the generated token). Then paste the",
575
+ "pairing token below and press enter.",
576
+ ""
577
+ ].join(`
578
+ `));
579
+ const token = await readPairingTokenFromStdin({
580
+ promptStream,
581
+ inputStream: options.inputStream ?? process.stdin
582
+ });
583
+ const applyOptions = options.now ? { now: options.now } : {};
584
+ applyPairingToken(token, keyring, applyOptions);
585
+ await options.storage.save(keyring);
586
+ promptStream.write(`Pairing applied. Keyring saved.
587
+ `);
588
+ return keyring;
589
+ }
590
+ async function readPairingTokenFromStdin(options = {}) {
591
+ const rl = createInterface({
592
+ input: options.inputStream ?? process.stdin,
593
+ output: options.promptStream ?? process.stderr
594
+ });
595
+ try {
596
+ const line = await rl.question("pairing-token> ");
597
+ return decodePairingToken(line.trim());
598
+ } finally {
599
+ rl.close();
600
+ }
601
+ }
602
+ function fingerprintPublicKey(publicKey) {
603
+ const slice = publicKey.slice(0, 8);
604
+ const hex = Array.from(slice).map((b) => b.toString(16).padStart(2, "0")).join("");
605
+ return hex.match(/.{2}/g)?.join(":") ?? hex;
606
+ }
607
+ function isFileNotFound(err) {
608
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
609
+ }
610
+ export {
611
+ serialiseKeyring,
612
+ readPairingTokenFromStdin,
613
+ memoryKeyringStorage,
614
+ fileKeyringStorage,
615
+ deserialiseKeyring,
616
+ bootstrapCliKeyring
617
+ };
618
+
619
+ //# debugId=79E90F8B0F70449064756E2164756E21