@agentunion/fastaun-browser 0.2.17 → 0.2.19
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/dist/auth.d.ts +12 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +370 -215
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +24 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1307 -849
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/discovery.d.ts +3 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +15 -1
- package/dist/discovery.js.map +1 -1
- package/dist/e2ee-group.d.ts +4 -0
- package/dist/e2ee-group.d.ts.map +1 -1
- package/dist/e2ee-group.js +327 -201
- package/dist/e2ee-group.js.map +1 -1
- package/dist/e2ee.d.ts +4 -0
- package/dist/e2ee.d.ts.map +1 -1
- package/dist/e2ee.js +196 -117
- package/dist/e2ee.js.map +1 -1
- package/dist/events.d.ts +3 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +4 -1
- package/dist/events.js.map +1 -1
- package/dist/keystore/index.d.ts +11 -0
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb.d.ts +38 -0
- package/dist/keystore/indexeddb.d.ts.map +1 -1
- package/dist/keystore/indexeddb.js +245 -98
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/logger.d.ts +37 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +112 -0
- package/dist/logger.js.map +1 -0
- package/dist/namespaces/auth.d.ts +13 -3
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +284 -106
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/namespaces/custody.d.ts +3 -0
- package/dist/namespaces/custody.d.ts.map +1 -1
- package/dist/namespaces/custody.js +147 -75
- package/dist/namespaces/custody.js.map +1 -1
- package/dist/namespaces/meta.d.ts +3 -0
- package/dist/namespaces/meta.d.ts.map +1 -1
- package/dist/namespaces/meta.js +94 -43
- package/dist/namespaces/meta.js.map +1 -1
- package/dist/secret-store/indexeddb-store.d.ts +3 -0
- package/dist/secret-store/indexeddb-store.d.ts.map +1 -1
- package/dist/secret-store/indexeddb-store.js +57 -29
- package/dist/secret-store/indexeddb-store.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +74 -4
- package/dist/transport.js.map +1 -1
- package/package.json +37 -37
package/dist/e2ee.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// 所有密码学操作均为异步(SubtleCrypto API 要求)
|
|
3
3
|
import { E2EEDecryptFailedError, E2EEError } from './errors.js';
|
|
4
4
|
import { uint8ToBase64, base64ToUint8, pemToArrayBuffer, p1363ToDer, toArrayBuffer, toBufferSource } from './crypto.js';
|
|
5
|
+
const _noopLog = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
|
|
6
|
+
// 顶层函数共享的模块 logger(client 构造时通过 setModuleLogger 注入)
|
|
7
|
+
let _moduleLog = _noopLog;
|
|
8
|
+
export function setModuleLogger(log) { _moduleLog = log; }
|
|
5
9
|
/** 加密套件标识 */
|
|
6
10
|
export const SUITE = 'P256_HKDF_SHA256_AES_256_GCM';
|
|
7
11
|
/** 加密模式 */
|
|
@@ -558,6 +562,8 @@ async function importUncompressedPointEcdh(pointBytes) {
|
|
|
558
562
|
* 所有密码学操作均为异步(SubtleCrypto 要求)。
|
|
559
563
|
*/
|
|
560
564
|
export class E2EEManager {
|
|
565
|
+
_log = _noopLog;
|
|
566
|
+
setLogger(log) { this._log = log; }
|
|
561
567
|
_identityFn;
|
|
562
568
|
_deviceIdFn;
|
|
563
569
|
_keystoreRef;
|
|
@@ -607,16 +613,26 @@ export class E2EEManager {
|
|
|
607
613
|
* 调用方负责提前获取 peerCertPem 和 prekey(可选)。
|
|
608
614
|
*/
|
|
609
615
|
async encryptMessage(toAid, payload, opts) {
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
616
|
+
const tStart = Date.now();
|
|
617
|
+
this._log.debug(`encryptMessage enter: to_aid=${toAid} has_prekey=${!!opts.prekey}`);
|
|
618
|
+
try {
|
|
619
|
+
const messageId = opts.messageId ?? uuidV4();
|
|
620
|
+
const timestamp = opts.timestamp ?? Date.now();
|
|
621
|
+
const result = await this.encryptOutbound(toAid, payload, {
|
|
622
|
+
peerCertPem: opts.peerCertPem,
|
|
623
|
+
prekey: opts.prekey ?? null,
|
|
624
|
+
messageId,
|
|
625
|
+
timestamp,
|
|
626
|
+
protectedHeaders: opts.protectedHeaders ?? opts.protected_headers ?? opts.headers,
|
|
627
|
+
context: opts.context ?? null,
|
|
628
|
+
});
|
|
629
|
+
this._log.debug(`encryptMessage exit: elapsed=${Date.now() - tStart}ms to_aid=${toAid} mode=${result[1].mode}`);
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
this._log.debug(`encryptMessage exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
634
|
+
throw err;
|
|
635
|
+
}
|
|
620
636
|
}
|
|
621
637
|
// ── 加密 ──────────────────────────────────────────
|
|
622
638
|
/**
|
|
@@ -626,37 +642,47 @@ export class E2EEManager {
|
|
|
626
642
|
* prekey 传入时自动缓存;传入 null 时自动查缓存。
|
|
627
643
|
*/
|
|
628
644
|
async encryptOutbound(peerAid, payload, opts) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
if (prekey) {
|
|
638
|
-
try {
|
|
639
|
-
const envelope = await this._encryptWithPrekey(peerAid, payload, prekey, opts.peerCertPem, opts.messageId, opts.timestamp, opts.protectedHeaders ?? opts.protected_headers ?? opts.headers, opts.context ?? null);
|
|
640
|
-
return [envelope, {
|
|
641
|
-
encrypted: true,
|
|
642
|
-
forward_secrecy: true,
|
|
643
|
-
mode: MODE_PREKEY_ECDH_V2,
|
|
644
|
-
degraded: false,
|
|
645
|
-
}];
|
|
645
|
+
const tStart = Date.now();
|
|
646
|
+
this._log.debug(`encryptOutbound enter: peer_aid=${peerAid} mid=${opts.messageId} has_prekey=${!!opts.prekey}`);
|
|
647
|
+
try {
|
|
648
|
+
let prekey = opts.prekey ?? null;
|
|
649
|
+
// 传入 prekey → 缓存;传入 null → 查缓存
|
|
650
|
+
if (prekey !== null) {
|
|
651
|
+
this.cachePrekey(peerAid, prekey);
|
|
646
652
|
}
|
|
647
|
-
|
|
648
|
-
|
|
653
|
+
else {
|
|
654
|
+
prekey = this.getCachedPrekey(peerAid);
|
|
649
655
|
}
|
|
656
|
+
if (prekey) {
|
|
657
|
+
try {
|
|
658
|
+
const envelope = await this._encryptWithPrekey(peerAid, payload, prekey, opts.peerCertPem, opts.messageId, opts.timestamp, opts.protectedHeaders ?? opts.protected_headers ?? opts.headers, opts.context ?? null);
|
|
659
|
+
this._log.debug(`encryptOutbound exit: elapsed=${Date.now() - tStart}ms peer_aid=${peerAid} mode=prekey_ecdh_v2`);
|
|
660
|
+
return [envelope, {
|
|
661
|
+
encrypted: true,
|
|
662
|
+
forward_secrecy: true,
|
|
663
|
+
mode: MODE_PREKEY_ECDH_V2,
|
|
664
|
+
degraded: false,
|
|
665
|
+
}];
|
|
666
|
+
}
|
|
667
|
+
catch (exc) {
|
|
668
|
+
this._log.warn('prekey encrypt failed, degrade to long_term_key (no forward secrecy):', exc);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const envelope = await this._encryptWithLongTermKey(peerAid, payload, opts.peerCertPem, opts.messageId, opts.timestamp, opts.protectedHeaders ?? opts.protected_headers ?? opts.headers, opts.context ?? null);
|
|
672
|
+
const degraded = prekey !== null; // 有 prekey 但失败了才算降级
|
|
673
|
+
this._log.debug(`encryptOutbound exit: elapsed=${Date.now() - tStart}ms peer_aid=${peerAid} mode=long_term_key degraded=${degraded}`);
|
|
674
|
+
return [envelope, {
|
|
675
|
+
encrypted: true,
|
|
676
|
+
forward_secrecy: false,
|
|
677
|
+
mode: MODE_LONG_TERM_KEY,
|
|
678
|
+
degraded,
|
|
679
|
+
degradation_reason: degraded ? 'prekey_encrypt_failed' : 'no_prekey_available',
|
|
680
|
+
}];
|
|
681
|
+
}
|
|
682
|
+
catch (err) {
|
|
683
|
+
this._log.debug(`encryptOutbound exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
684
|
+
throw err;
|
|
650
685
|
}
|
|
651
|
-
const envelope = await this._encryptWithLongTermKey(peerAid, payload, opts.peerCertPem, opts.messageId, opts.timestamp, opts.protectedHeaders ?? opts.protected_headers ?? opts.headers, opts.context ?? null);
|
|
652
|
-
const degraded = prekey !== null; // 有 prekey 但失败了才算降级
|
|
653
|
-
return [envelope, {
|
|
654
|
-
encrypted: true,
|
|
655
|
-
forward_secrecy: false,
|
|
656
|
-
mode: MODE_LONG_TERM_KEY,
|
|
657
|
-
degraded,
|
|
658
|
-
degradation_reason: degraded ? 'prekey_encrypt_failed' : 'no_prekey_available',
|
|
659
|
-
}];
|
|
660
686
|
}
|
|
661
687
|
/**
|
|
662
688
|
* 使用对方 prekey 加密(prekey_ecdh_v2 模式,四路 ECDH + 发送方签名)
|
|
@@ -828,44 +854,68 @@ export class E2EEManager {
|
|
|
828
854
|
* opts.skipReplay: 跳过防重放和 timestamp 窗口检查(用于 message.pull 场景)。
|
|
829
855
|
*/
|
|
830
856
|
async decryptMessage(message, opts) {
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const skipReplay = opts?.skipReplay ?? false;
|
|
841
|
-
if (!skipReplay) {
|
|
842
|
-
// timestamp 窗口检查
|
|
843
|
-
const ts = (message.timestamp ?? payload.aad?.timestamp);
|
|
844
|
-
if (typeof ts === 'number' && this._replayWindowSeconds > 0) {
|
|
845
|
-
const nowMs = Date.now();
|
|
846
|
-
const diffS = Math.abs(nowMs - ts) / 1000;
|
|
847
|
-
if (diffS > this._replayWindowSeconds) {
|
|
848
|
-
console.warn(`消息 timestamp 超出窗口 (${Math.round(diffS)}s > ${this._replayWindowSeconds}s),拒绝: from=${message.from} mid=${message.message_id}`);
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
857
|
+
const tStart = Date.now();
|
|
858
|
+
const mid = String(message.message_id ?? '');
|
|
859
|
+
const fromAid = String(message.from ?? '');
|
|
860
|
+
this._log.debug(`decryptMessage enter: from=${fromAid} mid=${mid} skip_replay=${!!opts?.skipReplay}`);
|
|
861
|
+
try {
|
|
862
|
+
const payload = message.payload;
|
|
863
|
+
if (!payload || typeof payload !== 'object') {
|
|
864
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=passthrough_no_payload`);
|
|
865
|
+
return message;
|
|
851
866
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
let seenKey = '';
|
|
856
|
-
if (messageIdVal && fromAid) {
|
|
857
|
-
seenKey = `${fromAid}:${messageIdVal}`;
|
|
858
|
-
if (this._seenMessages.has(seenKey))
|
|
859
|
-
return null;
|
|
867
|
+
if (payload.type !== 'e2ee.encrypted') {
|
|
868
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=passthrough_not_encrypted`);
|
|
869
|
+
return message;
|
|
860
870
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
871
|
+
if (message.encrypted === false) {
|
|
872
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=passthrough_flag_false`);
|
|
873
|
+
return message;
|
|
874
|
+
}
|
|
875
|
+
if (!this._shouldDecryptForCurrentAid(message, payload)) {
|
|
876
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=passthrough_not_for_current_aid`);
|
|
877
|
+
return message;
|
|
878
|
+
}
|
|
879
|
+
const skipReplay = opts?.skipReplay ?? false;
|
|
880
|
+
if (!skipReplay) {
|
|
881
|
+
// timestamp 窗口检查
|
|
882
|
+
const ts = (message.timestamp ?? payload.aad?.timestamp);
|
|
883
|
+
if (typeof ts === 'number' && this._replayWindowSeconds > 0) {
|
|
884
|
+
const nowMs = Date.now();
|
|
885
|
+
const diffS = Math.abs(nowMs - ts) / 1000;
|
|
886
|
+
if (diffS > this._replayWindowSeconds) {
|
|
887
|
+
this._log.warn(`消息 timestamp 超出窗口 (${Math.round(diffS)}s > ${this._replayWindowSeconds}s),拒绝: from=${message.from} mid=${message.message_id}`);
|
|
888
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=rejected_timestamp_window`);
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
// 本地防重放:先检查,解密成功后再记录。
|
|
893
|
+
const messageIdVal = message.message_id;
|
|
894
|
+
const fromAidLocal = message.from;
|
|
895
|
+
let seenKey = '';
|
|
896
|
+
if (messageIdVal && fromAidLocal) {
|
|
897
|
+
seenKey = `${fromAidLocal}:${messageIdVal}`;
|
|
898
|
+
if (this._seenMessages.has(seenKey)) {
|
|
899
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=rejected_replay`);
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const result = await this._decryptMessageInternal(message);
|
|
904
|
+
if (result !== null && seenKey) {
|
|
905
|
+
this._seenMessages.set(seenKey, true);
|
|
906
|
+
this._trimSeenSet();
|
|
907
|
+
}
|
|
908
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=${result !== null ? 'ok' : 'failed'}`);
|
|
909
|
+
return result;
|
|
865
910
|
}
|
|
911
|
+
const result = await this._decryptMessageInternal(message);
|
|
912
|
+
this._log.debug(`decryptMessage exit: elapsed=${Date.now() - tStart}ms result=${result !== null ? 'ok' : 'failed'} skip_replay=true`);
|
|
866
913
|
return result;
|
|
867
914
|
}
|
|
868
|
-
|
|
915
|
+
catch (err) {
|
|
916
|
+
this._log.debug(`decryptMessage exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
917
|
+
throw err;
|
|
918
|
+
}
|
|
869
919
|
}
|
|
870
920
|
/** 判断是否应该为当前 AID 解密(避免发送端回显消息误走解密) */
|
|
871
921
|
_shouldDecryptForCurrentAid(message, payload) {
|
|
@@ -890,7 +940,7 @@ export class E2EEManager {
|
|
|
890
940
|
await this._verifySenderSignature(payload, message);
|
|
891
941
|
}
|
|
892
942
|
catch (exc) {
|
|
893
|
-
|
|
943
|
+
this._log.warn('sendersignature verifyfailed:', exc);
|
|
894
944
|
return null;
|
|
895
945
|
}
|
|
896
946
|
const encryptionMode = payload.encryption_mode;
|
|
@@ -901,7 +951,7 @@ export class E2EEManager {
|
|
|
901
951
|
return this._decryptMessageLongTerm(message);
|
|
902
952
|
}
|
|
903
953
|
else {
|
|
904
|
-
|
|
954
|
+
this._log.warn('unsupported encryption mode:', encryptionMode);
|
|
905
955
|
return null;
|
|
906
956
|
}
|
|
907
957
|
}
|
|
@@ -1022,10 +1072,10 @@ export class E2EEManager {
|
|
|
1022
1072
|
}
|
|
1023
1073
|
catch (exc) {
|
|
1024
1074
|
if (exc instanceof E2EEError) {
|
|
1025
|
-
|
|
1075
|
+
this._log.warn('prekey_ecdh_v2 decryptfailed (E2EE):', exc);
|
|
1026
1076
|
}
|
|
1027
1077
|
else {
|
|
1028
|
-
|
|
1078
|
+
this._log.warn('prekey_ecdh_v2 decryptfailed:', exc);
|
|
1029
1079
|
}
|
|
1030
1080
|
return null;
|
|
1031
1081
|
}
|
|
@@ -1100,10 +1150,10 @@ export class E2EEManager {
|
|
|
1100
1150
|
}
|
|
1101
1151
|
catch (exc) {
|
|
1102
1152
|
if (exc instanceof E2EEError) {
|
|
1103
|
-
|
|
1153
|
+
this._log.warn('long_term_key decryptfailed (E2EE):', exc);
|
|
1104
1154
|
}
|
|
1105
1155
|
else {
|
|
1106
|
-
|
|
1156
|
+
this._log.warn('long_term_key decryptfailed:', exc);
|
|
1107
1157
|
}
|
|
1108
1158
|
return null;
|
|
1109
1159
|
}
|
|
@@ -1132,48 +1182,57 @@ export class E2EEManager {
|
|
|
1132
1182
|
* 返回 { prekey_id, public_key, signature, created_at },可直接用于 RPC 上传。
|
|
1133
1183
|
*/
|
|
1134
1184
|
async generatePrekey() {
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1185
|
+
const tStart = Date.now();
|
|
1186
|
+
this._log.debug('generatePrekey enter');
|
|
1187
|
+
try {
|
|
1188
|
+
const aid = this._currentAid();
|
|
1189
|
+
if (!aid)
|
|
1190
|
+
throw new E2EEError('AID unavailable for prekey generation');
|
|
1191
|
+
const deviceId = this._currentDeviceId();
|
|
1192
|
+
// 生成新 ECDH 密钥对(标记为 ECDH 用途)
|
|
1193
|
+
const keyPair = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits']);
|
|
1194
|
+
// 导出 SPKI 公钥(DER 格式)
|
|
1195
|
+
const spki = await crypto.subtle.exportKey('spki', keyPair.publicKey);
|
|
1196
|
+
const publicKeyB64 = uint8ToBase64(new Uint8Array(spki));
|
|
1197
|
+
// 导出 PKCS8 私钥(PEM 格式存储)
|
|
1198
|
+
const pkcs8 = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey);
|
|
1199
|
+
const privateKeyPem = arrayBufferToPemLocal(pkcs8, 'PRIVATE KEY');
|
|
1200
|
+
const prekeyId = uuidV4();
|
|
1201
|
+
const nowMs = Date.now();
|
|
1202
|
+
// 签名:prekey_id|public_key|created_at(绑定时间戳,防止旧 prekey 重放)
|
|
1203
|
+
const signData = _encoder.encode(`${prekeyId}|${publicKeyB64}|${nowMs}`);
|
|
1204
|
+
const senderSignKey = await this._loadSenderIdentityPrivateEcdsa();
|
|
1205
|
+
const sig = await ecdsaSignDer(senderSignKey, signData);
|
|
1206
|
+
const signatureB64 = uint8ToBase64(sig);
|
|
1207
|
+
await saveKeyStorePrekey(this._keystoreRef, aid, deviceId, prekeyId, {
|
|
1208
|
+
private_key_pem: privateKeyPem,
|
|
1209
|
+
created_at: nowMs,
|
|
1210
|
+
updated_at: nowMs,
|
|
1211
|
+
});
|
|
1212
|
+
// 内存缓存私钥 PEM
|
|
1213
|
+
this._localPrekeyCache.set(prekeyId, privateKeyPem);
|
|
1214
|
+
// 清理过期的旧 prekey
|
|
1215
|
+
await this._cleanupExpiredPrekeys(aid, deviceId);
|
|
1216
|
+
const result = {
|
|
1217
|
+
prekey_id: prekeyId,
|
|
1218
|
+
public_key: publicKeyB64,
|
|
1219
|
+
signature: signatureB64,
|
|
1220
|
+
created_at: nowMs,
|
|
1221
|
+
};
|
|
1222
|
+
const certFingerprint = await this._localCertSha256Fingerprint();
|
|
1223
|
+
if (certFingerprint) {
|
|
1224
|
+
result.cert_fingerprint = certFingerprint;
|
|
1225
|
+
}
|
|
1226
|
+
if (deviceId) {
|
|
1227
|
+
result.device_id = deviceId;
|
|
1228
|
+
}
|
|
1229
|
+
this._log.debug(`generatePrekey exit: elapsed=${Date.now() - tStart}ms aid=${aid} prekey_id=${prekeyId}`);
|
|
1230
|
+
return result;
|
|
1172
1231
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1232
|
+
catch (err) {
|
|
1233
|
+
this._log.debug(`generatePrekey exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
1234
|
+
throw err;
|
|
1175
1235
|
}
|
|
1176
|
-
return result;
|
|
1177
1236
|
}
|
|
1178
1237
|
/** 清理过期的本地 prekey 私钥 */
|
|
1179
1238
|
async _cleanupExpiredPrekeys(aid, deviceId) {
|
|
@@ -1195,6 +1254,26 @@ export class E2EEManager {
|
|
|
1195
1254
|
const aid = this._currentAid();
|
|
1196
1255
|
if (!aid)
|
|
1197
1256
|
return null;
|
|
1257
|
+
// 优先按 prekey_id 单点查询(IndexedDB 主键直查 O(log N))。
|
|
1258
|
+
// 旧路径需要全量加载 prekey 池(prekey 数量大时是性能瓶颈),单查能直接命中信封里的目标 prekey。
|
|
1259
|
+
const byIdLoader = this._keystoreRef.loadE2EEPrekeyById;
|
|
1260
|
+
if (typeof byIdLoader === 'function') {
|
|
1261
|
+
try {
|
|
1262
|
+
const byIdData = await byIdLoader.call(this._keystoreRef, aid, prekeyId);
|
|
1263
|
+
if (byIdData) {
|
|
1264
|
+
const pem = byIdData.private_key_pem;
|
|
1265
|
+
if (typeof pem === 'string' && pem) {
|
|
1266
|
+
this._log.debug(`prekey ${prekeyId} by_id lookup hit`);
|
|
1267
|
+
this._localPrekeyCache.set(prekeyId, pem);
|
|
1268
|
+
return pem;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
catch (err) {
|
|
1273
|
+
this._log.warn(`prekey ${prekeyId} by_id loader failed, falling back to full load: ${err instanceof Error ? err.message : String(err)}`);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
// 回退:旧版 keystore 没有 by_id 方法,或单查未命中 → 全量扫描(保持向后兼容)。
|
|
1198
1277
|
const prekeys = await loadKeyStorePrekeys(this._keystoreRef, aid, this._currentDeviceId());
|
|
1199
1278
|
const prekeyData = prekeys[prekeyId];
|
|
1200
1279
|
if (!prekeyData)
|