@agentunion/fastaun-browser 0.3.3 → 0.3.4
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 +26 -0
- package/_packed_docs/CHANGELOG.md +26 -0
- package/_packed_docs/INDEX.md +81 -0
- package/_packed_docs/KITE_DOCS_GUIDE.md +55 -0
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +328 -0
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -0
- package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -0
- package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +5 -5
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +454 -429
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1410 -1398
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +19 -1
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +20 -5
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +8 -8
- package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -0
- package/_packed_docs/sdk/INDEX.md +22 -22
- package/_packed_docs/sdk/README.md +3 -3
- package/dist/auth.d.ts +10 -11
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +127 -91
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +625 -274
- package/dist/client.d.ts +19 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +238 -111
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +5 -0
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb.d.ts +12 -0
- package/dist/keystore/indexeddb.d.ts.map +1 -1
- package/dist/keystore/indexeddb.js +64 -6
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/namespaces/auth.d.ts +3 -3
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +39 -20
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/secret-store/indexeddb-store.js +1 -1
- package/dist/secret-store/indexeddb-store.js.map +1 -1
- package/dist/transport.d.ts +9 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +158 -64
- package/dist/transport.js.map +1 -1
- package/dist/v2/e2ee/decrypt.js +1 -1
- package/dist/v2/e2ee/decrypt.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.d.ts.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +3 -2
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/session/session.d.ts +1 -0
- package/dist/v2/session/session.d.ts.map +1 -1
- package/dist/v2/session/session.js +7 -1
- package/dist/v2/session/session.js.map +1 -1
- package/package.json +43 -43
- package/dist/e2ee-group.d.ts +0 -276
- package/dist/e2ee-group.d.ts.map +0 -1
- package/dist/e2ee-group.js +0 -1653
- package/dist/e2ee-group.js.map +0 -1
package/dist/bundle.js
CHANGED
|
@@ -346,7 +346,7 @@ var init_indexeddb_store = __esm({
|
|
|
346
346
|
return this._masterKeyPromise;
|
|
347
347
|
}
|
|
348
348
|
async _initMasterKey() {
|
|
349
|
-
if (this._encryptionSeed) {
|
|
349
|
+
if (this._encryptionSeed !== void 0) {
|
|
350
350
|
return this._deriveKeyFromSeed(this._encryptionSeed);
|
|
351
351
|
}
|
|
352
352
|
const storedSeed = await secretGet(STORE_MASTER, "seed");
|
|
@@ -495,6 +495,12 @@ var AuthError = class extends AUNError {
|
|
|
495
495
|
this.name = "AuthError";
|
|
496
496
|
}
|
|
497
497
|
};
|
|
498
|
+
var IdentityConflictError = class extends AuthError {
|
|
499
|
+
constructor(message, opts) {
|
|
500
|
+
super(message, opts);
|
|
501
|
+
this.name = "IdentityConflictError";
|
|
502
|
+
}
|
|
503
|
+
};
|
|
498
504
|
var PermissionError = class extends AUNError {
|
|
499
505
|
constructor(message, opts) {
|
|
500
506
|
super(message, opts);
|
|
@@ -970,6 +976,8 @@ var GatewayDiscovery = class {
|
|
|
970
976
|
|
|
971
977
|
// src/transport.ts
|
|
972
978
|
var MAX_WS_PAYLOAD_SIZE = 1e6;
|
|
979
|
+
var MAX_RPC_INFLIGHT = 16;
|
|
980
|
+
var MAX_BACKGROUND_RPC_INFLIGHT = 8;
|
|
973
981
|
var _noopLog3 = { error: () => {
|
|
974
982
|
}, warn: () => {
|
|
975
983
|
}, info: () => {
|
|
@@ -1226,6 +1234,10 @@ var RPCTransport = class {
|
|
|
1226
1234
|
__publicField(this, "_closed", true);
|
|
1227
1235
|
__publicField(this, "_challenge", null);
|
|
1228
1236
|
__publicField(this, "_pending", /* @__PURE__ */ new Map());
|
|
1237
|
+
__publicField(this, "_pendingBackground", /* @__PURE__ */ new Set());
|
|
1238
|
+
__publicField(this, "_rpcQueue", []);
|
|
1239
|
+
__publicField(this, "_backgroundRpcQueue", []);
|
|
1240
|
+
__publicField(this, "_drainingRpcQueue", false);
|
|
1229
1241
|
// Gateway 在 RPC envelope 注入 _meta 字段(与 result 同级),由 client 层 observer 接收。
|
|
1230
1242
|
// 注入失败 / 字段缺失时 observer 不会被调用,不影响业务路径。
|
|
1231
1243
|
__publicField(this, "_metaObserver", null);
|
|
@@ -1329,7 +1341,8 @@ var RPCTransport = class {
|
|
|
1329
1341
|
async close() {
|
|
1330
1342
|
const tStart = Date.now();
|
|
1331
1343
|
const pendingCount = this._pending.size;
|
|
1332
|
-
this.
|
|
1344
|
+
const queuedCount = this._rpcQueue.length + this._backgroundRpcQueue.length;
|
|
1345
|
+
this._log.debug(`close enter: pending_rpc=${pendingCount}, queued_rpc=${queuedCount}, background_pending=${this._pendingBackground.size}`);
|
|
1333
1346
|
this._closed = true;
|
|
1334
1347
|
if (this._ws) {
|
|
1335
1348
|
try {
|
|
@@ -1343,16 +1356,28 @@ var RPCTransport = class {
|
|
|
1343
1356
|
this._ws = null;
|
|
1344
1357
|
}
|
|
1345
1358
|
for (const [, pending] of this._pending) {
|
|
1359
|
+
clearTimeout(pending.timer);
|
|
1346
1360
|
pending.reject(new ConnectionError("transport closed"));
|
|
1347
1361
|
}
|
|
1348
1362
|
this._pending.clear();
|
|
1349
|
-
this.
|
|
1363
|
+
this._pendingBackground.clear();
|
|
1364
|
+
for (const queued of this._rpcQueue) {
|
|
1365
|
+
clearTimeout(queued.pending.timer);
|
|
1366
|
+
queued.pending.reject(new ConnectionError("transport closed"));
|
|
1367
|
+
}
|
|
1368
|
+
this._rpcQueue = [];
|
|
1369
|
+
for (const queued of this._backgroundRpcQueue) {
|
|
1370
|
+
clearTimeout(queued.pending.timer);
|
|
1371
|
+
queued.pending.reject(new ConnectionError("transport closed"));
|
|
1372
|
+
}
|
|
1373
|
+
this._backgroundRpcQueue = [];
|
|
1374
|
+
this._log.debug(`close exit: elapsed=${Date.now() - tStart}ms, cancelled ${pendingCount} pending, ${queuedCount} queued`);
|
|
1350
1375
|
}
|
|
1351
1376
|
/**
|
|
1352
1377
|
* 发起 JSON-RPC 2.0 调用。
|
|
1353
1378
|
* 返回 result 字段的值;若有 error 字段则抛出映射后的错误。
|
|
1354
1379
|
*/
|
|
1355
|
-
async call(method, params, timeout, trace) {
|
|
1380
|
+
async call(method, params, timeout, trace, background = false) {
|
|
1356
1381
|
if (this._closed || !this._ws) {
|
|
1357
1382
|
throw new ConnectionError("transport not connected");
|
|
1358
1383
|
}
|
|
@@ -1361,20 +1386,19 @@ var RPCTransport = class {
|
|
|
1361
1386
|
const tStart = Date.now();
|
|
1362
1387
|
const effectiveTraceMode = trace === "off" || trace === "log" || trace === "diag" ? trace : this._traceMode;
|
|
1363
1388
|
let traceId = "";
|
|
1364
|
-
|
|
1389
|
+
const sendParams = { ...params ?? {} };
|
|
1390
|
+
const localParams = sendParams;
|
|
1391
|
+
const backgroundRpc = background || localParams._rpc_background === true;
|
|
1392
|
+
delete localParams._rpc_background;
|
|
1365
1393
|
if (effectiveTraceMode !== "off") {
|
|
1366
1394
|
traceId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID().replace(/-/g, "") : Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join("");
|
|
1367
|
-
sendParams = { ...params ?? {} };
|
|
1368
1395
|
const tracePayload = { trace_id: traceId, mode: effectiveTraceMode };
|
|
1369
1396
|
if (effectiveTraceMode === "diag") {
|
|
1370
1397
|
tracePayload.spans = [{ node: "sdk", ts: tStart, action: "send" }];
|
|
1371
1398
|
}
|
|
1372
|
-
|
|
1399
|
+
localParams._trace = tracePayload;
|
|
1373
1400
|
this._log.info(`[trace=${traceId}] rpc_send method=${method} rpc_id=${rpcId}`);
|
|
1374
1401
|
}
|
|
1375
|
-
const promise = new Promise((resolve, reject) => {
|
|
1376
|
-
this._pending.set(rpcId, { resolve, reject });
|
|
1377
|
-
});
|
|
1378
1402
|
const payload = JSON.stringify({
|
|
1379
1403
|
jsonrpc: "2.0",
|
|
1380
1404
|
id: rpcId,
|
|
@@ -1383,65 +1407,136 @@ var RPCTransport = class {
|
|
|
1383
1407
|
});
|
|
1384
1408
|
const payloadSize = new TextEncoder().encode(payload).length;
|
|
1385
1409
|
if (payloadSize > MAX_WS_PAYLOAD_SIZE) {
|
|
1386
|
-
this._pending.delete(rpcId);
|
|
1387
1410
|
throw new ValidationError("payload is too large");
|
|
1388
1411
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
} catch (exc) {
|
|
1393
|
-
this._pending.delete(rpcId);
|
|
1394
|
-
this._log.error(`RPC send failed: method=${method}, id=${rpcId}, error=${String(exc)}`, exc instanceof Error ? exc : void 0);
|
|
1395
|
-
throw new ConnectionError(`failed to send rpc ${method}: ${exc}`);
|
|
1396
|
-
}
|
|
1397
|
-
let timeoutHandle = null;
|
|
1398
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
1399
|
-
timeoutHandle = globalThis.setTimeout(() => {
|
|
1400
|
-
this._pending.delete(rpcId);
|
|
1412
|
+
return new Promise((resolve, reject) => {
|
|
1413
|
+
const timer = globalThis.setTimeout(() => {
|
|
1414
|
+
this._removeRpc(rpcId, pending);
|
|
1401
1415
|
this._log.warn(`RPC timeout: method=${method}, id=${rpcId}, elapsed=${Date.now() - tStart}ms, timeout=${effectiveTimeout}ms`);
|
|
1402
1416
|
reject(new TimeoutError(`rpc timeout: ${method}`, { retryable: true }));
|
|
1417
|
+
this._drainRpcQueue();
|
|
1403
1418
|
}, effectiveTimeout);
|
|
1419
|
+
const pending = {
|
|
1420
|
+
resolve: (response) => {
|
|
1421
|
+
clearTimeout(timer);
|
|
1422
|
+
const elapsed = Date.now() - tStart;
|
|
1423
|
+
if (response.error !== void 0) {
|
|
1424
|
+
this._log.debug(`RPC error response: method=${method}, id=${rpcId}, elapsed=${elapsed}ms, error=${JSON.stringify(response.error)}`);
|
|
1425
|
+
if (traceId) {
|
|
1426
|
+
this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=error`);
|
|
1427
|
+
}
|
|
1428
|
+
const respTrace = response._trace;
|
|
1429
|
+
if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
|
|
1430
|
+
this._handleResponseTrace(method, "error", elapsed, respTrace);
|
|
1431
|
+
}
|
|
1432
|
+
reject(mapRemoteError(response.error));
|
|
1433
|
+
} else if (response.result !== void 0) {
|
|
1434
|
+
this._log.debug(`RPC response ok: method=${method}, id=${rpcId}, elapsed=${elapsed}ms ${summarizeDict(response.result, DIAG_RESULT_FIELDS)}`);
|
|
1435
|
+
if (traceId) {
|
|
1436
|
+
this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=ok`);
|
|
1437
|
+
}
|
|
1438
|
+
if (this._metaObserver !== null) {
|
|
1439
|
+
const meta = response._meta;
|
|
1440
|
+
if (isJsonObject(meta)) {
|
|
1441
|
+
try {
|
|
1442
|
+
this._metaObserver(meta);
|
|
1443
|
+
} catch (exc) {
|
|
1444
|
+
this._log.debug(`meta_observer raised: ${String(exc)}`);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
const respTrace = response._trace;
|
|
1449
|
+
if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
|
|
1450
|
+
this._handleResponseTrace(method, "ok", elapsed, respTrace);
|
|
1451
|
+
}
|
|
1452
|
+
resolve(response.result);
|
|
1453
|
+
} else {
|
|
1454
|
+
this._log.warn(`RPC response missing result or error: method=${method}, id=${rpcId}, elapsed=${elapsed}ms`);
|
|
1455
|
+
reject(new SerializationError(`rpc response missing result and error: ${method}`));
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
reject: (err) => {
|
|
1459
|
+
clearTimeout(timer);
|
|
1460
|
+
reject(err);
|
|
1461
|
+
},
|
|
1462
|
+
timer
|
|
1463
|
+
};
|
|
1464
|
+
if (backgroundRpc) {
|
|
1465
|
+
this._backgroundRpcQueue.push({
|
|
1466
|
+
rpcId,
|
|
1467
|
+
method,
|
|
1468
|
+
payload,
|
|
1469
|
+
pending,
|
|
1470
|
+
tStart,
|
|
1471
|
+
timeoutMs: effectiveTimeout,
|
|
1472
|
+
background: true
|
|
1473
|
+
});
|
|
1474
|
+
} else {
|
|
1475
|
+
this._rpcQueue.push({
|
|
1476
|
+
rpcId,
|
|
1477
|
+
method,
|
|
1478
|
+
payload,
|
|
1479
|
+
pending,
|
|
1480
|
+
tStart,
|
|
1481
|
+
timeoutMs: effectiveTimeout,
|
|
1482
|
+
background: false
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
this._drainRpcQueue();
|
|
1404
1486
|
});
|
|
1487
|
+
}
|
|
1488
|
+
/** 从 pending / queue 中移除指定 RPC */
|
|
1489
|
+
_removeRpc(rpcId, pending) {
|
|
1490
|
+
const current = this._pending.get(rpcId);
|
|
1491
|
+
if (current && (!pending || current === pending)) {
|
|
1492
|
+
this._pending.delete(rpcId);
|
|
1493
|
+
this._pendingBackground.delete(rpcId);
|
|
1494
|
+
}
|
|
1495
|
+
if (this._rpcQueue.length > 0) {
|
|
1496
|
+
this._rpcQueue = this._rpcQueue.filter((entry) => entry.rpcId !== rpcId || pending !== void 0 && entry.pending !== pending);
|
|
1497
|
+
}
|
|
1498
|
+
if (this._backgroundRpcQueue.length > 0) {
|
|
1499
|
+
this._backgroundRpcQueue = this._backgroundRpcQueue.filter((entry) => entry.rpcId !== rpcId || pending !== void 0 && entry.pending !== pending);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
/** 从队列中取出 RPC 并发送,直到 inflight 达到上限 */
|
|
1503
|
+
_drainRpcQueue() {
|
|
1504
|
+
if (this._drainingRpcQueue) return;
|
|
1505
|
+
this._drainingRpcQueue = true;
|
|
1405
1506
|
try {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
if (
|
|
1411
|
-
this.
|
|
1507
|
+
while (!this._closed && this._ws && this._pending.size < MAX_RPC_INFLIGHT) {
|
|
1508
|
+
let entry;
|
|
1509
|
+
if (this._rpcQueue.length > 0) {
|
|
1510
|
+
entry = this._rpcQueue.shift();
|
|
1511
|
+
} else if (this._backgroundRpcQueue.length > 0 && this._pendingBackground.size < MAX_BACKGROUND_RPC_INFLIGHT) {
|
|
1512
|
+
entry = this._backgroundRpcQueue.shift();
|
|
1513
|
+
} else {
|
|
1514
|
+
break;
|
|
1412
1515
|
}
|
|
1413
|
-
const
|
|
1414
|
-
if (
|
|
1415
|
-
|
|
1516
|
+
const elapsed = Date.now() - entry.tStart;
|
|
1517
|
+
if (elapsed >= entry.timeoutMs) {
|
|
1518
|
+
clearTimeout(entry.pending.timer);
|
|
1519
|
+
this._log.warn(`RPC queue timeout: method=${entry.method}, id=${entry.rpcId}, elapsed=${elapsed}ms, timeout=${entry.timeoutMs}ms`);
|
|
1520
|
+
entry.pending.reject(new TimeoutError(`rpc timeout before send: ${entry.method}`, { retryable: true }));
|
|
1521
|
+
continue;
|
|
1416
1522
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
this._metaObserver(meta);
|
|
1431
|
-
} catch (exc) {
|
|
1432
|
-
this._log.debug(`meta_observer raised: ${String(exc)}`);
|
|
1433
|
-
}
|
|
1523
|
+
this._pending.set(entry.rpcId, entry.pending);
|
|
1524
|
+
if (entry.background) {
|
|
1525
|
+
this._pendingBackground.add(entry.rpcId);
|
|
1526
|
+
}
|
|
1527
|
+
try {
|
|
1528
|
+
this._ws.send(entry.payload);
|
|
1529
|
+
this._log.debug(`RPC request sent: method=${entry.method}, id=${entry.rpcId}, background=${entry.background}`);
|
|
1530
|
+
} catch (err) {
|
|
1531
|
+
this._removeRpc(entry.rpcId, entry.pending);
|
|
1532
|
+
this._log.error(`RPC send failed: method=${entry.method}, id=${entry.rpcId}, error=${String(err)}`, err instanceof Error ? err : void 0);
|
|
1533
|
+
entry.pending.reject(
|
|
1534
|
+
new ConnectionError(`failed to send rpc ${entry.method}: ${err instanceof Error ? err.message : String(err)}`)
|
|
1535
|
+
);
|
|
1434
1536
|
}
|
|
1435
1537
|
}
|
|
1436
|
-
const respTrace = response._trace;
|
|
1437
|
-
if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
|
|
1438
|
-
this._handleResponseTrace(method, "ok", elapsed, respTrace);
|
|
1439
|
-
}
|
|
1440
|
-
return response.result;
|
|
1441
1538
|
} finally {
|
|
1442
|
-
|
|
1443
|
-
globalThis.clearTimeout(timeoutHandle);
|
|
1444
|
-
}
|
|
1539
|
+
this._drainingRpcQueue = false;
|
|
1445
1540
|
}
|
|
1446
1541
|
}
|
|
1447
1542
|
/** 处理 RPC 响应中的 _trace 字段:追加 sdk.recv span,格式化输出,通知 observer */
|
|
@@ -1476,10 +1571,23 @@ var RPCTransport = class {
|
|
|
1476
1571
|
_handleClose(event) {
|
|
1477
1572
|
const wasClosed = this._closed;
|
|
1478
1573
|
this._closed = true;
|
|
1574
|
+
const err = new ConnectionError(`websocket closed: code=${event.code}`);
|
|
1479
1575
|
for (const [, pending] of this._pending) {
|
|
1480
|
-
pending.
|
|
1576
|
+
clearTimeout(pending.timer);
|
|
1577
|
+
pending.reject(err);
|
|
1481
1578
|
}
|
|
1482
1579
|
this._pending.clear();
|
|
1580
|
+
this._pendingBackground.clear();
|
|
1581
|
+
for (const queued of this._rpcQueue) {
|
|
1582
|
+
clearTimeout(queued.pending.timer);
|
|
1583
|
+
queued.pending.reject(err);
|
|
1584
|
+
}
|
|
1585
|
+
this._rpcQueue = [];
|
|
1586
|
+
for (const queued of this._backgroundRpcQueue) {
|
|
1587
|
+
clearTimeout(queued.pending.timer);
|
|
1588
|
+
queued.pending.reject(err);
|
|
1589
|
+
}
|
|
1590
|
+
this._backgroundRpcQueue = [];
|
|
1483
1591
|
if (!wasClosed) {
|
|
1484
1592
|
const error = new ConnectionError(`websocket closed: code=${event.code} reason=${event.reason}`);
|
|
1485
1593
|
this._dispatcher.publish("connection.error", { error });
|
|
@@ -1494,7 +1602,9 @@ var RPCTransport = class {
|
|
|
1494
1602
|
const pending = this._pending.get(rpcId);
|
|
1495
1603
|
if (pending) {
|
|
1496
1604
|
this._pending.delete(rpcId);
|
|
1605
|
+
this._pendingBackground.delete(rpcId);
|
|
1497
1606
|
pending.resolve(message);
|
|
1607
|
+
this._drainRpcQueue();
|
|
1498
1608
|
} else {
|
|
1499
1609
|
this._log.warn("[aun_core.transport] recv unknown rpc response (maybe arrived after timeout): id=" + rpcId);
|
|
1500
1610
|
}
|
|
@@ -1591,6 +1701,8 @@ var _noopLog4 = { error: () => {
|
|
|
1591
1701
|
}, info: () => {
|
|
1592
1702
|
}, debug: () => {
|
|
1593
1703
|
} };
|
|
1704
|
+
var AUN_SDK_LANG = "javascript";
|
|
1705
|
+
var AUN_SDK_VERSION = "0.3.4";
|
|
1594
1706
|
function splitPemBundle(bundle) {
|
|
1595
1707
|
const marker = "-----END CERTIFICATE-----";
|
|
1596
1708
|
const certs = [];
|
|
@@ -1932,47 +2044,85 @@ var _AuthFlow = class _AuthFlow {
|
|
|
1932
2044
|
this._slotId = String(opts.slotId ?? "").trim();
|
|
1933
2045
|
}
|
|
1934
2046
|
/**
|
|
1935
|
-
*
|
|
2047
|
+
* 严格注册新 AID(对齐 TS registerAid / Go RegisterAID)。
|
|
1936
2048
|
*
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1939
|
-
* 2. 短连接 RPC 调用 auth.create_aid
|
|
1940
|
-
* 3. 保存返回的证书
|
|
2049
|
+
* 注册与认证彻底分离:此方法绝不被 SDK 内部自动调用,
|
|
2050
|
+
* 必须由应用层显式调用。
|
|
1941
2051
|
*/
|
|
1942
|
-
async
|
|
2052
|
+
async registerAid(gatewayUrl, aid) {
|
|
1943
2053
|
const tStart = Date.now();
|
|
1944
|
-
this._log.debug(`
|
|
2054
|
+
this._log.debug(`registerAid enter: aid=${aid} gateway=${gatewayUrl}`);
|
|
1945
2055
|
_AuthFlow._validateAidName(aid);
|
|
1946
2056
|
try {
|
|
1947
|
-
const
|
|
1948
|
-
if (
|
|
1949
|
-
this._log.debug(`
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
|
|
1959
|
-
Object.assign(identity, recovered);
|
|
1960
|
-
} catch {
|
|
1961
|
-
this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=already_registered_recover_failed`);
|
|
1962
|
-
throw new StateError(
|
|
1963
|
-
`AID ${aid} already registered on server but local certificate is missing. Certificate download recovery failed. Options: (1) use a different AID name, or (2) restart server to clear registration.`
|
|
2057
|
+
const existing = await this._keystore.loadIdentity(aid);
|
|
2058
|
+
if (existing && existing.private_key_pem && existing.public_key_der_b64) {
|
|
2059
|
+
this._log.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
|
|
2060
|
+
const localPubB642 = String(existing.public_key_der_b64);
|
|
2061
|
+
const serverCertPem2 = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
2062
|
+
if (serverCertPem2) {
|
|
2063
|
+
const serverCert = parseCertDer(serverCertPem2);
|
|
2064
|
+
const serverPubB64 = uint8ToBase64(serverCert.spkiBytes);
|
|
2065
|
+
if (serverPubB64 !== localPubB642) {
|
|
2066
|
+
throw new IdentityConflictError(
|
|
2067
|
+
`AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
|
|
1964
2068
|
);
|
|
1965
2069
|
}
|
|
2070
|
+
this._log.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
|
|
2071
|
+
if (!existing.cert) {
|
|
2072
|
+
existing.cert = serverCertPem2;
|
|
2073
|
+
await this._persistIdentity(existing);
|
|
2074
|
+
}
|
|
2075
|
+
this._aid = aid;
|
|
2076
|
+
return { aid, cert: serverCertPem2 };
|
|
1966
2077
|
} else {
|
|
1967
|
-
|
|
2078
|
+
this._log.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
|
|
2079
|
+
const created2 = await this._createAid(gatewayUrl, existing);
|
|
2080
|
+
const certPem2 = String(created2.cert ?? "");
|
|
2081
|
+
if (!certPem2) {
|
|
2082
|
+
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
2083
|
+
}
|
|
2084
|
+
existing.cert = certPem2;
|
|
2085
|
+
const returnedCert2 = parseCertDer(certPem2);
|
|
2086
|
+
const certPubB642 = uint8ToBase64(returnedCert2.spkiBytes);
|
|
2087
|
+
if (certPubB642 !== localPubB642) {
|
|
2088
|
+
throw new AuthError(
|
|
2089
|
+
`registerAid: server returned certificate with mismatched public key for ${aid}`
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
await this._persistIdentity(existing);
|
|
2093
|
+
this._aid = aid;
|
|
2094
|
+
this._log.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
2095
|
+
return { aid, cert: certPem2 };
|
|
1968
2096
|
}
|
|
1969
2097
|
}
|
|
2098
|
+
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
2099
|
+
if (serverCertPem) {
|
|
2100
|
+
throw new IdentityConflictError(
|
|
2101
|
+
`AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
const identity = await this._crypto.generateIdentity();
|
|
2105
|
+
identity.aid = aid;
|
|
2106
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
2107
|
+
const certPem = String(created.cert ?? "");
|
|
2108
|
+
if (!certPem) {
|
|
2109
|
+
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
2110
|
+
}
|
|
2111
|
+
identity.cert = certPem;
|
|
2112
|
+
const returnedCert = parseCertDer(certPem);
|
|
2113
|
+
const certPubB64 = uint8ToBase64(returnedCert.spkiBytes);
|
|
2114
|
+
const localPubB64 = String(identity.public_key_der_b64);
|
|
2115
|
+
if (certPubB64 !== localPubB64) {
|
|
2116
|
+
throw new AuthError(
|
|
2117
|
+
`registerAid: server returned certificate with mismatched public key for ${aid}`
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
1970
2120
|
await this._persistIdentity(identity);
|
|
1971
|
-
this._aid =
|
|
1972
|
-
this._log.debug(`
|
|
2121
|
+
this._aid = aid;
|
|
2122
|
+
this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
1973
2123
|
return { aid: identity.aid, cert: identity.cert };
|
|
1974
2124
|
} catch (err) {
|
|
1975
|
-
this._log.debug(`
|
|
2125
|
+
this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
1976
2126
|
throw err;
|
|
1977
2127
|
}
|
|
1978
2128
|
}
|
|
@@ -2009,23 +2159,16 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2009
2159
|
await this._persistIdentity(identity);
|
|
2010
2160
|
} catch (e) {
|
|
2011
2161
|
throw new StateError(
|
|
2012
|
-
`local certificate missing and recovery failed: ${e}. Run auth.
|
|
2162
|
+
`local certificate missing and recovery failed: ${e}. Run auth.registerAid() to register a new identity.`
|
|
2013
2163
|
);
|
|
2014
2164
|
}
|
|
2015
2165
|
}
|
|
2166
|
+
this._assertCertMatchesLocalKeypair(identity);
|
|
2016
2167
|
let login;
|
|
2017
2168
|
try {
|
|
2018
2169
|
login = await this._login(gatewayUrl, identity);
|
|
2019
2170
|
} catch (e) {
|
|
2020
|
-
|
|
2021
|
-
this._log.warn(`[auth] cert not registered on server, auto re-register: aid=${identity.aid}`);
|
|
2022
|
-
const created = await this._createAid(gatewayUrl, identity);
|
|
2023
|
-
identity.cert = created.cert;
|
|
2024
|
-
await this._persistIdentity(identity);
|
|
2025
|
-
login = await this._login(gatewayUrl, identity);
|
|
2026
|
-
} else {
|
|
2027
|
-
throw e;
|
|
2028
|
-
}
|
|
2171
|
+
throw e;
|
|
2029
2172
|
}
|
|
2030
2173
|
this._rememberTokens(identity, login);
|
|
2031
2174
|
await this._validateNewCert(identity, gatewayUrl);
|
|
@@ -2045,18 +2188,19 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2045
2188
|
}
|
|
2046
2189
|
}
|
|
2047
2190
|
/**
|
|
2048
|
-
*
|
|
2191
|
+
* 确保已认证。注册和登录彻底分离:无身份或无 cert 直接抛错。
|
|
2049
2192
|
*/
|
|
2050
2193
|
async ensureAuthenticated(gatewayUrl) {
|
|
2051
2194
|
const tStart = Date.now();
|
|
2052
2195
|
this._log.debug(`ensureAuthenticated enter: gateway=${gatewayUrl}`);
|
|
2053
2196
|
try {
|
|
2054
|
-
const identity = await this.
|
|
2197
|
+
const identity = await this._loadIdentityOrRaise();
|
|
2055
2198
|
if (!identity.cert) {
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2199
|
+
throw new StateError(
|
|
2200
|
+
`local identity for aid ${identity.aid} has no certificate; call auth.authenticate() to attempt cert recovery, or auth.registerAid() if this is a fresh registration.`
|
|
2201
|
+
);
|
|
2059
2202
|
}
|
|
2203
|
+
this._assertCertMatchesLocalKeypair(identity);
|
|
2060
2204
|
const login = await this._login(gatewayUrl, identity);
|
|
2061
2205
|
this._rememberTokens(identity, login);
|
|
2062
2206
|
await this._validateNewCert(identity, gatewayUrl);
|
|
@@ -2388,6 +2532,35 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2388
2532
|
});
|
|
2389
2533
|
return { cert: response.cert };
|
|
2390
2534
|
}
|
|
2535
|
+
/** 下载服务端当前登记的证书;未注册返回 null */
|
|
2536
|
+
async _downloadRegisteredCert(gatewayUrl, aid) {
|
|
2537
|
+
const certUrl = gatewayHttpUrl(gatewayUrl, `/pki/cert/${aid}`);
|
|
2538
|
+
try {
|
|
2539
|
+
const certPem = await this._fetchText(certUrl);
|
|
2540
|
+
if (!certPem || !certPem.includes("BEGIN CERTIFICATE")) return null;
|
|
2541
|
+
return certPem;
|
|
2542
|
+
} catch {
|
|
2543
|
+
return null;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
/** 防线 B:cert 公钥必须与本地 keypair 公钥一致,否则拒绝登录 */
|
|
2547
|
+
_assertCertMatchesLocalKeypair(identity) {
|
|
2548
|
+
const aid = identity.aid ?? "?";
|
|
2549
|
+
const certPem = identity.cert;
|
|
2550
|
+
const localPubB64 = identity.public_key_der_b64;
|
|
2551
|
+
if (!certPem || !localPubB64) {
|
|
2552
|
+
throw new AuthError(
|
|
2553
|
+
`identity for aid ${aid} missing cert or public key; refusing to start two-phase login`
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
const cert = parseCertDer(certPem);
|
|
2557
|
+
const certSpkiB64 = uint8ToBase64(cert.spkiBytes);
|
|
2558
|
+
if (certSpkiB64 !== localPubB64) {
|
|
2559
|
+
throw new AuthError(
|
|
2560
|
+
`local certificate public key does not match local keypair for aid ${aid}; refusing to start two-phase login. Run auth.registerAid() to repair identity.`
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2391
2564
|
/** 下载已注册证书恢复本地状态 */
|
|
2392
2565
|
async _recoverCertViaDownload(gatewayUrl, identity) {
|
|
2393
2566
|
const certUrl = gatewayHttpUrl(gatewayUrl, `/pki/cert/${identity.aid}`);
|
|
@@ -2455,7 +2628,11 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2455
2628
|
auth: { method: "kite_token", token },
|
|
2456
2629
|
protocol: { min: "1.0", max: "1.0" },
|
|
2457
2630
|
device: { id: String(opts?.deviceId ?? this._deviceId ?? ""), type: "sdk" },
|
|
2458
|
-
client: {
|
|
2631
|
+
client: {
|
|
2632
|
+
slot_id: String(opts?.slotId ?? this._slotId ?? ""),
|
|
2633
|
+
sdk_lang: AUN_SDK_LANG,
|
|
2634
|
+
sdk_version: AUN_SDK_VERSION
|
|
2635
|
+
},
|
|
2459
2636
|
delivery_mode: opts?.deliveryMode ?? { mode: "fanout" },
|
|
2460
2637
|
capabilities
|
|
2461
2638
|
};
|
|
@@ -2843,26 +3020,9 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2843
3020
|
}
|
|
2844
3021
|
}
|
|
2845
3022
|
/** 确保本地有密钥对(没有则生成) */
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
this._aid = aid;
|
|
2850
|
-
return existing;
|
|
2851
|
-
}
|
|
2852
|
-
const identity = await this._crypto.generateIdentity();
|
|
2853
|
-
identity.aid = aid;
|
|
2854
|
-
if (existing) {
|
|
2855
|
-
for (const [k, v] of Object.entries(existing)) {
|
|
2856
|
-
if (k !== "aid" && !(k in identity)) {
|
|
2857
|
-
identity[k] = v;
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
await this._persistIdentity(identity);
|
|
2862
|
-
this._aid = aid;
|
|
2863
|
-
return identity;
|
|
2864
|
-
}
|
|
2865
|
-
/** 加载身份,不存在时抛出异常 */
|
|
3023
|
+
// (_ensureLocalIdentity 已移除:注册和登录彻底分离,
|
|
3024
|
+
// 登录路径绝不再隐式生成密钥;新身份必须由应用层显式调 registerAid)
|
|
3025
|
+
/** 加载身份,不存在或半成品时抛出异常 */
|
|
2866
3026
|
async _loadIdentityOrRaise(aid) {
|
|
2867
3027
|
const requestedAid = aid ?? this._aid;
|
|
2868
3028
|
if (requestedAid) {
|
|
@@ -2870,26 +3030,18 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2870
3030
|
if (!existing) {
|
|
2871
3031
|
throw new StateError(`identity not found for aid: ${requestedAid}`);
|
|
2872
3032
|
}
|
|
3033
|
+
if (!existing.private_key_pem || !existing.public_key_der_b64) {
|
|
3034
|
+
throw new StateError(
|
|
3035
|
+
`local identity for aid ${requestedAid} is incomplete (missing keypair); call auth.registerAid() first`
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
2873
3038
|
this._aid = requestedAid;
|
|
2874
3039
|
if (!existing.aid) existing.aid = requestedAid;
|
|
2875
3040
|
return existing;
|
|
2876
3041
|
}
|
|
2877
|
-
throw new StateError("no local identity found, call auth.
|
|
2878
|
-
}
|
|
2879
|
-
/** 确保有身份(无则尝试生成) */
|
|
2880
|
-
async _ensureIdentity() {
|
|
2881
|
-
try {
|
|
2882
|
-
return await this._loadIdentityOrRaise();
|
|
2883
|
-
} catch {
|
|
2884
|
-
if (!this._aid) {
|
|
2885
|
-
throw new StateError("no local identity found, call auth.createAid() first");
|
|
2886
|
-
}
|
|
2887
|
-
const identity = await this._crypto.generateIdentity();
|
|
2888
|
-
identity.aid = this._aid;
|
|
2889
|
-
await this._persistIdentity(identity);
|
|
2890
|
-
return identity;
|
|
2891
|
-
}
|
|
3042
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
2892
3043
|
}
|
|
3044
|
+
// (_ensureIdentity 已移除:注册和登录彻底分离)
|
|
2893
3045
|
async _loadInstanceState(aid) {
|
|
2894
3046
|
if (typeof this._keystore.loadInstanceState !== "function") {
|
|
2895
3047
|
return null;
|
|
@@ -3533,29 +3685,29 @@ var AuthNamespace = class {
|
|
|
3533
3685
|
}
|
|
3534
3686
|
/** 内部访问 client 私有属性 */
|
|
3535
3687
|
/**
|
|
3536
|
-
*
|
|
3537
|
-
* 通过 well-known 发现 gateway → 调用 AuthFlow.
|
|
3688
|
+
* 严格注册新 AID(对齐 TS registerAid)。
|
|
3689
|
+
* 通过 well-known 发现 gateway → 调用 AuthFlow.registerAid 注册。
|
|
3538
3690
|
*/
|
|
3539
|
-
async
|
|
3691
|
+
async registerAid(params) {
|
|
3540
3692
|
const tStart = Date.now();
|
|
3541
3693
|
const aid = String(params?.aid ?? "");
|
|
3542
|
-
this._log.debug(`
|
|
3694
|
+
this._log.debug(`registerAid enter: aid=${aid}`);
|
|
3543
3695
|
try {
|
|
3544
|
-
if (!aid) throw new ValidationError("auth.
|
|
3696
|
+
if (!aid) throw new ValidationError("auth.registerAid requires 'aid'");
|
|
3545
3697
|
const gatewayUrl = await this._resolveGateway(aid);
|
|
3546
3698
|
this._client.gatewayUrl = gatewayUrl;
|
|
3547
3699
|
const auth = this._internal._auth;
|
|
3548
|
-
const result = await auth.
|
|
3700
|
+
const result = await auth.registerAid(gatewayUrl, aid);
|
|
3549
3701
|
this._internal._aid = aid;
|
|
3550
3702
|
this._internal._identity = await auth.loadIdentityOrNone(aid);
|
|
3551
|
-
this._log.debug(`
|
|
3703
|
+
this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
3552
3704
|
return {
|
|
3553
3705
|
aid,
|
|
3554
3706
|
cert_pem: result.cert,
|
|
3555
3707
|
gateway: gatewayUrl
|
|
3556
3708
|
};
|
|
3557
3709
|
} catch (err) {
|
|
3558
|
-
this._log.debug(`
|
|
3710
|
+
this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
3559
3711
|
throw err;
|
|
3560
3712
|
}
|
|
3561
3713
|
}
|
|
@@ -3591,7 +3743,7 @@ var AuthNamespace = class {
|
|
|
3591
3743
|
const targetAid = String(opts?.aid ?? client._aid ?? "").trim();
|
|
3592
3744
|
const identity = await client._auth.loadIdentityOrNone(targetAid || void 0);
|
|
3593
3745
|
if (!identity) {
|
|
3594
|
-
throw new StateError("no local identity found, call auth.
|
|
3746
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3595
3747
|
}
|
|
3596
3748
|
const privateKeyPem = String(identity.private_key_pem ?? "").trim();
|
|
3597
3749
|
const certPem = normalizeIdentityCertPem(identity);
|
|
@@ -3713,7 +3865,7 @@ var AuthNamespace = class {
|
|
|
3713
3865
|
const auth = this._internal._auth;
|
|
3714
3866
|
let identity = await auth.loadIdentityOrNone(aid);
|
|
3715
3867
|
if (!identity) {
|
|
3716
|
-
throw new StateError("no local identity found, call auth.
|
|
3868
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3717
3869
|
}
|
|
3718
3870
|
const cachedToken = String(identity.access_token ?? "");
|
|
3719
3871
|
const expiresAt = auth.getAccessTokenExpiry(identity);
|
|
@@ -3745,11 +3897,11 @@ var AuthNamespace = class {
|
|
|
3745
3897
|
const auth = this._internal._auth;
|
|
3746
3898
|
const identity = await auth.loadIdentityOrNone(this._client.aid ?? void 0);
|
|
3747
3899
|
if (!identity) {
|
|
3748
|
-
throw new StateError("no local identity found, call auth.
|
|
3900
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3749
3901
|
}
|
|
3750
3902
|
const aid = String(identity.aid ?? this._client.aid ?? "").trim();
|
|
3751
3903
|
if (!aid) {
|
|
3752
|
-
throw new StateError("no local identity found, call auth.
|
|
3904
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3753
3905
|
}
|
|
3754
3906
|
const gatewayUrl = await this._resolveGateway(aid);
|
|
3755
3907
|
this._client.gatewayUrl = gatewayUrl;
|
|
@@ -3829,15 +3981,35 @@ var AuthNamespace = class {
|
|
|
3829
3981
|
}
|
|
3830
3982
|
const cached = this._agentMdCache.get(targetAid);
|
|
3831
3983
|
const requestHeaders = { Accept: "text/markdown" };
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(targetAid), {
|
|
3984
|
+
const agentMdUrl = await this._resolveAgentMdUrl(targetAid);
|
|
3985
|
+
const response = await fetchWithTimeout(agentMdUrl, {
|
|
3835
3986
|
method: "GET",
|
|
3836
|
-
headers: requestHeaders
|
|
3987
|
+
headers: requestHeaders,
|
|
3988
|
+
redirect: "follow"
|
|
3837
3989
|
});
|
|
3838
|
-
if (response.status === 304
|
|
3839
|
-
|
|
3840
|
-
|
|
3990
|
+
if (response.status === 304) {
|
|
3991
|
+
if (cached?.text) {
|
|
3992
|
+
this._log.debug(`downloadAgentMd exit (not_modified): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
3993
|
+
return cached.text;
|
|
3994
|
+
}
|
|
3995
|
+
this._log.warn(`downloadAgentMd got 304 but no local cache, retrying unconditional GET: aid=${targetAid}`);
|
|
3996
|
+
const retryResp = await fetchWithTimeout(agentMdUrl, {
|
|
3997
|
+
method: "GET",
|
|
3998
|
+
headers: { Accept: "text/markdown" },
|
|
3999
|
+
redirect: "follow"
|
|
4000
|
+
});
|
|
4001
|
+
if (retryResp.status === 404) {
|
|
4002
|
+
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
4003
|
+
}
|
|
4004
|
+
if (!retryResp.ok) {
|
|
4005
|
+
const message = (await retryResp.text()).trim();
|
|
4006
|
+
throw new AUNError(
|
|
4007
|
+
`download agent.md failed (retry): HTTP ${retryResp.status}${message ? ` - ${message}` : ""}`
|
|
4008
|
+
);
|
|
4009
|
+
}
|
|
4010
|
+
const retryText = await retryResp.text();
|
|
4011
|
+
this._log.debug(`downloadAgentMd exit (retry): elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${retryText.length}`);
|
|
4012
|
+
return retryText;
|
|
3841
4013
|
}
|
|
3842
4014
|
if (response.status === 404) {
|
|
3843
4015
|
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
@@ -4962,6 +5134,12 @@ function sameJson(a, b) {
|
|
|
4962
5134
|
}
|
|
4963
5135
|
var _ENC_ALGO = "AES-GCM";
|
|
4964
5136
|
var _PBKDF2_ITERATIONS = 1e5;
|
|
5137
|
+
var SeedMigrationError = class extends Error {
|
|
5138
|
+
constructor(message) {
|
|
5139
|
+
super(message);
|
|
5140
|
+
this.name = "SeedMigrationError";
|
|
5141
|
+
}
|
|
5142
|
+
};
|
|
4965
5143
|
function _uint8ToBase64(bytes) {
|
|
4966
5144
|
let b = "";
|
|
4967
5145
|
for (let i = 0; i < bytes.length; i++) b += String.fromCharCode(bytes[i]);
|
|
@@ -5000,6 +5178,9 @@ async function _decryptPEM(enc, seed) {
|
|
|
5000
5178
|
const pt = await crypto.subtle.decrypt({ name: _ENC_ALGO, iv: toBufferSource2(iv) }, key, toBufferSource2(ct));
|
|
5001
5179
|
return new TextDecoder().decode(pt);
|
|
5002
5180
|
}
|
|
5181
|
+
function hasEncryptionSeed(seed) {
|
|
5182
|
+
return seed !== void 0;
|
|
5183
|
+
}
|
|
5003
5184
|
var DB_NAME = "aun-keystore";
|
|
5004
5185
|
var DB_VERSION = 6;
|
|
5005
5186
|
var STORE_KEY_PAIRS = "key_pairs";
|
|
@@ -5322,6 +5503,52 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5322
5503
|
setLogger(log) {
|
|
5323
5504
|
this._log = log;
|
|
5324
5505
|
}
|
|
5506
|
+
static async changeSeed(oldSeed, newSeed) {
|
|
5507
|
+
if (oldSeed === ".seed") {
|
|
5508
|
+
throw new SeedMigrationError("JS IndexedDB keystore does not support file .seed migration");
|
|
5509
|
+
}
|
|
5510
|
+
const rows = await idbGetAll(STORE_KEY_PAIRS);
|
|
5511
|
+
const migrations = [];
|
|
5512
|
+
for (const row of rows) {
|
|
5513
|
+
if (!isRecord(row.value)) continue;
|
|
5514
|
+
const envelope = row.value._encrypted_pk;
|
|
5515
|
+
if (!isRecord(envelope)) continue;
|
|
5516
|
+
try {
|
|
5517
|
+
const privateKeyPem = await _decryptPEM(envelope, oldSeed);
|
|
5518
|
+
migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem });
|
|
5519
|
+
} catch {
|
|
5520
|
+
throw new SeedMigrationError(`seed migration refused: private key is not encrypted by old seed: aid=${row.key}`);
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
if (migrations.length === 0) {
|
|
5524
|
+
throw new SeedMigrationError("seed migration refused: no encrypted private key verified with old seed");
|
|
5525
|
+
}
|
|
5526
|
+
const result = {
|
|
5527
|
+
migrated: 0,
|
|
5528
|
+
skipped: 0,
|
|
5529
|
+
errors: 0,
|
|
5530
|
+
privateKeysVerified: migrations.length,
|
|
5531
|
+
privateKeysMigrated: 0
|
|
5532
|
+
};
|
|
5533
|
+
for (const item of migrations) {
|
|
5534
|
+
const next = deepClone(item.value);
|
|
5535
|
+
next._encrypted_pk = await _encryptPEM(item.privateKeyPem, newSeed);
|
|
5536
|
+
const verified = await _decryptPEM(next._encrypted_pk, newSeed);
|
|
5537
|
+
if (verified !== item.privateKeyPem) {
|
|
5538
|
+
throw new SeedMigrationError(`new seed verification failed for private key: aid=${item.key}`);
|
|
5539
|
+
}
|
|
5540
|
+
delete next.private_key_pem;
|
|
5541
|
+
await idbPut(STORE_KEY_PAIRS, item.key, next);
|
|
5542
|
+
result.migrated += 1;
|
|
5543
|
+
result.privateKeysMigrated += 1;
|
|
5544
|
+
}
|
|
5545
|
+
return result;
|
|
5546
|
+
}
|
|
5547
|
+
async changeSeed(oldSeed, newSeed) {
|
|
5548
|
+
const result = await _IndexedDBKeyStore.changeSeed(oldSeed, newSeed);
|
|
5549
|
+
this._encryptionSeed = newSeed;
|
|
5550
|
+
return result;
|
|
5551
|
+
}
|
|
5325
5552
|
async _withAidLock(aid, fn) {
|
|
5326
5553
|
const key = safeAid(aid);
|
|
5327
5554
|
const previous = _IndexedDBKeyStore._aidTails.get(key) ?? Promise.resolve();
|
|
@@ -5378,7 +5605,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5378
5605
|
if (!isRecord(data)) return null;
|
|
5379
5606
|
const result = deepClone(data);
|
|
5380
5607
|
const epk = result._encrypted_pk;
|
|
5381
|
-
if (epk && typeof epk === "object" && !Array.isArray(epk) && this._encryptionSeed) {
|
|
5608
|
+
if (epk && typeof epk === "object" && !Array.isArray(epk) && hasEncryptionSeed(this._encryptionSeed)) {
|
|
5382
5609
|
try {
|
|
5383
5610
|
const envelope = epk;
|
|
5384
5611
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
@@ -5388,7 +5615,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5388
5615
|
}
|
|
5389
5616
|
} else if (
|
|
5390
5617
|
// 透明迁移:旧版明文数据自动加密回写
|
|
5391
|
-
!epk && typeof result.private_key_pem === "string" && this._encryptionSeed
|
|
5618
|
+
!epk && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
5392
5619
|
) {
|
|
5393
5620
|
try {
|
|
5394
5621
|
await this.saveKeyPair(aid, result);
|
|
@@ -5399,7 +5626,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5399
5626
|
}
|
|
5400
5627
|
async saveKeyPair(aid, keyPair) {
|
|
5401
5628
|
const record = deepClone(keyPair);
|
|
5402
|
-
if (this._encryptionSeed && typeof record.private_key_pem === "string") {
|
|
5629
|
+
if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
|
|
5403
5630
|
record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
|
|
5404
5631
|
delete record.private_key_pem;
|
|
5405
5632
|
}
|
|
@@ -5775,7 +6002,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5775
6002
|
const data = await idbGet(STORE_KEY_PAIRS, metadataStoreKey(aid));
|
|
5776
6003
|
if (!isRecord(data)) return null;
|
|
5777
6004
|
const result = deepClone(data);
|
|
5778
|
-
if (isRecord(result._encrypted_pk) && this._encryptionSeed) {
|
|
6005
|
+
if (isRecord(result._encrypted_pk) && hasEncryptionSeed(this._encryptionSeed)) {
|
|
5779
6006
|
try {
|
|
5780
6007
|
const envelope = result._encrypted_pk;
|
|
5781
6008
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
@@ -5785,7 +6012,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5785
6012
|
}
|
|
5786
6013
|
} else if (
|
|
5787
6014
|
// 透明迁移:旧版明文数据自动加密回写
|
|
5788
|
-
!isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && this._encryptionSeed
|
|
6015
|
+
!isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
5789
6016
|
) {
|
|
5790
6017
|
try {
|
|
5791
6018
|
await this._saveKeyPairUnlocked(aid, result);
|
|
@@ -5796,7 +6023,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5796
6023
|
}
|
|
5797
6024
|
async _saveKeyPairUnlocked(aid, keyPair) {
|
|
5798
6025
|
const record = deepClone(keyPair);
|
|
5799
|
-
if (this._encryptionSeed && typeof record.private_key_pem === "string") {
|
|
6026
|
+
if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
|
|
5800
6027
|
record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
|
|
5801
6028
|
delete record.private_key_pem;
|
|
5802
6029
|
}
|
|
@@ -9654,6 +9881,7 @@ var V2Session = class {
|
|
|
9654
9881
|
__publicField(this, "_peerIKCache", /* @__PURE__ */ new Map());
|
|
9655
9882
|
__publicField(this, "_verifiedSPKs", /* @__PURE__ */ new Set());
|
|
9656
9883
|
__publicField(this, "_oldSPKMaxSeq", /* @__PURE__ */ new Map());
|
|
9884
|
+
__publicField(this, "_spkCache", /* @__PURE__ */ new Map());
|
|
9657
9885
|
__publicField(this, "_nowFn", () => Date.now());
|
|
9658
9886
|
if (!ikPriv || !ikPubDer) {
|
|
9659
9887
|
throw new Error("V2Session requires AID priv/pub keys (IK = AID identity)");
|
|
@@ -9771,8 +9999,13 @@ var V2Session = class {
|
|
|
9771
9999
|
await this.ensureKeys();
|
|
9772
10000
|
if (!spkId) return { ikPriv: this._ikPriv };
|
|
9773
10001
|
if (spkId === this._spkId) return { ikPriv: this._ikPriv, spkPriv: this._spkPriv };
|
|
10002
|
+
const cached = this._spkCache.get(spkId);
|
|
10003
|
+
if (cached) return { ikPriv: this._ikPriv, spkPriv: cached };
|
|
9774
10004
|
const oldSPK = await this._loadSPK(spkId);
|
|
9775
|
-
if (oldSPK)
|
|
10005
|
+
if (oldSPK) {
|
|
10006
|
+
this._spkCache.set(spkId, oldSPK);
|
|
10007
|
+
return { ikPriv: this._ikPriv, spkPriv: oldSPK };
|
|
10008
|
+
}
|
|
9776
10009
|
const ikAlias = await this._loadIKSPK(spkId);
|
|
9777
10010
|
if (ikAlias) return { ikPriv: ikAlias.priv, spkPriv: ikAlias.priv };
|
|
9778
10011
|
if (spkId === await this._ikSPKId()) {
|
|
@@ -10079,7 +10312,7 @@ function isPlainObject(value) {
|
|
|
10079
10312
|
var encoder3 = new TextEncoder();
|
|
10080
10313
|
var decoder = new TextDecoder();
|
|
10081
10314
|
var E2EE_SDK_LANG = "javascript";
|
|
10082
|
-
var E2EE_SDK_VERSION = "0.3.
|
|
10315
|
+
var E2EE_SDK_VERSION = "0.3.4";
|
|
10083
10316
|
async function sha2563(data) {
|
|
10084
10317
|
const buf = await crypto.subtle.digest("SHA-256", data.slice().buffer);
|
|
10085
10318
|
return new Uint8Array(buf);
|
|
@@ -10249,7 +10482,8 @@ function normalizeProtectedHeaders(headers, payload) {
|
|
|
10249
10482
|
normalized["payload_type"] = payloadType;
|
|
10250
10483
|
}
|
|
10251
10484
|
normalized.sdk_lang = E2EE_SDK_LANG;
|
|
10252
|
-
normalized.sdk_vesion
|
|
10485
|
+
delete normalized.sdk_vesion;
|
|
10486
|
+
normalized.sdk_version = E2EE_SDK_VERSION;
|
|
10253
10487
|
return normalized;
|
|
10254
10488
|
}
|
|
10255
10489
|
function normalizeProtectedHeaderKey(key) {
|
|
@@ -10641,7 +10875,7 @@ async function verifySenderSignature(env, senderPubDer) {
|
|
|
10641
10875
|
}
|
|
10642
10876
|
function findMyRow(recipients, selfAid, selfDeviceId) {
|
|
10643
10877
|
for (const row of recipients) {
|
|
10644
|
-
if (row[0] === selfAid && row[1] === selfDeviceId) return row;
|
|
10878
|
+
if (row[0] === selfAid && (row[1] === selfDeviceId || row[1] === "")) return row;
|
|
10645
10879
|
}
|
|
10646
10880
|
return null;
|
|
10647
10881
|
}
|
|
@@ -11092,6 +11326,54 @@ function isGroupServiceAid(value) {
|
|
|
11092
11326
|
const [name, ...issuerParts] = text.split(".");
|
|
11093
11327
|
return name === "group" && issuerParts.join(".").length > 0;
|
|
11094
11328
|
}
|
|
11329
|
+
function normalizeV2WrapPolicy(raw) {
|
|
11330
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
11331
|
+
const obj = raw;
|
|
11332
|
+
let protocol = String(obj.protocol ?? "").trim().toUpperCase();
|
|
11333
|
+
let scope = String(obj.scope ?? "").trim().toLowerCase();
|
|
11334
|
+
if (scope !== "aid" && scope !== "device") {
|
|
11335
|
+
if (obj.per_aid_wrap === true) scope = "aid";
|
|
11336
|
+
else if (obj.per_device_wrap === true) scope = "device";
|
|
11337
|
+
else scope = "";
|
|
11338
|
+
}
|
|
11339
|
+
if (protocol !== "1DH" && protocol !== "3DH") protocol = "";
|
|
11340
|
+
if (scope === "aid") protocol = "1DH";
|
|
11341
|
+
if (!protocol && !scope) return void 0;
|
|
11342
|
+
return {
|
|
11343
|
+
protocol: protocol ? protocol : void 0,
|
|
11344
|
+
scope: scope ? scope : void 0
|
|
11345
|
+
};
|
|
11346
|
+
}
|
|
11347
|
+
function v2WrapCapabilities() {
|
|
11348
|
+
return {
|
|
11349
|
+
version: "v2.1",
|
|
11350
|
+
protocols: ["1DH", "3DH"],
|
|
11351
|
+
scopes: ["aid", "device"],
|
|
11352
|
+
per_aid_wrap: true,
|
|
11353
|
+
per_device_wrap: true
|
|
11354
|
+
};
|
|
11355
|
+
}
|
|
11356
|
+
function applyV2WrapPolicyToTargets(targets, policy) {
|
|
11357
|
+
if (!policy) return targets;
|
|
11358
|
+
const normalized = targets.map((target) => {
|
|
11359
|
+
const row = { ...target };
|
|
11360
|
+
if (policy.protocol === "1DH") {
|
|
11361
|
+
row.keySource = "aid_master";
|
|
11362
|
+
row.spkPkDer = void 0;
|
|
11363
|
+
row.spkId = "";
|
|
11364
|
+
}
|
|
11365
|
+
return row;
|
|
11366
|
+
});
|
|
11367
|
+
if (policy.scope !== "aid") return normalized;
|
|
11368
|
+
const collapsed = /* @__PURE__ */ new Map();
|
|
11369
|
+
for (const target of normalized) {
|
|
11370
|
+
const key = `${target.aid}\0${target.role}`;
|
|
11371
|
+
if (!collapsed.has(key)) {
|
|
11372
|
+
collapsed.set(key, { ...target, deviceId: "" });
|
|
11373
|
+
}
|
|
11374
|
+
}
|
|
11375
|
+
return Array.from(collapsed.values());
|
|
11376
|
+
}
|
|
11095
11377
|
function _v2LeftPad32(b) {
|
|
11096
11378
|
if (b.length === 32) return b;
|
|
11097
11379
|
if (b.length > 32) return b.subarray(b.length - 32);
|
|
@@ -11315,7 +11597,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11315
11597
|
__publicField(this, "_agentMdPath", "");
|
|
11316
11598
|
__publicField(this, "_agentMdCache", /* @__PURE__ */ new Map());
|
|
11317
11599
|
__publicField(this, "_agentMdFetchInflight", /* @__PURE__ */ new Set());
|
|
11318
|
-
__publicField(this, "
|
|
11600
|
+
__publicField(this, "_agentMdLock", Promise.resolve());
|
|
11319
11601
|
/** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
|
|
11320
11602
|
__publicField(this, "_seqTracker", new SeqTracker());
|
|
11321
11603
|
__publicField(this, "_seqTrackerContext", null);
|
|
@@ -11329,6 +11611,8 @@ var _AUNClient = class _AUNClient {
|
|
|
11329
11611
|
__publicField(this, "_groupSynced", /* @__PURE__ */ new Set());
|
|
11330
11612
|
/** gap fill 来源标记:true 表示当前正在补洞(pull 触发),false 表示非补洞 */
|
|
11331
11613
|
__publicField(this, "_gapFillActive", false);
|
|
11614
|
+
// Pull Gate:序列化同一 key 的并发 pull 操作,防止重复拉取
|
|
11615
|
+
__publicField(this, "_pullGates", /* @__PURE__ */ new Map());
|
|
11332
11616
|
// 重连相关
|
|
11333
11617
|
__publicField(this, "_reconnectActive", false);
|
|
11334
11618
|
__publicField(this, "_reconnectAbort", null);
|
|
@@ -11372,7 +11656,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11372
11656
|
this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
|
|
11373
11657
|
this._dispatcher = new EventDispatcher();
|
|
11374
11658
|
this._discovery = new GatewayDiscovery();
|
|
11375
|
-
this._keystore = new IndexedDBKeyStore();
|
|
11659
|
+
this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? void 0 });
|
|
11376
11660
|
this._slotId = "";
|
|
11377
11661
|
this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
|
|
11378
11662
|
this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
|
|
@@ -11474,7 +11758,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11474
11758
|
}
|
|
11475
11759
|
/**
|
|
11476
11760
|
* 浏览器版本 publishAgentMd。默认从 {agentMdPath}/{self_aid}/agent.md 的等价 IndexedDB 正文读取,
|
|
11477
|
-
* 然后签名、上传,并刷新
|
|
11761
|
+
* 然后签名、上传,并刷新 agentmd.json 元数据。
|
|
11478
11762
|
*
|
|
11479
11763
|
* 兼容旧浏览器调用:传入 content 时会先写入等价正文,再从该正文发布。
|
|
11480
11764
|
*/
|
|
@@ -11516,7 +11800,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11516
11800
|
}
|
|
11517
11801
|
/**
|
|
11518
11802
|
* 浏览器版本 fetchAgentMd。aid 缺省时取自身;下载后的正文固定写入
|
|
11519
|
-
* {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,
|
|
11803
|
+
* {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,agentmd.json 只保存元数据。
|
|
11520
11804
|
*/
|
|
11521
11805
|
async fetchAgentMd(aid) {
|
|
11522
11806
|
const target = String(aid ?? this._aid ?? "").trim();
|
|
@@ -11588,8 +11872,8 @@ var _AUNClient = class _AUNClient {
|
|
|
11588
11872
|
}
|
|
11589
11873
|
return target;
|
|
11590
11874
|
}
|
|
11591
|
-
|
|
11592
|
-
return
|
|
11875
|
+
_agentMdMetaKey(aid) {
|
|
11876
|
+
return `${this._agentMdSafeAid(aid)}/agentmd.json`;
|
|
11593
11877
|
}
|
|
11594
11878
|
_agentMdContentKey(aid) {
|
|
11595
11879
|
return `${this._agentMdSafeAid(aid)}/agent.md`;
|
|
@@ -11621,20 +11905,13 @@ var _AUNClient = class _AUNClient {
|
|
|
11621
11905
|
fetched_at: Date.now()
|
|
11622
11906
|
});
|
|
11623
11907
|
}
|
|
11624
|
-
async
|
|
11625
|
-
const
|
|
11626
|
-
if (typeof list !== "function") {
|
|
11627
|
-
throw new Error("IndexedDB agent.md storage unavailable");
|
|
11628
|
-
}
|
|
11629
|
-
return await list.call(this._keystore, this._agentMdRoot());
|
|
11630
|
-
}
|
|
11631
|
-
async _withAgentMdListLock(fn) {
|
|
11632
|
-
const previous = this._agentMdListLock.catch(() => void 0);
|
|
11908
|
+
async _withAgentMdLock(fn) {
|
|
11909
|
+
const previous = this._agentMdLock.catch(() => void 0);
|
|
11633
11910
|
let release;
|
|
11634
11911
|
const current = new Promise((resolve) => {
|
|
11635
11912
|
release = resolve;
|
|
11636
11913
|
});
|
|
11637
|
-
this.
|
|
11914
|
+
this._agentMdLock = previous.then(() => current);
|
|
11638
11915
|
await previous;
|
|
11639
11916
|
try {
|
|
11640
11917
|
return await fn();
|
|
@@ -11642,70 +11919,35 @@ var _AUNClient = class _AUNClient {
|
|
|
11642
11919
|
release();
|
|
11643
11920
|
}
|
|
11644
11921
|
}
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
if (Array.isArray(payload.records)) iterable = payload.records;
|
|
11651
|
-
else if (isJsonObject(payload.records)) iterable = Object.values(payload.records);
|
|
11652
|
-
} else if (Array.isArray(data)) {
|
|
11653
|
-
iterable = data;
|
|
11654
|
-
}
|
|
11655
|
-
for (const item of iterable) {
|
|
11656
|
-
if (!isJsonObject(item)) continue;
|
|
11657
|
-
const raw = item;
|
|
11658
|
-
const aid = String(raw.aid ?? "").trim();
|
|
11659
|
-
if (!aid) continue;
|
|
11660
|
-
const record = {};
|
|
11661
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
11662
|
-
if (key !== "content") record[key] = value;
|
|
11663
|
-
}
|
|
11664
|
-
record.aid = aid;
|
|
11665
|
-
for (const key of ["fetched_at", "observed_at", "checked_at", "updated_at"]) {
|
|
11666
|
-
record[key] = Number(record[key] ?? 0) || 0;
|
|
11667
|
-
}
|
|
11668
|
-
records[aid] = record;
|
|
11669
|
-
}
|
|
11670
|
-
return records;
|
|
11671
|
-
}
|
|
11672
|
-
async _writeAgentMdListUnlocked(records) {
|
|
11673
|
-
const sorted = {};
|
|
11674
|
-
for (const aid of Object.keys(records).sort()) sorted[aid] = records[aid];
|
|
11675
|
-
await this._writeAgentMdStorage(this._agentMdListKey(), `${JSON.stringify({ version: 1, updated_at: Date.now(), records: sorted }, null, 2)}
|
|
11676
|
-
`);
|
|
11677
|
-
}
|
|
11678
|
-
async _rebuildAgentMdListUnlocked() {
|
|
11679
|
-
const records = {};
|
|
11680
|
-
const now = Date.now();
|
|
11681
|
-
for (const aid of await this._listAgentMdContentAids()) {
|
|
11682
|
-
try {
|
|
11683
|
-
const content = await this._readAgentMdStorage(this._agentMdContentKey(aid));
|
|
11684
|
-
if (content === null) continue;
|
|
11685
|
-
records[aid] = {
|
|
11686
|
-
aid,
|
|
11687
|
-
local_etag: await this._agentMdContentEtag(content),
|
|
11688
|
-
fetched_at: now,
|
|
11689
|
-
updated_at: now
|
|
11690
|
-
};
|
|
11691
|
-
} catch (err) {
|
|
11692
|
-
this._clientLog.warn(`agent.md rebuild skipped unreadable file aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
11693
|
-
}
|
|
11922
|
+
_normalizeAgentMdRecord(aid, data) {
|
|
11923
|
+
if (!isJsonObject(data)) return {};
|
|
11924
|
+
const record = {};
|
|
11925
|
+
for (const [key, value] of Object.entries(data)) {
|
|
11926
|
+
if (key !== "content") record[key] = value;
|
|
11694
11927
|
}
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11928
|
+
record.aid = this._agentMdSafeAid(String(record.aid ?? aid));
|
|
11929
|
+
for (const key of ["fetched_at", "observed_at", "checked_at", "updated_at"]) {
|
|
11930
|
+
record[key] = Number(record[key] ?? 0) || 0;
|
|
11931
|
+
}
|
|
11932
|
+
return record;
|
|
11698
11933
|
}
|
|
11699
|
-
async
|
|
11700
|
-
const
|
|
11701
|
-
|
|
11702
|
-
|
|
11934
|
+
async _writeAgentMdRecordUnlocked(aid, record) {
|
|
11935
|
+
const payload = {};
|
|
11936
|
+
for (const [key, value] of Object.entries(record)) {
|
|
11937
|
+
if (key !== "content" && value !== void 0 && value !== null) payload[key] = value;
|
|
11703
11938
|
}
|
|
11939
|
+
payload.aid = this._agentMdSafeAid(aid);
|
|
11940
|
+
await this._writeAgentMdStorage(this._agentMdMetaKey(aid), `${JSON.stringify(payload, null, 2)}
|
|
11941
|
+
`);
|
|
11942
|
+
}
|
|
11943
|
+
async _readAgentMdRecordUnlocked(aid) {
|
|
11944
|
+
const raw = await this._readAgentMdStorage(this._agentMdMetaKey(aid));
|
|
11945
|
+
if (raw === null) return {};
|
|
11704
11946
|
try {
|
|
11705
|
-
return this.
|
|
11947
|
+
return this._normalizeAgentMdRecord(aid, JSON.parse(raw));
|
|
11706
11948
|
} catch (err) {
|
|
11707
|
-
this._clientLog.warn(`agent.md
|
|
11708
|
-
return
|
|
11949
|
+
this._clientLog.warn(`agent.md metadata damaged, ignoring: aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
11950
|
+
return {};
|
|
11709
11951
|
}
|
|
11710
11952
|
}
|
|
11711
11953
|
async _readAgentMdContent(aid) {
|
|
@@ -11727,20 +11969,28 @@ var _AUNClient = class _AUNClient {
|
|
|
11727
11969
|
const target = String(aid ?? "").trim();
|
|
11728
11970
|
if (!target) return null;
|
|
11729
11971
|
try {
|
|
11730
|
-
const
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11972
|
+
const loaded = await this._withAgentMdLock(async () => {
|
|
11973
|
+
const record = await this._readAgentMdRecordUnlocked(target);
|
|
11974
|
+
const next = Object.keys(record).length > 0 ? { ...record, aid: target } : { aid: target };
|
|
11975
|
+
try {
|
|
11976
|
+
const content = await this._readAgentMdContent(target);
|
|
11977
|
+
if (content !== null) {
|
|
11978
|
+
next.content = content;
|
|
11979
|
+
next.local_etag = await this._agentMdContentEtag(content);
|
|
11980
|
+
} else {
|
|
11981
|
+
const metaRaw = await this._readAgentMdStorage(this._agentMdMetaKey(target));
|
|
11982
|
+
if (metaRaw !== null) {
|
|
11983
|
+
this._clientLog.warn(`agent.md content read failed: aid=${target}`);
|
|
11984
|
+
}
|
|
11985
|
+
}
|
|
11986
|
+
} catch (err) {
|
|
11987
|
+
this._clientLog.warn(`agent.md content read failed: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
|
|
11740
11988
|
}
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11989
|
+
return next;
|
|
11990
|
+
});
|
|
11991
|
+
if (Object.keys(loaded).length <= 1) return null;
|
|
11992
|
+
this._agentMdCache.set(target, { ...loaded });
|
|
11993
|
+
return { ...loaded };
|
|
11744
11994
|
} catch (err) {
|
|
11745
11995
|
this._clientLog.debug(`agent.md cache load skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
|
|
11746
11996
|
}
|
|
@@ -11759,15 +12009,13 @@ var _AUNClient = class _AUNClient {
|
|
|
11759
12009
|
if (!inputFields.fetched_at) inputFields.fetched_at = Date.now();
|
|
11760
12010
|
}
|
|
11761
12011
|
delete inputFields.content;
|
|
11762
|
-
const record = await this.
|
|
11763
|
-
const
|
|
11764
|
-
const next = { ...records[target] ?? {}, aid: target };
|
|
12012
|
+
const record = await this._withAgentMdLock(async () => {
|
|
12013
|
+
const next = { ...await this._readAgentMdRecordUnlocked(target), aid: target };
|
|
11765
12014
|
for (const [key, value] of Object.entries(inputFields)) {
|
|
11766
12015
|
if (value !== void 0 && value !== null) next[key] = value;
|
|
11767
12016
|
}
|
|
11768
12017
|
next.updated_at = Date.now();
|
|
11769
|
-
|
|
11770
|
-
await this._writeAgentMdListUnlocked(records);
|
|
12018
|
+
await this._writeAgentMdRecordUnlocked(target, next);
|
|
11771
12019
|
return next;
|
|
11772
12020
|
});
|
|
11773
12021
|
const loaded = { ...record };
|
|
@@ -12219,6 +12467,19 @@ var _AUNClient = class _AUNClient {
|
|
|
12219
12467
|
return this._putMessageThoughtEncryptedV2(p);
|
|
12220
12468
|
}
|
|
12221
12469
|
}
|
|
12470
|
+
const pullGateKey = this._pullGateKeyForCall(method, p);
|
|
12471
|
+
if (pullGateKey) {
|
|
12472
|
+
return await this._runPullSerialized(pullGateKey, async () => {
|
|
12473
|
+
return await this._callImplInner(method, p);
|
|
12474
|
+
});
|
|
12475
|
+
}
|
|
12476
|
+
return await this._callImplInner(method, p);
|
|
12477
|
+
}
|
|
12478
|
+
/**
|
|
12479
|
+
* _callImpl 的内层:pull gate 之后的实际 RPC 分发逻辑。
|
|
12480
|
+
* 拆分出来以便 pull gate 包裹整个操作。
|
|
12481
|
+
*/
|
|
12482
|
+
async _callImplInner(method, p) {
|
|
12222
12483
|
if (method === "message.pull" && this._v2Session) {
|
|
12223
12484
|
this._clientLog.debug("call route: message.pull \u2192 V2 pull");
|
|
12224
12485
|
const messages = await this.pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50);
|
|
@@ -14786,7 +15047,10 @@ var _AUNClient = class _AUNClient {
|
|
|
14786
15047
|
const session = this._v2Session;
|
|
14787
15048
|
if (session && fromAid) {
|
|
14788
15049
|
try {
|
|
14789
|
-
const bs = await this.call("message.v2.bootstrap", {
|
|
15050
|
+
const bs = await this.call("message.v2.bootstrap", {
|
|
15051
|
+
peer_aid: fromAid,
|
|
15052
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15053
|
+
});
|
|
14790
15054
|
const peers = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
|
|
14791
15055
|
for (const dev of peers) this._cacheV2PeerIKFromDevice(dev, fromAid);
|
|
14792
15056
|
} catch (exc) {
|
|
@@ -14794,7 +15058,10 @@ var _AUNClient = class _AUNClient {
|
|
|
14794
15058
|
}
|
|
14795
15059
|
if (groupId) {
|
|
14796
15060
|
try {
|
|
14797
|
-
const gbs = await this.call("group.v2.bootstrap", {
|
|
15061
|
+
const gbs = await this.call("group.v2.bootstrap", {
|
|
15062
|
+
group_id: groupId,
|
|
15063
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15064
|
+
});
|
|
14798
15065
|
const devices = Array.isArray(gbs?.devices) ? gbs.devices : [];
|
|
14799
15066
|
const audit = Array.isArray(gbs?.audit_recipients) ? gbs.audit_recipients : [];
|
|
14800
15067
|
for (const dev of devices) this._cacheV2PeerIKFromDevice(dev);
|
|
@@ -15040,7 +15307,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15040
15307
|
spkId = String(msg.spk_id ?? "");
|
|
15041
15308
|
if (!spkId) {
|
|
15042
15309
|
for (const row of envelope.recipients) {
|
|
15043
|
-
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
|
|
15310
|
+
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
|
|
15044
15311
|
spkId = String(row[5] ?? "");
|
|
15045
15312
|
recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
|
|
15046
15313
|
break;
|
|
@@ -15048,7 +15315,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15048
15315
|
}
|
|
15049
15316
|
} else {
|
|
15050
15317
|
for (const row of envelope.recipients) {
|
|
15051
|
-
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
|
|
15318
|
+
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
|
|
15052
15319
|
recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
|
|
15053
15320
|
break;
|
|
15054
15321
|
}
|
|
@@ -15397,19 +15664,26 @@ var _AUNClient = class _AUNClient {
|
|
|
15397
15664
|
const useCache = opts.useCache !== false;
|
|
15398
15665
|
let peerDevices = [];
|
|
15399
15666
|
let auditRaw = [];
|
|
15667
|
+
let wrapPolicy;
|
|
15400
15668
|
const cached = useCache ? this._v2BootstrapCache.get(to) : void 0;
|
|
15401
15669
|
if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
|
|
15402
15670
|
peerDevices = cached.devices;
|
|
15403
15671
|
auditRaw = cached.auditRecipients;
|
|
15672
|
+
wrapPolicy = cached.wrapPolicy;
|
|
15404
15673
|
} else {
|
|
15405
|
-
const bs = await this.call("message.v2.bootstrap", {
|
|
15674
|
+
const bs = await this.call("message.v2.bootstrap", {
|
|
15675
|
+
peer_aid: to,
|
|
15676
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15677
|
+
});
|
|
15406
15678
|
peerDevices = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
|
|
15407
15679
|
auditRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
|
|
15680
|
+
wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
|
|
15408
15681
|
if (peerDevices.length > 0) {
|
|
15409
15682
|
this._v2BootstrapCache.set(to, {
|
|
15410
15683
|
devices: peerDevices,
|
|
15411
15684
|
auditRecipients: auditRaw,
|
|
15412
|
-
cachedAt: Date.now()
|
|
15685
|
+
cachedAt: Date.now(),
|
|
15686
|
+
wrapPolicy
|
|
15413
15687
|
});
|
|
15414
15688
|
}
|
|
15415
15689
|
}
|
|
@@ -15445,7 +15719,10 @@ var _AUNClient = class _AUNClient {
|
|
|
15445
15719
|
if (selfCached && Date.now() - selfCached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
|
|
15446
15720
|
selfDevices = selfCached.devices;
|
|
15447
15721
|
} else {
|
|
15448
|
-
const selfBs = await this.call("message.v2.bootstrap", {
|
|
15722
|
+
const selfBs = await this.call("message.v2.bootstrap", {
|
|
15723
|
+
peer_aid: this._aid,
|
|
15724
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15725
|
+
});
|
|
15449
15726
|
selfDevices = Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : [];
|
|
15450
15727
|
if (selfDevices.length > 0) {
|
|
15451
15728
|
this._v2BootstrapCache.set(this._aid, {
|
|
@@ -15472,9 +15749,10 @@ var _AUNClient = class _AUNClient {
|
|
|
15472
15749
|
}
|
|
15473
15750
|
}
|
|
15474
15751
|
const sender = await session.getSenderIdentity();
|
|
15752
|
+
const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
|
|
15475
15753
|
const envelope = await encryptP2PMessage(
|
|
15476
15754
|
sender,
|
|
15477
|
-
{ targets, auditRecipients: [] },
|
|
15755
|
+
{ targets: sendTargets, auditRecipients: [] },
|
|
15478
15756
|
opts.payload,
|
|
15479
15757
|
{ messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }
|
|
15480
15758
|
);
|
|
@@ -15547,17 +15825,23 @@ var _AUNClient = class _AUNClient {
|
|
|
15547
15825
|
let epoch = 0;
|
|
15548
15826
|
let stateCommitment = { state_version: 0, state_hash: "", state_chain: "" };
|
|
15549
15827
|
let auditRecipientsRaw = [];
|
|
15828
|
+
let wrapPolicy;
|
|
15550
15829
|
const cached = useCache ? this._v2BootstrapCache.get(cacheKey) : void 0;
|
|
15551
15830
|
if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
|
|
15552
15831
|
allDevices = cached.devices;
|
|
15553
15832
|
epoch = cached.epoch ?? 0;
|
|
15554
15833
|
stateCommitment = cached.stateCommitment ?? { state_version: 0, state_hash: "", state_chain: "" };
|
|
15555
15834
|
auditRecipientsRaw = cached.auditRecipients;
|
|
15835
|
+
wrapPolicy = cached.wrapPolicy;
|
|
15556
15836
|
} else {
|
|
15557
|
-
const bs = await this.call("group.v2.bootstrap", {
|
|
15837
|
+
const bs = await this.call("group.v2.bootstrap", {
|
|
15838
|
+
group_id: groupId,
|
|
15839
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15840
|
+
});
|
|
15558
15841
|
allDevices = Array.isArray(bs?.devices) ? bs.devices : [];
|
|
15559
15842
|
epoch = Number(bs?.epoch ?? 0);
|
|
15560
15843
|
auditRecipientsRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
|
|
15844
|
+
wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
|
|
15561
15845
|
await this._v2CheckFork(groupId, String(bs?.state_chain ?? ""));
|
|
15562
15846
|
await this._v2VerifyStateSignature(groupId, bs);
|
|
15563
15847
|
await this._publishV2GroupSecurityLevel(groupId, bs);
|
|
@@ -15572,7 +15856,8 @@ var _AUNClient = class _AUNClient {
|
|
|
15572
15856
|
auditRecipients: auditRecipientsRaw,
|
|
15573
15857
|
cachedAt: Date.now(),
|
|
15574
15858
|
epoch,
|
|
15575
|
-
stateCommitment
|
|
15859
|
+
stateCommitment,
|
|
15860
|
+
wrapPolicy
|
|
15576
15861
|
});
|
|
15577
15862
|
}
|
|
15578
15863
|
const pendingAdds = Array.isArray(bs?.pending_adds) ? bs.pending_adds : [];
|
|
@@ -15612,11 +15897,12 @@ var _AUNClient = class _AUNClient {
|
|
|
15612
15897
|
throw new E2EEError(`V2 group: no target devices for ${groupId}`);
|
|
15613
15898
|
}
|
|
15614
15899
|
const sender = await session.getSenderIdentity();
|
|
15900
|
+
const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
|
|
15615
15901
|
const envelope = await encryptGroupMessage(
|
|
15616
15902
|
sender,
|
|
15617
15903
|
groupId,
|
|
15618
15904
|
epoch,
|
|
15619
|
-
|
|
15905
|
+
sendTargets,
|
|
15620
15906
|
opts.payload,
|
|
15621
15907
|
{ messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context },
|
|
15622
15908
|
stateCommitment
|
|
@@ -15697,7 +15983,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15697
15983
|
if (Array.isArray(recipients)) {
|
|
15698
15984
|
for (const row of recipients) {
|
|
15699
15985
|
if (Array.isArray(row) && row.length >= 6) {
|
|
15700
|
-
if (row[0] === this._aid && row[1] === this._deviceId) {
|
|
15986
|
+
if (row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
|
|
15701
15987
|
spkId = String(row[5] ?? "");
|
|
15702
15988
|
recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
|
|
15703
15989
|
break;
|
|
@@ -15976,7 +16262,10 @@ var _AUNClient = class _AUNClient {
|
|
|
15976
16262
|
if (aid === myAid) myRole = role;
|
|
15977
16263
|
}
|
|
15978
16264
|
if (myRole !== "owner" && myRole !== "admin") return false;
|
|
15979
|
-
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16265
|
+
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16266
|
+
group_id: groupId,
|
|
16267
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
16268
|
+
});
|
|
15980
16269
|
const devices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
|
|
15981
16270
|
const candidates = [];
|
|
15982
16271
|
for (const dev of devices) {
|
|
@@ -16065,7 +16354,10 @@ var _AUNClient = class _AUNClient {
|
|
|
16065
16354
|
}
|
|
16066
16355
|
}
|
|
16067
16356
|
}
|
|
16068
|
-
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16357
|
+
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16358
|
+
group_id: groupId,
|
|
16359
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
16360
|
+
});
|
|
16069
16361
|
const allDevices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
|
|
16070
16362
|
const auditRecipients = Array.isArray(bootstrapResp?.audit_recipients) ? bootstrapResp.audit_recipients : [];
|
|
16071
16363
|
const auditAidsList = [...new Set(
|
|
@@ -16374,6 +16666,62 @@ var _AUNClient = class _AUNClient {
|
|
|
16374
16666
|
this._clientLog.warn(`background task exception:${String(exc)}`);
|
|
16375
16667
|
});
|
|
16376
16668
|
}
|
|
16669
|
+
// ── Pull Gate(序列化同一 key 的并发 pull)──────────────────
|
|
16670
|
+
_pullGateKeyForCall(method, params) {
|
|
16671
|
+
if (method === "message.pull" || method === "message.v2.pull") {
|
|
16672
|
+
return this._aid ? `p2p:${this._aid}` : "";
|
|
16673
|
+
}
|
|
16674
|
+
if (method === "group.pull" || method === "group.v2.pull") {
|
|
16675
|
+
const gid = String(params.group_id ?? "").trim();
|
|
16676
|
+
return gid ? `group:${gid}` : "";
|
|
16677
|
+
}
|
|
16678
|
+
if (method === "group.pull_events") {
|
|
16679
|
+
const gid = String(params.group_id ?? "").trim();
|
|
16680
|
+
return gid ? `group_event:${gid}` : "";
|
|
16681
|
+
}
|
|
16682
|
+
return "";
|
|
16683
|
+
}
|
|
16684
|
+
_tryAcquirePullGate(key) {
|
|
16685
|
+
if (!key) return 0;
|
|
16686
|
+
const now = Date.now();
|
|
16687
|
+
const gate = this._pullGates.get(key) ?? { inflight: false, startedAt: 0, token: 0 };
|
|
16688
|
+
if (gate.inflight && now - gate.startedAt <= _AUNClient._PULL_GATE_STALE_MS) {
|
|
16689
|
+
return null;
|
|
16690
|
+
}
|
|
16691
|
+
if (gate.inflight) {
|
|
16692
|
+
this._clientLog.warn(`pull in-flight stale reset: key=${key} age=${now - gate.startedAt}ms`);
|
|
16693
|
+
}
|
|
16694
|
+
gate.token += 1;
|
|
16695
|
+
gate.inflight = true;
|
|
16696
|
+
gate.startedAt = now;
|
|
16697
|
+
this._pullGates.set(key, gate);
|
|
16698
|
+
return gate.token;
|
|
16699
|
+
}
|
|
16700
|
+
_releasePullGate(key, token) {
|
|
16701
|
+
if (!key || token == null) return;
|
|
16702
|
+
const gate = this._pullGates.get(key);
|
|
16703
|
+
if (!gate || gate.token !== token) return;
|
|
16704
|
+
gate.inflight = false;
|
|
16705
|
+
gate.startedAt = 0;
|
|
16706
|
+
}
|
|
16707
|
+
async _runPullSerialized(key, operation) {
|
|
16708
|
+
let token = this._tryAcquirePullGate(key);
|
|
16709
|
+
if (token === null) {
|
|
16710
|
+
const deadline = Date.now() + _AUNClient._PULL_GATE_STALE_MS + 100;
|
|
16711
|
+
while (token === null && Date.now() <= deadline) {
|
|
16712
|
+
await this._sleep(25);
|
|
16713
|
+
token = this._tryAcquirePullGate(key);
|
|
16714
|
+
}
|
|
16715
|
+
if (token === null) {
|
|
16716
|
+
throw new StateError(`pull already in-flight for ${key}`);
|
|
16717
|
+
}
|
|
16718
|
+
}
|
|
16719
|
+
try {
|
|
16720
|
+
return await operation();
|
|
16721
|
+
} finally {
|
|
16722
|
+
this._releasePullGate(key, token);
|
|
16723
|
+
}
|
|
16724
|
+
}
|
|
16377
16725
|
/** 可取消的 sleep */
|
|
16378
16726
|
_sleep(ms) {
|
|
16379
16727
|
return new Promise((resolve) => {
|
|
@@ -16385,6 +16733,7 @@ __publicField(_AUNClient, "V2_BOOTSTRAP_TTL_MS", 60 * 60 * 1e3);
|
|
|
16385
16733
|
__publicField(_AUNClient, "V2_RETRYABLE_CODES", /* @__PURE__ */ new Set([-33011, -33012, -33050, -33052, -33054]));
|
|
16386
16734
|
__publicField(_AUNClient, "_V2_SIG_CACHE_TTL", 60 * 60 * 1e3);
|
|
16387
16735
|
__publicField(_AUNClient, "_V2_SIG_CACHE_MAX", 16384);
|
|
16736
|
+
__publicField(_AUNClient, "_PULL_GATE_STALE_MS", 3e4);
|
|
16388
16737
|
// ── 内部:断线重连 ────────────────────────────────
|
|
16389
16738
|
/** 不重连 close code 集合:认证失败/权限错误/被踢等,重连无意义 */
|
|
16390
16739
|
__publicField(_AUNClient, "_NO_RECONNECT_CODES", /* @__PURE__ */ new Set([4001, 4003, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015]));
|
|
@@ -16455,7 +16804,7 @@ var ProtectedHeaders = class _ProtectedHeaders {
|
|
|
16455
16804
|
};
|
|
16456
16805
|
|
|
16457
16806
|
// src/index.ts
|
|
16458
|
-
var __version__ = "0.3.
|
|
16807
|
+
var __version__ = "0.3.4";
|
|
16459
16808
|
export {
|
|
16460
16809
|
AUNClient,
|
|
16461
16810
|
AUNError,
|
|
@@ -16480,6 +16829,7 @@ export {
|
|
|
16480
16829
|
GroupError,
|
|
16481
16830
|
GroupNotFoundError,
|
|
16482
16831
|
GroupStateError,
|
|
16832
|
+
IdentityConflictError,
|
|
16483
16833
|
IndexedDBKeyStore,
|
|
16484
16834
|
IndexedDBSecretStore,
|
|
16485
16835
|
MetaNamespace,
|
|
@@ -16490,6 +16840,7 @@ export {
|
|
|
16490
16840
|
RPCTransport,
|
|
16491
16841
|
RateLimitError,
|
|
16492
16842
|
STATE_PREFIX,
|
|
16843
|
+
SeedMigrationError,
|
|
16493
16844
|
SerializationError,
|
|
16494
16845
|
SessionError,
|
|
16495
16846
|
StateError,
|