@agentunion/fastaun-browser 0.3.6 → 0.4.1
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/CHANGELOG.md +24 -0
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +596 -0
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +1697 -0
- package/_packed_docs/CHANGELOG.md +24 -0
- package/_packed_docs/INDEX.md +17 -11
- package/_packed_docs/KITE_DOCS_GUIDE.md +11 -10
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +134 -158
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +11 -7
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +98 -119
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +147 -374
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +153 -153
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +168 -1383
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +71 -91
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +76 -63
- package/_packed_docs/sdk/09-custody-api-manual.md +7 -6
- package/_packed_docs/sdk/09-meta-rpc-manual.md +13 -14
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +37 -49
- package/_packed_docs/sdk/INDEX.md +72 -98
- package/_packed_docs/sdk/README.md +85 -266
- package/dist/aid-store.d.ts +125 -0
- package/dist/aid-store.d.ts.map +1 -0
- package/dist/aid-store.js +841 -0
- package/dist/aid-store.js.map +1 -0
- package/dist/aid.d.ts +56 -0
- package/dist/aid.d.ts.map +1 -0
- package/dist/aid.js +112 -0
- package/dist/aid.js.map +1 -0
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +1630 -1901
- package/dist/cert-utils.d.ts +26 -0
- package/dist/cert-utils.d.ts.map +1 -0
- package/dist/cert-utils.js +221 -0
- package/dist/cert-utils.js.map +1 -0
- package/dist/client.d.ts +89 -60
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +568 -160
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +0 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -2
- package/dist/config.js.map +1 -1
- package/dist/error-codes.d.ts +25 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +31 -0
- package/dist/error-codes.js.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +1 -1
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/result.d.ts +19 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +10 -0
- package/dist/result.js.map +1 -0
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +16 -1
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +13 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -1
package/dist/bundle.js
CHANGED
|
@@ -225,10 +225,10 @@ var init_crypto = __esm({
|
|
|
225
225
|
const pkcs8 = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
|
|
226
226
|
const privateKeyPem = arrayBufferToPem(pkcs8, "PRIVATE KEY");
|
|
227
227
|
const spki = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
|
228
|
-
const
|
|
228
|
+
const publicKeyDerB642 = uint8ToBase64(new Uint8Array(spki));
|
|
229
229
|
return {
|
|
230
230
|
private_key_pem: privateKeyPem,
|
|
231
|
-
public_key_der_b64:
|
|
231
|
+
public_key_der_b64: publicKeyDerB642,
|
|
232
232
|
curve: _CryptoProvider.curveName
|
|
233
233
|
};
|
|
234
234
|
}
|
|
@@ -314,12 +314,12 @@ async function secretPut(storeName, key, value) {
|
|
|
314
314
|
tx.oncomplete = () => db.close();
|
|
315
315
|
});
|
|
316
316
|
}
|
|
317
|
-
var
|
|
317
|
+
var _noopLog6, SECRET_DB_NAME, SECRET_DB_VERSION, STORE_SECRETS, STORE_MASTER, IndexedDBSecretStore;
|
|
318
318
|
var init_indexeddb_store = __esm({
|
|
319
319
|
"src/secret-store/indexeddb-store.ts"() {
|
|
320
320
|
"use strict";
|
|
321
321
|
init_crypto();
|
|
322
|
-
|
|
322
|
+
_noopLog6 = { error: () => {
|
|
323
323
|
}, warn: () => {
|
|
324
324
|
}, info: () => {
|
|
325
325
|
}, debug: () => {
|
|
@@ -332,7 +332,7 @@ var init_indexeddb_store = __esm({
|
|
|
332
332
|
constructor(encryptionSeed) {
|
|
333
333
|
__publicField(this, "_encryptionSeed");
|
|
334
334
|
__publicField(this, "_masterKeyPromise", null);
|
|
335
|
-
__publicField(this, "_log",
|
|
335
|
+
__publicField(this, "_log", _noopLog6);
|
|
336
336
|
this._encryptionSeed = encryptionSeed;
|
|
337
337
|
}
|
|
338
338
|
setLogger(log) {
|
|
@@ -453,7 +453,31 @@ var init_indexeddb_store = __esm({
|
|
|
453
453
|
}
|
|
454
454
|
});
|
|
455
455
|
|
|
456
|
+
// src/version.ts
|
|
457
|
+
var VERSION = "0.4.1";
|
|
458
|
+
|
|
456
459
|
// src/types.ts
|
|
460
|
+
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
461
|
+
ConnectionState2["NO_IDENTITY"] = "no_identity";
|
|
462
|
+
ConnectionState2["STANDBY"] = "standby";
|
|
463
|
+
ConnectionState2["AUTHENTICATED"] = "authenticated";
|
|
464
|
+
ConnectionState2["CONNECTING"] = "connecting";
|
|
465
|
+
ConnectionState2["READY"] = "ready";
|
|
466
|
+
ConnectionState2["RETRY_BACKOFF"] = "retry_backoff";
|
|
467
|
+
ConnectionState2["RECONNECTING"] = "reconnecting";
|
|
468
|
+
ConnectionState2["CONNECTION_FAILED"] = "connection_failed";
|
|
469
|
+
ConnectionState2["CLOSED"] = "closed";
|
|
470
|
+
return ConnectionState2;
|
|
471
|
+
})(ConnectionState || {});
|
|
472
|
+
var STATE_TO_PUBLIC = {
|
|
473
|
+
idle: "no_identity" /* NO_IDENTITY */,
|
|
474
|
+
authenticating: "connecting" /* CONNECTING */,
|
|
475
|
+
connected: "ready" /* READY */,
|
|
476
|
+
disconnected: "standby" /* STANDBY */,
|
|
477
|
+
terminal_failed: "connection_failed" /* CONNECTION_FAILED */,
|
|
478
|
+
retry_backoff: "retry_backoff" /* RETRY_BACKOFF */,
|
|
479
|
+
reconnecting: "reconnecting" /* RECONNECTING */
|
|
480
|
+
};
|
|
457
481
|
function isJsonObject(value) {
|
|
458
482
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
459
483
|
return false;
|
|
@@ -466,12 +490,16 @@ function isJsonObject(value) {
|
|
|
466
490
|
var AUNError = class extends Error {
|
|
467
491
|
constructor(message, opts) {
|
|
468
492
|
super(message);
|
|
493
|
+
/** JSON-RPC 错误码(数字,用于 RPC 层) */
|
|
469
494
|
__publicField(this, "code");
|
|
495
|
+
/** 业务字符串错误码(与 Python/TS SDK error_codes 对齐) */
|
|
496
|
+
__publicField(this, "stringCode");
|
|
470
497
|
__publicField(this, "data");
|
|
471
498
|
__publicField(this, "retryable");
|
|
472
499
|
__publicField(this, "traceId");
|
|
473
500
|
this.name = "AUNError";
|
|
474
501
|
this.code = opts?.code ?? -1;
|
|
502
|
+
this.stringCode = opts?.stringCode ?? "";
|
|
475
503
|
this.data = opts?.data ?? null;
|
|
476
504
|
this.retryable = opts?.retryable ?? false;
|
|
477
505
|
this.traceId = opts?.traceId ?? null;
|
|
@@ -721,7 +749,6 @@ var DEFAULTS = {
|
|
|
721
749
|
aunPath: "aun",
|
|
722
750
|
rootCaPem: null,
|
|
723
751
|
seedPassword: null,
|
|
724
|
-
discoveryPort: null,
|
|
725
752
|
groupE2ee: true,
|
|
726
753
|
verifySsl: true,
|
|
727
754
|
requireForwardSecrecy: true,
|
|
@@ -741,7 +768,6 @@ function createConfig(raw) {
|
|
|
741
768
|
data.seedPassword ?? data.seed_password ?? data.encryptionSeed ?? data.encryption_seed,
|
|
742
769
|
DEFAULTS.seedPassword
|
|
743
770
|
),
|
|
744
|
-
discoveryPort: readOptionalNumber(data.discoveryPort ?? data.discovery_port, DEFAULTS.discoveryPort),
|
|
745
771
|
groupE2ee: true,
|
|
746
772
|
// 必备能力,不可配置
|
|
747
773
|
verifySsl: DEFAULTS.verifySsl,
|
|
@@ -1233,6 +1259,8 @@ var RPCTransport = class {
|
|
|
1233
1259
|
__publicField(this, "_onDisconnect");
|
|
1234
1260
|
__publicField(this, "_ws", null);
|
|
1235
1261
|
__publicField(this, "_closed", true);
|
|
1262
|
+
__publicField(this, "_lastCloseCode", null);
|
|
1263
|
+
__publicField(this, "_lastCloseReason", "");
|
|
1236
1264
|
__publicField(this, "_challenge", null);
|
|
1237
1265
|
__publicField(this, "_pending", /* @__PURE__ */ new Map());
|
|
1238
1266
|
__publicField(this, "_pendingBackground", /* @__PURE__ */ new Set());
|
|
@@ -1288,6 +1316,8 @@ var RPCTransport = class {
|
|
|
1288
1316
|
async connect(url) {
|
|
1289
1317
|
const tStart = Date.now();
|
|
1290
1318
|
this._log.debug(`connect enter: url=${url}`);
|
|
1319
|
+
this._lastCloseCode = null;
|
|
1320
|
+
this._lastCloseReason = "";
|
|
1291
1321
|
return new Promise((resolve, reject) => {
|
|
1292
1322
|
const ws = new WebSocket(url);
|
|
1293
1323
|
this._ws = ws;
|
|
@@ -1330,6 +1360,8 @@ var RPCTransport = class {
|
|
|
1330
1360
|
}
|
|
1331
1361
|
};
|
|
1332
1362
|
ws.onclose = (event) => {
|
|
1363
|
+
this._lastCloseCode = event.code;
|
|
1364
|
+
this._lastCloseReason = event.reason ?? "";
|
|
1333
1365
|
if (!initialResolved) {
|
|
1334
1366
|
initialResolved = true;
|
|
1335
1367
|
this._log.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=closed_before_initial code=${event.code}`);
|
|
@@ -1380,7 +1412,7 @@ var RPCTransport = class {
|
|
|
1380
1412
|
*/
|
|
1381
1413
|
async call(method, params, timeout, trace, background = false) {
|
|
1382
1414
|
if (this._closed || !this._ws) {
|
|
1383
|
-
throw
|
|
1415
|
+
throw this._notConnectedError();
|
|
1384
1416
|
}
|
|
1385
1417
|
const rpcId = `rpc-${String(++_rpcIdCounter).padStart(6, "0")}`;
|
|
1386
1418
|
const effectiveTimeout = (timeout ?? this._timeout) * 1e3;
|
|
@@ -1572,6 +1604,8 @@ var RPCTransport = class {
|
|
|
1572
1604
|
_handleClose(event) {
|
|
1573
1605
|
const wasClosed = this._closed;
|
|
1574
1606
|
this._closed = true;
|
|
1607
|
+
this._lastCloseCode = event.code;
|
|
1608
|
+
this._lastCloseReason = event.reason ?? "";
|
|
1575
1609
|
const err = new ConnectionError(`websocket closed: code=${event.code}`);
|
|
1576
1610
|
for (const [, pending] of this._pending) {
|
|
1577
1611
|
clearTimeout(pending.timer);
|
|
@@ -1597,6 +1631,13 @@ var RPCTransport = class {
|
|
|
1597
1631
|
}
|
|
1598
1632
|
}
|
|
1599
1633
|
}
|
|
1634
|
+
_notConnectedError() {
|
|
1635
|
+
if (this._lastCloseCode !== null) {
|
|
1636
|
+
const reason = this._lastCloseReason ? ` reason=${this._lastCloseReason}` : "";
|
|
1637
|
+
return new ConnectionError(`transport not connected: last websocket close code=${this._lastCloseCode}${reason}`);
|
|
1638
|
+
}
|
|
1639
|
+
return new ConnectionError("transport not connected");
|
|
1640
|
+
}
|
|
1600
1641
|
_routeMessage(message) {
|
|
1601
1642
|
if ("id" in message) {
|
|
1602
1643
|
const rpcId = String(message.id);
|
|
@@ -1703,7 +1744,6 @@ var _noopLog4 = { error: () => {
|
|
|
1703
1744
|
}, debug: () => {
|
|
1704
1745
|
} };
|
|
1705
1746
|
var AUN_SDK_LANG = "javascript";
|
|
1706
|
-
var AUN_SDK_VERSION = "0.3.6";
|
|
1707
1747
|
function splitPemBundle(bundle) {
|
|
1708
1748
|
const marker = "-----END CERTIFICATE-----";
|
|
1709
1749
|
const certs = [];
|
|
@@ -2656,7 +2696,7 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2656
2696
|
client: {
|
|
2657
2697
|
slot_id: String(opts?.slotId ?? this._slotId ?? ""),
|
|
2658
2698
|
sdk_lang: AUN_SDK_LANG,
|
|
2659
|
-
sdk_version:
|
|
2699
|
+
sdk_version: VERSION
|
|
2660
2700
|
},
|
|
2661
2701
|
delivery_mode: opts?.deliveryMode ?? { mode: "fanout" },
|
|
2662
2702
|
capabilities
|
|
@@ -3383,1737 +3423,20 @@ var SeqTracker = class {
|
|
|
3383
3423
|
}
|
|
3384
3424
|
/** 导出各命名空间的 contiguousSeq,用于持久化 */
|
|
3385
3425
|
exportState() {
|
|
3386
|
-
const result = {};
|
|
3387
|
-
for (const [ns, t] of this._trackers) {
|
|
3388
|
-
if (t.contiguousSeq > 0) result[ns] = t.contiguousSeq;
|
|
3389
|
-
}
|
|
3390
|
-
return result;
|
|
3391
|
-
}
|
|
3392
|
-
/** 从持久化数据恢复各命名空间的 contiguousSeq */
|
|
3393
|
-
restoreState(state) {
|
|
3394
|
-
for (const [ns, seq] of Object.entries(state)) {
|
|
3395
|
-
if (typeof seq === "number" && seq > 0) {
|
|
3396
|
-
const t = this._get(ns);
|
|
3397
|
-
t.contiguousSeq = Math.max(t.contiguousSeq, seq);
|
|
3398
|
-
t.maxSeenSeq = Math.max(t.maxSeenSeq, seq);
|
|
3399
|
-
}
|
|
3400
|
-
}
|
|
3401
|
-
}
|
|
3402
|
-
};
|
|
3403
|
-
|
|
3404
|
-
// src/namespaces/auth.ts
|
|
3405
|
-
init_crypto();
|
|
3406
|
-
var _noopLog5 = { error: () => {
|
|
3407
|
-
}, warn: () => {
|
|
3408
|
-
}, info: () => {
|
|
3409
|
-
}, debug: () => {
|
|
3410
|
-
} };
|
|
3411
|
-
var AGENT_MD_HTTP_TIMEOUT_MS = 3e4;
|
|
3412
|
-
var AGENT_MD_SIGNATURE_MARKER = "<!-- AUN-SIGNATURE";
|
|
3413
|
-
var AGENT_MD_SIGNATURE_RE = /^<!-- AUN-SIGNATURE\r?\n(?<body>[\s\S]*?)\r?\n-->\s*$/;
|
|
3414
|
-
var AGENT_MD_FINGERPRINT_RE = /^sha256:[0-9a-f]{64}$/;
|
|
3415
|
-
function agentMdHttpScheme(gatewayUrl) {
|
|
3416
|
-
const raw = String(gatewayUrl ?? "").trim().toLowerCase();
|
|
3417
|
-
return raw.startsWith("ws://") ? "http" : "https";
|
|
3418
|
-
}
|
|
3419
|
-
function agentMdAuthority(aid, discoveryPort) {
|
|
3420
|
-
const host = String(aid ?? "").trim();
|
|
3421
|
-
if (!host) return "";
|
|
3422
|
-
if (discoveryPort && !host.includes(":")) {
|
|
3423
|
-
return `${host}:${discoveryPort}`;
|
|
3424
|
-
}
|
|
3425
|
-
return host;
|
|
3426
|
-
}
|
|
3427
|
-
function parseAgentMdTailSignature(content) {
|
|
3428
|
-
const idx = content.lastIndexOf(AGENT_MD_SIGNATURE_MARKER);
|
|
3429
|
-
if (idx < 0) {
|
|
3430
|
-
return { payload: content, fields: null };
|
|
3431
|
-
}
|
|
3432
|
-
if (idx > 0 && content[idx - 1] !== "\n" && content[idx - 1] !== "\r") {
|
|
3433
|
-
return { payload: content, fields: null };
|
|
3434
|
-
}
|
|
3435
|
-
const tail = content.slice(idx);
|
|
3436
|
-
const match = tail.match(AGENT_MD_SIGNATURE_RE);
|
|
3437
|
-
if (!match) {
|
|
3438
|
-
return { payload: content.slice(0, idx), fields: null, parseError: "malformed signature block" };
|
|
3439
|
-
}
|
|
3440
|
-
const fields = {};
|
|
3441
|
-
for (const rawLine of match.groups?.body?.split(/\r?\n/) ?? []) {
|
|
3442
|
-
const line = rawLine.trim();
|
|
3443
|
-
if (!line) continue;
|
|
3444
|
-
const colon = line.indexOf(":");
|
|
3445
|
-
if (colon < 0) {
|
|
3446
|
-
return { payload: content.slice(0, idx), fields: null, parseError: `malformed signature field: ${line}` };
|
|
3447
|
-
}
|
|
3448
|
-
const key = line.slice(0, colon).trim().toLowerCase();
|
|
3449
|
-
const value = line.slice(colon + 1).trim();
|
|
3450
|
-
fields[key] = value;
|
|
3451
|
-
}
|
|
3452
|
-
for (const required of ["cert_fingerprint", "timestamp", "signature"]) {
|
|
3453
|
-
if (!fields[required]) {
|
|
3454
|
-
return { payload: content.slice(0, idx), fields: null, parseError: `signature block missing ${required}` };
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
if (!AGENT_MD_FINGERPRINT_RE.test(fields.cert_fingerprint.toLowerCase())) {
|
|
3458
|
-
return { payload: content.slice(0, idx), fields: null, parseError: "invalid cert_fingerprint" };
|
|
3459
|
-
}
|
|
3460
|
-
if (!Number.isFinite(Number(fields.timestamp))) {
|
|
3461
|
-
return { payload: content.slice(0, idx), fields: null, parseError: "invalid timestamp" };
|
|
3462
|
-
}
|
|
3463
|
-
return { payload: content.slice(0, idx), fields, parseError: void 0 };
|
|
3464
|
-
}
|
|
3465
|
-
function extractAgentMDAid(payload) {
|
|
3466
|
-
const lines = payload.replace(/^\ufeff/, "").split(/\r?\n/);
|
|
3467
|
-
if (!lines.length || lines[0].trim() !== "---") {
|
|
3468
|
-
return "";
|
|
3469
|
-
}
|
|
3470
|
-
for (const line of lines.slice(1)) {
|
|
3471
|
-
const trimmed = line.trim();
|
|
3472
|
-
if (trimmed === "---") break;
|
|
3473
|
-
if (trimmed.startsWith("aid:")) {
|
|
3474
|
-
let value = trimmed.slice(4).trim();
|
|
3475
|
-
if (value.length >= 2 && value[0] === value[value.length - 1] && (value[0] === '"' || value[0] === "'")) {
|
|
3476
|
-
value = value.slice(1, -1);
|
|
3477
|
-
}
|
|
3478
|
-
return value.trim();
|
|
3479
|
-
}
|
|
3480
|
-
}
|
|
3481
|
-
return "";
|
|
3482
|
-
}
|
|
3483
|
-
function agentMdResult(status, payload, reason = "", aid = "", certFingerprint = "", timestamp) {
|
|
3484
|
-
const result = {
|
|
3485
|
-
status,
|
|
3486
|
-
verified: status === "verified",
|
|
3487
|
-
payload
|
|
3488
|
-
};
|
|
3489
|
-
if (reason) result.reason = reason;
|
|
3490
|
-
if (aid) result.aid = aid;
|
|
3491
|
-
if (certFingerprint) result.cert_fingerprint = certFingerprint;
|
|
3492
|
-
if (timestamp !== void 0) result.timestamp = timestamp;
|
|
3493
|
-
return result;
|
|
3494
|
-
}
|
|
3495
|
-
function normalizeIdentityCertPem(identity) {
|
|
3496
|
-
if (!identity) return "";
|
|
3497
|
-
return String(identity.cert ?? identity.cert_pem ?? "").trim();
|
|
3498
|
-
}
|
|
3499
|
-
function parseAgentMdTimestamp(value) {
|
|
3500
|
-
const parsed = Number(value.trim());
|
|
3501
|
-
return Number.isFinite(parsed) ? Math.trunc(parsed) : void 0;
|
|
3502
|
-
}
|
|
3503
|
-
function readDerLength(bytes, offset) {
|
|
3504
|
-
const first = bytes[offset];
|
|
3505
|
-
if (first === void 0) return null;
|
|
3506
|
-
if (first < 128) {
|
|
3507
|
-
return { length: first, lengthBytes: 1 };
|
|
3508
|
-
}
|
|
3509
|
-
const count = first & 127;
|
|
3510
|
-
if (count === 0 || count > 4 || offset + count >= bytes.length) {
|
|
3511
|
-
return null;
|
|
3512
|
-
}
|
|
3513
|
-
let length = 0;
|
|
3514
|
-
for (let i = 0; i < count; i++) {
|
|
3515
|
-
length = length << 8 | bytes[offset + 1 + i];
|
|
3516
|
-
}
|
|
3517
|
-
return { length, lengthBytes: 1 + count };
|
|
3518
|
-
}
|
|
3519
|
-
function extractCommonNameFromCertPem(certPem) {
|
|
3520
|
-
try {
|
|
3521
|
-
const bytes = new Uint8Array(pemToArrayBuffer(certPem));
|
|
3522
|
-
const oid = [6, 3, 85, 4, 3];
|
|
3523
|
-
const decoder2 = new TextDecoder();
|
|
3524
|
-
let commonName = "";
|
|
3525
|
-
for (let i = 0; i <= bytes.length - oid.length - 2; i++) {
|
|
3526
|
-
let matched = true;
|
|
3527
|
-
for (let j = 0; j < oid.length; j++) {
|
|
3528
|
-
if (bytes[i + j] !== oid[j]) {
|
|
3529
|
-
matched = false;
|
|
3530
|
-
break;
|
|
3531
|
-
}
|
|
3532
|
-
}
|
|
3533
|
-
if (!matched) continue;
|
|
3534
|
-
const tag = bytes[i + oid.length];
|
|
3535
|
-
const lengthInfo = readDerLength(bytes, i + oid.length + 1);
|
|
3536
|
-
if (!lengthInfo) continue;
|
|
3537
|
-
const start = i + oid.length + 1 + lengthInfo.lengthBytes;
|
|
3538
|
-
const end = start + lengthInfo.length;
|
|
3539
|
-
if (end > bytes.length) continue;
|
|
3540
|
-
const valueBytes = bytes.slice(start, end);
|
|
3541
|
-
if (tag === 12 || tag === 19 || tag === 22 || tag === 20 || tag === 30) {
|
|
3542
|
-
commonName = decoder2.decode(valueBytes).trim();
|
|
3543
|
-
} else {
|
|
3544
|
-
commonName = decoder2.decode(valueBytes).trim();
|
|
3545
|
-
}
|
|
3546
|
-
}
|
|
3547
|
-
return commonName;
|
|
3548
|
-
} catch {
|
|
3549
|
-
return "";
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3552
|
-
function parseCertValidity(certPem) {
|
|
3553
|
-
try {
|
|
3554
|
-
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
3555
|
-
const dates = [];
|
|
3556
|
-
for (let i = 0; i < der.length - 2 && dates.length < 2; i++) {
|
|
3557
|
-
const tag = der[i];
|
|
3558
|
-
if (tag !== 23 && tag !== 24) continue;
|
|
3559
|
-
const len = der[i + 1];
|
|
3560
|
-
if (len === 0 || len > 20 || i + 2 + len > der.length) continue;
|
|
3561
|
-
const str = new TextDecoder().decode(der.slice(i + 2, i + 2 + len));
|
|
3562
|
-
const ts = parseAsn1Time2(tag, str);
|
|
3563
|
-
if (ts !== null) {
|
|
3564
|
-
dates.push(ts);
|
|
3565
|
-
i += 1 + len;
|
|
3566
|
-
}
|
|
3567
|
-
}
|
|
3568
|
-
if (dates.length >= 2) {
|
|
3569
|
-
return { notBefore: dates[0], notAfter: dates[1] };
|
|
3570
|
-
}
|
|
3571
|
-
return null;
|
|
3572
|
-
} catch {
|
|
3573
|
-
return null;
|
|
3574
|
-
}
|
|
3575
|
-
}
|
|
3576
|
-
function parseAsn1Time2(tag, str) {
|
|
3577
|
-
try {
|
|
3578
|
-
if (tag === 23) {
|
|
3579
|
-
const cleaned = str.replace(/Z$/i, "");
|
|
3580
|
-
if (cleaned.length < 12) return null;
|
|
3581
|
-
let year = parseInt(cleaned.slice(0, 2), 10);
|
|
3582
|
-
year += year >= 50 ? 1900 : 2e3;
|
|
3583
|
-
const month = parseInt(cleaned.slice(2, 4), 10) - 1;
|
|
3584
|
-
const day = parseInt(cleaned.slice(4, 6), 10);
|
|
3585
|
-
const hour = parseInt(cleaned.slice(6, 8), 10);
|
|
3586
|
-
const min = parseInt(cleaned.slice(8, 10), 10);
|
|
3587
|
-
const sec = parseInt(cleaned.slice(10, 12), 10);
|
|
3588
|
-
return Date.UTC(year, month, day, hour, min, sec);
|
|
3589
|
-
} else if (tag === 24) {
|
|
3590
|
-
const cleaned = str.replace(/Z$/i, "");
|
|
3591
|
-
if (cleaned.length < 14) return null;
|
|
3592
|
-
const year = parseInt(cleaned.slice(0, 4), 10);
|
|
3593
|
-
const month = parseInt(cleaned.slice(4, 6), 10) - 1;
|
|
3594
|
-
const day = parseInt(cleaned.slice(6, 8), 10);
|
|
3595
|
-
const hour = parseInt(cleaned.slice(8, 10), 10);
|
|
3596
|
-
const min = parseInt(cleaned.slice(10, 12), 10);
|
|
3597
|
-
const sec = parseInt(cleaned.slice(12, 14), 10);
|
|
3598
|
-
return Date.UTC(year, month, day, hour, min, sec);
|
|
3599
|
-
}
|
|
3600
|
-
return null;
|
|
3601
|
-
} catch {
|
|
3602
|
-
return null;
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
async function fetchWithTimeout(input, init, timeoutMs = AGENT_MD_HTTP_TIMEOUT_MS) {
|
|
3606
|
-
const controller = new AbortController();
|
|
3607
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3608
|
-
try {
|
|
3609
|
-
return await fetch(input, { ...init, signal: controller.signal });
|
|
3610
|
-
} catch (error) {
|
|
3611
|
-
if (controller.signal.aborted) {
|
|
3612
|
-
throw new AUNError(`agent.md request timed out after ${timeoutMs}ms`);
|
|
3613
|
-
}
|
|
3614
|
-
throw error;
|
|
3615
|
-
} finally {
|
|
3616
|
-
clearTimeout(timer);
|
|
3617
|
-
}
|
|
3618
|
-
}
|
|
3619
|
-
var AuthNamespace = class {
|
|
3620
|
-
constructor(client) {
|
|
3621
|
-
__publicField(this, "_client");
|
|
3622
|
-
__publicField(this, "_log", _noopLog5);
|
|
3623
|
-
__publicField(this, "_agentMdCache", /* @__PURE__ */ new Map());
|
|
3624
|
-
this._client = client;
|
|
3625
|
-
}
|
|
3626
|
-
setLogger(log) {
|
|
3627
|
-
this._log = log;
|
|
3628
|
-
}
|
|
3629
|
-
get _internal() {
|
|
3630
|
-
return this._client;
|
|
3631
|
-
}
|
|
3632
|
-
/**
|
|
3633
|
-
* 解析 gateway URL。
|
|
3634
|
-
* 优先使用已预置的 gatewayUrl,否则基于 AID 自动发现。
|
|
3635
|
-
*
|
|
3636
|
-
* 发现流程:
|
|
3637
|
-
* 1. 若 gatewayUrl 已预置(内存),直接返回
|
|
3638
|
-
* 2. 从 keystore metadata 读 cached gateway_url(跨进程/会话复用)
|
|
3639
|
-
* 3. https://{aid}/.well-known/aun-gateway(泛域名 nameservice)
|
|
3640
|
-
* 4. https://gateway.{issuer}/.well-known/aun-gateway(Gateway 直连)
|
|
3641
|
-
* 发现成功后写入 keystore metadata,避免每次都做 well-known discovery。
|
|
3642
|
-
*/
|
|
3643
|
-
async _resolveGateway(aid) {
|
|
3644
|
-
if (this._client.gatewayUrl) {
|
|
3645
|
-
return this._client.gatewayUrl;
|
|
3646
|
-
}
|
|
3647
|
-
const resolvedAid = aid ?? this._client.aid;
|
|
3648
|
-
if (resolvedAid) {
|
|
3649
|
-
try {
|
|
3650
|
-
const cached = await this._loadCachedGatewayUrl(resolvedAid);
|
|
3651
|
-
if (cached) {
|
|
3652
|
-
this._log.debug(`_resolveGateway from keystore cache aid=${resolvedAid} gateway=${cached}`);
|
|
3653
|
-
this._client.gatewayUrl = cached;
|
|
3654
|
-
return cached;
|
|
3655
|
-
}
|
|
3656
|
-
} catch (exc) {
|
|
3657
|
-
this._log.debug(`_resolveGateway load cache failed: ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
3658
|
-
}
|
|
3659
|
-
const parts = resolvedAid.split(".");
|
|
3660
|
-
const issuerDomain = parts.length > 1 ? parts.slice(1).join(".") : resolvedAid;
|
|
3661
|
-
const port = this._client.configModel.discoveryPort;
|
|
3662
|
-
const portSuffix = port ? `:${port}` : "";
|
|
3663
|
-
const primaryUrl = `https://${resolvedAid}${portSuffix}/.well-known/aun-gateway`;
|
|
3664
|
-
try {
|
|
3665
|
-
const discovered2 = await this._client.discovery.discover(primaryUrl);
|
|
3666
|
-
await this._persistGatewayUrl(resolvedAid, discovered2);
|
|
3667
|
-
return discovered2;
|
|
3668
|
-
} catch {
|
|
3669
|
-
}
|
|
3670
|
-
const fallbackUrl = `https://gateway.${issuerDomain}${portSuffix}/.well-known/aun-gateway`;
|
|
3671
|
-
const discovered = await this._client.discovery.discover(fallbackUrl);
|
|
3672
|
-
await this._persistGatewayUrl(resolvedAid, discovered);
|
|
3673
|
-
return discovered;
|
|
3674
|
-
}
|
|
3675
|
-
throw new ValidationError(
|
|
3676
|
-
"unable to resolve gateway: set client.gatewayUrl or provide 'aid' for auto-discovery"
|
|
3677
|
-
);
|
|
3678
|
-
}
|
|
3679
|
-
/** 从 keystore metadata 读取 cached gateway_url(跨进程/会话复用) */
|
|
3680
|
-
async _loadCachedGatewayUrl(aid) {
|
|
3681
|
-
if (!aid) return "";
|
|
3682
|
-
const ks = this._internal._keystore;
|
|
3683
|
-
if (!ks || typeof ks.getMetadata !== "function") return "";
|
|
3684
|
-
try {
|
|
3685
|
-
const value = await ks.getMetadata(aid, "gateway_url");
|
|
3686
|
-
const raw = String(value ?? "").trim();
|
|
3687
|
-
if (!raw) return "";
|
|
3688
|
-
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
3689
|
-
try {
|
|
3690
|
-
const parsed = JSON.parse(raw);
|
|
3691
|
-
if (typeof parsed === "string") return parsed.trim();
|
|
3692
|
-
} catch {
|
|
3693
|
-
}
|
|
3694
|
-
}
|
|
3695
|
-
return raw;
|
|
3696
|
-
} catch {
|
|
3697
|
-
return "";
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
/** 持久化 gateway_url 到 keystore metadata,供跨进程复用 */
|
|
3701
|
-
async _persistGatewayUrl(aid, gatewayUrl) {
|
|
3702
|
-
if (!gatewayUrl || !aid) return;
|
|
3703
|
-
const ks = this._internal._keystore;
|
|
3704
|
-
if (!ks || typeof ks.setMetadata !== "function") return;
|
|
3705
|
-
try {
|
|
3706
|
-
await ks.setMetadata(aid, "gateway_url", gatewayUrl);
|
|
3707
|
-
} catch (exc) {
|
|
3708
|
-
this._log.debug(`persist gateway_url failed aid=${aid} err=${exc instanceof Error ? exc.message : String(exc)}`);
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
/** 内部访问 client 私有属性 */
|
|
3712
|
-
/**
|
|
3713
|
-
* 严格注册新 AID(对齐 TS registerAid)。
|
|
3714
|
-
* 通过 well-known 发现 gateway → 调用 AuthFlow.registerAid 注册。
|
|
3715
|
-
*/
|
|
3716
|
-
async registerAid(params) {
|
|
3717
|
-
const tStart = Date.now();
|
|
3718
|
-
const aid = String(params?.aid ?? "");
|
|
3719
|
-
this._log.debug(`registerAid enter: aid=${aid}`);
|
|
3720
|
-
try {
|
|
3721
|
-
if (!aid) throw new ValidationError("auth.registerAid requires 'aid'");
|
|
3722
|
-
const gatewayUrl = await this._resolveGateway(aid);
|
|
3723
|
-
this._client.gatewayUrl = gatewayUrl;
|
|
3724
|
-
const auth = this._internal._auth;
|
|
3725
|
-
const result = await auth.registerAid(gatewayUrl, aid);
|
|
3726
|
-
this._internal._aid = aid;
|
|
3727
|
-
this._internal._identity = await auth.loadIdentityOrNone(aid);
|
|
3728
|
-
this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
3729
|
-
return {
|
|
3730
|
-
aid,
|
|
3731
|
-
cert_pem: result.cert,
|
|
3732
|
-
gateway: gatewayUrl
|
|
3733
|
-
};
|
|
3734
|
-
} catch (err) {
|
|
3735
|
-
this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
3736
|
-
throw err;
|
|
3737
|
-
}
|
|
3738
|
-
}
|
|
3739
|
-
/**
|
|
3740
|
-
* 认证已有 AID(login1 + login2 两阶段认证)。
|
|
3741
|
-
* 通过 well-known 发现 gateway → 调用 AuthFlow.authenticate。
|
|
3742
|
-
*/
|
|
3743
|
-
async authenticate(params) {
|
|
3744
|
-
const tStart = Date.now();
|
|
3745
|
-
const aid = params?.aid ?? "";
|
|
3746
|
-
this._log.debug(`authenticate enter: aid=${aid || "<current>"}`);
|
|
3747
|
-
try {
|
|
3748
|
-
const request = { ...params ?? {} };
|
|
3749
|
-
const requestAid = request.aid;
|
|
3750
|
-
const gatewayUrl = await this._resolveGateway(requestAid);
|
|
3751
|
-
this._client.gatewayUrl = gatewayUrl;
|
|
3752
|
-
const auth = this._internal._auth;
|
|
3753
|
-
const result = await auth.authenticate(gatewayUrl, requestAid);
|
|
3754
|
-
this._internal._aid = result.aid ?? null;
|
|
3755
|
-
this._internal._identity = await auth.loadIdentityOrNone(String(result.aid));
|
|
3756
|
-
this._log.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${result.aid}`);
|
|
3757
|
-
return result;
|
|
3758
|
-
} catch (err) {
|
|
3759
|
-
this._log.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
3760
|
-
throw err;
|
|
3761
|
-
}
|
|
3762
|
-
}
|
|
3763
|
-
/** 只读加载本地已注册身份(密钥对 + 证书 + 实例状态)。无副作用,不触发网络请求。 */
|
|
3764
|
-
async loadIdentity(params) {
|
|
3765
|
-
const aid = String((params ?? {})?.aid ?? "").trim() || void 0;
|
|
3766
|
-
const identity = await this._internal._auth.loadIdentityOrNone(aid);
|
|
3767
|
-
if (!identity) {
|
|
3768
|
-
throw new StateError(`identity not found for aid: ${aid ?? "<default>"}`);
|
|
3769
|
-
}
|
|
3770
|
-
return identity;
|
|
3771
|
-
}
|
|
3772
|
-
/** 只读加载本地已注册身份,不存在时返回 null。 */
|
|
3773
|
-
async loadIdentityOrNull(params) {
|
|
3774
|
-
const aid = String((params ?? {})?.aid ?? "").trim() || void 0;
|
|
3775
|
-
return await this._internal._auth.loadIdentityOrNone(aid);
|
|
3776
|
-
}
|
|
3777
|
-
/** 获取对端 AID 的证书 PEM(本地缓存优先,未命中走 PKI HTTP + 链验证)。 */
|
|
3778
|
-
async fetchPeerCert(params) {
|
|
3779
|
-
const aid = String(params?.aid ?? "").trim();
|
|
3780
|
-
if (!aid) throw new Error("auth.fetchPeerCert requires 'aid'");
|
|
3781
|
-
const fp = String(params?.cert_fingerprint ?? "").trim() || void 0;
|
|
3782
|
-
if (typeof this._internal._fetchPeerCert !== "function") {
|
|
3783
|
-
throw new Error("client does not support _fetchPeerCert");
|
|
3784
|
-
}
|
|
3785
|
-
return String(await this._internal._fetchPeerCert(aid, fp)).trim();
|
|
3786
|
-
}
|
|
3787
|
-
async signAgentMd(content, opts) {
|
|
3788
|
-
const tStart = Date.now();
|
|
3789
|
-
this._log.debug(`signAgentMd enter: aid=${opts?.aid ?? "<current>"} content_len=${String(content ?? "").length}`);
|
|
3790
|
-
try {
|
|
3791
|
-
const client = this._internal;
|
|
3792
|
-
const targetAid = String(opts?.aid ?? client._aid ?? "").trim();
|
|
3793
|
-
const identity = await client._auth.loadIdentityOrNone(targetAid || void 0);
|
|
3794
|
-
if (!identity) {
|
|
3795
|
-
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3796
|
-
}
|
|
3797
|
-
const privateKeyPem = String(identity.private_key_pem ?? "").trim();
|
|
3798
|
-
const certPem = normalizeIdentityCertPem(identity);
|
|
3799
|
-
if (!privateKeyPem || !certPem) {
|
|
3800
|
-
throw new StateError("local identity missing private key or certificate");
|
|
3801
|
-
}
|
|
3802
|
-
let payload = parseAgentMdTailSignature(String(content ?? "")).payload;
|
|
3803
|
-
if (payload && !payload.endsWith("\n") && !payload.endsWith("\r")) {
|
|
3804
|
-
payload += "\n";
|
|
3805
|
-
}
|
|
3806
|
-
const privateKey = await importPrivateKeyEcdsa(privateKeyPem);
|
|
3807
|
-
const signatureDer = await ecdsaSignDer(privateKey, new TextEncoder().encode(payload));
|
|
3808
|
-
const fingerprint = await certificateSha256Fingerprint(certPem);
|
|
3809
|
-
if (!fingerprint) {
|
|
3810
|
-
throw new StateError("agent.md cert fingerprint failed");
|
|
3811
|
-
}
|
|
3812
|
-
const signedBlock = [
|
|
3813
|
-
AGENT_MD_SIGNATURE_MARKER,
|
|
3814
|
-
`cert_fingerprint: ${fingerprint}`,
|
|
3815
|
-
`timestamp: ${Math.floor(Date.now() / 1e3)}`,
|
|
3816
|
-
`signature: ${uint8ToBase64(signatureDer)}`,
|
|
3817
|
-
"-->"
|
|
3818
|
-
].join("\n");
|
|
3819
|
-
this._log.debug(`signAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
3820
|
-
return payload + signedBlock;
|
|
3821
|
-
} catch (err) {
|
|
3822
|
-
this._log.debug(`signAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
3823
|
-
throw err;
|
|
3824
|
-
}
|
|
3825
|
-
}
|
|
3826
|
-
async verifyAgentMd(content, opts) {
|
|
3827
|
-
const tStart = Date.now();
|
|
3828
|
-
this._log.debug(`verifyAgentMd enter: aid=${opts?.aid ?? "<infer>"} content_len=${String(content ?? "").length}`);
|
|
3829
|
-
try {
|
|
3830
|
-
const result = await this._verifyAgentMdImpl(content, opts);
|
|
3831
|
-
this._log.debug(`verifyAgentMd exit: elapsed=${Date.now() - tStart}ms status=${result.status} verified=${!!result.verified}`);
|
|
3832
|
-
return result;
|
|
3833
|
-
} catch (err) {
|
|
3834
|
-
this._log.debug(`verifyAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
3835
|
-
throw err;
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
async _verifyAgentMdImpl(content, opts) {
|
|
3839
|
-
const { payload, fields, parseError } = parseAgentMdTailSignature(String(content ?? ""));
|
|
3840
|
-
if (!fields) {
|
|
3841
|
-
if (!parseError) {
|
|
3842
|
-
return agentMdResult("unsigned", payload);
|
|
3843
|
-
}
|
|
3844
|
-
return agentMdResult("invalid", payload, parseError);
|
|
3845
|
-
}
|
|
3846
|
-
let expectedAid = String(opts?.aid ?? "").trim();
|
|
3847
|
-
const payloadAid = extractAgentMDAid(payload);
|
|
3848
|
-
if (expectedAid && payloadAid && expectedAid !== payloadAid) {
|
|
3849
|
-
return agentMdResult("invalid", payload, "aid mismatch", payloadAid);
|
|
3850
|
-
}
|
|
3851
|
-
if (!expectedAid) {
|
|
3852
|
-
expectedAid = payloadAid;
|
|
3853
|
-
}
|
|
3854
|
-
let certPem = String(opts?.certPem ?? opts?.cert_pem ?? "").trim();
|
|
3855
|
-
if (!certPem) {
|
|
3856
|
-
if (!expectedAid) {
|
|
3857
|
-
return agentMdResult("invalid", payload, "aid required to verify agent.md");
|
|
3858
|
-
}
|
|
3859
|
-
if (typeof this._internal._fetchPeerCert !== "function") {
|
|
3860
|
-
return agentMdResult("invalid", payload, "aid required to verify agent.md", expectedAid);
|
|
3861
|
-
}
|
|
3862
|
-
try {
|
|
3863
|
-
certPem = String(await this._internal._fetchPeerCert(expectedAid, fields.cert_fingerprint)).trim();
|
|
3864
|
-
} catch (error) {
|
|
3865
|
-
return agentMdResult("invalid", payload, String(error), expectedAid, fields.cert_fingerprint);
|
|
3866
|
-
}
|
|
3867
|
-
}
|
|
3868
|
-
let publicKey;
|
|
3869
|
-
try {
|
|
3870
|
-
publicKey = await importCertPublicKeyEcdsa(certPem);
|
|
3871
|
-
} catch (error) {
|
|
3872
|
-
return agentMdResult("invalid", payload, `invalid certificate: ${String(error)}`, expectedAid, fields.cert_fingerprint);
|
|
3873
|
-
}
|
|
3874
|
-
const actualFingerprint = await certificateSha256Fingerprint(certPem);
|
|
3875
|
-
if (!actualFingerprint || actualFingerprint.toLowerCase() !== fields.cert_fingerprint.toLowerCase()) {
|
|
3876
|
-
return agentMdResult("invalid", payload, "certificate fingerprint mismatch", expectedAid, fields.cert_fingerprint);
|
|
3877
|
-
}
|
|
3878
|
-
const cn = extractCommonNameFromCertPem(certPem);
|
|
3879
|
-
if (expectedAid && cn && cn !== expectedAid) {
|
|
3880
|
-
return agentMdResult("invalid", payload, "certificate aid mismatch", expectedAid, fields.cert_fingerprint);
|
|
3881
|
-
}
|
|
3882
|
-
let signature;
|
|
3883
|
-
try {
|
|
3884
|
-
signature = base64ToUint8(fields.signature);
|
|
3885
|
-
} catch {
|
|
3886
|
-
return agentMdResult("invalid", payload, "invalid signature", expectedAid, fields.cert_fingerprint);
|
|
3887
|
-
}
|
|
3888
|
-
if (!signature.length) {
|
|
3889
|
-
return agentMdResult("invalid", payload, "invalid signature", expectedAid, fields.cert_fingerprint);
|
|
3890
|
-
}
|
|
3891
|
-
const ok = await ecdsaVerifyDer(publicKey, signature, new TextEncoder().encode(payload));
|
|
3892
|
-
if (!ok) {
|
|
3893
|
-
return agentMdResult("invalid", payload, "signature verification failed", expectedAid, fields.cert_fingerprint, parseAgentMdTimestamp(fields.timestamp));
|
|
3894
|
-
}
|
|
3895
|
-
return agentMdResult("verified", payload, "", expectedAid || payloadAid, fields.cert_fingerprint, parseAgentMdTimestamp(fields.timestamp));
|
|
3896
|
-
}
|
|
3897
|
-
async _resolveAgentMdUrl(aid) {
|
|
3898
|
-
const resolvedAid = String(aid ?? "").trim();
|
|
3899
|
-
if (!resolvedAid) {
|
|
3900
|
-
throw new ValidationError("agent.md requires non-empty aid");
|
|
3901
|
-
}
|
|
3902
|
-
let gatewayUrl = this._client.gatewayUrl ?? "";
|
|
3903
|
-
if (!gatewayUrl) {
|
|
3904
|
-
try {
|
|
3905
|
-
gatewayUrl = await this._resolveGateway(resolvedAid);
|
|
3906
|
-
} catch {
|
|
3907
|
-
gatewayUrl = "";
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
const authority = agentMdAuthority(resolvedAid, this._client.configModel.discoveryPort);
|
|
3911
|
-
return `${agentMdHttpScheme(gatewayUrl)}://${authority}/agent.md`;
|
|
3912
|
-
}
|
|
3913
|
-
async _ensureAgentMdUploadToken(aid, gatewayUrl) {
|
|
3914
|
-
const auth = this._internal._auth;
|
|
3915
|
-
let identity = await auth.loadIdentityOrNone(aid);
|
|
3916
|
-
if (!identity) {
|
|
3917
|
-
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3918
|
-
}
|
|
3919
|
-
const cachedToken = String(identity.access_token ?? "");
|
|
3920
|
-
const expiresAt = auth.getAccessTokenExpiry(identity);
|
|
3921
|
-
if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1e3 + 30)) {
|
|
3922
|
-
return cachedToken;
|
|
3923
|
-
}
|
|
3924
|
-
if (typeof auth.refreshCachedTokens === "function" && identity.refresh_token) {
|
|
3925
|
-
try {
|
|
3926
|
-
identity = await auth.refreshCachedTokens(gatewayUrl, identity);
|
|
3927
|
-
const refreshedToken = String(identity.access_token ?? "");
|
|
3928
|
-
const refreshedExpiry = auth.getAccessTokenExpiry(identity);
|
|
3929
|
-
if (refreshedToken && (refreshedExpiry === null || refreshedExpiry > Date.now() / 1e3 + 30)) {
|
|
3930
|
-
return refreshedToken;
|
|
3931
|
-
}
|
|
3932
|
-
} catch {
|
|
3933
|
-
}
|
|
3934
|
-
}
|
|
3935
|
-
const result = await this.authenticate({ aid });
|
|
3936
|
-
const token = String(result.access_token ?? "");
|
|
3937
|
-
if (!token) {
|
|
3938
|
-
throw new StateError("authenticate did not return access_token");
|
|
3939
|
-
}
|
|
3940
|
-
return token;
|
|
3941
|
-
}
|
|
3942
|
-
async uploadAgentMd(content) {
|
|
3943
|
-
const tStart = Date.now();
|
|
3944
|
-
this._log.debug(`uploadAgentMd enter: content_len=${String(content ?? "").length}`);
|
|
3945
|
-
try {
|
|
3946
|
-
const auth = this._internal._auth;
|
|
3947
|
-
const identity = await auth.loadIdentityOrNone(this._client.aid ?? void 0);
|
|
3948
|
-
if (!identity) {
|
|
3949
|
-
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3950
|
-
}
|
|
3951
|
-
const aid = String(identity.aid ?? this._client.aid ?? "").trim();
|
|
3952
|
-
if (!aid) {
|
|
3953
|
-
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3954
|
-
}
|
|
3955
|
-
const gatewayUrl = await this._resolveGateway(aid);
|
|
3956
|
-
this._client.gatewayUrl = gatewayUrl;
|
|
3957
|
-
const token = await this._ensureAgentMdUploadToken(aid, gatewayUrl);
|
|
3958
|
-
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(aid), {
|
|
3959
|
-
method: "PUT",
|
|
3960
|
-
headers: {
|
|
3961
|
-
Authorization: `Bearer ${token}`,
|
|
3962
|
-
"Content-Type": "text/markdown; charset=utf-8"
|
|
3963
|
-
},
|
|
3964
|
-
body: content
|
|
3965
|
-
});
|
|
3966
|
-
if (response.status === 404) {
|
|
3967
|
-
throw new NotFoundError(`agent.md endpoint not found for aid: ${aid}`);
|
|
3968
|
-
}
|
|
3969
|
-
if (!response.ok) {
|
|
3970
|
-
const message = (await response.text()).trim();
|
|
3971
|
-
throw new AUNError(
|
|
3972
|
-
`upload agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ""}`
|
|
3973
|
-
);
|
|
3974
|
-
}
|
|
3975
|
-
const payload = await response.json();
|
|
3976
|
-
if (!isJsonObject(payload)) {
|
|
3977
|
-
throw new AUNError("upload agent.md returned invalid JSON payload");
|
|
3978
|
-
}
|
|
3979
|
-
this._log.debug(`uploadAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
3980
|
-
return payload;
|
|
3981
|
-
} catch (err) {
|
|
3982
|
-
this._log.debug(`uploadAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
3983
|
-
throw err;
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
async headAgentMd(aid) {
|
|
3987
|
-
const tStart = Date.now();
|
|
3988
|
-
const targetAid = String(aid ?? "").trim();
|
|
3989
|
-
if (!targetAid) {
|
|
3990
|
-
throw new ValidationError("headAgentMd requires non-empty aid");
|
|
3991
|
-
}
|
|
3992
|
-
this._log.debug(`headAgentMd enter: aid=${targetAid}`);
|
|
3993
|
-
try {
|
|
3994
|
-
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(targetAid), {
|
|
3995
|
-
method: "HEAD",
|
|
3996
|
-
headers: { Accept: "text/markdown" }
|
|
3997
|
-
});
|
|
3998
|
-
const respHeaders = response.headers;
|
|
3999
|
-
const etag = respHeaders ? String(respHeaders.get("ETag") ?? "").trim() : "";
|
|
4000
|
-
const lastModified = respHeaders ? String(respHeaders.get("Last-Modified") ?? "").trim() : "";
|
|
4001
|
-
if (response.status === 404) {
|
|
4002
|
-
this._log.debug(`headAgentMd exit (not_found): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
4003
|
-
return { aid: targetAid, found: false, etag: "", last_modified: "", status: 404 };
|
|
4004
|
-
}
|
|
4005
|
-
if (response.status < 200 || response.status >= 300) {
|
|
4006
|
-
throw new AUNError(`head agent.md failed: HTTP ${response.status}`);
|
|
4007
|
-
}
|
|
4008
|
-
if (etag || lastModified) {
|
|
4009
|
-
const cached = this._agentMdCache.get(targetAid) ?? { text: "", etag: "", lastModified: "" };
|
|
4010
|
-
this._agentMdCache.set(targetAid, {
|
|
4011
|
-
text: cached.text ?? "",
|
|
4012
|
-
etag,
|
|
4013
|
-
lastModified
|
|
4014
|
-
});
|
|
4015
|
-
}
|
|
4016
|
-
this._log.debug(`headAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${targetAid} status=${response.status} etag=${etag || "-"}`);
|
|
4017
|
-
return { aid: targetAid, found: true, etag, last_modified: lastModified, status: response.status };
|
|
4018
|
-
} catch (err) {
|
|
4019
|
-
this._log.debug(`headAgentMd exit (error): elapsed=${Date.now() - tStart}ms aid=${targetAid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
4020
|
-
throw err;
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
async downloadAgentMd(aid) {
|
|
4024
|
-
const tStart = Date.now();
|
|
4025
|
-
this._log.debug(`downloadAgentMd enter: aid=${aid}`);
|
|
4026
|
-
try {
|
|
4027
|
-
const targetAid = String(aid ?? "").trim();
|
|
4028
|
-
if (!targetAid) {
|
|
4029
|
-
throw new ValidationError("downloadAgentMd requires non-empty aid");
|
|
4030
|
-
}
|
|
4031
|
-
const cached = this._agentMdCache.get(targetAid);
|
|
4032
|
-
const requestHeaders = { Accept: "text/markdown" };
|
|
4033
|
-
const agentMdUrl = await this._resolveAgentMdUrl(targetAid);
|
|
4034
|
-
const response = await fetchWithTimeout(agentMdUrl, {
|
|
4035
|
-
method: "GET",
|
|
4036
|
-
headers: requestHeaders,
|
|
4037
|
-
redirect: "follow"
|
|
4038
|
-
});
|
|
4039
|
-
if (response.status === 304) {
|
|
4040
|
-
if (cached?.text) {
|
|
4041
|
-
this._log.debug(`downloadAgentMd exit (not_modified): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
4042
|
-
return cached.text;
|
|
4043
|
-
}
|
|
4044
|
-
this._log.warn(`downloadAgentMd got 304 but no local cache, retrying unconditional GET: aid=${targetAid}`);
|
|
4045
|
-
const retryResp = await fetchWithTimeout(agentMdUrl, {
|
|
4046
|
-
method: "GET",
|
|
4047
|
-
headers: { Accept: "text/markdown" },
|
|
4048
|
-
redirect: "follow"
|
|
4049
|
-
});
|
|
4050
|
-
if (retryResp.status === 404) {
|
|
4051
|
-
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
4052
|
-
}
|
|
4053
|
-
if (!retryResp.ok) {
|
|
4054
|
-
const message = (await retryResp.text()).trim();
|
|
4055
|
-
throw new AUNError(
|
|
4056
|
-
`download agent.md failed (retry): HTTP ${retryResp.status}${message ? ` - ${message}` : ""}`
|
|
4057
|
-
);
|
|
4058
|
-
}
|
|
4059
|
-
const retryText = await retryResp.text();
|
|
4060
|
-
this._log.debug(`downloadAgentMd exit (retry): elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${retryText.length}`);
|
|
4061
|
-
return retryText;
|
|
4062
|
-
}
|
|
4063
|
-
if (response.status === 404) {
|
|
4064
|
-
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
4065
|
-
}
|
|
4066
|
-
if (!response.ok) {
|
|
4067
|
-
const message = (await response.text()).trim();
|
|
4068
|
-
throw new AUNError(
|
|
4069
|
-
`download agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ""}`
|
|
4070
|
-
);
|
|
4071
|
-
}
|
|
4072
|
-
const text = await response.text();
|
|
4073
|
-
const respHeaders = response.headers;
|
|
4074
|
-
const etag = respHeaders ? String(respHeaders.get("ETag") ?? "").trim() : "";
|
|
4075
|
-
const lastModified = respHeaders ? String(respHeaders.get("Last-Modified") ?? "").trim() : "";
|
|
4076
|
-
if (etag || lastModified) {
|
|
4077
|
-
this._agentMdCache.set(targetAid, { text, etag, lastModified });
|
|
4078
|
-
}
|
|
4079
|
-
this._log.debug(`downloadAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${text.length}`);
|
|
4080
|
-
return text;
|
|
4081
|
-
} catch (err) {
|
|
4082
|
-
this._log.debug(`downloadAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4083
|
-
throw err;
|
|
4084
|
-
}
|
|
4085
|
-
}
|
|
4086
|
-
/** 下载证书(透传 RPC) */
|
|
4087
|
-
async downloadCert(params) {
|
|
4088
|
-
const tStart = Date.now();
|
|
4089
|
-
this._log.debug("downloadCert enter");
|
|
4090
|
-
try {
|
|
4091
|
-
const r = await this._client.call("auth.download_cert", params ?? {});
|
|
4092
|
-
this._log.debug(`downloadCert exit: elapsed=${Date.now() - tStart}ms`);
|
|
4093
|
-
return r;
|
|
4094
|
-
} catch (err) {
|
|
4095
|
-
this._log.debug(`downloadCert exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4096
|
-
throw err;
|
|
4097
|
-
}
|
|
4098
|
-
}
|
|
4099
|
-
/** 请求签发证书(透传 RPC) */
|
|
4100
|
-
async requestCert(params) {
|
|
4101
|
-
const tStart = Date.now();
|
|
4102
|
-
this._log.debug("requestCert enter");
|
|
4103
|
-
try {
|
|
4104
|
-
const r = await this._client.call("auth.request_cert", params);
|
|
4105
|
-
this._log.debug(`requestCert exit: elapsed=${Date.now() - tStart}ms`);
|
|
4106
|
-
return r;
|
|
4107
|
-
} catch (err) {
|
|
4108
|
-
this._log.debug(`requestCert exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4109
|
-
throw err;
|
|
4110
|
-
}
|
|
4111
|
-
}
|
|
4112
|
-
/** 续期证书(透传 RPC) */
|
|
4113
|
-
async renewCert(params) {
|
|
4114
|
-
const tStart = Date.now();
|
|
4115
|
-
this._log.debug("renewCert enter");
|
|
4116
|
-
try {
|
|
4117
|
-
const r = await this._client.call("auth.renew_cert", params ?? {});
|
|
4118
|
-
this._log.debug(`renewCert exit: elapsed=${Date.now() - tStart}ms`);
|
|
4119
|
-
return r;
|
|
4120
|
-
} catch (err) {
|
|
4121
|
-
this._log.debug(`renewCert exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4122
|
-
throw err;
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
/** 密钥轮换(透传 RPC) */
|
|
4126
|
-
async rekey(params) {
|
|
4127
|
-
const tStart = Date.now();
|
|
4128
|
-
this._log.debug("rekey enter");
|
|
4129
|
-
try {
|
|
4130
|
-
const r = await this._client.call("auth.rekey", params ?? {});
|
|
4131
|
-
this._log.debug(`rekey exit: elapsed=${Date.now() - tStart}ms`);
|
|
4132
|
-
return r;
|
|
4133
|
-
} catch (err) {
|
|
4134
|
-
this._log.debug(`rekey exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4135
|
-
throw err;
|
|
4136
|
-
}
|
|
4137
|
-
}
|
|
4138
|
-
/** 获取信任根(透传 RPC) */
|
|
4139
|
-
async trustRoots(params) {
|
|
4140
|
-
const tStart = Date.now();
|
|
4141
|
-
this._log.debug("trustRoots enter");
|
|
4142
|
-
try {
|
|
4143
|
-
const r = await this._client.call("meta.trust_roots", params ?? {});
|
|
4144
|
-
this._log.debug(`trustRoots exit: elapsed=${Date.now() - tStart}ms`);
|
|
4145
|
-
return r;
|
|
4146
|
-
} catch (err) {
|
|
4147
|
-
this._log.debug(`trustRoots exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4148
|
-
throw err;
|
|
4149
|
-
}
|
|
4150
|
-
}
|
|
4151
|
-
/**
|
|
4152
|
-
* 检查指定 AID 的本地和远程状态。
|
|
4153
|
-
* 与 Python SDK namespaces/auth_namespace.py:check_aid 对应。
|
|
4154
|
-
*/
|
|
4155
|
-
async checkAid(params) {
|
|
4156
|
-
const tStart = Date.now();
|
|
4157
|
-
const aid = String(params?.aid ?? "").trim();
|
|
4158
|
-
if (!aid) throw new ValidationError("auth.check_aid requires 'aid'");
|
|
4159
|
-
this._log.debug(`checkAid enter: aid=${aid}`);
|
|
4160
|
-
try {
|
|
4161
|
-
const result = await this._checkLocalAid(aid);
|
|
4162
|
-
const local = result.local;
|
|
4163
|
-
if (!local?.complete) {
|
|
4164
|
-
const remote = await this._checkRemoteAidRegistration(aid);
|
|
4165
|
-
result.remote = remote;
|
|
4166
|
-
const remoteStatus = remote?.status;
|
|
4167
|
-
if (remoteStatus === "available") {
|
|
4168
|
-
result.status = "available";
|
|
4169
|
-
result.can_register = true;
|
|
4170
|
-
} else if (remoteStatus === "registered") {
|
|
4171
|
-
result.status = "registered_remote";
|
|
4172
|
-
result.can_register = false;
|
|
4173
|
-
} else {
|
|
4174
|
-
result.status = "unknown";
|
|
4175
|
-
result.can_register = false;
|
|
4176
|
-
}
|
|
4177
|
-
}
|
|
4178
|
-
this._log.debug(`checkAid exit: elapsed=${Date.now() - tStart}ms aid=${aid} status=${String(result.status)}`);
|
|
4179
|
-
return result;
|
|
4180
|
-
} catch (err) {
|
|
4181
|
-
this._log.debug(`checkAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
4182
|
-
throw err;
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
async _checkLocalAid(aid) {
|
|
4186
|
-
const client = this._internal;
|
|
4187
|
-
const ks = client._keystore;
|
|
4188
|
-
const identity = await client._auth.loadIdentityOrNone(aid);
|
|
4189
|
-
let keyPair = null;
|
|
4190
|
-
let keyError = "";
|
|
4191
|
-
try {
|
|
4192
|
-
if (ks && typeof ks.loadKeyPair === "function") {
|
|
4193
|
-
keyPair = await ks.loadKeyPair(aid);
|
|
4194
|
-
}
|
|
4195
|
-
} catch (e) {
|
|
4196
|
-
keyError = e instanceof Error ? e.message : String(e);
|
|
4197
|
-
}
|
|
4198
|
-
let certPem = null;
|
|
4199
|
-
let certError = "";
|
|
4200
|
-
try {
|
|
4201
|
-
if (ks && typeof ks.loadCert === "function") {
|
|
4202
|
-
certPem = await ks.loadCert(aid);
|
|
4203
|
-
}
|
|
4204
|
-
} catch (e) {
|
|
4205
|
-
certError = e instanceof Error ? e.message : String(e);
|
|
4206
|
-
}
|
|
4207
|
-
const privateKeyPresent = !!(keyPair && keyPair.private_key_pem);
|
|
4208
|
-
const publicKeyPresent = !!(keyPair && keyPair.public_key_der_b64);
|
|
4209
|
-
const certPresent = !!certPem;
|
|
4210
|
-
const certInfo = certPresent ? await this._inspectCertBrowser(aid, certPem) : { present: false, valid: false, expired: false };
|
|
4211
|
-
const certValid = !!certInfo.valid;
|
|
4212
|
-
const localComplete = privateKeyPresent && publicKeyPresent && certPresent && certValid;
|
|
4213
|
-
const issues = [];
|
|
4214
|
-
if (!identity) issues.push("local identity not found");
|
|
4215
|
-
if (!privateKeyPresent) issues.push("private key missing");
|
|
4216
|
-
if (!publicKeyPresent) issues.push("public key missing");
|
|
4217
|
-
if (!certPresent) {
|
|
4218
|
-
issues.push("certificate missing");
|
|
4219
|
-
} else if (certInfo.parse_error) {
|
|
4220
|
-
issues.push(`certificate invalid: ${certInfo.parse_error}`);
|
|
4221
|
-
} else if (certInfo.expired) {
|
|
4222
|
-
issues.push("certificate expired");
|
|
4223
|
-
} else if (!certValid) {
|
|
4224
|
-
issues.push("certificate not currently valid");
|
|
4225
|
-
}
|
|
4226
|
-
if (keyError) issues.push(`key load error: ${keyError}`);
|
|
4227
|
-
if (certError) issues.push(`certificate load error: ${certError}`);
|
|
4228
|
-
return {
|
|
4229
|
-
aid,
|
|
4230
|
-
status: localComplete ? "local_ready" : "local_incomplete",
|
|
4231
|
-
can_register: localComplete ? false : null,
|
|
4232
|
-
local: {
|
|
4233
|
-
exists: identity !== null,
|
|
4234
|
-
complete: localComplete,
|
|
4235
|
-
private_key: privateKeyPresent,
|
|
4236
|
-
public_key: publicKeyPresent,
|
|
4237
|
-
certificate: certInfo,
|
|
4238
|
-
issues
|
|
4239
|
-
},
|
|
4240
|
-
remote: {
|
|
4241
|
-
status: localComplete ? "not_checked" : "pending"
|
|
4242
|
-
}
|
|
4243
|
-
};
|
|
4244
|
-
}
|
|
4245
|
-
/** 浏览器环境证书检查(无 node:crypto X509Certificate) */
|
|
4246
|
-
async _inspectCertBrowser(aid, certPem) {
|
|
4247
|
-
const result = { present: true, valid: false, expired: false };
|
|
4248
|
-
try {
|
|
4249
|
-
const fingerprint = await certificateSha256Fingerprint(certPem);
|
|
4250
|
-
const cn = extractCommonNameFromCertPem(certPem);
|
|
4251
|
-
const aidMatches = !cn || cn === aid;
|
|
4252
|
-
const validity = parseCertValidity(certPem);
|
|
4253
|
-
if (validity) {
|
|
4254
|
-
const now = Date.now();
|
|
4255
|
-
const valid = now >= validity.notBefore && now <= validity.notAfter && aidMatches;
|
|
4256
|
-
const expired = now > validity.notAfter;
|
|
4257
|
-
result.valid = valid;
|
|
4258
|
-
result.expired = expired;
|
|
4259
|
-
result.not_before = new Date(validity.notBefore).toISOString();
|
|
4260
|
-
result.not_after = new Date(validity.notAfter).toISOString();
|
|
4261
|
-
result.expires_at = Math.floor(validity.notAfter / 1e3);
|
|
4262
|
-
result.seconds_until_expiry = Math.floor((validity.notAfter - now) / 1e3);
|
|
4263
|
-
} else {
|
|
4264
|
-
result.valid = aidMatches;
|
|
4265
|
-
}
|
|
4266
|
-
result.fingerprint = fingerprint;
|
|
4267
|
-
result.subject_cn = cn;
|
|
4268
|
-
result.aid_matches = aidMatches;
|
|
4269
|
-
if (cn && cn !== aid) {
|
|
4270
|
-
result.valid = false;
|
|
4271
|
-
result.parse_error = `certificate CN mismatch: ${cn}`;
|
|
4272
|
-
}
|
|
4273
|
-
} catch (e) {
|
|
4274
|
-
result.parse_error = e instanceof Error ? e.message : String(e);
|
|
4275
|
-
}
|
|
4276
|
-
return result;
|
|
4277
|
-
}
|
|
4278
|
-
async _checkRemoteAidRegistration(aid) {
|
|
4279
|
-
try {
|
|
4280
|
-
const content = await this.downloadAgentMd(aid);
|
|
4281
|
-
return {
|
|
4282
|
-
status: "registered",
|
|
4283
|
-
registered: true,
|
|
4284
|
-
available: false,
|
|
4285
|
-
source: "agent.md",
|
|
4286
|
-
agent_md_bytes: new TextEncoder().encode(content).length,
|
|
4287
|
-
agent_md_aid: extractAgentMDAid(content)
|
|
4288
|
-
};
|
|
4289
|
-
} catch (err) {
|
|
4290
|
-
if (err instanceof NotFoundError) {
|
|
4291
|
-
return {
|
|
4292
|
-
status: "available",
|
|
4293
|
-
registered: false,
|
|
4294
|
-
available: true,
|
|
4295
|
-
source: "agent.md"
|
|
4296
|
-
};
|
|
4297
|
-
}
|
|
4298
|
-
return {
|
|
4299
|
-
status: "unknown",
|
|
4300
|
-
registered: null,
|
|
4301
|
-
available: null,
|
|
4302
|
-
source: "agent.md",
|
|
4303
|
-
error: err instanceof Error ? err.message : String(err)
|
|
4304
|
-
};
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
};
|
|
4308
|
-
|
|
4309
|
-
// src/namespaces/custody.ts
|
|
4310
|
-
var _noopLog6 = { error: () => {
|
|
4311
|
-
}, warn: () => {
|
|
4312
|
-
}, info: () => {
|
|
4313
|
-
}, debug: () => {
|
|
4314
|
-
} };
|
|
4315
|
-
var CUSTODY_HTTP_TIMEOUT_MS = 3e4;
|
|
4316
|
-
function issuerDomainFromAid(aid) {
|
|
4317
|
-
const parts = String(aid || "").trim().split(".", 2);
|
|
4318
|
-
return parts.length > 1 ? parts[1] : parts[0] || "";
|
|
4319
|
-
}
|
|
4320
|
-
function custodyWellKnownUrls(aid, discoveryPort, verifySsl) {
|
|
4321
|
-
const portSuffix = discoveryPort ? `:${discoveryPort}` : "";
|
|
4322
|
-
const issuerDomain = issuerDomainFromAid(aid);
|
|
4323
|
-
const aidUrl = `https://${aid}${portSuffix}/.well-known/aun-custody`;
|
|
4324
|
-
const fallbackUrl = `https://aid_custody.${issuerDomain}${portSuffix}/.well-known/aun-custody`;
|
|
4325
|
-
const urls = verifySsl ? [aidUrl, fallbackUrl] : [fallbackUrl, aidUrl];
|
|
4326
|
-
return [...new Set(urls)];
|
|
4327
|
-
}
|
|
4328
|
-
function extractCustodyUrl(payload) {
|
|
4329
|
-
for (const key of ["custody_url", "custodyUrl", "url"]) {
|
|
4330
|
-
const value = String(payload[key] ?? "").trim();
|
|
4331
|
-
if (value) return value;
|
|
4332
|
-
}
|
|
4333
|
-
if (isJsonObject(payload.custody)) {
|
|
4334
|
-
const value = String(payload.custody.url ?? "").trim();
|
|
4335
|
-
if (value) return value;
|
|
4336
|
-
}
|
|
4337
|
-
for (const key of ["custody_services", "custodyServices", "services"]) {
|
|
4338
|
-
const items = payload[key];
|
|
4339
|
-
if (Array.isArray(items)) {
|
|
4340
|
-
const candidates = items.filter(isJsonObject).sort((a, b) => Number(a.priority ?? 999) - Number(b.priority ?? 999));
|
|
4341
|
-
for (const item of candidates) {
|
|
4342
|
-
const value = String(item.url ?? "").trim();
|
|
4343
|
-
if (value) return value;
|
|
4344
|
-
}
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
|
-
throw new ValidationError("custody well-known missing custody url");
|
|
4348
|
-
}
|
|
4349
|
-
function normalizeCustodyUrl(url) {
|
|
4350
|
-
const value = String(url ?? "").trim().replace(/\/+$/, "");
|
|
4351
|
-
if (!value) return null;
|
|
4352
|
-
try {
|
|
4353
|
-
const parsed = new URL(value);
|
|
4354
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname) {
|
|
4355
|
-
return null;
|
|
4356
|
-
}
|
|
4357
|
-
return value;
|
|
4358
|
-
} catch {
|
|
4359
|
-
return null;
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
async function fetchJsonWithTimeout(input, init, timeoutMs = CUSTODY_HTTP_TIMEOUT_MS) {
|
|
4363
|
-
const controller = new AbortController();
|
|
4364
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4365
|
-
try {
|
|
4366
|
-
const response = await fetch(input, { ...init, signal: controller.signal });
|
|
4367
|
-
const payload = await response.json();
|
|
4368
|
-
if (response.ok) {
|
|
4369
|
-
return payload;
|
|
4370
|
-
}
|
|
4371
|
-
const error = isJsonObject(payload) && isJsonObject(payload.error) ? payload.error : null;
|
|
4372
|
-
const code = String(error?.code ?? "");
|
|
4373
|
-
const message = String(error?.message ?? "");
|
|
4374
|
-
throw new AUNError(
|
|
4375
|
-
message ? `custody ${code || response.status}: ${message}` : `custody HTTP ${response.status}`
|
|
4376
|
-
);
|
|
4377
|
-
} catch (error) {
|
|
4378
|
-
if (controller.signal.aborted) {
|
|
4379
|
-
throw new AUNError(`custody request timed out after ${timeoutMs}ms`);
|
|
4380
|
-
}
|
|
4381
|
-
throw error;
|
|
4382
|
-
} finally {
|
|
4383
|
-
clearTimeout(timer);
|
|
4384
|
-
}
|
|
4385
|
-
}
|
|
4386
|
-
var CustodyNamespace = class {
|
|
4387
|
-
constructor(client) {
|
|
4388
|
-
__publicField(this, "_client");
|
|
4389
|
-
__publicField(this, "_custodyUrl", "");
|
|
4390
|
-
__publicField(this, "_log", _noopLog6);
|
|
4391
|
-
this._client = client;
|
|
4392
|
-
}
|
|
4393
|
-
setLogger(log) {
|
|
4394
|
-
this._log = log;
|
|
4395
|
-
}
|
|
4396
|
-
get _internal() {
|
|
4397
|
-
return this._client;
|
|
4398
|
-
}
|
|
4399
|
-
setUrl(url) {
|
|
4400
|
-
this._custodyUrl = String(url ?? "").trim().replace(/\/+$/, "");
|
|
4401
|
-
}
|
|
4402
|
-
configureUrl(url) {
|
|
4403
|
-
this.setUrl(url);
|
|
4404
|
-
}
|
|
4405
|
-
async discoverUrl(params = {}) {
|
|
4406
|
-
const tStart = Date.now();
|
|
4407
|
-
this._log.debug(`discoverUrl enter: aid=${params.aid ?? "<current>"}`);
|
|
4408
|
-
try {
|
|
4409
|
-
const aid = String(params.aid ?? this._client.aid ?? "").trim();
|
|
4410
|
-
if (!aid) {
|
|
4411
|
-
throw new ValidationError("custody.discoverUrl requires aid or authenticated client");
|
|
4412
|
-
}
|
|
4413
|
-
let lastError = null;
|
|
4414
|
-
const urls = custodyWellKnownUrls(aid, this._client.configModel.discoveryPort, this._client.configModel.verifySsl);
|
|
4415
|
-
for (const wellKnownUrl of urls) {
|
|
4416
|
-
try {
|
|
4417
|
-
const payload = await fetchJsonWithTimeout(wellKnownUrl, { method: "GET" }, params.timeout ?? 5e3);
|
|
4418
|
-
if (!isJsonObject(payload)) {
|
|
4419
|
-
throw new ValidationError("custody well-known returned invalid payload");
|
|
4420
|
-
}
|
|
4421
|
-
const custodyUrl = normalizeCustodyUrl(extractCustodyUrl(payload));
|
|
4422
|
-
if (!custodyUrl) {
|
|
4423
|
-
throw new ValidationError("custody well-known returned invalid custody url");
|
|
4424
|
-
}
|
|
4425
|
-
this._custodyUrl = custodyUrl;
|
|
4426
|
-
this._log.debug(`discoverUrl exit: elapsed=${Date.now() - tStart}ms aid=${aid} url=${custodyUrl}`);
|
|
4427
|
-
return custodyUrl;
|
|
4428
|
-
} catch (error) {
|
|
4429
|
-
lastError = error;
|
|
4430
|
-
}
|
|
4431
|
-
}
|
|
4432
|
-
throw new AUNError(`custody discovery failed for ${aid}: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
4433
|
-
} catch (err) {
|
|
4434
|
-
this._log.debug(`discoverUrl exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4435
|
-
throw err;
|
|
4436
|
-
}
|
|
4437
|
-
}
|
|
4438
|
-
async _resolveCustodyUrl(aid) {
|
|
4439
|
-
const custodyUrl = normalizeCustodyUrl(this._custodyUrl);
|
|
4440
|
-
if (custodyUrl) {
|
|
4441
|
-
if (custodyUrl !== this._custodyUrl) {
|
|
4442
|
-
this._custodyUrl = custodyUrl;
|
|
4443
|
-
}
|
|
4444
|
-
return custodyUrl;
|
|
4445
|
-
}
|
|
4446
|
-
return this.discoverUrl({ aid });
|
|
4447
|
-
}
|
|
4448
|
-
_getAccessToken() {
|
|
4449
|
-
const identity = this._internal._identity;
|
|
4450
|
-
if (identity) {
|
|
4451
|
-
const token = String(identity.access_token ?? "").trim();
|
|
4452
|
-
if (token) return token;
|
|
4453
|
-
}
|
|
4454
|
-
throw new ValidationError("no access_token available: call auth.authenticate() first");
|
|
4455
|
-
}
|
|
4456
|
-
async _post(path, body, opts = {}) {
|
|
4457
|
-
const headers = { "Content-Type": "application/json" };
|
|
4458
|
-
const token = String(opts.token ?? "").trim();
|
|
4459
|
-
if (token) {
|
|
4460
|
-
headers.Authorization = `Bearer ${token}`;
|
|
4461
|
-
}
|
|
4462
|
-
const payload = await fetchJsonWithTimeout(
|
|
4463
|
-
`${await this._resolveCustodyUrl(String(body.aid ?? "") || this._client.aid)}${path}`,
|
|
4464
|
-
{
|
|
4465
|
-
method: "POST",
|
|
4466
|
-
headers,
|
|
4467
|
-
body: JSON.stringify(body)
|
|
4468
|
-
}
|
|
4469
|
-
);
|
|
4470
|
-
if (!isJsonObject(payload)) {
|
|
4471
|
-
throw new AUNError("custody returned invalid JSON payload");
|
|
4472
|
-
}
|
|
4473
|
-
return payload;
|
|
4474
|
-
}
|
|
4475
|
-
async sendCode(params) {
|
|
4476
|
-
const tStart = Date.now();
|
|
4477
|
-
this._log.debug(`sendCode enter: aid=${params.aid ?? "<current>"}`);
|
|
4478
|
-
try {
|
|
4479
|
-
const phone = String(params.phone ?? "").trim();
|
|
4480
|
-
const aid = String(params.aid ?? "").trim();
|
|
4481
|
-
if (!phone) {
|
|
4482
|
-
throw new ValidationError("custody.sendCode requires non-empty phone");
|
|
4483
|
-
}
|
|
4484
|
-
const body = { phone };
|
|
4485
|
-
let token = null;
|
|
4486
|
-
if (aid) {
|
|
4487
|
-
body.aid = aid;
|
|
4488
|
-
} else {
|
|
4489
|
-
token = this._getAccessToken();
|
|
4490
|
-
}
|
|
4491
|
-
const r = await this._post("/custody/accounts/send-code", body, { token });
|
|
4492
|
-
this._log.debug(`sendCode exit: elapsed=${Date.now() - tStart}ms`);
|
|
4493
|
-
return r;
|
|
4494
|
-
} catch (err) {
|
|
4495
|
-
this._log.debug(`sendCode exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4496
|
-
throw err;
|
|
4497
|
-
}
|
|
4498
|
-
}
|
|
4499
|
-
async bindPhone(params) {
|
|
4500
|
-
const tStart = Date.now();
|
|
4501
|
-
this._log.debug("bindPhone enter");
|
|
4502
|
-
try {
|
|
4503
|
-
const phone = String(params.phone ?? "").trim();
|
|
4504
|
-
const code = String(params.code ?? "").trim();
|
|
4505
|
-
const cert = String(params.cert ?? "").trim();
|
|
4506
|
-
const key = String(params.key ?? "").trim();
|
|
4507
|
-
if (!phone || !code || !cert || !key) {
|
|
4508
|
-
throw new ValidationError("custody.bindPhone requires phone, code, cert and key");
|
|
4509
|
-
}
|
|
4510
|
-
const body = { phone, code, cert, key };
|
|
4511
|
-
if (params.metadata && isJsonObject(params.metadata)) {
|
|
4512
|
-
body.metadata = params.metadata;
|
|
4513
|
-
}
|
|
4514
|
-
const r = await this._post("/custody/accounts/bind-phone", body, {
|
|
4515
|
-
token: this._getAccessToken()
|
|
4516
|
-
});
|
|
4517
|
-
this._log.debug(`bindPhone exit: elapsed=${Date.now() - tStart}ms`);
|
|
4518
|
-
return r;
|
|
4519
|
-
} catch (err) {
|
|
4520
|
-
this._log.debug(`bindPhone exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4521
|
-
throw err;
|
|
4522
|
-
}
|
|
4523
|
-
}
|
|
4524
|
-
async restorePhone(params) {
|
|
4525
|
-
const tStart = Date.now();
|
|
4526
|
-
this._log.debug(`restorePhone enter: aid=${params.aid}`);
|
|
4527
|
-
try {
|
|
4528
|
-
const phone = String(params.phone ?? "").trim();
|
|
4529
|
-
const code = String(params.code ?? "").trim();
|
|
4530
|
-
const aid = String(params.aid ?? "").trim();
|
|
4531
|
-
if (!phone || !code || !aid) {
|
|
4532
|
-
throw new ValidationError("custody.restorePhone requires phone, code and aid");
|
|
4533
|
-
}
|
|
4534
|
-
const r = await this._post("/custody/accounts/restore-phone", { phone, code, aid });
|
|
4535
|
-
this._log.debug(`restorePhone exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
4536
|
-
return r;
|
|
4537
|
-
} catch (err) {
|
|
4538
|
-
this._log.debug(`restorePhone exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4539
|
-
throw err;
|
|
4540
|
-
}
|
|
4541
|
-
}
|
|
4542
|
-
async createDeviceCopy(params = {}) {
|
|
4543
|
-
const tStart = Date.now();
|
|
4544
|
-
this._log.debug(`createDeviceCopy enter: aid=${params.aid ?? "<current>"}`);
|
|
4545
|
-
try {
|
|
4546
|
-
const aid = String(params.aid ?? this._client.aid ?? "").trim();
|
|
4547
|
-
if (!aid) {
|
|
4548
|
-
throw new ValidationError("custody.createDeviceCopy requires aid or authenticated client");
|
|
4549
|
-
}
|
|
4550
|
-
const r = await this._post("/custody/transfers", { aid }, { token: this._getAccessToken() });
|
|
4551
|
-
this._log.debug(`createDeviceCopy exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
4552
|
-
return r;
|
|
4553
|
-
} catch (err) {
|
|
4554
|
-
this._log.debug(`createDeviceCopy exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4555
|
-
throw err;
|
|
4556
|
-
}
|
|
4557
|
-
}
|
|
4558
|
-
async uploadDeviceCopyMaterials(params) {
|
|
4559
|
-
const tStart = Date.now();
|
|
4560
|
-
this._log.debug(`uploadDeviceCopyMaterials enter: aid=${params.aid ?? "<current>"}`);
|
|
4561
|
-
try {
|
|
4562
|
-
const transferCode = String(params.transferCode ?? "").trim();
|
|
4563
|
-
const aid = String(params.aid ?? this._client.aid ?? "").trim();
|
|
4564
|
-
const cert = String(params.cert ?? "").trim();
|
|
4565
|
-
const key = String(params.key ?? "").trim();
|
|
4566
|
-
if (!transferCode || !aid || !cert || !key) {
|
|
4567
|
-
throw new ValidationError("custody.uploadDeviceCopyMaterials requires transferCode, aid, cert and key");
|
|
4568
|
-
}
|
|
4569
|
-
const body = { aid, cert, key };
|
|
4570
|
-
if (params.metadata && isJsonObject(params.metadata)) {
|
|
4571
|
-
body.metadata = params.metadata;
|
|
4572
|
-
}
|
|
4573
|
-
const r = await this._post(`/custody/transfers/${encodeURIComponent(transferCode)}/materials`, body, {
|
|
4574
|
-
token: this._getAccessToken()
|
|
4575
|
-
});
|
|
4576
|
-
this._log.debug(`uploadDeviceCopyMaterials exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
4577
|
-
return r;
|
|
4578
|
-
} catch (err) {
|
|
4579
|
-
this._log.debug(`uploadDeviceCopyMaterials exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4580
|
-
throw err;
|
|
4581
|
-
}
|
|
4582
|
-
}
|
|
4583
|
-
async claimDeviceCopy(params) {
|
|
4584
|
-
const tStart = Date.now();
|
|
4585
|
-
this._log.debug(`claimDeviceCopy enter: aid=${params.aid}`);
|
|
4586
|
-
try {
|
|
4587
|
-
const aid = String(params.aid ?? "").trim();
|
|
4588
|
-
const transferCode = String(params.transferCode ?? "").trim();
|
|
4589
|
-
if (!aid || !transferCode) {
|
|
4590
|
-
throw new ValidationError("custody.claimDeviceCopy requires aid and transferCode");
|
|
4591
|
-
}
|
|
4592
|
-
const r = await this._post("/custody/transfers/claim", { aid, transfer_code: transferCode });
|
|
4593
|
-
this._log.debug(`claimDeviceCopy exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
4594
|
-
return r;
|
|
4595
|
-
} catch (err) {
|
|
4596
|
-
this._log.debug(`claimDeviceCopy exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4597
|
-
throw err;
|
|
4598
|
-
}
|
|
4599
|
-
}
|
|
4600
|
-
};
|
|
4601
|
-
|
|
4602
|
-
// src/namespaces/meta.ts
|
|
4603
|
-
init_crypto();
|
|
4604
|
-
var _noopLog7 = { error: () => {
|
|
4605
|
-
}, warn: () => {
|
|
4606
|
-
}, info: () => {
|
|
4607
|
-
}, debug: () => {
|
|
4608
|
-
} };
|
|
4609
|
-
var AUTHORITY_ENDPOINT = "https://trust.aun.network/.well-known/aun/trust-roots.json";
|
|
4610
|
-
var MAX_CLOCK_SKEW = 300;
|
|
4611
|
-
var MetaNamespace = class _MetaNamespace {
|
|
4612
|
-
constructor(client) {
|
|
4613
|
-
__publicField(this, "_log", _noopLog7);
|
|
4614
|
-
__publicField(this, "_client");
|
|
4615
|
-
this._client = client;
|
|
4616
|
-
}
|
|
4617
|
-
setLogger(log) {
|
|
4618
|
-
this._log = log;
|
|
4619
|
-
}
|
|
4620
|
-
get _internal() {
|
|
4621
|
-
return this._client;
|
|
4622
|
-
}
|
|
4623
|
-
// ── RPC 直通 ──────────────────────────────────────────────
|
|
4624
|
-
async ping(params) {
|
|
4625
|
-
const tStart = Date.now();
|
|
4626
|
-
this._log.debug("ping enter");
|
|
4627
|
-
try {
|
|
4628
|
-
const r = await this._client.call("meta.ping", params ?? {});
|
|
4629
|
-
this._log.debug(`ping exit: elapsed=${Date.now() - tStart}ms`);
|
|
4630
|
-
return r;
|
|
4631
|
-
} catch (err) {
|
|
4632
|
-
this._log.debug(`ping exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4633
|
-
throw err;
|
|
4634
|
-
}
|
|
4635
|
-
}
|
|
4636
|
-
async status(params) {
|
|
4637
|
-
const tStart = Date.now();
|
|
4638
|
-
this._log.debug("status enter");
|
|
4639
|
-
try {
|
|
4640
|
-
const r = await this._client.call("meta.status", params ?? {});
|
|
4641
|
-
this._log.debug(`status exit: elapsed=${Date.now() - tStart}ms`);
|
|
4642
|
-
return r;
|
|
4643
|
-
} catch (err) {
|
|
4644
|
-
this._log.debug(`status exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4645
|
-
throw err;
|
|
4646
|
-
}
|
|
4647
|
-
}
|
|
4648
|
-
async trustRoots(params) {
|
|
4649
|
-
const tStart = Date.now();
|
|
4650
|
-
this._log.debug("trustRoots enter");
|
|
4651
|
-
try {
|
|
4652
|
-
const r = await this._client.call("meta.trust_roots", params ?? {});
|
|
4653
|
-
this._log.debug(`trustRoots exit: elapsed=${Date.now() - tStart}ms`);
|
|
4654
|
-
return r;
|
|
4655
|
-
} catch (err) {
|
|
4656
|
-
this._log.debug(`trustRoots exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4657
|
-
throw err;
|
|
4658
|
-
}
|
|
4659
|
-
}
|
|
4660
|
-
// ── 信任根下载 ────────────────────────────────────────────
|
|
4661
|
-
async downloadTrustRoots(opts) {
|
|
4662
|
-
const tStart = Date.now();
|
|
4663
|
-
this._log.debug(`downloadTrustRoots enter: issuer=${opts?.issuer ?? "-"}`);
|
|
4664
|
-
try {
|
|
4665
|
-
const target = this._resolveTrustRootsUrl(
|
|
4666
|
-
opts?.url,
|
|
4667
|
-
opts?.issuer,
|
|
4668
|
-
opts?.gateway_url
|
|
4669
|
-
);
|
|
4670
|
-
if (!target.toLowerCase().startsWith("https://") && !target.toLowerCase().startsWith("http://")) {
|
|
4671
|
-
throw new ValidationError("trust roots url must be http(s)");
|
|
4672
|
-
}
|
|
4673
|
-
const timeoutMs = (opts?.timeout ?? 10) * 1e3;
|
|
4674
|
-
const controller = new AbortController();
|
|
4675
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4676
|
-
try {
|
|
4677
|
-
const response = await fetch(target, {
|
|
4678
|
-
headers: { Accept: "application/json" },
|
|
4679
|
-
signal: controller.signal
|
|
4680
|
-
});
|
|
4681
|
-
if (!response.ok) {
|
|
4682
|
-
throw new ValidationError(`trust roots download failed: HTTP ${response.status}`);
|
|
4683
|
-
}
|
|
4684
|
-
const payload = await response.json();
|
|
4685
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
4686
|
-
throw new ValidationError("trust roots endpoint returned non-object JSON");
|
|
4687
|
-
}
|
|
4688
|
-
this._log.debug(`downloadTrustRoots exit: elapsed=${Date.now() - tStart}ms`);
|
|
4689
|
-
return payload;
|
|
4690
|
-
} finally {
|
|
4691
|
-
clearTimeout(timer);
|
|
4692
|
-
}
|
|
4693
|
-
} catch (err) {
|
|
4694
|
-
this._log.debug(`downloadTrustRoots exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4695
|
-
throw err;
|
|
4696
|
-
}
|
|
4697
|
-
}
|
|
4698
|
-
async downloadIssuerRootCert(issuer, opts) {
|
|
4699
|
-
const tStart = Date.now();
|
|
4700
|
-
this._log.debug(`downloadIssuerRootCert enter: issuer=${issuer}`);
|
|
4701
|
-
try {
|
|
4702
|
-
const normalizedIssuer = _MetaNamespace._validateIssuer(issuer);
|
|
4703
|
-
const target = opts?.url?.trim() || this._issuerRootCertUrl(normalizedIssuer);
|
|
4704
|
-
if (!target.toLowerCase().startsWith("https://") && !target.toLowerCase().startsWith("http://")) {
|
|
4705
|
-
throw new ValidationError("issuer root certificate url must be http(s)");
|
|
4706
|
-
}
|
|
4707
|
-
const timeoutMs = (opts?.timeout ?? 10) * 1e3;
|
|
4708
|
-
const controller = new AbortController();
|
|
4709
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4710
|
-
try {
|
|
4711
|
-
const response = await fetch(target, {
|
|
4712
|
-
headers: { Accept: "application/x-pem-file,text/plain" },
|
|
4713
|
-
signal: controller.signal
|
|
4714
|
-
});
|
|
4715
|
-
if (!response.ok) {
|
|
4716
|
-
throw new ValidationError(`issuer root cert download failed: HTTP ${response.status}`);
|
|
4717
|
-
}
|
|
4718
|
-
const certPem = (await response.text()).trim();
|
|
4719
|
-
_MetaNamespace._validateCertPem(certPem, normalizedIssuer);
|
|
4720
|
-
this._log.debug(`downloadIssuerRootCert exit: elapsed=${Date.now() - tStart}ms issuer=${normalizedIssuer}`);
|
|
4721
|
-
return certPem + "\n";
|
|
4722
|
-
} finally {
|
|
4723
|
-
clearTimeout(timer);
|
|
4724
|
-
}
|
|
4725
|
-
} catch (err) {
|
|
4726
|
-
this._log.debug(`downloadIssuerRootCert exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4727
|
-
throw err;
|
|
4728
|
-
}
|
|
4729
|
-
}
|
|
4730
|
-
// ── 信任根验证 ────────────────────────────────────────────
|
|
4731
|
-
async verifyTrustRoots(trustList, opts) {
|
|
4732
|
-
if (!trustList || typeof trustList !== "object") {
|
|
4733
|
-
throw new ValidationError("trust roots list must be a JSON object");
|
|
4734
|
-
}
|
|
4735
|
-
const signature = String(trustList.authority_signature ?? "").trim();
|
|
4736
|
-
if (!signature && !opts?.allow_unsigned) {
|
|
4737
|
-
throw new ValidationError("trust roots list missing authority_signature");
|
|
4738
|
-
}
|
|
4739
|
-
_MetaNamespace._validateListMetadata(trustList);
|
|
4740
|
-
if (signature) {
|
|
4741
|
-
const publicKey = await this._loadAuthorityPublicKey(
|
|
4742
|
-
opts?.authority_cert_pem,
|
|
4743
|
-
opts?.authority_public_key_pem,
|
|
4744
|
-
trustList
|
|
4745
|
-
);
|
|
4746
|
-
const signedPayload = _MetaNamespace._canonicalSignedPayload(trustList);
|
|
4747
|
-
const sigBytes = _MetaNamespace._decodeSignature(signature);
|
|
4748
|
-
const ok = await crypto.subtle.verify(
|
|
4749
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
4750
|
-
publicKey,
|
|
4751
|
-
toBufferSource(sigBytes),
|
|
4752
|
-
toBufferSource(signedPayload)
|
|
4753
|
-
);
|
|
4754
|
-
if (!ok) {
|
|
4755
|
-
throw new ValidationError("trust roots authority_signature verification failed");
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
const roots = _MetaNamespace._extractRootEntries(trustList);
|
|
4759
|
-
const imported = [];
|
|
4760
|
-
const skipped = [];
|
|
4761
|
-
for (const item of roots) {
|
|
4762
|
-
const status = String(item.status ?? "active").trim().toLowerCase();
|
|
4763
|
-
const certPem = String(item.certificate ?? item.cert_pem ?? "").trim();
|
|
4764
|
-
const rootId = String(item.id ?? item.agentid ?? "").trim();
|
|
4765
|
-
if (status !== "active") {
|
|
4766
|
-
skipped.push({ id: rootId, reason: `status=${status}` });
|
|
4767
|
-
continue;
|
|
4768
|
-
}
|
|
4769
|
-
_MetaNamespace._validateCertPem(certPem, rootId || "root");
|
|
4770
|
-
const fingerprint = await _MetaNamespace._certFingerprint(certPem);
|
|
4771
|
-
const expectedFp = _MetaNamespace._normalizeFingerprint(item.fingerprint_sha256);
|
|
4772
|
-
if (expectedFp.length !== 64) {
|
|
4773
|
-
throw new ValidationError(`root certificate missing or invalid fingerprint_sha256: ${rootId || fingerprint}`);
|
|
4774
|
-
}
|
|
4775
|
-
if (expectedFp !== fingerprint) {
|
|
4776
|
-
throw new ValidationError(`root certificate fingerprint mismatch: ${rootId || fingerprint}`);
|
|
4777
|
-
}
|
|
4778
|
-
imported.push({ id: rootId || fingerprint, cert_pem: certPem, fingerprint_sha256: fingerprint });
|
|
4779
|
-
}
|
|
4780
|
-
if (imported.length === 0) {
|
|
4781
|
-
throw new ValidationError("trust roots list contains no active root certificates");
|
|
4782
|
-
}
|
|
4783
|
-
return { imported, skipped, count: imported.length };
|
|
4784
|
-
}
|
|
4785
|
-
// ── 信任根导入 ────────────────────────────────────────────
|
|
4786
|
-
async importTrustRoots(trustList, opts) {
|
|
4787
|
-
const verified = await this.verifyTrustRoots(trustList, opts);
|
|
4788
|
-
await this._enforceMonotonicVersion(trustList);
|
|
4789
|
-
const ks = this._internal._keystore;
|
|
4790
|
-
let bundlePath = "";
|
|
4791
|
-
if (typeof ks.saveTrustRoots === "function") {
|
|
4792
|
-
bundlePath = await ks.saveTrustRoots(trustList, verified.imported);
|
|
4793
|
-
} else {
|
|
4794
|
-
this._log.warn("[MetaNamespace] keystore missing saveTrustRoots method, skip persist");
|
|
4795
|
-
}
|
|
4796
|
-
const auth = this._internal._auth;
|
|
4797
|
-
const reloaded = auth.reloadTrustedRoots?.() ?? auth.reload_trusted_roots?.() ?? 0;
|
|
4798
|
-
return {
|
|
4799
|
-
imported: verified.count,
|
|
4800
|
-
skipped: verified.skipped,
|
|
4801
|
-
bundle_path: bundlePath,
|
|
4802
|
-
reloaded_roots: reloaded,
|
|
4803
|
-
fingerprints: verified.imported.map((i) => i.fingerprint_sha256)
|
|
4804
|
-
};
|
|
4805
|
-
}
|
|
4806
|
-
async refreshTrustRoots(opts) {
|
|
4807
|
-
const sourceUrl = this._resolveTrustRootsUrl(opts?.url, opts?.issuer, opts?.gateway_url);
|
|
4808
|
-
const trustList = await this.downloadTrustRoots({ url: sourceUrl, timeout: opts?.timeout });
|
|
4809
|
-
const result = await this.importTrustRoots(trustList, {
|
|
4810
|
-
authority_cert_pem: opts?.authority_cert_pem,
|
|
4811
|
-
authority_public_key_pem: opts?.authority_public_key_pem,
|
|
4812
|
-
allow_unsigned: opts?.allow_unsigned
|
|
4813
|
-
});
|
|
4814
|
-
result.source_url = sourceUrl;
|
|
4815
|
-
return result;
|
|
4816
|
-
}
|
|
4817
|
-
async updateIssuerRootCert(issuer, opts) {
|
|
4818
|
-
const normalizedIssuer = _MetaNamespace._validateIssuer(issuer);
|
|
4819
|
-
const sourceUrl = opts?.url?.trim() || this._issuerRootCertUrl(normalizedIssuer);
|
|
4820
|
-
let rootPem = opts?.cert_pem?.trim() ? opts.cert_pem.trim() + "\n" : "";
|
|
4821
|
-
if (!rootPem) {
|
|
4822
|
-
rootPem = await this.downloadIssuerRootCert(normalizedIssuer, { url: sourceUrl, timeout: opts?.timeout });
|
|
4823
|
-
}
|
|
4824
|
-
_MetaNamespace._validateCertPem(rootPem, normalizedIssuer);
|
|
4825
|
-
const fingerprint = await _MetaNamespace._certFingerprint(rootPem);
|
|
4826
|
-
let effectiveTrustList = opts?.trust_list ?? await this._loadLocalTrustList();
|
|
4827
|
-
let trustSource = "local";
|
|
4828
|
-
if (!effectiveTrustList) {
|
|
4829
|
-
effectiveTrustList = await this.downloadTrustRoots({ issuer: normalizedIssuer, timeout: opts?.timeout });
|
|
4830
|
-
trustSource = this._issuerTrustRootUrl(normalizedIssuer);
|
|
4831
|
-
}
|
|
4832
|
-
const verified = await this.verifyTrustRoots(effectiveTrustList, {
|
|
4833
|
-
authority_cert_pem: opts?.authority_cert_pem,
|
|
4834
|
-
authority_public_key_pem: opts?.authority_public_key_pem,
|
|
4835
|
-
allow_unsigned: opts?.allow_unsigned
|
|
4836
|
-
});
|
|
4837
|
-
await this._enforceMonotonicVersion(effectiveTrustList);
|
|
4838
|
-
const trustedFingerprints = new Set(verified.imported.map((i) => i.fingerprint_sha256));
|
|
4839
|
-
if (!trustedFingerprints.has(fingerprint)) {
|
|
4840
|
-
throw new ValidationError("issuer root certificate is not in trusted root list");
|
|
4841
|
-
}
|
|
4842
|
-
const ks = this._internal._keystore;
|
|
4843
|
-
let certPath = "";
|
|
4844
|
-
let bundlePath = "";
|
|
4845
|
-
if (typeof ks.saveIssuerRootCert === "function") {
|
|
4846
|
-
[certPath, bundlePath] = await ks.saveIssuerRootCert(normalizedIssuer, rootPem, fingerprint);
|
|
4847
|
-
} else {
|
|
4848
|
-
this._log.warn("[MetaNamespace] keystore missing saveIssuerRootCert method, skip persist");
|
|
4849
|
-
}
|
|
4850
|
-
const auth = this._internal._auth;
|
|
4851
|
-
const reloaded = auth.reloadTrustedRoots?.() ?? auth.reload_trusted_roots?.() ?? 0;
|
|
4852
|
-
return {
|
|
4853
|
-
issuer: normalizedIssuer,
|
|
4854
|
-
fingerprint_sha256: fingerprint,
|
|
4855
|
-
cert_path: certPath,
|
|
4856
|
-
bundle_path: bundlePath,
|
|
4857
|
-
reloaded_roots: reloaded,
|
|
4858
|
-
source_url: sourceUrl,
|
|
4859
|
-
trust_source: trustSource
|
|
4860
|
-
};
|
|
4861
|
-
}
|
|
4862
|
-
// ── URL 解析 ──────────────────────────────────────────────
|
|
4863
|
-
_resolveTrustRootsUrl(url, issuer, gatewayUrl) {
|
|
4864
|
-
const target = (url ?? "").trim();
|
|
4865
|
-
if (target) return target;
|
|
4866
|
-
if (issuer) return this._issuerTrustRootUrl(issuer);
|
|
4867
|
-
const gw = (gatewayUrl ?? this._internal._gatewayUrl ?? "").trim();
|
|
4868
|
-
return gw ? this._gatewayTrustRootsUrl(gw) : AUTHORITY_ENDPOINT;
|
|
4869
|
-
}
|
|
4870
|
-
_issuerTrustRootUrl(issuer) {
|
|
4871
|
-
const authority = this._pkiAuthority(issuer);
|
|
4872
|
-
return `https://${authority}/trust-root.json`;
|
|
4873
|
-
}
|
|
4874
|
-
_issuerRootCertUrl(issuer) {
|
|
4875
|
-
const authority = this._pkiAuthority(issuer);
|
|
4876
|
-
return `https://${authority}/root.crt`;
|
|
4877
|
-
}
|
|
4878
|
-
_pkiAuthority(issuer) {
|
|
4879
|
-
const normalized = _MetaNamespace._validateIssuer(issuer);
|
|
4880
|
-
const port = this._internal.configModel.discoveryPort;
|
|
4881
|
-
const portSuffix = port && !normalized.includes(":") ? `:${port}` : "";
|
|
4882
|
-
return `pki.${normalized}${portSuffix}`;
|
|
4883
|
-
}
|
|
4884
|
-
_gatewayTrustRootsUrl(gatewayUrl) {
|
|
4885
|
-
let parsed;
|
|
4886
|
-
try {
|
|
4887
|
-
parsed = new URL(gatewayUrl);
|
|
4888
|
-
} catch {
|
|
4889
|
-
throw new ValidationError("gateway_url must include scheme and host");
|
|
4890
|
-
}
|
|
4891
|
-
if (!parsed.host) {
|
|
4892
|
-
throw new ValidationError("gateway_url must include scheme and host");
|
|
4893
|
-
}
|
|
4894
|
-
const scheme = ["wss:", "https:"].includes(parsed.protocol) ? "https" : "http";
|
|
4895
|
-
return `${scheme}://${parsed.host}/pki/trust-roots.json`;
|
|
4896
|
-
}
|
|
4897
|
-
// ── 内部辅助 ──────────────────────────────────────────────
|
|
4898
|
-
/** 校验 issuer 域名格式 */
|
|
4899
|
-
static _validateIssuer(issuer) {
|
|
4900
|
-
const value = (issuer ?? "").trim().toLowerCase();
|
|
4901
|
-
if (!value || value.includes("://") || value.includes("/") || value.includes("\\") || value.startsWith(".")) {
|
|
4902
|
-
throw new ValidationError("issuer must be a domain name");
|
|
4903
|
-
}
|
|
4904
|
-
return value;
|
|
4905
|
-
}
|
|
4906
|
-
/** 校验信任列表元数据(版本、时间戳) */
|
|
4907
|
-
static _validateListMetadata(trustList) {
|
|
4908
|
-
const version = trustList.version;
|
|
4909
|
-
if (typeof version !== "number" || !Number.isInteger(version) || version < 0) {
|
|
4910
|
-
throw new ValidationError("trust roots list version must be a non-negative integer");
|
|
4911
|
-
}
|
|
4912
|
-
const issuedAt = _MetaNamespace._parseTimestamp(trustList.issued_at, "issued_at");
|
|
4913
|
-
const nextUpdate = _MetaNamespace._parseTimestamp(trustList.next_update, "next_update");
|
|
4914
|
-
if (nextUpdate < issuedAt) {
|
|
4915
|
-
throw new ValidationError("trust roots list next_update must not be earlier than issued_at");
|
|
4916
|
-
}
|
|
4917
|
-
if (issuedAt > Date.now() + MAX_CLOCK_SKEW * 1e3) {
|
|
4918
|
-
throw new ValidationError("trust roots list issued_at is too far in the future");
|
|
4919
|
-
}
|
|
4920
|
-
}
|
|
4921
|
-
static _parseTimestamp(value, field) {
|
|
4922
|
-
const text = String(value ?? "").trim();
|
|
4923
|
-
if (!text) throw new ValidationError(`trust roots list missing ${field}`);
|
|
4924
|
-
const ts = new Date(text).getTime();
|
|
4925
|
-
if (isNaN(ts)) throw new ValidationError(`trust roots list ${field} must be ISO-8601`);
|
|
4926
|
-
return ts;
|
|
4927
|
-
}
|
|
4928
|
-
/** 构建用于签名验证的规范化 JSON 载荷 */
|
|
4929
|
-
static _canonicalSignedPayload(trustList) {
|
|
4930
|
-
const payload = { ...trustList };
|
|
4931
|
-
delete payload.authority_signature;
|
|
4932
|
-
const canonical = _MetaNamespace._stableStringify(payload);
|
|
4933
|
-
return new TextEncoder().encode(canonical);
|
|
4934
|
-
}
|
|
4935
|
-
/** 递归稳定排序 JSON 序列化 */
|
|
4936
|
-
static _stableStringify(obj) {
|
|
4937
|
-
if (obj === null || obj === void 0) return JSON.stringify(obj);
|
|
4938
|
-
if (typeof obj !== "object") return JSON.stringify(obj);
|
|
4939
|
-
if (Array.isArray(obj)) {
|
|
4940
|
-
return "[" + obj.map((v) => _MetaNamespace._stableStringify(v)).join(",") + "]";
|
|
4941
|
-
}
|
|
4942
|
-
const keys = Object.keys(obj).sort();
|
|
4943
|
-
const pairs = keys.map((k) => JSON.stringify(k) + ":" + _MetaNamespace._stableStringify(obj[k]));
|
|
4944
|
-
return "{" + pairs.join(",") + "}";
|
|
4945
|
-
}
|
|
4946
|
-
/** 解码 base64 编码的签名 */
|
|
4947
|
-
static _decodeSignature(signature) {
|
|
4948
|
-
let value = signature.trim();
|
|
4949
|
-
if (value.startsWith("base64:")) {
|
|
4950
|
-
value = value.slice(7);
|
|
4951
|
-
}
|
|
4952
|
-
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
4953
|
-
const padding = "=".repeat((4 - normalized.length % 4) % 4);
|
|
4954
|
-
return base64ToUint8(normalized + padding);
|
|
4955
|
-
}
|
|
4956
|
-
/** 提取信任列表中的根 CA 条目 */
|
|
4957
|
-
static _extractRootEntries(trustList) {
|
|
4958
|
-
const roots = trustList.root_cas;
|
|
4959
|
-
if (Array.isArray(roots)) {
|
|
4960
|
-
return roots.filter((item) => item && typeof item === "object");
|
|
4961
|
-
}
|
|
4962
|
-
const legacy = trustList.roots;
|
|
4963
|
-
if (Array.isArray(legacy)) {
|
|
4964
|
-
return legacy.filter((item) => item && typeof item === "object").map((item) => ({
|
|
4965
|
-
id: item.agentid ?? item.id ?? item.cert_sn,
|
|
4966
|
-
certificate: item.cert_pem ?? item.certificate,
|
|
4967
|
-
fingerprint_sha256: item.fingerprint_sha256,
|
|
4968
|
-
status: item.status ?? "active"
|
|
4969
|
-
}));
|
|
4970
|
-
}
|
|
4971
|
-
throw new ValidationError("trust roots list missing root_cas");
|
|
4972
|
-
}
|
|
4973
|
-
/** 规范化指纹字符串(去除前缀和分隔符) */
|
|
4974
|
-
static _normalizeFingerprint(value) {
|
|
4975
|
-
let text = String(value ?? "").trim().toLowerCase();
|
|
4976
|
-
if (text.startsWith("sha256:")) text = text.slice(7);
|
|
4977
|
-
return text.replace(/[^0-9a-f]/g, "");
|
|
4978
|
-
}
|
|
4979
|
-
/** 计算证书 PEM 的 SHA-256 指纹(纯 hex,不含 sha256: 前缀) */
|
|
4980
|
-
static async _certFingerprint(certPem) {
|
|
4981
|
-
const der = pemToArrayBuffer(certPem);
|
|
4982
|
-
const hash = await crypto.subtle.digest("SHA-256", der);
|
|
4983
|
-
return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
4984
|
-
}
|
|
4985
|
-
/** 校验 PEM 格式是否包含 BEGIN CERTIFICATE */
|
|
4986
|
-
static _validateCertPem(certPem, rootId) {
|
|
4987
|
-
if (!certPem.includes("BEGIN CERTIFICATE")) {
|
|
4988
|
-
throw new ValidationError(`root certificate missing PEM data: ${rootId}`);
|
|
4989
|
-
}
|
|
4990
|
-
}
|
|
4991
|
-
/**
|
|
4992
|
-
* 加载权威公钥用于签名验证。
|
|
4993
|
-
* 浏览器环境:从 PEM 提取 SPKI 并导入为 CryptoKey。
|
|
4994
|
-
* 支持 P-256 和 P-384 曲线。
|
|
4995
|
-
*/
|
|
4996
|
-
async _loadAuthorityPublicKey(authorityCertPem, authorityPublicKeyPem, trustList) {
|
|
4997
|
-
if (authorityPublicKeyPem) {
|
|
4998
|
-
return this._importPublicKeyPem(authorityPublicKeyPem);
|
|
4999
|
-
}
|
|
5000
|
-
const candidateCert = authorityCertPem || String(trustList?.authority_cert_pem ?? "");
|
|
5001
|
-
if (!candidateCert) {
|
|
5002
|
-
throw new ValidationError("authority certificate/public key is required to verify trust roots");
|
|
5003
|
-
}
|
|
5004
|
-
return this._importCertPublicKey(candidateCert);
|
|
5005
|
-
}
|
|
5006
|
-
/** 从 SPKI PEM 导入公钥(尝试 P-384,降级 P-256) */
|
|
5007
|
-
async _importPublicKeyPem(pem) {
|
|
5008
|
-
const der = pemToArrayBuffer(pem);
|
|
5009
|
-
for (const curve of ["P-384", "P-256"]) {
|
|
5010
|
-
try {
|
|
5011
|
-
return await crypto.subtle.importKey(
|
|
5012
|
-
"spki",
|
|
5013
|
-
der,
|
|
5014
|
-
{ name: "ECDSA", namedCurve: curve },
|
|
5015
|
-
true,
|
|
5016
|
-
["verify"]
|
|
5017
|
-
);
|
|
5018
|
-
} catch {
|
|
5019
|
-
}
|
|
5020
|
-
}
|
|
5021
|
-
throw new ValidationError("invalid authority public key PEM");
|
|
5022
|
-
}
|
|
5023
|
-
/**
|
|
5024
|
-
* 从证书 PEM 提取公钥并导入。
|
|
5025
|
-
* 使用简化 ASN.1 解析提取 SPKI 段。
|
|
5026
|
-
*/
|
|
5027
|
-
async _importCertPublicKey(certPem) {
|
|
5028
|
-
if (!certPem.includes("BEGIN CERTIFICATE")) {
|
|
5029
|
-
throw new ValidationError("invalid authority certificate PEM");
|
|
5030
|
-
}
|
|
5031
|
-
const certDer = new Uint8Array(pemToArrayBuffer(certPem));
|
|
5032
|
-
const spki = this._extractSpkiFromDer(certDer);
|
|
5033
|
-
if (!spki) {
|
|
5034
|
-
throw new ValidationError("invalid authority certificate PEM");
|
|
5035
|
-
}
|
|
5036
|
-
for (const curve of ["P-384", "P-256"]) {
|
|
5037
|
-
try {
|
|
5038
|
-
return await crypto.subtle.importKey(
|
|
5039
|
-
"spki",
|
|
5040
|
-
spki,
|
|
5041
|
-
{ name: "ECDSA", namedCurve: curve },
|
|
5042
|
-
true,
|
|
5043
|
-
["verify"]
|
|
5044
|
-
);
|
|
5045
|
-
} catch {
|
|
5046
|
-
}
|
|
5047
|
-
}
|
|
5048
|
-
throw new ValidationError("invalid authority certificate PEM");
|
|
5049
|
-
}
|
|
5050
|
-
/**
|
|
5051
|
-
* 从 X.509 DER 证书中提取 SubjectPublicKeyInfo。
|
|
5052
|
-
* 搜索 EC 公钥 OID (1.2.840.10045.2.1)。
|
|
5053
|
-
*/
|
|
5054
|
-
_extractSpkiFromDer(certDer) {
|
|
5055
|
-
const ecOid = [6, 7, 42, 134, 72, 206, 61, 2, 1];
|
|
5056
|
-
for (let i = 0; i < certDer.length - ecOid.length; i++) {
|
|
5057
|
-
let match = true;
|
|
5058
|
-
for (let j = 0; j < ecOid.length; j++) {
|
|
5059
|
-
if (certDer[i + j] !== ecOid[j]) {
|
|
5060
|
-
match = false;
|
|
5061
|
-
break;
|
|
5062
|
-
}
|
|
5063
|
-
}
|
|
5064
|
-
if (!match) continue;
|
|
5065
|
-
for (let back = 1; back <= 4; back++) {
|
|
5066
|
-
const seqStart = i - back;
|
|
5067
|
-
if (seqStart < 0) continue;
|
|
5068
|
-
if (certDer[seqStart] !== 48) continue;
|
|
5069
|
-
const seqLen = this._parseDerLength(certDer, seqStart + 1);
|
|
5070
|
-
if (!seqLen) continue;
|
|
5071
|
-
const totalLen = 1 + seqLen.lenBytes + seqLen.value;
|
|
5072
|
-
if (totalLen < 50 || totalLen > 200) continue;
|
|
5073
|
-
return certDer.slice(seqStart, seqStart + totalLen).buffer;
|
|
5074
|
-
}
|
|
5075
|
-
}
|
|
5076
|
-
return null;
|
|
5077
|
-
}
|
|
5078
|
-
/** 解析 DER 长度字段 */
|
|
5079
|
-
_parseDerLength(data, offset) {
|
|
5080
|
-
if (offset >= data.length) return null;
|
|
5081
|
-
const first = data[offset];
|
|
5082
|
-
if (first < 128) return { value: first, lenBytes: 1 };
|
|
5083
|
-
const numBytes = first & 127;
|
|
5084
|
-
if (numBytes === 0 || numBytes > 4 || offset + 1 + numBytes > data.length) return null;
|
|
5085
|
-
let value = 0;
|
|
5086
|
-
for (let i = 0; i < numBytes; i++) {
|
|
5087
|
-
value = value << 8 | data[offset + 1 + i];
|
|
3426
|
+
const result = {};
|
|
3427
|
+
for (const [ns, t] of this._trackers) {
|
|
3428
|
+
if (t.contiguousSeq > 0) result[ns] = t.contiguousSeq;
|
|
5088
3429
|
}
|
|
5089
|
-
return
|
|
3430
|
+
return result;
|
|
5090
3431
|
}
|
|
5091
|
-
/**
|
|
5092
|
-
|
|
5093
|
-
const
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
const current = await ks.loadTrustRoots();
|
|
5099
|
-
const currentVersion = current?.version;
|
|
5100
|
-
if (typeof currentVersion === "number" && version < currentVersion) {
|
|
5101
|
-
throw new ValidationError("trust roots list version rollback is not allowed");
|
|
3432
|
+
/** 从持久化数据恢复各命名空间的 contiguousSeq */
|
|
3433
|
+
restoreState(state) {
|
|
3434
|
+
for (const [ns, seq] of Object.entries(state)) {
|
|
3435
|
+
if (typeof seq === "number" && seq > 0) {
|
|
3436
|
+
const t = this._get(ns);
|
|
3437
|
+
t.contiguousSeq = Math.max(t.contiguousSeq, seq);
|
|
3438
|
+
t.maxSeenSeq = Math.max(t.maxSeenSeq, seq);
|
|
5102
3439
|
}
|
|
5103
|
-
} catch (e) {
|
|
5104
|
-
if (e instanceof ValidationError) throw e;
|
|
5105
|
-
}
|
|
5106
|
-
}
|
|
5107
|
-
/** 从 keystore 加载本地已存储的信任列表 */
|
|
5108
|
-
async _loadLocalTrustList() {
|
|
5109
|
-
const ks = this._internal._keystore;
|
|
5110
|
-
if (typeof ks.loadTrustRoots !== "function") return null;
|
|
5111
|
-
try {
|
|
5112
|
-
const payload = await ks.loadTrustRoots();
|
|
5113
|
-
if (!payload || typeof payload !== "object") return null;
|
|
5114
|
-
return payload;
|
|
5115
|
-
} catch {
|
|
5116
|
-
return null;
|
|
5117
3440
|
}
|
|
5118
3441
|
}
|
|
5119
3442
|
};
|
|
@@ -5123,7 +3446,7 @@ init_crypto();
|
|
|
5123
3446
|
|
|
5124
3447
|
// src/keystore/indexeddb.ts
|
|
5125
3448
|
init_crypto();
|
|
5126
|
-
var
|
|
3449
|
+
var _noopLog5 = { error: () => {
|
|
5127
3450
|
}, warn: () => {
|
|
5128
3451
|
}, info: () => {
|
|
5129
3452
|
}, debug: () => {
|
|
@@ -5544,7 +3867,7 @@ async function idbDelete(storeName, key) {
|
|
|
5544
3867
|
}
|
|
5545
3868
|
var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
5546
3869
|
constructor(opts) {
|
|
5547
|
-
__publicField(this, "_log",
|
|
3870
|
+
__publicField(this, "_log", _noopLog5);
|
|
5548
3871
|
/** 私钥加密种子;为空时降级为明文存储(向后兼容) */
|
|
5549
3872
|
__publicField(this, "_encryptionSeed");
|
|
5550
3873
|
this._encryptionSeed = opts?.encryptionSeed;
|
|
@@ -10361,7 +8684,6 @@ function isPlainObject(value) {
|
|
|
10361
8684
|
var encoder3 = new TextEncoder();
|
|
10362
8685
|
var decoder = new TextDecoder();
|
|
10363
8686
|
var E2EE_SDK_LANG = "javascript";
|
|
10364
|
-
var E2EE_SDK_VERSION = "0.3.6";
|
|
10365
8687
|
async function sha2563(data) {
|
|
10366
8688
|
const buf = await crypto.subtle.digest("SHA-256", data.slice().buffer);
|
|
10367
8689
|
return new Uint8Array(buf);
|
|
@@ -10532,7 +8854,7 @@ function normalizeProtectedHeaders(headers, payload) {
|
|
|
10532
8854
|
}
|
|
10533
8855
|
normalized.sdk_lang = E2EE_SDK_LANG;
|
|
10534
8856
|
delete normalized.sdk_vesion;
|
|
10535
|
-
normalized.sdk_version =
|
|
8857
|
+
normalized.sdk_version = VERSION;
|
|
10536
8858
|
return normalized;
|
|
10537
8859
|
}
|
|
10538
8860
|
function normalizeProtectedHeaderKey(key) {
|
|
@@ -11131,6 +9453,324 @@ var AUNLogger = class {
|
|
|
11131
9453
|
}
|
|
11132
9454
|
};
|
|
11133
9455
|
|
|
9456
|
+
// src/error-codes.ts
|
|
9457
|
+
var CERT_NOT_FOUND = "CERT_NOT_FOUND";
|
|
9458
|
+
var CERT_PARSE_ERROR = "CERT_PARSE_ERROR";
|
|
9459
|
+
var CERT_EXPIRED = "CERT_EXPIRED";
|
|
9460
|
+
var CERT_NOT_YET_VALID = "CERT_NOT_YET_VALID";
|
|
9461
|
+
var CERT_CHAIN_BROKEN = "CERT_CHAIN_BROKEN";
|
|
9462
|
+
var KEYPAIR_MISMATCH = "KEYPAIR_MISMATCH";
|
|
9463
|
+
var PRIVATE_KEY_PARSE_ERROR = "PRIVATE_KEY_PARSE_ERROR";
|
|
9464
|
+
var IDENTITY_CONFLICT = "IDENTITY_CONFLICT";
|
|
9465
|
+
var INVALID_AID_FORMAT = "INVALID_AID_FORMAT";
|
|
9466
|
+
var NETWORK_ERROR = "NETWORK_ERROR";
|
|
9467
|
+
var SERVER_ERROR = "SERVER_ERROR";
|
|
9468
|
+
var AGENTMD_NOT_FOUND = "AGENTMD_NOT_FOUND";
|
|
9469
|
+
var CERT_RENEWAL_FAILED = "CERT_RENEWAL_FAILED";
|
|
9470
|
+
var REKEY_FAILED = "REKEY_FAILED";
|
|
9471
|
+
var PRIVATE_KEY_REQUIRED = "PRIVATE_KEY_REQUIRED";
|
|
9472
|
+
var SIGNATURE_OPERATION_ERROR = "SIGNATURE_OPERATION_ERROR";
|
|
9473
|
+
var VERIFICATION_OPERATION_ERROR = "VERIFICATION_OPERATION_ERROR";
|
|
9474
|
+
var CERT_NOT_VALID = "CERT_NOT_VALID";
|
|
9475
|
+
var PRIVATE_KEY_NOT_VALID = "PRIVATE_KEY_NOT_VALID";
|
|
9476
|
+
|
|
9477
|
+
// src/cert-utils.ts
|
|
9478
|
+
init_crypto();
|
|
9479
|
+
var AGENT_MD_SIGNATURE_MARKER = "<!-- AUN-SIGNATURE";
|
|
9480
|
+
var AGENT_MD_SIGNATURE_RE = /^<!-- AUN-SIGNATURE\r?\n(?<body>[\s\S]*?)\r?\n-->\s*$/;
|
|
9481
|
+
var AGENT_MD_FINGERPRINT_RE = /^sha256:[0-9a-f]{64}$/;
|
|
9482
|
+
function parseAgentMdTailSignature(content) {
|
|
9483
|
+
const idx = content.lastIndexOf(AGENT_MD_SIGNATURE_MARKER);
|
|
9484
|
+
if (idx < 0) return { payload: content, fields: null };
|
|
9485
|
+
if (idx > 0 && content[idx - 1] !== "\n" && content[idx - 1] !== "\r") return { payload: content, fields: null };
|
|
9486
|
+
const tail = content.slice(idx);
|
|
9487
|
+
const match = tail.match(AGENT_MD_SIGNATURE_RE);
|
|
9488
|
+
if (!match) return { payload: content.slice(0, idx), fields: null, parseError: "malformed signature block" };
|
|
9489
|
+
const fields = {};
|
|
9490
|
+
for (const rawLine of match.groups?.body?.split(/\r?\n/) ?? []) {
|
|
9491
|
+
const line = rawLine.trim();
|
|
9492
|
+
if (!line) continue;
|
|
9493
|
+
const colon = line.indexOf(":");
|
|
9494
|
+
if (colon < 0) return { payload: content.slice(0, idx), fields: null, parseError: `malformed signature field: ${line}` };
|
|
9495
|
+
fields[line.slice(0, colon).trim().toLowerCase()] = line.slice(colon + 1).trim();
|
|
9496
|
+
}
|
|
9497
|
+
for (const req of ["cert_fingerprint", "timestamp", "signature"]) {
|
|
9498
|
+
if (!fields[req]) return { payload: content.slice(0, idx), fields: null, parseError: `signature block missing ${req}` };
|
|
9499
|
+
}
|
|
9500
|
+
if (!AGENT_MD_FINGERPRINT_RE.test(fields.cert_fingerprint.toLowerCase())) {
|
|
9501
|
+
return { payload: content.slice(0, idx), fields: null, parseError: "invalid cert_fingerprint" };
|
|
9502
|
+
}
|
|
9503
|
+
if (!Number.isFinite(Number(fields.timestamp))) {
|
|
9504
|
+
return { payload: content.slice(0, idx), fields: null, parseError: "invalid timestamp" };
|
|
9505
|
+
}
|
|
9506
|
+
return { payload: content.slice(0, idx), fields };
|
|
9507
|
+
}
|
|
9508
|
+
function normalizeAgentMdPayload(content) {
|
|
9509
|
+
let payload = parseAgentMdTailSignature(String(content ?? "")).payload;
|
|
9510
|
+
if (payload && !payload.endsWith("\n") && !payload.endsWith("\r")) payload += "\n";
|
|
9511
|
+
return payload;
|
|
9512
|
+
}
|
|
9513
|
+
function buildAgentMdSignatureBlock(certFingerprint, timestamp, signatureB64) {
|
|
9514
|
+
return [
|
|
9515
|
+
AGENT_MD_SIGNATURE_MARKER,
|
|
9516
|
+
`cert_fingerprint: ${certFingerprint}`,
|
|
9517
|
+
`timestamp: ${Math.trunc(timestamp)}`,
|
|
9518
|
+
`signature: ${signatureB64}`,
|
|
9519
|
+
"-->"
|
|
9520
|
+
].join("\n");
|
|
9521
|
+
}
|
|
9522
|
+
function extractAgentMdAid(payload) {
|
|
9523
|
+
const lines = payload.replace(/^\ufeff/, "").split(/\r?\n/);
|
|
9524
|
+
if (!lines.length || lines[0].trim() !== "---") return "";
|
|
9525
|
+
for (const line of lines.slice(1)) {
|
|
9526
|
+
const t = line.trim();
|
|
9527
|
+
if (t === "---") break;
|
|
9528
|
+
if (t.startsWith("aid:")) {
|
|
9529
|
+
let v = t.slice(4).trim();
|
|
9530
|
+
if (v.length >= 2 && v[0] === v[v.length - 1] && (v[0] === '"' || v[0] === "'")) v = v.slice(1, -1);
|
|
9531
|
+
return v.trim();
|
|
9532
|
+
}
|
|
9533
|
+
}
|
|
9534
|
+
return "";
|
|
9535
|
+
}
|
|
9536
|
+
function readDerLength(data, offset) {
|
|
9537
|
+
if (offset >= data.length) return null;
|
|
9538
|
+
const first = data[offset];
|
|
9539
|
+
if (first < 128) return { value: first, lenBytes: 1 };
|
|
9540
|
+
const numBytes = first & 127;
|
|
9541
|
+
if (numBytes === 0 || numBytes > 4) return null;
|
|
9542
|
+
let value = 0;
|
|
9543
|
+
for (let i = 0; i < numBytes; i++) value = value << 8 | data[offset + 1 + i];
|
|
9544
|
+
return { value, lenBytes: 1 + numBytes };
|
|
9545
|
+
}
|
|
9546
|
+
function readDerTlv(data, offset) {
|
|
9547
|
+
const len = readDerLength(data, offset + 1);
|
|
9548
|
+
if (!len) return null;
|
|
9549
|
+
const valueStart = offset + 1 + len.lenBytes;
|
|
9550
|
+
const valueEnd = valueStart + len.value;
|
|
9551
|
+
if (valueEnd > data.length) return null;
|
|
9552
|
+
return { tag: data[offset], valueStart, valueEnd, fullEnd: valueEnd };
|
|
9553
|
+
}
|
|
9554
|
+
function publicKeyDerB64(certPem) {
|
|
9555
|
+
const certDer = new Uint8Array(pemToArrayBuffer(certPem));
|
|
9556
|
+
const cert = readDerTlv(certDer, 0);
|
|
9557
|
+
if (!cert || cert.tag !== 48) return "";
|
|
9558
|
+
const tbs = readDerTlv(certDer, cert.valueStart);
|
|
9559
|
+
if (!tbs || tbs.tag !== 48) return "";
|
|
9560
|
+
let pos = tbs.valueStart;
|
|
9561
|
+
if (certDer[pos] === 160) pos = readDerTlv(certDer, pos)?.fullEnd ?? pos;
|
|
9562
|
+
for (let i = 0; i < 5; i++) pos = readDerTlv(certDer, pos)?.fullEnd ?? pos;
|
|
9563
|
+
const spki = readDerTlv(certDer, pos);
|
|
9564
|
+
if (!spki || spki.tag !== 48) return "";
|
|
9565
|
+
return uint8ToBase64(certDer.slice(pos, spki.fullEnd));
|
|
9566
|
+
}
|
|
9567
|
+
function extractCommonName(der, nameStart, nameEnd) {
|
|
9568
|
+
let pos = nameStart;
|
|
9569
|
+
while (pos < nameEnd) {
|
|
9570
|
+
const set = readDerTlv(der, pos);
|
|
9571
|
+
if (!set || set.tag !== 49) break;
|
|
9572
|
+
let rpos = set.valueStart;
|
|
9573
|
+
while (rpos < set.valueEnd) {
|
|
9574
|
+
const seq = readDerTlv(der, rpos);
|
|
9575
|
+
if (!seq || seq.tag !== 48) break;
|
|
9576
|
+
const oid = readDerTlv(der, seq.valueStart);
|
|
9577
|
+
if (oid && oid.tag === 6) {
|
|
9578
|
+
const isCN = oid.valueEnd - oid.valueStart === 3 && der[oid.valueStart] === 85 && der[oid.valueStart + 1] === 4 && der[oid.valueStart + 2] === 3;
|
|
9579
|
+
if (isCN) {
|
|
9580
|
+
const val = readDerTlv(der, oid.fullEnd);
|
|
9581
|
+
if (val) return new TextDecoder().decode(der.slice(val.valueStart, val.valueEnd));
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
rpos = seq.fullEnd;
|
|
9585
|
+
}
|
|
9586
|
+
pos = set.fullEnd;
|
|
9587
|
+
}
|
|
9588
|
+
return "";
|
|
9589
|
+
}
|
|
9590
|
+
function parseDerTime(der, offset) {
|
|
9591
|
+
const tlv = readDerTlv(der, offset);
|
|
9592
|
+
if (!tlv) return null;
|
|
9593
|
+
const raw = new TextDecoder().decode(der.slice(tlv.valueStart, tlv.valueEnd));
|
|
9594
|
+
if (tlv.tag === 23) {
|
|
9595
|
+
const yy = parseInt(raw.slice(0, 2), 10);
|
|
9596
|
+
const year = yy >= 50 ? 1900 + yy : 2e3 + yy;
|
|
9597
|
+
const ms = Date.UTC(
|
|
9598
|
+
year,
|
|
9599
|
+
parseInt(raw.slice(2, 4), 10) - 1,
|
|
9600
|
+
parseInt(raw.slice(4, 6), 10),
|
|
9601
|
+
parseInt(raw.slice(6, 8), 10),
|
|
9602
|
+
parseInt(raw.slice(8, 10), 10),
|
|
9603
|
+
parseInt(raw.slice(10, 12), 10)
|
|
9604
|
+
);
|
|
9605
|
+
return Number.isNaN(ms) ? null : new Date(ms);
|
|
9606
|
+
}
|
|
9607
|
+
if (tlv.tag === 24) {
|
|
9608
|
+
const ms = Date.UTC(
|
|
9609
|
+
parseInt(raw.slice(0, 4), 10),
|
|
9610
|
+
parseInt(raw.slice(4, 6), 10) - 1,
|
|
9611
|
+
parseInt(raw.slice(6, 8), 10),
|
|
9612
|
+
parseInt(raw.slice(8, 10), 10),
|
|
9613
|
+
parseInt(raw.slice(10, 12), 10),
|
|
9614
|
+
parseInt(raw.slice(12, 14), 10)
|
|
9615
|
+
);
|
|
9616
|
+
return Number.isNaN(ms) ? null : new Date(ms);
|
|
9617
|
+
}
|
|
9618
|
+
return null;
|
|
9619
|
+
}
|
|
9620
|
+
function parseCertMetadata(certPem) {
|
|
9621
|
+
const fallback = { subject: "", issuer: "", notBefore: /* @__PURE__ */ new Date(0), notAfter: /* @__PURE__ */ new Date(0) };
|
|
9622
|
+
try {
|
|
9623
|
+
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
9624
|
+
const cert = readDerTlv(der, 0);
|
|
9625
|
+
if (!cert || cert.tag !== 48) return fallback;
|
|
9626
|
+
const tbs = readDerTlv(der, cert.valueStart);
|
|
9627
|
+
if (!tbs || tbs.tag !== 48) return fallback;
|
|
9628
|
+
let pos = tbs.valueStart;
|
|
9629
|
+
if (der[pos] === 160) pos = readDerTlv(der, pos)?.fullEnd ?? pos;
|
|
9630
|
+
pos = readDerTlv(der, pos)?.fullEnd ?? pos;
|
|
9631
|
+
pos = readDerTlv(der, pos)?.fullEnd ?? pos;
|
|
9632
|
+
const issuer = readDerTlv(der, pos);
|
|
9633
|
+
if (!issuer || issuer.tag !== 48) return fallback;
|
|
9634
|
+
const issuerCN = extractCommonName(der, issuer.valueStart, issuer.valueEnd);
|
|
9635
|
+
pos = issuer.fullEnd;
|
|
9636
|
+
const validity = readDerTlv(der, pos);
|
|
9637
|
+
if (!validity || validity.tag !== 48) return fallback;
|
|
9638
|
+
const notBefore = parseDerTime(der, validity.valueStart);
|
|
9639
|
+
const nb = readDerTlv(der, validity.valueStart);
|
|
9640
|
+
const notAfter = nb ? parseDerTime(der, nb.fullEnd) : null;
|
|
9641
|
+
pos = validity.fullEnd;
|
|
9642
|
+
const subject = readDerTlv(der, pos);
|
|
9643
|
+
if (!subject || subject.tag !== 48) return fallback;
|
|
9644
|
+
const subjectCN = extractCommonName(der, subject.valueStart, subject.valueEnd);
|
|
9645
|
+
return {
|
|
9646
|
+
subject: subjectCN,
|
|
9647
|
+
issuer: issuerCN,
|
|
9648
|
+
notBefore: notBefore ?? /* @__PURE__ */ new Date(0),
|
|
9649
|
+
notAfter: notAfter ?? /* @__PURE__ */ new Date(0)
|
|
9650
|
+
};
|
|
9651
|
+
} catch {
|
|
9652
|
+
return fallback;
|
|
9653
|
+
}
|
|
9654
|
+
}
|
|
9655
|
+
async function signBytes(privateKeyPem, payload) {
|
|
9656
|
+
const key = await importPrivateKeyEcdsa(privateKeyPem);
|
|
9657
|
+
return uint8ToBase64(await ecdsaSignDer(key, payload));
|
|
9658
|
+
}
|
|
9659
|
+
async function verifySignatureWithCert(certPem, signatureB64, data) {
|
|
9660
|
+
try {
|
|
9661
|
+
const key = await importCertPublicKeyEcdsa(certPem);
|
|
9662
|
+
return await ecdsaVerifyDer(key, base64ToUint8(signatureB64), data);
|
|
9663
|
+
} catch {
|
|
9664
|
+
return false;
|
|
9665
|
+
}
|
|
9666
|
+
}
|
|
9667
|
+
|
|
9668
|
+
// src/result.ts
|
|
9669
|
+
function resultOk(data) {
|
|
9670
|
+
return { ok: true, data };
|
|
9671
|
+
}
|
|
9672
|
+
function resultErr(code, message, cause) {
|
|
9673
|
+
return { ok: false, error: { code, message, ...cause !== void 0 ? { cause } : {} } };
|
|
9674
|
+
}
|
|
9675
|
+
|
|
9676
|
+
// src/aid.ts
|
|
9677
|
+
var AID = class _AID {
|
|
9678
|
+
constructor(params) {
|
|
9679
|
+
__publicField(this, "aid");
|
|
9680
|
+
__publicField(this, "aunPath");
|
|
9681
|
+
__publicField(this, "certPem");
|
|
9682
|
+
__publicField(this, "publicKey");
|
|
9683
|
+
__publicField(this, "certSubject");
|
|
9684
|
+
__publicField(this, "certNotBefore");
|
|
9685
|
+
__publicField(this, "certNotAfter");
|
|
9686
|
+
__publicField(this, "certIssuer");
|
|
9687
|
+
__publicField(this, "deviceId");
|
|
9688
|
+
__publicField(this, "slotId");
|
|
9689
|
+
__publicField(this, "verifySsl");
|
|
9690
|
+
__publicField(this, "rootCaPath");
|
|
9691
|
+
__publicField(this, "debug");
|
|
9692
|
+
__publicField(this, "_privateKeyPem");
|
|
9693
|
+
__publicField(this, "_certValid");
|
|
9694
|
+
__publicField(this, "_privateKeyValid");
|
|
9695
|
+
__publicField(this, "_certFingerprint", "");
|
|
9696
|
+
this.aid = params.aid;
|
|
9697
|
+
this.aunPath = params.aunPath;
|
|
9698
|
+
this.deviceId = params.deviceId ?? "";
|
|
9699
|
+
this.slotId = params.slotId ?? "default";
|
|
9700
|
+
this.verifySsl = params.verifySsl ?? true;
|
|
9701
|
+
this.rootCaPath = params.rootCaPath ?? null;
|
|
9702
|
+
this.debug = params.debug ?? false;
|
|
9703
|
+
this.certPem = params.certPem;
|
|
9704
|
+
this.publicKey = publicKeyDerB64(params.certPem);
|
|
9705
|
+
const meta = parseCertMetadata(params.certPem);
|
|
9706
|
+
this.certSubject = meta.subject;
|
|
9707
|
+
this.certIssuer = meta.issuer;
|
|
9708
|
+
this.certNotBefore = meta.notBefore;
|
|
9709
|
+
this.certNotAfter = meta.notAfter;
|
|
9710
|
+
this._privateKeyPem = params.privateKeyPem;
|
|
9711
|
+
this._certValid = params.certValid;
|
|
9712
|
+
this._privateKeyValid = params.privateKeyValid;
|
|
9713
|
+
}
|
|
9714
|
+
static async create(params) {
|
|
9715
|
+
const aid = new _AID(params);
|
|
9716
|
+
aid._certFingerprint = await certificateSha256Fingerprint(params.certPem);
|
|
9717
|
+
return aid;
|
|
9718
|
+
}
|
|
9719
|
+
get certFingerprint() {
|
|
9720
|
+
return this._certFingerprint;
|
|
9721
|
+
}
|
|
9722
|
+
isCertValid() {
|
|
9723
|
+
return this._certValid;
|
|
9724
|
+
}
|
|
9725
|
+
isPrivateKeyValid() {
|
|
9726
|
+
return this._privateKeyValid;
|
|
9727
|
+
}
|
|
9728
|
+
async sign(payload) {
|
|
9729
|
+
if (!this._privateKeyValid || !this._privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
|
|
9730
|
+
try {
|
|
9731
|
+
const data = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
9732
|
+
return resultOk({ signature: await signBytes(this._privateKeyPem, data) });
|
|
9733
|
+
} catch (exc) {
|
|
9734
|
+
return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
|
|
9735
|
+
}
|
|
9736
|
+
}
|
|
9737
|
+
async verify(payload, signature) {
|
|
9738
|
+
if (!this._certValid) return resultErr(CERT_NOT_VALID, "certificate is not valid");
|
|
9739
|
+
try {
|
|
9740
|
+
const data = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
9741
|
+
return resultOk({ valid: await verifySignatureWithCert(this.certPem, signature, data) });
|
|
9742
|
+
} catch (exc) {
|
|
9743
|
+
return resultErr(VERIFICATION_OPERATION_ERROR, String(exc), exc);
|
|
9744
|
+
}
|
|
9745
|
+
}
|
|
9746
|
+
async signAgentMd(content) {
|
|
9747
|
+
if (!this._privateKeyValid || !this._privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
|
|
9748
|
+
try {
|
|
9749
|
+
const payload = normalizeAgentMdPayload(content);
|
|
9750
|
+
const signature = await signBytes(this._privateKeyPem, new TextEncoder().encode(payload));
|
|
9751
|
+
return resultOk({ signed: payload + buildAgentMdSignatureBlock(this.certFingerprint, Date.now() / 1e3, signature) });
|
|
9752
|
+
} catch (exc) {
|
|
9753
|
+
return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
|
|
9754
|
+
}
|
|
9755
|
+
}
|
|
9756
|
+
async verifyAgentMd(content) {
|
|
9757
|
+
if (!this._certValid) return resultErr(CERT_NOT_VALID, "certificate is not valid");
|
|
9758
|
+
try {
|
|
9759
|
+
const { payload, fields, parseError } = parseAgentMdTailSignature(String(content ?? ""));
|
|
9760
|
+
if (!fields) return resultOk(parseError ? { status: "invalid", payload, reason: parseError } : { status: "unsigned", payload });
|
|
9761
|
+
const payloadAid = extractAgentMdAid(payload);
|
|
9762
|
+
if (payloadAid && payloadAid !== this.aid) return resultOk({ status: "invalid", payload, aid: payloadAid, reason: "aid mismatch" });
|
|
9763
|
+
if (fields.cert_fingerprint.toLowerCase() !== this.certFingerprint.toLowerCase()) {
|
|
9764
|
+
return resultOk({ status: "invalid", payload, aid: this.aid, reason: "certificate fingerprint mismatch" });
|
|
9765
|
+
}
|
|
9766
|
+
const valid = await verifySignatureWithCert(this.certPem, fields.signature, new TextEncoder().encode(payload));
|
|
9767
|
+
return resultOk(valid ? { status: "verified", payload, aid: this.aid, cert_fingerprint: fields.cert_fingerprint, timestamp: Number(fields.timestamp) } : { status: "invalid", payload, aid: this.aid, cert_fingerprint: fields.cert_fingerprint, timestamp: Number(fields.timestamp), reason: "signature verification failed" });
|
|
9768
|
+
} catch (exc) {
|
|
9769
|
+
return resultErr(VERIFICATION_OPERATION_ERROR, String(exc), exc);
|
|
9770
|
+
}
|
|
9771
|
+
}
|
|
9772
|
+
};
|
|
9773
|
+
|
|
11134
9774
|
// src/client.ts
|
|
11135
9775
|
function stableStringify(obj) {
|
|
11136
9776
|
if (obj === null || obj === void 0) return "null";
|
|
@@ -11287,6 +9927,12 @@ var DEFAULT_SESSION_OPTIONS = {
|
|
|
11287
9927
|
http: 30
|
|
11288
9928
|
}
|
|
11289
9929
|
};
|
|
9930
|
+
var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
|
|
9931
|
+
"message.send",
|
|
9932
|
+
"group.send",
|
|
9933
|
+
"message.thought.put",
|
|
9934
|
+
"group.thought.put"
|
|
9935
|
+
]);
|
|
11290
9936
|
var RECONNECT_MIN_BASE_DELAY_SECONDS = 1;
|
|
11291
9937
|
var RECONNECT_MAX_BASE_DELAY_SECONDS = 64;
|
|
11292
9938
|
var TOKEN_REFRESH_CHECK_INTERVAL_MS = 3e4;
|
|
@@ -11334,6 +9980,7 @@ function reconnectSleepDelaySeconds(baseDelay, maxBaseDelay) {
|
|
|
11334
9980
|
return baseDelay + Math.random() * maxBaseDelay;
|
|
11335
9981
|
}
|
|
11336
9982
|
var PEER_CERT_CACHE_TTL = 3600;
|
|
9983
|
+
var AGENT_MD_HTTP_TIMEOUT_MS = 3e4;
|
|
11337
9984
|
function gatewayHttpUrl2(gatewayUrl, path) {
|
|
11338
9985
|
try {
|
|
11339
9986
|
const parsed = new URL(gatewayUrl);
|
|
@@ -11360,6 +10007,27 @@ function buildCertUrl(gatewayUrl, aid, certFingerprint) {
|
|
|
11360
10007
|
}
|
|
11361
10008
|
return url.toString();
|
|
11362
10009
|
}
|
|
10010
|
+
function agentMdHttpScheme(gatewayUrl) {
|
|
10011
|
+
const raw = String(gatewayUrl ?? "").trim().toLowerCase();
|
|
10012
|
+
return raw.startsWith("ws://") ? "http" : "https";
|
|
10013
|
+
}
|
|
10014
|
+
function agentMdAuthority(aid) {
|
|
10015
|
+
return String(aid ?? "").trim();
|
|
10016
|
+
}
|
|
10017
|
+
async function fetchWithTimeout(input, init, timeoutMs = AGENT_MD_HTTP_TIMEOUT_MS) {
|
|
10018
|
+
const controller = new AbortController();
|
|
10019
|
+
const timer = globalThis.setTimeout(() => controller.abort(), timeoutMs);
|
|
10020
|
+
try {
|
|
10021
|
+
return await fetch(input, { ...init, signal: controller.signal });
|
|
10022
|
+
} catch (error) {
|
|
10023
|
+
if (controller.signal.aborted) {
|
|
10024
|
+
throw new AUNError(`agent.md request timed out after ${timeoutMs}ms`);
|
|
10025
|
+
}
|
|
10026
|
+
throw error;
|
|
10027
|
+
} finally {
|
|
10028
|
+
globalThis.clearTimeout(timer);
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
11363
10031
|
function resolvePeerGatewayUrl(localGatewayUrl, peerAid) {
|
|
11364
10032
|
if (!peerAid.includes(".")) return localGatewayUrl;
|
|
11365
10033
|
const peerIssuer = peerAid.split(".").slice(1).join(".");
|
|
@@ -11640,8 +10308,17 @@ function normalizeDeliveryModeConfig(raw, opts = {}) {
|
|
|
11640
10308
|
affinity_ttl_ms: affinityTtlMs
|
|
11641
10309
|
};
|
|
11642
10310
|
}
|
|
10311
|
+
function clientOptionsConfig(options) {
|
|
10312
|
+
const raw = { ...options ?? {} };
|
|
10313
|
+
if (Object.prototype.hasOwnProperty.call(raw, "aid")) {
|
|
10314
|
+
throw new ValidationError("AUNClient options must not include aid; pass an AID object as the first argument");
|
|
10315
|
+
}
|
|
10316
|
+
delete raw.debug;
|
|
10317
|
+
delete raw.protected_headers;
|
|
10318
|
+
return raw;
|
|
10319
|
+
}
|
|
11643
10320
|
var _AUNClient = class _AUNClient {
|
|
11644
|
-
constructor(
|
|
10321
|
+
constructor(aid) {
|
|
11645
10322
|
/** SDK 配置模型 */
|
|
11646
10323
|
__publicField(this, "configModel");
|
|
11647
10324
|
/** 原始配置字典 */
|
|
@@ -11649,6 +10326,8 @@ var _AUNClient = class _AUNClient {
|
|
|
11649
10326
|
__publicField(this, "_aid", null);
|
|
11650
10327
|
__publicField(this, "_identity", null);
|
|
11651
10328
|
__publicField(this, "_state", "idle");
|
|
10329
|
+
__publicField(this, "_currentAid", null);
|
|
10330
|
+
__publicField(this, "_instanceProtectedHeaders", null);
|
|
11652
10331
|
__publicField(this, "_gatewayUrl", null);
|
|
11653
10332
|
__publicField(this, "_deviceId");
|
|
11654
10333
|
__publicField(this, "_slotId");
|
|
@@ -11663,12 +10342,6 @@ var _AUNClient = class _AUNClient {
|
|
|
11663
10342
|
__publicField(this, "_keystore");
|
|
11664
10343
|
__publicField(this, "_auth");
|
|
11665
10344
|
__publicField(this, "_transport");
|
|
11666
|
-
/** 认证命名空间 */
|
|
11667
|
-
__publicField(this, "auth");
|
|
11668
|
-
/** AID 托管命名空间 */
|
|
11669
|
-
__publicField(this, "custody");
|
|
11670
|
-
/** 元数据命名空间(心跳、状态、信任根管理) */
|
|
11671
|
-
__publicField(this, "meta");
|
|
11672
10345
|
// E2EE 编排状态(内存缓存)
|
|
11673
10346
|
__publicField(this, "_certCache", /* @__PURE__ */ new Map());
|
|
11674
10347
|
// 后台任务 handle(浏览器 setInterval/setTimeout)
|
|
@@ -11705,7 +10378,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11705
10378
|
__publicField(this, "_localAgentMdEtag", "");
|
|
11706
10379
|
/** gateway 在 RPC envelope._meta.agent_md_etag 注入的服务端 etag;纯观察,无下游依赖。 */
|
|
11707
10380
|
__publicField(this, "_remoteAgentMdEtag", "");
|
|
11708
|
-
/** 浏览器侧
|
|
10381
|
+
/** 浏览器侧 AIDs 逻辑根目录,正文映射到 IndexedDB 里的 {aid}/agent.md。 */
|
|
11709
10382
|
__publicField(this, "_agentMdPath", "");
|
|
11710
10383
|
__publicField(this, "_agentMdCache", /* @__PURE__ */ new Map());
|
|
11711
10384
|
__publicField(this, "_agentMdFetchInflight", /* @__PURE__ */ new Set());
|
|
@@ -11729,6 +10402,14 @@ var _AUNClient = class _AUNClient {
|
|
|
11729
10402
|
__publicField(this, "_reconnectActive", false);
|
|
11730
10403
|
__publicField(this, "_reconnectAbort", null);
|
|
11731
10404
|
__publicField(this, "_serverKicked", false);
|
|
10405
|
+
// 重连状态追踪(对齐 Python client.py)
|
|
10406
|
+
__publicField(this, "_nextRetryAt", null);
|
|
10407
|
+
__publicField(this, "_retryAttempt", 0);
|
|
10408
|
+
__publicField(this, "_retryMaxAttempts", 0);
|
|
10409
|
+
__publicField(this, "_lastError", null);
|
|
10410
|
+
__publicField(this, "_lastErrorCode", null);
|
|
10411
|
+
/** 对端 AID 缓存(aid string → AID 对象) */
|
|
10412
|
+
__publicField(this, "_peerCache", /* @__PURE__ */ new Map());
|
|
11732
10413
|
/**
|
|
11733
10414
|
* 缓存最近一次服务端 gateway.disconnect 信息(含 code/reason/detail),
|
|
11734
10415
|
* 让后续 connection.state(terminal_failed) 也能携带 detail(如配额超限信息)。
|
|
@@ -11747,16 +10428,23 @@ var _AUNClient = class _AUNClient {
|
|
|
11747
10428
|
*/
|
|
11748
10429
|
__publicField(this, "_v2PullInflight", false);
|
|
11749
10430
|
__publicField(this, "_v2PullPending", false);
|
|
11750
|
-
const
|
|
10431
|
+
const inputAid = aid instanceof AID ? aid : null;
|
|
10432
|
+
if (typeof aid === "string") {
|
|
10433
|
+
throw new ValidationError("AUNClient aid must be an AID object, not a string");
|
|
10434
|
+
}
|
|
10435
|
+
const options = {};
|
|
10436
|
+
const rawConfig = clientOptionsConfig(options);
|
|
10437
|
+
if (inputAid) rawConfig.aun_path = inputAid.aunPath;
|
|
10438
|
+
const _debug = false;
|
|
11751
10439
|
this.configModel = createConfig(rawConfig);
|
|
11752
|
-
const initAid =
|
|
10440
|
+
const initAid = inputAid ? inputAid.aid : null;
|
|
11753
10441
|
this.config = {
|
|
11754
10442
|
aun_path: this.configModel.aunPath,
|
|
11755
10443
|
root_ca_path: this.configModel.rootCaPem,
|
|
11756
10444
|
seed_password: this.configModel.seedPassword
|
|
11757
10445
|
};
|
|
11758
10446
|
this._agentMdPath = this._agentMdDefaultRoot();
|
|
11759
|
-
this._deviceId = getDeviceId();
|
|
10447
|
+
this._deviceId = inputAid?.deviceId || getDeviceId();
|
|
11760
10448
|
this._logger = new AUNLogger({ debug: _debug, aunPath: this.configModel.aunPath });
|
|
11761
10449
|
this._logger.bindDeviceId(this._deviceId);
|
|
11762
10450
|
this._clientLog = this._logger.for("aun_core.client");
|
|
@@ -11769,7 +10457,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11769
10457
|
this._dispatcher = new EventDispatcher();
|
|
11770
10458
|
this._discovery = new GatewayDiscovery();
|
|
11771
10459
|
this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? void 0 });
|
|
11772
|
-
this._slotId = "";
|
|
10460
|
+
this._slotId = inputAid?.slotId || "default";
|
|
11773
10461
|
this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
|
|
11774
10462
|
this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
|
|
11775
10463
|
this._auth = new AuthFlow({
|
|
@@ -11792,27 +10480,29 @@ var _AUNClient = class _AUNClient {
|
|
|
11792
10480
|
this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
|
|
11793
10481
|
});
|
|
11794
10482
|
});
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
10483
|
+
if (inputAid) {
|
|
10484
|
+
if (!inputAid.isPrivateKeyValid()) throw new StateError("AUNClient requires an AID with a valid private key");
|
|
10485
|
+
this._currentAid = inputAid;
|
|
10486
|
+
this._identity = {
|
|
10487
|
+
aid: inputAid.aid,
|
|
10488
|
+
private_key_pem: inputAid._privateKeyPem ?? "",
|
|
10489
|
+
public_key_der_b64: inputAid.publicKey,
|
|
10490
|
+
cert: inputAid.certPem
|
|
10491
|
+
};
|
|
10492
|
+
this._state = "disconnected";
|
|
10493
|
+
}
|
|
10494
|
+
if (options?.protected_headers !== void 0) {
|
|
10495
|
+
this.setProtectedHeaders(options.protected_headers);
|
|
10496
|
+
}
|
|
11798
10497
|
this._auth.setLogger(this._logAuth);
|
|
11799
10498
|
this._transport.setLogger(this._logTransport);
|
|
11800
10499
|
this._dispatcher.setLogger(this._logEvents);
|
|
11801
10500
|
if (typeof this._discovery.setLogger === "function") {
|
|
11802
10501
|
this._discovery.setLogger(this._logger.for("aun_core.discovery"));
|
|
11803
10502
|
}
|
|
11804
|
-
if (typeof this.auth.setLogger === "function") {
|
|
11805
|
-
this.auth.setLogger(this._logger.for("aun_core.namespace.auth"));
|
|
11806
|
-
}
|
|
11807
|
-
if (typeof this.custody.setLogger === "function") {
|
|
11808
|
-
this.custody.setLogger(this._logger.for("aun_core.namespace.custody"));
|
|
11809
|
-
}
|
|
11810
10503
|
if (typeof this._keystore.setLogger === "function") {
|
|
11811
10504
|
this._keystore.setLogger(this._logKeystore);
|
|
11812
10505
|
}
|
|
11813
|
-
if (typeof this.meta.setLogger === "function") {
|
|
11814
|
-
this.meta.setLogger(this._logger.for("aun_core.namespace.meta"));
|
|
11815
|
-
}
|
|
11816
10506
|
this._dispatcher.subscribe("_raw.message.received", (data) => {
|
|
11817
10507
|
this._onRawMessageReceived(data);
|
|
11818
10508
|
});
|
|
@@ -11856,17 +10546,169 @@ var _AUNClient = class _AUNClient {
|
|
|
11856
10546
|
get aid() {
|
|
11857
10547
|
return this._aid;
|
|
11858
10548
|
}
|
|
11859
|
-
|
|
10549
|
+
_setAgentMdRoot(root) {
|
|
11860
10550
|
const next = String(root ?? "").trim() || this._agentMdDefaultRoot();
|
|
11861
10551
|
this._agentMdPath = next;
|
|
11862
10552
|
this._agentMdCache.clear();
|
|
11863
10553
|
return next;
|
|
11864
10554
|
}
|
|
11865
|
-
|
|
11866
|
-
|
|
10555
|
+
async _resolveAgentMdUrl(aid) {
|
|
10556
|
+
const target = String(aid ?? "").trim();
|
|
10557
|
+
if (!target) throw new ValidationError("agent.md requires non-empty aid");
|
|
10558
|
+
let gatewayUrl = String(this._gatewayUrl ?? "").trim();
|
|
10559
|
+
if (!gatewayUrl) {
|
|
10560
|
+
try {
|
|
10561
|
+
gatewayUrl = await this._resolveGatewayForAid(target);
|
|
10562
|
+
} catch {
|
|
10563
|
+
gatewayUrl = "";
|
|
10564
|
+
}
|
|
10565
|
+
}
|
|
10566
|
+
const authority = agentMdAuthority(target);
|
|
10567
|
+
return `${agentMdHttpScheme(gatewayUrl)}://${authority}/agent.md`;
|
|
10568
|
+
}
|
|
10569
|
+
async _ensureAgentMdUploadToken(aid, gatewayUrl) {
|
|
10570
|
+
let identity = await this._auth.loadIdentityOrNone(aid);
|
|
10571
|
+
if (!identity && this._identity && String(this._identity.aid ?? "") === aid) {
|
|
10572
|
+
identity = this._identity;
|
|
10573
|
+
}
|
|
10574
|
+
if (!identity) {
|
|
10575
|
+
throw new StateError("no local identity found, register or load an AID first");
|
|
10576
|
+
}
|
|
10577
|
+
const cachedToken = String(identity.access_token ?? "");
|
|
10578
|
+
const expiresAt = this._auth.getAccessTokenExpiry(identity);
|
|
10579
|
+
if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1e3 + 30)) {
|
|
10580
|
+
return cachedToken;
|
|
10581
|
+
}
|
|
10582
|
+
if (identity.refresh_token) {
|
|
10583
|
+
try {
|
|
10584
|
+
const refreshed = await this._auth.refreshCachedTokens(gatewayUrl, identity);
|
|
10585
|
+
const refreshedToken = String(refreshed.access_token ?? "");
|
|
10586
|
+
const refreshedExpiry = this._auth.getAccessTokenExpiry(refreshed);
|
|
10587
|
+
if (refreshedToken && (refreshedExpiry === null || refreshedExpiry > Date.now() / 1e3 + 30)) {
|
|
10588
|
+
this._identity = refreshed;
|
|
10589
|
+
return refreshedToken;
|
|
10590
|
+
}
|
|
10591
|
+
} catch {
|
|
10592
|
+
}
|
|
10593
|
+
}
|
|
10594
|
+
const result = await this._auth.authenticate(gatewayUrl, aid);
|
|
10595
|
+
const token = String(result.access_token ?? "");
|
|
10596
|
+
if (!token) throw new StateError("authenticate did not return access_token");
|
|
10597
|
+
const fallbackIdentity = {
|
|
10598
|
+
...identity,
|
|
10599
|
+
access_token: token,
|
|
10600
|
+
refresh_token: String(result.refresh_token ?? identity.refresh_token ?? "")
|
|
10601
|
+
};
|
|
10602
|
+
const fallbackExpiresAt = Number(result.expires_at ?? identity.expires_at ?? NaN);
|
|
10603
|
+
if (Number.isFinite(fallbackExpiresAt)) fallbackIdentity.expires_at = fallbackExpiresAt;
|
|
10604
|
+
this._identity = await this._auth.loadIdentityOrNone(aid) ?? fallbackIdentity;
|
|
10605
|
+
return token;
|
|
10606
|
+
}
|
|
10607
|
+
async _uploadAgentMd(content) {
|
|
10608
|
+
const target = String(this._aid ?? this._currentAid?.aid ?? "").trim();
|
|
10609
|
+
if (!target) throw new StateError("uploadAgentMd requires local AID");
|
|
10610
|
+
const gatewayUrl = await this._resolveGatewayForAid(target);
|
|
10611
|
+
this._gatewayUrl = gatewayUrl;
|
|
10612
|
+
const token = await this._ensureAgentMdUploadToken(target, gatewayUrl);
|
|
10613
|
+
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(target), {
|
|
10614
|
+
method: "PUT",
|
|
10615
|
+
headers: {
|
|
10616
|
+
Authorization: `Bearer ${token}`,
|
|
10617
|
+
"Content-Type": "text/markdown; charset=utf-8"
|
|
10618
|
+
},
|
|
10619
|
+
body: content
|
|
10620
|
+
});
|
|
10621
|
+
if (response.status === 404) {
|
|
10622
|
+
throw new NotFoundError(`agent.md endpoint not found for aid: ${target}`);
|
|
10623
|
+
}
|
|
10624
|
+
if (!response.ok) {
|
|
10625
|
+
const message = (await response.text()).trim();
|
|
10626
|
+
throw new AUNError(`upload agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ""}`);
|
|
10627
|
+
}
|
|
10628
|
+
const payload = await response.json();
|
|
10629
|
+
if (!isJsonObject(payload)) throw new AUNError("upload agent.md returned invalid JSON payload");
|
|
10630
|
+
return payload;
|
|
10631
|
+
}
|
|
10632
|
+
async _downloadAgentMd(aid) {
|
|
10633
|
+
const target = String(aid ?? "").trim();
|
|
10634
|
+
if (!target) throw new ValidationError("downloadAgentMd requires non-empty aid");
|
|
10635
|
+
const cached = this._agentMdCache.get(target);
|
|
10636
|
+
const url = await this._resolveAgentMdUrl(target);
|
|
10637
|
+
const response = await fetchWithTimeout(url, {
|
|
10638
|
+
method: "GET",
|
|
10639
|
+
headers: { Accept: "text/markdown" },
|
|
10640
|
+
redirect: "follow"
|
|
10641
|
+
});
|
|
10642
|
+
if (response.status === 304 && typeof cached?.text === "string") {
|
|
10643
|
+
return String(cached.text);
|
|
10644
|
+
}
|
|
10645
|
+
if (response.status === 404) {
|
|
10646
|
+
throw new NotFoundError(`agent.md not found for aid: ${target}`);
|
|
10647
|
+
}
|
|
10648
|
+
if (!response.ok) {
|
|
10649
|
+
const message = (await response.text()).trim();
|
|
10650
|
+
throw new AUNError(`download agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ""}`);
|
|
10651
|
+
}
|
|
10652
|
+
const text = await response.text();
|
|
10653
|
+
const etag = String(response.headers?.get("ETag") ?? response.headers?.get("etag") ?? "").trim();
|
|
10654
|
+
const lastModified = String(response.headers?.get("Last-Modified") ?? response.headers?.get("last-modified") ?? "").trim();
|
|
10655
|
+
this._agentMdCache.set(target, {
|
|
10656
|
+
...cached ?? {},
|
|
10657
|
+
text,
|
|
10658
|
+
etag,
|
|
10659
|
+
lastModified,
|
|
10660
|
+
remote_etag: etag,
|
|
10661
|
+
last_modified: lastModified
|
|
10662
|
+
});
|
|
10663
|
+
return text;
|
|
10664
|
+
}
|
|
10665
|
+
async _headAgentMd(aid) {
|
|
10666
|
+
const target = String(aid ?? "").trim();
|
|
10667
|
+
if (!target) throw new ValidationError("headAgentMd requires non-empty aid");
|
|
10668
|
+
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(target), {
|
|
10669
|
+
method: "HEAD",
|
|
10670
|
+
headers: { Accept: "text/markdown" }
|
|
10671
|
+
});
|
|
10672
|
+
const etag = String(response.headers?.get("ETag") ?? response.headers?.get("etag") ?? "").trim();
|
|
10673
|
+
const lastModified = String(response.headers?.get("Last-Modified") ?? response.headers?.get("last-modified") ?? "").trim();
|
|
10674
|
+
if (response.status === 404) {
|
|
10675
|
+
return { aid: target, found: false, etag: "", last_modified: "", status: 404 };
|
|
10676
|
+
}
|
|
10677
|
+
if (!response.ok) {
|
|
10678
|
+
throw new AUNError(`head agent.md failed: HTTP ${response.status}`);
|
|
10679
|
+
}
|
|
10680
|
+
const cached = this._agentMdCache.get(target) ?? {};
|
|
10681
|
+
this._agentMdCache.set(target, {
|
|
10682
|
+
...cached,
|
|
10683
|
+
etag,
|
|
10684
|
+
lastModified,
|
|
10685
|
+
remote_etag: etag,
|
|
10686
|
+
last_modified: lastModified
|
|
10687
|
+
});
|
|
10688
|
+
return { aid: target, found: true, etag, last_modified: lastModified, status: response.status };
|
|
11867
10689
|
}
|
|
11868
|
-
|
|
11869
|
-
|
|
10690
|
+
async _verifyAgentMd(content, aid) {
|
|
10691
|
+
const target = String(aid ?? "").trim();
|
|
10692
|
+
if (!target) throw new ValidationError("verifyAgentMd requires non-empty aid");
|
|
10693
|
+
let peer = target === this._currentAid?.aid ? this._currentAid : null;
|
|
10694
|
+
if (!peer) {
|
|
10695
|
+
let certPem = String(await this._keystore.loadCert(target) ?? "").trim();
|
|
10696
|
+
if (!certPem) {
|
|
10697
|
+
certPem = String(await this._fetchPeerCert(target) ?? "").trim();
|
|
10698
|
+
}
|
|
10699
|
+
if (!certPem) throw new NotFoundError(`certificate not found for aid: ${target}`);
|
|
10700
|
+
peer = await AID.create({
|
|
10701
|
+
aid: target,
|
|
10702
|
+
aunPath: this.configModel.aunPath,
|
|
10703
|
+
certPem,
|
|
10704
|
+
privateKeyPem: null,
|
|
10705
|
+
certValid: true,
|
|
10706
|
+
privateKeyValid: false
|
|
10707
|
+
});
|
|
10708
|
+
}
|
|
10709
|
+
const result = await peer.verifyAgentMd(content);
|
|
10710
|
+
if (!result.ok) throw new AUNError(result.error.message);
|
|
10711
|
+
return { ...result.data, verified: result.data.status === "verified" };
|
|
11870
10712
|
}
|
|
11871
10713
|
/**
|
|
11872
10714
|
* 浏览器版本 publishAgentMd。默认从 {agentMdPath}/{self_aid}/agent.md 的等价 IndexedDB 正文读取,
|
|
@@ -11894,8 +10736,12 @@ var _AUNClient = class _AUNClient {
|
|
|
11894
10736
|
if (localContent === null || localContent.length === 0) {
|
|
11895
10737
|
throw new ValidationError("publishAgentMd requires local agent.md content");
|
|
11896
10738
|
}
|
|
11897
|
-
const
|
|
11898
|
-
|
|
10739
|
+
const signedResult = await this._currentAid?.signAgentMd(localContent);
|
|
10740
|
+
if (!signedResult?.ok) {
|
|
10741
|
+
throw new StateError(signedResult?.error.message ?? "publishAgentMd requires a valid local AID private key");
|
|
10742
|
+
}
|
|
10743
|
+
const signed = signedResult.data.signed;
|
|
10744
|
+
const result = await this._uploadAgentMd(signed);
|
|
11899
10745
|
this._localAgentMdEtag = await this._agentMdContentEtag(signed);
|
|
11900
10746
|
const remoteEtag = isJsonObject(result) ? String(result.etag ?? "").trim() : "";
|
|
11901
10747
|
if (remoteEtag) this._remoteAgentMdEtag = remoteEtag;
|
|
@@ -11914,13 +10760,13 @@ var _AUNClient = class _AUNClient {
|
|
|
11914
10760
|
* 浏览器版本 fetchAgentMd。aid 缺省时取自身;下载后的正文固定写入
|
|
11915
10761
|
* {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,agentmd.json 只保存元数据。
|
|
11916
10762
|
*/
|
|
11917
|
-
async
|
|
10763
|
+
async _fetchAgentMdCache(aid) {
|
|
11918
10764
|
const target = String(aid ?? this._aid ?? "").trim();
|
|
11919
10765
|
if (!target) {
|
|
11920
10766
|
throw new ValidationError("fetchAgentMd requires aid (or local AID)");
|
|
11921
10767
|
}
|
|
11922
|
-
const content = await this.
|
|
11923
|
-
const signature = await this.
|
|
10768
|
+
const content = await this._downloadAgentMd(target);
|
|
10769
|
+
const signature = await this._verifyAgentMd(content, target);
|
|
11924
10770
|
const isSelf = target === (this._aid ?? "");
|
|
11925
10771
|
const localEtag = await this._agentMdContentEtag(content);
|
|
11926
10772
|
const cacheMeta = this._agentMdAuthCacheMeta(target);
|
|
@@ -11968,7 +10814,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11968
10814
|
return String(this._aid ?? "").trim();
|
|
11969
10815
|
}
|
|
11970
10816
|
_agentMdDefaultRoot() {
|
|
11971
|
-
return this._joinAgentMdPath(this.configModel.aunPath || ".", "
|
|
10817
|
+
return this._joinAgentMdPath(this.configModel.aunPath || ".", "AIDs");
|
|
11972
10818
|
}
|
|
11973
10819
|
_joinAgentMdPath(base, name) {
|
|
11974
10820
|
const left = String(base ?? "").trim().replace(/[\\/]+$/g, "");
|
|
@@ -12070,8 +10916,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12070
10916
|
}
|
|
12071
10917
|
_agentMdAuthCacheMeta(aid) {
|
|
12072
10918
|
try {
|
|
12073
|
-
const
|
|
12074
|
-
const record = store?.get(String(aid ?? "").trim());
|
|
10919
|
+
const record = this._agentMdCache.get(String(aid ?? "").trim());
|
|
12075
10920
|
return record && typeof record === "object" ? { ...record } : {};
|
|
12076
10921
|
} catch {
|
|
12077
10922
|
return {};
|
|
@@ -12173,7 +11018,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12173
11018
|
if (this._agentMdFetchInflight.has(target)) return;
|
|
12174
11019
|
this._agentMdFetchInflight.add(target);
|
|
12175
11020
|
try {
|
|
12176
|
-
await this.
|
|
11021
|
+
await this._fetchAgentMdCache(target);
|
|
12177
11022
|
} catch (err) {
|
|
12178
11023
|
await this._saveAgentMdRecord(target, {
|
|
12179
11024
|
last_error: err instanceof Error ? err.message : String(err),
|
|
@@ -12228,7 +11073,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12228
11073
|
"envelope"
|
|
12229
11074
|
);
|
|
12230
11075
|
}
|
|
12231
|
-
async
|
|
11076
|
+
async _checkAgentMdCache(aid, maxUnsyncedDays = 0) {
|
|
12232
11077
|
const target = String(aid ?? this._aid ?? "").trim();
|
|
12233
11078
|
if (!target) throw new ValidationError("checkAgentMd requires aid (or local AID)");
|
|
12234
11079
|
const before = await this._loadAgentMdRecord(target) ?? {};
|
|
@@ -12256,7 +11101,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12256
11101
|
const now = Date.now();
|
|
12257
11102
|
let remote;
|
|
12258
11103
|
try {
|
|
12259
|
-
remote = await this.
|
|
11104
|
+
remote = await this._headAgentMd(target);
|
|
12260
11105
|
} catch (err) {
|
|
12261
11106
|
await this._saveAgentMdRecord(target, { checked_at: now, remote_status: "error", last_error: err instanceof Error ? err.message : String(err) });
|
|
12262
11107
|
throw err;
|
|
@@ -12310,7 +11155,118 @@ var _AUNClient = class _AUNClient {
|
|
|
12310
11155
|
}
|
|
12311
11156
|
}
|
|
12312
11157
|
get state() {
|
|
12313
|
-
return this._state;
|
|
11158
|
+
return this._publicState(this._state);
|
|
11159
|
+
}
|
|
11160
|
+
_publicState(state) {
|
|
11161
|
+
return STATE_TO_PUBLIC[state] ?? state;
|
|
11162
|
+
}
|
|
11163
|
+
get currentAid() {
|
|
11164
|
+
return this._currentAid;
|
|
11165
|
+
}
|
|
11166
|
+
get hasIdentity() {
|
|
11167
|
+
return this._currentAid !== null && this.state !== "closed" /* CLOSED */;
|
|
11168
|
+
}
|
|
11169
|
+
get canSign() {
|
|
11170
|
+
return this.hasIdentity && !!this._currentAid?.isPrivateKeyValid();
|
|
11171
|
+
}
|
|
11172
|
+
get canConnect() {
|
|
11173
|
+
return this.hasIdentity && this.state !== "closed" /* CLOSED */;
|
|
11174
|
+
}
|
|
11175
|
+
get canSend() {
|
|
11176
|
+
return this.state === "ready" /* READY */;
|
|
11177
|
+
}
|
|
11178
|
+
get isReady() {
|
|
11179
|
+
return this.canSend;
|
|
11180
|
+
}
|
|
11181
|
+
get isOnline() {
|
|
11182
|
+
return this.state === "ready" /* READY */ || this.state === "reconnecting" /* RECONNECTING */ || this.state === "retry_backoff" /* RETRY_BACKOFF */;
|
|
11183
|
+
}
|
|
11184
|
+
get isClosed() {
|
|
11185
|
+
return this.state === "closed" /* CLOSED */;
|
|
11186
|
+
}
|
|
11187
|
+
get aunPath() {
|
|
11188
|
+
return this.hasIdentity ? this._currentAid?.aunPath ?? this.configModel.aunPath : null;
|
|
11189
|
+
}
|
|
11190
|
+
/** 下次重连时间(仅在 retry_backoff 状态时非 null,对齐 Python next_retry_at) */
|
|
11191
|
+
get nextRetryAt() {
|
|
11192
|
+
return this.state === "retry_backoff" /* RETRY_BACKOFF */ ? this._nextRetryAt : null;
|
|
11193
|
+
}
|
|
11194
|
+
/** 距下次重连的剩余秒数(仅在 retry_backoff 状态时非 null,对齐 Python next_retry_in_seconds) */
|
|
11195
|
+
get nextRetryInSeconds() {
|
|
11196
|
+
const t = this.nextRetryAt;
|
|
11197
|
+
if (t === null) return null;
|
|
11198
|
+
return Math.max(0, (t.getTime() - Date.now()) / 1e3);
|
|
11199
|
+
}
|
|
11200
|
+
/** 当前重连尝试次数(对齐 Python retry_attempt) */
|
|
11201
|
+
get retryAttempt() {
|
|
11202
|
+
return this._retryAttempt;
|
|
11203
|
+
}
|
|
11204
|
+
/** 最大重连次数(0 = 无限,对齐 Python retry_max_attempts) */
|
|
11205
|
+
get retryMaxAttempts() {
|
|
11206
|
+
return this._retryMaxAttempts;
|
|
11207
|
+
}
|
|
11208
|
+
/** 最近一次错误(对齐 Python last_error) */
|
|
11209
|
+
get lastError() {
|
|
11210
|
+
return this._lastError;
|
|
11211
|
+
}
|
|
11212
|
+
/** 最近一次错误码(对齐 Python last_error_code) */
|
|
11213
|
+
get lastErrorCode() {
|
|
11214
|
+
return this._lastErrorCode;
|
|
11215
|
+
}
|
|
11216
|
+
loadIdentity(aid) {
|
|
11217
|
+
if (!aid?.isPrivateKeyValid()) throw new StateError("loadIdentity requires an AID with a valid private key");
|
|
11218
|
+
const publicState = this.state;
|
|
11219
|
+
if (publicState !== "no_identity" /* NO_IDENTITY */ && publicState !== "closed" /* CLOSED */) {
|
|
11220
|
+
throw new StateError(`loadIdentity not allowed in state ${publicState}`);
|
|
11221
|
+
}
|
|
11222
|
+
this._currentAid = aid;
|
|
11223
|
+
this._aid = aid.aid;
|
|
11224
|
+
this._identity = {
|
|
11225
|
+
aid: aid.aid,
|
|
11226
|
+
private_key_pem: aid._privateKeyPem ?? "",
|
|
11227
|
+
public_key_der_b64: aid.publicKey,
|
|
11228
|
+
cert: aid.certPem
|
|
11229
|
+
};
|
|
11230
|
+
this._auth._aid = aid.aid;
|
|
11231
|
+
this._state = "disconnected";
|
|
11232
|
+
this._closing = false;
|
|
11233
|
+
}
|
|
11234
|
+
setProtectedHeaders(headers) {
|
|
11235
|
+
if (!headers) {
|
|
11236
|
+
this._instanceProtectedHeaders = null;
|
|
11237
|
+
return;
|
|
11238
|
+
}
|
|
11239
|
+
const cleaned = {};
|
|
11240
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
11241
|
+
if (key === "_auth") continue;
|
|
11242
|
+
cleaned[String(key)] = String(value);
|
|
11243
|
+
}
|
|
11244
|
+
this._instanceProtectedHeaders = Object.keys(cleaned).length ? cleaned : null;
|
|
11245
|
+
}
|
|
11246
|
+
getProtectedHeaders() {
|
|
11247
|
+
return this._instanceProtectedHeaders ? { ...this._instanceProtectedHeaders } : null;
|
|
11248
|
+
}
|
|
11249
|
+
cachePeer(aid) {
|
|
11250
|
+
if (!this.hasIdentity) throw new StateError("cachePeer requires a loaded identity");
|
|
11251
|
+
if (!aid.isCertValid()) throw new ValidationError("cachePeer requires an AID with a valid certificate");
|
|
11252
|
+
this._peerCache.set(aid.aid, aid);
|
|
11253
|
+
return aid;
|
|
11254
|
+
}
|
|
11255
|
+
getPeer(aid) {
|
|
11256
|
+
if (!this.hasIdentity) throw new StateError("getPeer requires a loaded identity");
|
|
11257
|
+
return this._peerCache.get(String(aid ?? "").trim()) ?? null;
|
|
11258
|
+
}
|
|
11259
|
+
async lookupPeer(aid) {
|
|
11260
|
+
if (!this.hasIdentity) throw new StateError("lookupPeer requires a loaded identity");
|
|
11261
|
+
const target = String(aid ?? "").trim();
|
|
11262
|
+
if (!target) throw new ValidationError("lookupPeer requires non-empty aid");
|
|
11263
|
+
const cached = this._peerCache.get(target);
|
|
11264
|
+
if (cached) return cached;
|
|
11265
|
+
throw new NotFoundError(`peer not found in cache: ${target}`);
|
|
11266
|
+
}
|
|
11267
|
+
peers() {
|
|
11268
|
+
if (!this.hasIdentity) throw new StateError("peers requires a loaded identity");
|
|
11269
|
+
return [...this._peerCache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
|
|
12314
11270
|
}
|
|
12315
11271
|
get gatewayUrl() {
|
|
12316
11272
|
return this._gatewayUrl;
|
|
@@ -12325,35 +11281,77 @@ var _AUNClient = class _AUNClient {
|
|
|
12325
11281
|
get gatewayHealth() {
|
|
12326
11282
|
return this._discovery.lastHealthy;
|
|
12327
11283
|
}
|
|
12328
|
-
|
|
12329
|
-
|
|
11284
|
+
// ── 生命周期 ──────────────────────────────────────
|
|
11285
|
+
/** 仅认证当前身份,获取/刷新 token,但不建立长连接。 */
|
|
11286
|
+
async authenticate(options = {}) {
|
|
12330
11287
|
const tStart = Date.now();
|
|
12331
|
-
this.
|
|
11288
|
+
const target = this._currentAid?.aid ?? this._aid ?? "";
|
|
11289
|
+
if (!target || !this._currentAid?.isPrivateKeyValid()) {
|
|
11290
|
+
throw new StateError("authenticate requires a loaded AID with a valid private key");
|
|
11291
|
+
}
|
|
11292
|
+
const publicState = this.state;
|
|
11293
|
+
if (publicState !== "standby" /* STANDBY */) {
|
|
11294
|
+
throw new StateError(`authenticate not allowed in state ${publicState}`);
|
|
11295
|
+
}
|
|
11296
|
+
if ("aid" in options || "access_token" in options || "token" in options || "kite_token" in options) {
|
|
11297
|
+
throw new ValidationError("authenticate options must not include aid or token fields; load an AID object first");
|
|
11298
|
+
}
|
|
11299
|
+
this._state = "connecting";
|
|
12332
11300
|
try {
|
|
12333
|
-
const
|
|
12334
|
-
this.
|
|
11301
|
+
const gateway = String(options.gateway ?? this._gatewayUrl ?? await this._resolveGatewayForAid(target)).trim();
|
|
11302
|
+
const result = await this._auth.authenticate(gateway, target);
|
|
11303
|
+
this._gatewayUrl = String(result.gateway ?? gateway);
|
|
11304
|
+
this._identity = await this._auth.loadIdentityOrNone(target);
|
|
11305
|
+
this._state = "authenticated";
|
|
11306
|
+
this._clientLog.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${target}`);
|
|
12335
11307
|
return result;
|
|
12336
11308
|
} catch (err) {
|
|
12337
|
-
this.
|
|
11309
|
+
this._state = "disconnected";
|
|
11310
|
+
this._clientLog.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
12338
11311
|
throw err;
|
|
12339
11312
|
}
|
|
12340
11313
|
}
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
* 连接到 Gateway。
|
|
12344
|
-
*
|
|
12345
|
-
* @param auth - 认证参数,必须包含 access_token 和 gateway
|
|
12346
|
-
* @param options - 可选的会话选项(auto_reconnect, heartbeat_interval 等)
|
|
12347
|
-
*/
|
|
12348
|
-
async connect(auth, options) {
|
|
11314
|
+
/** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
|
|
11315
|
+
async connect(opts) {
|
|
12349
11316
|
const tStart = Date.now();
|
|
11317
|
+
const options = {};
|
|
11318
|
+
if (opts?.auto_reconnect !== void 0) options.auto_reconnect = opts.auto_reconnect;
|
|
11319
|
+
if (opts?.heartbeat_interval !== void 0) options.heartbeat_interval = opts.heartbeat_interval;
|
|
11320
|
+
if (opts?.connect_timeout !== void 0 || opts?.call_timeout !== void 0) {
|
|
11321
|
+
options.timeouts = {
|
|
11322
|
+
...opts.connect_timeout !== void 0 ? { connect: opts.connect_timeout } : {},
|
|
11323
|
+
...opts.call_timeout !== void 0 ? { call: opts.call_timeout } : {}
|
|
11324
|
+
};
|
|
11325
|
+
}
|
|
11326
|
+
if (opts?.retry_initial_delay !== void 0 || opts?.retry_max_delay !== void 0 || opts?.retry_max_attempts !== void 0) {
|
|
11327
|
+
options.retry = {
|
|
11328
|
+
initial_delay: opts.retry_initial_delay ?? 1,
|
|
11329
|
+
max_delay: opts.retry_max_delay ?? 64,
|
|
11330
|
+
max_attempts: opts.retry_max_attempts ?? 0
|
|
11331
|
+
};
|
|
11332
|
+
}
|
|
12350
11333
|
this._clientLog.debug(`connect enter: state=${this._state} aid=${this._aid ?? "-"}`);
|
|
12351
|
-
|
|
11334
|
+
const target = this._currentAid?.aid ?? this._aid ?? "";
|
|
11335
|
+
if (!target || !this._currentAid?.isPrivateKeyValid()) {
|
|
11336
|
+
throw new StateError("connect requires a loaded AID with a valid private key");
|
|
11337
|
+
}
|
|
11338
|
+
const publicState = this.state;
|
|
11339
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
11340
|
+
"standby" /* STANDBY */,
|
|
11341
|
+
"authenticated" /* AUTHENTICATED */,
|
|
11342
|
+
"retry_backoff" /* RETRY_BACKOFF */,
|
|
11343
|
+
"connection_failed" /* CONNECTION_FAILED */
|
|
11344
|
+
]);
|
|
11345
|
+
if (!allowed.has(publicState)) {
|
|
12352
11346
|
this._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=invalid_state state=${this._state}`);
|
|
12353
|
-
throw new StateError(`connect not allowed in state ${
|
|
11347
|
+
throw new StateError(`connect not allowed in state ${publicState}`);
|
|
11348
|
+
}
|
|
11349
|
+
if (!this._gatewayUrl) {
|
|
11350
|
+
await this.authenticate();
|
|
12354
11351
|
}
|
|
12355
11352
|
this._state = "connecting";
|
|
12356
|
-
const
|
|
11353
|
+
const gateway = String(this._gatewayUrl ?? "").trim();
|
|
11354
|
+
const params = { ...options, gateway };
|
|
12357
11355
|
const normalized = this._normalizeConnectParams(params);
|
|
12358
11356
|
this._sessionParams = normalized;
|
|
12359
11357
|
this._sessionOptions = this._buildSessionOptions(normalized);
|
|
@@ -12364,7 +11362,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12364
11362
|
for (const gw of gateways) {
|
|
12365
11363
|
try {
|
|
12366
11364
|
const gwParams = { ...normalized, gateway: gw };
|
|
12367
|
-
await this._connectOnce(gwParams,
|
|
11365
|
+
await this._connectOnce(gwParams, true);
|
|
12368
11366
|
this._clientLog.debug(`connect exit: elapsed=${Date.now() - tStart}ms state=${this._state}`);
|
|
12369
11367
|
return;
|
|
12370
11368
|
} catch (err) {
|
|
@@ -12378,7 +11376,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12378
11376
|
}
|
|
12379
11377
|
}
|
|
12380
11378
|
if (this._state === "connecting" || this._state === "authenticating") {
|
|
12381
|
-
this._state = "
|
|
11379
|
+
this._state = "terminal_failed";
|
|
12382
11380
|
}
|
|
12383
11381
|
this._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
12384
11382
|
throw lastErr;
|
|
@@ -12400,52 +11398,9 @@ var _AUNClient = class _AUNClient {
|
|
|
12400
11398
|
}
|
|
12401
11399
|
await this._transport.close();
|
|
12402
11400
|
this._state = "disconnected";
|
|
12403
|
-
await this._dispatcher.publish("
|
|
11401
|
+
await this._dispatcher.publish("state_change", { state: this._publicState(this._state) });
|
|
12404
11402
|
this._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
|
|
12405
11403
|
}
|
|
12406
|
-
/** 列出本地所有已存储的身份摘要(仅返回有有效私钥的 AID) */
|
|
12407
|
-
async listIdentities() {
|
|
12408
|
-
const tStart = Date.now();
|
|
12409
|
-
this._clientLog.debug("listIdentities enter");
|
|
12410
|
-
try {
|
|
12411
|
-
const listFn = this._keystore.listIdentities;
|
|
12412
|
-
if (typeof listFn !== "function") {
|
|
12413
|
-
this._clientLog.debug(`listIdentities exit: elapsed=${Date.now() - tStart}ms count=0 reason=keystore_no_list`);
|
|
12414
|
-
return [];
|
|
12415
|
-
}
|
|
12416
|
-
const aids = await listFn.call(this._keystore);
|
|
12417
|
-
const summaries = [];
|
|
12418
|
-
for (const aid of [...aids].sort()) {
|
|
12419
|
-
const identity = await this._keystore.loadIdentity(aid);
|
|
12420
|
-
if (!identity || !identity.private_key_pem) continue;
|
|
12421
|
-
const summary = { aid };
|
|
12422
|
-
const loadMeta = this._keystore.loadMetadata;
|
|
12423
|
-
if (typeof loadMeta === "function") {
|
|
12424
|
-
const md = await loadMeta.call(this._keystore, aid);
|
|
12425
|
-
if (md && Object.keys(md).length > 0) {
|
|
12426
|
-
summary.metadata = md;
|
|
12427
|
-
}
|
|
12428
|
-
}
|
|
12429
|
-
if (!summary.metadata) {
|
|
12430
|
-
const metadata = {};
|
|
12431
|
-
for (const [key, value] of Object.entries(identity)) {
|
|
12432
|
-
if (!["aid", "private_key_pem", "public_key_der_b64", "curve", "cert"].includes(key)) {
|
|
12433
|
-
metadata[key] = value;
|
|
12434
|
-
}
|
|
12435
|
-
}
|
|
12436
|
-
if (Object.keys(metadata).length > 0) {
|
|
12437
|
-
summary.metadata = metadata;
|
|
12438
|
-
}
|
|
12439
|
-
}
|
|
12440
|
-
summaries.push(summary);
|
|
12441
|
-
}
|
|
12442
|
-
this._clientLog.debug(`listIdentities exit: elapsed=${Date.now() - tStart}ms count=${summaries.length}`);
|
|
12443
|
-
return summaries;
|
|
12444
|
-
} catch (err) {
|
|
12445
|
-
this._clientLog.debug(`listIdentities exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
12446
|
-
throw err;
|
|
12447
|
-
}
|
|
12448
|
-
}
|
|
12449
11404
|
/** 关闭连接 */
|
|
12450
11405
|
async close() {
|
|
12451
11406
|
const tStart = Date.now();
|
|
@@ -12470,7 +11425,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12470
11425
|
}
|
|
12471
11426
|
await this._transport.close();
|
|
12472
11427
|
this._state = "closed";
|
|
12473
|
-
await this._dispatcher.publish("
|
|
11428
|
+
await this._dispatcher.publish("state_change", { state: this._publicState(this._state) });
|
|
12474
11429
|
this._resetSeqTrackingState();
|
|
12475
11430
|
this._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
|
|
12476
11431
|
}
|
|
@@ -12504,6 +11459,10 @@ var _AUNClient = class _AUNClient {
|
|
|
12504
11459
|
throw new PermissionError(`legacy E2EE method is removed in this SDK: ${method}`);
|
|
12505
11460
|
}
|
|
12506
11461
|
const p = { ...params ?? {} };
|
|
11462
|
+
if (this._instanceProtectedHeaders && PROTECTED_HEADERS_METHODS.has(method)) {
|
|
11463
|
+
const existing = isJsonObject(p.protected_headers) ? p.protected_headers : {};
|
|
11464
|
+
p.protected_headers = { ...this._instanceProtectedHeaders, ...existing };
|
|
11465
|
+
}
|
|
12507
11466
|
if (method === "message.send" || method === "group.send") {
|
|
12508
11467
|
this._normalizeOutboundMessagePayload(p, method);
|
|
12509
11468
|
}
|
|
@@ -12531,7 +11490,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12531
11490
|
throw new StateError("V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)");
|
|
12532
11491
|
}
|
|
12533
11492
|
this._clientLog.debug("call route: message.send \u2192 V2 encrypted send");
|
|
12534
|
-
return await this.
|
|
11493
|
+
return await this._sendV2(String(p.to ?? ""), p.payload ?? {}, {
|
|
12535
11494
|
messageId: String(p.message_id ?? "") || void 0,
|
|
12536
11495
|
timestamp: p.timestamp,
|
|
12537
11496
|
protectedHeaders: this._protectedHeadersFromParams(p),
|
|
@@ -12548,7 +11507,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12548
11507
|
throw new StateError("V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)");
|
|
12549
11508
|
}
|
|
12550
11509
|
this._clientLog.debug("call route: group.send \u2192 V2 encrypted send");
|
|
12551
|
-
return await this.
|
|
11510
|
+
return await this._sendGroupV2(String(p.group_id ?? ""), p.payload ?? {}, {
|
|
12552
11511
|
messageId: String(p.message_id ?? "") || void 0,
|
|
12553
11512
|
timestamp: p.timestamp,
|
|
12554
11513
|
protectedHeaders: this._protectedHeadersFromParams(p),
|
|
@@ -12594,16 +11553,16 @@ var _AUNClient = class _AUNClient {
|
|
|
12594
11553
|
async _callImplInner(method, p) {
|
|
12595
11554
|
if (method === "message.pull" && this._v2Session) {
|
|
12596
11555
|
this._clientLog.debug("call route: message.pull \u2192 V2 pull");
|
|
12597
|
-
const messages = await this.
|
|
11556
|
+
const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
|
|
12598
11557
|
return { messages };
|
|
12599
11558
|
}
|
|
12600
11559
|
if (method === "message.ack" && this._v2Session) {
|
|
12601
11560
|
this._clientLog.debug("call route: message.ack \u2192 V2 ack");
|
|
12602
|
-
return await this.
|
|
11561
|
+
return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || void 0);
|
|
12603
11562
|
}
|
|
12604
11563
|
if (method === "group.pull" && this._v2Session && p.group_id) {
|
|
12605
11564
|
this._clientLog.debug("call route: group.pull \u2192 V2 pull");
|
|
12606
|
-
const messages = await this.
|
|
11565
|
+
const messages = await this._pullGroupV2(
|
|
12607
11566
|
String(p.group_id),
|
|
12608
11567
|
Number(p.after_seq ?? p.after_message_seq ?? 0) || 0,
|
|
12609
11568
|
Number(p.limit ?? 50) || 50
|
|
@@ -12612,7 +11571,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12612
11571
|
}
|
|
12613
11572
|
if (method === "group.ack_messages" && this._v2Session && p.group_id) {
|
|
12614
11573
|
this._clientLog.debug("call route: group.ack_messages \u2192 V2 ack");
|
|
12615
|
-
return await this.
|
|
11574
|
+
return await this._ackGroupV2(
|
|
12616
11575
|
String(p.group_id),
|
|
12617
11576
|
Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || void 0
|
|
12618
11577
|
);
|
|
@@ -12730,16 +11689,6 @@ var _AUNClient = class _AUNClient {
|
|
|
12730
11689
|
}
|
|
12731
11690
|
return await this._transport.call(method, p);
|
|
12732
11691
|
}
|
|
12733
|
-
// ── 便利方法 ──────────────────────────────────────
|
|
12734
|
-
async ping(params) {
|
|
12735
|
-
return this.meta.ping(params);
|
|
12736
|
-
}
|
|
12737
|
-
async status(params) {
|
|
12738
|
-
return this.meta.status(params);
|
|
12739
|
-
}
|
|
12740
|
-
async trustRoots(params) {
|
|
12741
|
-
return this.meta.trustRoots(params);
|
|
12742
|
-
}
|
|
12743
11692
|
// ── 事件 ──────────────────────────────────────────
|
|
12744
11693
|
/**
|
|
12745
11694
|
* 订阅事件。
|
|
@@ -12871,7 +11820,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12871
11820
|
this._gapFillDone.add(dedupKey);
|
|
12872
11821
|
try {
|
|
12873
11822
|
this._clientLog.debug(`_onRawGroupV2MessageCreated -> group.v2.pull group=${groupId} after_seq=${afterSeq}`);
|
|
12874
|
-
const messages = await this.
|
|
11823
|
+
const messages = await this._pullGroupV2(groupId, afterSeq, 50);
|
|
12875
11824
|
this._clientLog.debug(`_onRawGroupV2MessageCreated pulled ${messages.length} msgs for group=${groupId}`);
|
|
12876
11825
|
} finally {
|
|
12877
11826
|
this._gapFillDone.delete(dedupKey);
|
|
@@ -14200,8 +13149,8 @@ var _AUNClient = class _AUNClient {
|
|
|
14200
13149
|
}
|
|
14201
13150
|
this._state = "connected";
|
|
14202
13151
|
this._connectedAt = Date.now();
|
|
14203
|
-
await this._dispatcher.publish("
|
|
14204
|
-
state: this._state,
|
|
13152
|
+
await this._dispatcher.publish("state_change", {
|
|
13153
|
+
state: this._publicState(this._state),
|
|
14205
13154
|
gateway: gatewayUrl
|
|
14206
13155
|
});
|
|
14207
13156
|
if (this._seqTrackerContext !== this._currentSeqTrackerContext()) {
|
|
@@ -14210,7 +13159,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14210
13159
|
}
|
|
14211
13160
|
this._startBackgroundTasks();
|
|
14212
13161
|
try {
|
|
14213
|
-
await this.
|
|
13162
|
+
await this._initV2Session();
|
|
14214
13163
|
} catch (exc) {
|
|
14215
13164
|
this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
|
|
14216
13165
|
}
|
|
@@ -14225,6 +13174,48 @@ var _AUNClient = class _AUNClient {
|
|
|
14225
13174
|
const gateways = this._resolveGateways(params);
|
|
14226
13175
|
return gateways[0];
|
|
14227
13176
|
}
|
|
13177
|
+
async _resolveGatewayForAid(aid) {
|
|
13178
|
+
const target = String(aid ?? this._aid ?? "").trim();
|
|
13179
|
+
if (!target) throw new StateError("gateway discovery requires a loaded AID");
|
|
13180
|
+
if (this._gatewayUrl) return this._gatewayUrl;
|
|
13181
|
+
try {
|
|
13182
|
+
const getMetadata = this._keystore.getMetadata;
|
|
13183
|
+
const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this._keystore, target, "gateway_url") ?? "").trim() : "";
|
|
13184
|
+
if (raw) {
|
|
13185
|
+
const gateway = raw.startsWith('"') && raw.endsWith('"') ? String(JSON.parse(raw)).trim() : raw;
|
|
13186
|
+
if (gateway) {
|
|
13187
|
+
this._gatewayUrl = gateway;
|
|
13188
|
+
return gateway;
|
|
13189
|
+
}
|
|
13190
|
+
}
|
|
13191
|
+
} catch {
|
|
13192
|
+
}
|
|
13193
|
+
const dotIdx = target.indexOf(".");
|
|
13194
|
+
const issuerDomain = dotIdx >= 0 ? target.slice(dotIdx + 1) : target;
|
|
13195
|
+
const portSuffix = "";
|
|
13196
|
+
const candidates = [
|
|
13197
|
+
`https://${target}${portSuffix}/.well-known/aun-gateway`,
|
|
13198
|
+
`https://gateway.${issuerDomain}${portSuffix}/.well-known/aun-gateway`
|
|
13199
|
+
];
|
|
13200
|
+
let lastError = null;
|
|
13201
|
+
for (const url of candidates) {
|
|
13202
|
+
try {
|
|
13203
|
+
const gateway = await this._discovery.discover(url);
|
|
13204
|
+
this._gatewayUrl = gateway;
|
|
13205
|
+
try {
|
|
13206
|
+
const setMetadata = this._keystore.setMetadata;
|
|
13207
|
+
if (typeof setMetadata === "function") {
|
|
13208
|
+
await setMetadata.call(this._keystore, target, "gateway_url", gateway);
|
|
13209
|
+
}
|
|
13210
|
+
} catch {
|
|
13211
|
+
}
|
|
13212
|
+
return gateway;
|
|
13213
|
+
} catch (err) {
|
|
13214
|
+
lastError = err;
|
|
13215
|
+
}
|
|
13216
|
+
}
|
|
13217
|
+
throw lastError instanceof Error ? lastError : new ConnectionError(`gateway discovery failed for ${target}`);
|
|
13218
|
+
}
|
|
14228
13219
|
_resolveGateways(params) {
|
|
14229
13220
|
const topology = isJsonObject(params.topology) ? params.topology : null;
|
|
14230
13221
|
if (topology) {
|
|
@@ -14271,10 +13262,10 @@ var _AUNClient = class _AUNClient {
|
|
|
14271
13262
|
_normalizeConnectParams(params) {
|
|
14272
13263
|
const request = { ...params };
|
|
14273
13264
|
const accessToken = String(request.access_token ?? "");
|
|
14274
|
-
if (!accessToken) throw new StateError("connect requires non-empty access_token");
|
|
14275
13265
|
const gateway = String(request.gateway ?? this._gatewayUrl ?? "");
|
|
14276
13266
|
if (!gateway) throw new StateError("connect requires non-empty gateway");
|
|
14277
|
-
request.access_token = accessToken;
|
|
13267
|
+
if (accessToken) request.access_token = accessToken;
|
|
13268
|
+
else delete request.access_token;
|
|
14278
13269
|
request.gateway = gateway;
|
|
14279
13270
|
request.device_id = this._deviceId;
|
|
14280
13271
|
request.slot_id = normalizeInstanceId(request.slot_id ?? this._slotId, "slot_id", { allowEmpty: true });
|
|
@@ -14470,6 +13461,10 @@ var _AUNClient = class _AUNClient {
|
|
|
14470
13461
|
}
|
|
14471
13462
|
try {
|
|
14472
13463
|
identity = await this._auth.refreshCachedTokens(this._gatewayUrl, identity);
|
|
13464
|
+
if (this._state !== "connected") {
|
|
13465
|
+
scheduleRefresh();
|
|
13466
|
+
return;
|
|
13467
|
+
}
|
|
14473
13468
|
this._identity = identity;
|
|
14474
13469
|
if (this._sessionParams && identity.access_token) {
|
|
14475
13470
|
this._sessionParams.access_token = identity.access_token;
|
|
@@ -14600,8 +13595,8 @@ var _AUNClient = class _AUNClient {
|
|
|
14600
13595
|
if (this._closing || this._state === "closed") return;
|
|
14601
13596
|
this._state = "disconnected";
|
|
14602
13597
|
this._stopBackgroundTasks();
|
|
14603
|
-
await this._dispatcher.publish("
|
|
14604
|
-
state: this._state,
|
|
13598
|
+
await this._dispatcher.publish("state_change", {
|
|
13599
|
+
state: this._publicState(this._state),
|
|
14605
13600
|
error
|
|
14606
13601
|
});
|
|
14607
13602
|
if (!this._sessionOptions.auto_reconnect) return;
|
|
@@ -14612,7 +13607,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14612
13607
|
this._clientLog.warn(`suppress auto-reconnect: ${reason}`);
|
|
14613
13608
|
const disconnectInfo = this._lastDisconnectInfo ?? {};
|
|
14614
13609
|
const eventPayload = {
|
|
14615
|
-
state: this._state,
|
|
13610
|
+
state: this._publicState(this._state),
|
|
14616
13611
|
error,
|
|
14617
13612
|
reason
|
|
14618
13613
|
};
|
|
@@ -14623,7 +13618,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14623
13618
|
if (disconnectInfo.code !== void 0 && disconnectInfo.code !== null) {
|
|
14624
13619
|
eventPayload.code = disconnectInfo.code;
|
|
14625
13620
|
}
|
|
14626
|
-
await this._dispatcher.publish("
|
|
13621
|
+
await this._dispatcher.publish("state_change", eventPayload);
|
|
14627
13622
|
return;
|
|
14628
13623
|
}
|
|
14629
13624
|
const serverInitiated = closeCode !== void 0 && closeCode !== 1e3 && closeCode !== 1006;
|
|
@@ -14645,32 +13640,47 @@ var _AUNClient = class _AUNClient {
|
|
|
14645
13640
|
serverInitiated ? 16 : 1,
|
|
14646
13641
|
maxBaseDelay
|
|
14647
13642
|
);
|
|
13643
|
+
this._retryAttempt = 0;
|
|
13644
|
+
this._retryMaxAttempts = maxAttempts;
|
|
14648
13645
|
for (let attempt = 1; !this._reconnectAbort?.signal.aborted; attempt++) {
|
|
14649
13646
|
if (maxAttempts > 0 && attempt > maxAttempts) {
|
|
14650
13647
|
this._state = "terminal_failed";
|
|
13648
|
+
this._nextRetryAt = null;
|
|
14651
13649
|
this._reconnectActive = false;
|
|
14652
13650
|
this._reconnectAbort = null;
|
|
14653
|
-
await this._dispatcher.publish("
|
|
14654
|
-
state: this._state,
|
|
13651
|
+
await this._dispatcher.publish("state_change", {
|
|
13652
|
+
state: this._publicState(this._state),
|
|
14655
13653
|
attempt: attempt - 1,
|
|
14656
13654
|
reason: "max_attempts_exhausted"
|
|
14657
13655
|
});
|
|
14658
13656
|
return;
|
|
14659
13657
|
}
|
|
14660
|
-
this.
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
13658
|
+
this._retryAttempt = attempt;
|
|
13659
|
+
const sleepMs = reconnectSleepDelaySeconds(delay, maxBaseDelay) * 1e3;
|
|
13660
|
+
this._nextRetryAt = new Date(Date.now() + sleepMs);
|
|
13661
|
+
this._state = "retry_backoff";
|
|
13662
|
+
await this._dispatcher.publish("state_change", {
|
|
13663
|
+
state: this._publicState(this._state),
|
|
13664
|
+
attempt,
|
|
13665
|
+
next_retry_at: this._nextRetryAt.getTime() / 1e3
|
|
14664
13666
|
});
|
|
14665
13667
|
try {
|
|
14666
|
-
await this._sleep(
|
|
13668
|
+
await this._sleep(sleepMs);
|
|
13669
|
+
this._nextRetryAt = null;
|
|
14667
13670
|
if (this._reconnectAbort?.signal.aborted) {
|
|
14668
13671
|
this._reconnectActive = false;
|
|
14669
13672
|
return;
|
|
14670
13673
|
}
|
|
13674
|
+
this._state = "reconnecting";
|
|
13675
|
+
await this._dispatcher.publish("state_change", {
|
|
13676
|
+
state: this._publicState(this._state),
|
|
13677
|
+
attempt
|
|
13678
|
+
});
|
|
14671
13679
|
if (this._gatewayUrl) {
|
|
14672
13680
|
const healthy = await this._discovery.checkHealth(this._gatewayUrl, 5e3);
|
|
14673
13681
|
if (!healthy) {
|
|
13682
|
+
this._lastError = new Error("gateway health check failed");
|
|
13683
|
+
this._lastErrorCode = "gateway_unhealthy";
|
|
14674
13684
|
delay = Math.min(delay * 2, maxBaseDelay);
|
|
14675
13685
|
continue;
|
|
14676
13686
|
}
|
|
@@ -14680,20 +13690,26 @@ var _AUNClient = class _AUNClient {
|
|
|
14680
13690
|
throw new StateError("missing connect params for reconnect");
|
|
14681
13691
|
}
|
|
14682
13692
|
await this._connectOnce(this._sessionParams, true);
|
|
13693
|
+
this._lastError = null;
|
|
13694
|
+
this._lastErrorCode = null;
|
|
13695
|
+
this._nextRetryAt = null;
|
|
14683
13696
|
this._reconnectActive = false;
|
|
14684
13697
|
this._reconnectAbort = null;
|
|
14685
13698
|
return;
|
|
14686
13699
|
} catch (exc) {
|
|
13700
|
+
this._lastError = exc instanceof Error ? exc : new Error(String(exc));
|
|
13701
|
+
this._lastErrorCode = "reconnect_failed";
|
|
14687
13702
|
await this._dispatcher.publish("connection.error", {
|
|
14688
13703
|
error: formatCaughtError(exc),
|
|
14689
13704
|
attempt
|
|
14690
13705
|
});
|
|
14691
13706
|
if (!this._shouldRetryReconnect(exc)) {
|
|
14692
13707
|
this._state = "terminal_failed";
|
|
13708
|
+
this._nextRetryAt = null;
|
|
14693
13709
|
this._reconnectActive = false;
|
|
14694
13710
|
this._reconnectAbort = null;
|
|
14695
|
-
await this._dispatcher.publish("
|
|
14696
|
-
state: this._state,
|
|
13711
|
+
await this._dispatcher.publish("state_change", {
|
|
13712
|
+
state: this._publicState(this._state),
|
|
14697
13713
|
error: formatCaughtError(exc),
|
|
14698
13714
|
attempt
|
|
14699
13715
|
});
|
|
@@ -15030,7 +14046,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15030
14046
|
*
|
|
15031
14047
|
* connect 成功后自动调用,可幂等手动调用。
|
|
15032
14048
|
*/
|
|
15033
|
-
async
|
|
14049
|
+
async _initV2Session() {
|
|
15034
14050
|
if (!this._aid) return;
|
|
15035
14051
|
let identity = this._identity;
|
|
15036
14052
|
if (!identity?.private_key_pem) {
|
|
@@ -15319,7 +14335,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15319
14335
|
* @param opts 可选 messageId / timestamp(与 Python 行为一致)
|
|
15320
14336
|
* @returns 服务端响应
|
|
15321
14337
|
*/
|
|
15322
|
-
async
|
|
14338
|
+
async _sendV2(to, payload, opts) {
|
|
15323
14339
|
if (!this._v2Session) {
|
|
15324
14340
|
throw new StateError("V2 session not initialized (not connected?)");
|
|
15325
14341
|
}
|
|
@@ -15360,7 +14376,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15360
14376
|
* @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
|
|
15361
14377
|
* @param limit 最多拉取条数
|
|
15362
14378
|
*/
|
|
15363
|
-
async
|
|
14379
|
+
async _pullV2(afterSeq = 0, limit = 50, opts) {
|
|
15364
14380
|
if (!this._v2Session) {
|
|
15365
14381
|
throw new StateError("V2 session not initialized (not connected?)");
|
|
15366
14382
|
}
|
|
@@ -15444,7 +14460,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15444
14460
|
this._saveSeqTrackerState();
|
|
15445
14461
|
}
|
|
15446
14462
|
if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
|
|
15447
|
-
this._safeAsync(this.
|
|
14463
|
+
this._safeAsync(this._ackV2(ackSeq).then(() => void 0));
|
|
15448
14464
|
}
|
|
15449
14465
|
}
|
|
15450
14466
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
@@ -15461,7 +14477,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15461
14477
|
*
|
|
15462
14478
|
* @param upToSeq 确认到此 seq;省略则用当前 contiguous
|
|
15463
14479
|
*/
|
|
15464
|
-
async
|
|
14480
|
+
async _ackV2(upToSeq) {
|
|
15465
14481
|
const ns = this._aid ? `p2p:${this._aid}` : "";
|
|
15466
14482
|
let seq = upToSeq ?? (ns ? this._seqTracker.getContiguousSeq(ns) : 0);
|
|
15467
14483
|
if (seq <= 0) return { acked: 0 };
|
|
@@ -15678,7 +14694,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15678
14694
|
* @param opts 可选 messageId / timestamp
|
|
15679
14695
|
* @returns 服务端响应
|
|
15680
14696
|
*/
|
|
15681
|
-
async
|
|
14697
|
+
async _sendGroupV2(groupId, payload, opts) {
|
|
15682
14698
|
if (!this._v2Session) {
|
|
15683
14699
|
throw new StateError("V2 session not initialized (not connected?)");
|
|
15684
14700
|
}
|
|
@@ -15733,7 +14749,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15733
14749
|
}
|
|
15734
14750
|
}
|
|
15735
14751
|
async _pullGroupV2Internal(params) {
|
|
15736
|
-
await this.
|
|
14752
|
+
await this._pullGroupV2(params.group_id, params.after_seq, params.limit);
|
|
15737
14753
|
}
|
|
15738
14754
|
/**
|
|
15739
14755
|
* 拉取并解密 V2 Group 消息。
|
|
@@ -15742,7 +14758,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15742
14758
|
* @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
|
|
15743
14759
|
* @param limit 最多拉取条数
|
|
15744
14760
|
*/
|
|
15745
|
-
async
|
|
14761
|
+
async _pullGroupV2(groupId, afterSeq = 0, limit = 50) {
|
|
15746
14762
|
if (!this._v2Session) {
|
|
15747
14763
|
throw new StateError("V2 session not initialized (not connected?)");
|
|
15748
14764
|
}
|
|
@@ -15835,7 +14851,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15835
14851
|
this._saveSeqTrackerState();
|
|
15836
14852
|
}
|
|
15837
14853
|
if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
|
|
15838
|
-
this._safeAsync(this.
|
|
14854
|
+
this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => void 0));
|
|
15839
14855
|
}
|
|
15840
14856
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
15841
14857
|
if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false) break;
|
|
@@ -15852,7 +14868,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15852
14868
|
* @param groupId 群 ID
|
|
15853
14869
|
* @param upToSeq 确认到此 seq;省略则用当前 contiguous
|
|
15854
14870
|
*/
|
|
15855
|
-
async
|
|
14871
|
+
async _ackGroupV2(groupId, upToSeq) {
|
|
15856
14872
|
const gid = normalizeGroupId(groupId) || String(groupId ?? "").trim();
|
|
15857
14873
|
if (!gid) throw new ValidationError("group.ack_messages requires group_id");
|
|
15858
14874
|
const ns = `group:${gid}`;
|
|
@@ -16838,7 +15854,7 @@ var _AUNClient = class _AUNClient {
|
|
|
16838
15854
|
try {
|
|
16839
15855
|
do {
|
|
16840
15856
|
this._v2PullPending = false;
|
|
16841
|
-
await this.
|
|
15857
|
+
await this._pullV2();
|
|
16842
15858
|
const newContig = ns ? this._seqTracker.getContiguousSeq(ns) : -1;
|
|
16843
15859
|
this._clientLog.debug(
|
|
16844
15860
|
`_onV2PushNotification pull done: contiguous_seq=${contigBefore}->${newContig} (push_seq=${pushSeq || "null"})`
|
|
@@ -16963,6 +15979,720 @@ function _uuidV4() {
|
|
|
16963
15979
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
16964
15980
|
}
|
|
16965
15981
|
|
|
15982
|
+
// src/aid-store.ts
|
|
15983
|
+
init_crypto();
|
|
15984
|
+
function _derReadLength(data, offset) {
|
|
15985
|
+
if (offset >= data.length) return null;
|
|
15986
|
+
const first = data[offset];
|
|
15987
|
+
if (first < 128) return { value: first, lenBytes: 1 };
|
|
15988
|
+
const n = first & 127;
|
|
15989
|
+
if (n === 0 || n > 4) return null;
|
|
15990
|
+
let v = 0;
|
|
15991
|
+
for (let i = 0; i < n; i++) v = v << 8 | data[offset + 1 + i];
|
|
15992
|
+
return { value: v, lenBytes: 1 + n };
|
|
15993
|
+
}
|
|
15994
|
+
function _derSkipTlv(data, offset) {
|
|
15995
|
+
const len = _derReadLength(data, offset + 1);
|
|
15996
|
+
if (!len) return null;
|
|
15997
|
+
return offset + 1 + len.lenBytes + len.value;
|
|
15998
|
+
}
|
|
15999
|
+
function parseCertValidity(certPem) {
|
|
16000
|
+
try {
|
|
16001
|
+
let parseTime2 = function(offset) {
|
|
16002
|
+
const tag = der[offset];
|
|
16003
|
+
const len = _derReadLength(der, offset + 1);
|
|
16004
|
+
if (!len) return null;
|
|
16005
|
+
const raw = new TextDecoder().decode(der.slice(offset + 1 + len.lenBytes, offset + 1 + len.lenBytes + len.value));
|
|
16006
|
+
if (tag === 23) {
|
|
16007
|
+
const y = parseInt(raw.slice(0, 2), 10);
|
|
16008
|
+
const year = y >= 50 ? 1900 + y : 2e3 + y;
|
|
16009
|
+
return Date.UTC(
|
|
16010
|
+
year,
|
|
16011
|
+
parseInt(raw.slice(2, 4), 10) - 1,
|
|
16012
|
+
parseInt(raw.slice(4, 6), 10),
|
|
16013
|
+
parseInt(raw.slice(6, 8), 10),
|
|
16014
|
+
parseInt(raw.slice(8, 10), 10),
|
|
16015
|
+
parseInt(raw.slice(10, 12), 10)
|
|
16016
|
+
);
|
|
16017
|
+
} else if (tag === 24) {
|
|
16018
|
+
return Date.UTC(
|
|
16019
|
+
parseInt(raw.slice(0, 4), 10),
|
|
16020
|
+
parseInt(raw.slice(4, 6), 10) - 1,
|
|
16021
|
+
parseInt(raw.slice(6, 8), 10),
|
|
16022
|
+
parseInt(raw.slice(8, 10), 10),
|
|
16023
|
+
parseInt(raw.slice(10, 12), 10),
|
|
16024
|
+
parseInt(raw.slice(12, 14), 10)
|
|
16025
|
+
);
|
|
16026
|
+
}
|
|
16027
|
+
return null;
|
|
16028
|
+
};
|
|
16029
|
+
var parseTime = parseTime2;
|
|
16030
|
+
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
16031
|
+
const cert = _derSkipTlv(der, -1);
|
|
16032
|
+
const certLen = _derReadLength(der, 1);
|
|
16033
|
+
if (!certLen || der[0] !== 48) return null;
|
|
16034
|
+
const tbsStart = 1 + certLen.lenBytes;
|
|
16035
|
+
if (der[tbsStart] !== 48) return null;
|
|
16036
|
+
const tbsLen = _derReadLength(der, tbsStart + 1);
|
|
16037
|
+
if (!tbsLen) return null;
|
|
16038
|
+
let pos = tbsStart + 1 + tbsLen.lenBytes;
|
|
16039
|
+
if (der[pos] === 160) {
|
|
16040
|
+
const e = _derSkipTlv(der, pos);
|
|
16041
|
+
if (e === null) return null;
|
|
16042
|
+
pos = e;
|
|
16043
|
+
}
|
|
16044
|
+
{
|
|
16045
|
+
const e = _derSkipTlv(der, pos);
|
|
16046
|
+
if (e === null) return null;
|
|
16047
|
+
pos = e;
|
|
16048
|
+
}
|
|
16049
|
+
{
|
|
16050
|
+
const e = _derSkipTlv(der, pos);
|
|
16051
|
+
if (e === null) return null;
|
|
16052
|
+
pos = e;
|
|
16053
|
+
}
|
|
16054
|
+
{
|
|
16055
|
+
const e = _derSkipTlv(der, pos);
|
|
16056
|
+
if (e === null) return null;
|
|
16057
|
+
pos = e;
|
|
16058
|
+
}
|
|
16059
|
+
if (der[pos] !== 48) return null;
|
|
16060
|
+
const valLen = _derReadLength(der, pos + 1);
|
|
16061
|
+
if (!valLen) return null;
|
|
16062
|
+
pos = pos + 1 + valLen.lenBytes;
|
|
16063
|
+
const notBefore = parseTime2(pos);
|
|
16064
|
+
if (notBefore === null) return null;
|
|
16065
|
+
const nbLen = _derReadLength(der, pos + 1);
|
|
16066
|
+
if (!nbLen) return null;
|
|
16067
|
+
const notAfterOffset = pos + 1 + nbLen.lenBytes + nbLen.value;
|
|
16068
|
+
const notAfter = parseTime2(notAfterOffset);
|
|
16069
|
+
if (notAfter === null) return null;
|
|
16070
|
+
return { notBefore, notAfter };
|
|
16071
|
+
} catch {
|
|
16072
|
+
return null;
|
|
16073
|
+
}
|
|
16074
|
+
}
|
|
16075
|
+
function parseCertCN(certPem) {
|
|
16076
|
+
try {
|
|
16077
|
+
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
16078
|
+
if (der[0] !== 48) return null;
|
|
16079
|
+
const certLen = _derReadLength(der, 1);
|
|
16080
|
+
if (!certLen) return null;
|
|
16081
|
+
const tbsStart = 1 + certLen.lenBytes;
|
|
16082
|
+
if (der[tbsStart] !== 48) return null;
|
|
16083
|
+
const tbsLen = _derReadLength(der, tbsStart + 1);
|
|
16084
|
+
if (!tbsLen) return null;
|
|
16085
|
+
let pos = tbsStart + 1 + tbsLen.lenBytes;
|
|
16086
|
+
if (der[pos] === 160) {
|
|
16087
|
+
const e = _derSkipTlv(der, pos);
|
|
16088
|
+
if (e === null) return null;
|
|
16089
|
+
pos = e;
|
|
16090
|
+
}
|
|
16091
|
+
{
|
|
16092
|
+
const e = _derSkipTlv(der, pos);
|
|
16093
|
+
if (e === null) return null;
|
|
16094
|
+
pos = e;
|
|
16095
|
+
}
|
|
16096
|
+
{
|
|
16097
|
+
const e = _derSkipTlv(der, pos);
|
|
16098
|
+
if (e === null) return null;
|
|
16099
|
+
pos = e;
|
|
16100
|
+
}
|
|
16101
|
+
if (der[pos] !== 48) return null;
|
|
16102
|
+
const issuerLen = _derReadLength(der, pos + 1);
|
|
16103
|
+
if (!issuerLen) return null;
|
|
16104
|
+
const issuerEnd = pos + 1 + issuerLen.lenBytes + issuerLen.value;
|
|
16105
|
+
pos = pos + 1 + issuerLen.lenBytes;
|
|
16106
|
+
pos = issuerEnd;
|
|
16107
|
+
{
|
|
16108
|
+
const e = _derSkipTlv(der, pos);
|
|
16109
|
+
if (e === null) return null;
|
|
16110
|
+
pos = e;
|
|
16111
|
+
}
|
|
16112
|
+
if (der[pos] !== 48) return null;
|
|
16113
|
+
const subjectLen = _derReadLength(der, pos + 1);
|
|
16114
|
+
if (!subjectLen) return null;
|
|
16115
|
+
const subjectEnd = pos + 1 + subjectLen.lenBytes + subjectLen.value;
|
|
16116
|
+
pos = pos + 1 + subjectLen.lenBytes;
|
|
16117
|
+
while (pos < subjectEnd) {
|
|
16118
|
+
if (der[pos] !== 49) break;
|
|
16119
|
+
const setLen = _derReadLength(der, pos + 1);
|
|
16120
|
+
if (!setLen) break;
|
|
16121
|
+
const setEnd = pos + 1 + setLen.lenBytes + setLen.value;
|
|
16122
|
+
let rpos = pos + 1 + setLen.lenBytes;
|
|
16123
|
+
while (rpos < setEnd) {
|
|
16124
|
+
if (der[rpos] !== 48) break;
|
|
16125
|
+
const seqLen = _derReadLength(der, rpos + 1);
|
|
16126
|
+
if (!seqLen) break;
|
|
16127
|
+
const seqEnd = rpos + 1 + seqLen.lenBytes + seqLen.value;
|
|
16128
|
+
let apos = rpos + 1 + seqLen.lenBytes;
|
|
16129
|
+
if (der[apos] !== 6) {
|
|
16130
|
+
rpos = seqEnd;
|
|
16131
|
+
continue;
|
|
16132
|
+
}
|
|
16133
|
+
const oidLen = _derReadLength(der, apos + 1);
|
|
16134
|
+
if (!oidLen) {
|
|
16135
|
+
rpos = seqEnd;
|
|
16136
|
+
continue;
|
|
16137
|
+
}
|
|
16138
|
+
const oidBytes = der.slice(apos + 1 + oidLen.lenBytes, apos + 1 + oidLen.lenBytes + oidLen.value);
|
|
16139
|
+
const isCN = oidBytes.length === 3 && oidBytes[0] === 85 && oidBytes[1] === 4 && oidBytes[2] === 3;
|
|
16140
|
+
apos = apos + 1 + oidLen.lenBytes + oidLen.value;
|
|
16141
|
+
if (isCN && apos < seqEnd) {
|
|
16142
|
+
const valLen = _derReadLength(der, apos + 1);
|
|
16143
|
+
if (valLen) {
|
|
16144
|
+
return new TextDecoder().decode(der.slice(apos + 1 + valLen.lenBytes, apos + 1 + valLen.lenBytes + valLen.value));
|
|
16145
|
+
}
|
|
16146
|
+
}
|
|
16147
|
+
rpos = seqEnd;
|
|
16148
|
+
}
|
|
16149
|
+
pos = setEnd;
|
|
16150
|
+
}
|
|
16151
|
+
return null;
|
|
16152
|
+
} catch {
|
|
16153
|
+
return null;
|
|
16154
|
+
}
|
|
16155
|
+
}
|
|
16156
|
+
function normalizeSlotId(slotId) {
|
|
16157
|
+
const value = String(slotId ?? "default").trim();
|
|
16158
|
+
return value || "default";
|
|
16159
|
+
}
|
|
16160
|
+
function issuerFromAid(aid) {
|
|
16161
|
+
const target = String(aid ?? "").trim();
|
|
16162
|
+
const dotIdx = target.indexOf(".");
|
|
16163
|
+
return dotIdx >= 0 ? target.slice(dotIdx + 1) : target;
|
|
16164
|
+
}
|
|
16165
|
+
function validateRegisterAidName(aid) {
|
|
16166
|
+
if (!aid) throw new ValidationError("AIDStore.register requires 'aid'");
|
|
16167
|
+
const name = aid.includes(".") ? aid.split(".")[0] : aid;
|
|
16168
|
+
if (!/^[a-z0-9_][a-z0-9_-]{3,63}$/.test(name)) {
|
|
16169
|
+
throw new ValidationError(
|
|
16170
|
+
`Invalid AID name '${name}': must be 4-64 characters, only [a-z0-9_-], cannot start with '-'`
|
|
16171
|
+
);
|
|
16172
|
+
}
|
|
16173
|
+
if (name.startsWith("guest")) {
|
|
16174
|
+
throw new ValidationError("AID name must not start with 'guest'");
|
|
16175
|
+
}
|
|
16176
|
+
}
|
|
16177
|
+
function gatewayHttpUrl3(gatewayUrl, path) {
|
|
16178
|
+
const parsed = new URL(gatewayUrl);
|
|
16179
|
+
parsed.protocol = parsed.protocol === "wss:" ? "https:" : "http:";
|
|
16180
|
+
parsed.pathname = path;
|
|
16181
|
+
parsed.search = "";
|
|
16182
|
+
parsed.hash = "";
|
|
16183
|
+
return parsed.toString();
|
|
16184
|
+
}
|
|
16185
|
+
function pkiCertUrl(gatewayUrl, aid) {
|
|
16186
|
+
return gatewayHttpUrl3(gatewayUrl, `/pki/cert/${encodeURIComponent(aid)}`);
|
|
16187
|
+
}
|
|
16188
|
+
function agentMdUrl(aid, gatewayUrl) {
|
|
16189
|
+
const scheme = String(gatewayUrl ?? "").trim().toLowerCase().startsWith("ws://") ? "http" : "https";
|
|
16190
|
+
const host = String(aid ?? "").trim();
|
|
16191
|
+
return `${scheme}://${host}/agent.md`;
|
|
16192
|
+
}
|
|
16193
|
+
async function fetchWithTimeout2(input, init = {}, timeoutMs = 3e4) {
|
|
16194
|
+
const controller = new AbortController();
|
|
16195
|
+
const timer = globalThis.setTimeout(() => controller.abort(), timeoutMs);
|
|
16196
|
+
try {
|
|
16197
|
+
return await fetch(input, { ...init, signal: controller.signal });
|
|
16198
|
+
} finally {
|
|
16199
|
+
globalThis.clearTimeout(timer);
|
|
16200
|
+
}
|
|
16201
|
+
}
|
|
16202
|
+
function resultError(result) {
|
|
16203
|
+
return result.ok ? null : result.error;
|
|
16204
|
+
}
|
|
16205
|
+
var AIDStore = class {
|
|
16206
|
+
constructor(opts) {
|
|
16207
|
+
__publicField(this, "aunPath");
|
|
16208
|
+
__publicField(this, "deviceId");
|
|
16209
|
+
__publicField(this, "slotId");
|
|
16210
|
+
__publicField(this, "_encryptionSeed");
|
|
16211
|
+
__publicField(this, "_keystore");
|
|
16212
|
+
__publicField(this, "_auth");
|
|
16213
|
+
__publicField(this, "_crypto");
|
|
16214
|
+
__publicField(this, "_discovery");
|
|
16215
|
+
__publicField(this, "_verifySsl");
|
|
16216
|
+
__publicField(this, "_gatewayCache", /* @__PURE__ */ new Map());
|
|
16217
|
+
__publicField(this, "_agentMdCache", /* @__PURE__ */ new Map());
|
|
16218
|
+
this.aunPath = String(opts.aunPath ?? "");
|
|
16219
|
+
this._encryptionSeed = String(opts.encryptionSeed ?? "");
|
|
16220
|
+
this.deviceId = opts.deviceId ? normalizeInstanceId(opts.deviceId, "deviceId", { allowEmpty: true }) : getDeviceId();
|
|
16221
|
+
this.slotId = normalizeSlotId(opts.slotId);
|
|
16222
|
+
this._verifySsl = opts.verifySsl ?? true;
|
|
16223
|
+
this._keystore = new IndexedDBKeyStore({ encryptionSeed: this._encryptionSeed || void 0 });
|
|
16224
|
+
this._crypto = new CryptoProvider();
|
|
16225
|
+
this._discovery = new GatewayDiscovery();
|
|
16226
|
+
this._auth = new AuthFlow({
|
|
16227
|
+
keystore: this._keystore,
|
|
16228
|
+
crypto: this._crypto,
|
|
16229
|
+
deviceId: this.deviceId,
|
|
16230
|
+
slotId: this.slotId,
|
|
16231
|
+
rootCaPem: opts.rootCaPem ?? null,
|
|
16232
|
+
verifySsl: this._verifySsl
|
|
16233
|
+
});
|
|
16234
|
+
}
|
|
16235
|
+
close() {
|
|
16236
|
+
const close = this._keystore.close;
|
|
16237
|
+
if (typeof close === "function") close.call(this._keystore);
|
|
16238
|
+
}
|
|
16239
|
+
async load(aid) {
|
|
16240
|
+
const target = String(aid ?? "").trim();
|
|
16241
|
+
const certPem = await this._keystore.loadCert(target);
|
|
16242
|
+
if (!certPem) return resultErr(CERT_NOT_FOUND, `certificate not found for aid: ${target}`);
|
|
16243
|
+
let certPub = "";
|
|
16244
|
+
try {
|
|
16245
|
+
certPub = publicKeyDerB64(certPem);
|
|
16246
|
+
if (!certPub) return resultErr(CERT_PARSE_ERROR, `certificate parse failed for aid: ${target}`);
|
|
16247
|
+
} catch (exc) {
|
|
16248
|
+
return resultErr(CERT_PARSE_ERROR, `certificate parse failed for aid: ${target}`, exc);
|
|
16249
|
+
}
|
|
16250
|
+
const validity = parseCertValidity(certPem);
|
|
16251
|
+
if (validity) {
|
|
16252
|
+
const now = Date.now();
|
|
16253
|
+
if (now > validity.notAfter) {
|
|
16254
|
+
return resultErr(CERT_EXPIRED, `certificate expired for aid: ${target}`);
|
|
16255
|
+
}
|
|
16256
|
+
if (now < validity.notBefore) {
|
|
16257
|
+
return resultErr(CERT_NOT_YET_VALID, `certificate not yet valid for aid: ${target}`);
|
|
16258
|
+
}
|
|
16259
|
+
}
|
|
16260
|
+
const cn = parseCertCN(certPem);
|
|
16261
|
+
if (cn && cn !== target) {
|
|
16262
|
+
return resultErr(CERT_CHAIN_BROKEN, `certificate CN mismatch: expected ${target}, got ${cn}`);
|
|
16263
|
+
}
|
|
16264
|
+
let keyPair;
|
|
16265
|
+
try {
|
|
16266
|
+
keyPair = await this._keystore.loadKeyPair(target);
|
|
16267
|
+
} catch (exc) {
|
|
16268
|
+
return resultErr(PRIVATE_KEY_PARSE_ERROR, `private key load failed for aid: ${target}`, exc);
|
|
16269
|
+
}
|
|
16270
|
+
if (!keyPair?.private_key_pem) {
|
|
16271
|
+
return resultOk({
|
|
16272
|
+
aid: await AID.create({
|
|
16273
|
+
aid: target,
|
|
16274
|
+
aunPath: this.aunPath,
|
|
16275
|
+
certPem,
|
|
16276
|
+
privateKeyPem: null,
|
|
16277
|
+
certValid: true,
|
|
16278
|
+
privateKeyValid: false,
|
|
16279
|
+
deviceId: this.deviceId,
|
|
16280
|
+
slotId: this.slotId,
|
|
16281
|
+
verifySsl: this._verifySsl,
|
|
16282
|
+
rootCaPath: null,
|
|
16283
|
+
debug: false
|
|
16284
|
+
})
|
|
16285
|
+
});
|
|
16286
|
+
}
|
|
16287
|
+
try {
|
|
16288
|
+
const privateKey = await importPrivateKeyEcdsa(String(keyPair.private_key_pem));
|
|
16289
|
+
if (keyPair.public_key_der_b64 && keyPair.public_key_der_b64 !== certPub) {
|
|
16290
|
+
return resultErr(KEYPAIR_MISMATCH, `keypair public key mismatch for aid: ${target}`);
|
|
16291
|
+
}
|
|
16292
|
+
const probe = new TextEncoder().encode("aun-aidstore-private-key-self-test");
|
|
16293
|
+
const sig = await ecdsaSignDer(privateKey, probe);
|
|
16294
|
+
const pubKey = await importCertPublicKeyEcdsa(certPem);
|
|
16295
|
+
const ok = await ecdsaVerifyDer(pubKey, sig, probe);
|
|
16296
|
+
if (!ok) {
|
|
16297
|
+
return resultErr(KEYPAIR_MISMATCH, `private key does not match certificate for aid: ${target}`);
|
|
16298
|
+
}
|
|
16299
|
+
} catch (exc) {
|
|
16300
|
+
return resultErr(PRIVATE_KEY_PARSE_ERROR, `private key parse failed for aid: ${target}`, exc);
|
|
16301
|
+
}
|
|
16302
|
+
return resultOk({
|
|
16303
|
+
aid: await AID.create({
|
|
16304
|
+
aid: target,
|
|
16305
|
+
aunPath: this.aunPath,
|
|
16306
|
+
certPem,
|
|
16307
|
+
privateKeyPem: String(keyPair.private_key_pem),
|
|
16308
|
+
certValid: true,
|
|
16309
|
+
privateKeyValid: true,
|
|
16310
|
+
deviceId: this.deviceId,
|
|
16311
|
+
slotId: this.slotId,
|
|
16312
|
+
verifySsl: this._verifySsl,
|
|
16313
|
+
rootCaPath: null,
|
|
16314
|
+
debug: false
|
|
16315
|
+
})
|
|
16316
|
+
});
|
|
16317
|
+
}
|
|
16318
|
+
async list() {
|
|
16319
|
+
try {
|
|
16320
|
+
const aids = await this._keystore.listIdentities();
|
|
16321
|
+
const identities = [];
|
|
16322
|
+
for (const aid of aids.sort()) {
|
|
16323
|
+
const loaded = await this.load(aid);
|
|
16324
|
+
if (loaded.ok && loaded.data.aid.isPrivateKeyValid()) {
|
|
16325
|
+
identities.push({
|
|
16326
|
+
aid: loaded.data.aid.aid,
|
|
16327
|
+
certFingerprint: loaded.data.aid.certFingerprint,
|
|
16328
|
+
certNotAfter: loaded.data.aid.certNotAfter,
|
|
16329
|
+
certIssuer: loaded.data.aid.certIssuer
|
|
16330
|
+
});
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
16333
|
+
return resultOk({ identities });
|
|
16334
|
+
} catch (exc) {
|
|
16335
|
+
return resultErr("LIST_IDENTITIES_FAILED", String(exc), exc);
|
|
16336
|
+
}
|
|
16337
|
+
}
|
|
16338
|
+
async changeSeed(oldSeed, newSeed) {
|
|
16339
|
+
try {
|
|
16340
|
+
const changed = await this._keystore.changeSeed?.(oldSeed, newSeed);
|
|
16341
|
+
this._encryptionSeed = String(newSeed ?? "");
|
|
16342
|
+
return resultOk({ changed: true, count: changed?.privateKeysMigrated ?? changed?.migrated ?? 0 });
|
|
16343
|
+
} catch (exc) {
|
|
16344
|
+
return resultErr(PRIVATE_KEY_PARSE_ERROR, String(exc), exc);
|
|
16345
|
+
}
|
|
16346
|
+
}
|
|
16347
|
+
async register(aid) {
|
|
16348
|
+
const target = String(aid ?? "").trim();
|
|
16349
|
+
try {
|
|
16350
|
+
validateRegisterAidName(target);
|
|
16351
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
16352
|
+
await this._auth.registerAid(gatewayUrl, target);
|
|
16353
|
+
return resultOk({ registered: true });
|
|
16354
|
+
} catch (exc) {
|
|
16355
|
+
if (exc instanceof IdentityConflictError) {
|
|
16356
|
+
return resultErr(IDENTITY_CONFLICT, String(exc), exc);
|
|
16357
|
+
}
|
|
16358
|
+
if (exc instanceof ValidationError) {
|
|
16359
|
+
return resultErr(INVALID_AID_FORMAT, String(exc), exc);
|
|
16360
|
+
}
|
|
16361
|
+
const message = exc instanceof Error ? exc.message : String(exc);
|
|
16362
|
+
if (/network|connect|timeout|abort/i.test(message)) {
|
|
16363
|
+
return resultErr(NETWORK_ERROR, message, exc);
|
|
16364
|
+
}
|
|
16365
|
+
return resultErr(SERVER_ERROR, message, exc);
|
|
16366
|
+
}
|
|
16367
|
+
}
|
|
16368
|
+
async exists(aid) {
|
|
16369
|
+
const target = String(aid ?? "").trim();
|
|
16370
|
+
try {
|
|
16371
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
16372
|
+
const response = await fetchWithTimeout2(pkiCertUrl(gatewayUrl, target), { method: "HEAD" }, 1e4);
|
|
16373
|
+
if (response.status === 200) return resultOk({ exists: true });
|
|
16374
|
+
if (response.status === 404) return resultOk({ exists: false });
|
|
16375
|
+
return resultErr(NETWORK_ERROR, `unexpected PKI HEAD status ${response.status}`);
|
|
16376
|
+
} catch (exc) {
|
|
16377
|
+
return resultErr(NETWORK_ERROR, String(exc), exc);
|
|
16378
|
+
}
|
|
16379
|
+
}
|
|
16380
|
+
async resolve(aid, opts) {
|
|
16381
|
+
const target = String(aid ?? "").trim();
|
|
16382
|
+
const forceRefresh = !!opts?.forceRefresh;
|
|
16383
|
+
const skipAgentMd = !!opts?.skipAgentMd;
|
|
16384
|
+
try {
|
|
16385
|
+
let peer;
|
|
16386
|
+
let certFromCache = false;
|
|
16387
|
+
const cached = await this.load(target);
|
|
16388
|
+
if (cached.ok && !forceRefresh) {
|
|
16389
|
+
peer = cached.data.aid;
|
|
16390
|
+
certFromCache = true;
|
|
16391
|
+
} else {
|
|
16392
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
16393
|
+
const certPem = await this._auth.fetchPeerCert(gatewayUrl, target);
|
|
16394
|
+
if (!certPem) return resultErr(CERT_NOT_FOUND, `certificate not found for aid: ${target}`);
|
|
16395
|
+
await this._keystore.saveCert(target, certPem);
|
|
16396
|
+
const reloaded = await this.load(target);
|
|
16397
|
+
if (!reloaded.ok) return reloaded;
|
|
16398
|
+
peer = reloaded.data.aid;
|
|
16399
|
+
}
|
|
16400
|
+
const source = { cert_from_cache: certFromCache, agent_md_fetched: false };
|
|
16401
|
+
if (skipAgentMd) return resultOk({ aid: peer, source });
|
|
16402
|
+
const agentMd = await this.fetchAgentMd(target, opts?.timeout);
|
|
16403
|
+
if (!agentMd.ok) return agentMd;
|
|
16404
|
+
source.agent_md_fetched = true;
|
|
16405
|
+
return resultOk({ aid: peer, agent_md: agentMd.data, source });
|
|
16406
|
+
} catch (exc) {
|
|
16407
|
+
return resultErr(NETWORK_ERROR, String(exc), exc);
|
|
16408
|
+
}
|
|
16409
|
+
}
|
|
16410
|
+
async fetchAgentMd(aid, timeoutMs = 3e4) {
|
|
16411
|
+
const target = String(aid ?? "").trim();
|
|
16412
|
+
try {
|
|
16413
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
16414
|
+
const cached = this._agentMdCache.get(target) ?? {};
|
|
16415
|
+
const headers = { Accept: "text/markdown" };
|
|
16416
|
+
if (cached.etag) headers["If-None-Match"] = cached.etag;
|
|
16417
|
+
if (cached.lastModified) headers["If-Modified-Since"] = cached.lastModified;
|
|
16418
|
+
const response = await fetchWithTimeout2(agentMdUrl(target, gatewayUrl), {
|
|
16419
|
+
method: "GET",
|
|
16420
|
+
headers
|
|
16421
|
+
}, timeoutMs);
|
|
16422
|
+
let content = "";
|
|
16423
|
+
if (response.status === 304 && cached.content) {
|
|
16424
|
+
content = cached.content;
|
|
16425
|
+
} else if (response.status === 404) {
|
|
16426
|
+
return resultErr(AGENTMD_NOT_FOUND, `agent.md not found for aid: ${target}`);
|
|
16427
|
+
} else if (!response.ok) {
|
|
16428
|
+
return resultErr(NETWORK_ERROR, `download agent.md failed: HTTP ${response.status}`);
|
|
16429
|
+
} else {
|
|
16430
|
+
content = await response.text();
|
|
16431
|
+
}
|
|
16432
|
+
let peer;
|
|
16433
|
+
const loaded = await this.load(target);
|
|
16434
|
+
if (loaded.ok) {
|
|
16435
|
+
peer = loaded.data.aid;
|
|
16436
|
+
} else {
|
|
16437
|
+
const resolved = await this.resolve(target, { skipAgentMd: true });
|
|
16438
|
+
if (!resolved.ok) return resolved;
|
|
16439
|
+
peer = resolved.data.aid;
|
|
16440
|
+
}
|
|
16441
|
+
const verified = await peer.verifyAgentMd(content);
|
|
16442
|
+
if (!verified.ok) return verified;
|
|
16443
|
+
const signature = verified.data;
|
|
16444
|
+
const verification = { status: signature.status };
|
|
16445
|
+
if (signature.reason) verification.reason = signature.reason;
|
|
16446
|
+
const etag = String(response.headers?.get("ETag") ?? response.headers?.get("etag") ?? cached.etag ?? "").trim();
|
|
16447
|
+
const lastModified = String(response.headers?.get("Last-Modified") ?? response.headers?.get("last-modified") ?? cached.lastModified ?? "").trim();
|
|
16448
|
+
this._agentMdCache.set(target, { content, etag, lastModified, updatedAt: Date.now() });
|
|
16449
|
+
return resultOk({
|
|
16450
|
+
aid: target,
|
|
16451
|
+
content,
|
|
16452
|
+
verification,
|
|
16453
|
+
cert_pem: peer.certPem,
|
|
16454
|
+
etag,
|
|
16455
|
+
last_modified: lastModified
|
|
16456
|
+
});
|
|
16457
|
+
} catch (exc) {
|
|
16458
|
+
return resultErr(NETWORK_ERROR, String(exc), exc);
|
|
16459
|
+
}
|
|
16460
|
+
}
|
|
16461
|
+
async headAgentMd(aid) {
|
|
16462
|
+
const target = String(aid ?? "").trim();
|
|
16463
|
+
try {
|
|
16464
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
16465
|
+
const response = await fetchWithTimeout2(agentMdUrl(target, gatewayUrl), {
|
|
16466
|
+
method: "HEAD",
|
|
16467
|
+
headers: { Accept: "text/markdown" }
|
|
16468
|
+
}, 15e3);
|
|
16469
|
+
if (response.status === 404) {
|
|
16470
|
+
return resultErr(AGENTMD_NOT_FOUND, `agent.md not found for aid: ${target}`);
|
|
16471
|
+
}
|
|
16472
|
+
if (!response.ok) {
|
|
16473
|
+
return resultErr(NETWORK_ERROR, `head agent.md failed: HTTP ${response.status}`);
|
|
16474
|
+
}
|
|
16475
|
+
const etag = String(response.headers?.get("ETag") ?? response.headers?.get("etag") ?? "").trim();
|
|
16476
|
+
const lastModified = String(response.headers?.get("Last-Modified") ?? response.headers?.get("last-modified") ?? "").trim();
|
|
16477
|
+
const contentLength = Number.parseInt(String(response.headers?.get("Content-Length") ?? response.headers?.get("content-length") ?? "0"), 10) || 0;
|
|
16478
|
+
const cached = this._agentMdCache.get(target) ?? {};
|
|
16479
|
+
this._agentMdCache.set(target, { ...cached, etag, lastModified, updatedAt: Date.now() });
|
|
16480
|
+
return resultOk({
|
|
16481
|
+
aid: target,
|
|
16482
|
+
found: true,
|
|
16483
|
+
etag,
|
|
16484
|
+
last_modified: lastModified,
|
|
16485
|
+
content_length: contentLength
|
|
16486
|
+
});
|
|
16487
|
+
} catch (exc) {
|
|
16488
|
+
return resultErr(NETWORK_ERROR, String(exc), exc);
|
|
16489
|
+
}
|
|
16490
|
+
}
|
|
16491
|
+
async checkAgentMd(aid, ttlDays = 1) {
|
|
16492
|
+
const target = String(aid ?? "").trim();
|
|
16493
|
+
const cached = this._agentMdCache.get(target) ?? {};
|
|
16494
|
+
const head = await this.headAgentMd(target);
|
|
16495
|
+
let remote;
|
|
16496
|
+
if (!head.ok) {
|
|
16497
|
+
if (head.error.code === AGENTMD_NOT_FOUND) {
|
|
16498
|
+
remote = { aid: target, found: false, etag: "", last_modified: "", content_length: 0, status: 404 };
|
|
16499
|
+
} else {
|
|
16500
|
+
return head;
|
|
16501
|
+
}
|
|
16502
|
+
} else {
|
|
16503
|
+
remote = head.data;
|
|
16504
|
+
}
|
|
16505
|
+
const localEtag = String(cached.etag ?? "").trim();
|
|
16506
|
+
const localFound = !!cached.content;
|
|
16507
|
+
const remoteFound = !!remote.found;
|
|
16508
|
+
const remoteEtag = String(remote.etag ?? "").trim();
|
|
16509
|
+
const needsUpdate = remoteFound && (!localFound || !!remoteEtag && remoteEtag !== localEtag);
|
|
16510
|
+
return resultOk({
|
|
16511
|
+
aid: target,
|
|
16512
|
+
local_found: localFound,
|
|
16513
|
+
remote_found: remoteFound,
|
|
16514
|
+
local_etag: localEtag,
|
|
16515
|
+
remote_etag: remoteEtag,
|
|
16516
|
+
needs_update: needsUpdate,
|
|
16517
|
+
ttl_days: ttlDays
|
|
16518
|
+
});
|
|
16519
|
+
}
|
|
16520
|
+
async diagnose(aid) {
|
|
16521
|
+
const target = String(aid ?? "").trim();
|
|
16522
|
+
const loaded = await this.load(target);
|
|
16523
|
+
const remote = await this.exists(target);
|
|
16524
|
+
const remoteError = resultError(remote);
|
|
16525
|
+
const localError = resultError(loaded);
|
|
16526
|
+
const localAid = loaded.ok ? loaded.data.aid : null;
|
|
16527
|
+
const localCert = !!localAid?.isCertValid();
|
|
16528
|
+
const localPrivateKey = !!localAid?.isPrivateKeyValid();
|
|
16529
|
+
const localValid = localCert && localPrivateKey;
|
|
16530
|
+
const remoteRegistered = !!(remote.ok && remote.data.exists);
|
|
16531
|
+
const suggestions = [];
|
|
16532
|
+
if (!localPrivateKey) suggestions.push("load or register a local identity with a valid private key");
|
|
16533
|
+
if (!remoteRegistered) suggestions.push("register the AID before using it on the network");
|
|
16534
|
+
if (remoteError) suggestions.push(`remote registration check failed: ${remoteError.message}`);
|
|
16535
|
+
const status = localPrivateKey && remoteRegistered ? "ready" : remoteRegistered ? "registered_remote" : remote.ok ? "available" : "unknown";
|
|
16536
|
+
return resultOk({
|
|
16537
|
+
aid: target,
|
|
16538
|
+
status,
|
|
16539
|
+
localValid,
|
|
16540
|
+
local_valid: localValid,
|
|
16541
|
+
remoteRegistered,
|
|
16542
|
+
remote_registered: remoteRegistered,
|
|
16543
|
+
suggestions,
|
|
16544
|
+
local: { cert: localCert, private_key: localPrivateKey, error: localError },
|
|
16545
|
+
remote: { checked: remote.ok, exists: remote.ok ? remoteRegistered : null, error: remoteError }
|
|
16546
|
+
});
|
|
16547
|
+
}
|
|
16548
|
+
async renewCert(aid) {
|
|
16549
|
+
const target = String(aid ?? "").trim();
|
|
16550
|
+
const loaded = await this.load(target);
|
|
16551
|
+
if (!loaded.ok || !loaded.data.aid.isPrivateKeyValid()) {
|
|
16552
|
+
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16553
|
+
}
|
|
16554
|
+
try {
|
|
16555
|
+
const identity = await this._auth.loadIdentityOrNone(target);
|
|
16556
|
+
if (!identity?.cert || !identity.private_key_pem) {
|
|
16557
|
+
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16558
|
+
}
|
|
16559
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
16560
|
+
const clientNonce = this._crypto.newClientNonce();
|
|
16561
|
+
const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
|
|
16562
|
+
aid: target,
|
|
16563
|
+
cert: identity.cert,
|
|
16564
|
+
client_nonce: clientNonce
|
|
16565
|
+
});
|
|
16566
|
+
const [signature, clientTime] = await this._crypto.signLoginNonce(identity.private_key_pem, String(phase1.nonce ?? ""));
|
|
16567
|
+
const response = await this._auth._shortRpc(gatewayUrl, "auth.renew_cert", {
|
|
16568
|
+
aid: target,
|
|
16569
|
+
request_id: String(phase1.request_id ?? ""),
|
|
16570
|
+
nonce: String(phase1.nonce ?? ""),
|
|
16571
|
+
client_time: clientTime,
|
|
16572
|
+
signature
|
|
16573
|
+
});
|
|
16574
|
+
const certPem = String(response.cert ?? response.cert_pem ?? "").trim();
|
|
16575
|
+
if (!certPem) return resultErr(CERT_RENEWAL_FAILED, "server response missing certificate");
|
|
16576
|
+
await this._keystore.saveCert(target, certPem);
|
|
16577
|
+
const refreshed = await this.load(target);
|
|
16578
|
+
if (!refreshed.ok) return resultErr(CERT_RENEWAL_FAILED, "renewed certificate reload failed");
|
|
16579
|
+
return resultOk({
|
|
16580
|
+
renewed: true,
|
|
16581
|
+
new_cert_not_after: refreshed.data.aid.certNotAfter,
|
|
16582
|
+
new_fingerprint: refreshed.data.aid.certFingerprint
|
|
16583
|
+
});
|
|
16584
|
+
} catch (exc) {
|
|
16585
|
+
return resultErr(CERT_RENEWAL_FAILED, String(exc), exc);
|
|
16586
|
+
}
|
|
16587
|
+
}
|
|
16588
|
+
async rekey(aid) {
|
|
16589
|
+
const target = String(aid ?? "").trim();
|
|
16590
|
+
const loaded = await this.load(target);
|
|
16591
|
+
if (!loaded.ok || !loaded.data.aid.isPrivateKeyValid()) {
|
|
16592
|
+
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16593
|
+
}
|
|
16594
|
+
try {
|
|
16595
|
+
const identity = await this._auth.loadIdentityOrNone(target);
|
|
16596
|
+
if (!identity?.cert || !identity.private_key_pem) {
|
|
16597
|
+
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16598
|
+
}
|
|
16599
|
+
const gatewayUrl = await this._resolveGateway(target);
|
|
16600
|
+
const newIdentity = await this._crypto.generateIdentity();
|
|
16601
|
+
const clientNonce = this._crypto.newClientNonce();
|
|
16602
|
+
const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
|
|
16603
|
+
aid: target,
|
|
16604
|
+
cert: identity.cert,
|
|
16605
|
+
client_nonce: clientNonce
|
|
16606
|
+
});
|
|
16607
|
+
const signPayload = `${String(phase1.nonce ?? "")}${newIdentity.public_key_der_b64}`;
|
|
16608
|
+
const signResult = await loaded.data.aid.sign(signPayload);
|
|
16609
|
+
if (!signResult.ok) return resultErr(REKEY_FAILED, signResult.error.message);
|
|
16610
|
+
const response = await this._auth._shortRpc(gatewayUrl, "auth.rekey", {
|
|
16611
|
+
aid: target,
|
|
16612
|
+
request_id: String(phase1.request_id ?? ""),
|
|
16613
|
+
nonce: String(phase1.nonce ?? ""),
|
|
16614
|
+
new_public_key: newIdentity.public_key_der_b64,
|
|
16615
|
+
signature: signResult.data.signature
|
|
16616
|
+
});
|
|
16617
|
+
const certPem = String(response.cert ?? response.cert_pem ?? "").trim();
|
|
16618
|
+
if (!certPem) return resultErr(REKEY_FAILED, "server response missing certificate");
|
|
16619
|
+
const nextIdentity = {
|
|
16620
|
+
aid: target,
|
|
16621
|
+
private_key_pem: newIdentity.private_key_pem,
|
|
16622
|
+
public_key_der_b64: newIdentity.public_key_der_b64,
|
|
16623
|
+
curve: newIdentity.curve,
|
|
16624
|
+
cert: certPem
|
|
16625
|
+
};
|
|
16626
|
+
await this._keystore.saveIdentity(target, nextIdentity);
|
|
16627
|
+
const refreshed = await this.load(target);
|
|
16628
|
+
if (!refreshed.ok) return resultErr(REKEY_FAILED, "rekeyed identity reload failed");
|
|
16629
|
+
return resultOk({
|
|
16630
|
+
rekeyed: true,
|
|
16631
|
+
new_cert_not_after: refreshed.data.aid.certNotAfter,
|
|
16632
|
+
new_fingerprint: refreshed.data.aid.certFingerprint
|
|
16633
|
+
});
|
|
16634
|
+
} catch (exc) {
|
|
16635
|
+
return resultErr(REKEY_FAILED, String(exc), exc);
|
|
16636
|
+
}
|
|
16637
|
+
}
|
|
16638
|
+
async _resolveGateway(aid) {
|
|
16639
|
+
const target = String(aid ?? "").trim();
|
|
16640
|
+
if (!target) throw new ValidationError("AIDStore requires non-empty aid to resolve gateway");
|
|
16641
|
+
const issuerDomain = issuerFromAid(target);
|
|
16642
|
+
const cached = this._gatewayCache.get(issuerDomain);
|
|
16643
|
+
if (cached) return cached;
|
|
16644
|
+
const persisted = await this._loadCachedGatewayUrl(target);
|
|
16645
|
+
if (persisted) {
|
|
16646
|
+
this._gatewayCache.set(issuerDomain, persisted);
|
|
16647
|
+
return persisted;
|
|
16648
|
+
}
|
|
16649
|
+
const port = "";
|
|
16650
|
+
const candidates = [
|
|
16651
|
+
`https://${target}${port}/.well-known/aun-gateway`,
|
|
16652
|
+
`https://gateway.${issuerDomain}${port}/.well-known/aun-gateway`
|
|
16653
|
+
];
|
|
16654
|
+
let lastError = null;
|
|
16655
|
+
for (const url of candidates) {
|
|
16656
|
+
try {
|
|
16657
|
+
const discovered = await this._discovery.discover(url);
|
|
16658
|
+
this._gatewayCache.set(issuerDomain, discovered);
|
|
16659
|
+
await this._persistGatewayUrl(target, discovered);
|
|
16660
|
+
return discovered;
|
|
16661
|
+
} catch (exc) {
|
|
16662
|
+
lastError = exc;
|
|
16663
|
+
}
|
|
16664
|
+
}
|
|
16665
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
16666
|
+
}
|
|
16667
|
+
async _loadCachedGatewayUrl(aid) {
|
|
16668
|
+
const getMetadata = this._keystore.getMetadata;
|
|
16669
|
+
if (typeof getMetadata !== "function") return "";
|
|
16670
|
+
try {
|
|
16671
|
+
const raw = String(await getMetadata.call(this._keystore, aid, "gateway_url") ?? "").trim();
|
|
16672
|
+
if (!raw) return "";
|
|
16673
|
+
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
16674
|
+
try {
|
|
16675
|
+
const parsed = JSON.parse(raw);
|
|
16676
|
+
if (typeof parsed === "string") return parsed.trim();
|
|
16677
|
+
} catch {
|
|
16678
|
+
return raw;
|
|
16679
|
+
}
|
|
16680
|
+
}
|
|
16681
|
+
return raw;
|
|
16682
|
+
} catch {
|
|
16683
|
+
return "";
|
|
16684
|
+
}
|
|
16685
|
+
}
|
|
16686
|
+
async _persistGatewayUrl(aid, gatewayUrl) {
|
|
16687
|
+
const setMetadata = this._keystore.setMetadata;
|
|
16688
|
+
if (typeof setMetadata !== "function" || !gatewayUrl) return;
|
|
16689
|
+
try {
|
|
16690
|
+
await setMetadata.call(this._keystore, aid, "gateway_url", gatewayUrl);
|
|
16691
|
+
} catch {
|
|
16692
|
+
}
|
|
16693
|
+
}
|
|
16694
|
+
};
|
|
16695
|
+
|
|
16966
16696
|
// src/index.ts
|
|
16967
16697
|
init_crypto();
|
|
16968
16698
|
|
|
@@ -17018,20 +16748,18 @@ var ProtectedHeaders = class _ProtectedHeaders {
|
|
|
17018
16748
|
return new _ProtectedHeaders(values ?? {});
|
|
17019
16749
|
}
|
|
17020
16750
|
};
|
|
17021
|
-
|
|
17022
|
-
// src/index.ts
|
|
17023
|
-
var __version__ = "0.3.6";
|
|
17024
16751
|
export {
|
|
16752
|
+
AID,
|
|
16753
|
+
AIDStore,
|
|
17025
16754
|
AUNClient,
|
|
17026
16755
|
AUNError,
|
|
17027
16756
|
AuthError,
|
|
17028
16757
|
AuthFlow,
|
|
17029
|
-
AuthNamespace,
|
|
17030
16758
|
CertificateRevokedError,
|
|
17031
16759
|
ClientSignatureError,
|
|
17032
16760
|
ConnectionError,
|
|
16761
|
+
ConnectionState,
|
|
17033
16762
|
CryptoProvider,
|
|
17034
|
-
CustodyNamespace,
|
|
17035
16763
|
E2EEDecryptFailedError,
|
|
17036
16764
|
E2EEDegradedError,
|
|
17037
16765
|
E2EEError,
|
|
@@ -17048,7 +16776,6 @@ export {
|
|
|
17048
16776
|
IdentityConflictError,
|
|
17049
16777
|
IndexedDBKeyStore,
|
|
17050
16778
|
IndexedDBSecretStore,
|
|
17051
|
-
MetaNamespace,
|
|
17052
16779
|
NotFoundError,
|
|
17053
16780
|
PermissionError,
|
|
17054
16781
|
ProtectedHeaders,
|
|
@@ -17065,7 +16792,7 @@ export {
|
|
|
17065
16792
|
V2KeyStore,
|
|
17066
16793
|
V2Session,
|
|
17067
16794
|
ValidationError,
|
|
17068
|
-
__version__,
|
|
16795
|
+
VERSION as __version__,
|
|
17069
16796
|
computeStateCommitment,
|
|
17070
16797
|
createConfig,
|
|
17071
16798
|
createDefaultSecretStore,
|
|
@@ -17074,7 +16801,9 @@ export {
|
|
|
17074
16801
|
encryptP2PMessage,
|
|
17075
16802
|
getDeviceId,
|
|
17076
16803
|
isJsonObject,
|
|
17077
|
-
mapRemoteError
|
|
16804
|
+
mapRemoteError,
|
|
16805
|
+
resultErr,
|
|
16806
|
+
resultOk
|
|
17078
16807
|
};
|
|
17079
16808
|
/*! Bundled license information:
|
|
17080
16809
|
|