@youam/sdk 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/cli/index.cjs +448 -176
- package/dist/index.cjs +1053 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +184 -7
- package/dist/index.d.ts +184 -7
- package/dist/index.js +1047 -129
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -60,11 +60,11 @@ function extractPublicKey(tags) {
|
|
|
60
60
|
}
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
|
-
async function queryUamTxt(
|
|
63
|
+
async function queryUamTxt(domain2, timeout) {
|
|
64
64
|
const controller = new AbortController();
|
|
65
65
|
const timer = timeout != null ? setTimeout(() => controller.abort(), timeout) : void 0;
|
|
66
66
|
try {
|
|
67
|
-
const records = await (0, import_promises.resolveTxt)(`_uam.${
|
|
67
|
+
const records = await (0, import_promises.resolveTxt)(`_uam.${domain2}`);
|
|
68
68
|
const results = [];
|
|
69
69
|
for (const parts of records) {
|
|
70
70
|
const txtValue = parts.join("");
|
|
@@ -79,8 +79,8 @@ async function queryUamTxt(domain, timeout) {
|
|
|
79
79
|
if (timer != null) clearTimeout(timer);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
async function resolveKeyViaHttps(agentName,
|
|
83
|
-
const url = `https://${
|
|
82
|
+
async function resolveKeyViaHttps(agentName, domain2, timeout) {
|
|
83
|
+
const url = `https://${domain2}/.well-known/uam.json`;
|
|
84
84
|
try {
|
|
85
85
|
const resp = await fetch(url, {
|
|
86
86
|
signal: AbortSignal.timeout(timeout ?? 1e4)
|
|
@@ -115,7 +115,7 @@ var init_dns_verifier = __esm({
|
|
|
115
115
|
var import_commander = require("commander");
|
|
116
116
|
|
|
117
117
|
// src/version.ts
|
|
118
|
-
var SDK_VERSION = "0.
|
|
118
|
+
var SDK_VERSION = "0.4.0";
|
|
119
119
|
|
|
120
120
|
// src/cli/commands/init.ts
|
|
121
121
|
var import_node_os5 = require("os");
|
|
@@ -199,6 +199,18 @@ var KeyPinningError = class _KeyPinningError extends UAMError {
|
|
|
199
199
|
Object.setPrototypeOf(this, _KeyPinningError.prototype);
|
|
200
200
|
}
|
|
201
201
|
};
|
|
202
|
+
var SendRefusedError = class extends UAMError {
|
|
203
|
+
messageId;
|
|
204
|
+
code;
|
|
205
|
+
detail;
|
|
206
|
+
constructor(messageId, code, detail) {
|
|
207
|
+
super(`Relay refused message ${messageId}: [${code}] ${detail ?? ""}`.trimEnd());
|
|
208
|
+
this.name = "SendRefusedError";
|
|
209
|
+
this.messageId = messageId;
|
|
210
|
+
this.code = code;
|
|
211
|
+
this.detail = detail;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
202
214
|
|
|
203
215
|
// src/protocol/address.ts
|
|
204
216
|
var ADDRESS_RE = /^(?<agent>[a-z0-9][a-z0-9_-]{0,62}[a-z0-9]|[a-z0-9])::(?<domain>[a-z0-9](?:[a-z0-9.-]{0,253}[a-z0-9])?)$/;
|
|
@@ -325,11 +337,11 @@ function encryptPayload(plaintext, senderSigningKey, recipientVerifyKey) {
|
|
|
325
337
|
try {
|
|
326
338
|
const senderCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_sk_to_curve25519(senderSigningKey);
|
|
327
339
|
const recipientCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_pk_to_curve25519(recipientVerifyKey);
|
|
328
|
-
const
|
|
329
|
-
const encrypted = import_libsodium_wrappers.default.crypto_box_easy(plaintext,
|
|
330
|
-
const combined = new Uint8Array(
|
|
331
|
-
combined.set(
|
|
332
|
-
combined.set(encrypted,
|
|
340
|
+
const nonce2 = import_libsodium_wrappers.default.randombytes_buf(import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
|
|
341
|
+
const encrypted = import_libsodium_wrappers.default.crypto_box_easy(plaintext, nonce2, recipientCurve, senderCurve);
|
|
342
|
+
const combined = new Uint8Array(nonce2.length + encrypted.length);
|
|
343
|
+
combined.set(nonce2, 0);
|
|
344
|
+
combined.set(encrypted, nonce2.length);
|
|
333
345
|
return b64Encode(combined);
|
|
334
346
|
} catch (err) {
|
|
335
347
|
if (err instanceof EncryptionError) throw err;
|
|
@@ -339,11 +351,11 @@ function encryptPayload(plaintext, senderSigningKey, recipientVerifyKey) {
|
|
|
339
351
|
function decryptPayload(ciphertextB64, recipientSigningKey, senderVerifyKey) {
|
|
340
352
|
try {
|
|
341
353
|
const combined = b64Decode(ciphertextB64);
|
|
342
|
-
const
|
|
354
|
+
const nonce2 = combined.slice(0, import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
|
|
343
355
|
const ciphertext = combined.slice(import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
|
|
344
356
|
const recipientCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_sk_to_curve25519(recipientSigningKey);
|
|
345
357
|
const senderCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_pk_to_curve25519(senderVerifyKey);
|
|
346
|
-
return import_libsodium_wrappers.default.crypto_box_open_easy(ciphertext,
|
|
358
|
+
return import_libsodium_wrappers.default.crypto_box_open_easy(ciphertext, nonce2, senderCurve, recipientCurve);
|
|
347
359
|
} catch (err) {
|
|
348
360
|
if (err instanceof DecryptionError) throw err;
|
|
349
361
|
throw new DecryptionError(err instanceof Error ? err.message : String(err));
|
|
@@ -451,7 +463,7 @@ async function createEnvelope(fromAddress, toAddress, messageType, payloadPlaint
|
|
|
451
463
|
parseAddress(fromAddress);
|
|
452
464
|
parseAddress(toAddress);
|
|
453
465
|
const messageId = (0, import_node_crypto2.randomUUID)();
|
|
454
|
-
const
|
|
466
|
+
const nonce2 = generateNonce();
|
|
455
467
|
const timestamp = utcTimestamp();
|
|
456
468
|
let encryptedPayload;
|
|
457
469
|
if (typeValue === "handshake.request" /* HANDSHAKE_REQUEST */) {
|
|
@@ -470,7 +482,7 @@ async function createEnvelope(fromAddress, toAddress, messageType, payloadPlaint
|
|
|
470
482
|
toAddress,
|
|
471
483
|
timestamp,
|
|
472
484
|
type: typeValue,
|
|
473
|
-
nonce,
|
|
485
|
+
nonce: nonce2,
|
|
474
486
|
payload: encryptedPayload,
|
|
475
487
|
signature: "",
|
|
476
488
|
threadId: options?.threadId,
|
|
@@ -489,7 +501,7 @@ async function createEnvelope(fromAddress, toAddress, messageType, payloadPlaint
|
|
|
489
501
|
toAddress,
|
|
490
502
|
timestamp,
|
|
491
503
|
type: typeValue,
|
|
492
|
-
nonce,
|
|
504
|
+
nonce: nonce2,
|
|
493
505
|
payload: encryptedPayload,
|
|
494
506
|
signature,
|
|
495
507
|
threadId: options?.threadId,
|
|
@@ -581,8 +593,8 @@ function contactCardFromDict(d, options) {
|
|
|
581
593
|
}
|
|
582
594
|
return card;
|
|
583
595
|
}
|
|
584
|
-
async function createContactCard(
|
|
585
|
-
parseAddress(
|
|
596
|
+
async function createContactCard(address2, displayName, relay, signingKey, options) {
|
|
597
|
+
parseAddress(address2);
|
|
586
598
|
const verifyKey = signingKey.slice(32);
|
|
587
599
|
const pubKeyB64 = serializeVerifyKey(verifyKey);
|
|
588
600
|
const fp = publicKeyFingerprint(verifyKey);
|
|
@@ -592,7 +604,7 @@ async function createContactCard(address, displayName, relay, signingKey, option
|
|
|
592
604
|
];
|
|
593
605
|
const tempCard = {
|
|
594
606
|
version: UAM_VERSION,
|
|
595
|
-
address,
|
|
607
|
+
address: address2,
|
|
596
608
|
displayName,
|
|
597
609
|
description: options?.description,
|
|
598
610
|
system: options?.system,
|
|
@@ -609,7 +621,7 @@ async function createContactCard(address, displayName, relay, signingKey, option
|
|
|
609
621
|
const signature = signMessage(canonicalize(signable), signingKey);
|
|
610
622
|
return Object.freeze({
|
|
611
623
|
version: UAM_VERSION,
|
|
612
|
-
address,
|
|
624
|
+
address: address2,
|
|
613
625
|
displayName,
|
|
614
626
|
description: options?.description,
|
|
615
627
|
system: options?.system,
|
|
@@ -636,6 +648,156 @@ function verifyContactCard(card) {
|
|
|
636
648
|
verifySignature(canonicalize(signable), card.signature, vk);
|
|
637
649
|
}
|
|
638
650
|
|
|
651
|
+
// src/protocol/signed.ts
|
|
652
|
+
var import_node_crypto3 = require("crypto");
|
|
653
|
+
|
|
654
|
+
// src/protocol/didkey.ts
|
|
655
|
+
var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
656
|
+
var PREFIX = {
|
|
657
|
+
ed25519: new Uint8Array([237, 1]),
|
|
658
|
+
p256: new Uint8Array([128, 36])
|
|
659
|
+
};
|
|
660
|
+
var LENGTH = { ed25519: 32, p256: 33 };
|
|
661
|
+
var InvalidDidKeyError = class extends UAMError {
|
|
662
|
+
constructor(message) {
|
|
663
|
+
super(message);
|
|
664
|
+
this.name = "InvalidDidKeyError";
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
function b58decode(text) {
|
|
668
|
+
let n = 0n;
|
|
669
|
+
for (const c of text) {
|
|
670
|
+
const i = B58.indexOf(c);
|
|
671
|
+
if (i < 0) throw new InvalidDidKeyError(`not base58btc: ${JSON.stringify(c)}`);
|
|
672
|
+
n = n * 58n + BigInt(i);
|
|
673
|
+
}
|
|
674
|
+
const body = [];
|
|
675
|
+
while (n > 0n) {
|
|
676
|
+
body.unshift(Number(n & 0xffn));
|
|
677
|
+
n >>= 8n;
|
|
678
|
+
}
|
|
679
|
+
let zeros = 0;
|
|
680
|
+
while (zeros < text.length && text[zeros] === "1") zeros += 1;
|
|
681
|
+
return new Uint8Array([...new Array(zeros).fill(0), ...body]);
|
|
682
|
+
}
|
|
683
|
+
function checkKey(kind, key) {
|
|
684
|
+
if (key.length !== LENGTH[kind]) {
|
|
685
|
+
throw new InvalidDidKeyError(`${kind} key must be ${LENGTH[kind]} bytes`);
|
|
686
|
+
}
|
|
687
|
+
if (kind === "p256" && key[0] !== 2 && key[0] !== 3) {
|
|
688
|
+
throw new InvalidDidKeyError("P-256 key must be a compressed point");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function decodeDidKey(did2) {
|
|
692
|
+
if (typeof did2 !== "string" || !did2.startsWith("did:key:z")) {
|
|
693
|
+
throw new InvalidDidKeyError("not a did:key with base58btc multibase");
|
|
694
|
+
}
|
|
695
|
+
const raw = b58decode(did2.slice("did:key:z".length));
|
|
696
|
+
for (const kind of Object.keys(PREFIX)) {
|
|
697
|
+
const prefix = PREFIX[kind];
|
|
698
|
+
if (raw.length >= prefix.length && prefix.every((b, i) => raw[i] === b)) {
|
|
699
|
+
const key = raw.slice(prefix.length);
|
|
700
|
+
checkKey(kind, key);
|
|
701
|
+
return { kind, key };
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
throw new InvalidDidKeyError("unsupported key type");
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/protocol/signed.ts
|
|
708
|
+
var MAX_INT = 2 ** 53 - 1;
|
|
709
|
+
var SignedObjectError = class extends UAMError {
|
|
710
|
+
constructor(message) {
|
|
711
|
+
super(message);
|
|
712
|
+
this.name = "SignedObjectError";
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
var has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
716
|
+
function isPlainObject(value) {
|
|
717
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
718
|
+
const proto = Object.getPrototypeOf(value);
|
|
719
|
+
return proto === null || proto === Object.prototype;
|
|
720
|
+
}
|
|
721
|
+
var HASH_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
722
|
+
var OWNER_ID_RE = /^[A-Za-z0-9_-]{22}$/;
|
|
723
|
+
var NONCE_RE = /^[A-Za-z0-9_-]{16,64}$/;
|
|
724
|
+
var SECRET_RE = /^[A-Za-z0-9_-]{22}$/;
|
|
725
|
+
var DOMAIN_RE = /^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/;
|
|
726
|
+
function fail(path, why) {
|
|
727
|
+
throw new SignedObjectError(`${path}: ${why}`);
|
|
728
|
+
}
|
|
729
|
+
var matching = (re, what) => (v, p) => {
|
|
730
|
+
if (typeof v !== "string" || !re.test(v)) fail(p, `must be ${what}`);
|
|
731
|
+
};
|
|
732
|
+
var hash = matching(HASH_RE, "a hash (43 base64url characters)");
|
|
733
|
+
var ownerId = matching(OWNER_ID_RE, "an owner id (22 base64url characters)");
|
|
734
|
+
var nonce = matching(NONCE_RE, "a nonce (16-64 base64url characters)");
|
|
735
|
+
var secret = matching(SECRET_RE, "a secret (22 base64url characters)");
|
|
736
|
+
var domain = matching(DOMAIN_RE, "a lowercase domain");
|
|
737
|
+
var constant = (expected) => (v, p) => {
|
|
738
|
+
if (v !== expected) fail(p, `must be ${JSON.stringify(expected)}`);
|
|
739
|
+
};
|
|
740
|
+
var oneOf = (...options) => (v, p) => {
|
|
741
|
+
if (typeof v !== "string" || !options.includes(v)) fail(p, `must be one of ${options.join(", ")}`);
|
|
742
|
+
};
|
|
743
|
+
var address = (v, p) => {
|
|
744
|
+
if (typeof v !== "string") fail(p, "must be an address");
|
|
745
|
+
let full;
|
|
746
|
+
try {
|
|
747
|
+
full = parseAddress(v).full;
|
|
748
|
+
} catch {
|
|
749
|
+
return fail(p, "must be an address");
|
|
750
|
+
}
|
|
751
|
+
if (full !== v) fail(p, "must be an address in canonical (lowercase) form");
|
|
752
|
+
};
|
|
753
|
+
var did = (kind) => (v, p) => {
|
|
754
|
+
let found;
|
|
755
|
+
try {
|
|
756
|
+
found = decodeDidKey(v).kind;
|
|
757
|
+
} catch (err) {
|
|
758
|
+
return fail(p, err instanceof Error ? err.message : String(err));
|
|
759
|
+
}
|
|
760
|
+
if (found !== kind) fail(p, `must be a ${kind} did:key`);
|
|
761
|
+
};
|
|
762
|
+
var list = (item, minLen = 1, maxLen = 64) => (v, p) => {
|
|
763
|
+
if (!Array.isArray(v) || v.length < minLen || v.length > maxLen) {
|
|
764
|
+
fail(p, `must be a list of ${minLen}-${maxLen} items`);
|
|
765
|
+
}
|
|
766
|
+
v.forEach((entry, i) => item(entry, `${p}[${i}]`));
|
|
767
|
+
};
|
|
768
|
+
var object = (required, optional = {}) => (v, p) => {
|
|
769
|
+
if (!isPlainObject(v)) fail(p, "must be an object");
|
|
770
|
+
for (const key of Object.keys(v)) {
|
|
771
|
+
if (!has(required, key) && !has(optional, key)) fail(`${p}.${key}`, "unknown member");
|
|
772
|
+
}
|
|
773
|
+
for (const [key, member] of Object.entries(required)) {
|
|
774
|
+
if (!has(v, key)) fail(`${p}.${key}`, "missing");
|
|
775
|
+
member(v[key], `${p}.${key}`);
|
|
776
|
+
}
|
|
777
|
+
for (const [key, member] of Object.entries(optional)) {
|
|
778
|
+
if (has(v, key)) member(v[key], `${p}.${key}`);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
var location = (v, p) => {
|
|
782
|
+
if (v !== "shared") address(v, p);
|
|
783
|
+
};
|
|
784
|
+
var access = object({ actions: list(oneOf("message"), 1, 1), locations: list(location) });
|
|
785
|
+
var ownerRef = object({ relay: domain, ownerId, keysHash: hash });
|
|
786
|
+
var ownerRefNoKeys = object({ relay: domain, ownerId });
|
|
787
|
+
var passkey = did("p256");
|
|
788
|
+
var agentKey = did("ed25519");
|
|
789
|
+
var keysOwner = object({ relay: domain }, { ownerId });
|
|
790
|
+
var DAY = 24 * 60 * 60 * 1e3;
|
|
791
|
+
var SUBJECTS = {
|
|
792
|
+
owner: object({ kind: constant("owner"), owner: ownerRef }),
|
|
793
|
+
invitee: object({ kind: constant("invitee"), invite: hash }),
|
|
794
|
+
agent: object({ kind: constant("agent"), id: agentKey, address })
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/protocol/webauthn.ts
|
|
798
|
+
var import_node_crypto4 = require("crypto");
|
|
799
|
+
var import_p256 = require("@noble/curves/p256");
|
|
800
|
+
|
|
639
801
|
// src/sdk/config.ts
|
|
640
802
|
var import_node_os = require("os");
|
|
641
803
|
var import_node_path = require("path");
|
|
@@ -783,11 +945,11 @@ var KeyManager = class {
|
|
|
783
945
|
/**
|
|
784
946
|
* Store the address `relayUrl` gave this agent.
|
|
785
947
|
*/
|
|
786
|
-
saveAddress(name,
|
|
948
|
+
saveAddress(name, address2, relayUrl) {
|
|
787
949
|
(0, import_node_fs.mkdirSync)(this._keyDir, { recursive: true });
|
|
788
950
|
(0, import_node_fs.writeFileSync)(
|
|
789
951
|
(0, import_node_path2.join)(this._keyDir, `${name}.address`),
|
|
790
|
-
JSON.stringify({ address, relay_url: relayUrl })
|
|
952
|
+
JSON.stringify({ address: address2, relay_url: relayUrl })
|
|
791
953
|
);
|
|
792
954
|
}
|
|
793
955
|
/**
|
|
@@ -800,14 +962,14 @@ var KeyManager = class {
|
|
|
800
962
|
try {
|
|
801
963
|
const path = (0, import_node_path2.join)(this._keyDir, `${name}.address`);
|
|
802
964
|
const saved = JSON.parse((0, import_node_fs.readFileSync)(path, "utf-8"));
|
|
803
|
-
const
|
|
965
|
+
const address2 = saved?.address;
|
|
804
966
|
const savedRelay = saved?.relay_url;
|
|
805
|
-
if (typeof
|
|
967
|
+
if (typeof address2 !== "string" || typeof savedRelay !== "string") {
|
|
806
968
|
return null;
|
|
807
969
|
}
|
|
808
970
|
if (trimRelayUrl(savedRelay) !== trimRelayUrl(relayUrl)) return null;
|
|
809
|
-
parseAddress(
|
|
810
|
-
return
|
|
971
|
+
parseAddress(address2);
|
|
972
|
+
return address2;
|
|
811
973
|
} catch {
|
|
812
974
|
return null;
|
|
813
975
|
}
|
|
@@ -899,6 +1061,31 @@ function relayVouches(raw, signedAt, now, relayDomain) {
|
|
|
899
1061
|
return Math.abs(receivedAt - signedAt) <= window && age >= 0 && age <= STORED_MAIL_MAX_AGE * 1e3 + window;
|
|
900
1062
|
}
|
|
901
1063
|
|
|
1064
|
+
// src/sdk/message.ts
|
|
1065
|
+
var SEND_STATUSES = [
|
|
1066
|
+
"delivered",
|
|
1067
|
+
"stored",
|
|
1068
|
+
"relayed",
|
|
1069
|
+
"queued",
|
|
1070
|
+
"duplicate",
|
|
1071
|
+
"dropped",
|
|
1072
|
+
"pending",
|
|
1073
|
+
"accepted",
|
|
1074
|
+
"unknown"
|
|
1075
|
+
];
|
|
1076
|
+
function sendStatus(answer) {
|
|
1077
|
+
if (answer === null) return "unknown";
|
|
1078
|
+
const status = answer["status"];
|
|
1079
|
+
if (typeof status === "string" && SEND_STATUSES.includes(status) && status !== "accepted" && status !== "unknown") {
|
|
1080
|
+
return status;
|
|
1081
|
+
}
|
|
1082
|
+
return "accepted";
|
|
1083
|
+
}
|
|
1084
|
+
var PENDING_NOTE = "Not delivered: {to} only accepts messages from approved contacts. Do not resend it.";
|
|
1085
|
+
function pendingNote(to) {
|
|
1086
|
+
return PENDING_NOTE.replace("{to}", to);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
902
1089
|
// src/sdk/contact-book.ts
|
|
903
1090
|
var import_node_fs3 = require("fs");
|
|
904
1091
|
var import_node_path3 = require("path");
|
|
@@ -996,36 +1183,36 @@ var ContactBook = class {
|
|
|
996
1183
|
/**
|
|
997
1184
|
* Check if an address is in the contact book (in-memory, no I/O).
|
|
998
1185
|
*/
|
|
999
|
-
isKnown(
|
|
1000
|
-
return this._knownAddresses.has(
|
|
1186
|
+
isKnown(address2) {
|
|
1187
|
+
return this._knownAddresses.has(address2);
|
|
1001
1188
|
}
|
|
1002
1189
|
/**
|
|
1003
1190
|
* Check if an address matches any blocked pattern (O(1) set lookup).
|
|
1004
1191
|
*/
|
|
1005
|
-
isBlocked(
|
|
1006
|
-
if (this._blockedExact.has(
|
|
1192
|
+
isBlocked(address2) {
|
|
1193
|
+
if (this._blockedExact.has(address2)) {
|
|
1007
1194
|
return true;
|
|
1008
1195
|
}
|
|
1009
|
-
if (
|
|
1010
|
-
const
|
|
1011
|
-
return this._blockedDomains.has(
|
|
1196
|
+
if (address2.includes("::")) {
|
|
1197
|
+
const domain2 = address2.split("::")[1];
|
|
1198
|
+
return this._blockedDomains.has(domain2);
|
|
1012
1199
|
}
|
|
1013
1200
|
return false;
|
|
1014
1201
|
}
|
|
1015
1202
|
/**
|
|
1016
1203
|
* Look up the public key for a known contact.
|
|
1017
1204
|
*/
|
|
1018
|
-
getPublicKey(
|
|
1205
|
+
getPublicKey(address2) {
|
|
1019
1206
|
if (this._db === null) return null;
|
|
1020
|
-
const row = this._db.prepare("SELECT public_key FROM contacts WHERE address = ?").get(
|
|
1207
|
+
const row = this._db.prepare("SELECT public_key FROM contacts WHERE address = ?").get(address2);
|
|
1021
1208
|
return row?.public_key ?? null;
|
|
1022
1209
|
}
|
|
1023
1210
|
/**
|
|
1024
1211
|
* Return the relay URLs for a known contact (CARD-04).
|
|
1025
1212
|
*/
|
|
1026
|
-
getRelayUrls(
|
|
1213
|
+
getRelayUrls(address2) {
|
|
1027
1214
|
if (this._db === null) return null;
|
|
1028
|
-
const row = this._db.prepare("SELECT relay, relays_json FROM contacts WHERE address = ?").get(
|
|
1215
|
+
const row = this._db.prepare("SELECT relay, relays_json FROM contacts WHERE address = ?").get(address2);
|
|
1029
1216
|
if (!row) return null;
|
|
1030
1217
|
if (row.relays_json !== null) {
|
|
1031
1218
|
return JSON.parse(row.relays_json);
|
|
@@ -1046,19 +1233,19 @@ var ContactBook = class {
|
|
|
1046
1233
|
* @throws {KeyPinningError} If a pinned/verified contact's key would be
|
|
1047
1234
|
* replaced by a different key without `force`.
|
|
1048
1235
|
*/
|
|
1049
|
-
addContact(
|
|
1236
|
+
addContact(address2, publicKey, options) {
|
|
1050
1237
|
if (this._db === null) {
|
|
1051
1238
|
throw new Error("ContactBook not open. Call open() first.");
|
|
1052
1239
|
}
|
|
1053
|
-
const existing = this._db.prepare("SELECT public_key, trust_state FROM contacts WHERE address = ?").get(
|
|
1240
|
+
const existing = this._db.prepare("SELECT public_key, trust_state FROM contacts WHERE address = ?").get(address2);
|
|
1054
1241
|
if (existing !== void 0 && (existing.trust_state === "pinned" || existing.trust_state === "verified") && existing.public_key !== publicKey) {
|
|
1055
1242
|
if (!options?.force) {
|
|
1056
1243
|
throw new KeyPinningError(
|
|
1057
|
-
`Refusing to overwrite ${existing.trust_state} contact ${JSON.stringify(
|
|
1244
|
+
`Refusing to overwrite ${existing.trust_state} contact ${JSON.stringify(address2)}: stored public key does not match the new key. Pass force: true to rotate.`
|
|
1058
1245
|
);
|
|
1059
1246
|
}
|
|
1060
1247
|
log.warn(
|
|
1061
|
-
`Force-replacing the key of ${existing.trust_state} contact ${
|
|
1248
|
+
`Force-replacing the key of ${existing.trust_state} contact ${address2} (key rotation)`
|
|
1062
1249
|
);
|
|
1063
1250
|
}
|
|
1064
1251
|
const displayName = options?.displayName ?? null;
|
|
@@ -1077,8 +1264,8 @@ var ContactBook = class {
|
|
|
1077
1264
|
relay = COALESCE(excluded.relay, contacts.relay),
|
|
1078
1265
|
relays_json = COALESCE(excluded.relays_json, contacts.relays_json),
|
|
1079
1266
|
last_seen = datetime('now')`
|
|
1080
|
-
).run(
|
|
1081
|
-
this._knownAddresses.add(
|
|
1267
|
+
).run(address2, publicKey, displayName, trustState, trustSource, relay, relaysJson);
|
|
1268
|
+
this._knownAddresses.add(address2);
|
|
1082
1269
|
}
|
|
1083
1270
|
/**
|
|
1084
1271
|
* Return all contacts sorted by last_seen DESC.
|
|
@@ -1099,13 +1286,13 @@ var ContactBook = class {
|
|
|
1099
1286
|
/**
|
|
1100
1287
|
* Store a pending handshake request.
|
|
1101
1288
|
*/
|
|
1102
|
-
addPending(
|
|
1289
|
+
addPending(address2, contactCardJson) {
|
|
1103
1290
|
if (this._db === null) {
|
|
1104
1291
|
throw new Error("ContactBook not open. Call open() first.");
|
|
1105
1292
|
}
|
|
1106
1293
|
this._db.prepare(
|
|
1107
1294
|
`INSERT OR REPLACE INTO pending_handshakes (address, contact_card) VALUES (?, ?)`
|
|
1108
|
-
).run(
|
|
1295
|
+
).run(address2, contactCardJson);
|
|
1109
1296
|
}
|
|
1110
1297
|
/**
|
|
1111
1298
|
* Retrieve all pending handshake requests.
|
|
@@ -1124,9 +1311,9 @@ var ContactBook = class {
|
|
|
1124
1311
|
/**
|
|
1125
1312
|
* Remove a pending handshake request.
|
|
1126
1313
|
*/
|
|
1127
|
-
removePending(
|
|
1314
|
+
removePending(address2) {
|
|
1128
1315
|
if (this._db === null) return;
|
|
1129
|
-
this._db.prepare("DELETE FROM pending_handshakes WHERE address = ?").run(
|
|
1316
|
+
this._db.prepare("DELETE FROM pending_handshakes WHERE address = ?").run(address2);
|
|
1130
1317
|
}
|
|
1131
1318
|
/**
|
|
1132
1319
|
* Return pending handshakes older than N days.
|
|
@@ -1146,31 +1333,31 @@ var ContactBook = class {
|
|
|
1146
1333
|
/**
|
|
1147
1334
|
* Return the trust_state for an address, or null if unknown.
|
|
1148
1335
|
*/
|
|
1149
|
-
getTrustState(
|
|
1336
|
+
getTrustState(address2) {
|
|
1150
1337
|
if (this._db === null) return null;
|
|
1151
|
-
const row = this._db.prepare("SELECT trust_state FROM contacts WHERE address = ?").get(
|
|
1338
|
+
const row = this._db.prepare("SELECT trust_state FROM contacts WHERE address = ?").get(address2);
|
|
1152
1339
|
return row?.trust_state ?? null;
|
|
1153
1340
|
}
|
|
1154
1341
|
/**
|
|
1155
1342
|
* Set the pinned_at timestamp and trust_state='pinned' for a contact (TOFU).
|
|
1156
1343
|
*/
|
|
1157
|
-
setPinnedAt(
|
|
1344
|
+
setPinnedAt(address2) {
|
|
1158
1345
|
if (this._db === null) {
|
|
1159
1346
|
throw new Error("ContactBook not open. Call open() first.");
|
|
1160
1347
|
}
|
|
1161
1348
|
this._db.prepare(
|
|
1162
1349
|
"UPDATE contacts SET pinned_at = datetime('now'), trust_state = 'pinned' WHERE address = ?"
|
|
1163
|
-
).run(
|
|
1350
|
+
).run(address2);
|
|
1164
1351
|
}
|
|
1165
1352
|
/**
|
|
1166
1353
|
* Remove a contact by address. Returns true if a contact was deleted.
|
|
1167
1354
|
*/
|
|
1168
|
-
removeContact(
|
|
1355
|
+
removeContact(address2) {
|
|
1169
1356
|
if (this._db === null) {
|
|
1170
1357
|
throw new Error("ContactBook not open. Call open() first.");
|
|
1171
1358
|
}
|
|
1172
|
-
const result = this._db.prepare("DELETE FROM contacts WHERE address = ?").run(
|
|
1173
|
-
this._knownAddresses.delete(
|
|
1359
|
+
const result = this._db.prepare("DELETE FROM contacts WHERE address = ?").run(address2);
|
|
1360
|
+
this._knownAddresses.delete(address2);
|
|
1174
1361
|
return result.changes > 0;
|
|
1175
1362
|
}
|
|
1176
1363
|
/**
|
|
@@ -1178,8 +1365,8 @@ var ContactBook = class {
|
|
|
1178
1365
|
*
|
|
1179
1366
|
* Convenience helper for inbound message filtering (CARD-05, TOFU-02).
|
|
1180
1367
|
*/
|
|
1181
|
-
isTrustedOrVerified(
|
|
1182
|
-
const state = this.getTrustState(
|
|
1368
|
+
isTrustedOrVerified(address2) {
|
|
1369
|
+
const state = this.getTrustState(address2);
|
|
1183
1370
|
return state === "trusted" || state === "verified" || state === "pinned";
|
|
1184
1371
|
}
|
|
1185
1372
|
/**
|
|
@@ -1476,8 +1663,8 @@ var Tier3Resolver = class extends AddressResolver {
|
|
|
1476
1663
|
transport: (0, import_viem.http)(rpcUrl)
|
|
1477
1664
|
});
|
|
1478
1665
|
}
|
|
1479
|
-
async resolvePublicKey(
|
|
1480
|
-
const parsed = parseAddress(
|
|
1666
|
+
async resolvePublicKey(address2, _token, _relayUrl) {
|
|
1667
|
+
const parsed = parseAddress(address2);
|
|
1481
1668
|
const name = parsed.domain;
|
|
1482
1669
|
const cached = this._cache.get(name);
|
|
1483
1670
|
if (cached && cached.expiresAt > Date.now()) {
|
|
@@ -1541,12 +1728,12 @@ var Tier3Resolver = class extends AddressResolver {
|
|
|
1541
1728
|
// src/sdk/resolver.ts
|
|
1542
1729
|
init_dns_verifier();
|
|
1543
1730
|
var Tier1Resolver = class extends AddressResolver {
|
|
1544
|
-
async resolvePublicKey(
|
|
1731
|
+
async resolvePublicKey(address2, _token, relayUrl) {
|
|
1545
1732
|
const resp = await fetch(
|
|
1546
|
-
`${relayUrl}/api/v1/agents/${
|
|
1733
|
+
`${relayUrl}/api/v1/agents/${address2}/public-key`
|
|
1547
1734
|
);
|
|
1548
1735
|
if (resp.status === 404) {
|
|
1549
|
-
throw new UAMError(`Agent not found: ${
|
|
1736
|
+
throw new UAMError(`Agent not found: ${address2}`);
|
|
1550
1737
|
}
|
|
1551
1738
|
if (!resp.ok) {
|
|
1552
1739
|
throw new UAMError(
|
|
@@ -1558,8 +1745,8 @@ var Tier1Resolver = class extends AddressResolver {
|
|
|
1558
1745
|
}
|
|
1559
1746
|
};
|
|
1560
1747
|
var Tier2Resolver = class extends AddressResolver {
|
|
1561
|
-
async resolvePublicKey(
|
|
1562
|
-
const parsed = parseAddress(
|
|
1748
|
+
async resolvePublicKey(address2, _token, _relayUrl) {
|
|
1749
|
+
const parsed = parseAddress(address2);
|
|
1563
1750
|
const txtRecords = await queryUamTxt(parsed.domain);
|
|
1564
1751
|
for (const txt of txtRecords) {
|
|
1565
1752
|
const tags = parseUamTxt(txt);
|
|
@@ -1571,7 +1758,7 @@ var Tier2Resolver = class extends AddressResolver {
|
|
|
1571
1758
|
const httpsKey = await resolveKeyViaHttps(parsed.agent, parsed.domain);
|
|
1572
1759
|
if (httpsKey) return httpsKey;
|
|
1573
1760
|
throw new UAMError(
|
|
1574
|
-
`Could not resolve public key for ${
|
|
1761
|
+
`Could not resolve public key for ${address2} via DNS or HTTPS`
|
|
1575
1762
|
);
|
|
1576
1763
|
}
|
|
1577
1764
|
};
|
|
@@ -1587,21 +1774,21 @@ var SmartResolver = class extends AddressResolver {
|
|
|
1587
1774
|
this._tier2 = new Tier2Resolver();
|
|
1588
1775
|
this._tier3 = tier3Config ? new Tier3Resolver(tier3Config) : null;
|
|
1589
1776
|
}
|
|
1590
|
-
async resolvePublicKey(
|
|
1591
|
-
const parsed = parseAddress(
|
|
1592
|
-
const
|
|
1593
|
-
if (
|
|
1594
|
-
return this._tier1.resolvePublicKey(
|
|
1777
|
+
async resolvePublicKey(address2, token, relayUrl) {
|
|
1778
|
+
const parsed = parseAddress(address2);
|
|
1779
|
+
const domain2 = parsed.domain;
|
|
1780
|
+
if (domain2 === this.relayDomain) {
|
|
1781
|
+
return this._tier1.resolvePublicKey(address2, token, relayUrl);
|
|
1595
1782
|
}
|
|
1596
|
-
if (
|
|
1597
|
-
return this._tier2.resolvePublicKey(
|
|
1783
|
+
if (domain2.includes(".")) {
|
|
1784
|
+
return this._tier2.resolvePublicKey(address2, token, relayUrl);
|
|
1598
1785
|
}
|
|
1599
1786
|
if (!this._tier3) {
|
|
1600
1787
|
throw new UAMError(
|
|
1601
|
-
`Tier 3 resolution requires contract config. Cannot resolve dot-free domain: '${
|
|
1788
|
+
`Tier 3 resolution requires contract config. Cannot resolve dot-free domain: '${domain2}'`
|
|
1602
1789
|
);
|
|
1603
1790
|
}
|
|
1604
|
-
return this._tier3.resolvePublicKey(
|
|
1791
|
+
return this._tier3.resolvePublicKey(address2, token, relayUrl);
|
|
1605
1792
|
}
|
|
1606
1793
|
};
|
|
1607
1794
|
|
|
@@ -1610,15 +1797,23 @@ var TransportBase = class {
|
|
|
1610
1797
|
};
|
|
1611
1798
|
|
|
1612
1799
|
// src/sdk/transport/http.ts
|
|
1800
|
+
async function readAnswer(resp) {
|
|
1801
|
+
try {
|
|
1802
|
+
const body = await resp.json();
|
|
1803
|
+
return body !== null && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
1804
|
+
} catch {
|
|
1805
|
+
return {};
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1613
1808
|
var HTTPTransport = class extends TransportBase {
|
|
1614
1809
|
_relayUrl;
|
|
1615
1810
|
_token;
|
|
1616
1811
|
_address;
|
|
1617
|
-
constructor(relayUrl, token,
|
|
1812
|
+
constructor(relayUrl, token, address2) {
|
|
1618
1813
|
super();
|
|
1619
1814
|
this._relayUrl = relayUrl.replace(/\/+$/, "");
|
|
1620
1815
|
this._token = token;
|
|
1621
|
-
this._address =
|
|
1816
|
+
this._address = address2;
|
|
1622
1817
|
}
|
|
1623
1818
|
async connect() {
|
|
1624
1819
|
}
|
|
@@ -1638,6 +1833,7 @@ var HTTPTransport = class extends TransportBase {
|
|
|
1638
1833
|
`HTTP send failed: ${resp.status} ${resp.statusText}`
|
|
1639
1834
|
);
|
|
1640
1835
|
}
|
|
1836
|
+
return readAnswer(resp);
|
|
1641
1837
|
}
|
|
1642
1838
|
async receive(limit = 50) {
|
|
1643
1839
|
const url = new URL(
|
|
@@ -1670,6 +1866,7 @@ var BASE_DELAY = 1;
|
|
|
1670
1866
|
var MAX_DELAY = 60;
|
|
1671
1867
|
var JITTER_RANGE = 1;
|
|
1672
1868
|
var SEND_SETTLE_SECONDS = 5;
|
|
1869
|
+
var ACK_TIMEOUT_SECONDS = 5;
|
|
1673
1870
|
var WebSocketTransport = class extends TransportBase {
|
|
1674
1871
|
_url;
|
|
1675
1872
|
_token;
|
|
@@ -1685,8 +1882,12 @@ var WebSocketTransport = class extends TransportBase {
|
|
|
1685
1882
|
_unanswered = 0;
|
|
1686
1883
|
_settling = null;
|
|
1687
1884
|
_settled = null;
|
|
1885
|
+
/** Sends waiting for the relay's answer, by message_id. */
|
|
1886
|
+
_waiting = /* @__PURE__ */ new Map();
|
|
1688
1887
|
/** @internal How long a close waits, in ms. Replaceable in tests. */
|
|
1689
1888
|
_settleMs = SEND_SETTLE_SECONDS * 1e3;
|
|
1889
|
+
/** @internal How long send() waits for an answer, in ms. Replaceable in tests. */
|
|
1890
|
+
_ackMs = ACK_TIMEOUT_SECONDS * 1e3;
|
|
1690
1891
|
constructor(wsUrl, token, onMessage) {
|
|
1691
1892
|
super();
|
|
1692
1893
|
this._url = wsUrl;
|
|
@@ -1730,13 +1931,42 @@ var WebSocketTransport = class extends TransportBase {
|
|
|
1730
1931
|
this._ws = null;
|
|
1731
1932
|
}
|
|
1732
1933
|
this._connected = false;
|
|
1934
|
+
this._abandonWaiting();
|
|
1733
1935
|
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Send an envelope and wait for the relay's answer.
|
|
1938
|
+
*
|
|
1939
|
+
* Resolves with the ack that names the message, or null when none came
|
|
1940
|
+
* within _ackMs or the socket went: the message may still have arrived.
|
|
1941
|
+
* Rejects with SendRefusedError when the relay refuses it by message_id
|
|
1942
|
+
* (relays before 1.1 don't name it, so their refusals end as null).
|
|
1943
|
+
*/
|
|
1734
1944
|
async send(envelope) {
|
|
1735
1945
|
if (!this._ws || this._ws.readyState !== import_ws.default.OPEN) {
|
|
1736
1946
|
throw new Error("WebSocket not connected");
|
|
1737
1947
|
}
|
|
1948
|
+
const messageId = envelope["message_id"];
|
|
1949
|
+
let answer = null;
|
|
1950
|
+
let timer;
|
|
1951
|
+
if (typeof messageId === "string" && !this._waiting.has(messageId)) {
|
|
1952
|
+
answer = new Promise((resolve, reject) => {
|
|
1953
|
+
this._waiting.set(messageId, { resolve, reject });
|
|
1954
|
+
timer = setTimeout(() => {
|
|
1955
|
+
log.warn(
|
|
1956
|
+
`No answer from the relay for ${messageId} within ${this._ackMs / 1e3}s`
|
|
1957
|
+
);
|
|
1958
|
+
this._settle(messageId, null);
|
|
1959
|
+
}, this._ackMs);
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1738
1962
|
this._ws.send(JSON.stringify(envelope));
|
|
1739
1963
|
this._unanswered += 1;
|
|
1964
|
+
if (answer === null) return null;
|
|
1965
|
+
try {
|
|
1966
|
+
return await answer;
|
|
1967
|
+
} finally {
|
|
1968
|
+
clearTimeout(timer);
|
|
1969
|
+
}
|
|
1740
1970
|
}
|
|
1741
1971
|
async receive(limit = 50) {
|
|
1742
1972
|
const result = this._pending.slice(0, limit);
|
|
@@ -1763,6 +1993,21 @@ var WebSocketTransport = class extends TransportBase {
|
|
|
1763
1993
|
});
|
|
1764
1994
|
return this._settling;
|
|
1765
1995
|
}
|
|
1996
|
+
/** Hand the relay's answer (or a refusal) to the send waiting for it. */
|
|
1997
|
+
_settle(messageId, answer, error) {
|
|
1998
|
+
if (typeof messageId !== "string") return;
|
|
1999
|
+
const waiting = this._waiting.get(messageId);
|
|
2000
|
+
if (!waiting) return;
|
|
2001
|
+
this._waiting.delete(messageId);
|
|
2002
|
+
if (error) waiting.reject(error);
|
|
2003
|
+
else waiting.resolve(answer);
|
|
2004
|
+
}
|
|
2005
|
+
/** The socket went: no answers will come for what it carried. */
|
|
2006
|
+
_abandonWaiting() {
|
|
2007
|
+
const waiting = [...this._waiting.values()];
|
|
2008
|
+
this._waiting.clear();
|
|
2009
|
+
for (const w of waiting) w.resolve(null);
|
|
2010
|
+
}
|
|
1766
2011
|
_answered() {
|
|
1767
2012
|
this._unanswered = Math.max(0, this._unanswered - 1);
|
|
1768
2013
|
if (this._unanswered === 0) this._settled?.();
|
|
@@ -1794,6 +2039,7 @@ var WebSocketTransport = class extends TransportBase {
|
|
|
1794
2039
|
this._ws = null;
|
|
1795
2040
|
this._unanswered = 0;
|
|
1796
2041
|
this._settled?.();
|
|
2042
|
+
this._abandonWaiting();
|
|
1797
2043
|
if (!this._running) return;
|
|
1798
2044
|
attempt += 1;
|
|
1799
2045
|
const delay = Math.min(BASE_DELAY * Math.pow(2, attempt), MAX_DELAY);
|
|
@@ -1823,16 +2069,28 @@ var WebSocketTransport = class extends TransportBase {
|
|
|
1823
2069
|
}
|
|
1824
2070
|
} else if (msgType === "ack") {
|
|
1825
2071
|
this._answered();
|
|
2072
|
+
this._settle(msg["message_id"], msg);
|
|
2073
|
+
} else if (msgType === "relay.notice") {
|
|
2074
|
+
log.warn(
|
|
2075
|
+
`Relay notice: message ${String(msg["message_id"])} is ${String(msg["status"])}`
|
|
2076
|
+
);
|
|
1826
2077
|
} else if (msgType === "error" || "error" in msg) {
|
|
1827
2078
|
this._answered();
|
|
2079
|
+
const code = String(msg["error"] ?? msg["code"] ?? "error");
|
|
2080
|
+
const detail = typeof msg["detail"] === "string" ? msg["detail"] : void 0;
|
|
2081
|
+
log.warn(`Relay error: [${code}] ${detail ?? ""}`.trimEnd());
|
|
2082
|
+
const messageId = msg["message_id"];
|
|
2083
|
+
if (typeof messageId === "string") {
|
|
2084
|
+
this._settle(messageId, null, new SendRefusedError(messageId, code, detail));
|
|
2085
|
+
}
|
|
1828
2086
|
}
|
|
1829
2087
|
}
|
|
1830
2088
|
};
|
|
1831
2089
|
|
|
1832
2090
|
// src/sdk/transport/index.ts
|
|
1833
|
-
function createTransport(config, token,
|
|
2091
|
+
function createTransport(config, token, address2, onMessage) {
|
|
1834
2092
|
if (config.transportType === "http") {
|
|
1835
|
-
return new HTTPTransport(config.relayUrl, token,
|
|
2093
|
+
return new HTTPTransport(config.relayUrl, token, address2);
|
|
1836
2094
|
} else {
|
|
1837
2095
|
return new WebSocketTransport(config.relayWsUrl, token, onMessage);
|
|
1838
2096
|
}
|
|
@@ -2225,7 +2483,12 @@ var Agent = class {
|
|
|
2225
2483
|
}
|
|
2226
2484
|
// -- Messaging -----------------------------------------------------------
|
|
2227
2485
|
/**
|
|
2228
|
-
* Send an encrypted, signed message.
|
|
2486
|
+
* Send an encrypted, signed message.
|
|
2487
|
+
*
|
|
2488
|
+
* Returns the message id with what the relay did with it: `status`
|
|
2489
|
+
* "pending" means the recipient only takes approved contacts, and the
|
|
2490
|
+
* message was not kept (don't resend it). `delivered` is true only when
|
|
2491
|
+
* the relay says the recipient has it.
|
|
2229
2492
|
*/
|
|
2230
2493
|
async send(toAddress, message, options) {
|
|
2231
2494
|
await this._ensureConnected();
|
|
@@ -2257,14 +2520,20 @@ var Agent = class {
|
|
|
2257
2520
|
);
|
|
2258
2521
|
const wire = toWireDict(envelope);
|
|
2259
2522
|
const relayUrls = await this._getRelayUrls(toAddress);
|
|
2523
|
+
let answer;
|
|
2260
2524
|
if (relayUrls !== null && relayUrls.length === 1 && relayUrls[0] === this._config.relayUrl) {
|
|
2261
|
-
await this._transport.send(wire);
|
|
2525
|
+
answer = await this._transport.send(wire);
|
|
2262
2526
|
} else if (relayUrls !== null && relayUrls.length > 0) {
|
|
2263
|
-
await this._trySendWithFailover(wire, relayUrls);
|
|
2527
|
+
answer = await this._trySendWithFailover(wire, relayUrls);
|
|
2264
2528
|
} else {
|
|
2265
|
-
await this._transport.send(wire);
|
|
2529
|
+
answer = await this._transport.send(wire);
|
|
2266
2530
|
}
|
|
2267
|
-
|
|
2531
|
+
const status = sendStatus(answer);
|
|
2532
|
+
return {
|
|
2533
|
+
messageId: envelope.messageId,
|
|
2534
|
+
status,
|
|
2535
|
+
delivered: status === "delivered"
|
|
2536
|
+
};
|
|
2268
2537
|
}
|
|
2269
2538
|
/**
|
|
2270
2539
|
* Retrieve, decrypt, and verify pending messages.
|
|
@@ -2309,43 +2578,43 @@ var Agent = class {
|
|
|
2309
2578
|
* Adds the sender to contacts with trust_source='explicit-approval'
|
|
2310
2579
|
* and sends handshake.accept back.
|
|
2311
2580
|
*/
|
|
2312
|
-
async approve(
|
|
2581
|
+
async approve(address2) {
|
|
2313
2582
|
await this._ensureConnected();
|
|
2314
2583
|
const pendingList = this._contactBook.getPending();
|
|
2315
|
-
const entry = pendingList.find((p) => p.address ===
|
|
2584
|
+
const entry = pendingList.find((p) => p.address === address2);
|
|
2316
2585
|
if (!entry) {
|
|
2317
|
-
throw new UAMError(`No pending handshake from ${
|
|
2586
|
+
throw new UAMError(`No pending handshake from ${address2}`);
|
|
2318
2587
|
}
|
|
2319
2588
|
const cardDict = JSON.parse(entry.contactCard);
|
|
2320
2589
|
const card = contactCardFromDict(cardDict);
|
|
2321
2590
|
verifyContactCard(card);
|
|
2322
2591
|
const senderVk = deserializeVerifyKey(card.publicKey);
|
|
2323
|
-
bindCardToEnvelope(card, { fromAddress:
|
|
2592
|
+
bindCardToEnvelope(card, { fromAddress: address2 }, senderVk);
|
|
2324
2593
|
this._contactBook.addContact(card.address, card.publicKey, {
|
|
2325
2594
|
displayName: card.displayName,
|
|
2326
2595
|
trustState: "trusted",
|
|
2327
2596
|
trustSource: "explicit-approval"
|
|
2328
2597
|
});
|
|
2329
|
-
this._contactBook.removePending(
|
|
2330
|
-
await this._handshake._sendAccept(this,
|
|
2598
|
+
this._contactBook.removePending(address2);
|
|
2599
|
+
await this._handshake._sendAccept(this, address2, senderVk);
|
|
2331
2600
|
}
|
|
2332
2601
|
/**
|
|
2333
2602
|
* Deny a pending handshake request.
|
|
2334
2603
|
*
|
|
2335
2604
|
* Removes the entry from pending and sends handshake.deny.
|
|
2336
2605
|
*/
|
|
2337
|
-
async deny(
|
|
2606
|
+
async deny(address2) {
|
|
2338
2607
|
await this._ensureConnected();
|
|
2339
2608
|
const pendingList = this._contactBook.getPending();
|
|
2340
|
-
const entry = pendingList.find((p) => p.address ===
|
|
2609
|
+
const entry = pendingList.find((p) => p.address === address2);
|
|
2341
2610
|
if (!entry) {
|
|
2342
|
-
throw new UAMError(`No pending handshake from ${
|
|
2611
|
+
throw new UAMError(`No pending handshake from ${address2}`);
|
|
2343
2612
|
}
|
|
2344
2613
|
const cardDict = JSON.parse(entry.contactCard);
|
|
2345
2614
|
const card = contactCardFromDict(cardDict);
|
|
2346
2615
|
const senderVk = deserializeVerifyKey(card.publicKey);
|
|
2347
|
-
this._contactBook.removePending(
|
|
2348
|
-
await this._handshake._sendDeny(this,
|
|
2616
|
+
this._contactBook.removePending(address2);
|
|
2617
|
+
await this._handshake._sendDeny(this, address2, senderVk);
|
|
2349
2618
|
}
|
|
2350
2619
|
/**
|
|
2351
2620
|
* Block an address or domain pattern (HAND-04).
|
|
@@ -2371,7 +2640,7 @@ var Agent = class {
|
|
|
2371
2640
|
*
|
|
2372
2641
|
* Returns `true` if verified, `false` on timeout.
|
|
2373
2642
|
*/
|
|
2374
|
-
async verifyDomain(
|
|
2643
|
+
async verifyDomain(domain2, options) {
|
|
2375
2644
|
const { generateTxtRecord: generateTxtRecord2 } = await Promise.resolve().then(() => (init_dns_verifier(), dns_verifier_exports));
|
|
2376
2645
|
await this._ensureConnected();
|
|
2377
2646
|
const timeout = options?.timeout ?? 3e5;
|
|
@@ -2392,7 +2661,7 @@ var Agent = class {
|
|
|
2392
2661
|
"Content-Type": "application/json",
|
|
2393
2662
|
Authorization: `Bearer ${this._token}`
|
|
2394
2663
|
},
|
|
2395
|
-
body: JSON.stringify({ domain }),
|
|
2664
|
+
body: JSON.stringify({ domain: domain2 }),
|
|
2396
2665
|
signal: AbortSignal.timeout(3e4)
|
|
2397
2666
|
}
|
|
2398
2667
|
);
|
|
@@ -2445,7 +2714,7 @@ var Agent = class {
|
|
|
2445
2714
|
if (!resp.ok) {
|
|
2446
2715
|
throw new Error(`HTTP ${resp.status}`);
|
|
2447
2716
|
}
|
|
2448
|
-
return;
|
|
2717
|
+
return await readAnswer(resp);
|
|
2449
2718
|
} catch (err) {
|
|
2450
2719
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
2451
2720
|
}
|
|
@@ -2743,13 +3012,13 @@ var Agent = class {
|
|
|
2743
3012
|
};
|
|
2744
3013
|
let resp = await this._postRegistration(body);
|
|
2745
3014
|
if (resp.status === 409) {
|
|
2746
|
-
const
|
|
3015
|
+
const address3 = await this._addressOnRelay();
|
|
2747
3016
|
resp = await this._postRegistration({
|
|
2748
3017
|
...body,
|
|
2749
|
-
proof: this._registrationProof(
|
|
3018
|
+
proof: this._registrationProof(address3)
|
|
2750
3019
|
});
|
|
2751
3020
|
if (resp.status === 409 || resp.status === 403) {
|
|
2752
|
-
throw this._ownershipNotProven(
|
|
3021
|
+
throw this._ownershipNotProven(address3);
|
|
2753
3022
|
}
|
|
2754
3023
|
}
|
|
2755
3024
|
if (!resp.ok) {
|
|
@@ -2759,26 +3028,26 @@ var Agent = class {
|
|
|
2759
3028
|
);
|
|
2760
3029
|
}
|
|
2761
3030
|
const data = await resp.json();
|
|
2762
|
-
const
|
|
2763
|
-
this._useAddress(
|
|
3031
|
+
const address2 = data["address"];
|
|
3032
|
+
this._useAddress(address2);
|
|
2764
3033
|
this._token = data["token"];
|
|
2765
3034
|
const { name } = this._config;
|
|
2766
3035
|
const token = this._token;
|
|
2767
3036
|
this._tryToSave("token", () => this._keyManager.saveToken(name, token));
|
|
2768
|
-
this._saveAddress(
|
|
3037
|
+
this._saveAddress(address2);
|
|
2769
3038
|
}
|
|
2770
3039
|
/** This agent's address on the relay, for signing a proof. */
|
|
2771
3040
|
async _addressOnRelay() {
|
|
2772
3041
|
const { name, explicitRelayDomain, relayDomain, relayUrl } = this._config;
|
|
2773
3042
|
if (explicitRelayDomain !== null) return `${name}::${explicitRelayDomain}`;
|
|
2774
|
-
const
|
|
2775
|
-
if (
|
|
2776
|
-
if (!this._relayMayUse(
|
|
3043
|
+
const domain2 = await this._fetchRelayDomain();
|
|
3044
|
+
if (domain2 === null) return `${name}::${relayDomain}`;
|
|
3045
|
+
if (!this._relayMayUse(domain2)) {
|
|
2777
3046
|
throw new UAMError(
|
|
2778
|
-
`The relay at ${relayUrl} says its domain is ${
|
|
3047
|
+
`The relay at ${relayUrl} says its domain is ${domain2}. Pass the domain option as ${JSON.stringify(domain2)} if that is right.`
|
|
2779
3048
|
);
|
|
2780
3049
|
}
|
|
2781
|
-
return `${name}::${
|
|
3050
|
+
return `${name}::${domain2}`;
|
|
2782
3051
|
}
|
|
2783
3052
|
/**
|
|
2784
3053
|
* True if the relay's URL fits `domain`, so a proof can be signed for it.
|
|
@@ -2786,14 +3055,14 @@ var Agent = class {
|
|
|
2786
3055
|
* A proof names an address on one relay, so it is only signed for a domain
|
|
2787
3056
|
* this URL belongs to. A relay on this machine may use any domain.
|
|
2788
3057
|
*/
|
|
2789
|
-
_relayMayUse(
|
|
3058
|
+
_relayMayUse(domain2) {
|
|
2790
3059
|
let host;
|
|
2791
3060
|
try {
|
|
2792
3061
|
host = new URL(this._config.relayUrl).hostname.toLowerCase();
|
|
2793
3062
|
} catch {
|
|
2794
3063
|
return false;
|
|
2795
3064
|
}
|
|
2796
|
-
const wanted =
|
|
3065
|
+
const wanted = domain2.toLowerCase();
|
|
2797
3066
|
return host === wanted || host.endsWith(`.${wanted}`) || host === "localhost" || host === "[::1]" || /^127\.\d+\.\d+\.\d+$/.test(host);
|
|
2798
3067
|
}
|
|
2799
3068
|
_postRegistration(body) {
|
|
@@ -2808,27 +3077,27 @@ var Agent = class {
|
|
|
2808
3077
|
*
|
|
2809
3078
|
* The signed string must stay byte-identical to the Python SDK's.
|
|
2810
3079
|
*/
|
|
2811
|
-
_registrationProof(
|
|
3080
|
+
_registrationProof(address2) {
|
|
2812
3081
|
const timestamp = utcTimestamp();
|
|
2813
3082
|
const signature = signMessage(
|
|
2814
3083
|
new TextEncoder().encode(`uam-register-v1
|
|
2815
|
-
${
|
|
3084
|
+
${address2}
|
|
2816
3085
|
${timestamp}`),
|
|
2817
3086
|
this._keyManager.signingKey
|
|
2818
3087
|
);
|
|
2819
3088
|
return { timestamp, signature };
|
|
2820
3089
|
}
|
|
2821
|
-
_ownershipNotProven(
|
|
3090
|
+
_ownershipNotProven(address2) {
|
|
2822
3091
|
return new UAMError(
|
|
2823
|
-
`Address already registered, and this key could not prove ownership: ` +
|
|
3092
|
+
`Address already registered, and this key could not prove ownership: ` + address2
|
|
2824
3093
|
);
|
|
2825
3094
|
}
|
|
2826
3095
|
/** Remember `address` for this relay, if the key folder allows it. */
|
|
2827
|
-
_saveAddress(
|
|
3096
|
+
_saveAddress(address2) {
|
|
2828
3097
|
const { name, relayUrl } = this._config;
|
|
2829
3098
|
this._tryToSave(
|
|
2830
3099
|
"address",
|
|
2831
|
-
() => this._keyManager.saveAddress(name,
|
|
3100
|
+
() => this._keyManager.saveAddress(name, address2, relayUrl)
|
|
2832
3101
|
);
|
|
2833
3102
|
}
|
|
2834
3103
|
/** Saving is best effort: a key folder we can't write doesn't stop us. */
|
|
@@ -2841,12 +3110,12 @@ ${timestamp}`),
|
|
|
2841
3110
|
}
|
|
2842
3111
|
}
|
|
2843
3112
|
/** Take `address` as ours; its domain is this relay's real domain. */
|
|
2844
|
-
_useAddress(
|
|
2845
|
-
this._address =
|
|
2846
|
-
const
|
|
2847
|
-
this._config.relayDomain =
|
|
3113
|
+
_useAddress(address2) {
|
|
3114
|
+
this._address = address2;
|
|
3115
|
+
const domain2 = parseAddress(address2).domain;
|
|
3116
|
+
this._config.relayDomain = domain2;
|
|
2848
3117
|
if (this._resolver instanceof SmartResolver) {
|
|
2849
|
-
this._resolver.relayDomain =
|
|
3118
|
+
this._resolver.relayDomain = domain2;
|
|
2850
3119
|
}
|
|
2851
3120
|
}
|
|
2852
3121
|
/**
|
|
@@ -2861,11 +3130,11 @@ ${timestamp}`),
|
|
|
2861
3130
|
if (explicitRelayDomain !== null) return `${name}::${explicitRelayDomain}`;
|
|
2862
3131
|
const saved = this._keyManager.loadAddress(name, this._config.relayUrl);
|
|
2863
3132
|
if (saved) return saved;
|
|
2864
|
-
const
|
|
2865
|
-
if (
|
|
2866
|
-
const
|
|
2867
|
-
this._saveAddress(
|
|
2868
|
-
return
|
|
3133
|
+
const domain2 = await this._fetchRelayDomain();
|
|
3134
|
+
if (domain2 === null) return `${name}::${this._config.relayDomain}`;
|
|
3135
|
+
const address2 = `${name}::${domain2}`;
|
|
3136
|
+
this._saveAddress(address2);
|
|
3137
|
+
return address2;
|
|
2869
3138
|
}
|
|
2870
3139
|
/** Ask the relay for its domain; null if it cannot say. */
|
|
2871
3140
|
async _fetchRelayDomain() {
|
|
@@ -2874,9 +3143,9 @@ ${timestamp}`),
|
|
|
2874
3143
|
const resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
2875
3144
|
if (!resp.ok) return null;
|
|
2876
3145
|
const data = await resp.json();
|
|
2877
|
-
const
|
|
2878
|
-
if (typeof
|
|
2879
|
-
return parseAddress(`${this._config.name}::${
|
|
3146
|
+
const domain2 = data["relay_domain"];
|
|
3147
|
+
if (typeof domain2 !== "string" || domain2 === "") return null;
|
|
3148
|
+
return parseAddress(`${this._config.name}::${domain2}`).domain;
|
|
2880
3149
|
} catch {
|
|
2881
3150
|
return null;
|
|
2882
3151
|
}
|
|
@@ -2915,18 +3184,18 @@ async function initCommand(options) {
|
|
|
2915
3184
|
const keyPath = (0, import_node_path7.join)(cfg.keyDir, `${agentName}.key`);
|
|
2916
3185
|
if ((0, import_node_fs6.existsSync)(keyPath)) {
|
|
2917
3186
|
km.loadOrGenerate(agentName);
|
|
2918
|
-
const
|
|
3187
|
+
const address3 = km.loadAddress(agentName, cfg.relayUrl) ?? `${agentName}::${cfg.relayDomain}`;
|
|
2919
3188
|
const fp2 = publicKeyFingerprint(km.verifyKey);
|
|
2920
|
-
console.log(`Agent already initialized: ${
|
|
3189
|
+
console.log(`Agent already initialized: ${address3}`);
|
|
2921
3190
|
console.log(`Fingerprint: ${fp2}`);
|
|
2922
3191
|
return;
|
|
2923
3192
|
}
|
|
2924
3193
|
const agent = new Agent(agentName, { relay: options.relay });
|
|
2925
3194
|
await agent.connect();
|
|
2926
|
-
const
|
|
3195
|
+
const address2 = agent.address;
|
|
2927
3196
|
const fp = publicKeyFingerprint(agent._keyManager.verifyKey);
|
|
2928
3197
|
await agent.close();
|
|
2929
|
-
console.log(`Initialized agent: ${
|
|
3198
|
+
console.log(`Initialized agent: ${address2}`);
|
|
2930
3199
|
console.log(`Fingerprint: ${fp}`);
|
|
2931
3200
|
} catch (err) {
|
|
2932
3201
|
cliError(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -2934,7 +3203,7 @@ async function initCommand(options) {
|
|
|
2934
3203
|
}
|
|
2935
3204
|
|
|
2936
3205
|
// src/cli/commands/send.ts
|
|
2937
|
-
async function sendCommand(
|
|
3206
|
+
async function sendCommand(address2, message, options) {
|
|
2938
3207
|
const agentName = options.name ?? findAgentName();
|
|
2939
3208
|
if (!agentName) {
|
|
2940
3209
|
cliError("No agent initialized. Run `uam init` first.");
|
|
@@ -2942,9 +3211,12 @@ async function sendCommand(address, message, options) {
|
|
|
2942
3211
|
try {
|
|
2943
3212
|
const agent = new Agent(agentName);
|
|
2944
3213
|
await agent.connect();
|
|
2945
|
-
const
|
|
3214
|
+
const result = await agent.send(address2, message);
|
|
2946
3215
|
await agent.close();
|
|
2947
|
-
console.log(`Message
|
|
3216
|
+
console.log(`Message to ${address2}: ${result.status} (id: ${result.messageId})`);
|
|
3217
|
+
if (result.status === "pending") {
|
|
3218
|
+
console.log(pendingNote(address2));
|
|
3219
|
+
}
|
|
2948
3220
|
} catch (err) {
|
|
2949
3221
|
cliError(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2950
3222
|
}
|
|
@@ -3011,9 +3283,9 @@ async function whoamiCommand(options) {
|
|
|
3011
3283
|
await sodiumReady;
|
|
3012
3284
|
const km = new KeyManager(cfg.keyDir);
|
|
3013
3285
|
km.loadOrGenerate(agentName);
|
|
3014
|
-
const
|
|
3286
|
+
const address2 = km.loadAddress(agentName, cfg.relayUrl) ?? `${agentName}::${cfg.relayDomain}`;
|
|
3015
3287
|
const fp = publicKeyFingerprint(km.verifyKey);
|
|
3016
|
-
console.log(`Address: ${
|
|
3288
|
+
console.log(`Address: ${address2}`);
|
|
3017
3289
|
console.log(`Fingerprint: ${fp}`);
|
|
3018
3290
|
console.log(`Key file: ${keyPath}`);
|
|
3019
3291
|
}
|
|
@@ -3084,7 +3356,7 @@ async function pendingCommand(options) {
|
|
|
3084
3356
|
}
|
|
3085
3357
|
|
|
3086
3358
|
// src/cli/commands/approve.ts
|
|
3087
|
-
async function approveCommand(
|
|
3359
|
+
async function approveCommand(address2, options) {
|
|
3088
3360
|
const agentName = options.name ?? findAgentName();
|
|
3089
3361
|
if (!agentName) {
|
|
3090
3362
|
cliError("No agent initialized. Run `uam init` first.");
|
|
@@ -3092,16 +3364,16 @@ async function approveCommand(address, options) {
|
|
|
3092
3364
|
try {
|
|
3093
3365
|
const agent = new Agent(agentName);
|
|
3094
3366
|
await agent.connect();
|
|
3095
|
-
await agent.approve(
|
|
3367
|
+
await agent.approve(address2);
|
|
3096
3368
|
await agent.close();
|
|
3097
|
-
console.log(`Approved: ${
|
|
3369
|
+
console.log(`Approved: ${address2}`);
|
|
3098
3370
|
} catch (err) {
|
|
3099
3371
|
cliError(`Error: ${err instanceof Error ? err.message : err}`);
|
|
3100
3372
|
}
|
|
3101
3373
|
}
|
|
3102
3374
|
|
|
3103
3375
|
// src/cli/commands/deny.ts
|
|
3104
|
-
async function denyCommand(
|
|
3376
|
+
async function denyCommand(address2, options) {
|
|
3105
3377
|
const agentName = options.name ?? findAgentName();
|
|
3106
3378
|
if (!agentName) {
|
|
3107
3379
|
cliError("No agent initialized. Run `uam init` first.");
|
|
@@ -3109,9 +3381,9 @@ async function denyCommand(address, options) {
|
|
|
3109
3381
|
try {
|
|
3110
3382
|
const agent = new Agent(agentName);
|
|
3111
3383
|
await agent.connect();
|
|
3112
|
-
await agent.deny(
|
|
3384
|
+
await agent.deny(address2);
|
|
3113
3385
|
await agent.close();
|
|
3114
|
-
console.log(`Denied: ${
|
|
3386
|
+
console.log(`Denied: ${address2}`);
|
|
3115
3387
|
} catch (err) {
|
|
3116
3388
|
cliError(`Error: ${err instanceof Error ? err.message : err}`);
|
|
3117
3389
|
}
|
|
@@ -3153,21 +3425,21 @@ async function unblockCommand(pattern, options) {
|
|
|
3153
3425
|
}
|
|
3154
3426
|
|
|
3155
3427
|
// src/cli/commands/contact.ts
|
|
3156
|
-
async function contactFingerprintCommand(
|
|
3428
|
+
async function contactFingerprintCommand(address2, options) {
|
|
3157
3429
|
const agentName = options.name ?? findAgentName();
|
|
3158
3430
|
const cfg = new SDKConfig({ name: agentName ?? "_probe" });
|
|
3159
3431
|
const book = new ContactBook(cfg.dataDir);
|
|
3160
3432
|
try {
|
|
3161
3433
|
book.open();
|
|
3162
|
-
const pkStr = book.getPublicKey(
|
|
3434
|
+
const pkStr = book.getPublicKey(address2);
|
|
3163
3435
|
book.close();
|
|
3164
3436
|
if (pkStr === null) {
|
|
3165
|
-
cliError(`Contact not found: ${
|
|
3437
|
+
cliError(`Contact not found: ${address2}`);
|
|
3166
3438
|
}
|
|
3167
3439
|
const vk = deserializeVerifyKey(pkStr);
|
|
3168
3440
|
const fp = publicKeyFingerprint(vk);
|
|
3169
3441
|
const shortFp = fp.slice(0, 16);
|
|
3170
|
-
console.log(`Address: ${
|
|
3442
|
+
console.log(`Address: ${address2}`);
|
|
3171
3443
|
console.log(`Fingerprint: ${shortFp}`);
|
|
3172
3444
|
console.log(`Full: ${fp}`);
|
|
3173
3445
|
} catch (err) {
|
|
@@ -3177,43 +3449,43 @@ async function contactFingerprintCommand(address, options) {
|
|
|
3177
3449
|
cliError(`Error: ${err instanceof Error ? err.message : err}`);
|
|
3178
3450
|
}
|
|
3179
3451
|
}
|
|
3180
|
-
async function contactVerifyCommand(
|
|
3452
|
+
async function contactVerifyCommand(address2, options) {
|
|
3181
3453
|
const agentName = options.name ?? findAgentName();
|
|
3182
3454
|
const cfg = new SDKConfig({ name: agentName ?? "_probe" });
|
|
3183
3455
|
const book = new ContactBook(cfg.dataDir);
|
|
3184
3456
|
try {
|
|
3185
3457
|
book.open();
|
|
3186
|
-
const pkStr = book.getPublicKey(
|
|
3458
|
+
const pkStr = book.getPublicKey(address2);
|
|
3187
3459
|
if (pkStr === null) {
|
|
3188
3460
|
book.close();
|
|
3189
|
-
cliError(`Contact not found: ${
|
|
3461
|
+
cliError(`Contact not found: ${address2}`);
|
|
3190
3462
|
}
|
|
3191
3463
|
const vk = deserializeVerifyKey(pkStr);
|
|
3192
3464
|
const fp = publicKeyFingerprint(vk);
|
|
3193
|
-
console.log(`Fingerprint for ${
|
|
3194
|
-
book.addContact(
|
|
3465
|
+
console.log(`Fingerprint for ${address2}: ${fp.slice(0, 16)}`);
|
|
3466
|
+
book.addContact(address2, pkStr, {
|
|
3195
3467
|
trustState: "verified",
|
|
3196
3468
|
trustSource: "manual-verify"
|
|
3197
3469
|
});
|
|
3198
3470
|
book.close();
|
|
3199
|
-
console.log(`Contact ${
|
|
3471
|
+
console.log(`Contact ${address2} verified. Trust state: verified`);
|
|
3200
3472
|
} catch (err) {
|
|
3201
3473
|
cliError(`Error: ${err instanceof Error ? err.message : err}`);
|
|
3202
3474
|
}
|
|
3203
3475
|
}
|
|
3204
|
-
async function contactRemoveCommand(
|
|
3476
|
+
async function contactRemoveCommand(address2, options) {
|
|
3205
3477
|
const agentName = options.name ?? findAgentName();
|
|
3206
3478
|
const cfg = new SDKConfig({ name: agentName ?? "_probe" });
|
|
3207
3479
|
const book = new ContactBook(cfg.dataDir);
|
|
3208
3480
|
try {
|
|
3209
3481
|
book.open();
|
|
3210
|
-
const removed = book.removeContact(
|
|
3482
|
+
const removed = book.removeContact(address2);
|
|
3211
3483
|
book.close();
|
|
3212
3484
|
if (!removed) {
|
|
3213
|
-
cliError(`Contact not found: ${
|
|
3485
|
+
cliError(`Contact not found: ${address2}`);
|
|
3214
3486
|
}
|
|
3215
3487
|
console.log(
|
|
3216
|
-
`Contact ${
|
|
3488
|
+
`Contact ${address2} removed. Future messages will re-resolve the public key.`
|
|
3217
3489
|
);
|
|
3218
3490
|
} catch (err) {
|
|
3219
3491
|
cliError(`Error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -3222,7 +3494,7 @@ async function contactRemoveCommand(address, options) {
|
|
|
3222
3494
|
|
|
3223
3495
|
// src/cli/commands/verify-domain.ts
|
|
3224
3496
|
init_dns_verifier();
|
|
3225
|
-
async function verifyDomainCommand(
|
|
3497
|
+
async function verifyDomainCommand(domain2, options) {
|
|
3226
3498
|
const agentName = options.name ?? findAgentName();
|
|
3227
3499
|
if (!agentName) {
|
|
3228
3500
|
cliError("No agent initialized. Run `uam init` first.");
|
|
@@ -3235,27 +3507,27 @@ async function verifyDomainCommand(domain, options) {
|
|
|
3235
3507
|
const pubkey = agent.publicKey;
|
|
3236
3508
|
const relayUrl = agent._config.relayUrl;
|
|
3237
3509
|
const txtValue = generateTxtRecord(pubkey, relayUrl);
|
|
3238
|
-
console.log(`Add this DNS TXT record to verify ${
|
|
3510
|
+
console.log(`Add this DNS TXT record to verify ${domain2}:`);
|
|
3239
3511
|
console.log();
|
|
3240
|
-
console.log(` Host: _uam.${
|
|
3512
|
+
console.log(` Host: _uam.${domain2}`);
|
|
3241
3513
|
console.log(` Type: TXT`);
|
|
3242
3514
|
console.log(` Value: ${txtValue}`);
|
|
3243
3515
|
console.log();
|
|
3244
3516
|
console.log(`Or serve this HTTPS fallback:`);
|
|
3245
3517
|
console.log();
|
|
3246
|
-
console.log(` URL: https://${
|
|
3518
|
+
console.log(` URL: https://${domain2}/.well-known/uam.json`);
|
|
3247
3519
|
console.log();
|
|
3248
3520
|
console.log(`See documentation for .well-known/uam.json format.`);
|
|
3249
3521
|
console.log();
|
|
3250
3522
|
console.log("Polling for verification...");
|
|
3251
|
-
const verified = await agent.verifyDomain(
|
|
3523
|
+
const verified = await agent.verifyDomain(domain2, {
|
|
3252
3524
|
timeout: timeout * 1e3,
|
|
3253
3525
|
pollInterval: pollInterval * 1e3
|
|
3254
3526
|
});
|
|
3255
3527
|
await agent.close();
|
|
3256
3528
|
if (verified) {
|
|
3257
3529
|
console.log(
|
|
3258
|
-
`
|
|
3530
|
+
`Domain checked by ${agent.address.split("::")[1] ?? "your relay"}: ${agent.address} is now Tier 2 via ${domain2}.`
|
|
3259
3531
|
);
|
|
3260
3532
|
} else {
|
|
3261
3533
|
console.log(
|
|
@@ -3278,9 +3550,9 @@ program.command("init").description("Initialize a new agent: generate keys and r
|
|
|
3278
3550
|
relay: opts.relay
|
|
3279
3551
|
});
|
|
3280
3552
|
});
|
|
3281
|
-
program.command("send <address> <message>").description("Send a message to another agent").action(async (
|
|
3553
|
+
program.command("send <address> <message>").description("Send a message to another agent").action(async (address2, message) => {
|
|
3282
3554
|
const globalName = program.opts().name;
|
|
3283
|
-
await sendCommand(
|
|
3555
|
+
await sendCommand(address2, message, { name: globalName });
|
|
3284
3556
|
});
|
|
3285
3557
|
program.command("inbox").description("Check your inbox for pending messages").option("-l, --limit <number>", "Max messages to retrieve", "20").action(async (opts) => {
|
|
3286
3558
|
const globalName = program.opts().name;
|
|
@@ -3302,21 +3574,21 @@ program.command("contacts").description("List known contacts from the local cont
|
|
|
3302
3574
|
await contactsCommand({ name: globalName });
|
|
3303
3575
|
});
|
|
3304
3576
|
var contactCmd = program.command("contact").description("Contact management commands (fingerprint, verify, remove)");
|
|
3305
|
-
contactCmd.command("fingerprint <address>").description("Display the fingerprint for a known contact's public key").action(async (
|
|
3577
|
+
contactCmd.command("fingerprint <address>").description("Display the fingerprint for a known contact's public key").action(async (address2) => {
|
|
3306
3578
|
const globalName = program.opts().name;
|
|
3307
|
-
await contactFingerprintCommand(
|
|
3579
|
+
await contactFingerprintCommand(address2, { name: globalName });
|
|
3308
3580
|
});
|
|
3309
|
-
contactCmd.command("verify <address>").description("Manually verify a contact, upgrading trust state to verified").option("-y, --yes", "Skip confirmation prompt").action(async (
|
|
3581
|
+
contactCmd.command("verify <address>").description("Manually verify a contact, upgrading trust state to verified").option("-y, --yes", "Skip confirmation prompt").action(async (address2, opts) => {
|
|
3310
3582
|
const globalName = program.opts().name;
|
|
3311
|
-
await contactVerifyCommand(
|
|
3583
|
+
await contactVerifyCommand(address2, { name: globalName, yes: opts.yes });
|
|
3312
3584
|
});
|
|
3313
|
-
contactCmd.command("remove <address>").description("Remove a contact from the contact book").action(async (
|
|
3585
|
+
contactCmd.command("remove <address>").description("Remove a contact from the contact book").action(async (address2) => {
|
|
3314
3586
|
const globalName = program.opts().name;
|
|
3315
|
-
await contactRemoveCommand(
|
|
3587
|
+
await contactRemoveCommand(address2, { name: globalName });
|
|
3316
3588
|
});
|
|
3317
|
-
program.command("verify-domain <domain>").description("Verify domain ownership for Tier 2 DNS-verified status").option("-t, --timeout <seconds>", "Polling timeout in seconds", "300").option("--poll-interval <seconds>", "Polling interval in seconds", "10").action(async (
|
|
3589
|
+
program.command("verify-domain <domain>").description("Verify domain ownership for Tier 2 DNS-verified status").option("-t, --timeout <seconds>", "Polling timeout in seconds", "300").option("--poll-interval <seconds>", "Polling interval in seconds", "10").action(async (domain2, opts) => {
|
|
3318
3590
|
const globalName = program.opts().name;
|
|
3319
|
-
await verifyDomainCommand(
|
|
3591
|
+
await verifyDomainCommand(domain2, {
|
|
3320
3592
|
name: globalName,
|
|
3321
3593
|
timeout: parseInt(opts.timeout, 10),
|
|
3322
3594
|
pollInterval: parseInt(opts.pollInterval, 10)
|
|
@@ -3326,13 +3598,13 @@ program.command("pending").description("List pending handshake requests awaiting
|
|
|
3326
3598
|
const globalName = program.opts().name;
|
|
3327
3599
|
await pendingCommand({ name: globalName });
|
|
3328
3600
|
});
|
|
3329
|
-
program.command("approve <address>").description("Approve a pending handshake request").action(async (
|
|
3601
|
+
program.command("approve <address>").description("Approve a pending handshake request").action(async (address2) => {
|
|
3330
3602
|
const globalName = program.opts().name;
|
|
3331
|
-
await approveCommand(
|
|
3603
|
+
await approveCommand(address2, { name: globalName });
|
|
3332
3604
|
});
|
|
3333
|
-
program.command("deny <address>").description("Deny a pending handshake request").action(async (
|
|
3605
|
+
program.command("deny <address>").description("Deny a pending handshake request").action(async (address2) => {
|
|
3334
3606
|
const globalName = program.opts().name;
|
|
3335
|
-
await denyCommand(
|
|
3607
|
+
await denyCommand(address2, { name: globalName });
|
|
3336
3608
|
});
|
|
3337
3609
|
program.command("block <pattern>").description("Block an address or domain pattern (e.g., spammer::evil.com or *::evil.com)").action(async (pattern) => {
|
|
3338
3610
|
const globalName = program.opts().name;
|