@weaveprotocol/cli 0.2.0 → 0.2.2

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 (3) hide show
  1. package/README.md +18 -1
  2. package/dist/weave.mjs +847 -108
  3. package/package.json +11 -6
package/dist/weave.mjs CHANGED
@@ -6,7 +6,7 @@ import path6 from "node:path";
6
6
  import { createInterface as createInterface2 } from "node:readline";
7
7
  import { parseArgs } from "node:util";
8
8
 
9
- // ../src/utils/encoding.ts
9
+ // ../core/src/utils/encoding.ts
10
10
  var encoder = new TextEncoder();
11
11
  var decoder = new TextDecoder();
12
12
  function utf8Encode(str2) {
@@ -46,7 +46,7 @@ function bytesToHex(bytes) {
46
46
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
47
47
  }
48
48
 
49
- // ../node_modules/@noble/hashes/_u64.js
49
+ // ../../node_modules/@noble/hashes/_u64.js
50
50
  var fromNumH = (n) => n / 2 ** 32 | 0;
51
51
  var fromNumL = (n) => n >>> 0;
52
52
  function setU64FromNum(view, byteOffset, n, isLE) {
@@ -56,7 +56,7 @@ function setU64FromNum(view, byteOffset, n, isLE) {
56
56
  view.setUint32(byteOffset + 4, isLE ? h : l, isLE);
57
57
  }
58
58
 
59
- // ../node_modules/@noble/hashes/utils.js
59
+ // ../../node_modules/@noble/hashes/utils.js
60
60
  function isBytes(a) {
61
61
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
62
62
  }
@@ -221,7 +221,7 @@ var oidNist = (suffix) => ({
221
221
  oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
222
222
  });
223
223
 
224
- // ../node_modules/@noble/hashes/_md.js
224
+ // ../../node_modules/@noble/hashes/_md.js
225
225
  function Chi(a, b, c) {
226
226
  return a & b ^ ~a & c;
227
227
  }
@@ -334,7 +334,7 @@ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
334
334
  1541459225
335
335
  ]);
336
336
 
