@agentunion/fastaun-browser 0.4.3 → 0.4.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 +203 -178
- package/_packed_docs/CHANGELOG.md +203 -178
- package/_packed_docs/INDEX.md +17 -17
- package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
- package/_packed_docs/agent.md/SCHEMA.md +49 -49
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
- 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 +327 -327
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
- package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
- 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 -124
- 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 -665
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +2 -2
- package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -170
- package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +419 -419
- package/_packed_docs/protocol/README.md +1 -1
- package/_packed_docs/protocol/aun-docs-guide.md +1 -1
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
- package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +4 -4
- package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +98 -98
- package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +46 -46
- 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 -257
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1 -0
- package/_packed_docs/sdk/09-payload-reference.md +13 -13
- 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 -171
- package/dist/aid-store.d.ts +1 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +26 -9
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +2 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +7 -6
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +8 -13
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +38 -127
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +872 -350
- package/dist/client.d.ts +12 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +296 -213
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +45 -22
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/index.js +6 -1
- package/dist/keystore/index.js.map +1 -1
- package/dist/keystore/indexeddb.d.ts +11 -1
- package/dist/keystore/indexeddb.d.ts.map +1 -1
- package/dist/keystore/indexeddb.js +167 -18
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/register-flow.d.ts +34 -0
- package/dist/register-flow.d.ts.map +1 -0
- package/dist/register-flow.js +355 -0
- package/dist/register-flow.js.map +1 -0
- package/dist/v2/session/keystore.d.ts +5 -0
- package/dist/v2/session/keystore.d.ts.map +1 -1
- package/dist/v2/session/keystore.js +29 -0
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/_packed_docs/0.4.0_/345/267/256/345/274/202/346/240/270/345/256/236/345/206/263/347/255/226/350/256/260/345/275/225.md +0 -302
- package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +0 -194
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +0 -596
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +0 -1697
- package/_packed_docs/python-sdk-v2-only-changelog.md +0 -189
package/dist/bundle.js
CHANGED
|
@@ -314,12 +314,12 @@ async function secretPut(storeName, key, value) {
|
|
|
314
314
|
tx.oncomplete = () => db.close();
|
|
315
315
|
});
|
|
316
316
|
}
|
|
317
|
-
var
|
|
317
|
+
var _noopLog7, SECRET_DB_NAME, SECRET_DB_VERSION, STORE_SECRETS, STORE_MASTER, IndexedDBSecretStore;
|
|
318
318
|
var init_indexeddb_store = __esm({
|
|
319
319
|
"src/secret-store/indexeddb-store.ts"() {
|
|
320
320
|
"use strict";
|
|
321
321
|
init_crypto();
|
|
322
|
-
|
|
322
|
+
_noopLog7 = { error: () => {
|
|
323
323
|
}, warn: () => {
|
|
324
324
|
}, info: () => {
|
|
325
325
|
}, debug: () => {
|
|
@@ -332,7 +332,7 @@ var init_indexeddb_store = __esm({
|
|
|
332
332
|
constructor(encryptionSeed) {
|
|
333
333
|
__publicField(this, "_encryptionSeed");
|
|
334
334
|
__publicField(this, "_masterKeyPromise", null);
|
|
335
|
-
__publicField(this, "_log",
|
|
335
|
+
__publicField(this, "_log", _noopLog7);
|
|
336
336
|
this._encryptionSeed = encryptionSeed;
|
|
337
337
|
}
|
|
338
338
|
setLogger(log) {
|
|
@@ -454,7 +454,7 @@ var init_indexeddb_store = __esm({
|
|
|
454
454
|
});
|
|
455
455
|
|
|
456
456
|
// src/version.ts
|
|
457
|
-
var VERSION = "0.4.
|
|
457
|
+
var VERSION = "0.4.5";
|
|
458
458
|
|
|
459
459
|
// src/types.ts
|
|
460
460
|
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
@@ -2031,13 +2031,14 @@ function gatewayHttpUrl(gatewayUrl, path) {
|
|
|
2031
2031
|
var _AuthFlow = class _AuthFlow {
|
|
2032
2032
|
constructor(opts) {
|
|
2033
2033
|
__publicField(this, "_log", _noopLog4);
|
|
2034
|
-
__publicField(this, "
|
|
2034
|
+
__publicField(this, "_tokenStore");
|
|
2035
2035
|
__publicField(this, "_crypto");
|
|
2036
2036
|
__publicField(this, "_aid");
|
|
2037
2037
|
__publicField(this, "_deviceId");
|
|
2038
2038
|
__publicField(this, "_slotId");
|
|
2039
2039
|
__publicField(this, "_rootCaPem");
|
|
2040
2040
|
__publicField(this, "_verifySsl");
|
|
2041
|
+
__publicField(this, "_memIdentity", null);
|
|
2041
2042
|
// 缓存
|
|
2042
2043
|
__publicField(this, "_rootCerts", null);
|
|
2043
2044
|
__publicField(this, "_gatewayChainCache", /* @__PURE__ */ new Map());
|
|
@@ -2046,7 +2047,7 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2046
2047
|
__publicField(this, "_chainVerifiedCache", /* @__PURE__ */ new Map());
|
|
2047
2048
|
__publicField(this, "_chainCacheTtl");
|
|
2048
2049
|
__publicField(this, "_gatewayCaVerified", /* @__PURE__ */ new Map());
|
|
2049
|
-
this.
|
|
2050
|
+
this._tokenStore = opts.tokenStore;
|
|
2050
2051
|
this._crypto = opts.crypto;
|
|
2051
2052
|
this._aid = opts.aid ?? null;
|
|
2052
2053
|
this._deviceId = String(opts.deviceId ?? "").trim();
|
|
@@ -2059,13 +2060,18 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2059
2060
|
this._log = log;
|
|
2060
2061
|
}
|
|
2061
2062
|
// ── 公开 API ──────────────────────────────────────
|
|
2063
|
+
/** 注入内存私钥,禁止 AuthFlow 内部再走 tokenStore 解密 */
|
|
2064
|
+
setIdentity(identity) {
|
|
2065
|
+
this._memIdentity = identity;
|
|
2066
|
+
if (identity?.aid) this._aid = String(identity.aid);
|
|
2067
|
+
}
|
|
2062
2068
|
/** 加载本地身份信息 */
|
|
2063
2069
|
async loadIdentity(aid) {
|
|
2064
2070
|
const tStart = Date.now();
|
|
2065
2071
|
this._log.debug(`loadIdentity enter: aid=${aid ?? "<current>"}`);
|
|
2066
2072
|
try {
|
|
2067
2073
|
const identity = await this._loadIdentityOrRaise(aid);
|
|
2068
|
-
const cert = await this.
|
|
2074
|
+
const cert = await this._tokenStore.loadCert(identity.aid);
|
|
2069
2075
|
if (cert) identity.cert = cert;
|
|
2070
2076
|
const instanceState = await this._loadInstanceState(identity.aid);
|
|
2071
2077
|
if (instanceState) {
|
|
@@ -2104,93 +2110,10 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2104
2110
|
this._deviceId = String(opts.deviceId ?? "").trim();
|
|
2105
2111
|
this._slotId = String(opts.slotId ?? "").trim();
|
|
2106
2112
|
}
|
|
2107
|
-
/**
|
|
2108
|
-
* 严格注册新 AID(对齐 TS registerAid / Go RegisterAID)。
|
|
2109
|
-
*
|
|
2110
|
-
* 注册与认证彻底分离:此方法绝不被 SDK 内部自动调用,
|
|
2111
|
-
* 必须由应用层显式调用。
|
|
2112
|
-
*/
|
|
2113
|
-
async registerAid(gatewayUrl, aid) {
|
|
2114
|
-
const tStart = Date.now();
|
|
2115
|
-
this._log.debug(`registerAid enter: aid=${aid} gateway=${gatewayUrl}`);
|
|
2116
|
-
_AuthFlow._validateAidName(aid);
|
|
2117
|
-
try {
|
|
2118
|
-
const existing = await this._keystore.loadIdentity(aid);
|
|
2119
|
-
if (existing && existing.private_key_pem && existing.public_key_der_b64) {
|
|
2120
|
-
this._log.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
|
|
2121
|
-
const localPubB642 = String(existing.public_key_der_b64);
|
|
2122
|
-
const serverCertPem2 = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
2123
|
-
if (serverCertPem2) {
|
|
2124
|
-
const serverCert = parseCertDer(serverCertPem2);
|
|
2125
|
-
const serverPubB64 = uint8ToBase64(serverCert.spkiBytes);
|
|
2126
|
-
if (serverPubB64 !== localPubB642) {
|
|
2127
|
-
throw new IdentityConflictError(
|
|
2128
|
-
`AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
|
|
2129
|
-
);
|
|
2130
|
-
}
|
|
2131
|
-
this._log.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
|
|
2132
|
-
if (!existing.cert) {
|
|
2133
|
-
existing.cert = serverCertPem2;
|
|
2134
|
-
await this._persistIdentity(existing);
|
|
2135
|
-
}
|
|
2136
|
-
this._aid = aid;
|
|
2137
|
-
return { aid, cert: serverCertPem2 };
|
|
2138
|
-
} else {
|
|
2139
|
-
this._log.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
|
|
2140
|
-
const created2 = await this._createAid(gatewayUrl, existing);
|
|
2141
|
-
const certPem2 = String(created2.cert ?? "");
|
|
2142
|
-
if (!certPem2) {
|
|
2143
|
-
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
2144
|
-
}
|
|
2145
|
-
existing.cert = certPem2;
|
|
2146
|
-
const returnedCert2 = parseCertDer(certPem2);
|
|
2147
|
-
const certPubB642 = uint8ToBase64(returnedCert2.spkiBytes);
|
|
2148
|
-
if (certPubB642 !== localPubB642) {
|
|
2149
|
-
throw new AuthError(
|
|
2150
|
-
`registerAid: server returned certificate with mismatched public key for ${aid}`
|
|
2151
|
-
);
|
|
2152
|
-
}
|
|
2153
|
-
await this._persistIdentity(existing);
|
|
2154
|
-
this._aid = aid;
|
|
2155
|
-
this._log.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
2156
|
-
return { aid, cert: certPem2 };
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
2160
|
-
if (serverCertPem) {
|
|
2161
|
-
throw new IdentityConflictError(
|
|
2162
|
-
`AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
|
|
2163
|
-
);
|
|
2164
|
-
}
|
|
2165
|
-
const identity = await this._crypto.generateIdentity();
|
|
2166
|
-
identity.aid = aid;
|
|
2167
|
-
const created = await this._createAid(gatewayUrl, identity);
|
|
2168
|
-
const certPem = String(created.cert ?? "");
|
|
2169
|
-
if (!certPem) {
|
|
2170
|
-
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
2171
|
-
}
|
|
2172
|
-
identity.cert = certPem;
|
|
2173
|
-
const returnedCert = parseCertDer(certPem);
|
|
2174
|
-
const certPubB64 = uint8ToBase64(returnedCert.spkiBytes);
|
|
2175
|
-
const localPubB64 = String(identity.public_key_der_b64);
|
|
2176
|
-
if (certPubB64 !== localPubB64) {
|
|
2177
|
-
throw new AuthError(
|
|
2178
|
-
`registerAid: server returned certificate with mismatched public key for ${aid}`
|
|
2179
|
-
);
|
|
2180
|
-
}
|
|
2181
|
-
await this._persistIdentity(identity);
|
|
2182
|
-
this._aid = aid;
|
|
2183
|
-
this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
2184
|
-
return { aid: identity.aid, cert: identity.cert };
|
|
2185
|
-
} catch (err) {
|
|
2186
|
-
this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
2187
|
-
throw err;
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
2113
|
/**
|
|
2191
2114
|
* 认证已有 AID — login1/login2 双阶段流程。
|
|
2192
2115
|
*
|
|
2193
|
-
* 优先复用
|
|
2116
|
+
* 优先复用 tokenStore 里的 cached access_token(未过期且有 refresh_token),
|
|
2194
2117
|
* 避免每次 authenticate 都走两阶段重登的网络往返。与 Python SDK 行为对齐。
|
|
2195
2118
|
*/
|
|
2196
2119
|
async authenticate(gatewayUrl, aid) {
|
|
@@ -2584,15 +2507,6 @@ var _AuthFlow = class _AuthFlow {
|
|
|
2584
2507
|
clearTimeout(timeoutId);
|
|
2585
2508
|
}
|
|
2586
2509
|
}
|
|
2587
|
-
// ── 内部方法:AID 创建 ───────────────────────────
|
|
2588
|
-
async _createAid(gatewayUrl, identity) {
|
|
2589
|
-
const response = await this._shortRpc(gatewayUrl, "auth.create_aid", {
|
|
2590
|
-
aid: identity.aid,
|
|
2591
|
-
public_key: identity.public_key_der_b64,
|
|
2592
|
-
curve: identity.curve ?? "P-256"
|
|
2593
|
-
});
|
|
2594
|
-
return { cert: response.cert };
|
|
2595
|
-
}
|
|
2596
2510
|
/**
|
|
2597
2511
|
* 从服务端下载指定 AID 的证书(公开 API)。
|
|
2598
2512
|
*
|
|
@@ -3104,49 +3018,53 @@ var _AuthFlow = class _AuthFlow {
|
|
|
3104
3018
|
/** 加载身份,不存在或半成品时抛出异常 */
|
|
3105
3019
|
async _loadIdentityOrRaise(aid) {
|
|
3106
3020
|
const requestedAid = aid ?? this._aid;
|
|
3107
|
-
if (
|
|
3108
|
-
const
|
|
3109
|
-
if (
|
|
3110
|
-
throw new StateError(`identity
|
|
3021
|
+
if (this._memIdentity) {
|
|
3022
|
+
const mem = this._memIdentity;
|
|
3023
|
+
if (requestedAid && String(mem.aid ?? "") !== requestedAid) {
|
|
3024
|
+
throw new StateError(`identity mismatch: requested ${requestedAid}, loaded ${mem.aid}`);
|
|
3111
3025
|
}
|
|
3112
|
-
if (!
|
|
3113
|
-
throw new StateError(
|
|
3114
|
-
`local identity for aid ${requestedAid} is incomplete (missing keypair); call auth.registerAid() first`
|
|
3115
|
-
);
|
|
3026
|
+
if (!mem.private_key_pem || !mem.public_key_der_b64) {
|
|
3027
|
+
throw new StateError(`injected identity for aid ${mem.aid} is incomplete (missing keypair)`);
|
|
3116
3028
|
}
|
|
3117
|
-
this._aid = requestedAid;
|
|
3118
|
-
|
|
3119
|
-
|
|
3029
|
+
if (requestedAid) this._aid = requestedAid;
|
|
3030
|
+
return { ...mem };
|
|
3031
|
+
}
|
|
3032
|
+
if (requestedAid) {
|
|
3033
|
+
throw new StateError(
|
|
3034
|
+
`no injected identity for aid ${requestedAid}; call AUNClient.loadIdentity(aid) first`
|
|
3035
|
+
);
|
|
3120
3036
|
}
|
|
3121
|
-
throw new StateError("no local identity found, call
|
|
3037
|
+
throw new StateError("no local identity found, call AUNClient.loadIdentity(aid) first");
|
|
3122
3038
|
}
|
|
3123
3039
|
// (_ensureIdentity 已移除:注册和登录彻底分离)
|
|
3124
3040
|
async _loadInstanceState(aid) {
|
|
3125
|
-
if (typeof this.
|
|
3041
|
+
if (typeof this._tokenStore.loadInstanceState !== "function") {
|
|
3126
3042
|
return null;
|
|
3127
3043
|
}
|
|
3128
|
-
return await this.
|
|
3044
|
+
return await this._tokenStore.loadInstanceState(aid, this._deviceId, this._slotId);
|
|
3129
3045
|
}
|
|
3130
3046
|
async _persistIdentity(identity) {
|
|
3131
3047
|
const aid = String(identity.aid ?? "");
|
|
3132
3048
|
if (!aid) {
|
|
3133
3049
|
throw new StateError("identity missing aid");
|
|
3134
3050
|
}
|
|
3135
|
-
const persisted = { ...identity };
|
|
3136
3051
|
const instanceState = {};
|
|
3137
3052
|
const instanceStateRecord = instanceState;
|
|
3138
|
-
const persistedRecord =
|
|
3053
|
+
const persistedRecord = { ...identity };
|
|
3139
3054
|
for (const key of _AuthFlow._INSTANCE_STATE_FIELDS) {
|
|
3140
|
-
if (key in
|
|
3055
|
+
if (key in persistedRecord) {
|
|
3141
3056
|
instanceStateRecord[key] = persistedRecord[key];
|
|
3142
3057
|
delete persistedRecord[key];
|
|
3143
3058
|
}
|
|
3144
3059
|
}
|
|
3145
|
-
|
|
3146
|
-
if (
|
|
3060
|
+
const certPem = String(persistedRecord.cert ?? "");
|
|
3061
|
+
if (certPem) {
|
|
3062
|
+
await this._tokenStore.saveCert(aid, certPem);
|
|
3063
|
+
}
|
|
3064
|
+
if (Object.keys(instanceState).length === 0 || typeof this._tokenStore.updateInstanceState !== "function") {
|
|
3147
3065
|
return;
|
|
3148
3066
|
}
|
|
3149
|
-
await this.
|
|
3067
|
+
await this._tokenStore.updateInstanceState(aid, this._deviceId, this._slotId, (current) => {
|
|
3150
3068
|
Object.assign(current, instanceState);
|
|
3151
3069
|
return current;
|
|
3152
3070
|
});
|
|
@@ -3568,7 +3486,7 @@ function hasEncryptionSeed(seed) {
|
|
|
3568
3486
|
return seed !== void 0;
|
|
3569
3487
|
}
|
|
3570
3488
|
var DB_NAME = "aun-keystore";
|
|
3571
|
-
var DB_VERSION =
|
|
3489
|
+
var DB_VERSION = 7;
|
|
3572
3490
|
var STORE_KEY_PAIRS = "key_pairs";
|
|
3573
3491
|
var STORE_CERTS = "certs";
|
|
3574
3492
|
var STORE_METADATA = "metadata";
|
|
@@ -3579,10 +3497,18 @@ var STORE_GROUP_OLD_EPOCHS = "group_old_epochs";
|
|
|
3579
3497
|
var STORE_SESSIONS = "e2ee_sessions";
|
|
3580
3498
|
var STORE_GROUP_STATE = "group_state";
|
|
3581
3499
|
var STORE_AGENT_MD_CACHE = "agent_md_cache";
|
|
3500
|
+
var STORE_PENDING_IDENTITIES = "pending_identities";
|
|
3582
3501
|
var STRUCTURED_RECOVERY_RETENTION_MS = 7 * 24 * 3600 * 1e3;
|
|
3583
3502
|
function metadataStoreKey(aid) {
|
|
3584
3503
|
return safeAid(aid);
|
|
3585
3504
|
}
|
|
3505
|
+
function randomHex(byteLength) {
|
|
3506
|
+
const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
|
|
3507
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3508
|
+
}
|
|
3509
|
+
function pendingIdentityPrefix(aid) {
|
|
3510
|
+
return `${safeAid(aid)}-`;
|
|
3511
|
+
}
|
|
3586
3512
|
function normalizeCertFingerprint(certFingerprint) {
|
|
3587
3513
|
const normalized = String(certFingerprint ?? "").trim().toLowerCase();
|
|
3588
3514
|
if (!normalized) return "";
|
|
@@ -3748,6 +3674,9 @@ function openDB() {
|
|
|
3748
3674
|
if (!db.objectStoreNames.contains(STORE_AGENT_MD_CACHE)) {
|
|
3749
3675
|
db.createObjectStore(STORE_AGENT_MD_CACHE);
|
|
3750
3676
|
}
|
|
3677
|
+
if (!db.objectStoreNames.contains(STORE_PENDING_IDENTITIES)) {
|
|
3678
|
+
db.createObjectStore(STORE_PENDING_IDENTITIES);
|
|
3679
|
+
}
|
|
3751
3680
|
};
|
|
3752
3681
|
request.onsuccess = () => {
|
|
3753
3682
|
const db = request.result;
|
|
@@ -3882,9 +3811,9 @@ async function idbDelete(storeName, key) {
|
|
|
3882
3811
|
var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
3883
3812
|
constructor(opts) {
|
|
3884
3813
|
__publicField(this, "_log", _noopLog5);
|
|
3885
|
-
/**
|
|
3814
|
+
/** 私钥加密种子;空字符串也是有效 seed,默认不再写入明文私钥。 */
|
|
3886
3815
|
__publicField(this, "_encryptionSeed");
|
|
3887
|
-
this._encryptionSeed = opts?.encryptionSeed;
|
|
3816
|
+
this._encryptionSeed = opts?.encryptionSeed ?? "";
|
|
3888
3817
|
}
|
|
3889
3818
|
setLogger(log) {
|
|
3890
3819
|
this._log = log;
|
|
@@ -3898,7 +3827,13 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
3898
3827
|
for (const row of rows) {
|
|
3899
3828
|
if (!isRecord(row.value)) continue;
|
|
3900
3829
|
const envelope = row.value._encrypted_pk;
|
|
3901
|
-
if (!isRecord(envelope))
|
|
3830
|
+
if (!isRecord(envelope)) {
|
|
3831
|
+
const plain = row.value.private_key_pem;
|
|
3832
|
+
if (typeof plain === "string" && plain) {
|
|
3833
|
+
migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem: plain });
|
|
3834
|
+
}
|
|
3835
|
+
continue;
|
|
3836
|
+
}
|
|
3902
3837
|
try {
|
|
3903
3838
|
const privateKeyPem = await _decryptPEM(envelope, oldSeed);
|
|
3904
3839
|
migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem });
|
|
@@ -3997,16 +3932,14 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
3997
3932
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
3998
3933
|
delete result._encrypted_pk;
|
|
3999
3934
|
} catch {
|
|
4000
|
-
this._log.error(`[keystore] decrypt ${aid} private
|
|
3935
|
+
this._log.error(`[keystore] decrypt ${aid} private key failed, maybe encryptionSeed mismatch`);
|
|
3936
|
+
throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
|
|
4001
3937
|
}
|
|
4002
3938
|
} else if (
|
|
4003
3939
|
// 透明迁移:旧版明文数据自动加密回写
|
|
4004
3940
|
!epk && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
4005
3941
|
) {
|
|
4006
|
-
|
|
4007
|
-
await this.saveKeyPair(aid, result);
|
|
4008
|
-
} catch {
|
|
4009
|
-
}
|
|
3942
|
+
await this.saveKeyPair(aid, result);
|
|
4010
3943
|
}
|
|
4011
3944
|
return result;
|
|
4012
3945
|
}
|
|
@@ -4018,6 +3951,123 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
4018
3951
|
}
|
|
4019
3952
|
await idbPut(STORE_KEY_PAIRS, metadataStoreKey(aid), record);
|
|
4020
3953
|
}
|
|
3954
|
+
// ── RegisterAID pending 身份 ─────────────────────────
|
|
3955
|
+
async pendingIdentityDir(aid) {
|
|
3956
|
+
const handle = `${pendingIdentityPrefix(aid)}${randomHex(4)}-${Math.floor(Date.now() / 1e3)}`;
|
|
3957
|
+
const now = Date.now();
|
|
3958
|
+
await idbPut(STORE_PENDING_IDENTITIES, handle, {
|
|
3959
|
+
aid,
|
|
3960
|
+
created_at: now,
|
|
3961
|
+
updated_at: now
|
|
3962
|
+
});
|
|
3963
|
+
return handle;
|
|
3964
|
+
}
|
|
3965
|
+
async listPendingIdentityDirs(aid) {
|
|
3966
|
+
const prefix = pendingIdentityPrefix(aid);
|
|
3967
|
+
const rows = await idbGetAllByPrefix(STORE_PENDING_IDENTITIES, prefix);
|
|
3968
|
+
return rows.filter((row) => row.key.startsWith(prefix) && isRecord(row.value) && String(row.value.aid ?? "") === aid).sort((a, b) => Number(b.value.updated_at ?? 0) - Number(a.value.updated_at ?? 0)).map((row) => row.key);
|
|
3969
|
+
}
|
|
3970
|
+
async savePendingKeyPair(handle, aid, keyPair) {
|
|
3971
|
+
const current = await this._loadPendingRecord(handle, aid);
|
|
3972
|
+
if (!current) throw new Error(`pending identity not found: ${handle}`);
|
|
3973
|
+
const record = deepClone(keyPair);
|
|
3974
|
+
const privateKeyPem = record.private_key_pem;
|
|
3975
|
+
if (typeof privateKeyPem !== "string" || !privateKeyPem) {
|
|
3976
|
+
throw new Error("savePendingKeyPair requires private_key_pem");
|
|
3977
|
+
}
|
|
3978
|
+
record._encrypted_pk = await _encryptPEM(privateKeyPem, this._encryptionSeed ?? "");
|
|
3979
|
+
delete record.private_key_pem;
|
|
3980
|
+
await idbPut(STORE_PENDING_IDENTITIES, handle, {
|
|
3981
|
+
...current,
|
|
3982
|
+
aid,
|
|
3983
|
+
key_pair: record,
|
|
3984
|
+
updated_at: Date.now()
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
async loadPendingKeyPair(handle, aid) {
|
|
3988
|
+
const current = await this._loadPendingRecord(handle, aid, false);
|
|
3989
|
+
if (!current || !isRecord(current.key_pair)) return null;
|
|
3990
|
+
const result = deepClone(current.key_pair);
|
|
3991
|
+
const encrypted = result._encrypted_pk;
|
|
3992
|
+
if (isRecord(encrypted)) {
|
|
3993
|
+
try {
|
|
3994
|
+
const envelope = encrypted;
|
|
3995
|
+
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed ?? "");
|
|
3996
|
+
delete result._encrypted_pk;
|
|
3997
|
+
return result;
|
|
3998
|
+
} catch {
|
|
3999
|
+
this._log.error(`[keystore] decrypt pending ${aid} private key failed, maybe encryptionSeed mismatch`);
|
|
4000
|
+
throw new Error(`pending identity private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
if (typeof result.private_key_pem === "string" && result.private_key_pem) {
|
|
4004
|
+
await this.savePendingKeyPair(handle, aid, result);
|
|
4005
|
+
return result;
|
|
4006
|
+
}
|
|
4007
|
+
return result;
|
|
4008
|
+
}
|
|
4009
|
+
async savePendingCert(handle, certPem) {
|
|
4010
|
+
const current = await this._loadPendingRecord(handle, void 0);
|
|
4011
|
+
if (!current) throw new Error(`pending identity not found: ${handle}`);
|
|
4012
|
+
await idbPut(STORE_PENDING_IDENTITIES, handle, {
|
|
4013
|
+
...current,
|
|
4014
|
+
cert: certPem,
|
|
4015
|
+
updated_at: Date.now()
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
4018
|
+
async promotePendingIdentity(handle, aid) {
|
|
4019
|
+
const current = await this._loadPendingRecord(handle, aid);
|
|
4020
|
+
if (!current) throw new Error(`pending identity not found: ${handle}`);
|
|
4021
|
+
if (!isRecord(current.key_pair)) {
|
|
4022
|
+
throw new Error(`promotePendingIdentity: missing pending key pair: ${handle}`);
|
|
4023
|
+
}
|
|
4024
|
+
const targetKey = metadataStoreKey(aid);
|
|
4025
|
+
const [existingKeyPair, existingCert, existingMetadata] = await Promise.all([
|
|
4026
|
+
idbGet(STORE_KEY_PAIRS, targetKey),
|
|
4027
|
+
idbGet(STORE_CERTS, certStoreKey(aid)),
|
|
4028
|
+
idbGet(STORE_METADATA, targetKey)
|
|
4029
|
+
]);
|
|
4030
|
+
if (existingKeyPair || existingCert || existingMetadata) {
|
|
4031
|
+
throw new Error(`promotePendingIdentity: target exists: ${targetKey}`);
|
|
4032
|
+
}
|
|
4033
|
+
const keyPair = await this._protectedPendingKeyPair(current, aid);
|
|
4034
|
+
await idbPut(STORE_KEY_PAIRS, targetKey, keyPair);
|
|
4035
|
+
if (typeof current.cert === "string" && current.cert) {
|
|
4036
|
+
await idbPut(STORE_CERTS, certStoreKey(aid), current.cert);
|
|
4037
|
+
}
|
|
4038
|
+
await idbDelete(STORE_PENDING_IDENTITIES, handle);
|
|
4039
|
+
return targetKey;
|
|
4040
|
+
}
|
|
4041
|
+
async _protectedPendingKeyPair(current, aid) {
|
|
4042
|
+
if (!isRecord(current.key_pair)) {
|
|
4043
|
+
throw new Error(`pending identity missing key pair for ${aid}`);
|
|
4044
|
+
}
|
|
4045
|
+
const keyPair = deepClone(current.key_pair);
|
|
4046
|
+
const privateKeyPem = keyPair.private_key_pem;
|
|
4047
|
+
if (typeof privateKeyPem === "string" && privateKeyPem) {
|
|
4048
|
+
throw new Error(`pending identity private key is plaintext for ${aid}`);
|
|
4049
|
+
}
|
|
4050
|
+
if (!isRecord(keyPair._encrypted_pk)) {
|
|
4051
|
+
throw new Error(`pending identity private key is not encrypted for ${aid}`);
|
|
4052
|
+
}
|
|
4053
|
+
return keyPair;
|
|
4054
|
+
}
|
|
4055
|
+
async cleanupPendingDirs(maxAgeMs = 6e5) {
|
|
4056
|
+
const rows = await idbGetAll(STORE_PENDING_IDENTITIES);
|
|
4057
|
+
const now = Date.now();
|
|
4058
|
+
let removed = 0;
|
|
4059
|
+
for (const row of rows) {
|
|
4060
|
+
if (!isRecord(row.value)) continue;
|
|
4061
|
+
const updatedAt = Number(row.value.updated_at ?? row.value.created_at ?? 0);
|
|
4062
|
+
if (updatedAt && now - updatedAt < maxAgeMs) continue;
|
|
4063
|
+
await idbDelete(STORE_PENDING_IDENTITIES, row.key);
|
|
4064
|
+
removed++;
|
|
4065
|
+
}
|
|
4066
|
+
return removed;
|
|
4067
|
+
}
|
|
4068
|
+
async discardPendingIdentity(handle) {
|
|
4069
|
+
await idbDelete(STORE_PENDING_IDENTITIES, handle);
|
|
4070
|
+
}
|
|
4021
4071
|
// ── 证书 ──────────────────────────────────────────
|
|
4022
4072
|
async loadCert(aid, certFingerprint) {
|
|
4023
4073
|
const tStart = Date.now();
|
|
@@ -4384,6 +4434,18 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
4384
4434
|
});
|
|
4385
4435
|
}
|
|
4386
4436
|
// ── 内部辅助 ─────────────────────────────────────────
|
|
4437
|
+
async _loadPendingRecord(handle, aid, required = true) {
|
|
4438
|
+
const data = await idbGet(STORE_PENDING_IDENTITIES, handle);
|
|
4439
|
+
if (!isRecord(data)) {
|
|
4440
|
+
if (required) throw new Error(`pending identity not found: ${handle}`);
|
|
4441
|
+
return null;
|
|
4442
|
+
}
|
|
4443
|
+
const record = deepClone(data);
|
|
4444
|
+
if (aid !== void 0 && String(record.aid ?? "") !== aid) {
|
|
4445
|
+
throw new Error(`pending identity aid mismatch: ${handle}`);
|
|
4446
|
+
}
|
|
4447
|
+
return record;
|
|
4448
|
+
}
|
|
4387
4449
|
async _loadKeyPairUnlocked(aid) {
|
|
4388
4450
|
const data = await idbGet(STORE_KEY_PAIRS, metadataStoreKey(aid));
|
|
4389
4451
|
if (!isRecord(data)) return null;
|
|
@@ -4394,16 +4456,14 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
|
|
|
4394
4456
|
result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
|
|
4395
4457
|
delete result._encrypted_pk;
|
|
4396
4458
|
} catch {
|
|
4397
|
-
this._log.error(`[keystore] decrypt ${aid} private
|
|
4459
|
+
this._log.error(`[keystore] decrypt ${aid} private key failed, maybe encryptionSeed mismatch`);
|
|
4460
|
+
throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
|
|
4398
4461
|
}
|
|
4399
4462
|
} else if (
|
|
4400
4463
|
// 透明迁移:旧版明文数据自动加密回写
|
|
4401
4464
|
!isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
|
|
4402
4465
|
) {
|
|
4403
|
-
|
|
4404
|
-
await this._saveKeyPairUnlocked(aid, result);
|
|
4405
|
-
} catch {
|
|
4406
|
-
}
|
|
4466
|
+
await this._saveKeyPairUnlocked(aid, result);
|
|
4407
4467
|
}
|
|
4408
4468
|
return result;
|
|
4409
4469
|
}
|
|
@@ -5462,6 +5522,35 @@ var V2KeyStore = class _V2KeyStore {
|
|
|
5462
5522
|
req.onerror = () => reject(req.error);
|
|
5463
5523
|
});
|
|
5464
5524
|
}
|
|
5525
|
+
async saveGroupIdentity(deviceId, groupAid, privateKeyPem, pubDer) {
|
|
5526
|
+
const record = {
|
|
5527
|
+
device_id: deviceId,
|
|
5528
|
+
key_type: "group_identity",
|
|
5529
|
+
group_id: groupAid,
|
|
5530
|
+
key_id: "",
|
|
5531
|
+
private_key: new TextEncoder().encode(privateKeyPem),
|
|
5532
|
+
public_key: pubDer,
|
|
5533
|
+
created_at: Date.now()
|
|
5534
|
+
};
|
|
5535
|
+
return new Promise((resolve, reject) => {
|
|
5536
|
+
const req = this.store("readwrite").put(record);
|
|
5537
|
+
req.onsuccess = () => resolve();
|
|
5538
|
+
req.onerror = () => reject(req.error);
|
|
5539
|
+
});
|
|
5540
|
+
}
|
|
5541
|
+
async loadGroupIdentity(deviceId, groupAid) {
|
|
5542
|
+
return new Promise((resolve, reject) => {
|
|
5543
|
+
const req = this.store("readonly").get([deviceId, "group_identity", groupAid, ""]);
|
|
5544
|
+
req.onsuccess = () => {
|
|
5545
|
+
const r = req.result;
|
|
5546
|
+
resolve(r ? {
|
|
5547
|
+
privateKeyPem: new TextDecoder().decode(r.private_key),
|
|
5548
|
+
pubDer: new Uint8Array(r.public_key)
|
|
5549
|
+
} : null);
|
|
5550
|
+
};
|
|
5551
|
+
req.onerror = () => reject(req.error);
|
|
5552
|
+
});
|
|
5553
|
+
}
|
|
5465
5554
|
async markGroupSPKUploaded(deviceId, groupId, spkId) {
|
|
5466
5555
|
const record = {
|
|
5467
5556
|
device_id: deviceId,
|
|
@@ -9703,7 +9792,8 @@ var AID = class _AID {
|
|
|
9703
9792
|
__publicField(this, "verifySsl");
|
|
9704
9793
|
__publicField(this, "rootCaPath");
|
|
9705
9794
|
__publicField(this, "debug");
|
|
9706
|
-
|
|
9795
|
+
/** AIDStore 加载时注入的明文私钥 PEM,供 AUNClient 直接使用(无需 seed)。*/
|
|
9796
|
+
__publicField(this, "privateKeyPem");
|
|
9707
9797
|
__publicField(this, "_certValid");
|
|
9708
9798
|
__publicField(this, "_privateKeyValid");
|
|
9709
9799
|
__publicField(this, "_certFingerprint", "");
|
|
@@ -9721,7 +9811,7 @@ var AID = class _AID {
|
|
|
9721
9811
|
this.certIssuer = meta.issuer;
|
|
9722
9812
|
this.certNotBefore = meta.notBefore;
|
|
9723
9813
|
this.certNotAfter = meta.notAfter;
|
|
9724
|
-
this.
|
|
9814
|
+
this.privateKeyPem = params.privateKeyPem ?? "";
|
|
9725
9815
|
this._certValid = params.certValid;
|
|
9726
9816
|
this._privateKeyValid = params.privateKeyValid;
|
|
9727
9817
|
}
|
|
@@ -9740,10 +9830,10 @@ var AID = class _AID {
|
|
|
9740
9830
|
return this._privateKeyValid;
|
|
9741
9831
|
}
|
|
9742
9832
|
async sign(payload) {
|
|
9743
|
-
if (!this._privateKeyValid || !this.
|
|
9833
|
+
if (!this._privateKeyValid || !this.privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
|
|
9744
9834
|
try {
|
|
9745
9835
|
const data = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
9746
|
-
return resultOk({ signature: await signBytes(this.
|
|
9836
|
+
return resultOk({ signature: await signBytes(this.privateKeyPem, data) });
|
|
9747
9837
|
} catch (exc) {
|
|
9748
9838
|
return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
|
|
9749
9839
|
}
|
|
@@ -9758,10 +9848,10 @@ var AID = class _AID {
|
|
|
9758
9848
|
}
|
|
9759
9849
|
}
|
|
9760
9850
|
async signAgentMd(content) {
|
|
9761
|
-
if (!this._privateKeyValid || !this.
|
|
9851
|
+
if (!this._privateKeyValid || !this.privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
|
|
9762
9852
|
try {
|
|
9763
9853
|
const payload = normalizeAgentMdPayload(content);
|
|
9764
|
-
const signature = await signBytes(this.
|
|
9854
|
+
const signature = await signBytes(this.privateKeyPem, new TextEncoder().encode(payload));
|
|
9765
9855
|
return resultOk({ signed: payload + buildAgentMdSignatureBlock(this.certFingerprint, Date.now() / 1e3, signature) });
|
|
9766
9856
|
} catch (exc) {
|
|
9767
9857
|
return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
|
|
@@ -9809,6 +9899,12 @@ function getV2DeviceId(dev) {
|
|
|
9809
9899
|
}
|
|
9810
9900
|
return { present: false, value: "" };
|
|
9811
9901
|
}
|
|
9902
|
+
function isAIDObject(value) {
|
|
9903
|
+
const candidate = value;
|
|
9904
|
+
return Boolean(
|
|
9905
|
+
candidate && typeof candidate === "object" && typeof candidate.aid === "string" && typeof candidate.aunPath === "string" && typeof candidate.isPrivateKeyValid === "function"
|
|
9906
|
+
);
|
|
9907
|
+
}
|
|
9812
9908
|
function sortObjectKeys(obj) {
|
|
9813
9909
|
if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
|
|
9814
9910
|
if (Array.isArray(obj)) return obj.map(sortObjectKeys);
|
|
@@ -9941,6 +10037,20 @@ var DEFAULT_SESSION_OPTIONS = {
|
|
|
9941
10037
|
http: 30
|
|
9942
10038
|
}
|
|
9943
10039
|
};
|
|
10040
|
+
var PUBLIC_CONNECTION_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
10041
|
+
"auto_reconnect",
|
|
10042
|
+
"connect_timeout",
|
|
10043
|
+
"retry_initial_delay",
|
|
10044
|
+
"retry_max_delay",
|
|
10045
|
+
"retry_max_attempts",
|
|
10046
|
+
"heartbeat_interval",
|
|
10047
|
+
"call_timeout",
|
|
10048
|
+
"connection_kind",
|
|
10049
|
+
"short_ttl_ms",
|
|
10050
|
+
"delivery_mode",
|
|
10051
|
+
"extra_info",
|
|
10052
|
+
"background_sync"
|
|
10053
|
+
]);
|
|
9944
10054
|
var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
|
|
9945
10055
|
"message.send",
|
|
9946
10056
|
"group.send",
|
|
@@ -10344,7 +10454,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10344
10454
|
__publicField(this, "_sessionOptions", { ...DEFAULT_SESSION_OPTIONS });
|
|
10345
10455
|
__publicField(this, "_dispatcher");
|
|
10346
10456
|
__publicField(this, "_discovery");
|
|
10347
|
-
__publicField(this, "
|
|
10457
|
+
__publicField(this, "_tokenStore");
|
|
10348
10458
|
__publicField(this, "_auth");
|
|
10349
10459
|
__publicField(this, "_transport");
|
|
10350
10460
|
// E2EE 编排状态(内存缓存)
|
|
@@ -10358,6 +10468,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10358
10468
|
// V2 E2EE 状态
|
|
10359
10469
|
__publicField(this, "_v2Session");
|
|
10360
10470
|
__publicField(this, "_v2KeyStore");
|
|
10471
|
+
__publicField(this, "_v2SessionInitInFlight", null);
|
|
10361
10472
|
__publicField(this, "_v2BootstrapCache", /* @__PURE__ */ new Map());
|
|
10362
10473
|
__publicField(this, "_v2SenderIKPending", /* @__PURE__ */ new Map());
|
|
10363
10474
|
__publicField(this, "_v2SenderIKFetching", /* @__PURE__ */ new Set());
|
|
@@ -10425,7 +10536,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10425
10536
|
__publicField(this, "_clientLog");
|
|
10426
10537
|
__publicField(this, "_logAuth");
|
|
10427
10538
|
__publicField(this, "_logTransport");
|
|
10428
|
-
__publicField(this, "
|
|
10539
|
+
__publicField(this, "_tokenStoreLog");
|
|
10429
10540
|
__publicField(this, "_logDiscovery");
|
|
10430
10541
|
__publicField(this, "_logEvents");
|
|
10431
10542
|
/**
|
|
@@ -10433,10 +10544,10 @@ var _AUNClient = class _AUNClient {
|
|
|
10433
10544
|
*/
|
|
10434
10545
|
__publicField(this, "_v2PullInflight", false);
|
|
10435
10546
|
__publicField(this, "_v2PullPending", false);
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
throw new ValidationError("AUNClient aid must be an AID object, not a string");
|
|
10547
|
+
if (aid !== null && aid !== void 0 && !isAIDObject(aid)) {
|
|
10548
|
+
throw new ValidationError("AUNClient only accepts an AID object or no argument");
|
|
10439
10549
|
}
|
|
10550
|
+
const inputAid = aid ?? null;
|
|
10440
10551
|
const rawConfig = {};
|
|
10441
10552
|
if (inputAid) rawConfig.aun_path = inputAid.aunPath;
|
|
10442
10553
|
const _debug = inputAid ? inputAid.debug : false;
|
|
@@ -10454,18 +10565,18 @@ var _AUNClient = class _AUNClient {
|
|
|
10454
10565
|
this._clientLog = this._logger.for("aun_core.client");
|
|
10455
10566
|
this._logAuth = this._logger.for("aun_core.auth");
|
|
10456
10567
|
this._logTransport = this._logger.for("aun_core.transport");
|
|
10457
|
-
this.
|
|
10568
|
+
this._tokenStoreLog = this._logger.for("aun_core.keystore");
|
|
10458
10569
|
this._logDiscovery = this._logger.for("aun_core.discovery");
|
|
10459
10570
|
this._logEvents = this._logger.for("aun_core.events");
|
|
10460
10571
|
this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
|
|
10461
10572
|
this._dispatcher = new EventDispatcher();
|
|
10462
10573
|
this._discovery = new GatewayDiscovery();
|
|
10463
|
-
this.
|
|
10574
|
+
this._tokenStore = new IndexedDBKeyStore({});
|
|
10464
10575
|
this._slotId = inputAid?.slotId || "default";
|
|
10465
10576
|
this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
|
|
10466
10577
|
this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
|
|
10467
10578
|
this._auth = new AuthFlow({
|
|
10468
|
-
|
|
10579
|
+
tokenStore: this._tokenStore,
|
|
10469
10580
|
crypto: new CryptoProvider(),
|
|
10470
10581
|
aid: initAid,
|
|
10471
10582
|
deviceId: this._deviceId,
|
|
@@ -10489,10 +10600,11 @@ var _AUNClient = class _AUNClient {
|
|
|
10489
10600
|
this._currentAid = inputAid;
|
|
10490
10601
|
this._identity = {
|
|
10491
10602
|
aid: inputAid.aid,
|
|
10492
|
-
private_key_pem: inputAid.
|
|
10603
|
+
private_key_pem: inputAid.privateKeyPem,
|
|
10493
10604
|
public_key_der_b64: inputAid.publicKey,
|
|
10494
10605
|
cert: inputAid.certPem
|
|
10495
10606
|
};
|
|
10607
|
+
this._auth.setIdentity(this._identity);
|
|
10496
10608
|
this._state = "disconnected";
|
|
10497
10609
|
}
|
|
10498
10610
|
}
|
|
@@ -10502,8 +10614,8 @@ var _AUNClient = class _AUNClient {
|
|
|
10502
10614
|
if (typeof this._discovery.setLogger === "function") {
|
|
10503
10615
|
this._discovery.setLogger(this._logger.for("aun_core.discovery"));
|
|
10504
10616
|
}
|
|
10505
|
-
if (typeof this.
|
|
10506
|
-
this.
|
|
10617
|
+
if (typeof this._tokenStore.setLogger === "function") {
|
|
10618
|
+
this._tokenStore.setLogger(this._tokenStoreLog);
|
|
10507
10619
|
}
|
|
10508
10620
|
this._dispatcher.subscribe("_raw.message.received", (data) => {
|
|
10509
10621
|
this._onRawMessageReceived(data);
|
|
@@ -10694,7 +10806,7 @@ var _AUNClient = class _AUNClient {
|
|
|
10694
10806
|
if (!target) throw new ValidationError("verifyAgentMd requires non-empty aid");
|
|
10695
10807
|
let peer = target === this._currentAid?.aid ? this._currentAid : null;
|
|
10696
10808
|
if (!peer) {
|
|
10697
|
-
let certPem = String(await this.
|
|
10809
|
+
let certPem = String(await this._tokenStore.loadCert(target) ?? "").trim();
|
|
10698
10810
|
if (!certPem) {
|
|
10699
10811
|
certPem = String(await this._fetchPeerCert(target) ?? "").trim();
|
|
10700
10812
|
}
|
|
@@ -10841,11 +10953,11 @@ var _AUNClient = class _AUNClient {
|
|
|
10841
10953
|
async _readAgentMdStorage(logicalKey) {
|
|
10842
10954
|
const key = String(logicalKey ?? "").trim();
|
|
10843
10955
|
if (!key) return null;
|
|
10844
|
-
const load = this.
|
|
10956
|
+
const load = this._tokenStore.loadAgentMdCache;
|
|
10845
10957
|
if (typeof load !== "function") {
|
|
10846
10958
|
throw new Error("IndexedDB agent.md storage unavailable");
|
|
10847
10959
|
}
|
|
10848
|
-
const record = await load.call(this.
|
|
10960
|
+
const record = await load.call(this._tokenStore, this._agentMdRoot(), key);
|
|
10849
10961
|
if (record && Object.prototype.hasOwnProperty.call(record, "content")) {
|
|
10850
10962
|
return String(record.content ?? "");
|
|
10851
10963
|
}
|
|
@@ -10854,12 +10966,12 @@ var _AUNClient = class _AUNClient {
|
|
|
10854
10966
|
async _writeAgentMdStorage(logicalKey, content) {
|
|
10855
10967
|
const key = String(logicalKey ?? "").trim();
|
|
10856
10968
|
if (!key) return;
|
|
10857
|
-
const save = this.
|
|
10969
|
+
const save = this._tokenStore.upsertAgentMdCache;
|
|
10858
10970
|
if (typeof save !== "function") {
|
|
10859
10971
|
throw new Error("IndexedDB agent.md storage unavailable");
|
|
10860
10972
|
}
|
|
10861
10973
|
const text = String(content ?? "");
|
|
10862
|
-
await save.call(this.
|
|
10974
|
+
await save.call(this._tokenStore, this._agentMdRoot(), key, {
|
|
10863
10975
|
content: text,
|
|
10864
10976
|
local_etag: await this._agentMdContentEtag(text),
|
|
10865
10977
|
fetched_at: Date.now()
|
|
@@ -11215,22 +11327,79 @@ var _AUNClient = class _AUNClient {
|
|
|
11215
11327
|
get lastErrorCode() {
|
|
11216
11328
|
return this._lastErrorCode;
|
|
11217
11329
|
}
|
|
11330
|
+
_applyAidRuntimeContext(aid) {
|
|
11331
|
+
const nextConfig = createConfig({
|
|
11332
|
+
aunPath: aid.aunPath,
|
|
11333
|
+
rootCaPem: aid.rootCaPath,
|
|
11334
|
+
verifySsl: aid.verifySsl
|
|
11335
|
+
});
|
|
11336
|
+
Object.assign(this.configModel, nextConfig);
|
|
11337
|
+
this.config.aun_path = nextConfig.aunPath;
|
|
11338
|
+
this.config.root_ca_path = nextConfig.rootCaPem;
|
|
11339
|
+
this.config.seed_password = nextConfig.seedPassword;
|
|
11340
|
+
this._agentMdPath = this._agentMdDefaultRoot();
|
|
11341
|
+
this._agentMdCache.clear();
|
|
11342
|
+
this._agentMdFetchInflight.clear();
|
|
11343
|
+
this._peerCache.clear();
|
|
11344
|
+
this._certCache.clear();
|
|
11345
|
+
this._gatewayUrl = null;
|
|
11346
|
+
this._deviceId = aid.deviceId || getDeviceId();
|
|
11347
|
+
this._slotId = aid.slotId || "default";
|
|
11348
|
+
this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
|
|
11349
|
+
this._logger.bindDeviceId(this._deviceId);
|
|
11350
|
+
this._clientLog = this._logger.for("aun_core.client");
|
|
11351
|
+
this._logAuth = this._logger.for("aun_core.auth");
|
|
11352
|
+
this._logTransport = this._logger.for("aun_core.transport");
|
|
11353
|
+
this._tokenStoreLog = this._logger.for("aun_core.keystore");
|
|
11354
|
+
this._logDiscovery = this._logger.for("aun_core.discovery");
|
|
11355
|
+
this._logEvents = this._logger.for("aun_core.events");
|
|
11356
|
+
this._discovery = new GatewayDiscovery();
|
|
11357
|
+
this._tokenStore = new IndexedDBKeyStore({});
|
|
11358
|
+
this._auth = new AuthFlow({
|
|
11359
|
+
tokenStore: this._tokenStore,
|
|
11360
|
+
crypto: new CryptoProvider(),
|
|
11361
|
+
aid: aid.aid,
|
|
11362
|
+
deviceId: this._deviceId,
|
|
11363
|
+
slotId: this._slotId,
|
|
11364
|
+
rootCaPem: nextConfig.rootCaPem,
|
|
11365
|
+
verifySsl: nextConfig.verifySsl
|
|
11366
|
+
});
|
|
11367
|
+
this._transport = new RPCTransport({
|
|
11368
|
+
eventDispatcher: this._dispatcher,
|
|
11369
|
+
timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
|
|
11370
|
+
onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode)
|
|
11371
|
+
});
|
|
11372
|
+
this._transport.setMetaObserver((meta) => {
|
|
11373
|
+
void this._observeRpcMeta(meta).catch((exc) => {
|
|
11374
|
+
this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
|
|
11375
|
+
});
|
|
11376
|
+
});
|
|
11377
|
+
this._auth.setLogger(this._logAuth);
|
|
11378
|
+
this._transport.setLogger(this._logTransport);
|
|
11379
|
+
this._dispatcher.setLogger(this._logEvents);
|
|
11380
|
+
if (typeof this._discovery.setLogger === "function") {
|
|
11381
|
+
this._discovery.setLogger(this._logDiscovery);
|
|
11382
|
+
}
|
|
11383
|
+
if (typeof this._tokenStore.setLogger === "function") {
|
|
11384
|
+
this._tokenStore.setLogger(this._tokenStoreLog);
|
|
11385
|
+
}
|
|
11386
|
+
}
|
|
11218
11387
|
loadIdentity(aid) {
|
|
11219
11388
|
if (!aid?.isPrivateKeyValid()) throw new StateError("loadIdentity requires an AID with a valid private key");
|
|
11220
11389
|
const publicState = this.state;
|
|
11221
11390
|
if (publicState !== "no_identity" /* NO_IDENTITY */ && publicState !== "closed" /* CLOSED */) {
|
|
11222
11391
|
throw new StateError(`loadIdentity not allowed in state ${publicState}`);
|
|
11223
11392
|
}
|
|
11393
|
+
this._applyAidRuntimeContext(aid);
|
|
11224
11394
|
this._currentAid = aid;
|
|
11225
11395
|
this._aid = aid.aid;
|
|
11226
11396
|
this._identity = {
|
|
11227
11397
|
aid: aid.aid,
|
|
11228
|
-
private_key_pem: aid.
|
|
11398
|
+
private_key_pem: aid.privateKeyPem,
|
|
11229
11399
|
public_key_der_b64: aid.publicKey,
|
|
11230
11400
|
cert: aid.certPem
|
|
11231
11401
|
};
|
|
11232
|
-
this._auth.
|
|
11233
|
-
this._slotId = aid.slotId || "default";
|
|
11402
|
+
this._auth.setIdentity(this._identity);
|
|
11234
11403
|
this._state = "disconnected";
|
|
11235
11404
|
this._closing = false;
|
|
11236
11405
|
}
|
|
@@ -11274,9 +11443,6 @@ var _AUNClient = class _AUNClient {
|
|
|
11274
11443
|
get gatewayUrl() {
|
|
11275
11444
|
return this._gatewayUrl;
|
|
11276
11445
|
}
|
|
11277
|
-
set gatewayUrl(url) {
|
|
11278
|
-
this._gatewayUrl = url;
|
|
11279
|
-
}
|
|
11280
11446
|
get discovery() {
|
|
11281
11447
|
return this._discovery;
|
|
11282
11448
|
}
|
|
@@ -11317,10 +11483,11 @@ var _AUNClient = class _AUNClient {
|
|
|
11317
11483
|
/** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
|
|
11318
11484
|
async connect(opts) {
|
|
11319
11485
|
const tStart = Date.now();
|
|
11320
|
-
if (opts !== void 0 && typeof opts === "object") {
|
|
11486
|
+
if (opts !== void 0 && opts !== null && typeof opts === "object") {
|
|
11321
11487
|
const raw = opts;
|
|
11322
|
-
|
|
11323
|
-
|
|
11488
|
+
const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
|
|
11489
|
+
if (invalid.length > 0) {
|
|
11490
|
+
throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(", ")}`);
|
|
11324
11491
|
}
|
|
11325
11492
|
}
|
|
11326
11493
|
const target = this._currentAid?.aid ?? this._aid ?? "";
|
|
@@ -11482,6 +11649,12 @@ var _AUNClient = class _AUNClient {
|
|
|
11482
11649
|
}
|
|
11483
11650
|
this._validateOutboundCall(method, p);
|
|
11484
11651
|
this._injectMessageCursorContext(method, p);
|
|
11652
|
+
if (method.startsWith("group.") && !("_group_cursor_params" in p) && !Boolean(p._pull_gate_locked)) {
|
|
11653
|
+
const explicitCursorParams = this._groupCursorParams(p);
|
|
11654
|
+
if (Object.keys(explicitCursorParams).length > 0) {
|
|
11655
|
+
p._group_cursor_params = explicitCursorParams;
|
|
11656
|
+
}
|
|
11657
|
+
}
|
|
11485
11658
|
if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
|
|
11486
11659
|
const rawGroupId = String(p.group_id);
|
|
11487
11660
|
const normalizedGroupId = normalizeGroupId(rawGroupId);
|
|
@@ -11500,9 +11673,10 @@ var _AUNClient = class _AUNClient {
|
|
|
11500
11673
|
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
11501
11674
|
delete p.encrypt;
|
|
11502
11675
|
if (encrypt) {
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11676
|
+
await this._ensureV2SessionReady(
|
|
11677
|
+
"message.send",
|
|
11678
|
+
"V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)"
|
|
11679
|
+
);
|
|
11506
11680
|
this._clientLog.debug("call route: message.send \u2192 V2 encrypted send");
|
|
11507
11681
|
return await this._sendV2(String(p.to ?? ""), p.payload ?? {}, {
|
|
11508
11682
|
messageId: String(p.message_id ?? "") || void 0,
|
|
@@ -11517,9 +11691,10 @@ var _AUNClient = class _AUNClient {
|
|
|
11517
11691
|
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
11518
11692
|
delete p.encrypt;
|
|
11519
11693
|
if (encrypt) {
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11694
|
+
await this._ensureV2SessionReady(
|
|
11695
|
+
"group.send",
|
|
11696
|
+
"V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)"
|
|
11697
|
+
);
|
|
11523
11698
|
this._clientLog.debug("call route: group.send \u2192 V2 encrypted send");
|
|
11524
11699
|
return await this._sendGroupV2(String(p.group_id ?? ""), p.payload ?? {}, {
|
|
11525
11700
|
messageId: String(p.message_id ?? "") || void 0,
|
|
@@ -11534,9 +11709,10 @@ var _AUNClient = class _AUNClient {
|
|
|
11534
11709
|
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
11535
11710
|
delete p.encrypt;
|
|
11536
11711
|
if (encrypt) {
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11712
|
+
await this._ensureV2SessionReady(
|
|
11713
|
+
"group.thought.put",
|
|
11714
|
+
"V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)"
|
|
11715
|
+
);
|
|
11540
11716
|
this._clientLog.debug("call route: group.thought.put \u2192 V2 encrypted put");
|
|
11541
11717
|
return this._putGroupThoughtEncryptedV2(p);
|
|
11542
11718
|
}
|
|
@@ -11545,9 +11721,10 @@ var _AUNClient = class _AUNClient {
|
|
|
11545
11721
|
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
11546
11722
|
delete p.encrypt;
|
|
11547
11723
|
if (encrypt) {
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11724
|
+
await this._ensureV2SessionReady(
|
|
11725
|
+
"message.thought.put",
|
|
11726
|
+
"V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)"
|
|
11727
|
+
);
|
|
11551
11728
|
this._clientLog.debug("call route: message.thought.put \u2192 V2 encrypted put");
|
|
11552
11729
|
return this._putMessageThoughtEncryptedV2(p);
|
|
11553
11730
|
}
|
|
@@ -11565,26 +11742,43 @@ var _AUNClient = class _AUNClient {
|
|
|
11565
11742
|
* 拆分出来以便 pull gate 包裹整个操作。
|
|
11566
11743
|
*/
|
|
11567
11744
|
async _callImplInner(method, p) {
|
|
11568
|
-
if (method === "message.pull"
|
|
11745
|
+
if (method === "message.pull") {
|
|
11746
|
+
await this._ensureV2SessionReady("message.pull");
|
|
11569
11747
|
this._clientLog.debug("call route: message.pull \u2192 V2 pull");
|
|
11570
11748
|
const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
|
|
11571
11749
|
return { messages };
|
|
11572
11750
|
}
|
|
11573
|
-
if (method === "message.ack"
|
|
11751
|
+
if (method === "message.ack") {
|
|
11752
|
+
await this._ensureV2SessionReady("message.ack");
|
|
11574
11753
|
this._clientLog.debug("call route: message.ack \u2192 V2 ack");
|
|
11575
11754
|
return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || void 0);
|
|
11576
11755
|
}
|
|
11577
|
-
if (method === "group.pull" &&
|
|
11756
|
+
if (method === "group.pull" && p.group_id) {
|
|
11757
|
+
await this._ensureV2SessionReady("group.pull");
|
|
11578
11758
|
this._clientLog.debug("call route: group.pull \u2192 V2 pull");
|
|
11759
|
+
const hasExplicitAfterSeq = "after_seq" in p || "after_message_seq" in p;
|
|
11760
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
11761
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
11762
|
+
const pullOpts = {};
|
|
11763
|
+
if (hasExplicitAfterSeq) pullOpts.explicitAfterSeq = true;
|
|
11764
|
+
if (Object.keys(cursorParams).length > 0) pullOpts.cursorParams = cursorParams;
|
|
11765
|
+
if (!ownsCursor) pullOpts.ownsCursor = false;
|
|
11579
11766
|
const messages = await this._pullGroupV2(
|
|
11580
11767
|
String(p.group_id),
|
|
11581
11768
|
Number(p.after_seq ?? p.after_message_seq ?? 0) || 0,
|
|
11582
|
-
Number(p.limit ?? 50) || 50
|
|
11769
|
+
Number(p.limit ?? 50) || 50,
|
|
11770
|
+
Object.keys(pullOpts).length > 0 ? pullOpts : void 0
|
|
11583
11771
|
);
|
|
11584
11772
|
return { messages };
|
|
11585
11773
|
}
|
|
11586
|
-
if (method === "group.ack_messages" &&
|
|
11774
|
+
if (method === "group.ack_messages" && p.group_id) {
|
|
11775
|
+
await this._ensureV2SessionReady("group.ack_messages");
|
|
11587
11776
|
this._clientLog.debug("call route: group.ack_messages \u2192 V2 ack");
|
|
11777
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
11778
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
11779
|
+
if (!ownsCursor) {
|
|
11780
|
+
return await this._rawGroupAckMessages(p);
|
|
11781
|
+
}
|
|
11588
11782
|
return await this._ackGroupV2(
|
|
11589
11783
|
String(p.group_id),
|
|
11590
11784
|
Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || void 0
|
|
@@ -11685,6 +11879,7 @@ var _AUNClient = class _AUNClient {
|
|
|
11685
11879
|
delete p._pull_gate_locked;
|
|
11686
11880
|
delete p._skip_auto_ack;
|
|
11687
11881
|
delete p.skip_auto_ack;
|
|
11882
|
+
delete p._group_cursor_params;
|
|
11688
11883
|
if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
|
|
11689
11884
|
p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
11690
11885
|
}
|
|
@@ -12014,54 +12209,27 @@ var _AUNClient = class _AUNClient {
|
|
|
12014
12209
|
/** 后台补齐群消息空洞 */
|
|
12015
12210
|
async _fillGroupGap(groupId) {
|
|
12016
12211
|
if (this._state !== "connected" || this._closing) return;
|
|
12212
|
+
groupId = normalizeGroupId(groupId) || String(groupId ?? "").trim();
|
|
12213
|
+
if (!groupId) return;
|
|
12017
12214
|
const ns = `group:${groupId}`;
|
|
12018
12215
|
const afterSeq = this._seqTracker.getContiguousSeq(ns);
|
|
12019
12216
|
const dedupKey = `group_pull:${ns}`;
|
|
12020
12217
|
if (this._gapFillDone.has(dedupKey)) return;
|
|
12021
12218
|
this._gapFillDone.add(dedupKey);
|
|
12022
12219
|
this._gapFillActive = true;
|
|
12220
|
+
let filled = 0;
|
|
12023
12221
|
try {
|
|
12024
|
-
const
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
device_id: this._deviceId,
|
|
12028
|
-
limit: 50
|
|
12029
|
-
});
|
|
12030
|
-
if (isJsonObject(result)) {
|
|
12031
|
-
const messages = result.messages;
|
|
12032
|
-
if (Array.isArray(messages)) {
|
|
12033
|
-
const pushed = this._pushedSeqs.get(ns);
|
|
12034
|
-
for (const msg of messages) {
|
|
12035
|
-
if (isJsonObject(msg)) {
|
|
12036
|
-
const s = msg.seq;
|
|
12037
|
-
if (pushed && s !== void 0 && s !== null && pushed.has(s)) continue;
|
|
12038
|
-
if (s !== void 0 && s !== null) {
|
|
12039
|
-
await this._publishPulledMessage("group.message_created", ns, s, msg);
|
|
12040
|
-
} else {
|
|
12041
|
-
await this._publishAppEvent("group.message_created", msg);
|
|
12042
|
-
}
|
|
12043
|
-
}
|
|
12044
|
-
}
|
|
12045
|
-
this._prunePushedSeqs(ns);
|
|
12046
|
-
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
12047
|
-
if (contig > 0) {
|
|
12048
|
-
const gid = groupId;
|
|
12049
|
-
this._transport.call("group.ack_messages", {
|
|
12050
|
-
group_id: gid,
|
|
12051
|
-
msg_seq: contig,
|
|
12052
|
-
device_id: this._deviceId,
|
|
12053
|
-
slot_id: this._slotId
|
|
12054
|
-
}).catch((e) => {
|
|
12055
|
-
this._clientLog.warn(`group gap-fill auto-ack failed: group=${gid}`, e);
|
|
12056
|
-
});
|
|
12057
|
-
}
|
|
12058
|
-
}
|
|
12059
|
-
}
|
|
12222
|
+
const messages = await this._pullGroupV2(groupId, afterSeq, 50);
|
|
12223
|
+
filled = messages.length;
|
|
12224
|
+
this._prunePushedSeqs(ns);
|
|
12060
12225
|
} catch (exc) {
|
|
12061
12226
|
this._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
|
|
12062
12227
|
} finally {
|
|
12063
12228
|
this._gapFillDone.delete(dedupKey);
|
|
12064
12229
|
this._gapFillActive = false;
|
|
12230
|
+
if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
12231
|
+
this._safeAsync(this._fillGroupGap(groupId));
|
|
12232
|
+
}
|
|
12065
12233
|
}
|
|
12066
12234
|
}
|
|
12067
12235
|
/** 后台补齐群事件空洞 */
|
|
@@ -12159,44 +12327,19 @@ var _AUNClient = class _AUNClient {
|
|
|
12159
12327
|
if (this._gapFillDone.has(dedupKey)) return;
|
|
12160
12328
|
this._gapFillDone.add(dedupKey);
|
|
12161
12329
|
this._gapFillActive = true;
|
|
12330
|
+
let filled = 0;
|
|
12162
12331
|
try {
|
|
12163
|
-
const
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
});
|
|
12167
|
-
if (isJsonObject(result)) {
|
|
12168
|
-
const messages = result.messages;
|
|
12169
|
-
if (Array.isArray(messages)) {
|
|
12170
|
-
const pushed = this._pushedSeqs.get(ns);
|
|
12171
|
-
for (const msg of messages) {
|
|
12172
|
-
if (isJsonObject(msg)) {
|
|
12173
|
-
const s = msg.seq;
|
|
12174
|
-
if (pushed && s !== void 0 && s !== null && pushed.has(s)) continue;
|
|
12175
|
-
if (s !== void 0 && s !== null) {
|
|
12176
|
-
await this._publishPulledMessage("message.received", ns, s, msg);
|
|
12177
|
-
} else {
|
|
12178
|
-
await this._publishAppEvent("message.received", msg);
|
|
12179
|
-
}
|
|
12180
|
-
}
|
|
12181
|
-
}
|
|
12182
|
-
this._prunePushedSeqs(ns);
|
|
12183
|
-
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
12184
|
-
if (contig > 0) {
|
|
12185
|
-
this._transport.call("message.ack", {
|
|
12186
|
-
seq: contig,
|
|
12187
|
-
device_id: this._deviceId,
|
|
12188
|
-
slot_id: this._slotId
|
|
12189
|
-
}).catch((e) => {
|
|
12190
|
-
this._clientLog.warn(`P2P gap-fill auto-ack failed:${String(e)}`);
|
|
12191
|
-
});
|
|
12192
|
-
}
|
|
12193
|
-
}
|
|
12194
|
-
}
|
|
12332
|
+
const messages = await this._pullV2(afterSeq, 50);
|
|
12333
|
+
filled = messages.length;
|
|
12334
|
+
this._prunePushedSeqs(ns);
|
|
12195
12335
|
} catch (exc) {
|
|
12196
12336
|
this._clientLog.warn(`P2P message gap-fill failed:${String(exc)}`);
|
|
12197
12337
|
} finally {
|
|
12198
12338
|
this._gapFillDone.delete(dedupKey);
|
|
12199
12339
|
this._gapFillActive = false;
|
|
12340
|
+
if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
12341
|
+
this._safeAsync(this._fillP2pGap());
|
|
12342
|
+
}
|
|
12200
12343
|
}
|
|
12201
12344
|
}
|
|
12202
12345
|
/** 只按硬上限裁剪 published guard,不能按 contiguousSeq 清理。 */
|
|
@@ -12516,8 +12659,8 @@ var _AUNClient = class _AUNClient {
|
|
|
12516
12659
|
const keyEpoch = Number(d.key_epoch ?? 0);
|
|
12517
12660
|
const membershipSnapshot = String(d.membership_snapshot ?? "").trim();
|
|
12518
12661
|
const policySnapshot = String(d.policy_snapshot ?? "").trim();
|
|
12519
|
-
const loadFn = this.
|
|
12520
|
-
const localState = loadFn ? await loadFn.call(this.
|
|
12662
|
+
const loadFn = this._tokenStore.loadGroupState;
|
|
12663
|
+
const localState = loadFn ? await loadFn.call(this._tokenStore, groupId) : null;
|
|
12521
12664
|
if (localState && localState.state_hash && localState.state_hash !== prevStateHash) {
|
|
12522
12665
|
this._clientLog.warn(
|
|
12523
12666
|
"[aun_core] state_hash \u94FE\u4E0D\u8FDE\u7EED group=%s local_sv=%d event_sv=%d",
|
|
@@ -12556,9 +12699,9 @@ var _AUNClient = class _AUNClient {
|
|
|
12556
12699
|
return;
|
|
12557
12700
|
}
|
|
12558
12701
|
}
|
|
12559
|
-
const saveFn2 = this.
|
|
12702
|
+
const saveFn2 = this._tokenStore.saveGroupState;
|
|
12560
12703
|
if (saveFn2) {
|
|
12561
|
-
await saveFn2.call(this.
|
|
12704
|
+
await saveFn2.call(this._tokenStore, groupId, {
|
|
12562
12705
|
group_id: groupId,
|
|
12563
12706
|
state_version: sv,
|
|
12564
12707
|
state_hash: sHash,
|
|
@@ -12594,9 +12737,9 @@ var _AUNClient = class _AUNClient {
|
|
|
12594
12737
|
);
|
|
12595
12738
|
return;
|
|
12596
12739
|
}
|
|
12597
|
-
const saveFn = this.
|
|
12740
|
+
const saveFn = this._tokenStore.saveGroupState;
|
|
12598
12741
|
if (saveFn) {
|
|
12599
|
-
await saveFn.call(this.
|
|
12742
|
+
await saveFn.call(this._tokenStore, groupId, {
|
|
12600
12743
|
group_id: groupId,
|
|
12601
12744
|
state_version: stateVersion,
|
|
12602
12745
|
state_hash: stateHash,
|
|
@@ -12967,7 +13110,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12967
13110
|
refreshAfter: now + PEER_CERT_CACHE_TTL
|
|
12968
13111
|
});
|
|
12969
13112
|
try {
|
|
12970
|
-
await this.
|
|
13113
|
+
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
12971
13114
|
} catch (exc) {
|
|
12972
13115
|
this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : void 0);
|
|
12973
13116
|
}
|
|
@@ -12984,7 +13127,7 @@ var _AUNClient = class _AUNClient {
|
|
|
12984
13127
|
const cached = this._certCache.get(cacheKey);
|
|
12985
13128
|
const now = Date.now() / 1e3;
|
|
12986
13129
|
if (cached && now < cached.refreshAfter) return true;
|
|
12987
|
-
const localCert = await this.
|
|
13130
|
+
const localCert = await this._tokenStore.loadCert(aid, certFingerprint);
|
|
12988
13131
|
if (localCert) {
|
|
12989
13132
|
if (certFingerprint) {
|
|
12990
13133
|
const actualFingerprint = await this._certFingerprint(localCert);
|
|
@@ -13007,7 +13150,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13007
13150
|
}
|
|
13008
13151
|
try {
|
|
13009
13152
|
const certPem = await this._fetchPeerCert(aid, certFingerprint);
|
|
13010
|
-
await this.
|
|
13153
|
+
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
13011
13154
|
return true;
|
|
13012
13155
|
} catch (exc) {
|
|
13013
13156
|
if (cached && now < cached.validatedAt + PEER_CERT_CACHE_TTL * 2) {
|
|
@@ -13039,10 +13182,10 @@ var _AUNClient = class _AUNClient {
|
|
|
13039
13182
|
* 使用 SubtleCrypto 异步签名。
|
|
13040
13183
|
*/
|
|
13041
13184
|
async _signClientOperation(method, params) {
|
|
13042
|
-
const
|
|
13043
|
-
if (!
|
|
13185
|
+
const currentAid = this._currentAid;
|
|
13186
|
+
if (!currentAid?.privateKeyPem) return;
|
|
13044
13187
|
try {
|
|
13045
|
-
const aid =
|
|
13188
|
+
const aid = currentAid.aid;
|
|
13046
13189
|
const ts = String(Math.floor(Date.now() / 1e3));
|
|
13047
13190
|
const paramsForHash = {};
|
|
13048
13191
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -13057,7 +13200,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13057
13200
|
);
|
|
13058
13201
|
const paramsHash = Array.from(new Uint8Array(paramsHashBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
13059
13202
|
const signData = new TextEncoder().encode(`${method}|${aid}|${ts}|${paramsHash}`);
|
|
13060
|
-
const pkcs8 = pemToArrayBuffer(
|
|
13203
|
+
const pkcs8 = pemToArrayBuffer(currentAid.privateKeyPem);
|
|
13061
13204
|
const cryptoKey = await crypto.subtle.importKey(
|
|
13062
13205
|
"pkcs8",
|
|
13063
13206
|
pkcs8,
|
|
@@ -13072,7 +13215,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13072
13215
|
);
|
|
13073
13216
|
const sigDer = p1363ToDer(new Uint8Array(sigP1363));
|
|
13074
13217
|
let certFingerprint = "";
|
|
13075
|
-
const certPem =
|
|
13218
|
+
const certPem = currentAid.certPem;
|
|
13076
13219
|
if (certPem) {
|
|
13077
13220
|
const certDer = pemToArrayBuffer(certPem);
|
|
13078
13221
|
const fpBuf = await crypto.subtle.digest("SHA-256", certDer);
|
|
@@ -13172,12 +13315,20 @@ var _AUNClient = class _AUNClient {
|
|
|
13172
13315
|
await this._restoreSeqTrackerState();
|
|
13173
13316
|
}
|
|
13174
13317
|
this._startBackgroundTasks();
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13318
|
+
const connectionKind = String(params.connection_kind ?? "long");
|
|
13319
|
+
const isShortConnection = connectionKind === "short";
|
|
13320
|
+
if (!isShortConnection) {
|
|
13321
|
+
try {
|
|
13322
|
+
await this._initV2Session();
|
|
13323
|
+
} catch (exc) {
|
|
13324
|
+
this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
|
|
13325
|
+
}
|
|
13326
|
+
} else {
|
|
13327
|
+
this._clientLog.debug("V2 session init deferred for short connection");
|
|
13179
13328
|
}
|
|
13180
|
-
|
|
13329
|
+
const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, "background_sync");
|
|
13330
|
+
const backgroundSyncEnabled = this._sessionOptions?.background_sync !== false && (!isShortConnection || hasExplicitBackgroundSync);
|
|
13331
|
+
if (backgroundSyncEnabled) {
|
|
13181
13332
|
this._safeAsync(this._fillP2pGap());
|
|
13182
13333
|
}
|
|
13183
13334
|
this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? "-"}`);
|
|
@@ -13195,8 +13346,8 @@ var _AUNClient = class _AUNClient {
|
|
|
13195
13346
|
if (!target) throw new StateError("gateway discovery requires a loaded AID");
|
|
13196
13347
|
if (this._gatewayUrl) return this._gatewayUrl;
|
|
13197
13348
|
try {
|
|
13198
|
-
const getMetadata = this.
|
|
13199
|
-
const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this.
|
|
13349
|
+
const getMetadata = this._tokenStore.getMetadata;
|
|
13350
|
+
const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this._tokenStore, target, "gateway_url") ?? "").trim() : "";
|
|
13200
13351
|
if (raw) {
|
|
13201
13352
|
const gateway = raw.startsWith('"') && raw.endsWith('"') ? String(JSON.parse(raw)).trim() : raw;
|
|
13202
13353
|
if (gateway) {
|
|
@@ -13219,9 +13370,9 @@ var _AUNClient = class _AUNClient {
|
|
|
13219
13370
|
const gateway = await this._discovery.discover(url);
|
|
13220
13371
|
this._gatewayUrl = gateway;
|
|
13221
13372
|
try {
|
|
13222
|
-
const setMetadata = this.
|
|
13373
|
+
const setMetadata = this._tokenStore.setMetadata;
|
|
13223
13374
|
if (typeof setMetadata === "function") {
|
|
13224
|
-
await setMetadata.call(this.
|
|
13375
|
+
await setMetadata.call(this._tokenStore, target, "gateway_url", gateway);
|
|
13225
13376
|
}
|
|
13226
13377
|
} catch {
|
|
13227
13378
|
}
|
|
@@ -13253,13 +13404,8 @@ var _AUNClient = class _AUNClient {
|
|
|
13253
13404
|
return [gateway];
|
|
13254
13405
|
}
|
|
13255
13406
|
async _syncIdentityAfterConnect(accessToken) {
|
|
13256
|
-
|
|
13257
|
-
try {
|
|
13258
|
-
identity = await this._auth.loadIdentityOrNone(this._aid ?? void 0);
|
|
13259
|
-
} catch {
|
|
13260
|
-
}
|
|
13407
|
+
const identity = this._identity;
|
|
13261
13408
|
if (!identity) {
|
|
13262
|
-
this._identity = null;
|
|
13263
13409
|
return;
|
|
13264
13410
|
}
|
|
13265
13411
|
identity.access_token = accessToken;
|
|
@@ -13269,8 +13415,6 @@ var _AUNClient = class _AUNClient {
|
|
|
13269
13415
|
const persistIdentity = this._auth._persistIdentity;
|
|
13270
13416
|
if (typeof persistIdentity === "function") {
|
|
13271
13417
|
await persistIdentity.call(this._auth, identity);
|
|
13272
|
-
} else {
|
|
13273
|
-
await this._keystore.saveIdentity(String(identity.aid), identity);
|
|
13274
13418
|
}
|
|
13275
13419
|
}
|
|
13276
13420
|
}
|
|
@@ -13740,8 +13884,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13740
13884
|
}
|
|
13741
13885
|
// ── Named Group(命名群)高层 API ────────────────────────────
|
|
13742
13886
|
/**
|
|
13743
|
-
*
|
|
13744
|
-
* 服务端签发群 AID 证书,返回后将证书和私钥存入 keystore。
|
|
13887
|
+
* 创建命名群:群/P2P 私钥由 V2 数据库存储,不写入 AID 身份私钥存储。
|
|
13745
13888
|
*/
|
|
13746
13889
|
async createNamedGroup(groupName, opts = {}) {
|
|
13747
13890
|
const tStart = Date.now();
|
|
@@ -13761,15 +13904,10 @@ var _AUNClient = class _AUNClient {
|
|
|
13761
13904
|
const aidCert = result?.aid_cert;
|
|
13762
13905
|
const groupAid = String(groupInfo?.group_aid ?? "");
|
|
13763
13906
|
if (groupAid && aidCert) {
|
|
13764
|
-
await this.
|
|
13765
|
-
private_key_pem: identity.private_key_pem,
|
|
13766
|
-
public_key: identity.public_key_der_b64,
|
|
13767
|
-
curve: "P-256",
|
|
13768
|
-
type: "group_identity"
|
|
13769
|
-
});
|
|
13907
|
+
await this._saveGroupIdentityToV2(groupAid, identity);
|
|
13770
13908
|
const certPem = String(aidCert.cert ?? "");
|
|
13771
13909
|
if (certPem) {
|
|
13772
|
-
await this.
|
|
13910
|
+
await this._tokenStore.saveCert(groupAid, certPem);
|
|
13773
13911
|
}
|
|
13774
13912
|
}
|
|
13775
13913
|
this._clientLog.debug(`createNamedGroup exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
|
|
@@ -13799,15 +13937,10 @@ var _AUNClient = class _AUNClient {
|
|
|
13799
13937
|
const aidCert = result?.aid_cert;
|
|
13800
13938
|
const groupAid = String(groupInfo?.group_aid ?? "");
|
|
13801
13939
|
if (groupAid && aidCert) {
|
|
13802
|
-
await this.
|
|
13803
|
-
private_key_pem: identity.private_key_pem,
|
|
13804
|
-
public_key: identity.public_key_der_b64,
|
|
13805
|
-
curve: "P-256",
|
|
13806
|
-
type: "group_identity"
|
|
13807
|
-
});
|
|
13940
|
+
await this._saveGroupIdentityToV2(groupAid, identity);
|
|
13808
13941
|
const certPem = String(aidCert.cert ?? "");
|
|
13809
13942
|
if (certPem) {
|
|
13810
|
-
await this.
|
|
13943
|
+
await this._tokenStore.saveCert(groupAid, certPem);
|
|
13811
13944
|
}
|
|
13812
13945
|
}
|
|
13813
13946
|
this._clientLog.debug(`bindGroupAid exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
|
|
@@ -13843,7 +13976,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13843
13976
|
const deviceId = this._deviceId;
|
|
13844
13977
|
const slotId = this._slotId;
|
|
13845
13978
|
try {
|
|
13846
|
-
const loadAll = this.
|
|
13979
|
+
const loadAll = this._tokenStore.loadAllSeqs?.bind(this._tokenStore);
|
|
13847
13980
|
if (typeof loadAll === "function") {
|
|
13848
13981
|
let state = await loadAll(aid, deviceId, slotId);
|
|
13849
13982
|
if (this._seqTrackerContext !== context) return;
|
|
@@ -13853,7 +13986,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13853
13986
|
}
|
|
13854
13987
|
return;
|
|
13855
13988
|
}
|
|
13856
|
-
const loader = this.
|
|
13989
|
+
const loader = this._tokenStore.loadInstanceState?.bind(this._tokenStore);
|
|
13857
13990
|
if (typeof loader !== "function") return;
|
|
13858
13991
|
const stateHolder = await loader(aid, deviceId, slotId);
|
|
13859
13992
|
if (this._seqTrackerContext !== context) return;
|
|
@@ -13906,8 +14039,8 @@ var _AUNClient = class _AUNClient {
|
|
|
13906
14039
|
const aid = this._aid;
|
|
13907
14040
|
const deviceId = this._deviceId;
|
|
13908
14041
|
const slotId = this._slotId;
|
|
13909
|
-
const saver = this.
|
|
13910
|
-
const deleter = this.
|
|
14042
|
+
const saver = this._tokenStore.saveSeq?.bind(this._tokenStore);
|
|
14043
|
+
const deleter = this._tokenStore.deleteSeq?.bind(this._tokenStore);
|
|
13911
14044
|
if (typeof saver === "function") {
|
|
13912
14045
|
for (const [oldNs, newNs] of Object.entries(renameMap)) {
|
|
13913
14046
|
if (typeof deleter === "function") {
|
|
@@ -13974,7 +14107,7 @@ var _AUNClient = class _AUNClient {
|
|
|
13974
14107
|
const state = this._seqTracker.exportState();
|
|
13975
14108
|
if (Object.keys(state).length === 0) return;
|
|
13976
14109
|
try {
|
|
13977
|
-
const saveFn = this.
|
|
14110
|
+
const saveFn = this._tokenStore.saveSeq?.bind(this._tokenStore);
|
|
13978
14111
|
if (typeof saveFn === "function") {
|
|
13979
14112
|
for (const [ns, seq] of Object.entries(state)) {
|
|
13980
14113
|
saveFn(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
|
|
@@ -13990,8 +14123,8 @@ var _AUNClient = class _AUNClient {
|
|
|
13990
14123
|
}
|
|
13991
14124
|
return;
|
|
13992
14125
|
}
|
|
13993
|
-
if (typeof this.
|
|
13994
|
-
this.
|
|
14126
|
+
if (typeof this._tokenStore.updateInstanceState === "function") {
|
|
14127
|
+
this._tokenStore.updateInstanceState(this._aid, this._deviceId, this._slotId, (current) => {
|
|
13995
14128
|
current.seq_tracker_state = state;
|
|
13996
14129
|
return current;
|
|
13997
14130
|
}).catch((exc) => {
|
|
@@ -14020,15 +14153,15 @@ var _AUNClient = class _AUNClient {
|
|
|
14020
14153
|
if (!this._aid || !ns) return;
|
|
14021
14154
|
const seq = this._seqTracker.getContiguousSeq(ns);
|
|
14022
14155
|
try {
|
|
14023
|
-
if (seq > 0 && typeof this.
|
|
14024
|
-
this.
|
|
14156
|
+
if (seq > 0 && typeof this._tokenStore.saveSeq === "function") {
|
|
14157
|
+
this._tokenStore.saveSeq(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
|
|
14025
14158
|
this._clientLog.debug(`persist repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
|
|
14026
14159
|
});
|
|
14027
14160
|
return;
|
|
14028
14161
|
}
|
|
14029
|
-
const deleteSeq = this.
|
|
14162
|
+
const deleteSeq = this._tokenStore.deleteSeq;
|
|
14030
14163
|
if (seq <= 0 && typeof deleteSeq === "function") {
|
|
14031
|
-
deleteSeq.call(this.
|
|
14164
|
+
deleteSeq.call(this._tokenStore, this._aid, this._deviceId, this._slotId, ns).catch((exc) => {
|
|
14032
14165
|
this._clientLog.debug(`delete repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
|
|
14033
14166
|
});
|
|
14034
14167
|
return;
|
|
@@ -14056,6 +14189,19 @@ var _AUNClient = class _AUNClient {
|
|
|
14056
14189
|
);
|
|
14057
14190
|
return repaired;
|
|
14058
14191
|
}
|
|
14192
|
+
async _ensureV2SessionReady(method, errorMessage) {
|
|
14193
|
+
if (!this._v2Session) {
|
|
14194
|
+
if (!this._v2SessionInitInFlight) {
|
|
14195
|
+
this._v2SessionInitInFlight = this._initV2Session().finally(() => {
|
|
14196
|
+
this._v2SessionInitInFlight = null;
|
|
14197
|
+
});
|
|
14198
|
+
}
|
|
14199
|
+
await this._v2SessionInitInFlight;
|
|
14200
|
+
}
|
|
14201
|
+
if (!this._v2Session) {
|
|
14202
|
+
throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
|
|
14203
|
+
}
|
|
14204
|
+
}
|
|
14059
14205
|
// ── V2 E2EE API(async,与 Python `client.py` `_init_v2_session` / `send_v2` / `pull_v2` / `ack_v2` 对齐) ──
|
|
14060
14206
|
/**
|
|
14061
14207
|
* 初始化 V2 session:从 AID PEM 私钥提取 raw scalar + DER 公钥,
|
|
@@ -14065,31 +14211,13 @@ var _AUNClient = class _AUNClient {
|
|
|
14065
14211
|
*/
|
|
14066
14212
|
async _initV2Session() {
|
|
14067
14213
|
if (!this._aid) return;
|
|
14068
|
-
|
|
14069
|
-
if (!
|
|
14070
|
-
try {
|
|
14071
|
-
const reloaded = await this._keystore.loadIdentity(this._aid);
|
|
14072
|
-
if (reloaded?.private_key_pem) {
|
|
14073
|
-
this._identity = reloaded;
|
|
14074
|
-
identity = reloaded;
|
|
14075
|
-
this._clientLog.warn("V2 session init: identity cache was stale, reloaded from keystore");
|
|
14076
|
-
try {
|
|
14077
|
-
const persistIdentity = this._auth._persistIdentity;
|
|
14078
|
-
if (typeof persistIdentity === "function") {
|
|
14079
|
-
await persistIdentity.call(this._auth, reloaded);
|
|
14080
|
-
}
|
|
14081
|
-
} catch {
|
|
14082
|
-
}
|
|
14083
|
-
}
|
|
14084
|
-
} catch {
|
|
14085
|
-
}
|
|
14086
|
-
}
|
|
14087
|
-
if (!identity?.private_key_pem) {
|
|
14214
|
+
const currentAid = this._currentAid;
|
|
14215
|
+
if (!currentAid?.privateKeyPem) {
|
|
14088
14216
|
this._clientLog.warn("V2 session init skipped: no AID private key");
|
|
14089
14217
|
return;
|
|
14090
14218
|
}
|
|
14091
14219
|
if (this._v2Session) return;
|
|
14092
|
-
const pem =
|
|
14220
|
+
const pem = currentAid.privateKeyPem.trim();
|
|
14093
14221
|
const pemBody = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, "");
|
|
14094
14222
|
const pkcs8Der = _v2B64ToBytes(pemBody);
|
|
14095
14223
|
const privKey = await crypto.subtle.importKey(
|
|
@@ -14132,6 +14260,25 @@ var _AUNClient = class _AUNClient {
|
|
|
14132
14260
|
this._clientLog.debug(`V2 session initialized aid=${this._aid} device=${this._deviceId}`);
|
|
14133
14261
|
this._safeAsync(this._v2AutoConfirmPendingProposals());
|
|
14134
14262
|
}
|
|
14263
|
+
_v2StoreDeviceId() {
|
|
14264
|
+
return `aid:${encodeURIComponent(String(this._aid ?? ""))}|device:${encodeURIComponent(String(this._deviceId ?? ""))}`;
|
|
14265
|
+
}
|
|
14266
|
+
async _saveGroupIdentityToV2(groupAid, identity) {
|
|
14267
|
+
const privateKeyPem = String(identity.private_key_pem ?? "").trim();
|
|
14268
|
+
const publicKeyDerB642 = String(identity.public_key_der_b64 ?? "").trim();
|
|
14269
|
+
if (!groupAid || !privateKeyPem || !publicKeyDerB642) {
|
|
14270
|
+
throw new StateError("group identity is incomplete");
|
|
14271
|
+
}
|
|
14272
|
+
if (!this._v2KeyStore) {
|
|
14273
|
+
this._v2KeyStore = await V2KeyStore.open();
|
|
14274
|
+
}
|
|
14275
|
+
await this._v2KeyStore.saveGroupIdentity(
|
|
14276
|
+
this._v2StoreDeviceId(),
|
|
14277
|
+
groupAid,
|
|
14278
|
+
privateKeyPem,
|
|
14279
|
+
base64ToUint8(publicKeyDerB642)
|
|
14280
|
+
);
|
|
14281
|
+
}
|
|
14135
14282
|
async _v2TrustedIKPubDer(aid) {
|
|
14136
14283
|
const normalizedAid = String(aid ?? "").trim();
|
|
14137
14284
|
if (!normalizedAid) throw new E2EEError("spk_aid_missing");
|
|
@@ -14461,6 +14608,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14461
14608
|
decrypted.push(plaintext);
|
|
14462
14609
|
}
|
|
14463
14610
|
}
|
|
14611
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, "server_ack_seq");
|
|
14464
14612
|
const serverAckSeq = Number(result.server_ack_seq ?? 0);
|
|
14465
14613
|
if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
|
|
14466
14614
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
@@ -14476,7 +14624,8 @@ var _AUNClient = class _AUNClient {
|
|
|
14476
14624
|
await this._drainOrderedMessages(ns);
|
|
14477
14625
|
this._saveSeqTrackerState();
|
|
14478
14626
|
}
|
|
14479
|
-
|
|
14627
|
+
const ackNeeded = messages.length > 0 && ackSeq > 0 && (contigAdvanced || hasServerAckSeq && ackSeq > serverAckSeq);
|
|
14628
|
+
if (ackNeeded) {
|
|
14480
14629
|
this._safeAsync(this._ackV2(ackSeq).then(() => void 0));
|
|
14481
14630
|
}
|
|
14482
14631
|
}
|
|
@@ -14505,7 +14654,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14505
14654
|
seq = maxSeen;
|
|
14506
14655
|
}
|
|
14507
14656
|
}
|
|
14508
|
-
const raw = await this.
|
|
14657
|
+
const raw = await this._callRawV2Rpc("message.v2.ack", { up_to_seq: seq });
|
|
14509
14658
|
const result = isJsonObject(raw) ? { ...raw } : { result: raw };
|
|
14510
14659
|
let actualAckSeq = seq;
|
|
14511
14660
|
if ("effective_ack_seq" in result) actualAckSeq = Number(result.effective_ack_seq ?? 0);
|
|
@@ -14775,7 +14924,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14775
14924
|
* @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
|
|
14776
14925
|
* @param limit 最多拉取条数
|
|
14777
14926
|
*/
|
|
14778
|
-
async _pullGroupV2(groupId, afterSeq = 0, limit = 50) {
|
|
14927
|
+
async _pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
|
|
14779
14928
|
if (!this._v2Session) {
|
|
14780
14929
|
throw new StateError("V2 session not initialized (not connected?)");
|
|
14781
14930
|
}
|
|
@@ -14783,15 +14932,18 @@ var _AUNClient = class _AUNClient {
|
|
|
14783
14932
|
if (!gid) throw new ValidationError("group.pull requires group_id");
|
|
14784
14933
|
const ns = `group:${gid}`;
|
|
14785
14934
|
const decrypted = [];
|
|
14786
|
-
|
|
14935
|
+
const cursorParams = opts?.cursorParams ?? {};
|
|
14936
|
+
const ownsCursor = opts?.ownsCursor !== false;
|
|
14937
|
+
let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : afterSeq || this._seqTracker.getContiguousSeq(ns);
|
|
14787
14938
|
let pageCount = 0;
|
|
14788
14939
|
const maxPages = 100;
|
|
14789
14940
|
while (pageCount < maxPages) {
|
|
14790
14941
|
pageCount += 1;
|
|
14791
|
-
const result = await this.
|
|
14942
|
+
const result = await this._callRawV2Rpc("group.v2.pull", {
|
|
14792
14943
|
group_id: gid,
|
|
14793
14944
|
after_seq: nextAfterSeq,
|
|
14794
|
-
limit
|
|
14945
|
+
limit,
|
|
14946
|
+
...cursorParams
|
|
14795
14947
|
});
|
|
14796
14948
|
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
14797
14949
|
const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
@@ -14853,6 +15005,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14853
15005
|
decrypted.push(plaintext);
|
|
14854
15006
|
}
|
|
14855
15007
|
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
15008
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, "current_seq");
|
|
14856
15009
|
const serverAckSeq = Number(cursor?.current_seq ?? 0);
|
|
14857
15010
|
if (Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
|
|
14858
15011
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
@@ -14867,10 +15020,12 @@ var _AUNClient = class _AUNClient {
|
|
|
14867
15020
|
await this._drainOrderedMessages(ns);
|
|
14868
15021
|
this._saveSeqTrackerState();
|
|
14869
15022
|
}
|
|
14870
|
-
|
|
15023
|
+
const ackNeeded = messages.length > 0 && ackSeq > 0 && ownsCursor && (contigAdvanced || hasServerCursor && ackSeq > serverAckSeq);
|
|
15024
|
+
if (ackNeeded) {
|
|
14871
15025
|
this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => void 0));
|
|
14872
15026
|
}
|
|
14873
15027
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
15028
|
+
if (!ownsCursor) break;
|
|
14874
15029
|
if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false) break;
|
|
14875
15030
|
nextAfterSeq = nextAfter;
|
|
14876
15031
|
}
|
|
@@ -14879,6 +15034,28 @@ var _AUNClient = class _AUNClient {
|
|
|
14879
15034
|
}
|
|
14880
15035
|
return decrypted;
|
|
14881
15036
|
}
|
|
15037
|
+
_groupCursorParams(params) {
|
|
15038
|
+
const cursorParams = {};
|
|
15039
|
+
for (const key of ["device_id", "slot_id", "device_name", "device_type"]) {
|
|
15040
|
+
const value = params[key];
|
|
15041
|
+
if (value !== void 0 && value !== null) cursorParams[key] = value;
|
|
15042
|
+
}
|
|
15043
|
+
return cursorParams;
|
|
15044
|
+
}
|
|
15045
|
+
_explicitGroupCursorParams(params) {
|
|
15046
|
+
const value = params._group_cursor_params;
|
|
15047
|
+
if (!isJsonObject(value)) return {};
|
|
15048
|
+
return { ...value };
|
|
15049
|
+
}
|
|
15050
|
+
_groupCursorTargetsCurrentInstance(params) {
|
|
15051
|
+
const deviceId = String(params.device_id ?? "").trim();
|
|
15052
|
+
const slotId = String(params.slot_id ?? "").trim();
|
|
15053
|
+
return (!deviceId || deviceId === (this._deviceId ?? "")) && (!slotId || slotId === (this._slotId ?? ""));
|
|
15054
|
+
}
|
|
15055
|
+
async _rawGroupAckMessages(params) {
|
|
15056
|
+
const p = { ...params };
|
|
15057
|
+
return await this._callRawV2Rpc("group.ack_messages", p);
|
|
15058
|
+
}
|
|
14882
15059
|
/**
|
|
14883
15060
|
* 确认 V2 群消息已消费。
|
|
14884
15061
|
*
|
|
@@ -14896,7 +15073,7 @@ var _AUNClient = class _AUNClient {
|
|
|
14896
15073
|
this._clientLog.warn(`ackGroupV2 clamp: group=${gid} up_to_seq=${seq} > max_seen=${maxSeen}, clamp`);
|
|
14897
15074
|
seq = maxSeen;
|
|
14898
15075
|
}
|
|
14899
|
-
return this.
|
|
15076
|
+
return this._callRawV2Rpc("group.v2.ack", { group_id: gid, up_to_seq: seq });
|
|
14900
15077
|
}
|
|
14901
15078
|
// ── V2 thought(per-device wrap,服务端透传,不持久化)──────────
|
|
14902
15079
|
/**
|
|
@@ -15655,8 +15832,8 @@ var _AUNClient = class _AUNClient {
|
|
|
15655
15832
|
return;
|
|
15656
15833
|
}
|
|
15657
15834
|
let signature = "";
|
|
15658
|
-
const
|
|
15659
|
-
if (
|
|
15835
|
+
const currentAid = this._currentAid;
|
|
15836
|
+
if (currentAid?.privateKeyPem) {
|
|
15660
15837
|
try {
|
|
15661
15838
|
const signPayloadObj = {
|
|
15662
15839
|
group_id: groupId,
|
|
@@ -15666,7 +15843,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15666
15843
|
};
|
|
15667
15844
|
const signPayload = stableStringify(signPayloadObj);
|
|
15668
15845
|
const signPayloadBytes = new TextEncoder().encode(signPayload);
|
|
15669
|
-
const privKey = await importPrivateKeyEcdsa(
|
|
15846
|
+
const privKey = await importPrivateKeyEcdsa(currentAid.privateKeyPem);
|
|
15670
15847
|
const sigBytes = await ecdsaSignDer(privKey, signPayloadBytes);
|
|
15671
15848
|
signature = uint8ToBase64(sigBytes);
|
|
15672
15849
|
} catch (sigExc) {
|
|
@@ -15840,7 +16017,7 @@ var _AUNClient = class _AUNClient {
|
|
|
15840
16017
|
if (newContig > 0 && newContig !== contigBefore) {
|
|
15841
16018
|
const maxSeen = this._seqTracker.getMaxSeenSeq(ns);
|
|
15842
16019
|
const ackSeq = maxSeen > 0 ? Math.min(newContig, maxSeen) : newContig;
|
|
15843
|
-
this.
|
|
16020
|
+
this._callRawV2Rpc("message.v2.ack", { up_to_seq: ackSeq }).catch((e) => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
|
|
15844
16021
|
}
|
|
15845
16022
|
this._clientLog.debug(
|
|
15846
16023
|
`_onV2PushNotification: push \u5E26 payload \u89E3\u5BC6\u6210\u529F, contiguous_seq=${contigBefore}->${newContig} push_seq=${pushSeq}`
|
|
@@ -15998,6 +16175,334 @@ function _uuidV4() {
|
|
|
15998
16175
|
|
|
15999
16176
|
// src/aid-store.ts
|
|
16000
16177
|
init_crypto();
|
|
16178
|
+
|
|
16179
|
+
// src/register-flow.ts
|
|
16180
|
+
init_crypto();
|
|
16181
|
+
var _noopLog6 = { error: () => {
|
|
16182
|
+
}, warn: () => {
|
|
16183
|
+
}, info: () => {
|
|
16184
|
+
}, debug: () => {
|
|
16185
|
+
} };
|
|
16186
|
+
function _gatewayHttpUrl(gatewayUrl, path) {
|
|
16187
|
+
try {
|
|
16188
|
+
const parsed = new URL(gatewayUrl);
|
|
16189
|
+
const scheme = parsed.protocol === "wss:" ? "https:" : "http:";
|
|
16190
|
+
return `${scheme}//${parsed.host}${path}`;
|
|
16191
|
+
} catch {
|
|
16192
|
+
return gatewayUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:") + path;
|
|
16193
|
+
}
|
|
16194
|
+
}
|
|
16195
|
+
function _extractSpkiB64FromPem(certPem) {
|
|
16196
|
+
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
16197
|
+
function readLen(data, offset) {
|
|
16198
|
+
const first = data[offset];
|
|
16199
|
+
if (!(first & 128)) return { value: first, lenBytes: 1 };
|
|
16200
|
+
const n = first & 127;
|
|
16201
|
+
let v = 0;
|
|
16202
|
+
for (let i = 0; i < n; i++) v = v << 8 | data[offset + 1 + i];
|
|
16203
|
+
return { value: v, lenBytes: 1 + n };
|
|
16204
|
+
}
|
|
16205
|
+
function skipTlv(data, offset) {
|
|
16206
|
+
const len = readLen(data, offset + 1);
|
|
16207
|
+
return offset + 1 + len.lenBytes + len.value;
|
|
16208
|
+
}
|
|
16209
|
+
const certLen = readLen(der, 1);
|
|
16210
|
+
const tbsStart = 1 + certLen.lenBytes;
|
|
16211
|
+
const tbsLen = readLen(der, tbsStart + 1);
|
|
16212
|
+
let pos = tbsStart + 1 + tbsLen.lenBytes;
|
|
16213
|
+
if (der[pos] === 160) pos = skipTlv(der, pos);
|
|
16214
|
+
pos = skipTlv(der, pos);
|
|
16215
|
+
pos = skipTlv(der, pos);
|
|
16216
|
+
pos = skipTlv(der, pos);
|
|
16217
|
+
pos = skipTlv(der, pos);
|
|
16218
|
+
pos = skipTlv(der, pos);
|
|
16219
|
+
const spkiLen = readLen(der, pos + 1);
|
|
16220
|
+
const spkiEnd = pos + 1 + spkiLen.lenBytes + spkiLen.value;
|
|
16221
|
+
return uint8ToBase64(der.slice(pos, spkiEnd));
|
|
16222
|
+
}
|
|
16223
|
+
var RegisterFlow = class {
|
|
16224
|
+
constructor(opts) {
|
|
16225
|
+
__publicField(this, "_keystore");
|
|
16226
|
+
__publicField(this, "_crypto");
|
|
16227
|
+
__publicField(this, "_logger");
|
|
16228
|
+
this._keystore = opts.keystore;
|
|
16229
|
+
this._crypto = opts.crypto;
|
|
16230
|
+
this._logger = opts.logger ?? _noopLog6;
|
|
16231
|
+
}
|
|
16232
|
+
async registerAid(gatewayUrl, aid) {
|
|
16233
|
+
const tStart = Date.now();
|
|
16234
|
+
AuthFlow._validateAidName(aid);
|
|
16235
|
+
this._logger.debug(`registerAid enter: aid=${aid}, gateway=${gatewayUrl}`);
|
|
16236
|
+
try {
|
|
16237
|
+
const existing = await this._keystore.loadIdentity(aid);
|
|
16238
|
+
if (existing && existing.private_key_pem && existing.public_key_der_b64) {
|
|
16239
|
+
this._logger.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
|
|
16240
|
+
const localPubB64 = String(existing.public_key_der_b64);
|
|
16241
|
+
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
16242
|
+
if (serverCertPem) {
|
|
16243
|
+
const serverPubB64 = _extractSpkiB64FromPem(serverCertPem);
|
|
16244
|
+
if (serverPubB64 !== localPubB64) {
|
|
16245
|
+
throw new IdentityConflictError(
|
|
16246
|
+
`AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
|
|
16247
|
+
);
|
|
16248
|
+
}
|
|
16249
|
+
this._logger.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
|
|
16250
|
+
if (!existing.cert) {
|
|
16251
|
+
existing.cert = serverCertPem;
|
|
16252
|
+
await this._persistIdentity(existing);
|
|
16253
|
+
}
|
|
16254
|
+
this._logger.debug(`registerAid exit (idempotent): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
16255
|
+
return {
|
|
16256
|
+
aid,
|
|
16257
|
+
cert: serverCertPem,
|
|
16258
|
+
private_key_pem: String(existing.private_key_pem),
|
|
16259
|
+
public_key_der_b64: localPubB64,
|
|
16260
|
+
curve: String(existing.curve ?? "P-256")
|
|
16261
|
+
};
|
|
16262
|
+
} else {
|
|
16263
|
+
this._logger.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
|
|
16264
|
+
const created2 = await this._createAid(gatewayUrl, existing);
|
|
16265
|
+
const certPem2 = String(created2.cert ?? "");
|
|
16266
|
+
if (!certPem2) throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
16267
|
+
existing.cert = certPem2;
|
|
16268
|
+
this._assertCertMatchesLocalKeypair(existing);
|
|
16269
|
+
await this._persistIdentity(existing);
|
|
16270
|
+
this._logger.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
16271
|
+
return {
|
|
16272
|
+
aid,
|
|
16273
|
+
cert: certPem2,
|
|
16274
|
+
private_key_pem: String(existing.private_key_pem),
|
|
16275
|
+
public_key_der_b64: localPubB64,
|
|
16276
|
+
curve: String(existing.curve ?? "P-256")
|
|
16277
|
+
};
|
|
16278
|
+
}
|
|
16279
|
+
}
|
|
16280
|
+
const recovered = await this._tryRecoverPendingRegistration(gatewayUrl, aid);
|
|
16281
|
+
if (recovered !== null) {
|
|
16282
|
+
this._logger.info(`registerAid recovered from pending: aid=${aid}`);
|
|
16283
|
+
return recovered;
|
|
16284
|
+
}
|
|
16285
|
+
const existingCert = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
16286
|
+
if (existingCert) {
|
|
16287
|
+
throw new IdentityConflictError(
|
|
16288
|
+
`AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
|
|
16289
|
+
);
|
|
16290
|
+
}
|
|
16291
|
+
const identity = await this._crypto.generateIdentity();
|
|
16292
|
+
identity.aid = aid;
|
|
16293
|
+
const pendingStore = this._pendingStore();
|
|
16294
|
+
const pendingHandle = await pendingStore.pendingIdentityDir(aid);
|
|
16295
|
+
await pendingStore.savePendingKeyPair(pendingHandle, aid, identity);
|
|
16296
|
+
let created;
|
|
16297
|
+
try {
|
|
16298
|
+
created = await this._createAid(gatewayUrl, identity);
|
|
16299
|
+
} catch (e) {
|
|
16300
|
+
this._logger.warn(`registerAid RPC failed (pending kept for recovery): aid=${aid}, pending=${pendingHandle}, error=${e instanceof Error ? e.message : String(e)}`);
|
|
16301
|
+
throw e;
|
|
16302
|
+
}
|
|
16303
|
+
const certPem = String(created.cert ?? "");
|
|
16304
|
+
if (!certPem) throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
16305
|
+
identity.cert = certPem;
|
|
16306
|
+
this._assertCertMatchesLocalKeypair(identity);
|
|
16307
|
+
await pendingStore.savePendingCert(pendingHandle, certPem);
|
|
16308
|
+
try {
|
|
16309
|
+
await pendingStore.promotePendingIdentity(pendingHandle, aid);
|
|
16310
|
+
} catch (e) {
|
|
16311
|
+
throw new IdentityConflictError(
|
|
16312
|
+
`AID '${aid}' was created by another process during registration; pending record kept for cleanup.`
|
|
16313
|
+
);
|
|
16314
|
+
}
|
|
16315
|
+
await this._persistIdentity(identity);
|
|
16316
|
+
this._logger.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
16317
|
+
return {
|
|
16318
|
+
aid,
|
|
16319
|
+
cert: certPem,
|
|
16320
|
+
private_key_pem: String(identity.private_key_pem ?? ""),
|
|
16321
|
+
public_key_der_b64: String(identity.public_key_der_b64 ?? ""),
|
|
16322
|
+
curve: String(identity.curve ?? "P-256")
|
|
16323
|
+
};
|
|
16324
|
+
} catch (err) {
|
|
16325
|
+
this._logger.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
16326
|
+
throw err;
|
|
16327
|
+
}
|
|
16328
|
+
}
|
|
16329
|
+
_pendingStore() {
|
|
16330
|
+
const store = this._keystore;
|
|
16331
|
+
for (const name of [
|
|
16332
|
+
"pendingIdentityDir",
|
|
16333
|
+
"listPendingIdentityDirs",
|
|
16334
|
+
"savePendingKeyPair",
|
|
16335
|
+
"loadPendingKeyPair",
|
|
16336
|
+
"savePendingCert",
|
|
16337
|
+
"promotePendingIdentity"
|
|
16338
|
+
]) {
|
|
16339
|
+
if (typeof store[name] !== "function") {
|
|
16340
|
+
throw new AuthError(`keystore does not support pending registration: ${name}`);
|
|
16341
|
+
}
|
|
16342
|
+
}
|
|
16343
|
+
return store;
|
|
16344
|
+
}
|
|
16345
|
+
async _tryRecoverPendingRegistration(gatewayUrl, aid) {
|
|
16346
|
+
const pendingStore = this._pendingStore();
|
|
16347
|
+
const handles = await pendingStore.listPendingIdentityDirs(aid);
|
|
16348
|
+
for (const handle of handles) {
|
|
16349
|
+
let keyPair;
|
|
16350
|
+
try {
|
|
16351
|
+
keyPair = await pendingStore.loadPendingKeyPair(handle, aid);
|
|
16352
|
+
} catch (exc) {
|
|
16353
|
+
this._logger.warn(`pending identity keypair load failed; keeping pending for retry: aid=${aid}, pending=${handle}, error=${exc instanceof Error ? exc.message : String(exc)}`);
|
|
16354
|
+
throw exc;
|
|
16355
|
+
}
|
|
16356
|
+
const privateKeyPem = String(keyPair?.private_key_pem ?? "");
|
|
16357
|
+
const publicKeyDerB642 = String(keyPair?.public_key_der_b64 ?? "");
|
|
16358
|
+
if (!privateKeyPem || !publicKeyDerB642) {
|
|
16359
|
+
await pendingStore.discardPendingIdentity?.(handle);
|
|
16360
|
+
continue;
|
|
16361
|
+
}
|
|
16362
|
+
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
16363
|
+
if (!serverCertPem) {
|
|
16364
|
+
this._logger.info(`pending record found but server has no registration; cleaning up: ${handle}`);
|
|
16365
|
+
await pendingStore.discardPendingIdentity?.(handle);
|
|
16366
|
+
return null;
|
|
16367
|
+
}
|
|
16368
|
+
const serverPubB64 = _extractSpkiB64FromPem(serverCertPem);
|
|
16369
|
+
if (serverPubB64 !== publicKeyDerB642) {
|
|
16370
|
+
await pendingStore.discardPendingIdentity?.(handle);
|
|
16371
|
+
throw new IdentityConflictError(
|
|
16372
|
+
`AID '${aid}' has been registered by another party while local pending registration was incomplete; local pending key discarded.`
|
|
16373
|
+
);
|
|
16374
|
+
}
|
|
16375
|
+
const identity = {
|
|
16376
|
+
aid,
|
|
16377
|
+
cert: serverCertPem,
|
|
16378
|
+
private_key_pem: privateKeyPem,
|
|
16379
|
+
public_key_der_b64: publicKeyDerB642,
|
|
16380
|
+
curve: String(keyPair?.curve ?? "P-256")
|
|
16381
|
+
};
|
|
16382
|
+
await pendingStore.savePendingKeyPair(handle, aid, identity);
|
|
16383
|
+
await pendingStore.savePendingCert(handle, serverCertPem);
|
|
16384
|
+
try {
|
|
16385
|
+
await pendingStore.promotePendingIdentity(handle, aid);
|
|
16386
|
+
} catch (e) {
|
|
16387
|
+
throw new IdentityConflictError(
|
|
16388
|
+
`AID '${aid}' was created by another process during recovery; pending record kept for cleanup.`
|
|
16389
|
+
);
|
|
16390
|
+
}
|
|
16391
|
+
await this._persistIdentity(identity);
|
|
16392
|
+
return {
|
|
16393
|
+
aid,
|
|
16394
|
+
cert: serverCertPem,
|
|
16395
|
+
private_key_pem: privateKeyPem,
|
|
16396
|
+
public_key_der_b64: publicKeyDerB642,
|
|
16397
|
+
curve: String(keyPair?.curve ?? "P-256")
|
|
16398
|
+
};
|
|
16399
|
+
}
|
|
16400
|
+
return null;
|
|
16401
|
+
}
|
|
16402
|
+
async _downloadRegisteredCert(gatewayUrl, aid) {
|
|
16403
|
+
const certUrl = _gatewayHttpUrl(gatewayUrl, `/pki/cert/${aid}`);
|
|
16404
|
+
const controller = new AbortController();
|
|
16405
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
16406
|
+
try {
|
|
16407
|
+
const resp = await fetch(certUrl, { signal: controller.signal });
|
|
16408
|
+
if (!resp.ok) return null;
|
|
16409
|
+
const certPem = await resp.text();
|
|
16410
|
+
if (!certPem || !certPem.includes("BEGIN CERTIFICATE")) return null;
|
|
16411
|
+
return certPem;
|
|
16412
|
+
} catch {
|
|
16413
|
+
return null;
|
|
16414
|
+
} finally {
|
|
16415
|
+
clearTimeout(timer);
|
|
16416
|
+
}
|
|
16417
|
+
}
|
|
16418
|
+
async _createAid(gatewayUrl, identity) {
|
|
16419
|
+
const response = await this._shortRpc(gatewayUrl, "auth.create_aid", {
|
|
16420
|
+
aid: identity.aid,
|
|
16421
|
+
public_key: identity.public_key_der_b64,
|
|
16422
|
+
curve: identity.curve ?? "P-256"
|
|
16423
|
+
});
|
|
16424
|
+
return { cert: response.cert };
|
|
16425
|
+
}
|
|
16426
|
+
_shortRpc(gatewayUrl, method, params) {
|
|
16427
|
+
return new Promise((resolve, reject) => {
|
|
16428
|
+
let ws;
|
|
16429
|
+
try {
|
|
16430
|
+
ws = new WebSocket(gatewayUrl);
|
|
16431
|
+
} catch {
|
|
16432
|
+
reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
|
|
16433
|
+
return;
|
|
16434
|
+
}
|
|
16435
|
+
let receivedChallenge = false;
|
|
16436
|
+
const timeout = globalThis.setTimeout(() => {
|
|
16437
|
+
try {
|
|
16438
|
+
ws.close();
|
|
16439
|
+
} catch {
|
|
16440
|
+
}
|
|
16441
|
+
reject(new AuthError(`shortRpc \u8D85\u65F6: ${method}`));
|
|
16442
|
+
}, 1e4);
|
|
16443
|
+
ws.onerror = () => {
|
|
16444
|
+
globalThis.clearTimeout(timeout);
|
|
16445
|
+
reject(new AuthError(`WebSocket \u8FDE\u63A5\u9519\u8BEF: ${gatewayUrl}`));
|
|
16446
|
+
};
|
|
16447
|
+
ws.onmessage = (event) => {
|
|
16448
|
+
try {
|
|
16449
|
+
const msg = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
|
|
16450
|
+
if (!receivedChallenge) {
|
|
16451
|
+
receivedChallenge = true;
|
|
16452
|
+
ws.send(JSON.stringify({ jsonrpc: "2.0", id: `pre-${method}`, method, params }));
|
|
16453
|
+
return;
|
|
16454
|
+
}
|
|
16455
|
+
globalThis.clearTimeout(timeout);
|
|
16456
|
+
try {
|
|
16457
|
+
ws.close();
|
|
16458
|
+
} catch {
|
|
16459
|
+
}
|
|
16460
|
+
if (msg.error) {
|
|
16461
|
+
reject(mapRemoteError(msg.error));
|
|
16462
|
+
return;
|
|
16463
|
+
}
|
|
16464
|
+
const result = msg.result;
|
|
16465
|
+
if (!isJsonObject(result)) {
|
|
16466
|
+
reject(new ValidationError(`invalid pre-auth response for ${method}`));
|
|
16467
|
+
return;
|
|
16468
|
+
}
|
|
16469
|
+
if (result.success === false) {
|
|
16470
|
+
reject(new AuthError(String(result.error ?? `${method} failed`)));
|
|
16471
|
+
return;
|
|
16472
|
+
}
|
|
16473
|
+
resolve(result);
|
|
16474
|
+
} catch (e) {
|
|
16475
|
+
globalThis.clearTimeout(timeout);
|
|
16476
|
+
try {
|
|
16477
|
+
ws.close();
|
|
16478
|
+
} catch {
|
|
16479
|
+
}
|
|
16480
|
+
reject(e instanceof Error ? e : new AuthError(String(e)));
|
|
16481
|
+
}
|
|
16482
|
+
};
|
|
16483
|
+
});
|
|
16484
|
+
}
|
|
16485
|
+
_assertCertMatchesLocalKeypair(identity) {
|
|
16486
|
+
const aid = identity.aid ?? "?";
|
|
16487
|
+
const certPem = identity.cert;
|
|
16488
|
+
const localPubB64 = identity.public_key_der_b64;
|
|
16489
|
+
if (!certPem || !localPubB64) {
|
|
16490
|
+
throw new AuthError(`identity for aid ${aid} missing cert or public key`);
|
|
16491
|
+
}
|
|
16492
|
+
const certPubB64 = _extractSpkiB64FromPem(certPem);
|
|
16493
|
+
if (certPubB64 !== localPubB64) {
|
|
16494
|
+
throw new AuthError(`local certificate public key does not match local keypair for aid ${aid}`);
|
|
16495
|
+
}
|
|
16496
|
+
}
|
|
16497
|
+
async _persistIdentity(identity) {
|
|
16498
|
+
const aid = String(identity.aid ?? "");
|
|
16499
|
+
if (!aid) return;
|
|
16500
|
+
const certPem = String(identity.cert ?? "");
|
|
16501
|
+
if (certPem) await this._keystore.saveCert(aid, certPem);
|
|
16502
|
+
}
|
|
16503
|
+
};
|
|
16504
|
+
|
|
16505
|
+
// src/aid-store.ts
|
|
16001
16506
|
function _derReadLength(data, offset) {
|
|
16002
16507
|
if (offset >= data.length) return null;
|
|
16003
16508
|
const first = data[offset];
|
|
@@ -16223,6 +16728,7 @@ var AIDStore = class {
|
|
|
16223
16728
|
__publicField(this, "_encryptionSeed");
|
|
16224
16729
|
__publicField(this, "_keystore");
|
|
16225
16730
|
__publicField(this, "_auth");
|
|
16731
|
+
__publicField(this, "_registerFlow");
|
|
16226
16732
|
__publicField(this, "_crypto");
|
|
16227
16733
|
__publicField(this, "_discovery");
|
|
16228
16734
|
__publicField(this, "_verifySsl");
|
|
@@ -16240,13 +16746,18 @@ var AIDStore = class {
|
|
|
16240
16746
|
this._crypto = new CryptoProvider();
|
|
16241
16747
|
this._discovery = new GatewayDiscovery();
|
|
16242
16748
|
this._auth = new AuthFlow({
|
|
16243
|
-
|
|
16749
|
+
tokenStore: this._keystore,
|
|
16244
16750
|
crypto: this._crypto,
|
|
16245
16751
|
deviceId: this.deviceId,
|
|
16246
16752
|
slotId: this.slotId,
|
|
16247
16753
|
rootCaPem: opts.rootCaPem ?? null,
|
|
16248
16754
|
verifySsl: this._verifySsl
|
|
16249
16755
|
});
|
|
16756
|
+
this._registerFlow = new RegisterFlow({
|
|
16757
|
+
keystore: this._keystore,
|
|
16758
|
+
crypto: this._crypto,
|
|
16759
|
+
verifySsl: this._verifySsl
|
|
16760
|
+
});
|
|
16250
16761
|
}
|
|
16251
16762
|
close() {
|
|
16252
16763
|
const close = this._keystore.close;
|
|
@@ -16365,7 +16876,17 @@ var AIDStore = class {
|
|
|
16365
16876
|
try {
|
|
16366
16877
|
validateRegisterAidName(target);
|
|
16367
16878
|
const gatewayUrl = await this._resolveGateway(target);
|
|
16368
|
-
await this.
|
|
16879
|
+
const result = await this._registerFlow.registerAid(gatewayUrl, target);
|
|
16880
|
+
if (result.cert) {
|
|
16881
|
+
await this._keystore.saveCert(target, result.cert);
|
|
16882
|
+
}
|
|
16883
|
+
if (result.private_key_pem || result.public_key_der_b64) {
|
|
16884
|
+
await this._keystore.saveKeyPair(target, {
|
|
16885
|
+
private_key_pem: result.private_key_pem,
|
|
16886
|
+
public_key_der_b64: result.public_key_der_b64,
|
|
16887
|
+
curve: result.curve
|
|
16888
|
+
});
|
|
16889
|
+
}
|
|
16369
16890
|
return resultOk({ registered: true });
|
|
16370
16891
|
} catch (exc) {
|
|
16371
16892
|
if (exc instanceof IdentityConflictError) {
|
|
@@ -16568,18 +17089,18 @@ var AIDStore = class {
|
|
|
16568
17089
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16569
17090
|
}
|
|
16570
17091
|
try {
|
|
16571
|
-
const
|
|
16572
|
-
if (!
|
|
17092
|
+
const aidObj = loaded.data.aid;
|
|
17093
|
+
if (!aidObj.certPem || !aidObj.privateKeyPem) {
|
|
16573
17094
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16574
17095
|
}
|
|
16575
17096
|
const gatewayUrl = await this._resolveGateway(target);
|
|
16576
17097
|
const clientNonce = this._crypto.newClientNonce();
|
|
16577
17098
|
const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
|
|
16578
17099
|
aid: target,
|
|
16579
|
-
cert:
|
|
17100
|
+
cert: aidObj.certPem,
|
|
16580
17101
|
client_nonce: clientNonce
|
|
16581
17102
|
});
|
|
16582
|
-
const [signature, clientTime] = await this._crypto.signLoginNonce(
|
|
17103
|
+
const [signature, clientTime] = await this._crypto.signLoginNonce(aidObj.privateKeyPem, String(phase1.nonce ?? ""));
|
|
16583
17104
|
const response = await this._auth._shortRpc(gatewayUrl, "auth.renew_cert", {
|
|
16584
17105
|
aid: target,
|
|
16585
17106
|
request_id: String(phase1.request_id ?? ""),
|
|
@@ -16608,8 +17129,8 @@ var AIDStore = class {
|
|
|
16608
17129
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16609
17130
|
}
|
|
16610
17131
|
try {
|
|
16611
|
-
const
|
|
16612
|
-
if (!
|
|
17132
|
+
const oldAid = loaded.data.aid;
|
|
17133
|
+
if (!oldAid.certPem || !oldAid.privateKeyPem) {
|
|
16613
17134
|
return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
|
|
16614
17135
|
}
|
|
16615
17136
|
const gatewayUrl = await this._resolveGateway(target);
|
|
@@ -16617,7 +17138,7 @@ var AIDStore = class {
|
|
|
16617
17138
|
const clientNonce = this._crypto.newClientNonce();
|
|
16618
17139
|
const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
|
|
16619
17140
|
aid: target,
|
|
16620
|
-
cert:
|
|
17141
|
+
cert: oldAid.certPem,
|
|
16621
17142
|
client_nonce: clientNonce
|
|
16622
17143
|
});
|
|
16623
17144
|
const signPayload = `${String(phase1.nonce ?? "")}${newIdentity.public_key_der_b64}`;
|
|
@@ -16798,6 +17319,7 @@ export {
|
|
|
16798
17319
|
ROOT_CA_PEM,
|
|
16799
17320
|
RPCTransport,
|
|
16800
17321
|
RateLimitError,
|
|
17322
|
+
RegisterFlow,
|
|
16801
17323
|
STATE_PREFIX,
|
|
16802
17324
|
SeedMigrationError,
|
|
16803
17325
|
SerializationError,
|