@agentunion/fastaun-browser 0.3.3 → 0.3.5
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 +113 -85
- package/_packed_docs/CHANGELOG.md +113 -85
- 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/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -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 +46 -6
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +89 -12
- 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 +649 -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 +9 -3
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +64 -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.5";
|
|
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
|
}
|
|
@@ -3583,6 +3735,30 @@ var AuthNamespace = class {
|
|
|
3583
3735
|
throw err;
|
|
3584
3736
|
}
|
|
3585
3737
|
}
|
|
3738
|
+
/** 只读加载本地已注册身份(密钥对 + 证书 + 实例状态)。无副作用,不触发网络请求。 */
|
|
3739
|
+
async loadIdentity(params) {
|
|
3740
|
+
const aid = String((params ?? {})?.aid ?? "").trim() || void 0;
|
|
3741
|
+
const identity = await this._internal._auth.loadIdentityOrNone(aid);
|
|
3742
|
+
if (!identity) {
|
|
3743
|
+
throw new StateError(`identity not found for aid: ${aid ?? "<default>"}`);
|
|
3744
|
+
}
|
|
3745
|
+
return identity;
|
|
3746
|
+
}
|
|
3747
|
+
/** 只读加载本地已注册身份,不存在时返回 null。 */
|
|
3748
|
+
async loadIdentityOrNull(params) {
|
|
3749
|
+
const aid = String((params ?? {})?.aid ?? "").trim() || void 0;
|
|
3750
|
+
return await this._internal._auth.loadIdentityOrNone(aid);
|
|
3751
|
+
}
|
|
3752
|
+
/** 获取对端 AID 的证书 PEM(本地缓存优先,未命中走 PKI HTTP + 链验证)。 */
|
|
3753
|
+
async fetchPeerCert(params) {
|
|
3754
|
+
const aid = String(params?.aid ?? "").trim();
|
|
3755
|
+
if (!aid) throw new Error("auth.fetchPeerCert requires 'aid'");
|
|
3756
|
+
const fp = String(params?.cert_fingerprint ?? "").trim() || void 0;
|
|
3757
|
+
if (typeof this._internal._fetchPeerCert !== "function") {
|
|
3758
|
+
throw new Error("client does not support _fetchPeerCert");
|
|
3759
|
+
}
|
|
3760
|
+
return String(await this._internal._fetchPeerCert(aid, fp)).trim();
|
|
3761
|
+
}
|
|
3586
3762
|
async signAgentMd(content, opts) {
|
|
3587
3763
|
const tStart = Date.now();
|
|
3588
3764
|
this._log.debug(`signAgentMd enter: aid=${opts?.aid ?? "<current>"} content_len=${String(content ?? "").length}`);
|
|
@@ -3591,7 +3767,7 @@ var AuthNamespace = class {
|
|
|
3591
3767
|
const targetAid = String(opts?.aid ?? client._aid ?? "").trim();
|
|
3592
3768
|
const identity = await client._auth.loadIdentityOrNone(targetAid || void 0);
|
|
3593
3769
|
if (!identity) {
|
|
3594
|
-
throw new StateError("no local identity found, call auth.
|
|
3770
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3595
3771
|
}
|
|
3596
3772
|
const privateKeyPem = String(identity.private_key_pem ?? "").trim();
|
|
3597
3773
|
const certPem = normalizeIdentityCertPem(identity);
|
|
@@ -3713,7 +3889,7 @@ var AuthNamespace = class {
|
|
|
3713
3889
|
const auth = this._internal._auth;
|
|
3714
3890
|
let identity = await auth.loadIdentityOrNone(aid);
|
|
3715
3891
|
if (!identity) {
|
|
3716
|
-
throw new StateError("no local identity found, call auth.
|
|
3892
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3717
3893
|
}
|
|
3718
3894
|
const cachedToken = String(identity.access_token ?? "");
|
|
3719
3895
|
const expiresAt = auth.getAccessTokenExpiry(identity);
|
|
@@ -3745,11 +3921,11 @@ var AuthNamespace = class {
|
|
|
3745
3921
|
const auth = this._internal._auth;
|
|
3746
3922
|
const identity = await auth.loadIdentityOrNone(this._client.aid ?? void 0);
|
|
3747
3923
|
if (!identity) {
|
|
3748
|
-
throw new StateError("no local identity found, call auth.
|
|
3924
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3749
3925
|
}
|
|
3750
3926
|
const aid = String(identity.aid ?? this._client.aid ?? "").trim();
|
|
3751
3927
|
if (!aid) {
|
|
3752
|
-
throw new StateError("no local identity found, call auth.
|
|
3928
|
+
throw new StateError("no local identity found, call auth.registerAid() first");
|
|
3753
3929
|
}
|
|
3754
3930
|
const gatewayUrl = await this._resolveGateway(aid);
|
|
3755
3931
|
this._client.gatewayUrl = gatewayUrl;
|
|
@@ -3829,15 +4005,35 @@ var AuthNamespace = class {
|
|
|
3829
4005
|
}
|
|
3830
4006
|
const cached = this._agentMdCache.get(targetAid);
|
|
3831
4007
|
const requestHeaders = { Accept: "text/markdown" };
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(targetAid), {
|
|
4008
|
+
const agentMdUrl = await this._resolveAgentMdUrl(targetAid);
|
|
4009
|
+
const response = await fetchWithTimeout(agentMdUrl, {
|
|
3835
4010
|
method: "GET",
|
|
3836
|
-
headers: requestHeaders
|
|
4011
|
+
headers: requestHeaders,
|
|
4012
|
+
redirect: "follow"
|
|
3837
4013
|
});
|
|
3838
|
-
if (response.status === 304
|
|
3839
|
-
|
|
3840
|
-
|
|
4014
|
+
if (response.status === 304) {
|
|
4015
|
+
if (cached?.text) {
|
|
4016
|
+
this._log.debug(`downloadAgentMd exit (not_modified): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
4017
|
+
return cached.text;
|
|
4018
|
+
}
|
|
4019
|
+
this._log.warn(`downloadAgentMd got 304 but no local cache, retrying unconditional GET: aid=${targetAid}`);
|
|
4020
|
+
const retryResp = await fetchWithTimeout(agentMdUrl, {
|
|
4021
|
+
method: "GET",
|
|
4022
|
+
headers: { Accept: "text/markdown" },
|
|
4023
|
+
redirect: "follow"
|
|
4024
|
+
});
|
|
4025
|
+
if (retryResp.status === 404) {
|
|
4026
|
+
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
4027
|
+
}
|
|
4028
|
+
if (!retryResp.ok) {
|
|
4029
|
+
const message = (await retryResp.text()).trim();
|
|
4030
|
+
throw new AUNError(
|
|
4031
|
+
`download agent.md failed (retry): HTTP ${retryResp.status}${message ? ` - ${message}` : ""}`
|
|
4032
|
+
);
|
|
4033
|
+
}
|
|
4034
|
+
const retryText = await retryResp.text();
|
|
4035
|
+
this._log.debug(`downloadAgentMd exit (retry): elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${retryText.length}`);
|
|
4036
|
+
return retryText;
|
|
3841
4037
|
}
|
|
3842
4038
|
if (response.status === 404) {
|
|
3843
4039
|
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
@@ -4962,6 +5158,12 @@ function sameJson(a, b) {
|
|
|
4962
5158
|
}
|
|
4963
5159
|
var _ENC_ALGO = "AES-GCM";
|
|
4964
5160
|
var _PBKDF2_ITERATIONS = 1e5;
|
|
5161
|
+
var SeedMigrationError = class extends Error {
|
|
5162
|
+
constructor(message) {
|
|
5163
|
+
super(message);
|
|
5164
|
+
this.name = "SeedMigrationError";
|
|
5165
|
+
}
|
|
5166
|
+
};
|
|
4965
5167
|
function _uint8ToBase64(bytes) {
|
|
4966
5168
|
let b = "";
|
|
4967
5169
|
for (let i = 0; i < bytes.length; i++) b += String.fromCharCode(bytes[i]);
|
|
@@ -5000,6 +5202,9 @@ async function _decryptPEM(enc, seed) {
|
|
|
5000
5202
|
const pt = await crypto.subtle.decrypt({ name: _ENC_ALGO, iv: toBufferSource2(iv) }, key, toBufferSource2(ct));
|
|
5001
5203
|
return new TextDecoder().decode(pt);
|
|
5002
5204
|
}
|
|
5205
|
+
function hasEncryptionSeed(seed) {
|
|
5206
|
+
return seed !== void 0;
|
|
5207
|
+
}
|
|
5003
5208
|
var DB_NAME = "aun-keystore";
|
|
5004
5209
|
var DB_VERSION = 6;
|
|
5005
5210
|
var STORE_KEY_PAIRS = "key_pairs";
|
|
@@ -5322,6 +5527,52 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5322
5527
|
setLogger(log) {
|
|
5323
5528
|
this._log = log;
|
|
5324
5529
|
}
|
|
5530
|
+
static async changeSeed(oldSeed, newSeed) {
|
|
5531
|
+
if (oldSeed === ".seed") {
|
|
5532
|
+
throw new SeedMigrationError("JS IndexedDB keystore does not support file .seed migration");
|
|
5533
|
+
}
|
|
5534
|
+
const rows = await idbGetAll(STORE_KEY_PAIRS);
|
|
5535
|
+
const migrations = [];
|
|
5536
|
+
for (const row of rows) {
|
|
5537
|
+
if (!isRecord(row.value)) continue;
|
|
5538
|
+
const envelope = row.value._encrypted_pk;
|
|
5539
|
+
if (!isRecord(envelope)) continue;
|
|
5540
|
+
try {
|
|
5541
|
+
const privateKeyPem = await _decryptPEM(envelope, oldSeed);
|
|
5542
|
+
migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem });
|
|
5543
|
+
} catch {
|
|
5544
|
+
throw new SeedMigrationError(`seed migration refused: private key is not encrypted by old seed: aid=${row.key}`);
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
if (migrations.length === 0) {
|
|
5548
|
+
throw new SeedMigrationError("seed migration refused: no encrypted private key verified with old seed");
|
|
5549
|
+
}
|
|
5550
|
+
const result = {
|
|
5551
|
+
migrated: 0,
|
|
5552
|
+
skipped: 0,
|
|
5553
|
+
errors: 0,
|
|
5554
|
+
privateKeysVerified: migrations.length,
|
|
5555
|
+
privateKeysMigrated: 0
|
|
5556
|
+
};
|
|
5557
|
+
for (const item of migrations) {
|
|
5558
|
+
const next = deepClone(item.value);
|
|
5559
|
+
next._encrypted_pk = await _encryptPEM(item.privateKeyPem, newSeed);
|
|
5560
|
+
const verified = await _decryptPEM(next._encrypted_pk, newSeed);
|
|
5561
|
+
if (verified !== item.privateKeyPem) {
|
|
5562
|
+
throw new SeedMigrationError(`new seed verification failed for private key: aid=${item.key}`);
|
|
5563
|
+
}
|
|
5564
|
+
delete next.private_key_pem;
|
|
5565
|
+
await idbPut(STORE_KEY_PAIRS, item.key, next);
|
|
5566
|
+
result.migrated += 1;
|
|
5567
|
+
result.privateKeysMigrated += 1;
|
|
5568
|
+
}
|
|
5569
|
+
return result;
|
|
5570
|
+
}
|
|
5571
|
+
async changeSeed(oldSeed, newSeed) {
|
|
5572
|
+
const result = await _IndexedDBKeyStore.changeSeed(oldSeed, newSeed);
|
|
5573
|
+
this._encryptionSeed = newSeed;
|
|
5574
|
+
return result;
|
|
5575
|
+
}
|
|
5325
5576
|
async _withAidLock(aid, fn) {
|
|
5326
5577
|
const key = safeAid(aid);
|
|
5327
5578
|
const previous = _IndexedDBKeyStore._aidTails.get(key) ?? Promise.resolve();
|
|
@@ -5378,7 +5629,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5378
5629
|
if (!isRecord(data)) return null;
|
|
5379
5630
|
const result = deepClone(data);
|
|
5380
5631
|
const epk = result._encrypted_pk;
|
|
5381
|
-
if (epk && typeof epk === "object" && !Array.isArray(epk) && this._encryptionSeed) {
|
|
5632
|
+
if (epk && typeof epk === "object" && !Array.isArray(epk) && hasEncryptionSeed(this._encryptionSeed)) {
|
|
5382
5633
|
try {
|
|
5383
5634
|
const envelope = epk;
|
|
5384
5635
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
@@ -5388,7 +5639,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5388
5639
|
}
|
|
5389
5640
|
} else if (
|
|
5390
5641
|
// 透明迁移:旧版明文数据自动加密回写
|
|
5391
|
-
!epk && typeof result.private_key_pem === "string" && this._encryptionSeed
|
|
5642
|
+
!epk && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
5392
5643
|
) {
|
|
5393
5644
|
try {
|
|
5394
5645
|
await this.saveKeyPair(aid, result);
|
|
@@ -5399,7 +5650,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5399
5650
|
}
|
|
5400
5651
|
async saveKeyPair(aid, keyPair) {
|
|
5401
5652
|
const record = deepClone(keyPair);
|
|
5402
|
-
if (this._encryptionSeed && typeof record.private_key_pem === "string") {
|
|
5653
|
+
if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
|
|
5403
5654
|
record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
|
|
5404
5655
|
delete record.private_key_pem;
|
|
5405
5656
|
}
|
|
@@ -5775,7 +6026,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5775
6026
|
const data = await idbGet(STORE_KEY_PAIRS, metadataStoreKey(aid));
|
|
5776
6027
|
if (!isRecord(data)) return null;
|
|
5777
6028
|
const result = deepClone(data);
|
|
5778
|
-
if (isRecord(result._encrypted_pk) && this._encryptionSeed) {
|
|
6029
|
+
if (isRecord(result._encrypted_pk) && hasEncryptionSeed(this._encryptionSeed)) {
|
|
5779
6030
|
try {
|
|
5780
6031
|
const envelope = result._encrypted_pk;
|
|
5781
6032
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
@@ -5785,7 +6036,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5785
6036
|
}
|
|
5786
6037
|
} else if (
|
|
5787
6038
|
// 透明迁移:旧版明文数据自动加密回写
|
|
5788
|
-
!isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && this._encryptionSeed
|
|
6039
|
+
!isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
5789
6040
|
) {
|
|
5790
6041
|
try {
|
|
5791
6042
|
await this._saveKeyPairUnlocked(aid, result);
|
|
@@ -5796,7 +6047,7 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
5796
6047
|
}
|
|
5797
6048
|
async _saveKeyPairUnlocked(aid, keyPair) {
|
|
5798
6049
|
const record = deepClone(keyPair);
|
|
5799
|
-
if (this._encryptionSeed && typeof record.private_key_pem === "string") {
|
|
6050
|
+
if (hasEncryptionSeed(this._encryptionSeed) && typeof record.private_key_pem === "string") {
|
|
5800
6051
|
record._encrypted_pk = await _encryptPEM(record.private_key_pem, this._encryptionSeed);
|
|
5801
6052
|
delete record.private_key_pem;
|
|
5802
6053
|
}
|
|
@@ -9654,6 +9905,7 @@ var V2Session = class {
|
|
|
9654
9905
|
__publicField(this, "_peerIKCache", /* @__PURE__ */ new Map());
|
|
9655
9906
|
__publicField(this, "_verifiedSPKs", /* @__PURE__ */ new Set());
|
|
9656
9907
|
__publicField(this, "_oldSPKMaxSeq", /* @__PURE__ */ new Map());
|
|
9908
|
+
__publicField(this, "_spkCache", /* @__PURE__ */ new Map());
|
|
9657
9909
|
__publicField(this, "_nowFn", () => Date.now());
|
|
9658
9910
|
if (!ikPriv || !ikPubDer) {
|
|
9659
9911
|
throw new Error("V2Session requires AID priv/pub keys (IK = AID identity)");
|
|
@@ -9771,8 +10023,13 @@ var V2Session = class {
|
|
|
9771
10023
|
await this.ensureKeys();
|
|
9772
10024
|
if (!spkId) return { ikPriv: this._ikPriv };
|
|
9773
10025
|
if (spkId === this._spkId) return { ikPriv: this._ikPriv, spkPriv: this._spkPriv };
|
|
10026
|
+
const cached = this._spkCache.get(spkId);
|
|
10027
|
+
if (cached) return { ikPriv: this._ikPriv, spkPriv: cached };
|
|
9774
10028
|
const oldSPK = await this._loadSPK(spkId);
|
|
9775
|
-
if (oldSPK)
|
|
10029
|
+
if (oldSPK) {
|
|
10030
|
+
this._spkCache.set(spkId, oldSPK);
|
|
10031
|
+
return { ikPriv: this._ikPriv, spkPriv: oldSPK };
|
|
10032
|
+
}
|
|
9776
10033
|
const ikAlias = await this._loadIKSPK(spkId);
|
|
9777
10034
|
if (ikAlias) return { ikPriv: ikAlias.priv, spkPriv: ikAlias.priv };
|
|
9778
10035
|
if (spkId === await this._ikSPKId()) {
|
|
@@ -10079,7 +10336,7 @@ function isPlainObject(value) {
|
|
|
10079
10336
|
var encoder3 = new TextEncoder();
|
|
10080
10337
|
var decoder = new TextDecoder();
|
|
10081
10338
|
var E2EE_SDK_LANG = "javascript";
|
|
10082
|
-
var E2EE_SDK_VERSION = "0.3.
|
|
10339
|
+
var E2EE_SDK_VERSION = "0.3.5";
|
|
10083
10340
|
async function sha2563(data) {
|
|
10084
10341
|
const buf = await crypto.subtle.digest("SHA-256", data.slice().buffer);
|
|
10085
10342
|
return new Uint8Array(buf);
|
|
@@ -10249,7 +10506,8 @@ function normalizeProtectedHeaders(headers, payload) {
|
|
|
10249
10506
|
normalized["payload_type"] = payloadType;
|
|
10250
10507
|
}
|
|
10251
10508
|
normalized.sdk_lang = E2EE_SDK_LANG;
|
|
10252
|
-
normalized.sdk_vesion
|
|
10509
|
+
delete normalized.sdk_vesion;
|
|
10510
|
+
normalized.sdk_version = E2EE_SDK_VERSION;
|
|
10253
10511
|
return normalized;
|
|
10254
10512
|
}
|
|
10255
10513
|
function normalizeProtectedHeaderKey(key) {
|
|
@@ -10641,7 +10899,7 @@ async function verifySenderSignature(env, senderPubDer) {
|
|
|
10641
10899
|
}
|
|
10642
10900
|
function findMyRow(recipients, selfAid, selfDeviceId) {
|
|
10643
10901
|
for (const row of recipients) {
|
|
10644
|
-
if (row[0] === selfAid && row[1] === selfDeviceId) return row;
|
|
10902
|
+
if (row[0] === selfAid && (row[1] === selfDeviceId || row[1] === "")) return row;
|
|
10645
10903
|
}
|
|
10646
10904
|
return null;
|
|
10647
10905
|
}
|
|
@@ -11092,6 +11350,54 @@ function isGroupServiceAid(value) {
|
|
|
11092
11350
|
const [name, ...issuerParts] = text.split(".");
|
|
11093
11351
|
return name === "group" && issuerParts.join(".").length > 0;
|
|
11094
11352
|
}
|
|
11353
|
+
function normalizeV2WrapPolicy(raw) {
|
|
11354
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
11355
|
+
const obj = raw;
|
|
11356
|
+
let protocol = String(obj.protocol ?? "").trim().toUpperCase();
|
|
11357
|
+
let scope = String(obj.scope ?? "").trim().toLowerCase();
|
|
11358
|
+
if (scope !== "aid" && scope !== "device") {
|
|
11359
|
+
if (obj.per_aid_wrap === true) scope = "aid";
|
|
11360
|
+
else if (obj.per_device_wrap === true) scope = "device";
|
|
11361
|
+
else scope = "";
|
|
11362
|
+
}
|
|
11363
|
+
if (protocol !== "1DH" && protocol !== "3DH") protocol = "";
|
|
11364
|
+
if (scope === "aid") protocol = "1DH";
|
|
11365
|
+
if (!protocol && !scope) return void 0;
|
|
11366
|
+
return {
|
|
11367
|
+
protocol: protocol ? protocol : void 0,
|
|
11368
|
+
scope: scope ? scope : void 0
|
|
11369
|
+
};
|
|
11370
|
+
}
|
|
11371
|
+
function v2WrapCapabilities() {
|
|
11372
|
+
return {
|
|
11373
|
+
version: "v2.1",
|
|
11374
|
+
protocols: ["1DH", "3DH"],
|
|
11375
|
+
scopes: ["aid", "device"],
|
|
11376
|
+
per_aid_wrap: true,
|
|
11377
|
+
per_device_wrap: true
|
|
11378
|
+
};
|
|
11379
|
+
}
|
|
11380
|
+
function applyV2WrapPolicyToTargets(targets, policy) {
|
|
11381
|
+
if (!policy) return targets;
|
|
11382
|
+
const normalized = targets.map((target) => {
|
|
11383
|
+
const row = { ...target };
|
|
11384
|
+
if (policy.protocol === "1DH") {
|
|
11385
|
+
row.keySource = "aid_master";
|
|
11386
|
+
row.spkPkDer = void 0;
|
|
11387
|
+
row.spkId = "";
|
|
11388
|
+
}
|
|
11389
|
+
return row;
|
|
11390
|
+
});
|
|
11391
|
+
if (policy.scope !== "aid") return normalized;
|
|
11392
|
+
const collapsed = /* @__PURE__ */ new Map();
|
|
11393
|
+
for (const target of normalized) {
|
|
11394
|
+
const key = `${target.aid}\0${target.role}`;
|
|
11395
|
+
if (!collapsed.has(key)) {
|
|
11396
|
+
collapsed.set(key, { ...target, deviceId: "" });
|
|
11397
|
+
}
|
|
11398
|
+
}
|
|
11399
|
+
return Array.from(collapsed.values());
|
|
11400
|
+
}
|
|
11095
11401
|
function _v2LeftPad32(b) {
|
|
11096
11402
|
if (b.length === 32) return b;
|
|
11097
11403
|
if (b.length > 32) return b.subarray(b.length - 32);
|
|
@@ -11315,7 +11621,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11315
11621
|
__publicField(this, "_agentMdPath", "");
|
|
11316
11622
|
__publicField(this, "_agentMdCache", /* @__PURE__ */ new Map());
|
|
11317
11623
|
__publicField(this, "_agentMdFetchInflight", /* @__PURE__ */ new Set());
|
|
11318
|
-
__publicField(this, "
|
|
11624
|
+
__publicField(this, "_agentMdLock", Promise.resolve());
|
|
11319
11625
|
/** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
|
|
11320
11626
|
__publicField(this, "_seqTracker", new SeqTracker());
|
|
11321
11627
|
__publicField(this, "_seqTrackerContext", null);
|
|
@@ -11329,6 +11635,8 @@ var _AUNClient = class _AUNClient {
|
|
|
11329
11635
|
__publicField(this, "_groupSynced", /* @__PURE__ */ new Set());
|
|
11330
11636
|
/** gap fill 来源标记:true 表示当前正在补洞(pull 触发),false 表示非补洞 */
|
|
11331
11637
|
__publicField(this, "_gapFillActive", false);
|
|
11638
|
+
// Pull Gate:序列化同一 key 的并发 pull 操作,防止重复拉取
|
|
11639
|
+
__publicField(this, "_pullGates", /* @__PURE__ */ new Map());
|
|
11332
11640
|
// 重连相关
|
|
11333
11641
|
__publicField(this, "_reconnectActive", false);
|
|
11334
11642
|
__publicField(this, "_reconnectAbort", null);
|
|
@@ -11372,7 +11680,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11372
11680
|
this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
|
|
11373
11681
|
this._dispatcher = new EventDispatcher();
|
|
11374
11682
|
this._discovery = new GatewayDiscovery();
|
|
11375
|
-
this._keystore = new IndexedDBKeyStore();
|
|
11683
|
+
this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? void 0 });
|
|
11376
11684
|
this._slotId = "";
|
|
11377
11685
|
this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
|
|
11378
11686
|
this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
|
|
@@ -11474,7 +11782,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11474
11782
|
}
|
|
11475
11783
|
/**
|
|
11476
11784
|
* 浏览器版本 publishAgentMd。默认从 {agentMdPath}/{self_aid}/agent.md 的等价 IndexedDB 正文读取,
|
|
11477
|
-
* 然后签名、上传,并刷新
|
|
11785
|
+
* 然后签名、上传,并刷新 agentmd.json 元数据。
|
|
11478
11786
|
*
|
|
11479
11787
|
* 兼容旧浏览器调用:传入 content 时会先写入等价正文,再从该正文发布。
|
|
11480
11788
|
*/
|
|
@@ -11516,7 +11824,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11516
11824
|
}
|
|
11517
11825
|
/**
|
|
11518
11826
|
* 浏览器版本 fetchAgentMd。aid 缺省时取自身;下载后的正文固定写入
|
|
11519
|
-
* {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,
|
|
11827
|
+
* {agentMdPath}/{aid}/agent.md 的等价 IndexedDB 正文,agentmd.json 只保存元数据。
|
|
11520
11828
|
*/
|
|
11521
11829
|
async fetchAgentMd(aid) {
|
|
11522
11830
|
const target = String(aid ?? this._aid ?? "").trim();
|
|
@@ -11588,8 +11896,8 @@ var _AUNClient = class _AUNClient {
|
|
|
11588
11896
|
}
|
|
11589
11897
|
return target;
|
|
11590
11898
|
}
|
|
11591
|
-
|
|
11592
|
-
return
|
|
11899
|
+
_agentMdMetaKey(aid) {
|
|
11900
|
+
return `${this._agentMdSafeAid(aid)}/agentmd.json`;
|
|
11593
11901
|
}
|
|
11594
11902
|
_agentMdContentKey(aid) {
|
|
11595
11903
|
return `${this._agentMdSafeAid(aid)}/agent.md`;
|
|
@@ -11621,20 +11929,13 @@ var _AUNClient = class _AUNClient {
|
|
|
11621
11929
|
fetched_at: Date.now()
|
|
11622
11930
|
});
|
|
11623
11931
|
}
|
|
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);
|
|
11932
|
+
async _withAgentMdLock(fn) {
|
|
11933
|
+
const previous = this._agentMdLock.catch(() => void 0);
|
|
11633
11934
|
let release;
|
|
11634
11935
|
const current = new Promise((resolve) => {
|
|
11635
11936
|
release = resolve;
|
|
11636
11937
|
});
|
|
11637
|
-
this.
|
|
11938
|
+
this._agentMdLock = previous.then(() => current);
|
|
11638
11939
|
await previous;
|
|
11639
11940
|
try {
|
|
11640
11941
|
return await fn();
|
|
@@ -11642,70 +11943,35 @@ var _AUNClient = class _AUNClient {
|
|
|
11642
11943
|
release();
|
|
11643
11944
|
}
|
|
11644
11945
|
}
|
|
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
|
-
}
|
|
11946
|
+
_normalizeAgentMdRecord(aid, data) {
|
|
11947
|
+
if (!isJsonObject(data)) return {};
|
|
11948
|
+
const record = {};
|
|
11949
|
+
for (const [key, value] of Object.entries(data)) {
|
|
11950
|
+
if (key !== "content") record[key] = value;
|
|
11694
11951
|
}
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11952
|
+
record.aid = this._agentMdSafeAid(String(record.aid ?? aid));
|
|
11953
|
+
for (const key of ["fetched_at", "observed_at", "checked_at", "updated_at"]) {
|
|
11954
|
+
record[key] = Number(record[key] ?? 0) || 0;
|
|
11955
|
+
}
|
|
11956
|
+
return record;
|
|
11698
11957
|
}
|
|
11699
|
-
async
|
|
11700
|
-
const
|
|
11701
|
-
|
|
11702
|
-
|
|
11958
|
+
async _writeAgentMdRecordUnlocked(aid, record) {
|
|
11959
|
+
const payload = {};
|
|
11960
|
+
for (const [key, value] of Object.entries(record)) {
|
|
11961
|
+
if (key !== "content" && value !== void 0 && value !== null) payload[key] = value;
|
|
11703
11962
|
}
|
|
11963
|
+
payload.aid = this._agentMdSafeAid(aid);
|
|
11964
|
+
await this._writeAgentMdStorage(this._agentMdMetaKey(aid), `${JSON.stringify(payload, null, 2)}
|
|
11965
|
+
`);
|
|
11966
|
+
}
|
|
11967
|
+
async _readAgentMdRecordUnlocked(aid) {
|
|
11968
|
+
const raw = await this._readAgentMdStorage(this._agentMdMetaKey(aid));
|
|
11969
|
+
if (raw === null) return {};
|
|
11704
11970
|
try {
|
|
11705
|
-
return this.
|
|
11971
|
+
return this._normalizeAgentMdRecord(aid, JSON.parse(raw));
|
|
11706
11972
|
} catch (err) {
|
|
11707
|
-
this._clientLog.warn(`agent.md
|
|
11708
|
-
return
|
|
11973
|
+
this._clientLog.warn(`agent.md metadata damaged, ignoring: aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
11974
|
+
return {};
|
|
11709
11975
|
}
|
|
11710
11976
|
}
|
|
11711
11977
|
async _readAgentMdContent(aid) {
|
|
@@ -11727,20 +11993,28 @@ var _AUNClient = class _AUNClient {
|
|
|
11727
11993
|
const target = String(aid ?? "").trim();
|
|
11728
11994
|
if (!target) return null;
|
|
11729
11995
|
try {
|
|
11730
|
-
const
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11996
|
+
const loaded = await this._withAgentMdLock(async () => {
|
|
11997
|
+
const record = await this._readAgentMdRecordUnlocked(target);
|
|
11998
|
+
const next = Object.keys(record).length > 0 ? { ...record, aid: target } : { aid: target };
|
|
11999
|
+
try {
|
|
12000
|
+
const content = await this._readAgentMdContent(target);
|
|
12001
|
+
if (content !== null) {
|
|
12002
|
+
next.content = content;
|
|
12003
|
+
next.local_etag = await this._agentMdContentEtag(content);
|
|
12004
|
+
} else {
|
|
12005
|
+
const metaRaw = await this._readAgentMdStorage(this._agentMdMetaKey(target));
|
|
12006
|
+
if (metaRaw !== null) {
|
|
12007
|
+
this._clientLog.warn(`agent.md content read failed: aid=${target}`);
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
12010
|
+
} catch (err) {
|
|
12011
|
+
this._clientLog.warn(`agent.md content read failed: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
|
|
11740
12012
|
}
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
12013
|
+
return next;
|
|
12014
|
+
});
|
|
12015
|
+
if (Object.keys(loaded).length <= 1) return null;
|
|
12016
|
+
this._agentMdCache.set(target, { ...loaded });
|
|
12017
|
+
return { ...loaded };
|
|
11744
12018
|
} catch (err) {
|
|
11745
12019
|
this._clientLog.debug(`agent.md cache load skipped: aid=${target} err=${err instanceof Error ? err.message : String(err)}`);
|
|
11746
12020
|
}
|
|
@@ -11759,15 +12033,13 @@ var _AUNClient = class _AUNClient {
|
|
|
11759
12033
|
if (!inputFields.fetched_at) inputFields.fetched_at = Date.now();
|
|
11760
12034
|
}
|
|
11761
12035
|
delete inputFields.content;
|
|
11762
|
-
const record = await this.
|
|
11763
|
-
const
|
|
11764
|
-
const next = { ...records[target] ?? {}, aid: target };
|
|
12036
|
+
const record = await this._withAgentMdLock(async () => {
|
|
12037
|
+
const next = { ...await this._readAgentMdRecordUnlocked(target), aid: target };
|
|
11765
12038
|
for (const [key, value] of Object.entries(inputFields)) {
|
|
11766
12039
|
if (value !== void 0 && value !== null) next[key] = value;
|
|
11767
12040
|
}
|
|
11768
12041
|
next.updated_at = Date.now();
|
|
11769
|
-
|
|
11770
|
-
await this._writeAgentMdListUnlocked(records);
|
|
12042
|
+
await this._writeAgentMdRecordUnlocked(target, next);
|
|
11771
12043
|
return next;
|
|
11772
12044
|
});
|
|
11773
12045
|
const loaded = { ...record };
|
|
@@ -12219,6 +12491,19 @@ var _AUNClient = class _AUNClient {
|
|
|
12219
12491
|
return this._putMessageThoughtEncryptedV2(p);
|
|
12220
12492
|
}
|
|
12221
12493
|
}
|
|
12494
|
+
const pullGateKey = this._pullGateKeyForCall(method, p);
|
|
12495
|
+
if (pullGateKey) {
|
|
12496
|
+
return await this._runPullSerialized(pullGateKey, async () => {
|
|
12497
|
+
return await this._callImplInner(method, p);
|
|
12498
|
+
});
|
|
12499
|
+
}
|
|
12500
|
+
return await this._callImplInner(method, p);
|
|
12501
|
+
}
|
|
12502
|
+
/**
|
|
12503
|
+
* _callImpl 的内层:pull gate 之后的实际 RPC 分发逻辑。
|
|
12504
|
+
* 拆分出来以便 pull gate 包裹整个操作。
|
|
12505
|
+
*/
|
|
12506
|
+
async _callImplInner(method, p) {
|
|
12222
12507
|
if (method === "message.pull" && this._v2Session) {
|
|
12223
12508
|
this._clientLog.debug("call route: message.pull \u2192 V2 pull");
|
|
12224
12509
|
const messages = await this.pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50);
|
|
@@ -14786,7 +15071,10 @@ var _AUNClient = class _AUNClient {
|
|
|
14786
15071
|
const session = this._v2Session;
|
|
14787
15072
|
if (session && fromAid) {
|
|
14788
15073
|
try {
|
|
14789
|
-
const bs = await this.call("message.v2.bootstrap", {
|
|
15074
|
+
const bs = await this.call("message.v2.bootstrap", {
|
|
15075
|
+
peer_aid: fromAid,
|
|
15076
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15077
|
+
});
|
|
14790
15078
|
const peers = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
|
|
14791
15079
|
for (const dev of peers) this._cacheV2PeerIKFromDevice(dev, fromAid);
|
|
14792
15080
|
} catch (exc) {
|
|
@@ -14794,7 +15082,10 @@ var _AUNClient = class _AUNClient {
|
|
|
14794
15082
|
}
|
|
14795
15083
|
if (groupId) {
|
|
14796
15084
|
try {
|
|
14797
|
-
const gbs = await this.call("group.v2.bootstrap", {
|
|
15085
|
+
const gbs = await this.call("group.v2.bootstrap", {
|
|
15086
|
+
group_id: groupId,
|
|
15087
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15088
|
+
});
|
|
14798
15089
|
const devices = Array.isArray(gbs?.devices) ? gbs.devices : [];
|
|
14799
15090
|
const audit = Array.isArray(gbs?.audit_recipients) ? gbs.audit_recipients : [];
|
|
14800
15091
|
for (const dev of devices) this._cacheV2PeerIKFromDevice(dev);
|
|
@@ -15040,7 +15331,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15040
15331
|
spkId = String(msg.spk_id ?? "");
|
|
15041
15332
|
if (!spkId) {
|
|
15042
15333
|
for (const row of envelope.recipients) {
|
|
15043
|
-
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
|
|
15334
|
+
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
|
|
15044
15335
|
spkId = String(row[5] ?? "");
|
|
15045
15336
|
recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
|
|
15046
15337
|
break;
|
|
@@ -15048,7 +15339,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15048
15339
|
}
|
|
15049
15340
|
} else {
|
|
15050
15341
|
for (const row of envelope.recipients) {
|
|
15051
|
-
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && row[1] === this._deviceId) {
|
|
15342
|
+
if (Array.isArray(row) && row.length >= 6 && row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
|
|
15052
15343
|
recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
|
|
15053
15344
|
break;
|
|
15054
15345
|
}
|
|
@@ -15397,19 +15688,26 @@ var _AUNClient = class _AUNClient {
|
|
|
15397
15688
|
const useCache = opts.useCache !== false;
|
|
15398
15689
|
let peerDevices = [];
|
|
15399
15690
|
let auditRaw = [];
|
|
15691
|
+
let wrapPolicy;
|
|
15400
15692
|
const cached = useCache ? this._v2BootstrapCache.get(to) : void 0;
|
|
15401
15693
|
if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
|
|
15402
15694
|
peerDevices = cached.devices;
|
|
15403
15695
|
auditRaw = cached.auditRecipients;
|
|
15696
|
+
wrapPolicy = cached.wrapPolicy;
|
|
15404
15697
|
} else {
|
|
15405
|
-
const bs = await this.call("message.v2.bootstrap", {
|
|
15698
|
+
const bs = await this.call("message.v2.bootstrap", {
|
|
15699
|
+
peer_aid: to,
|
|
15700
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15701
|
+
});
|
|
15406
15702
|
peerDevices = Array.isArray(bs?.peer_devices) ? bs.peer_devices : [];
|
|
15407
15703
|
auditRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
|
|
15704
|
+
wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
|
|
15408
15705
|
if (peerDevices.length > 0) {
|
|
15409
15706
|
this._v2BootstrapCache.set(to, {
|
|
15410
15707
|
devices: peerDevices,
|
|
15411
15708
|
auditRecipients: auditRaw,
|
|
15412
|
-
cachedAt: Date.now()
|
|
15709
|
+
cachedAt: Date.now(),
|
|
15710
|
+
wrapPolicy
|
|
15413
15711
|
});
|
|
15414
15712
|
}
|
|
15415
15713
|
}
|
|
@@ -15445,7 +15743,10 @@ var _AUNClient = class _AUNClient {
|
|
|
15445
15743
|
if (selfCached && Date.now() - selfCached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
|
|
15446
15744
|
selfDevices = selfCached.devices;
|
|
15447
15745
|
} else {
|
|
15448
|
-
const selfBs = await this.call("message.v2.bootstrap", {
|
|
15746
|
+
const selfBs = await this.call("message.v2.bootstrap", {
|
|
15747
|
+
peer_aid: this._aid,
|
|
15748
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15749
|
+
});
|
|
15449
15750
|
selfDevices = Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : [];
|
|
15450
15751
|
if (selfDevices.length > 0) {
|
|
15451
15752
|
this._v2BootstrapCache.set(this._aid, {
|
|
@@ -15472,9 +15773,10 @@ var _AUNClient = class _AUNClient {
|
|
|
15472
15773
|
}
|
|
15473
15774
|
}
|
|
15474
15775
|
const sender = await session.getSenderIdentity();
|
|
15776
|
+
const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
|
|
15475
15777
|
const envelope = await encryptP2PMessage(
|
|
15476
15778
|
sender,
|
|
15477
|
-
{ targets, auditRecipients: [] },
|
|
15779
|
+
{ targets: sendTargets, auditRecipients: [] },
|
|
15478
15780
|
opts.payload,
|
|
15479
15781
|
{ messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }
|
|
15480
15782
|
);
|
|
@@ -15547,17 +15849,23 @@ var _AUNClient = class _AUNClient {
|
|
|
15547
15849
|
let epoch = 0;
|
|
15548
15850
|
let stateCommitment = { state_version: 0, state_hash: "", state_chain: "" };
|
|
15549
15851
|
let auditRecipientsRaw = [];
|
|
15852
|
+
let wrapPolicy;
|
|
15550
15853
|
const cached = useCache ? this._v2BootstrapCache.get(cacheKey) : void 0;
|
|
15551
15854
|
if (cached && Date.now() - cached.cachedAt < _AUNClient.V2_BOOTSTRAP_TTL_MS) {
|
|
15552
15855
|
allDevices = cached.devices;
|
|
15553
15856
|
epoch = cached.epoch ?? 0;
|
|
15554
15857
|
stateCommitment = cached.stateCommitment ?? { state_version: 0, state_hash: "", state_chain: "" };
|
|
15555
15858
|
auditRecipientsRaw = cached.auditRecipients;
|
|
15859
|
+
wrapPolicy = cached.wrapPolicy;
|
|
15556
15860
|
} else {
|
|
15557
|
-
const bs = await this.call("group.v2.bootstrap", {
|
|
15861
|
+
const bs = await this.call("group.v2.bootstrap", {
|
|
15862
|
+
group_id: groupId,
|
|
15863
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
15864
|
+
});
|
|
15558
15865
|
allDevices = Array.isArray(bs?.devices) ? bs.devices : [];
|
|
15559
15866
|
epoch = Number(bs?.epoch ?? 0);
|
|
15560
15867
|
auditRecipientsRaw = Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : [];
|
|
15868
|
+
wrapPolicy = normalizeV2WrapPolicy(bs?.e2ee_wrap_policy);
|
|
15561
15869
|
await this._v2CheckFork(groupId, String(bs?.state_chain ?? ""));
|
|
15562
15870
|
await this._v2VerifyStateSignature(groupId, bs);
|
|
15563
15871
|
await this._publishV2GroupSecurityLevel(groupId, bs);
|
|
@@ -15572,7 +15880,8 @@ var _AUNClient = class _AUNClient {
|
|
|
15572
15880
|
auditRecipients: auditRecipientsRaw,
|
|
15573
15881
|
cachedAt: Date.now(),
|
|
15574
15882
|
epoch,
|
|
15575
|
-
stateCommitment
|
|
15883
|
+
stateCommitment,
|
|
15884
|
+
wrapPolicy
|
|
15576
15885
|
});
|
|
15577
15886
|
}
|
|
15578
15887
|
const pendingAdds = Array.isArray(bs?.pending_adds) ? bs.pending_adds : [];
|
|
@@ -15612,11 +15921,12 @@ var _AUNClient = class _AUNClient {
|
|
|
15612
15921
|
throw new E2EEError(`V2 group: no target devices for ${groupId}`);
|
|
15613
15922
|
}
|
|
15614
15923
|
const sender = await session.getSenderIdentity();
|
|
15924
|
+
const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
|
|
15615
15925
|
const envelope = await encryptGroupMessage(
|
|
15616
15926
|
sender,
|
|
15617
15927
|
groupId,
|
|
15618
15928
|
epoch,
|
|
15619
|
-
|
|
15929
|
+
sendTargets,
|
|
15620
15930
|
opts.payload,
|
|
15621
15931
|
{ messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context },
|
|
15622
15932
|
stateCommitment
|
|
@@ -15697,7 +16007,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15697
16007
|
if (Array.isArray(recipients)) {
|
|
15698
16008
|
for (const row of recipients) {
|
|
15699
16009
|
if (Array.isArray(row) && row.length >= 6) {
|
|
15700
|
-
if (row[0] === this._aid && row[1] === this._deviceId) {
|
|
16010
|
+
if (row[0] === this._aid && (row[1] === this._deviceId || row[1] === "")) {
|
|
15701
16011
|
spkId = String(row[5] ?? "");
|
|
15702
16012
|
recipientKeySource = row.length > 3 ? String(row[3] ?? "") : "";
|
|
15703
16013
|
break;
|
|
@@ -15976,7 +16286,10 @@ var _AUNClient = class _AUNClient {
|
|
|
15976
16286
|
if (aid === myAid) myRole = role;
|
|
15977
16287
|
}
|
|
15978
16288
|
if (myRole !== "owner" && myRole !== "admin") return false;
|
|
15979
|
-
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16289
|
+
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16290
|
+
group_id: groupId,
|
|
16291
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
16292
|
+
});
|
|
15980
16293
|
const devices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
|
|
15981
16294
|
const candidates = [];
|
|
15982
16295
|
for (const dev of devices) {
|
|
@@ -16065,7 +16378,10 @@ var _AUNClient = class _AUNClient {
|
|
|
16065
16378
|
}
|
|
16066
16379
|
}
|
|
16067
16380
|
}
|
|
16068
|
-
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16381
|
+
const bootstrapResp = await this.call("group.v2.bootstrap", {
|
|
16382
|
+
group_id: groupId,
|
|
16383
|
+
e2ee_wrap_capabilities: v2WrapCapabilities()
|
|
16384
|
+
});
|
|
16069
16385
|
const allDevices = Array.isArray(bootstrapResp?.devices) ? bootstrapResp.devices : [];
|
|
16070
16386
|
const auditRecipients = Array.isArray(bootstrapResp?.audit_recipients) ? bootstrapResp.audit_recipients : [];
|
|
16071
16387
|
const auditAidsList = [...new Set(
|
|
@@ -16374,6 +16690,62 @@ var _AUNClient = class _AUNClient {
|
|
|
16374
16690
|
this._clientLog.warn(`background task exception:${String(exc)}`);
|
|
16375
16691
|
});
|
|
16376
16692
|
}
|
|
16693
|
+
// ── Pull Gate(序列化同一 key 的并发 pull)──────────────────
|
|
16694
|
+
_pullGateKeyForCall(method, params) {
|
|
16695
|
+
if (method === "message.pull" || method === "message.v2.pull") {
|
|
16696
|
+
return this._aid ? `p2p:${this._aid}` : "";
|
|
16697
|
+
}
|
|
16698
|
+
if (method === "group.pull" || method === "group.v2.pull") {
|
|
16699
|
+
const gid = String(params.group_id ?? "").trim();
|
|
16700
|
+
return gid ? `group:${gid}` : "";
|
|
16701
|
+
}
|
|
16702
|
+
if (method === "group.pull_events") {
|
|
16703
|
+
const gid = String(params.group_id ?? "").trim();
|
|
16704
|
+
return gid ? `group_event:${gid}` : "";
|
|
16705
|
+
}
|
|
16706
|
+
return "";
|
|
16707
|
+
}
|
|
16708
|
+
_tryAcquirePullGate(key) {
|
|
16709
|
+
if (!key) return 0;
|
|
16710
|
+
const now = Date.now();
|
|
16711
|
+
const gate = this._pullGates.get(key) ?? { inflight: false, startedAt: 0, token: 0 };
|
|
16712
|
+
if (gate.inflight && now - gate.startedAt <= _AUNClient._PULL_GATE_STALE_MS) {
|
|
16713
|
+
return null;
|
|
16714
|
+
}
|
|
16715
|
+
if (gate.inflight) {
|
|
16716
|
+
this._clientLog.warn(`pull in-flight stale reset: key=${key} age=${now - gate.startedAt}ms`);
|
|
16717
|
+
}
|
|
16718
|
+
gate.token += 1;
|
|
16719
|
+
gate.inflight = true;
|
|
16720
|
+
gate.startedAt = now;
|
|
16721
|
+
this._pullGates.set(key, gate);
|
|
16722
|
+
return gate.token;
|
|
16723
|
+
}
|
|
16724
|
+
_releasePullGate(key, token) {
|
|
16725
|
+
if (!key || token == null) return;
|
|
16726
|
+
const gate = this._pullGates.get(key);
|
|
16727
|
+
if (!gate || gate.token !== token) return;
|
|
16728
|
+
gate.inflight = false;
|
|
16729
|
+
gate.startedAt = 0;
|
|
16730
|
+
}
|
|
16731
|
+
async _runPullSerialized(key, operation) {
|
|
16732
|
+
let token = this._tryAcquirePullGate(key);
|
|
16733
|
+
if (token === null) {
|
|
16734
|
+
const deadline = Date.now() + _AUNClient._PULL_GATE_STALE_MS + 100;
|
|
16735
|
+
while (token === null && Date.now() <= deadline) {
|
|
16736
|
+
await this._sleep(25);
|
|
16737
|
+
token = this._tryAcquirePullGate(key);
|
|
16738
|
+
}
|
|
16739
|
+
if (token === null) {
|
|
16740
|
+
throw new StateError(`pull already in-flight for ${key}`);
|
|
16741
|
+
}
|
|
16742
|
+
}
|
|
16743
|
+
try {
|
|
16744
|
+
return await operation();
|
|
16745
|
+
} finally {
|
|
16746
|
+
this._releasePullGate(key, token);
|
|
16747
|
+
}
|
|
16748
|
+
}
|
|
16377
16749
|
/** 可取消的 sleep */
|
|
16378
16750
|
_sleep(ms) {
|
|
16379
16751
|
return new Promise((resolve) => {
|
|
@@ -16385,6 +16757,7 @@ __publicField(_AUNClient, "V2_BOOTSTRAP_TTL_MS", 60 * 60 * 1e3);
|
|
|
16385
16757
|
__publicField(_AUNClient, "V2_RETRYABLE_CODES", /* @__PURE__ */ new Set([-33011, -33012, -33050, -33052, -33054]));
|
|
16386
16758
|
__publicField(_AUNClient, "_V2_SIG_CACHE_TTL", 60 * 60 * 1e3);
|
|
16387
16759
|
__publicField(_AUNClient, "_V2_SIG_CACHE_MAX", 16384);
|
|
16760
|
+
__publicField(_AUNClient, "_PULL_GATE_STALE_MS", 3e4);
|
|
16388
16761
|
// ── 内部:断线重连 ────────────────────────────────
|
|
16389
16762
|
/** 不重连 close code 集合:认证失败/权限错误/被踢等,重连无意义 */
|
|
16390
16763
|
__publicField(_AUNClient, "_NO_RECONNECT_CODES", /* @__PURE__ */ new Set([4001, 4003, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015]));
|
|
@@ -16455,7 +16828,7 @@ var ProtectedHeaders = class _ProtectedHeaders {
|
|
|
16455
16828
|
};
|
|
16456
16829
|
|
|
16457
16830
|
// src/index.ts
|
|
16458
|
-
var __version__ = "0.3.
|
|
16831
|
+
var __version__ = "0.3.5";
|
|
16459
16832
|
export {
|
|
16460
16833
|
AUNClient,
|
|
16461
16834
|
AUNError,
|
|
@@ -16480,6 +16853,7 @@ export {
|
|
|
16480
16853
|
GroupError,
|
|
16481
16854
|
GroupNotFoundError,
|
|
16482
16855
|
GroupStateError,
|
|
16856
|
+
IdentityConflictError,
|
|
16483
16857
|
IndexedDBKeyStore,
|
|
16484
16858
|
IndexedDBSecretStore,
|
|
16485
16859
|
MetaNamespace,
|
|
@@ -16490,6 +16864,7 @@ export {
|
|
|
16490
16864
|
RPCTransport,
|
|
16491
16865
|
RateLimitError,
|
|
16492
16866
|
STATE_PREFIX,
|
|
16867
|
+
SeedMigrationError,
|
|
16493
16868
|
SerializationError,
|
|
16494
16869
|
SessionError,
|
|
16495
16870
|
StateError,
|