337
- // ../node_modules/@noble/hashes/sha2.js
337
+ // ../../node_modules/@noble/hashes/sha2.js
338
338
  var SHA256_K = /* @__PURE__ */ Uint32Array.from([
339
339
  1116352408,
340
340
  1899447441,
@@ -499,7 +499,7 @@ var sha256 = /* @__PURE__ */ createHasher(
499
499
  /* @__PURE__ */ oidNist(1)
500
500
  );
501
501
 
502
- // ../node_modules/@noble/curves/utils.js
502
+ // ../../node_modules/@noble/curves/utils.js
503
503
  function aarray(item, title, inner = () => {
504
504
  }) {
505
505
  if (!Array.isArray(item))
@@ -681,7 +681,7 @@ function validateObject(object, fields = {}, optFields = {}, title = "object") {
681
681
  iter(optFields, true);
682
682
  }
683
683
 
684
- // ../node_modules/@noble/curves/abstract/modular.js
684
+ // ../../node_modules/@noble/curves/abstract/modular.js
685
685
  var _0n2 = /* @__PURE__ */ BigInt(0);
686
686
  var _1n2 = /* @__PURE__ */ BigInt(1);
687
687
  var _2n = /* @__PURE__ */ BigInt(2);
@@ -1131,7 +1131,7 @@ function mapHashToField(key3, fieldOrder, isLE = false) {
1131
1131
  return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
1132
1132
  }
1133
1133
 
1134
- // ../node_modules/@noble/curves/abstract/curve.js
1134
+ // ../../node_modules/@noble/curves/abstract/curve.js
1135
1135
  var _0n3 = /* @__PURE__ */ BigInt(0);
1136
1136
  var _1n3 = /* @__PURE__ */ BigInt(1);
1137
1137
  var _4n2 = /* @__PURE__ */ BigInt(4);
@@ -1518,7 +1518,7 @@ function createKeygen(randomSecretKey, getPublicKey) {
1518
1518
  };
1519
1519
  }
1520
1520
 
1521
- // ../node_modules/@noble/hashes/hmac.js
1521
+ // ../../node_modules/@noble/hashes/hmac.js
1522
1522
  var _HMAC = class {
1523
1523
  oHash;
1524
1524
  iHash;
@@ -1595,7 +1595,7 @@ var hmac = /* @__PURE__ */ (() => {
1595
1595
  return hmac_;
1596
1596
  })();
1597
1597
 
1598
- // ../node_modules/@noble/curves/abstract/der.js
1598
+ // ../../node_modules/@noble/curves/abstract/der.js
1599
1599
  var _0n4 = /* @__PURE__ */ BigInt(0);
1600
1600
  var DERErr = class extends Error {
1601
1601
  constructor(m = "") {
@@ -1722,7 +1722,7 @@ var DER = /* @__PURE__ */ (() => {
1722
1722
  return Object.freeze(_DER);
1723
1723
  })();
1724
1724
 
1725
- // ../node_modules/@noble/curves/abstract/weierstrass.js
1725
+ // ../../node_modules/@noble/curves/abstract/weierstrass.js
1726
1726
  var divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n2) / den;
1727
1727
  function _splitEndoScalar(k, basis, n) {
1728
1728
  aInRange("scalar", k, _0n5, n);
@@ -2499,7 +2499,7 @@ function ecdsa(Point, hash2, ecdsaOpts = {}) {
2499
2499
  const sig = drbg(seed, k2sig);
2500
2500
  return sig.toBytes(opts2.format);
2501
2501
  }
2502
- function verify(signature, message, publicKey, opts2 = {}) {
2502
+ function verify2(signature, message, publicKey, opts2 = {}) {
2503
2503
  const { lowS, prehash, format: format2 } = validateSigOpts(opts2, defaultSigOpts);
2504
2504
  publicKey = abytes2(publicKey, void 0, "publicKey");
2505
2505
  message = validateMsgAndHash(message, prehash);
@@ -2547,14 +2547,14 @@ function ecdsa(Point, hash2, ecdsaOpts = {}) {
2547
2547
  lengths,
2548
2548
  Point,
2549
2549
  sign,
2550
- verify,
2550
+ verify: verify2,
2551
2551
  recoverPublicKey,
2552
2552
  Signature,
2553
2553
  hash: hash_
2554
2554
  });
2555
2555
  }
2556
2556
 
2557
- // ../node_modules/@noble/curves/nist.js
2557
+ // ../../node_modules/@noble/curves/nist.js
2558
2558
  var p256_CURVE = /* @__PURE__ */ (() => ({
2559
2559
  p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
2560
2560
  n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
@@ -2567,7 +2567,7 @@ var p256_CURVE = /* @__PURE__ */ (() => ({
2567
2567
  var p256_Point = /* @__PURE__ */ weierstrass(p256_CURVE);
2568
2568
  var p256 = /* @__PURE__ */ ecdsa(p256_Point, sha256);
2569
2569
 
2570
- // ../src/identity/crypto-p256.ts
2570
+ // ../core/src/identity/crypto-p256.ts
2571
2571
  var P256_SEED_BYTES = 48;
2572
2572
  var P256_KEY_INFO = new TextEncoder().encode("weave/p256-identity-key/v1");
2573
2573
  async function hkdf(ikm, info, length) {
@@ -2679,12 +2679,12 @@ function createP256Provider() {
2679
2679
  });
2680
2680
  }
2681
2681
 
2682
- // ../src/utils/errors.ts
2682
+ // ../core/src/utils/errors.ts
2683
2683
  function protocolError(code, message, hint) {
2684
2684
  return Object.assign(new Error(message), { code, ...hint ? { hint } : {} });
2685
2685
  }
2686
2686
 
2687
- // ../src/identity/webauthn.ts
2687
+ // ../core/src/identity/webauthn.ts
2688
2688
  var PRF_SALT = new TextEncoder().encode("weave-protocol-key-v1");
2689
2689
  async function registerPasskey(options) {
2690
2690
  if (!globalThis.navigator?.credentials) {
@@ -2780,10 +2780,10 @@ async function authenticatePasskey(credentialId, options) {
2780
2780
  });
2781
2781
  }
2782
2782
 
2783
- // ../src/identity/passkey-diagnostics.ts
2783
+ // ../core/src/identity/passkey-diagnostics.ts
2784
2784
  var PRF_SALT2 = new TextEncoder().encode("weave-protocol-key-v1");
2785
2785
 
2786
- // ../src/identity/keys.ts
2786
+ // ../core/src/identity/keys.ts
2787
2787
  async function deriveKeyPair(seed, provider) {
2788
2788
  const keyPair = await provider.deriveKeyPairFromSeed(seed);
2789
2789
  const publicKeyBytes = await provider.exportPublicKey(keyPair.publicKey);
@@ -2815,7 +2815,7 @@ async function deriveKeyFromPassword(password, salt, provider) {
2815
2815
  return deriveKeyPair(new Uint8Array(derivedBits), provider);
2816
2816
  }
2817
2817
 
2818
- // ../src/identity/recovery-code.ts
2818
+ // ../core/src/identity/recovery-code.ts
2819
2819
  var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
2820
2820
  var SUBSTITUTIONS = { O: "0", I: "1", L: "1", U: "V" };
2821
2821
  var SEED_BYTES = 16;
@@ -2876,7 +2876,7 @@ function encodeBase32(bytes) {
2876
2876
  return out;
2877
2877
  }
2878
2878
 
2879
- // ../node_modules/@scure/base/index.js
2879
+ // ../../node_modules/@scure/base/index.js
2880
2880
  var freeze = (fn) => Object.freeze(fn());
2881
2881
  function isBytes3(a) {
2882
2882
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
@@ -3082,7 +3082,7 @@ var radix58 = /* @__PURE__ */ radixBaseN(58, B58_GROUP);
3082
3082
  var genBase58 = (abc) => genBaseN(radix58, abc);
3083
3083
  var base58 = /* @__PURE__ */ freeze(() => genBase58("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"));
3084
3084
 
3085
- // ../src/identity/did.ts
3085
+ // ../core/src/identity/did.ts
3086
3086
  var P256_MULTICODEC = new Uint8Array([128, 36]);
3087
3087
  function publicKeyToDid(publicKeyBytes, multicodecPrefix) {
3088
3088
  const prefixedKey = concatBytes(multicodecPrefix, publicKeyBytes);
@@ -3103,7 +3103,7 @@ function didToPublicKey(did) {
3103
3103
  return { multicodecPrefix, publicKeyBytes };
3104
3104
  }
3105
3105
 
3106
- // ../src/identity/identity-manager.ts
3106
+ // ../core/src/identity/identity-manager.ts
3107
3107
  function prfUnsupported() {
3108
3108
  return protocolError(
3109
3109
  "PRF_UNSUPPORTED",
@@ -3194,7 +3194,7 @@ function createIdentityManager(config) {
3194
3194
  });
3195
3195
  }
3196
3196
 
3197
- // ../src/storage/folder-adapter.ts
3197
+ // ../core/src/storage/folder-adapter.ts
3198
3198
  var EXPRESSION_SUFFIX = ".json";
3199
3199
  function encodeKey(key3) {
3200
3200
  let out = "";
@@ -3365,7 +3365,7 @@ async function createFolderAdapter(root, path7) {
3365
3365
  });
3366
3366
  }
3367
3367
 
3368
- // ../src/identity/folder-account.ts
3368
+ // ../core/src/identity/folder-account.ts
3369
3369
  function createVault(params) {
3370
3370
  return {
3371
3371
  version: 2,
@@ -3376,7 +3376,7 @@ function createVault(params) {
3376
3376
  };
3377
3377
  }
3378
3378
 
3379
- // ../src/identity/account-store.ts
3379
+ // ../core/src/identity/account-store.ts
3380
3380
  var LIST_FILE = "accounts.json";
3381
3381
  var ACCOUNTS_DIR = "accounts";
3382
3382
  var VAULT_FILE = "account.json";
@@ -3491,7 +3491,7 @@ function createFolderAccountStore(dir) {
3491
3491
  });
3492
3492
  }
3493
3493
 
3494
- // ../src/identity/account-vault.ts
3494
+ // ../core/src/identity/account-vault.ts
3495
3495
  var IV_BYTES = 12;
3496
3496
  var SALT_BYTES = 16;
3497
3497
  var PASSPHRASE_ITERATIONS = 6e5;
@@ -3601,15 +3601,18 @@ async function deriveVaultKey(seed) {
3601
3601
  ["encrypt", "decrypt"]
3602
3602
  );
3603
3603
  }
3604
+ var CLI_PASSPHRASE_LABEL = "CLI passphrase";
3604
3605
  function withWrap(vault, wrap) {
3605
3606
  const superseded = (existing) => existing.kind === "device" && wrap.kind === "device" && existing.rpId === wrap.rpId;
3606
3607
  return { ...vault, wraps: [...vault.wraps.filter((existing) => !superseded(existing)), wrap] };
3607
3608
  }
3608
3609
 
3609
- // ../src/identity/contact-key.ts
3610
+ // ../core/src/identity/contact-key.ts
3610
3611
  var CONTACT_KEY_INFO = "weave/p256-contact-key/v1";
3611
3612
  var SEAL_INFO = "weave/contact-seal/v1";
3612
3613
  var MEMBER_KEY_INFO = "weave/p256-member-key/v1";
3614
+ var DOOR_KEY_INFO = "weave/p256-door-key/v1";
3615
+ var DOOR_SIGN_KEY_INFO = "weave/p256-door-sign-key/v1";
3613
3616
  var P256_SEED_BYTES2 = 48;
3614
3617
  var POINT_BYTES = 65;
3615
3618
  var IV_BYTES2 = 12;
@@ -3628,6 +3631,39 @@ async function deriveContactKeyBytes(seed) {
3628
3631
  async function deriveMemberKeyBytes(accountKey, spaceId) {
3629
3632
  return p256.utils.randomSecretKey(await hkdf2(accountKey, `${MEMBER_KEY_INFO}|${spaceId}`, P256_SEED_BYTES2));
3630
3633
  }
3634
+ async function deriveDoorKeyBytes(contactKey, doorId) {
3635
+ return p256.utils.randomSecretKey(await hkdf2(contactKey, `${DOOR_KEY_INFO}|${doorId}`, P256_SEED_BYTES2));
3636
+ }
3637
+ async function deriveDoorSignKeyBytes(contactKey, doorId) {
3638
+ return p256.utils.randomSecretKey(await hkdf2(contactKey, `${DOOR_SIGN_KEY_INFO}|${doorId}`, P256_SEED_BYTES2));
3639
+ }
3640
+ async function signWithScalar(secret, data) {
3641
+ const point = p256.getPublicKey(secret, false);
3642
+ const key3 = await globalThis.crypto.subtle.importKey(
3643
+ "jwk",
3644
+ {
3645
+ kty: "EC",
3646
+ crv: "P-256",
3647
+ x: base64UrlEncode(point.subarray(1, 33)),
3648
+ y: base64UrlEncode(point.subarray(33, 65)),
3649
+ d: base64UrlEncode(secret),
3650
+ ext: false
3651
+ },
3652
+ { name: "ECDSA", namedCurve: "P-256" },
3653
+ false,
3654
+ ["sign"]
3655
+ );
3656
+ return base64UrlEncode(new Uint8Array(await globalThis.crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, key3, data)));
3657
+ }
3658
+ async function verifyWithPoint(publicKey, data, signature) {
3659
+ try {
3660
+ const point = p256.Point.fromBytes(base64UrlDecode(publicKey)).toBytes(false);
3661
+ const key3 = await globalThis.crypto.subtle.importKey("raw", point, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
3662
+ return await globalThis.crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, key3, base64UrlDecode(signature), data);
3663
+ } catch {
3664
+ return false;
3665
+ }
3666
+ }
3631
3667
  function contactPublicKey(secret) {
3632
3668
  return base64UrlEncode(p256.getPublicKey(secret, true));
3633
3669
  }
@@ -3714,7 +3750,7 @@ async function openSealed(privateKey, sealed, context) {
3714
3750
  }
3715
3751
  }
3716
3752
 
3717
- // ../src/utils/hash.ts
3753
+ // ../core/src/utils/hash.ts
3718
3754
  async function sha2562(data) {
3719
3755
  const buffer = await globalThis.crypto.subtle.digest("SHA-256", data);
3720
3756
  return new Uint8Array(buffer);
@@ -3765,7 +3801,7 @@ async function cidFromBytes(data) {
3765
3801
  return "b" + base32Encode(hashBytes);
3766
3802
  }
3767
3803
 
3768
- // ../src/records/links.ts
3804
+ // ../core/src/records/links.ts
3769
3805
  var LINK_REL_PATTERN = /^[a-z][a-zA-Z0-9]{0,63}$/;
3770
3806
  var MAX_LINKS = 32;
3771
3807
  function checkLinks(links2) {
@@ -3783,7 +3819,7 @@ function checkLinks(links2) {
3783
3819
  return null;
3784
3820
  }
3785
3821
 
3786
- // ../src/records/version.ts
3822
+ // ../core/src/records/version.ts
3787
3823
  var MAX_SEEN = 64;
3788
3824
  var RECORD_KEY_PATTERN = /^[a-z0-9:._-]{1,128}$/;
3789
3825
  function newRecordKey() {
@@ -3824,7 +3860,7 @@ function checkVersionShape(expression) {
3824
3860
  return null;
3825
3861
  }
3826
3862
 
3827
- // ../src/schema/expression.ts
3863
+ // ../core/src/schema/expression.ts
3828
3864
  function canonicalize(obj) {
3829
3865
  if (obj === null || typeof obj !== "object") {
3830
3866
  return JSON.stringify(obj) ?? "null";
@@ -3869,7 +3905,7 @@ async function getExpressionId(expr) {
3869
3905
  return await cidFromBytes(bytes);
3870
3906
  }
3871
3907
 
3872
- // ../src/identity/ucan.ts
3908
+ // ../core/src/identity/ucan.ts
3873
3909
  var UCAN_CLOCK_SKEW_SECONDS = 300;
3874
3910
  async function issueUCAN(options, provider) {
3875
3911
  const header = { alg: "ES256", typ: "JWT", ucv: "0.10.0" };
@@ -4060,7 +4096,7 @@ async function resolveDelegationRoot(encoded, resolveProof, provider, options =
4060
4096
  return { valid: false, rootDid: null, capabilities: [], audience: null, reason: `Delegation chain deeper than ${MAX_CHAIN_DEPTH} links` };
4061
4097
  }
4062
4098
 
4063
- // ../src/identity/root-signer.ts
4099
+ // ../core/src/identity/root-signer.ts
4064
4100
  function createLocalRootSigner(identity, provider) {
4065
4101
  return Object.freeze({
4066
4102
  did: identity.did,
@@ -4080,7 +4116,7 @@ function createLocalRootSigner(identity, provider) {
4080
4116
  });
4081
4117
  }
4082
4118
 
4083
- // ../src/identity/pairing.ts
4119
+ // ../core/src/identity/pairing.ts
4084
4120
  var ROOM_PREFIX = utf8Encode("weave-pairing-room-v1");
4085
4121
  var PAIRING_KEY_INFO = utf8Encode("weave-pairing-key-v1");
4086
4122
  var IV_BYTES3 = 12;
@@ -4112,7 +4148,7 @@ async function openPairingPayload(sealed, key3) {
4112
4148
  }
4113
4149
  }
4114
4150
 
4115
- // ../src/identity/agent-note.ts
4151
+ // ../core/src/identity/agent-note.ts
4116
4152
  var AGENT_FACT = Object.freeze({ weave: "agent" });
4117
4153
  function isAgentNote(encoded) {
4118
4154
  if (!encoded) return false;
@@ -4124,7 +4160,7 @@ function isAgentNote(encoded) {
4124
4160
  }
4125
4161
  }
4126
4162
 
4127
- // ../src/schema/schema-engine.ts
4163
+ // ../core/src/schema/schema-engine.ts
4128
4164
  function createSchemaEngine() {
4129
4165
  const collections = /* @__PURE__ */ new Map();
4130
4166
  return Object.freeze({
@@ -4157,7 +4193,7 @@ function createSchemaEngine() {
4157
4193
  });
4158
4194
  }
4159
4195
 
4160
- // ../src/schema/signer.ts
4196
+ // ../core/src/schema/signer.ts
4161
4197
  function createSigner(provider) {
4162
4198
  return Object.freeze({
4163
4199
  async sign(payload, privateKey) {
@@ -4183,7 +4219,7 @@ function createSigner(provider) {
4183
4219
  });
4184
4220
  }
4185
4221
 
4186
- // ../src/sync/negentropy.ts
4222
+ // ../core/src/sync/negentropy.ts
4187
4223
  var NEGENTROPY_VERSION = 97;
4188
4224
  var ID_SIZE = 32;
4189
4225
  var FINGERPRINT_SIZE = 16;
@@ -4473,7 +4509,7 @@ function createReconciler(set, options) {
4473
4509
  }
4474
4510
  var key = (id) => String.fromCharCode(...id);
4475
4511
 
4476
- // ../src/storage/storage-provider.ts
4512
+ // ../core/src/storage/storage-provider.ts
4477
4513
  var CURRENT_PREFIX = "r/";
4478
4514
  var GENESIS_PREFIX = "g/";
4479
4515
  var HISTORY_PREFIX = "h/";
@@ -4698,7 +4734,7 @@ function createStorageProvider(adapter) {
4698
4734
  });
4699
4735
  }
4700
4736
 
4701
- // ../src/storage/encrypted-adapter.ts
4737
+ // ../core/src/storage/encrypted-adapter.ts
4702
4738
  var MAGIC = new Uint8Array([119, 101, 97, 118, 101, 101, 2, 0]);
4703
4739
  var IV_BYTES4 = 12;
4704
4740
  var DEFAULT_ENCRYPTED_PREFIXES = ["space:", "spacekey:", "spaceinvite:", "spacerole:"];
@@ -4762,7 +4798,7 @@ function createEncryptedAdapter(inner, key3, options) {
4762
4798
  });
4763
4799
  }
4764
4800
 
4765
- // ../src/storage/folder-reconcile.ts
4801
+ // ../core/src/storage/folder-reconcile.ts
4766
4802
  async function reconcileFolder(storage, adapter, accept = async () => true) {
4767
4803
  const disk = await adapter.reload();
4768
4804
  if (disk.added.length > 0 || disk.removed.length > 0) storage.invalidate();
@@ -4791,7 +4827,7 @@ async function reconcileFolder(storage, adapter, accept = async () => true) {
4791
4827
  };
4792
4828
  }
4793
4829
 
4794
- // ../src/privacy/space-encryption.ts
4830
+ // ../core/src/privacy/space-encryption.ts
4795
4831
  async function generateSpaceKey() {
4796
4832
  const key3 = await globalThis.crypto.subtle.generateKey(
4797
4833
  { name: "AES-GCM", length: 256 },
@@ -4877,7 +4913,7 @@ async function openWith(spaceKey, sealed, context) {
4877
4913
  }
4878
4914
  }
4879
4915
 
4880
- // ../src/space/roles.ts
4916
+ // ../core/src/space/roles.ts
4881
4917
  var MANAGE = "manage";
4882
4918
  var INVITE = "invite";
4883
4919
  var DEFINE = "define";
@@ -5296,7 +5332,7 @@ function replayAccess(genesis, events) {
5296
5332
  }
5297
5333
  }
5298
5334
 
5299
- // ../src/space/space-access.ts
5335
+ // ../core/src/space/space-access.ts
5300
5336
  var READ_INFO = "weave/space-read/v1";
5301
5337
  var INVITE_INFO = "weave/space-invite/v1";
5302
5338
  function generateInviteSecret() {
@@ -5400,7 +5436,7 @@ async function verifyInvite(spaceId, did, inviteDid, signature, provider) {
5400
5436
  }
5401
5437
  }
5402
5438
 
5403
- // ../src/space/presets.ts
5439
+ // ../core/src/space/presets.ts
5404
5440
  var solo = Object.freeze({
5405
5441
  roles: Object.freeze([{ name: "owner", title: "Owner", rank: 100, permissions: ["*"] }]),
5406
5442
  creatorRole: "owner"
@@ -5422,7 +5458,7 @@ var community = Object.freeze({
5422
5458
  });
5423
5459
  var rolePresets = Object.freeze({ solo, team, community });
5424
5460
 
5425
- // ../src/space/space-manager.ts
5461
+ // ../core/src/space/space-manager.ts
5426
5462
  var SPACE_PREFIX = "space:";
5427
5463
  var KEY_PREFIX = "spacekey:";
5428
5464
  var INVITE_PREFIX = "spaceinvite:";
@@ -5600,7 +5636,7 @@ function parseSpaceInvite(invite) {
5600
5636
  }
5601
5637
  }
5602
5638
 
5603
- // ../src/space/account-registry.ts
5639
+ // ../core/src/space/account-registry.ts
5604
5640
  var MEMBERSHIP_COLLECTION = "sys.joined";
5605
5641
  var PROFILE_COLLECTION = "sys.profile";
5606
5642
  var PROFILE_KEY = "profile";
@@ -5652,7 +5688,7 @@ async function deriveAccountSpace(accountKey, owner, label, name, provider) {
5652
5688
  return { space: space2, key: key3, invite: null, role: solo.creatorRole };
5653
5689
  }
5654
5690
 
5655
- // ../node_modules/@cfworker/json-schema/dist/esm/deep-compare-strict.js
5691
+ // ../../node_modules/@cfworker/json-schema/dist/esm/deep-compare-strict.js
5656
5692
  function deepCompareStrict(a, b) {
5657
5693
  const typeofa = typeof a;
5658
5694
  if (typeofa !== typeof b) {
@@ -5693,7 +5729,7 @@ function deepCompareStrict(a, b) {
5693
5729
  return a === b;
5694
5730
  }
5695
5731
 
5696
- // ../node_modules/@cfworker/json-schema/dist/esm/pointer.js
5732
+ // ../../node_modules/@cfworker/json-schema/dist/esm/pointer.js
5697
5733
  function encodePointer(p) {
5698
5734
  return encodeURI(escapePointer(p));
5699
5735
  }
@@ -5701,7 +5737,7 @@ function escapePointer(p) {
5701
5737
  return p.replace(/~/g, "~0").replace(/\//g, "~1");
5702
5738
  }
5703
5739
 
5704
- // ../node_modules/@cfworker/json-schema/dist/esm/dereference.js
5740
+ // ../../node_modules/@cfworker/json-schema/dist/esm/dereference.js
5705
5741
  var schemaArrayKeyword = {
5706
5742
  prefixItems: true,
5707
5743
  items: true,
@@ -5822,7 +5858,7 @@ function dereference(schema, lookup = /* @__PURE__ */ Object.create(null), baseU
5822
5858
  return lookup;
5823
5859
  }
5824
5860
 
5825
- // ../node_modules/@cfworker/json-schema/dist/esm/format.js
5861
+ // ../../node_modules/@cfworker/json-schema/dist/esm/format.js
5826
5862
  var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
5827
5863
  var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
5828
5864
  var TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i;
@@ -5915,7 +5951,7 @@ function regex(str2) {
5915
5951
  }
5916
5952
  }
5917
5953
 
5918
- // ../node_modules/@cfworker/json-schema/dist/esm/types.js
5954
+ // ../../node_modules/@cfworker/json-schema/dist/esm/types.js
5919
5955
  var OutputFormat;
5920
5956
  (function(OutputFormat2) {
5921
5957
  OutputFormat2[OutputFormat2["Flag"] = 1] = "Flag";
@@ -5923,7 +5959,7 @@ var OutputFormat;
5923
5959
  OutputFormat2[OutputFormat2["Detailed"] = 4] = "Detailed";
5924
5960
  })(OutputFormat || (OutputFormat = {}));
5925
5961
 
5926
- // ../node_modules/@cfworker/json-schema/dist/esm/ucs2-length.js
5962
+ // ../../node_modules/@cfworker/json-schema/dist/esm/ucs2-length.js
5927
5963
  function ucs2length(s) {
5928
5964
  let result = 0;
5929
5965
  let length = s.length;
@@ -5942,7 +5978,7 @@ function ucs2length(s) {
5942
5978
  return result;
5943
5979
  }
5944
5980
 
5945
- // ../node_modules/@cfworker/json-schema/dist/esm/validate.js
5981
+ // ../../node_modules/@cfworker/json-schema/dist/esm/validate.js
5946
5982
  function validate(instance, schema, draft = "2019-09", lookup = dereference(schema), shortCircuit = true, recursiveAnchor = null, instanceLocation = "#", schemaLocation = "#", evaluated = /* @__PURE__ */ Object.create(null)) {
5947
5983
  if (schema === true) {
5948
5984
  return { valid: true, errors: [] };
@@ -6700,7 +6736,7 @@ Known schemas:
6700
6736
  return { valid: errors.length === 0, errors };
6701
6737
  }
6702
6738
 
6703
- // ../node_modules/@cfworker/json-schema/dist/esm/validator.js
6739
+ // ../../node_modules/@cfworker/json-schema/dist/esm/validator.js
6704
6740
  var Validator = class {
6705
6741
  schema;
6706
6742
  draft;
@@ -6723,7 +6759,7 @@ var Validator = class {
6723
6759
  }
6724
6760
  };
6725
6761
 
6726
- // ../src/records/rules.ts
6762
+ // ../core/src/records/rules.ts
6727
6763
  var PERMISSION_PATTERN = /^[a-z][a-zA-Z0-9]{0,39}$/;
6728
6764
  var permissionName = (collection2, permission) => `${collection2}/${permission}`;
6729
6765
  var isWho = (w) => w === "member" || w === "creator" || typeof w === "string" && w.startsWith("can:") && PERMISSION_PATTERN.test(w.slice(4));
@@ -6793,7 +6829,7 @@ function changedFixedField(fixed, first, next) {
6793
6829
  return null;
6794
6830
  }
6795
6831
 
6796
- // ../src/records/topics.ts
6832
+ // ../core/src/records/topics.ts
6797
6833
  var MAX_TOPICS = 8;
6798
6834
  var MAX_TAGS = 64;
6799
6835
  var TAG_BYTES = 16;
@@ -6845,7 +6881,7 @@ function sameTags(a, b) {
6845
6881
  return left.length === right.length && left.every((tag, i) => tag === right[i]);
6846
6882
  }
6847
6883
 
6848
- // ../src/schema/collection-def.ts
6884
+ // ../core/src/schema/collection-def.ts
6849
6885
  function toJsonSchema(schema) {
6850
6886
  const standard = schema["~standard"];
6851
6887
  if (!standard) return schema;
@@ -7006,7 +7042,7 @@ function validateJsonSchema(schema, value) {
7006
7042
  }));
7007
7043
  }
7008
7044
 
7009
- // ../src/utils/events.ts
7045
+ // ../core/src/utils/events.ts
7010
7046
  function createEmitter() {
7011
7047
  const listeners = /* @__PURE__ */ new Map();
7012
7048
  return {
@@ -7028,7 +7064,7 @@ function createEmitter() {
7028
7064
  };
7029
7065
  }
7030
7066
 
7031
- // ../src/network/signaling.ts
7067
+ // ../core/src/network/signaling.ts
7032
7068
  var SIGNAL_KINDS = /* @__PURE__ */ new Set(["offer", "answer", "candidate"]);
7033
7069
  function iceFrom(payload) {
7034
7070
  const { servers, expiresAt } = payload ?? {};
@@ -7145,7 +7181,7 @@ function createSignalingClient(url, did) {
7145
7181
  });
7146
7182
  }
7147
7183
 
7148
- // ../src/network/multi-signaling.ts
7184
+ // ../core/src/network/multi-signaling.ts
7149
7185
  function createMultiSignalingClient(urls, did) {
7150
7186
  if (urls.length === 0) {
7151
7187
  throw new Error("At least one relay is needed to introduce peers.");
@@ -7279,7 +7315,7 @@ function createMultiSignalingClient(urls, did) {
7279
7315
  });
7280
7316
  }
7281
7317
 
7282
- // ../src/network/introductions.ts
7318
+ // ../core/src/network/introductions.ts
7283
7319
  var PEERS_MESSAGE = "__peers";
7284
7320
  var SIGNAL_MESSAGE = "__signal";
7285
7321
  var AUTH_HELLO_MESSAGE = "__auth-hello";
@@ -7318,7 +7354,7 @@ function signalId() {
7318
7354
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
7319
7355
  }
7320
7356
 
7321
- // ../src/network/rtc-transport.ts
7357
+ // ../core/src/network/rtc-transport.ts
7322
7358
  function fingerprintOf2(description) {
7323
7359
  const match = description?.sdp?.match(/^a=fingerprint:\s*(\S+)\s+(\S+)/im);
7324
7360
  return match ? `${match[1].toLowerCase()} ${match[2].toUpperCase()}` : null;
@@ -7454,7 +7490,7 @@ function createRTCTransport(config) {
7454
7490
  });
7455
7491
  }
7456
7492
 
7457
- // ../src/network/peer-auth.ts
7493
+ // ../core/src/network/peer-auth.ts
7458
7494
  function readAccessOf(read) {
7459
7495
  if (!read) return null;
7460
7496
  if ("current" in read) return read;
@@ -7540,7 +7576,7 @@ function createMeshAuth(spaceId, session, read, provider) {
7540
7576
  });
7541
7577
  }
7542
7578
 
7543
- // ../src/network/ws-transport.ts
7579
+ // ../core/src/network/ws-transport.ts
7544
7580
  function parseFrame(data, type) {
7545
7581
  if (typeof data !== "string") return null;
7546
7582
  try {
@@ -7686,7 +7722,7 @@ function createWebSocketTransport(config) {
7686
7722
  });
7687
7723
  }
7688
7724
 
7689
- // ../src/network/mesh.ts
7725
+ // ../core/src/network/mesh.ts
7690
7726
  var ICE_REFRESH_MS = 10 * 6e4;
7691
7727
  var ICE_WAIT_MS = 1500;
7692
7728
  function createMesh(config) {
@@ -7967,7 +8003,7 @@ function createMesh(config) {
7967
8003
  };
7968
8004
  }
7969
8005
 
7970
- // ../src/network/network-manager.ts
8006
+ // ../core/src/network/network-manager.ts
7971
8007
  function createNetworkManager(config) {
7972
8008
  const transport = config.createTransport();
7973
8009
  const peers = /* @__PURE__ */ new Map();
@@ -8013,7 +8049,7 @@ function createNetworkManager(config) {
8013
8049
  });
8014
8050
  }
8015
8051
 
8016
- // ../src/sync/sync-messages.ts
8052
+ // ../core/src/sync/sync-messages.ts
8017
8053
  var SYNC_PROTOCOL_VERSION = 4;
8018
8054
  function parseSyncMessage(value) {
8019
8055
  const message = value;
@@ -8021,7 +8057,7 @@ function parseSyncMessage(value) {
8021
8057
  return message;
8022
8058
  }
8023
8059
 
8024
- // ../src/sync/sync-engine.ts
8060
+ // ../core/src/sync/sync-engine.ts
8025
8061
  var MAX_WAITING = 1e3;
8026
8062
  var MAX_REFUSED = 1e4;
8027
8063
  var holdsCollection = (holds2, collection2) => collection2.startsWith("sys.") || holds2 === "all" || holds2.has(collection2);
@@ -8309,7 +8345,7 @@ ${id}`;
8309
8345
  };
8310
8346
  }
8311
8347
 
8312
- // ../src/validation/crypto-gate.ts
8348
+ // ../core/src/validation/crypto-gate.ts
8313
8349
  function createCryptoGate(provider) {
8314
8350
  return {
8315
8351
  async validate(expression, resolvePublicKey) {
@@ -8336,7 +8372,7 @@ function createCryptoGate(provider) {
8336
8372
  };
8337
8373
  }
8338
8374
 
8339
- // ../src/validation/structural-gate.ts
8375
+ // ../core/src/validation/structural-gate.ts
8340
8376
  function isEncryptedBody(body) {
8341
8377
  if (!body || typeof body !== "object") return false;
8342
8378
  const envelope = body;
@@ -8370,7 +8406,7 @@ function createStructuralGate(schemaEngine, options = {}) {
8370
8406
  };
8371
8407
  }
8372
8408
 
8373
- // ../src/validation/stateful-gate.ts
8409
+ // ../core/src/validation/stateful-gate.ts
8374
8410
  function createStatefulGate() {
8375
8411
  const rules = /* @__PURE__ */ new Map();
8376
8412
  return {
@@ -8416,7 +8452,7 @@ function createStatefulGate() {
8416
8452
  };
8417
8453
  }
8418
8454
 
8419
- // ../src/validation/capability-gate.ts
8455
+ // ../core/src/validation/capability-gate.ts
8420
8456
  var fail = (reason) => ({ passed: false, gate: "capability", reason });
8421
8457
  var MAX_CLOCK_SKEW_SECONDS = 300;
8422
8458
  function createCapabilityGate(config) {
@@ -8459,7 +8495,7 @@ function createCapabilityGate(config) {
8459
8495
  };
8460
8496
  }
8461
8497
 
8462
- // ../src/validation/validation-engine.ts
8498
+ // ../core/src/validation/validation-engine.ts
8463
8499
  function createValidationEngine(config) {
8464
8500
  const { cryptoGate, structuralGate, statefulGate, capabilityGate, resolvePublicKey, getExpression } = config;
8465
8501
  return {
@@ -8485,7 +8521,7 @@ function createValidationEngine(config) {
8485
8521
  };
8486
8522
  }
8487
8523
 
8488
- // ../src/query/filter.ts
8524
+ // ../core/src/query/filter.ts
8489
8525
  var MAX_INCLUDE_DEPTH = 3;
8490
8526
  var META = {
8491
8527
  "@key": (r) => r.key,
@@ -8649,7 +8685,7 @@ function checkQuery(query) {
8649
8685
  return null;
8650
8686
  }
8651
8687
 
8652
- // ../src/query/types.ts
8688
+ // ../core/src/query/types.ts
8653
8689
  var nameOf = (ref) => typeof ref === "object" && ref !== null && typeof ref.name === "string" ? ref.name : ref;
8654
8690
  function plainQuery(query) {
8655
8691
  const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -8663,7 +8699,7 @@ function plainQuery(query) {
8663
8699
  return { ...query, collection: nameOf(query.collection), ...query.include !== void 0 ? { include: includes(query.include) } : {} };
8664
8700
  }
8665
8701
 
8666
- // ../src/query/engine.ts
8702
+ // ../core/src/query/engine.ts
8667
8703
  function compareValues(a, b) {
8668
8704
  if (a === b) return 0;
8669
8705
  if (a === void 0 || a === null) return -1;
@@ -8723,7 +8759,7 @@ async function runQuery(source, given) {
8723
8759
  return { records, cursor: more ? page[page.length - 1].key : null, complete: true };
8724
8760
  }
8725
8761
 
8726
- // ../src/storage/segment.ts
8762
+ // ../core/src/storage/segment.ts
8727
8763
  var SEGMENT_SUFFIX = ".seg";
8728
8764
  function packSegment(versions) {
8729
8765
  return utf8Encode(JSON.stringify({ v: 1, versions }));
@@ -8743,7 +8779,7 @@ async function segmentName(counter, bytes) {
8743
8779
  }
8744
8780
  var versionSize = (version) => utf8Encode(JSON.stringify(version)).length;
8745
8781
 
8746
- // ../src/storage/mirror.ts
8782
+ // ../core/src/storage/mirror.ts
8747
8783
  var WRITER_KEY = "mirror:writer";
8748
8784
  var COUNTER_KEY = "mirror:counter";
8749
8785
  var readKey = (name) => `mirror:read:${name}`;
@@ -8889,7 +8925,7 @@ async function createMirror(config) {
8889
8925
  });
8890
8926
  }
8891
8927
 
8892
- // ../src/space/pass.ts
8928
+ // ../core/src/space/pass.ts
8893
8929
  var PASS_COLLECTION = "sys.pass";
8894
8930
  var CARRY_CLOSED_KEY = "carry:closed";
8895
8931
  async function passKey(spaceId) {
@@ -8920,7 +8956,7 @@ function carriedRecord(pass) {
8920
8956
  return { space: pass.space, key: null, invite: null, role: null, read: pass.read };
8921
8957
  }
8922
8958
 
8923
- // ../src/node/space-runtime.ts
8959
+ // ../core/src/node/space-runtime.ts
8924
8960
  var MANAGED = /* @__PURE__ */ new Set([MEMBERSHIP_COLLECTION, PROFILE_COLLECTION, CARRIER_COLLECTION, PASS_COLLECTION, BOX_COLLECTION, MEMBER_KEY_COLLECTION, ...ACCESS_COLLECTIONS]);
8925
8961
  var IN_THE_CLEAR = /* @__PURE__ */ new Set([ROLE_COLLECTION, MEMBER_COLLECTION, INVITE_COLLECTION, REVOKE_COLLECTION, KEY_COLLECTION, BOX_COLLECTION, MEMBER_KEY_COLLECTION, RELAYS_COLLECTION, KEEPERS_COLLECTION]);
8926
8962
  var ACCESS_KEYS = [
@@ -9863,7 +9899,7 @@ async function openSpaceRuntime(deps) {
9863
9899
  channel.unref?.();
9864
9900
  }
9865
9901
  const HOUR = 36e5;
9866
- const DAY2 = 24 * HOUR;
9902
+ const DAY3 = 24 * HOUR;
9867
9903
  const declared = new Set((cache?.collections ?? []).filter((c) => !c.startsWith("sys.")));
9868
9904
  let cacheTimer = null;
9869
9905
  const saveCache = () => {
@@ -9951,7 +9987,7 @@ async function openSpaceRuntime(deps) {
9951
9987
  });
9952
9988
  async function dropUnused() {
9953
9989
  if (!cache || !partial || closed) return;
9954
- const cutoff = Date.now() - (cache.unusedAfterDays ?? 30) * DAY2;
9990
+ const cutoff = Date.now() - (cache.unusedAfterDays ?? 30) * DAY3;
9955
9991
  const waiting = /* @__PURE__ */ new Set();
9956
9992
  for (const name of await adapter.list(PENDING_PREFIX)) {
9957
9993
  const bytes = await adapter.get(name);
@@ -10379,7 +10415,7 @@ async function openSpaceRuntime(deps) {
10379
10415
  });
10380
10416
  }
10381
10417
 
10382
- // ../src/schemas/contacts.ts
10418
+ // ../core/src/schemas/contacts.ts
10383
10419
  var typed = () => (definition) => definition;
10384
10420
  var contact = typed()({
10385
10421
  name: "std.contact",
@@ -10392,7 +10428,8 @@ var contact = typed()({
10392
10428
  name: { type: "string", maxLength: 200, description: "What you call them" },
10393
10429
  space: { type: "string", maxLength: 256, description: "The id of your space for two" },
10394
10430
  note: { type: "string", maxLength: 2e3 },
10395
- blocked: { type: "boolean", description: "Their contact requests are hidden, in every space" }
10431
+ blocked: { type: "boolean", description: "Their contact requests are hidden, in every space" },
10432
+ door: { type: "string", maxLength: 64, description: "The door they knocked on, while your answer is still to be written in your space for two" }
10396
10433
  },
10397
10434
  required: ["did", "name"]
10398
10435
  },
@@ -10412,8 +10449,275 @@ var contactRequest = typed()({
10412
10449
  },
10413
10450
  rules: { edit: "creator", delete: "creator" }
10414
10451
  });
10452
+ var door = typed()({
10453
+ name: "std.door",
10454
+ title: "Door",
10455
+ description: "A way for people you share no space with to ask to become your contact.",
10456
+ schema: {
10457
+ type: "object",
10458
+ properties: {
10459
+ id: { type: "string", minLength: 16, maxLength: 64, description: "Random; the door key is derived from it" },
10460
+ label: { type: "string", maxLength: 64, description: "What you call this door \u2014 only you see it" },
10461
+ name: { type: "string", maxLength: 64, description: "The name its code gives, shown to whoever knocks" },
10462
+ relays: { type: "array", items: { type: "string", maxLength: 200 }, minItems: 1, maxItems: 3, description: "Whose mailboxes hold its knocks" }
10463
+ },
10464
+ required: ["id", "relays"]
10465
+ },
10466
+ rules: { onePer: ["id"] }
10467
+ });
10468
+ var knock = typed()({
10469
+ name: "std.knock",
10470
+ title: "Knock",
10471
+ description: "A knock left on a door, waiting for an answer.",
10472
+ schema: {
10473
+ type: "object",
10474
+ properties: {
10475
+ space: { type: "string", maxLength: 256, description: "The space for two it invites them to" },
10476
+ name: { type: "string", maxLength: 64, description: "The name their door code gave" },
10477
+ door: { type: "string", maxLength: 64, description: "The door key knocked on" },
10478
+ sign: { type: "string", maxLength: 64, description: "The door's signing key, which their answer must be signed with" },
10479
+ invite: { type: "string", maxLength: 8e3, description: "The invite sent, to close once they answer" }
10480
+ },
10481
+ required: ["space", "name", "door", "sign", "invite"]
10482
+ },
10483
+ rules: { onePer: ["space"] }
10484
+ });
10485
+ var knockAnswer = typed()({
10486
+ name: "std.knock-answer",
10487
+ title: "Knock answer",
10488
+ description: "Proof that the account which joined is the one behind the door knocked on.",
10489
+ schema: {
10490
+ type: "object",
10491
+ properties: {
10492
+ sig: { type: "string", maxLength: 200, description: 'The door signing key over "weave/knock-answer/v1|<space>|<account>"' }
10493
+ },
10494
+ required: ["sig"]
10495
+ },
10496
+ rules: { edit: "creator", delete: "creator" }
10497
+ });
10498
+
10499
+ // ../core/src/doors/doors.ts
10500
+ var MAX_DOOR_RELAYS = 3;
10501
+ var KNOCK_TTL_SECONDS = 14 * 24 * 3600;
10502
+ var KNOCK_DROP_WINDOW_SECONDS = 600;
10503
+ var MAX_NAME = 64;
10504
+ var MAX_NOTE = 2e3;
10505
+ var MAX_INVITE = 6e3;
10506
+ var MAX_PROOF = 4096;
10507
+ function clip(text, max) {
10508
+ const chars = Array.from(text);
10509
+ return chars.length <= max ? text : chars.slice(0, max).join("");
10510
+ }
10511
+ function encodeDoorCode(code) {
10512
+ const problem = checkDoorCode({ v: 1, ...code });
10513
+ if (problem) throw new Error(problem);
10514
+ return base64UrlEncode(
10515
+ utf8Encode(canonicalize({ v: 1, key: code.key, sign: code.sign, relays: [...code.relays], ...code.name ? { name: code.name } : {} }))
10516
+ );
10517
+ }
10518
+ function parseDoorCode(text) {
10519
+ const trimmed = text.trim();
10520
+ const found = /(?:^|[#?&])door=([A-Za-z0-9_-]+)/.exec(trimmed);
10521
+ const raw = found ? found[1] : trimmed;
10522
+ let parsed;
10523
+ try {
10524
+ parsed = JSON.parse(utf8Decode(base64UrlDecode(raw)));
10525
+ } catch {
10526
+ throw new Error("That is not a door code \u2014 it may be cut short.");
10527
+ }
10528
+ const problem = checkDoorCode(parsed);
10529
+ if (problem) throw new Error(`That door code doesn't work: ${problem}`);
10530
+ const code = parsed;
10531
+ return Object.freeze({ v: 1, key: code.key, sign: code.sign, relays: Object.freeze([...code.relays]), ...code.name ? { name: code.name } : {} });
10532
+ }
10533
+ function checkDoorCode(value) {
10534
+ const code = value;
10535
+ if (!code || typeof code !== "object" || code.v !== 1) return "it is not a version 1 door";
10536
+ if (!isContactPublicKey(code.key)) return "its key is not a P-256 public key";
10537
+ if (!isContactPublicKey(code.sign) || code.sign === code.key) return "its signing key is not a P-256 public key of its own";
10538
+ if (!Array.isArray(code.relays) || code.relays.length === 0 || code.relays.length > MAX_DOOR_RELAYS) {
10539
+ return `it names 1\u2013${MAX_DOOR_RELAYS} relays`;
10540
+ }
10541
+ const relays = checkRelays(code.relays);
10542
+ if (relays) return relays;
10543
+ if (code.name !== void 0 && (typeof code.name !== "string" || Array.from(code.name).length > MAX_NAME)) return `its name is text of at most ${MAX_NAME} characters`;
10544
+ return null;
10545
+ }
10546
+ async function doorTopic(sign) {
10547
+ return base64UrlEncode(await sha2562(utf8Encode(`weave/door-topic/v1|${sign}`)));
10548
+ }
10549
+ var purgeMessage = (topic, nonce, ids) => utf8Encode(`weave/door-purge/v1|${topic}|${nonce}|${ids ? [...ids].sort().join(",") : "*"}`);
10550
+ function signPurge(signKey, topic, nonce, ids) {
10551
+ return signWithScalar(signKey, purgeMessage(topic, nonce, ids));
10552
+ }
10553
+ var answerMessage = (pairSpace, did) => utf8Encode(`weave/knock-answer/v1|${pairSpace}|${did}`);
10554
+ function signAnswer(signKey, pairSpace, did) {
10555
+ return signWithScalar(signKey, answerMessage(pairSpace, did));
10556
+ }
10557
+ function checkAnswer(sign, pairSpace, did, signature) {
10558
+ return verifyWithPoint(sign, answerMessage(pairSpace, did), signature);
10559
+ }
10560
+ async function knockId(blob) {
10561
+ return base64UrlEncode(await sha2562(utf8Encode(blob)));
10562
+ }
10563
+ var knockContext = (door2) => `weave/knock/v1|${door2}`;
10564
+ async function sealKnock(door2, knock2, session, provider) {
10565
+ const note = knock2.note ? clip(knock2.note.trim(), MAX_NOTE) : void 0;
10566
+ const body = {
10567
+ v: 1,
10568
+ door: door2,
10569
+ from: knock2.from,
10570
+ name: clip(knock2.name.trim(), MAX_NAME) || "Someone",
10571
+ invite: knock2.invite,
10572
+ ...note ? { note } : {},
10573
+ at: Math.floor(Date.now() / 1e3),
10574
+ session: session.did,
10575
+ proof: session.proof
10576
+ };
10577
+ const sig = base64UrlEncode(await provider.sign(session.key, utf8Encode(canonicalize(body))));
10578
+ return sealFor(door2, { body, sig }, knockContext(door2));
10579
+ }
10580
+ async function openKnock(doorKey, blob, receivedAt, provider) {
10581
+ const door2 = contactPublicKey(doorKey);
10582
+ const opened = await openSealed((await contactKeyPair(doorKey)).privateKey, blob, knockContext(door2));
10583
+ const body = opened?.body;
10584
+ if (!body || typeof opened.sig !== "string") return null;
10585
+ if (body.v !== 1 || body.door !== door2) return null;
10586
+ if (typeof body.from !== "string" || typeof body.session !== "string" || typeof body.name !== "string") return null;
10587
+ if (Array.from(body.name).length > MAX_NAME) return null;
10588
+ if (typeof body.invite !== "string" || body.invite.length > MAX_INVITE) return null;
10589
+ if (typeof body.proof !== "string" || body.proof.length > MAX_PROOF) return null;
10590
+ if (body.note !== void 0 && (typeof body.note !== "string" || Array.from(body.note).length > MAX_NOTE)) return null;
10591
+ if (!Number.isSafeInteger(body.at) || !Number.isFinite(receivedAt)) return null;
10592
+ const now = Math.floor(Date.now() / 1e3);
10593
+ if (body.at > now + UCAN_CLOCK_SKEW_SECONDS || body.at < now - KNOCK_TTL_SECONDS) return null;
10594
+ const dropped = Math.floor(receivedAt / 1e3);
10595
+ if (body.at > dropped + UCAN_CLOCK_SKEW_SECONDS || body.at < dropped - KNOCK_DROP_WINDOW_SECONDS) return null;
10596
+ let signed = false;
10597
+ try {
10598
+ const key3 = await provider.importPublicKey(didToPublicKey(body.session).publicKeyBytes);
10599
+ signed = await provider.verify(key3, base64UrlDecode(opened.sig), utf8Encode(canonicalize(body)));
10600
+ } catch {
10601
+ return null;
10602
+ }
10603
+ if (!signed) return null;
10604
+ if (isAgentNote(body.proof)) return null;
10605
+ const chain = await resolveDelegationRoot(body.proof, () => null, provider, { at: body.at }).catch(() => null);
10606
+ if (!chain?.valid || chain.audience !== body.session || chain.rootDid !== body.from) return null;
10607
+ if (!chain.capabilities.some((capability) => capability.with === "*" && (capability.can === "expression/*" || capability.can === "*"))) return null;
10608
+ let invited;
10609
+ try {
10610
+ invited = parseSpaceInvite(body.invite);
10611
+ } catch {
10612
+ return null;
10613
+ }
10614
+ if (invited.space.creator !== body.from || invited.space.visibility !== "private" || !invited.key) return null;
10615
+ if (await checkSpace(invited.space) !== null) return null;
10616
+ return Object.freeze({
10617
+ from: body.from,
10618
+ name: clip(body.name, MAX_NAME) || "Someone",
10619
+ ...body.note ? { note: body.note } : {},
10620
+ invite: body.invite,
10621
+ pairSpace: invited.space.id,
10622
+ at: body.at * 1e3
10623
+ });
10624
+ }
10415
10625
 
10416
- // ../src/space/notify.ts
10626
+ // ../core/src/network/mailbox.ts
10627
+ var MAX_PAGES = 8;
10628
+ function createMailboxClient(options = {}) {
10629
+ const timeoutMs = options.timeoutMs ?? 8e3;
10630
+ function ask(relay, message, pick) {
10631
+ return talk(relay, (send2) => send2(message), async (answer) => pick(answer));
10632
+ }
10633
+ function talk(relay, start, hear) {
10634
+ const Socket = options.WebSocket ?? globalThis.WebSocket;
10635
+ if (!Socket) return Promise.reject(new Error("No WebSocket here to reach a relay with"));
10636
+ return new Promise((resolve, reject) => {
10637
+ let settled = false;
10638
+ const ws = new Socket(relay);
10639
+ const finish = (error, value) => {
10640
+ if (settled) return;
10641
+ settled = true;
10642
+ clearTimeout(timer);
10643
+ try {
10644
+ ws.close();
10645
+ } catch {
10646
+ }
10647
+ if (error) reject(error);
10648
+ else resolve(value);
10649
+ };
10650
+ const timer = setTimeout(() => finish(new Error(`${relay} did not answer`)), timeoutMs);
10651
+ const say = (message) => ws.send(JSON.stringify(message));
10652
+ ws.onopen = () => start(say);
10653
+ ws.onerror = () => finish(new Error(`Could not reach ${relay}`));
10654
+ ws.onclose = () => finish(new Error(`${relay} closed the connection`));
10655
+ ws.onmessage = (event) => {
10656
+ let answer;
10657
+ try {
10658
+ answer = JSON.parse(String(event.data));
10659
+ } catch {
10660
+ return;
10661
+ }
10662
+ if (!answer || typeof answer !== "object") return;
10663
+ hear(answer, say).then(
10664
+ (value) => {
10665
+ if (value !== void 0) finish(null, value);
10666
+ },
10667
+ (error) => finish(error instanceof Error ? error : new Error(String(error)))
10668
+ );
10669
+ };
10670
+ });
10671
+ }
10672
+ const client = {
10673
+ drop(relay, topic, blob, ttlSeconds) {
10674
+ return ask(relay, { type: "drop", topic, blob, ...ttlSeconds ? { ttl: ttlSeconds } : {} }, (answer) => {
10675
+ if (answer.topic !== topic) return void 0;
10676
+ if (answer.type === "dropped" && typeof answer.id === "string") return answer.id;
10677
+ if (answer.type === "refused") throw new Error(`${relay} refused the knock: ${String(answer.reason ?? "no reason given")}`);
10678
+ return void 0;
10679
+ });
10680
+ },
10681
+ async fetch(relay, topic, after = 0) {
10682
+ const found = [];
10683
+ let from = after;
10684
+ for (let page = 0; page < MAX_PAGES; page++) {
10685
+ const { items, more } = await ask(
10686
+ relay,
10687
+ { type: "fetch", topic, after: from },
10688
+ (answer) => answer.type === "mail" && answer.topic === topic && Array.isArray(answer.items) ? { items: answer.items.filter(isMailItem), more: answer.more === true } : void 0
10689
+ );
10690
+ found.push(...items);
10691
+ if (!more || items.length === 0) break;
10692
+ from = Math.max(...items.map((item) => item.seq));
10693
+ }
10694
+ return found;
10695
+ },
10696
+ purge(relay, topic, sign, ids, signChallenge) {
10697
+ return talk(
10698
+ relay,
10699
+ (send2) => send2({ type: "challenge" }),
10700
+ async (answer, send2) => {
10701
+ if (answer.type === "challenge" && typeof answer.nonce === "string") {
10702
+ send2({ type: "purge", topic, sign, ...ids ? { ids: [...ids] } : {}, sig: await signChallenge(answer.nonce) });
10703
+ return void 0;
10704
+ }
10705
+ if (answer.topic !== topic) return void 0;
10706
+ if (answer.type === "purged" && typeof answer.count === "number") return answer.count;
10707
+ if (answer.type === "refused") throw new Error(`${relay} refused to clear the door: ${String(answer.reason ?? "no reason given")}`);
10708
+ return void 0;
10709
+ }
10710
+ );
10711
+ }
10712
+ };
10713
+ return Object.freeze(client);
10714
+ }
10715
+ function isMailItem(value) {
10716
+ const item = value;
10717
+ return !!item && Number.isSafeInteger(item.seq) && typeof item.id === "string" && typeof item.at === "number" && typeof item.blob === "string";
10718
+ }
10719
+
10720
+ // ../core/src/space/notify.ts
10417
10721
  var NOTIFY_COLLECTION = "sys.notify";
10418
10722
  var SUBSCRIPTION_COLLECTION = "sys.subscription";
10419
10723
  var COLLECTION = /^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/;
@@ -10500,7 +10804,7 @@ function matchesSubscription(sub, spaceId, version, account, now = Date.now()) {
10500
10804
  return true;
10501
10805
  }
10502
10806
 
10503
- // ../src/session/hosting.ts
10807
+ // ../core/src/session/hosting.ts
10504
10808
  var HOSTING_COLLECTION = "sys.hosting";
10505
10809
  var REQUEST_WINDOW_SECONDS = 300;
10506
10810
  function newSubscriptionSeed() {
@@ -10616,7 +10920,7 @@ function createHostClient(url, host, key3, provider = createP256Provider()) {
10616
10920
  });
10617
10921
  }
10618
10922
 
10619
- // ../src/node/node.ts
10923
+ // ../core/src/node/node.ts
10620
10924
  var SESSION_CAPABILITY = { with: "*", can: "expression/*" };
10621
10925
  var DEFAULT_TTL_SECONDS = 3600;
10622
10926
  var FORGET_CARRIER_AFTER_MS = 30 * 24 * 3600 * 1e3;
@@ -11425,11 +11729,13 @@ async function createNode(config) {
11425
11729
  if (!contact2.did.startsWith("did:")) throw new Error("A contact needs their account DID");
11426
11730
  const name = contact2.name.trim().slice(0, 200);
11427
11731
  if (!name) throw new Error("A contact needs a name");
11732
+ const pending = (await contactRecords()).find((record) => record.body.did === contact2.did)?.body?.door;
11428
11733
  return writeContact({
11429
11734
  did: contact2.did,
11430
11735
  name,
11431
11736
  ...contact2.space ? { space: contact2.space } : {},
11432
- ...contact2.note ? { note: contact2.note.slice(0, 2e3) } : {}
11737
+ ...contact2.note ? { note: contact2.note.slice(0, 2e3) } : {},
11738
+ ...pending ? { door: pending } : {}
11433
11739
  });
11434
11740
  },
11435
11741
  async remove(did) {
@@ -11505,6 +11811,253 @@ async function createNode(config) {
11505
11811
  return [...seen].filter((account2) => account2 !== config.signer.did && account2 !== did).sort();
11506
11812
  }
11507
11813
  });
11814
+ const mailbox = config.mailbox ?? createMailboxClient();
11815
+ const knockCache = /* @__PURE__ */ new Map();
11816
+ const DAY_MS = 24 * 3600 * 1e3;
11817
+ function requireContactKey(what) {
11818
+ if (agentSession) throw new Error(`An agent can't use doors. ${what} is the person's to do.`);
11819
+ if (!config.contactKey) throw new Error(`${what} needs your contact key. Connect to your account home again, and allow contacts.`);
11820
+ return config.contactKey;
11821
+ }
11822
+ async function doorKeys(id) {
11823
+ const contactKey = requireContactKey("A door");
11824
+ const key3 = await deriveDoorKeyBytes(contactKey, id);
11825
+ const sign = await deriveDoorSignKeyBytes(contactKey, id);
11826
+ const signPublic = contactPublicKey(sign);
11827
+ return { key: key3, sign, publicKey: contactPublicKey(key3), signPublic, topic: await doorTopic(signPublic) };
11828
+ }
11829
+ async function doorRecords() {
11830
+ if (!contactsSpaceId) return [];
11831
+ return (await (await contactsRuntime()).list({ collection: door.name })).filter(
11832
+ (record) => record.verified && record.root === config.signer.did && typeof record.body?.id === "string" && Array.isArray(record.body.relays)
11833
+ );
11834
+ }
11835
+ async function doorView(record) {
11836
+ const body = record.body;
11837
+ const keys = await doorKeys(body.id);
11838
+ return Object.freeze({
11839
+ id: body.id,
11840
+ ...body.label ? { label: body.label } : {},
11841
+ ...body.name ? { name: body.name } : {},
11842
+ key: keys.publicKey,
11843
+ sign: keys.signPublic,
11844
+ relays: Object.freeze([...body.relays]),
11845
+ code: encodeDoorCode({ key: keys.publicKey, sign: keys.signPublic, relays: body.relays, ...body.name ? { name: body.name } : {} }),
11846
+ createdAt: record.createdAt
11847
+ });
11848
+ }
11849
+ async function purgeDoor(door2, ids) {
11850
+ const keys = await doorKeys(door2.id);
11851
+ const done = await Promise.allSettled(
11852
+ door2.relays.map((relay) => mailbox.purge(relay, keys.topic, keys.signPublic, ids, (nonce) => signPurge(keys.sign, keys.topic, nonce, ids)))
11853
+ );
11854
+ return done.filter((result) => result.status === "fulfilled").length;
11855
+ }
11856
+ async function sentRecords() {
11857
+ if (!contactsSpaceId) return [];
11858
+ return (await (await contactsRuntime()).list({ collection: knock.name })).filter(
11859
+ (record) => record.verified && record.root === config.signer.did && typeof record.body?.space === "string" && typeof record.body.sign === "string" && typeof record.body.invite === "string"
11860
+ );
11861
+ }
11862
+ async function settleSent() {
11863
+ for (const record of await sentRecords()) {
11864
+ const { space: space2, name, sign, invite } = record.body;
11865
+ if (!await registry.get(space2)) {
11866
+ await (await contactsRuntime()).remove(record.key);
11867
+ continue;
11868
+ }
11869
+ const open2 = await runtime(space2);
11870
+ const { members } = await open2.access();
11871
+ let answered = null;
11872
+ for (const answer of await open2.list({ collection: knockAnswer.name })) {
11873
+ const did = answer.root;
11874
+ if (!answer.verified || !did || did === config.signer.did || answer.createdBy !== did || typeof answer.body?.sig !== "string") continue;
11875
+ if (!members.some((member) => member.did === did)) continue;
11876
+ if (await checkAnswer(sign, space2, did, answer.body.sig)) {
11877
+ answered = did;
11878
+ break;
11879
+ }
11880
+ }
11881
+ if (answered) {
11882
+ await spaces.closeInvite(space2, invite).catch(() => {
11883
+ });
11884
+ const given = (await open2.profiles()).find((profile) => profile.did === answered)?.name;
11885
+ await writeContact({ did: answered, name: given ?? name, space: space2 });
11886
+ await (await contactsRuntime()).remove(record.key);
11887
+ } else if (Date.now() - Date.parse(record.createdAt) > KNOCK_TTL_SECONDS * 1e3 + DAY_MS) {
11888
+ await spaces.leave(space2).catch(() => {
11889
+ });
11890
+ await (await contactsRuntime()).remove(record.key);
11891
+ }
11892
+ }
11893
+ }
11894
+ async function answerAccepted() {
11895
+ for (const record of await contactRecords()) {
11896
+ const { did, space: space2, door: door2 } = record.body;
11897
+ if (typeof door2 !== "string" || typeof space2 !== "string") continue;
11898
+ if (!await registry.get(space2)) continue;
11899
+ const open2 = await runtime(space2);
11900
+ if ((await open2.access()).role === null) continue;
11901
+ const { sign } = await doorKeys(door2);
11902
+ try {
11903
+ await open2.put(knockAnswer.name, { sig: await signAnswer(sign, space2, config.signer.did) });
11904
+ } catch {
11905
+ continue;
11906
+ }
11907
+ const { door: _answered, ...rest } = record.body;
11908
+ await writeContact({ ...rest, did });
11909
+ }
11910
+ }
11911
+ async function readDoors() {
11912
+ const now = Date.now();
11913
+ for (const [id, cached] of knockCache) if (cached.until <= now) knockCache.delete(id);
11914
+ const found = [];
11915
+ for (const record of await doorRecords()) {
11916
+ const { id: doorId, relays } = record.body;
11917
+ const keys = await doorKeys(doorId);
11918
+ const held = /* @__PURE__ */ new Map();
11919
+ for (const result of await Promise.allSettled(relays.map((relay) => mailbox.fetch(relay, keys.topic)))) {
11920
+ if (result.status !== "fulfilled") continue;
11921
+ for (const item of result.value) {
11922
+ const id = await knockId(item.blob);
11923
+ const seen = held.get(id);
11924
+ if (!seen || item.at < seen.at) held.set(id, { blob: item.blob, at: item.at });
11925
+ }
11926
+ }
11927
+ for (const [id, item] of held) {
11928
+ let cached = knockCache.get(id);
11929
+ if (!cached) {
11930
+ cached = { opened: await openKnock(keys.key, item.blob, item.at, provider), door: doorId, until: item.at + KNOCK_TTL_SECONDS * 1e3 };
11931
+ knockCache.set(id, cached);
11932
+ }
11933
+ if (cached.opened) found.push({ id, door: doorId, opened: cached.opened });
11934
+ }
11935
+ }
11936
+ return found;
11937
+ }
11938
+ const doors = Object.freeze({
11939
+ async list() {
11940
+ if (!config.contactKey || agentSession) return [];
11941
+ return Promise.all((await doorRecords()).map(doorView));
11942
+ },
11943
+ async open(options = {}) {
11944
+ requireContactKey("Opening a door");
11945
+ if (!contactsSpaceId) await contactsRuntime();
11946
+ const relays = [...options.relays ?? (config.network?.relays ?? []).filter((url) => checkRelays([url]) === null)].slice(0, MAX_DOOR_RELAYS);
11947
+ if (relays.length === 0) throw new Error("A door needs a relay to hold its knocks, and this node has none.");
11948
+ const name = clip((options.name ?? await ownName() ?? "").trim(), 64);
11949
+ const label = options.label ? clip(options.label.trim(), 64) : "";
11950
+ const id = base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(16)));
11951
+ const keys = await doorKeys(id);
11952
+ encodeDoorCode({ key: keys.publicKey, sign: keys.signPublic, relays, ...name ? { name } : {} });
11953
+ const open2 = await contactsRuntime();
11954
+ await ensureDefined(open2, door);
11955
+ return doorView(await open2.put(door.name, { id, relays, ...name ? { name } : {}, ...label ? { label } : {} }));
11956
+ },
11957
+ async close(id) {
11958
+ const found = (await doorRecords()).find((record) => record.body.id === id);
11959
+ if (found) await (await contactsRuntime()).remove(found.key);
11960
+ for (const [knock2, cached] of knockCache) if (cached.door === id) knockCache.delete(knock2);
11961
+ },
11962
+ async clear(id) {
11963
+ requireContactKey("Clearing a door");
11964
+ const found = (await doorRecords()).find((record) => record.body.id === id);
11965
+ if (!found) throw new Error("There is no such door");
11966
+ if (await purgeDoor(found.body, null) === 0) throw new Error("None of the door's relays could be reached to clear it.");
11967
+ for (const [knock2, cached] of knockCache) if (cached.door === id) knockCache.delete(knock2);
11968
+ },
11969
+ async knock(code, options = {}) {
11970
+ requireContactKey("Knocking on a door");
11971
+ requireEverywhere("Knocking on a door");
11972
+ const door2 = parseDoorCode(code);
11973
+ for (const mine2 of await doors.list()) if (mine2.key === door2.key) throw new Error("That is one of your own doors");
11974
+ if (!contactsSpaceId) await contactsRuntime();
11975
+ const mine = await ownName() ?? "Someone";
11976
+ const theirs = door2.name ?? "Contact";
11977
+ const pair = await spaces.create({ name: `${clip(mine, 64)} & ${theirs}`, visibility: "private", ...team });
11978
+ let invite;
11979
+ try {
11980
+ await ensureDefined(await runtime(pair.id), knockAnswer);
11981
+ invite = await spaces.invite(pair.id, { role: "editor" });
11982
+ const blob = await sealKnock(
11983
+ door2.key,
11984
+ { from: config.signer.did, name: mine, invite, ...options.note ? { note: options.note } : {} },
11985
+ { did: session.did, key: session.key, proof: session.proof() },
11986
+ provider
11987
+ );
11988
+ const topic = await doorTopic(door2.sign);
11989
+ const left = await Promise.allSettled(door2.relays.map((relay) => mailbox.drop(relay, topic, blob, KNOCK_TTL_SECONDS)));
11990
+ if (!left.some((result) => result.status === "fulfilled")) {
11991
+ const reasons = left.map((result) => result.status === "rejected" ? String(result.reason?.message ?? result.reason) : "").filter(Boolean);
11992
+ throw new Error(`None of their door's relays took the knock. ${reasons.join("; ")}`);
11993
+ }
11994
+ } catch (error) {
11995
+ await spaces.leave(pair.id).catch(() => {
11996
+ });
11997
+ throw error;
11998
+ }
11999
+ const open2 = await contactsRuntime();
12000
+ await ensureDefined(open2, knock);
12001
+ await open2.put(knock.name, { space: pair.id, name: theirs, door: door2.key, sign: door2.sign, invite });
12002
+ return { space: pair.id };
12003
+ },
12004
+ async knocks() {
12005
+ requireContactKey("Reading knocks");
12006
+ await settleSent().catch(() => {
12007
+ });
12008
+ await answerAccepted().catch(() => {
12009
+ });
12010
+ const blocked = new Set((await contactRecords()).filter((record) => record.body.blocked === true).map((record) => record.body.did));
12011
+ const found = [];
12012
+ for (const { id, door: door2, opened } of await readDoors()) {
12013
+ if (opened.from === config.signer.did || blocked.has(opened.from)) continue;
12014
+ if (await registry.get(opened.pairSpace)) continue;
12015
+ found.push(
12016
+ Object.freeze({
12017
+ id,
12018
+ door: door2,
12019
+ from: opened.from,
12020
+ name: opened.name,
12021
+ ...opened.note ? { note: opened.note } : {},
12022
+ pairSpace: opened.pairSpace,
12023
+ at: new Date(opened.at).toISOString()
12024
+ })
12025
+ );
12026
+ }
12027
+ return found.sort((a, b) => b.at.localeCompare(a.at));
12028
+ },
12029
+ async sent() {
12030
+ await settleSent().catch(() => {
12031
+ });
12032
+ return (await sentRecords()).map((record) => Object.freeze({ space: record.body.space, name: record.body.name, at: record.createdAt }));
12033
+ },
12034
+ async accept(id) {
12035
+ requireContactKey("Opening the door to someone");
12036
+ requireEverywhere("Opening the door to someone");
12037
+ if (!knockCache.get(id)?.opened) await readDoors();
12038
+ const cached = knockCache.get(id);
12039
+ const knocked = cached?.opened;
12040
+ if (!cached || !knocked) throw new Error("That knock is gone: it expired, or the door was closed or cleared.");
12041
+ await spaces.join(knocked.invite);
12042
+ const known = await contacts.get(knocked.from);
12043
+ const contact2 = await writeContact({ did: knocked.from, name: known?.name ?? knocked.name, space: knocked.pairSpace, door: cached.door });
12044
+ await answerAccepted().catch(() => {
12045
+ });
12046
+ const door2 = (await doorRecords()).find((record) => record.body.id === cached.door);
12047
+ if (door2) await purgeDoor(door2.body, [id]).catch(() => 0);
12048
+ knockCache.delete(id);
12049
+ return contact2;
12050
+ },
12051
+ async dismiss(id) {
12052
+ requireContactKey("Dismissing a knock");
12053
+ if (!knockCache.has(id)) await readDoors();
12054
+ const cached = knockCache.get(id);
12055
+ if (!cached) return;
12056
+ const door2 = (await doorRecords()).find((record) => record.body.id === cached.door);
12057
+ if (door2) await purgeDoor(door2.body, [id]);
12058
+ knockCache.delete(id);
12059
+ }
12060
+ });
11508
12061
  const collections = Object.freeze({
11509
12062
  async list(spaceId) {
11510
12063
  return (await runtime(spaceId)).collections();
@@ -11721,6 +12274,18 @@ async function createNode(config) {
11721
12274
  accept: person("accept contact requests"),
11722
12275
  others: person("look inside a contact's space")
11723
12276
  }),
12277
+ // Doors are the person's: an agent has none and knocks on none.
12278
+ doors: Object.freeze({
12279
+ list: async () => [],
12280
+ open: person("open a door"),
12281
+ close: person("close a door"),
12282
+ knock: person("knock on anyone's door"),
12283
+ clear: person("clear a door"),
12284
+ knocks: person("read knocks"),
12285
+ sent: person("look at knocks"),
12286
+ accept: person("open the door to anyone"),
12287
+ dismiss: person("dismiss a knock")
12288
+ }),
11724
12289
  delegation: () => note,
11725
12290
  iceServers: node.iceServers,
11726
12291
  delegate: person("pass its access on"),
@@ -11744,6 +12309,7 @@ async function createNode(config) {
11744
12309
  hosting,
11745
12310
  notifications,
11746
12311
  contacts,
12312
+ doors,
11747
12313
  asAgent,
11748
12314
  delegation: () => current,
11749
12315
  iceServers: async () => mesh ? mesh.iceServers() : config.network?.iceServers ?? DEFAULT_ICE_SERVERS,
@@ -11779,7 +12345,7 @@ async function createNode(config) {
11779
12345
  return node;
11780
12346
  }
11781
12347
 
11782
- // ../src/node/stores.ts
12348
+ // ../core/src/node/stores.ts
11783
12349
  function folderStores(directory2, options = {}) {
11784
12350
  return async (path7, storeOptions) => {
11785
12351
  const full = options.basePath ? `${options.basePath}/${path7}` : path7;
@@ -11788,7 +12354,7 @@ function folderStores(directory2, options = {}) {
11788
12354
  };
11789
12355
  }
11790
12356
 
11791
- // ../src/network/local-transport.ts
12357
+ // ../core/src/network/local-transport.ts
11792
12358
  var later = (fn) => void setTimeout(fn, 0);
11793
12359
  function createLocalHub() {
11794
12360
  const connected = /* @__PURE__ */ new Map();
@@ -11834,7 +12400,7 @@ function createLocalHub() {
11834
12400
  };
11835
12401
  }
11836
12402
 
11837
- // ../src/node/carrier.ts
12403
+ // ../core/src/node/carrier.ts
11838
12404
  async function createCarryCore(config) {
11839
12405
  const provider = config.provider ?? createP256Provider();
11840
12406
  const signer = createSigner(provider);
@@ -12065,7 +12631,7 @@ async function createCarryCore(config) {
12065
12631
  };
12066
12632
  }
12067
12633
 
12068
- // ../src/node/host.ts
12634
+ // ../core/src/node/host.ts
12069
12635
  var SUBSCRIPTION_PREFIX = "subscription:";
12070
12636
  var NotAllowedError = class extends Error {
12071
12637
  constructor() {
@@ -12227,7 +12793,7 @@ async function createHostNode(config) {
12227
12793
  return Object.freeze(host);
12228
12794
  }
12229
12795
 
12230
- // ../src/records/describe.ts
12796
+ // ../core/src/records/describe.ts
12231
12797
  var KNOWN_RULES = ["create", "edit", "delete", "onePer", "fixed"];
12232
12798
  var words = (name) => name.replace(/^.*\./, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
12233
12799
  var article = (noun) => /^[aeiou]/.test(noun) ? `an ${noun}` : `a ${noun}`;
@@ -12305,7 +12871,7 @@ function describeCollection(definition) {
12305
12871
  return sentences;
12306
12872
  }
12307
12873
 
12308
- // ../src/schemas/apps.ts
12874
+ // ../core/src/schemas/apps.ts
12309
12875
  var MAX_APP_COLLECTIONS = 10;
12310
12876
  var app = {
12311
12877
  name: "std.app",
@@ -12406,7 +12972,7 @@ async function proposeApp(node, spaceId, body) {
12406
12972
  return node.records.put(spaceId, app.name, body);
12407
12973
  }
12408
12974
 
12409
- // ../src/schemas/screens.ts
12975
+ // ../core/src/schemas/screens.ts
12410
12976
  var SCREEN_GUIDE = `A screen is one HTML document (scripts and styles inline, at most 48 KB) put on a collection definition as "screen".
12411
12977
  When the app is added, apps in the space can show it instead of plain lists. It runs sealed: no network (no fetch, no
12412
12978
  images from URLs, no fonts from URLs), no storage, no popups, no forms that submit. Keep state in records, never in the page.
@@ -12432,7 +12998,7 @@ Draw everything from weave.list(...) and redraw on weave.onChange \u2014 other p
12432
12998
  Two people can write at once: design collections so that clashes resolve by the rules (onePer, creator-only edits), not by the screen.
12433
12999
  Use plain DOM; no libraries can be loaded. Prefer system fonts and simple, readable styling that works in light and dark.`;
12434
13000
 
12435
- // ../src/node/actions.ts
13001
+ // ../core/src/node/actions.ts
12436
13002
  var space = { type: "string", description: "Space id, from spaces_list" };
12437
13003
  var key2 = { type: "string", description: "Record key \u2014 stays the same when the record is edited" };
12438
13004
  var str = (input, key3) => input[key3];
@@ -12873,7 +13439,7 @@ async function runAction(node, name, input = {}) {
12873
13439
  return action.run(node, input);
12874
13440
  }
12875
13441
 
12876
- // ../node_modules/aws4fetch/dist/aws4fetch.esm.mjs
13442
+ // ../../node_modules/aws4fetch/dist/aws4fetch.esm.mjs
12877
13443
  var encoder2 = new TextEncoder();
12878
13444
  var HOST_SERVICES = {
12879
13445
  appstream2: "appstream",
@@ -13152,7 +13718,7 @@ function guessServiceRegion(url, headers) {
13152
13718
  return [HOST_SERVICES[service] || service, region || ""];
13153
13719
  }
13154
13720
 
13155
- // ../src/storage/blob/s3.ts
13721
+ // ../core/src/storage/blob/s3.ts
13156
13722
  var ATTEMPTS = 5;
13157
13723
  function createS3BlobStore(config) {
13158
13724
  const client = new AwsClient({
@@ -13328,7 +13894,7 @@ async function createAccount(home, options) {
13328
13894
  throw new Error(`This home already has an account for ${did}`);
13329
13895
  }
13330
13896
  let vault = createVault({ did, label: options.name, wraps: [] });
13331
- if (options.passphrase) vault = withWrap(vault, await wrapSeedWithPassphrase(seed, options.passphrase, "CLI passphrase"));
13897
+ if (options.passphrase) vault = withWrap(vault, await wrapSeedWithPassphrase(seed, options.passphrase, CLI_PASSPHRASE_LABEL));
13332
13898
  const id = newAccountId();
13333
13899
  const account = {
13334
13900
  id,
@@ -13389,8 +13955,8 @@ async function unlock(home, account, secret) {
13389
13955
  import { createServer } from "node:http";
13390
13956
  import { WebSocketServer } from "ws";
13391
13957
 
13392
- // ../server/relay.mjs
13393
- import { createHmac, randomBytes as randomBytes4 } from "node:crypto";
13958
+ // ../relay/relay.mjs
13959
+ import { createHash, createHmac, createPublicKey, randomBytes as randomBytes4, verify } from "node:crypto";
13394
13960
  var MAX_MESSAGE_BYTES = 64 * 1024;
13395
13961
  var MAX_CONNECTIONS = 5e3;
13396
13962
  var MAX_CONNECTIONS_PER_IP = 32;
@@ -13404,6 +13970,32 @@ var RATE_BURST = 100;
13404
13970
  var HEARTBEAT_MS = 15e3;
13405
13971
  var MAX_BUFFERED_BYTES = 1024 * 1024;
13406
13972
  var MAX_TURN_ADDRESSES = 2e4;
13973
+ var MAX_BLOB_LENGTH = 12e3;
13974
+ var TOPIC_PATTERN = /^[A-Za-z0-9_-]{43}$/;
13975
+ var MAX_MAIL_PER_TOPIC = 64;
13976
+ var RESERVED_PER_TOPIC = 2;
13977
+ var DROP_WINDOW_MS = 36e5;
13978
+ var MAILBOX_LIMITS = Object.freeze({
13979
+ /** Topics held at once */
13980
+ maxTopics: 5e4,
13981
+ /** Everything held at once, in blob characters */
13982
+ maxChars: 64 * 1024 * 1024,
13983
+ /**
13984
+ * Past `maxChars`, a reserve only topics holding fewer than two knocks may
13985
+ * use: a mailbox someone filled still takes a knock or two on every door.
13986
+ */
13987
+ reserveChars: 8 * 1024 * 1024,
13988
+ /** How long a knock is kept. A client may ask for less, never more. */
13989
+ ttlSeconds: 14 * 24 * 3600,
13990
+ /** Drops one address may make into one topic per hour: filling a door takes many addresses */
13991
+ dropsPerNetworkPerTopic: 4,
13992
+ /** Drops one address may make in all, per hour: filling the mailbox takes very many */
13993
+ dropsPerNetwork: 30
13994
+ });
13995
+ var PURGE_SPKI_PREFIX = Buffer.from("3039301306072a8648ce3d020106082a8648ce3d030107032200", "hex");
13996
+ var MAX_WATCHES_PER_SOCKET = 32;
13997
+ var MAX_PAGE_ITEMS = 16;
13998
+ var MAX_PAGE_CHARS = 48 * 1024;
13407
13999
  var ROUTED = /* @__PURE__ */ new Set(["offer", "answer", "candidate"]);
13408
14000
  var DID_PATTERN = /^did:key:z[1-9A-HJ-NP-Za-km-z]+$/;
13409
14001
  var CLOSE_DID_TAKEN = 4009;
@@ -13415,19 +14007,25 @@ function turnFromEnv(env) {
13415
14007
  }
13416
14008
  function createRelay(options = {}) {
13417
14009
  const { turn } = options;
14010
+ const limits = { ...MAILBOX_LIMITS, ...options.mailbox };
13418
14011
  const log = options.log ?? (() => {
13419
14012
  });
13420
14013
  const rooms = /* @__PURE__ */ new Map();
13421
14014
  const clients = /* @__PURE__ */ new Set();
13422
14015
  const perIp = /* @__PURE__ */ new Map();
13423
14016
  const turnByNetwork = /* @__PURE__ */ new Map();
14017
+ const mailbox = /* @__PURE__ */ new Map();
14018
+ const watchers = /* @__PURE__ */ new Map();
14019
+ const dropsBy = /* @__PURE__ */ new Map();
14020
+ let mailChars = 0;
14021
+ let mailSeq = 0;
13424
14022
  function canEnter(room) {
13425
14023
  if (typeof room !== "string" || room.length === 0 || room.length > MAX_ROOM_LENGTH) return false;
13426
14024
  const peers = rooms.get(room);
13427
14025
  return peers ? peers.size < MAX_PEERS_PER_ROOM : rooms.size < MAX_ROOMS;
13428
14026
  }
13429
14027
  function accept(ws, legacyRoom, ip) {
13430
- const client = { ws, legacyRoom, ip, did: null, rooms: /* @__PURE__ */ new Set(), alive: true, tokens: RATE_BURST, refilled: Date.now() };
14028
+ const client = { ws, legacyRoom, ip, did: null, rooms: /* @__PURE__ */ new Set(), watching: /* @__PURE__ */ new Set(), nonce: null, alive: true, tokens: RATE_BURST, refilled: Date.now() };
13431
14029
  clients.add(client);
13432
14030
  perIp.set(ip, (perIp.get(ip) ?? 0) + 1);
13433
14031
  ws.on("pong", () => client.alive = true);
@@ -13447,6 +14045,7 @@ function createRelay(options = {}) {
13447
14045
  if (left > 0) perIp.set(ip, left);
13448
14046
  else perIp.delete(ip);
13449
14047
  for (const room of [...client.rooms]) leave(client, room);
14048
+ for (const topic of [...client.watching]) unwatch(client, topic);
13450
14049
  };
13451
14050
  ws.on("close", drop);
13452
14051
  ws.on("error", () => ws.terminate());
@@ -13477,6 +14076,10 @@ function createRelay(options = {}) {
13477
14076
  log(`peer joined room ${short(room)} (${rooms.get(room).size} present)`);
13478
14077
  return;
13479
14078
  }
14079
+ if (message.type === "drop" || message.type === "fetch" || message.type === "unwatch" || message.type === "challenge" || message.type === "purge") {
14080
+ handleMail(client, message);
14081
+ return;
14082
+ }
13480
14083
  if (message.type === "ice") {
13481
14084
  if (client.did) offerTurn(client);
13482
14085
  return;
@@ -13494,6 +14097,137 @@ function createRelay(options = {}) {
13494
14097
  }
13495
14098
  }
13496
14099
  }
14100
+ function handleMail(client, message) {
14101
+ if (message.type === "challenge") {
14102
+ client.nonce = randomBytes4(18).toString("base64url");
14103
+ send2(client, JSON.stringify({ type: "challenge", nonce: client.nonce }));
14104
+ return;
14105
+ }
14106
+ const { topic } = message;
14107
+ if (typeof topic !== "string" || !TOPIC_PATTERN.test(topic)) return;
14108
+ if (message.type === "unwatch") {
14109
+ unwatch(client, topic);
14110
+ return;
14111
+ }
14112
+ if (message.type === "purge") {
14113
+ purge(client, message);
14114
+ return;
14115
+ }
14116
+ if (message.type === "fetch") {
14117
+ const after = Number.isSafeInteger(message.after) ? message.after : 0;
14118
+ if (message.watch === true && !client.watching.has(topic) && client.watching.size < MAX_WATCHES_PER_SOCKET) {
14119
+ client.watching.add(topic);
14120
+ if (!watchers.has(topic)) watchers.set(topic, /* @__PURE__ */ new Set());
14121
+ watchers.get(topic).add(client);
14122
+ }
14123
+ const now2 = Date.now();
14124
+ const items = [];
14125
+ let chars = 0;
14126
+ let more = false;
14127
+ for (const item2 of mailbox.get(topic) ?? []) {
14128
+ if (item2.seq <= after || item2.expires <= now2) continue;
14129
+ if (items.length >= MAX_PAGE_ITEMS || chars + item2.blob.length > MAX_PAGE_CHARS) {
14130
+ more = true;
14131
+ break;
14132
+ }
14133
+ items.push(mailItem(item2));
14134
+ chars += item2.blob.length;
14135
+ }
14136
+ send2(client, JSON.stringify({ type: "mail", topic, items, more }));
14137
+ return;
14138
+ }
14139
+ const refuse = (reason) => send2(client, JSON.stringify({ type: "refused", topic, reason }));
14140
+ const { blob } = message;
14141
+ if (typeof blob !== "string" || blob.length === 0 || blob.length > MAX_BLOB_LENGTH || !/^[A-Za-z0-9_-]+$/.test(blob)) {
14142
+ refuse("A knock is base64url, at most 12000 characters");
14143
+ return;
14144
+ }
14145
+ const id = createHash("sha256").update(blob).digest("base64url");
14146
+ const held = mailbox.get(topic) ?? [];
14147
+ if (held.some((item2) => item2.id === id)) {
14148
+ send2(client, JSON.stringify({ type: "dropped", topic, id }));
14149
+ return;
14150
+ }
14151
+ const now = Date.now();
14152
+ const network = networkOf(client.ip);
14153
+ const here = countOf(`${network}|${topic}`, now);
14154
+ const everywhere = countOf(network, now);
14155
+ if (here >= limits.dropsPerNetworkPerTopic) return refuse("Too many knocks on this door from here; try later");
14156
+ if (everywhere >= limits.dropsPerNetwork) return refuse("Too many knocks from here; try later");
14157
+ if (held.length >= MAX_MAIL_PER_TOPIC) return refuse("This door is full");
14158
+ if (!mailbox.has(topic) && mailbox.size >= limits.maxTopics) return refuse("The mailbox is full");
14159
+ const room = held.length < RESERVED_PER_TOPIC ? limits.maxChars + limits.reserveChars : limits.maxChars;
14160
+ if (mailChars + blob.length > room) return refuse("The mailbox is full");
14161
+ const ttl = Number.isFinite(message.ttl) && message.ttl > 0 ? Math.min(message.ttl, limits.ttlSeconds) : limits.ttlSeconds;
14162
+ const item = { seq: ++mailSeq, id, at: now, expires: now + ttl * 1e3, blob };
14163
+ held.push(item);
14164
+ mailbox.set(topic, held);
14165
+ mailChars += blob.length;
14166
+ counted(`${network}|${topic}`, now);
14167
+ counted(network, now);
14168
+ send2(client, JSON.stringify({ type: "dropped", topic, id }));
14169
+ const news = JSON.stringify({ type: "mail", topic, items: [mailItem(item)], more: false });
14170
+ for (const watcher of watchers.get(topic) ?? []) {
14171
+ if (watcher !== client) send2(watcher, news);
14172
+ }
14173
+ }
14174
+ const mailItem = ({ seq, id, at, blob }) => ({ seq, id, at, blob });
14175
+ function countOf(key3, now) {
14176
+ const recent = dropsBy.get(key3);
14177
+ return recent && now - recent.since < DROP_WINDOW_MS ? recent.count : 0;
14178
+ }
14179
+ function counted(key3, now) {
14180
+ const recent = dropsBy.get(key3);
14181
+ if (recent && now - recent.since < DROP_WINDOW_MS) recent.count += 1;
14182
+ else dropsBy.set(key3, { count: 1, since: now });
14183
+ }
14184
+ function purge(client, message) {
14185
+ const { topic, sign, sig } = message;
14186
+ const refuse = (reason) => send2(client, JSON.stringify({ type: "refused", topic, reason }));
14187
+ const nonce = client.nonce;
14188
+ client.nonce = null;
14189
+ const ids = message.ids === void 0 ? null : message.ids;
14190
+ if (!nonce) return refuse("Ask for a challenge first");
14191
+ if (typeof sign !== "string" || !/^[A-Za-z0-9_-]{44}$/.test(sign) || typeof sig !== "string" || sig.length > 200) return refuse("Not a purge");
14192
+ if (ids !== null && (!Array.isArray(ids) || ids.length > MAX_MAIL_PER_TOPIC || !ids.every((id) => typeof id === "string" && id.length <= 64))) {
14193
+ return refuse("Not a purge");
14194
+ }
14195
+ if (createHash("sha256").update(`weave/door-topic/v1|${sign}`).digest("base64url") !== topic) return refuse("That key is not this door\u2019s");
14196
+ let good = false;
14197
+ try {
14198
+ const key3 = createPublicKey({ key: Buffer.concat([PURGE_SPKI_PREFIX, Buffer.from(sign, "base64url")]), format: "der", type: "spki" });
14199
+ const signed = `weave/door-purge/v1|${topic}|${nonce}|${ids ? [...ids].sort().join(",") : "*"}`;
14200
+ good = verify("sha256", Buffer.from(signed), { key: key3, dsaEncoding: "ieee-p1363" }, Buffer.from(sig, "base64url"));
14201
+ } catch {
14202
+ good = false;
14203
+ }
14204
+ if (!good) return refuse("The signature does not check out");
14205
+ const held = mailbox.get(topic) ?? [];
14206
+ const gone = ids === null ? held : held.filter((item) => ids.includes(item.id));
14207
+ for (const item of gone) mailChars -= item.blob.length;
14208
+ const kept = ids === null ? [] : held.filter((item) => !ids.includes(item.id));
14209
+ if (kept.length === 0) mailbox.delete(topic);
14210
+ else mailbox.set(topic, kept);
14211
+ send2(client, JSON.stringify({ type: "purged", topic, count: gone.length }));
14212
+ }
14213
+ function unwatch(client, topic) {
14214
+ client.watching.delete(topic);
14215
+ const watching = watchers.get(topic);
14216
+ watching?.delete(client);
14217
+ if (watching?.size === 0) watchers.delete(topic);
14218
+ }
14219
+ function sweepMail() {
14220
+ const now = Date.now();
14221
+ for (const [topic, held] of mailbox) {
14222
+ const kept = held.filter((item) => item.expires > now);
14223
+ for (const item of held) if (item.expires <= now) mailChars -= item.blob.length;
14224
+ if (kept.length === 0) mailbox.delete(topic);
14225
+ else if (kept.length !== held.length) mailbox.set(topic, kept);
14226
+ }
14227
+ for (const [key3, recent] of dropsBy) {
14228
+ if (now - recent.since >= DROP_WINDOW_MS) dropsBy.delete(key3);
14229
+ }
14230
+ }
13497
14231
  function offerTurn(client) {
13498
14232
  if (!turn) return;
13499
14233
  const network = networkOf(client.ip);
@@ -13551,6 +14285,7 @@ function createRelay(options = {}) {
13551
14285
  for (const [network, held] of turnByNetwork) {
13552
14286
  if (held.expires <= now) turnByNetwork.delete(network);
13553
14287
  }
14288
+ sweepMail();
13554
14289
  for (const client of clients) {
13555
14290
  if (!client.alive) {
13556
14291
  client.ws.terminate();
@@ -14807,7 +15542,11 @@ import { mkdir as mkdir2, readFile as readFile3, rm, writeFile as writeFile2 } f
14807
15542
  import os3 from "node:os";
14808
15543
  import path4 from "node:path";
14809
15544
 
14810
- // ../src/session/connect.ts
15545
+ // ../core/src/session/stay-signed-in.ts
15546
+ var DAY2 = 24 * 60 * 60 * 1e3;
15547
+ var DURATION2 = { never: 0, "1d": DAY2, "7d": 7 * DAY2, "30d": 30 * DAY2 };
15548
+
15549
+ // ../core/src/session/connect.ts
14811
15550
  function grantSigner(grant) {
14812
15551
  let token = null;
14813
15552
  return {
@@ -14823,7 +15562,7 @@ function grantSigner(grant) {
14823
15562
  };
14824
15563
  }
14825
15564
 
14826
- // ../src/session/agent-link.ts
15565
+ // ../core/src/session/agent-link.ts
14827
15566
  var SECRET_BYTES = 16;
14828
15567
  var ROOM_PREFIX2 = utf8Encode("weave-agent-link-room-v1");
14829
15568
  var KEY_INFO = utf8Encode("weave-agent-link-key-v1");
@@ -14831,7 +15570,7 @@ var ASK = "agent-link:ask";
14831
15570
  var HEARD = "agent-link:heard";
14832
15571
  var ANSWER = "agent-link:answer";
14833
15572
  var DONE = "agent-link:done";
14834
- var MAX_NAME = 80;
15573
+ var MAX_NAME2 = 80;
14835
15574
  function readAgentCode(input) {
14836
15575
  const match = /wv_([A-Za-z0-9_-]{22})(?![A-Za-z0-9_-])/.exec(input);
14837
15576
  const secret = match ? base64UrlDecode(match[1]) : null;
@@ -14898,7 +15637,7 @@ async function acceptAgentLink(params) {
14898
15637
  done();
14899
15638
  };
14900
15639
  net.on("peer-connected", (peer) => {
14901
- void seal2({ did: params.did, name: params.name.slice(0, MAX_NAME) }, key3).then((payload) => {
15640
+ void seal2({ did: params.did, name: params.name.slice(0, MAX_NAME2) }, key3).then((payload) => {
14902
15641
  net.send(peer.did, { type: ASK, from: params.did, payload });
14903
15642
  });
14904
15643
  });