@agentunion/fastaun 0.2.13
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/LICENSE +17 -0
- package/README.md +78 -0
- package/dist/auth.d.ts +287 -0
- package/dist/auth.js +1668 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +359 -0
- package/dist/client.js +3918 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.js +119 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +41 -0
- package/dist/crypto.js +85 -0
- package/dist/crypto.js.map +1 -0
- package/dist/discovery.d.ts +22 -0
- package/dist/discovery.js +110 -0
- package/dist/discovery.js.map +1 -0
- package/dist/e2ee-group.d.ts +192 -0
- package/dist/e2ee-group.js +1134 -0
- package/dist/e2ee-group.js.map +1 -0
- package/dist/e2ee.d.ts +120 -0
- package/dist/e2ee.js +890 -0
- package/dist/e2ee.js.map +1 -0
- package/dist/errors.d.ts +115 -0
- package/dist/errors.js +253 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +39 -0
- package/dist/events.js +82 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/keystore/aid-db.d.ts +79 -0
- package/dist/keystore/aid-db.js +621 -0
- package/dist/keystore/aid-db.js.map +1 -0
- package/dist/keystore/file.d.ts +82 -0
- package/dist/keystore/file.js +395 -0
- package/dist/keystore/file.js.map +1 -0
- package/dist/keystore/index.d.ts +88 -0
- package/dist/keystore/index.js +7 -0
- package/dist/keystore/index.js.map +1 -0
- package/dist/keystore/sqlite-backup.d.ts +40 -0
- package/dist/keystore/sqlite-backup.js +379 -0
- package/dist/keystore/sqlite-backup.js.map +1 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +53 -0
- package/dist/logger.js.map +1 -0
- package/dist/namespaces/auth.d.ts +49 -0
- package/dist/namespaces/auth.js +248 -0
- package/dist/namespaces/auth.js.map +1 -0
- package/dist/namespaces/custody.d.ts +47 -0
- package/dist/namespaces/custody.js +231 -0
- package/dist/namespaces/custody.js.map +1 -0
- package/dist/secret-store/file-store.d.ts +25 -0
- package/dist/secret-store/file-store.js +124 -0
- package/dist/secret-store/file-store.js.map +1 -0
- package/dist/secret-store/index.d.ts +28 -0
- package/dist/secret-store/index.js +19 -0
- package/dist/secret-store/index.js.map +1 -0
- package/dist/seq-tracker.d.ts +29 -0
- package/dist/seq-tracker.js +221 -0
- package/dist/seq-tracker.js.map +1 -0
- package/dist/transport.d.ts +60 -0
- package/dist/transport.js +355 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基于文件的 SecretStore(AES-256-GCM 加密)
|
|
3
|
+
*
|
|
4
|
+
* 密钥派生:
|
|
5
|
+
* - 传入 encryptionSeed → 从 seed 字符串派生
|
|
6
|
+
* - 未传 → 从 {root}/.seed 文件派生(首次自动生成)
|
|
7
|
+
*
|
|
8
|
+
* 与 Python SDK 的 FileSecretStore 完全对齐。
|
|
9
|
+
*/
|
|
10
|
+
import { createCipheriv, createDecipheriv, createHmac, pbkdf2Sync, randomBytes, } from 'node:crypto';
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
function decodeSecretPart(value) {
|
|
14
|
+
if (/^[0-9a-fA-F]+$/.test(value) && value.length % 2 === 0) {
|
|
15
|
+
return Buffer.from(value, 'hex');
|
|
16
|
+
}
|
|
17
|
+
return Buffer.from(value, 'base64');
|
|
18
|
+
}
|
|
19
|
+
export class FileSecretStore {
|
|
20
|
+
_root;
|
|
21
|
+
_masterKey;
|
|
22
|
+
constructor(root, encryptionSeed, sqliteBackup) {
|
|
23
|
+
this._root = root;
|
|
24
|
+
mkdirSync(this._root, { recursive: true });
|
|
25
|
+
let seedBytes;
|
|
26
|
+
if (encryptionSeed) {
|
|
27
|
+
seedBytes = Buffer.from(encryptionSeed, 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
seedBytes = this._loadOrCreateSeed(sqliteBackup);
|
|
31
|
+
}
|
|
32
|
+
// PBKDF2 派生主密钥,与 Python SDK 参数完全一致
|
|
33
|
+
this._masterKey = pbkdf2Sync(seedBytes, 'aun_file_secret_store_v1', 100_000, 32, 'sha256');
|
|
34
|
+
}
|
|
35
|
+
protect(scope, name, plaintext) {
|
|
36
|
+
const key = this._deriveKey(scope, name);
|
|
37
|
+
const nonce = randomBytes(12);
|
|
38
|
+
const cipher = createCipheriv('aes-256-gcm', key, nonce);
|
|
39
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
40
|
+
const tag = cipher.getAuthTag();
|
|
41
|
+
return {
|
|
42
|
+
scheme: 'file_aes',
|
|
43
|
+
name,
|
|
44
|
+
persisted: true,
|
|
45
|
+
nonce: nonce.toString('base64'),
|
|
46
|
+
ciphertext: encrypted.toString('base64'),
|
|
47
|
+
tag: tag.toString('base64'),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
reveal(scope, name, record) {
|
|
51
|
+
if (record.scheme !== 'file_aes')
|
|
52
|
+
return null;
|
|
53
|
+
if (String(record.name ?? '') !== name)
|
|
54
|
+
return null;
|
|
55
|
+
const nonceB64 = String(record.nonce ?? '');
|
|
56
|
+
const ctB64 = String(record.ciphertext ?? '');
|
|
57
|
+
const tagB64 = String(record.tag ?? '');
|
|
58
|
+
if (!nonceB64 || !ctB64 || !tagB64)
|
|
59
|
+
return null;
|
|
60
|
+
const key = this._deriveKey(scope, name);
|
|
61
|
+
try {
|
|
62
|
+
const decipher = createDecipheriv('aes-256-gcm', key, decodeSecretPart(nonceB64));
|
|
63
|
+
decipher.setAuthTag(decodeSecretPart(tagB64));
|
|
64
|
+
const decrypted = Buffer.concat([
|
|
65
|
+
decipher.update(decodeSecretPart(ctB64)),
|
|
66
|
+
decipher.final(),
|
|
67
|
+
]);
|
|
68
|
+
return decrypted;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** 使用 HMAC-SHA256 从主密钥派生子密钥 */
|
|
75
|
+
_deriveKey(scope, name) {
|
|
76
|
+
return createHmac('sha256', this._masterKey)
|
|
77
|
+
.update(`aun:${scope}:${name}\x01`)
|
|
78
|
+
.digest();
|
|
79
|
+
}
|
|
80
|
+
/** 三级恢复:文件 → SQLite → 新建,双写确保一致 */
|
|
81
|
+
_loadOrCreateSeed(backup) {
|
|
82
|
+
const seedPath = join(this._root, '.seed');
|
|
83
|
+
let source = '';
|
|
84
|
+
// 1. 先读文件
|
|
85
|
+
if (existsSync(seedPath)) {
|
|
86
|
+
const seed = readFileSync(seedPath);
|
|
87
|
+
source = 'file';
|
|
88
|
+
// 双写:确保 SQLite 也有
|
|
89
|
+
if (backup)
|
|
90
|
+
backup.backupSeed(seed);
|
|
91
|
+
return seed;
|
|
92
|
+
}
|
|
93
|
+
// 2. 文件不存在 → 读 SQLite
|
|
94
|
+
if (backup) {
|
|
95
|
+
const restored = backup.restoreSeed();
|
|
96
|
+
if (restored && restored.length > 0) {
|
|
97
|
+
source = 'sqlite';
|
|
98
|
+
console.log('从 SQLite 恢复 .seed 文件');
|
|
99
|
+
writeFileSync(seedPath, restored);
|
|
100
|
+
if (process.platform !== 'win32') {
|
|
101
|
+
try {
|
|
102
|
+
chmodSync(seedPath, 0o600);
|
|
103
|
+
}
|
|
104
|
+
catch { /* ignore */ }
|
|
105
|
+
}
|
|
106
|
+
return restored;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 3. 都没有 → 生成新 seed
|
|
110
|
+
const seed = randomBytes(32);
|
|
111
|
+
writeFileSync(seedPath, seed);
|
|
112
|
+
if (process.platform !== 'win32') {
|
|
113
|
+
try {
|
|
114
|
+
chmodSync(seedPath, 0o600);
|
|
115
|
+
}
|
|
116
|
+
catch { /* ignore */ }
|
|
117
|
+
}
|
|
118
|
+
// 双写:备份到 SQLite
|
|
119
|
+
if (backup)
|
|
120
|
+
backup.backupSeed(seed);
|
|
121
|
+
return seed;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=file-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../src/secret-store/file-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecretStore 接口定义 + 默认工厂函数
|
|
3
|
+
*
|
|
4
|
+
* 与 Python SDK 的 SecretStore Protocol 完全对齐。
|
|
5
|
+
*/
|
|
6
|
+
import type { SecretRecord } from '../types.js';
|
|
7
|
+
/** 密钥保护/恢复接口 */
|
|
8
|
+
export interface SecretStore {
|
|
9
|
+
/**
|
|
10
|
+
* 保护明文数据,返回加密记录(可持久化)。
|
|
11
|
+
*/
|
|
12
|
+
protect(scope: string, name: string, plaintext: Buffer): SecretRecord;
|
|
13
|
+
/**
|
|
14
|
+
* 从加密记录恢复明文数据。
|
|
15
|
+
* 解密失败或格式不匹配返回 null。
|
|
16
|
+
*/
|
|
17
|
+
reveal(scope: string, name: string, record: SecretRecord): Buffer | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 创建默认的 SecretStore 实例。
|
|
21
|
+
*
|
|
22
|
+
* TypeScript SDK 统一使用 FileSecretStore(AES-256-GCM),
|
|
23
|
+
* 不区分平台原生方案(与 Python SDK v0.2.0+ 对齐:写入统一用 file_aes)。
|
|
24
|
+
*/
|
|
25
|
+
export declare function createDefaultSecretStore(root?: string, encryptionSeed?: string, sqliteBackup?: {
|
|
26
|
+
backupSeed(seed: Buffer): void;
|
|
27
|
+
restoreSeed(): Buffer | null;
|
|
28
|
+
}): SecretStore;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecretStore 接口定义 + 默认工厂函数
|
|
3
|
+
*
|
|
4
|
+
* 与 Python SDK 的 SecretStore Protocol 完全对齐。
|
|
5
|
+
*/
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { FileSecretStore } from './file-store.js';
|
|
9
|
+
/**
|
|
10
|
+
* 创建默认的 SecretStore 实例。
|
|
11
|
+
*
|
|
12
|
+
* TypeScript SDK 统一使用 FileSecretStore(AES-256-GCM),
|
|
13
|
+
* 不区分平台原生方案(与 Python SDK v0.2.0+ 对齐:写入统一用 file_aes)。
|
|
14
|
+
*/
|
|
15
|
+
export function createDefaultSecretStore(root, encryptionSeed, sqliteBackup) {
|
|
16
|
+
const storeRoot = root ?? join(homedir(), '.aun');
|
|
17
|
+
return new FileSecretStore(storeRoot, encryptionSeed, sqliteBackup);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/secret-store/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAiBlD;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACtC,IAAa,EACb,cAAuB,EACvB,YAA+E;IAE/E,MAAM,SAAS,GAAG,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IAClD,OAAO,IAAI,eAAe,CAAC,SAAS,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { JsonObject } from './types.js';
|
|
2
|
+
export declare class SeqTracker {
|
|
3
|
+
private _trackers;
|
|
4
|
+
private _get;
|
|
5
|
+
getContiguousSeq(ns: string): number;
|
|
6
|
+
getMaxSeenSeq(ns: string): number;
|
|
7
|
+
/** S2: 从持久化(keystore 最近 ack seq)恢复 baseline,
|
|
8
|
+
* 以便首条 push 消息能构造 [baseline+1, seq-1] 的历史 gap。
|
|
9
|
+
* 必须在收到首条消息前调用。 */
|
|
10
|
+
setBaseline(ns: string, baselineSeq: number): void;
|
|
11
|
+
/** 记录收到的 seq,返回 true 表示需要 pull 补齐空洞 */
|
|
12
|
+
onMessageSeq(ns: string, seq: number): boolean;
|
|
13
|
+
/** pull 返回后更新 tracker 状态 */
|
|
14
|
+
onPullResult(ns: string, messages: JsonObject[]): void;
|
|
15
|
+
private _tryAdvance;
|
|
16
|
+
private _shouldProbe;
|
|
17
|
+
/** S2: 服务端明确告知某区间无消息(tombstone)→ 将 gap 标记为 resolved */
|
|
18
|
+
markGapResolvedByTombstone(ns: string, gapStart: number, gapEnd: number): void;
|
|
19
|
+
/** 强制跳过不连续区间,将 contiguousSeq 拨到指定位置。
|
|
20
|
+
* 当服务端返回 server_ack_seq 且本地 contiguousSeq 落后时调用,
|
|
21
|
+
* 跳过 [contiguousSeq, server_ack_seq) 这段不连续区间。 */
|
|
22
|
+
forceContiguousSeq(ns: string, seq: number): void;
|
|
23
|
+
/** 删除指定命名空间的所有跟踪状态(群组解散时使用) */
|
|
24
|
+
removeNamespace(ns: string): void;
|
|
25
|
+
/** 导出各命名空间的 contiguousSeq,用于持久化 */
|
|
26
|
+
exportState(): Record<string, number>;
|
|
27
|
+
/** 从持久化数据恢复各命名空间的 contiguousSeq */
|
|
28
|
+
restoreState(state: Record<string, number>): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// ── 消息序列号跟踪与空洞检测 ──────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// SeqTracker 维护按命名空间(group_id 或 conversation_id)分组的消息序列连续性。
|
|
4
|
+
// 用法:群消息 key = "group:" + groupId,P2P 消息 key = "p2p:" + myAid
|
|
5
|
+
const BACKOFF_INTERVALS = [1, 3, 10, 30, 60]; // 秒
|
|
6
|
+
// S2: 删除"probeCount >= 5 强制 resolved"的硬限制;仅用作 backoff 索引上限。
|
|
7
|
+
// resolved 只应由"完整补齐"或服务端明确 tombstone 驱动。
|
|
8
|
+
const MAX_PROBE_COUNT = 5;
|
|
9
|
+
function gapKey(start, end) {
|
|
10
|
+
return `${start}:${end}`;
|
|
11
|
+
}
|
|
12
|
+
/** 统一使用 Date.now() 返回绝对时间戳(毫秒),
|
|
13
|
+
* 避免 performance.now()(相对时间)与 Date.now()(绝对时间)混用导致语义不一致。 */
|
|
14
|
+
function nowMs() {
|
|
15
|
+
return Date.now();
|
|
16
|
+
}
|
|
17
|
+
export class SeqTracker {
|
|
18
|
+
_trackers = new Map();
|
|
19
|
+
_get(ns) {
|
|
20
|
+
let t = this._trackers.get(ns);
|
|
21
|
+
if (!t) {
|
|
22
|
+
t = { contiguousSeq: 0, maxSeenSeq: 0, receivedSeqs: new Set(), pendingGaps: new Map() };
|
|
23
|
+
this._trackers.set(ns, t);
|
|
24
|
+
}
|
|
25
|
+
return t;
|
|
26
|
+
}
|
|
27
|
+
getContiguousSeq(ns) {
|
|
28
|
+
return this._get(ns).contiguousSeq;
|
|
29
|
+
}
|
|
30
|
+
getMaxSeenSeq(ns) {
|
|
31
|
+
return this._get(ns).maxSeenSeq;
|
|
32
|
+
}
|
|
33
|
+
/** S2: 从持久化(keystore 最近 ack seq)恢复 baseline,
|
|
34
|
+
* 以便首条 push 消息能构造 [baseline+1, seq-1] 的历史 gap。
|
|
35
|
+
* 必须在收到首条消息前调用。 */
|
|
36
|
+
setBaseline(ns, baselineSeq) {
|
|
37
|
+
if (baselineSeq <= 0)
|
|
38
|
+
return;
|
|
39
|
+
const t = this._get(ns);
|
|
40
|
+
if (t.contiguousSeq === 0 && t.maxSeenSeq === 0) {
|
|
41
|
+
t.contiguousSeq = baselineSeq;
|
|
42
|
+
t.maxSeenSeq = baselineSeq;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** 记录收到的 seq,返回 true 表示需要 pull 补齐空洞 */
|
|
46
|
+
onMessageSeq(ns, seq) {
|
|
47
|
+
if (seq <= 0)
|
|
48
|
+
return false;
|
|
49
|
+
const t = this._get(ns);
|
|
50
|
+
if (seq <= t.contiguousSeq)
|
|
51
|
+
return false;
|
|
52
|
+
// S2: 首次收到消息时,必须构造 [1, seq-1] 的历史 gap 来触发补洞,
|
|
53
|
+
// 而不是把当前 seq 当成 baseline 丢弃历史。
|
|
54
|
+
if (t.contiguousSeq === 0 && t.maxSeenSeq === 0) {
|
|
55
|
+
if (seq === 1) {
|
|
56
|
+
t.contiguousSeq = seq;
|
|
57
|
+
t.maxSeenSeq = seq;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
t.maxSeenSeq = seq;
|
|
61
|
+
t.receivedSeqs.add(seq);
|
|
62
|
+
const histKey = gapKey(1, seq - 1);
|
|
63
|
+
t.pendingGaps.set(histKey, {
|
|
64
|
+
gapStart: 1, gapEnd: seq - 1,
|
|
65
|
+
lastProbeAt: 0, probeCount: 0, resolved: false,
|
|
66
|
+
});
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
t.receivedSeqs.add(seq);
|
|
70
|
+
t.maxSeenSeq = Math.max(t.maxSeenSeq, seq);
|
|
71
|
+
if (seq === t.contiguousSeq + 1) {
|
|
72
|
+
t.contiguousSeq = seq;
|
|
73
|
+
t.receivedSeqs.delete(seq);
|
|
74
|
+
this._tryAdvance(t);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
// 空洞
|
|
78
|
+
const gs = t.contiguousSeq + 1;
|
|
79
|
+
const ge = seq - 1;
|
|
80
|
+
const key = gapKey(gs, ge);
|
|
81
|
+
const existing = t.pendingGaps.get(key);
|
|
82
|
+
if (existing) {
|
|
83
|
+
if (existing.resolved) {
|
|
84
|
+
t.contiguousSeq = Math.max(t.contiguousSeq, existing.gapEnd);
|
|
85
|
+
t.pendingGaps.delete(key);
|
|
86
|
+
this._tryAdvance(t);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (!this._shouldProbe(existing))
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
t.pendingGaps.set(key, {
|
|
94
|
+
gapStart: gs, gapEnd: ge,
|
|
95
|
+
lastProbeAt: 0, probeCount: 0, resolved: false,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
/** pull 返回后更新 tracker 状态 */
|
|
101
|
+
onPullResult(ns, messages) {
|
|
102
|
+
const t = this._get(ns);
|
|
103
|
+
const pulledSeqs = new Set();
|
|
104
|
+
for (const m of messages) {
|
|
105
|
+
const s = m.seq;
|
|
106
|
+
if (typeof s === 'number' && s > 0)
|
|
107
|
+
pulledSeqs.add(s);
|
|
108
|
+
}
|
|
109
|
+
const now = nowMs();
|
|
110
|
+
for (const [key, probe] of t.pendingGaps) {
|
|
111
|
+
if (probe.resolved)
|
|
112
|
+
continue;
|
|
113
|
+
probe.lastProbeAt = now;
|
|
114
|
+
probe.probeCount += 1;
|
|
115
|
+
let allCovered = true;
|
|
116
|
+
let anyHit = false;
|
|
117
|
+
for (let s = probe.gapStart; s <= probe.gapEnd; s++) {
|
|
118
|
+
if (pulledSeqs.has(s)) {
|
|
119
|
+
anyHit = true;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
allCovered = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (allCovered) {
|
|
126
|
+
probe.resolved = true;
|
|
127
|
+
}
|
|
128
|
+
// S2: 不再因 probeCount >= 3 自动 resolved;仅由完整补齐 / 服务端 tombstone 驱动。
|
|
129
|
+
}
|
|
130
|
+
for (const s of pulledSeqs) {
|
|
131
|
+
t.receivedSeqs.add(s);
|
|
132
|
+
}
|
|
133
|
+
if (pulledSeqs.size) {
|
|
134
|
+
t.maxSeenSeq = Math.max(t.maxSeenSeq, Math.max(...pulledSeqs));
|
|
135
|
+
}
|
|
136
|
+
this._tryAdvance(t);
|
|
137
|
+
}
|
|
138
|
+
_tryAdvance(t) {
|
|
139
|
+
// 先清理已解决的空洞
|
|
140
|
+
let changed = true;
|
|
141
|
+
while (changed) {
|
|
142
|
+
changed = false;
|
|
143
|
+
for (const [key, probe] of t.pendingGaps) {
|
|
144
|
+
if (probe.resolved && probe.gapStart <= t.contiguousSeq + 1) {
|
|
145
|
+
t.contiguousSeq = Math.max(t.contiguousSeq, probe.gapEnd);
|
|
146
|
+
t.pendingGaps.delete(key);
|
|
147
|
+
changed = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// 从 contiguous+1 逐个推进(检查 receivedSeqs)
|
|
152
|
+
while (t.receivedSeqs.has(t.contiguousSeq + 1)) {
|
|
153
|
+
t.contiguousSeq += 1;
|
|
154
|
+
t.receivedSeqs.delete(t.contiguousSeq);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
_shouldProbe(probe) {
|
|
158
|
+
// S2: 不再以 probeCount >= MAX_PROBE_COUNT 为由将 probe 置为 resolved。
|
|
159
|
+
// 超出 backoff 表长度后按最长间隔持续重试。
|
|
160
|
+
const now = nowMs();
|
|
161
|
+
const idx = Math.min(probe.probeCount, BACKOFF_INTERVALS.length - 1);
|
|
162
|
+
const interval = BACKOFF_INTERVALS[idx] * 1000; // 秒转毫秒
|
|
163
|
+
return now - probe.lastProbeAt >= interval;
|
|
164
|
+
}
|
|
165
|
+
/** S2: 服务端明确告知某区间无消息(tombstone)→ 将 gap 标记为 resolved */
|
|
166
|
+
markGapResolvedByTombstone(ns, gapStart, gapEnd) {
|
|
167
|
+
const t = this._get(ns);
|
|
168
|
+
for (const [, probe] of t.pendingGaps) {
|
|
169
|
+
if (probe.gapStart >= gapStart && probe.gapEnd <= gapEnd) {
|
|
170
|
+
probe.resolved = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this._tryAdvance(t);
|
|
174
|
+
}
|
|
175
|
+
/** 强制跳过不连续区间,将 contiguousSeq 拨到指定位置。
|
|
176
|
+
* 当服务端返回 server_ack_seq 且本地 contiguousSeq 落后时调用,
|
|
177
|
+
* 跳过 [contiguousSeq, server_ack_seq) 这段不连续区间。 */
|
|
178
|
+
forceContiguousSeq(ns, seq) {
|
|
179
|
+
const t = this._get(ns);
|
|
180
|
+
if (seq > t.contiguousSeq) {
|
|
181
|
+
// 清除被跳过区间内的 pendingGaps
|
|
182
|
+
for (const [key, probe] of t.pendingGaps) {
|
|
183
|
+
if (probe.gapEnd <= seq) {
|
|
184
|
+
t.pendingGaps.delete(key);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// 清除被跳过区间内的 receivedSeqs
|
|
188
|
+
for (const s of t.receivedSeqs) {
|
|
189
|
+
if (s <= seq)
|
|
190
|
+
t.receivedSeqs.delete(s);
|
|
191
|
+
}
|
|
192
|
+
t.contiguousSeq = seq;
|
|
193
|
+
t.maxSeenSeq = Math.max(t.maxSeenSeq, seq);
|
|
194
|
+
this._tryAdvance(t);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** 删除指定命名空间的所有跟踪状态(群组解散时使用) */
|
|
198
|
+
removeNamespace(ns) {
|
|
199
|
+
this._trackers.delete(ns);
|
|
200
|
+
}
|
|
201
|
+
/** 导出各命名空间的 contiguousSeq,用于持久化 */
|
|
202
|
+
exportState() {
|
|
203
|
+
const result = {};
|
|
204
|
+
for (const [ns, t] of this._trackers) {
|
|
205
|
+
if (t.contiguousSeq > 0)
|
|
206
|
+
result[ns] = t.contiguousSeq;
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
/** 从持久化数据恢复各命名空间的 contiguousSeq */
|
|
211
|
+
restoreState(state) {
|
|
212
|
+
for (const [ns, seq] of Object.entries(state)) {
|
|
213
|
+
if (typeof seq === 'number' && seq > 0) {
|
|
214
|
+
const t = this._get(ns);
|
|
215
|
+
t.contiguousSeq = Math.max(t.contiguousSeq, seq);
|
|
216
|
+
t.maxSeenSeq = Math.max(t.maxSeenSeq, seq);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=seq-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seq-tracker.js","sourceRoot":"","sources":["../src/seq-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC 传输层
|
|
3
|
+
*
|
|
4
|
+
* 基于 WebSocket(ws 包)的 JSON-RPC 2.0 传输层。
|
|
5
|
+
* 与 Python SDK 的 RPCTransport 完全对齐:
|
|
6
|
+
* - connect → 接收初始 challenge
|
|
7
|
+
* - call → 请求/响应匹配
|
|
8
|
+
* - 事件路由 → EventDispatcher
|
|
9
|
+
*/
|
|
10
|
+
import type { EventDispatcher } from './events.js';
|
|
11
|
+
import { type RpcMessage, type RpcParams, type RpcResult } from './types.js';
|
|
12
|
+
/** 断线回调,closeCode 为 WebSocket close code(1006 = 网络异常断开,其他 = 服务端主动关闭) */
|
|
13
|
+
export type DisconnectCallback = (error: Error | null, closeCode?: number) => void | Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* WebSocket JSON-RPC 2.0 传输层
|
|
16
|
+
*/
|
|
17
|
+
export declare class RPCTransport {
|
|
18
|
+
private _dispatcher;
|
|
19
|
+
private _timeout;
|
|
20
|
+
private _onDisconnect;
|
|
21
|
+
private _verifySsl;
|
|
22
|
+
private _ws;
|
|
23
|
+
private _closed;
|
|
24
|
+
private _challenge;
|
|
25
|
+
private _pending;
|
|
26
|
+
private _idCounter;
|
|
27
|
+
constructor(opts: {
|
|
28
|
+
eventDispatcher: EventDispatcher;
|
|
29
|
+
timeout?: number;
|
|
30
|
+
onDisconnect?: DisconnectCallback | null;
|
|
31
|
+
verifySsl?: boolean;
|
|
32
|
+
});
|
|
33
|
+
/** 设置默认 RPC 超时(毫秒) */
|
|
34
|
+
setTimeout(timeout: number): void;
|
|
35
|
+
/** 获取上次连接的 challenge 消息 */
|
|
36
|
+
get challenge(): RpcMessage | null;
|
|
37
|
+
/**
|
|
38
|
+
* 连接到 Gateway WebSocket 端点。
|
|
39
|
+
* 返回初始 challenge 消息(如果有)。
|
|
40
|
+
*/
|
|
41
|
+
connect(url: string): Promise<RpcMessage | null>;
|
|
42
|
+
/**
|
|
43
|
+
* 内部:使用已创建的 WebSocket 实例完成握手。
|
|
44
|
+
* 握手阶段(open 后到首条消息处理完毕)如果发生 error 或解析失败,
|
|
45
|
+
* 会回滚 _ws / _closed,确保不留下半连接状态。
|
|
46
|
+
*/
|
|
47
|
+
private _connectWithWs;
|
|
48
|
+
/** 关闭连接 */
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* 发送 JSON-RPC 2.0 请求并等待响应。
|
|
52
|
+
*/
|
|
53
|
+
call(method: string, params?: RpcParams, timeout?: number): Promise<RpcResult>;
|
|
54
|
+
/** 设置 WebSocket 监听器(首条消息已处理后调用) */
|
|
55
|
+
private _setupListeners;
|
|
56
|
+
/** 路由消息:RPC 响应 / challenge / 事件 / 通知 */
|
|
57
|
+
private _routeMessage;
|
|
58
|
+
/** 解码 WebSocket 消息为 JSON 对象 */
|
|
59
|
+
private _decodeMessage;
|
|
60
|
+
}
|