liangzimixin 0.2.17 → 0.3.1
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/index.cjs +335 -300
- package/dist/index.d.cts +146 -79
- package/dist/setup-entry.cjs +364 -329
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3658,7 +3658,7 @@ __export(index_exports, {
|
|
|
3658
3658
|
startPlugin: () => startPlugin
|
|
3659
3659
|
});
|
|
3660
3660
|
module.exports = __toCommonJS(index_exports);
|
|
3661
|
-
var
|
|
3661
|
+
var import_node_path2 = require("path");
|
|
3662
3662
|
var import_node_os = require("os");
|
|
3663
3663
|
|
|
3664
3664
|
// node_modules/zod/v4/classic/external.js
|
|
@@ -17454,14 +17454,6 @@ var AccountConfigSchema = external_exports.object({
|
|
|
17454
17454
|
"quantum-appId": external_exports.string().min(1, "quantum-appId \u4E0D\u80FD\u4E3A\u7A7A"),
|
|
17455
17455
|
/** 🔴 量子服务密钥 — 用于量子密钥分发 */
|
|
17456
17456
|
"quantum-appSecret": external_exports.string().min(1, "quantum-appSecret \u4E0D\u80FD\u4E3A\u7A7A"),
|
|
17457
|
-
/** 🔴 PIN 口令 — 保护本地敏感数据 (切勿泄露, 修改后需删除旧密钥数据) */
|
|
17458
|
-
pin: external_exports.string().min(1, "pin \u4E0D\u80FD\u4E3A\u7A7A"),
|
|
17459
|
-
/**
|
|
17460
|
-
* 🟡 量子密服地址 — 默认生产环境
|
|
17461
|
-
* 测试环境: http://223.244.14.238:8552
|
|
17462
|
-
* 生产环境: https://cmsp.zdxlz.com:8552
|
|
17463
|
-
*/
|
|
17464
|
-
"quantum-url": external_exports.string().url().optional().default("https://cmsp.zdxlz.com:8552"),
|
|
17465
17457
|
/**
|
|
17466
17458
|
* 🟡 Bot 自己的用户 ID — 用于 anti-loop 检查
|
|
17467
17459
|
* 如果不填,需要通过其他 API 在运行时获取。
|
|
@@ -17473,8 +17465,6 @@ var AccountConfigSchema = external_exports.object({
|
|
|
17473
17465
|
quantumAccount: raw["quantum-account"],
|
|
17474
17466
|
quantumAppId: raw["quantum-appId"],
|
|
17475
17467
|
quantumAppSecret: raw["quantum-appSecret"],
|
|
17476
|
-
pin: raw.pin,
|
|
17477
|
-
quantumUrl: raw["quantum-url"],
|
|
17478
17468
|
botUserId: raw.botUserId
|
|
17479
17469
|
}));
|
|
17480
17470
|
var DEFAULT_INTERNAL_CONFIG = {
|
|
@@ -17493,12 +17483,14 @@ var DEFAULT_INTERNAL_CONFIG = {
|
|
|
17493
17483
|
}
|
|
17494
17484
|
},
|
|
17495
17485
|
auth: {
|
|
17496
|
-
serverUrl: process.env.LZMX_AUTH_URL || "https://
|
|
17486
|
+
serverUrl: process.env.LZMX_AUTH_URL || "https://seal.example.com",
|
|
17487
|
+
clientName: "liangzimixin",
|
|
17488
|
+
scopes: ["openid", "im_sdk", "data_operate", "offline_access"],
|
|
17489
|
+
autoRegister: true,
|
|
17497
17490
|
refreshAheadMs: 3e5
|
|
17498
17491
|
},
|
|
17499
17492
|
crypto: {
|
|
17500
|
-
enabled:
|
|
17501
|
-
// 暂时关闭量子加密 (SDK 尚未部署到生产环境)
|
|
17493
|
+
enabled: true,
|
|
17502
17494
|
keySource: "env",
|
|
17503
17495
|
envKey: "QUANTUM_ENCRYPTION_KEY"
|
|
17504
17496
|
},
|
|
@@ -17982,7 +17974,7 @@ async function resolveAndUploadMedia(params) {
|
|
|
17982
17974
|
};
|
|
17983
17975
|
}
|
|
17984
17976
|
const msgType = fileType;
|
|
17985
|
-
const contentPayload = fileType === "file" ? {
|
|
17977
|
+
const contentPayload = fileType === "file" ? { fileKey: uploadResult.fileKey, filename: fileName, size: uploadResult.fileSize } : { fileKey: uploadResult.fileKey };
|
|
17986
17978
|
await messagePipe.sendMessage({
|
|
17987
17979
|
chatId,
|
|
17988
17980
|
senderId: chatId,
|
|
@@ -17992,7 +17984,7 @@ async function resolveAndUploadMedia(params) {
|
|
|
17992
17984
|
});
|
|
17993
17985
|
log3.info("media:sent", {
|
|
17994
17986
|
chatId,
|
|
17995
|
-
|
|
17987
|
+
fileKey: uploadResult.fileKey,
|
|
17996
17988
|
msgType
|
|
17997
17989
|
});
|
|
17998
17990
|
return {
|
|
@@ -18119,20 +18111,20 @@ ${body}` : body || raw.content;
|
|
|
18119
18111
|
}
|
|
18120
18112
|
case "image": {
|
|
18121
18113
|
const c = parsed;
|
|
18122
|
-
fileId = c?.
|
|
18114
|
+
fileId = c?.fileKey;
|
|
18123
18115
|
text = c?.altText ?? (fileId ? `` : "[image]");
|
|
18124
18116
|
break;
|
|
18125
18117
|
}
|
|
18126
18118
|
case "file": {
|
|
18127
18119
|
const c = parsed;
|
|
18128
|
-
fileId = c?.
|
|
18129
|
-
fileName = c?.
|
|
18120
|
+
fileId = c?.fileKey;
|
|
18121
|
+
fileName = c?.filename;
|
|
18130
18122
|
text = fileName ? `[File: ${fileName}]` : "[file]";
|
|
18131
18123
|
break;
|
|
18132
18124
|
}
|
|
18133
18125
|
case "voice": {
|
|
18134
18126
|
const c = parsed;
|
|
18135
|
-
fileId = c?.
|
|
18127
|
+
fileId = c?.fileKey;
|
|
18136
18128
|
const durationMs = c?.duration;
|
|
18137
18129
|
const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
|
|
18138
18130
|
text = `[Voice${durationStr}]`;
|
|
@@ -18140,7 +18132,7 @@ ${body}` : body || raw.content;
|
|
|
18140
18132
|
}
|
|
18141
18133
|
case "video": {
|
|
18142
18134
|
const c = parsed;
|
|
18143
|
-
fileId = c?.
|
|
18135
|
+
fileId = c?.fileKey;
|
|
18144
18136
|
const durationMs = c?.duration;
|
|
18145
18137
|
const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
|
|
18146
18138
|
text = `[Video${durationStr}]`;
|
|
@@ -18462,7 +18454,7 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
|
|
|
18462
18454
|
title: "\u91CF\u5B50\u5BC6\u4FE1 IM \u63D2\u4EF6\u914D\u7F6E",
|
|
18463
18455
|
description: "\u5BC6\u4FE1 IM Channel \u63D2\u4EF6\uFF0C\u652F\u6301\u91CF\u5B50\u52A0\u5BC6\u7684\u5B89\u5168\u5373\u65F6\u901A\u4FE1\u3002",
|
|
18464
18456
|
type: "object",
|
|
18465
|
-
required: ["appId", "appSecret", "quantum-account", "quantum-appId", "quantum-appSecret"
|
|
18457
|
+
required: ["appId", "appSecret", "quantum-account", "quantum-appId", "quantum-appSecret"],
|
|
18466
18458
|
properties: {
|
|
18467
18459
|
appId: {
|
|
18468
18460
|
type: "string",
|
|
@@ -18496,19 +18488,6 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
|
|
|
18496
18488
|
minLength: 1,
|
|
18497
18489
|
format: "password"
|
|
18498
18490
|
},
|
|
18499
|
-
pin: {
|
|
18500
|
-
type: "string",
|
|
18501
|
-
title: "PIN \u53E3\u4EE4",
|
|
18502
|
-
description: "\u4FDD\u62A4\u672C\u5730\u654F\u611F\u6570\u636E\u7684\u53E3\u4EE4\uFF0C\u7531\u7528\u6237\u81EA\u5B9A\u4E49\uFF08\u5207\u52FF\u6CC4\u9732\uFF0C\u4FEE\u6539\u540E\u8BF7\u5220\u9664\u65E7\u5BC6\u94A5\u6570\u636E\uFF09",
|
|
18503
|
-
minLength: 1,
|
|
18504
|
-
format: "password"
|
|
18505
|
-
},
|
|
18506
|
-
"quantum-url": {
|
|
18507
|
-
type: "string",
|
|
18508
|
-
title: "\u91CF\u5B50\u5BC6\u670D\u5730\u5740",
|
|
18509
|
-
description: "\u91CF\u5B50\u5BC6\u94A5\u7BA1\u7406\u670D\u52A1\u5730\u5740\u3002\u6D4B\u8BD5\u73AF\u5883: http://223.244.14.238:8552\uFF0C\u751F\u4EA7\u73AF\u5883: https://cmsp.zdxlz.com:8552",
|
|
18510
|
-
default: "https://cmsp.zdxlz.com:8552"
|
|
18511
|
-
},
|
|
18512
18491
|
botUserId: {
|
|
18513
18492
|
type: "string",
|
|
18514
18493
|
title: "Bot \u7528\u6237 ID",
|
|
@@ -18602,7 +18581,6 @@ var quantumImOnboarding = {
|
|
|
18602
18581
|
"\u8BF7\u51C6\u5907\u4EE5\u4E0B\u4FE1\u606F:",
|
|
18603
18582
|
" 1. \u5E73\u53F0\u7533\u8BF7\u7684 appId \u548C appSecret",
|
|
18604
18583
|
" 2. \u91CF\u5B50\u52A0\u5BC6\u670D\u52A1\u7684\u8D26\u6237\u3001appId \u548C appSecret",
|
|
18605
|
-
" 3. \u4FDD\u62A4\u672C\u5730\u5BC6\u94A5\u6570\u636E\u7684 PIN \u53E3\u4EE4",
|
|
18606
18584
|
"",
|
|
18607
18585
|
"\u5982\u6709\u7591\u95EE\u8BF7\u8054\u7CFB\u5E73\u53F0\u7BA1\u7406\u5458\u83B7\u53D6\u3002"
|
|
18608
18586
|
].join("\n"),
|
|
@@ -18634,36 +18612,6 @@ var quantumImOnboarding = {
|
|
|
18634
18612
|
initialValue: existing["quantum-appSecret"] ?? void 0,
|
|
18635
18613
|
validate: required2
|
|
18636
18614
|
});
|
|
18637
|
-
const pin = await prompter.text({
|
|
18638
|
-
message: "PIN \u53E3\u4EE4 (\u4FDD\u62A4\u672C\u5730\u654F\u611F\u6570\u636E, \u5207\u52FF\u6CC4\u9732)",
|
|
18639
|
-
initialValue: existing.pin ?? void 0,
|
|
18640
|
-
validate: required2
|
|
18641
|
-
});
|
|
18642
|
-
const urlChoice = await prompter.select({
|
|
18643
|
-
message: "\u91CF\u5B50\u5BC6\u670D\u73AF\u5883",
|
|
18644
|
-
options: [
|
|
18645
|
-
{ value: "https://cmsp.zdxlz.com:8552", label: "\u751F\u4EA7\u73AF\u5883", hint: "https://cmsp.zdxlz.com:8552" },
|
|
18646
|
-
{ value: "http://223.244.14.238:8552", label: "\u6D4B\u8BD5\u73AF\u5883", hint: "http://223.244.14.238:8552" },
|
|
18647
|
-
{ value: "custom", label: "\u81EA\u5B9A\u4E49\u5730\u5740" }
|
|
18648
|
-
],
|
|
18649
|
-
initialValue: existing["quantum-url"] ?? "https://cmsp.zdxlz.com:8552"
|
|
18650
|
-
});
|
|
18651
|
-
let quantumUrl = urlChoice;
|
|
18652
|
-
if (urlChoice === "custom") {
|
|
18653
|
-
quantumUrl = await prompter.text({
|
|
18654
|
-
message: "\u8BF7\u8F93\u5165\u91CF\u5B50\u5BC6\u670D\u5730\u5740",
|
|
18655
|
-
placeholder: "https://your-server:8552",
|
|
18656
|
-
validate: (v) => {
|
|
18657
|
-
if (!v.trim()) return "\u4E0D\u80FD\u4E3A\u7A7A";
|
|
18658
|
-
try {
|
|
18659
|
-
new URL(v);
|
|
18660
|
-
} catch {
|
|
18661
|
-
return "\u8BF7\u8F93\u5165\u5408\u6CD5\u7684 URL";
|
|
18662
|
-
}
|
|
18663
|
-
return void 0;
|
|
18664
|
-
}
|
|
18665
|
-
});
|
|
18666
|
-
}
|
|
18667
18615
|
const channels = cfg.channels ?? {};
|
|
18668
18616
|
const channelCfg = channels[CHANNEL_ID] ?? {};
|
|
18669
18617
|
const accounts = channelCfg.accounts ?? {};
|
|
@@ -18681,9 +18629,7 @@ var quantumImOnboarding = {
|
|
|
18681
18629
|
appSecret: appSecret.trim(),
|
|
18682
18630
|
"quantum-account": quantumAccount.trim(),
|
|
18683
18631
|
"quantum-appId": quantumAppId.trim(),
|
|
18684
|
-
"quantum-appSecret": quantumAppSecret.trim()
|
|
18685
|
-
pin: pin.trim(),
|
|
18686
|
-
"quantum-url": quantumUrl.trim()
|
|
18632
|
+
"quantum-appSecret": quantumAppSecret.trim()
|
|
18687
18633
|
}
|
|
18688
18634
|
}
|
|
18689
18635
|
}
|
|
@@ -18868,7 +18814,6 @@ var quantumImPlugin = {
|
|
|
18868
18814
|
|
|
18869
18815
|
// src/crypto/quantun-plug-adapter.ts
|
|
18870
18816
|
var import_node_module = require("module");
|
|
18871
|
-
var import_node_url = require("url");
|
|
18872
18817
|
var import_meta = {};
|
|
18873
18818
|
var log14 = createLogger("crypto/quantun-plug-adapter");
|
|
18874
18819
|
var SM4_MODE = {
|
|
@@ -18878,8 +18823,7 @@ var SM4_MODE = {
|
|
|
18878
18823
|
CBC_PKCS7PADDING: 4
|
|
18879
18824
|
};
|
|
18880
18825
|
var DEFAULT_ENCRYPT_MODE = SM4_MODE.CBC_PKCS7PADDING;
|
|
18881
|
-
var
|
|
18882
|
-
var require2 = (0, import_node_module.createRequire)(_currentUrl);
|
|
18826
|
+
var require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
18883
18827
|
var quantunPlug = null;
|
|
18884
18828
|
try {
|
|
18885
18829
|
quantunPlug = require2("./sdk/index.min.cjs");
|
|
@@ -18890,32 +18834,8 @@ try {
|
|
|
18890
18834
|
}
|
|
18891
18835
|
var quantun_plug_adapter_default = quantunPlug;
|
|
18892
18836
|
|
|
18893
|
-
// src/crypto/quantum-config-writer.ts
|
|
18894
|
-
var import_node_fs = require("fs");
|
|
18895
|
-
var import_node_path = require("path");
|
|
18896
|
-
var import_node_url2 = require("url");
|
|
18897
|
-
var import_meta2 = {};
|
|
18898
|
-
var log15 = createLogger("crypto/quantum-config-writer");
|
|
18899
|
-
var _currentDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
|
|
18900
|
-
var QUANTUM_JSON_PATH = (0, import_node_path.join)(
|
|
18901
|
-
_currentDir,
|
|
18902
|
-
"sdk",
|
|
18903
|
-
"quantum.json"
|
|
18904
|
-
);
|
|
18905
|
-
function writeQuantumConfig(credentials) {
|
|
18906
|
-
const sdkConfig = {
|
|
18907
|
-
account: credentials.quantumAccount,
|
|
18908
|
-
appID: credentials.quantumAppId,
|
|
18909
|
-
pin: credentials.pin,
|
|
18910
|
-
authCode: credentials.quantumAppSecret,
|
|
18911
|
-
url: credentials.quantumUrl
|
|
18912
|
-
};
|
|
18913
|
-
(0, import_node_fs.writeFileSync)(QUANTUM_JSON_PATH, JSON.stringify(sdkConfig, null, 2), "utf-8");
|
|
18914
|
-
log15.info("quantum.json written to", QUANTUM_JSON_PATH);
|
|
18915
|
-
}
|
|
18916
|
-
|
|
18917
18837
|
// src/crypto/crypto-engine.ts
|
|
18918
|
-
var
|
|
18838
|
+
var log15 = createLogger("crypto/crypto-engine");
|
|
18919
18839
|
var PASSTHROUGH_KEY_ID = "passthrough";
|
|
18920
18840
|
var FILE_CHUNK_SIZE = 10 * 1024 * 1024;
|
|
18921
18841
|
var FILE_DECRYPT_CHUNK_SIZE = FILE_CHUNK_SIZE + 16;
|
|
@@ -18926,26 +18846,20 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18926
18846
|
initialized;
|
|
18927
18847
|
/** 是否为透传模式 (不加密, 明文直接透传) */
|
|
18928
18848
|
passthrough;
|
|
18929
|
-
/** 用户凭据 — 用于生成 quantum.json (透传模式下为 undefined) */
|
|
18930
|
-
credentials;
|
|
18931
18849
|
/**
|
|
18932
18850
|
* 构造加密引擎
|
|
18933
18851
|
*
|
|
18934
|
-
* @param
|
|
18935
|
-
* @param opts.passthrough - true 为透传模式 (不加密); false 为加密模式 (默认)
|
|
18936
|
-
* @param opts.credentials - 用户凭据 (加密模式必需, 用于生成 quantum.json)
|
|
18852
|
+
* @param passthrough - true 为透传模式 (不加密); false 为加密模式 (默认)
|
|
18937
18853
|
*
|
|
18938
18854
|
* 请使用静态工厂方法:
|
|
18939
|
-
* - new CryptoEngine(
|
|
18855
|
+
* - new CryptoEngine() → 加密模式, 使用量子 SDK
|
|
18940
18856
|
* - CryptoEngine.createPassthrough() → 透传模式, 无需 init()
|
|
18941
18857
|
*/
|
|
18942
|
-
constructor(
|
|
18943
|
-
const { passthrough = false, credentials } = opts;
|
|
18858
|
+
constructor(passthrough = false) {
|
|
18944
18859
|
this.passthrough = passthrough;
|
|
18945
|
-
this.credentials = credentials;
|
|
18946
18860
|
this.plug = passthrough ? null : quantun_plug_adapter_default;
|
|
18947
18861
|
this.initialized = passthrough;
|
|
18948
|
-
|
|
18862
|
+
log15.info(`CryptoEngine created (${passthrough ? "passthrough mode \u2014 crypto disabled" : "quantum SDK mode"})`);
|
|
18949
18863
|
}
|
|
18950
18864
|
/**
|
|
18951
18865
|
* 创建透传模式的加密引擎 (静态工厂方法)
|
|
@@ -18959,7 +18873,7 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18959
18873
|
* 上层模块 (TokenStore, MessagePipe) 无需感知加密是否开启。
|
|
18960
18874
|
*/
|
|
18961
18875
|
static createPassthrough() {
|
|
18962
|
-
return new _CryptoEngine(
|
|
18876
|
+
return new _CryptoEngine(true);
|
|
18963
18877
|
}
|
|
18964
18878
|
/**
|
|
18965
18879
|
* 初始化加密引擎
|
|
@@ -18977,12 +18891,9 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18977
18891
|
if (!this.plug) {
|
|
18978
18892
|
throw new Error("Quantum encryption SDK is missing. Cannot initialize in encryption mode (check dist/index.min.cjs).");
|
|
18979
18893
|
}
|
|
18980
|
-
if (this.credentials) {
|
|
18981
|
-
writeQuantumConfig(this.credentials);
|
|
18982
|
-
}
|
|
18983
18894
|
await this.plug.init();
|
|
18984
18895
|
this.initialized = true;
|
|
18985
|
-
|
|
18896
|
+
log15.info("CryptoEngine initialized \u2713");
|
|
18986
18897
|
}
|
|
18987
18898
|
/**
|
|
18988
18899
|
* 加密明文
|
|
@@ -18998,12 +18909,12 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18998
18909
|
*/
|
|
18999
18910
|
async encrypt(plaintext) {
|
|
19000
18911
|
if (this.passthrough) {
|
|
19001
|
-
|
|
18912
|
+
log15.debug("Encrypt (passthrough)", { length: plaintext.length });
|
|
19002
18913
|
return { ciphertext: plaintext, keyId: PASSTHROUGH_KEY_ID };
|
|
19003
18914
|
}
|
|
19004
18915
|
const plug = this.requirePlug();
|
|
19005
18916
|
const result = await plug.encrypt(plaintext, DEFAULT_ENCRYPT_MODE);
|
|
19006
|
-
|
|
18917
|
+
log15.debug("Encrypted", { length: plaintext.length, keyId: result.keyId });
|
|
19007
18918
|
return { ciphertext: result.cipherText, keyId: result.keyId };
|
|
19008
18919
|
}
|
|
19009
18920
|
/**
|
|
@@ -19019,12 +18930,12 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19019
18930
|
*/
|
|
19020
18931
|
async decrypt(ciphertext, keyId) {
|
|
19021
18932
|
if (this.passthrough) {
|
|
19022
|
-
|
|
18933
|
+
log15.debug("Decrypt (passthrough)", { keyId });
|
|
19023
18934
|
return ciphertext;
|
|
19024
18935
|
}
|
|
19025
18936
|
const plug = this.requirePlug();
|
|
19026
18937
|
const result = await plug.decrypt(ciphertext, keyId, DEFAULT_ENCRYPT_MODE);
|
|
19027
|
-
|
|
18938
|
+
log15.debug("Decrypted", { keyId });
|
|
19028
18939
|
return result.plainText;
|
|
19029
18940
|
}
|
|
19030
18941
|
/**
|
|
@@ -19041,7 +18952,7 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19041
18952
|
*/
|
|
19042
18953
|
async encryptFile(fileData) {
|
|
19043
18954
|
if (this.passthrough) {
|
|
19044
|
-
|
|
18955
|
+
log15.debug("EncryptFile (passthrough)", { size: fileData.length });
|
|
19045
18956
|
return { fileBuffer: fileData, keyId: PASSTHROUGH_KEY_ID };
|
|
19046
18957
|
}
|
|
19047
18958
|
if (fileData.length === 0) {
|
|
@@ -19053,7 +18964,7 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19053
18964
|
let sessionKey;
|
|
19054
18965
|
let fillKey;
|
|
19055
18966
|
const plug = this.requirePlug();
|
|
19056
|
-
|
|
18967
|
+
log15.debug("EncryptFile", { size: fileData.length, chunks: total });
|
|
19057
18968
|
for (let i = 0; i < total; i++) {
|
|
19058
18969
|
const start = i * FILE_CHUNK_SIZE;
|
|
19059
18970
|
const end = Math.min(start + FILE_CHUNK_SIZE, fileData.length);
|
|
@@ -19068,7 +18979,7 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19068
18979
|
sessionKey = result.sessionKey;
|
|
19069
18980
|
fillKey = result.fillKey;
|
|
19070
18981
|
}
|
|
19071
|
-
|
|
18982
|
+
log15.debug("EncryptFile done", { keyId, chunks: total });
|
|
19072
18983
|
return { fileBuffer: Buffer.concat(chunks), keyId: keyId ?? "" };
|
|
19073
18984
|
}
|
|
19074
18985
|
/**
|
|
@@ -19086,7 +18997,7 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19086
18997
|
*/
|
|
19087
18998
|
async decryptFile(fileData, keyId) {
|
|
19088
18999
|
if (this.passthrough) {
|
|
19089
|
-
|
|
19000
|
+
log15.debug("DecryptFile (passthrough)", { keyId });
|
|
19090
19001
|
return fileData;
|
|
19091
19002
|
}
|
|
19092
19003
|
if (fileData.length === 0) {
|
|
@@ -19097,7 +19008,7 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19097
19008
|
const total = Math.ceil(fileData.length / FILE_DECRYPT_CHUNK_SIZE);
|
|
19098
19009
|
let sessionKey;
|
|
19099
19010
|
let fillKey;
|
|
19100
|
-
|
|
19011
|
+
log15.debug("DecryptFile", { size: fileData.length, chunks: total, keyId });
|
|
19101
19012
|
for (let i = 0; i < total; i++) {
|
|
19102
19013
|
const start = i * FILE_DECRYPT_CHUNK_SIZE;
|
|
19103
19014
|
const end = Math.min(start + FILE_DECRYPT_CHUNK_SIZE, fileData.length);
|
|
@@ -19110,7 +19021,7 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19110
19021
|
sessionKey = result.sessionKey;
|
|
19111
19022
|
fillKey = result.fillKey;
|
|
19112
19023
|
}
|
|
19113
|
-
|
|
19024
|
+
log15.debug("DecryptFile done", { keyId, chunks: total });
|
|
19114
19025
|
return Buffer.concat(chunks);
|
|
19115
19026
|
}
|
|
19116
19027
|
/**
|
|
@@ -19133,14 +19044,14 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19133
19044
|
};
|
|
19134
19045
|
|
|
19135
19046
|
// src/auth/oauth-client.ts
|
|
19136
|
-
var
|
|
19137
|
-
var
|
|
19047
|
+
var log16 = createLogger("auth/oauth-client");
|
|
19048
|
+
var SealApiError = class extends Error {
|
|
19138
19049
|
constructor(status, body, endpoint) {
|
|
19139
|
-
super(`API error: ${status} on ${endpoint}`);
|
|
19050
|
+
super(`Seal API error: ${status} on ${endpoint}`);
|
|
19140
19051
|
this.status = status;
|
|
19141
19052
|
this.body = body;
|
|
19142
19053
|
this.endpoint = endpoint;
|
|
19143
|
-
this.name = "
|
|
19054
|
+
this.name = "SealApiError";
|
|
19144
19055
|
}
|
|
19145
19056
|
};
|
|
19146
19057
|
var TokenAcquireError = class extends Error {
|
|
@@ -19157,21 +19068,28 @@ function sleep(ms) {
|
|
|
19157
19068
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
19158
19069
|
}
|
|
19159
19070
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
19160
|
-
var
|
|
19161
|
-
var SCOPE = "client_credentials refresh_token";
|
|
19162
|
-
var OAuthClient = class {
|
|
19071
|
+
var SealClient = class {
|
|
19163
19072
|
baseUrl;
|
|
19164
|
-
|
|
19165
|
-
|
|
19073
|
+
clientId;
|
|
19074
|
+
clientSecret;
|
|
19075
|
+
clientName;
|
|
19076
|
+
scopes;
|
|
19166
19077
|
constructor(config2) {
|
|
19167
|
-
this.baseUrl = config2.
|
|
19168
|
-
this.
|
|
19169
|
-
this.
|
|
19170
|
-
|
|
19078
|
+
this.baseUrl = config2.serverUrl.replace(/\/+$/, "");
|
|
19079
|
+
this.clientId = config2.clientId;
|
|
19080
|
+
this.clientSecret = config2.clientSecret;
|
|
19081
|
+
this.clientName = config2.clientName;
|
|
19082
|
+
this.scopes = config2.scopes;
|
|
19083
|
+
log16.info("SealClient initialized", { serverUrl: this.baseUrl, clientName: config2.clientName });
|
|
19171
19084
|
}
|
|
19172
19085
|
// ── 通用 HTTP 请求 ──
|
|
19173
19086
|
/**
|
|
19174
19087
|
* 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
|
|
19088
|
+
* @param method - HTTP 方法
|
|
19089
|
+
* @param path - 请求路径 (如 '/seal/client/register')
|
|
19090
|
+
* @param options - 请求选项
|
|
19091
|
+
* @returns 解析后的 JSON 响应
|
|
19092
|
+
* @throws SealApiError — HTTP 非 2xx 响应
|
|
19175
19093
|
*/
|
|
19176
19094
|
async request(method, path2, options = {}) {
|
|
19177
19095
|
const url2 = `${this.baseUrl}${path2}`;
|
|
@@ -19187,7 +19105,7 @@ var OAuthClient = class {
|
|
|
19187
19105
|
bodyStr = JSON.stringify(options.body);
|
|
19188
19106
|
}
|
|
19189
19107
|
}
|
|
19190
|
-
|
|
19108
|
+
log16.debug("HTTP request", { method, url: url2 });
|
|
19191
19109
|
const response = await fetch(url2, {
|
|
19192
19110
|
method,
|
|
19193
19111
|
headers,
|
|
@@ -19202,39 +19120,110 @@ var OAuthClient = class {
|
|
|
19202
19120
|
} catch {
|
|
19203
19121
|
parsedBody = responseBody;
|
|
19204
19122
|
}
|
|
19205
|
-
|
|
19206
|
-
throw new
|
|
19123
|
+
log16.error("HTTP error", { method, url: url2, status: response.status });
|
|
19124
|
+
throw new SealApiError(response.status, parsedBody, path2);
|
|
19207
19125
|
}
|
|
19208
|
-
|
|
19126
|
+
log16.debug("HTTP response", { method, url: url2, status: response.status });
|
|
19209
19127
|
try {
|
|
19210
19128
|
return JSON.parse(responseBody);
|
|
19211
19129
|
} catch {
|
|
19212
|
-
throw new
|
|
19130
|
+
throw new SealApiError(response.status, responseBody, path2);
|
|
19213
19131
|
}
|
|
19214
19132
|
}
|
|
19215
19133
|
// ── 公共方法 ──
|
|
19134
|
+
/** 判断是否已注册 (有 clientId + clientSecret) */
|
|
19135
|
+
isRegistered() {
|
|
19136
|
+
return !!(this.clientId && this.clientSecret);
|
|
19137
|
+
}
|
|
19138
|
+
/** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
|
|
19139
|
+
setCredentials(clientId, clientSecret) {
|
|
19140
|
+
this.clientId = clientId;
|
|
19141
|
+
this.clientSecret = clientSecret;
|
|
19142
|
+
log16.info("Credentials updated", { clientId: sanitize(clientId) });
|
|
19143
|
+
}
|
|
19216
19144
|
/**
|
|
19217
|
-
*
|
|
19145
|
+
* 动态注册客户端 → POST /seal/client/register
|
|
19218
19146
|
*
|
|
19219
|
-
*
|
|
19220
|
-
*
|
|
19221
|
-
|
|
19222
|
-
|
|
19223
|
-
|
|
19147
|
+
* 首次运行时自动调用,获取 clientId 和 clientSecret。
|
|
19148
|
+
* 注册成功后自动更新内部凭据。
|
|
19149
|
+
*/
|
|
19150
|
+
async register(params) {
|
|
19151
|
+
const body = {
|
|
19152
|
+
client_name: params.clientName,
|
|
19153
|
+
scopes: params.scopes,
|
|
19154
|
+
authentication_methods: params.authMethods ?? ["client_secret_post"],
|
|
19155
|
+
grant_types: params.grantTypes ?? ["authorization_code", "client_credentials", "refresh_token"],
|
|
19156
|
+
redirect_uris: params.redirectUris ?? ["https://placeholder.local"],
|
|
19157
|
+
logout_redirect_uris: [],
|
|
19158
|
+
client_settings: {
|
|
19159
|
+
"settings.client.require-authorization-consent": true
|
|
19160
|
+
}
|
|
19161
|
+
};
|
|
19162
|
+
if (params.tokenSettings) {
|
|
19163
|
+
const ts = {};
|
|
19164
|
+
if (params.tokenSettings.accessTokenTtl) {
|
|
19165
|
+
ts["settings.token.access-token-time-to-live"] = params.tokenSettings.accessTokenTtl;
|
|
19166
|
+
}
|
|
19167
|
+
if (params.tokenSettings.refreshTokenTtl) {
|
|
19168
|
+
ts["settings.token.refresh-token-time-to-live"] = params.tokenSettings.refreshTokenTtl;
|
|
19169
|
+
}
|
|
19170
|
+
if (params.tokenSettings.accessTokenFormat) {
|
|
19171
|
+
ts["settings.token.access-token-format"] = params.tokenSettings.accessTokenFormat;
|
|
19172
|
+
}
|
|
19173
|
+
if (params.tokenSettings.reuseRefreshTokens !== void 0) {
|
|
19174
|
+
ts["settings.token.reuse-refresh-tokens"] = params.tokenSettings.reuseRefreshTokens;
|
|
19175
|
+
}
|
|
19176
|
+
ts["settings.token.authorization-code-time-to-live"] = "300";
|
|
19177
|
+
body.token_settings = ts;
|
|
19178
|
+
}
|
|
19179
|
+
log16.info("Registering OAuth client", { clientName: params.clientName });
|
|
19180
|
+
const response = await this.request(
|
|
19181
|
+
"POST",
|
|
19182
|
+
"/seal/client/register",
|
|
19183
|
+
{ body, contentType: "json" }
|
|
19184
|
+
);
|
|
19185
|
+
const registration = {
|
|
19186
|
+
clientId: response.client_id,
|
|
19187
|
+
clientSecret: response.client_secret,
|
|
19188
|
+
clientSecretExpiresAt: response.client_secret_expires_at ?? "",
|
|
19189
|
+
clientIdIssuedAt: response.client_id_issued_at ?? "",
|
|
19190
|
+
scopes: response.scopes ?? params.scopes
|
|
19191
|
+
};
|
|
19192
|
+
this.setCredentials(registration.clientId, registration.clientSecret);
|
|
19193
|
+
log16.info("OAuth client registered", {
|
|
19194
|
+
clientId: sanitize(registration.clientId),
|
|
19195
|
+
clientSecret: sanitize(registration.clientSecret),
|
|
19196
|
+
scopes: registration.scopes,
|
|
19197
|
+
expiresAt: registration.clientSecretExpiresAt
|
|
19198
|
+
});
|
|
19199
|
+
return registration;
|
|
19200
|
+
}
|
|
19201
|
+
/**
|
|
19202
|
+
* 获取访问令牌 → POST /seal/oauth2/token (client_credentials)
|
|
19224
19203
|
*
|
|
19204
|
+
* 使用客户端凭证模式获取 Access Token。
|
|
19205
|
+
* 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
|
|
19206
|
+
*
|
|
19207
|
+
* @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
|
|
19225
19208
|
* @returns TokenData 包含 access_token、过期时间等
|
|
19226
|
-
* @throws
|
|
19209
|
+
* @throws Error — clientId/clientSecret 未设置
|
|
19210
|
+
* @throws SealApiError — HTTP 错误
|
|
19227
19211
|
*/
|
|
19228
|
-
async getToken() {
|
|
19212
|
+
async getToken(scope) {
|
|
19213
|
+
if (!this.clientId || !this.clientSecret) {
|
|
19214
|
+
throw new Error("Cannot get token: clientId and clientSecret are required. Call register() or setCredentials() first.");
|
|
19215
|
+
}
|
|
19229
19216
|
const params = new URLSearchParams();
|
|
19230
|
-
params.set("grant_type",
|
|
19231
|
-
params.set("client_id", this.
|
|
19232
|
-
params.set("client_secret", this.
|
|
19233
|
-
|
|
19234
|
-
|
|
19217
|
+
params.set("grant_type", "client_credentials");
|
|
19218
|
+
params.set("client_id", this.clientId);
|
|
19219
|
+
params.set("client_secret", this.clientSecret);
|
|
19220
|
+
if (scope) {
|
|
19221
|
+
params.set("scope", scope);
|
|
19222
|
+
}
|
|
19223
|
+
log16.info("Requesting access token", { clientId: sanitize(this.clientId) });
|
|
19235
19224
|
const response = await this.request(
|
|
19236
19225
|
"POST",
|
|
19237
|
-
"/
|
|
19226
|
+
"/seal/oauth2/token",
|
|
19238
19227
|
{ body: params, contentType: "form" }
|
|
19239
19228
|
);
|
|
19240
19229
|
const now = Date.now();
|
|
@@ -19245,28 +19234,32 @@ var OAuthClient = class {
|
|
|
19245
19234
|
expiresIn,
|
|
19246
19235
|
scope: response.scope ?? "",
|
|
19247
19236
|
refreshToken: void 0,
|
|
19237
|
+
// Seal 不返回 refresh_token
|
|
19248
19238
|
grantedAt: now,
|
|
19249
19239
|
expiresAt: now + expiresIn * 1e3
|
|
19250
19240
|
};
|
|
19251
|
-
|
|
19241
|
+
log16.info("Access token acquired", {
|
|
19252
19242
|
accessToken: sanitize(tokenData.accessToken),
|
|
19253
19243
|
expiresIn: tokenData.expiresIn,
|
|
19254
19244
|
scope: tokenData.scope
|
|
19255
19245
|
});
|
|
19256
19246
|
return tokenData;
|
|
19257
19247
|
}
|
|
19258
|
-
/**
|
|
19259
|
-
|
|
19260
|
-
|
|
19248
|
+
/**
|
|
19249
|
+
* 令牌校验 → GET /seal/oauth2/introspect
|
|
19250
|
+
* 暂不实现 — 保留接口签名,后续需要时再补充
|
|
19251
|
+
*/
|
|
19252
|
+
async introspect(_token) {
|
|
19253
|
+
throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
|
|
19261
19254
|
}
|
|
19262
19255
|
};
|
|
19263
19256
|
|
|
19264
19257
|
// src/auth/token-store.ts
|
|
19265
19258
|
var import_promises = require("fs/promises");
|
|
19266
|
-
var
|
|
19267
|
-
var
|
|
19259
|
+
var import_node_fs = require("fs");
|
|
19260
|
+
var import_node_path = require("path");
|
|
19268
19261
|
var import_node_crypto = require("crypto");
|
|
19269
|
-
var
|
|
19262
|
+
var log17 = createLogger("auth/token-store");
|
|
19270
19263
|
var TokenStore = class {
|
|
19271
19264
|
/** 存储目录路径 */
|
|
19272
19265
|
storageDir;
|
|
@@ -19281,7 +19274,7 @@ var TokenStore = class {
|
|
|
19281
19274
|
constructor(storageDir, crypto) {
|
|
19282
19275
|
this.storageDir = storageDir;
|
|
19283
19276
|
this.crypto = crypto;
|
|
19284
|
-
|
|
19277
|
+
log17.info("TokenStore initialized", { storageDir });
|
|
19285
19278
|
}
|
|
19286
19279
|
/**
|
|
19287
19280
|
* 确保存储目录存在并设置正确的权限。
|
|
@@ -19289,9 +19282,9 @@ var TokenStore = class {
|
|
|
19289
19282
|
*/
|
|
19290
19283
|
async ensureDir() {
|
|
19291
19284
|
if (this.dirEnsured) return;
|
|
19292
|
-
if (!(0,
|
|
19285
|
+
if (!(0, import_node_fs.existsSync)(this.storageDir)) {
|
|
19293
19286
|
await (0, import_promises.mkdir)(this.storageDir, { recursive: true, mode: 448 });
|
|
19294
|
-
|
|
19287
|
+
log17.debug("Storage directory created", { dir: this.storageDir });
|
|
19295
19288
|
}
|
|
19296
19289
|
this.dirEnsured = true;
|
|
19297
19290
|
}
|
|
@@ -19300,7 +19293,7 @@ var TokenStore = class {
|
|
|
19300
19293
|
* @param key - 存储键名 (如 'token', 'credentials')
|
|
19301
19294
|
*/
|
|
19302
19295
|
filePath(key) {
|
|
19303
|
-
return (0,
|
|
19296
|
+
return (0, import_node_path.join)(this.storageDir, `${key}.enc`);
|
|
19304
19297
|
}
|
|
19305
19298
|
/**
|
|
19306
19299
|
* 保存 Token — 加密写入文件
|
|
@@ -19326,7 +19319,7 @@ var TokenStore = class {
|
|
|
19326
19319
|
try {
|
|
19327
19320
|
await (0, import_promises.writeFile)(tmpPath, storageJson, { mode: 384 });
|
|
19328
19321
|
await (0, import_promises.rename)(tmpPath, targetPath);
|
|
19329
|
-
|
|
19322
|
+
log17.debug("Token saved", { key, path: targetPath });
|
|
19330
19323
|
} catch (err) {
|
|
19331
19324
|
try {
|
|
19332
19325
|
await (0, import_promises.unlink)(tmpPath);
|
|
@@ -19348,8 +19341,8 @@ var TokenStore = class {
|
|
|
19348
19341
|
*/
|
|
19349
19342
|
async load(key) {
|
|
19350
19343
|
const filePath = this.filePath(key);
|
|
19351
|
-
if (!(0,
|
|
19352
|
-
|
|
19344
|
+
if (!(0, import_node_fs.existsSync)(filePath)) {
|
|
19345
|
+
log17.debug("Token file not found", { key, path: filePath });
|
|
19353
19346
|
return null;
|
|
19354
19347
|
}
|
|
19355
19348
|
try {
|
|
@@ -19357,10 +19350,10 @@ var TokenStore = class {
|
|
|
19357
19350
|
const storage = JSON.parse(raw);
|
|
19358
19351
|
const json2 = await this.crypto.decrypt(storage.ciphertext, storage.keyId);
|
|
19359
19352
|
const data = JSON.parse(json2);
|
|
19360
|
-
|
|
19353
|
+
log17.debug("Token loaded", { key, path: filePath });
|
|
19361
19354
|
return data;
|
|
19362
19355
|
} catch (err) {
|
|
19363
|
-
|
|
19356
|
+
log17.warn("Failed to load token (corrupted or key mismatch), treating as empty", {
|
|
19364
19357
|
key,
|
|
19365
19358
|
error: err instanceof Error ? err.message : String(err)
|
|
19366
19359
|
});
|
|
@@ -19376,7 +19369,7 @@ var TokenStore = class {
|
|
|
19376
19369
|
const filePath = this.filePath(key);
|
|
19377
19370
|
try {
|
|
19378
19371
|
await (0, import_promises.unlink)(filePath);
|
|
19379
|
-
|
|
19372
|
+
log17.debug("Token file cleared", { key, path: filePath });
|
|
19380
19373
|
} catch (err) {
|
|
19381
19374
|
if (err.code !== "ENOENT") {
|
|
19382
19375
|
throw err;
|
|
@@ -19386,15 +19379,17 @@ var TokenStore = class {
|
|
|
19386
19379
|
};
|
|
19387
19380
|
|
|
19388
19381
|
// src/auth/token-manager.ts
|
|
19389
|
-
var
|
|
19382
|
+
var log18 = createLogger("auth/token-manager");
|
|
19390
19383
|
var DEFAULT_REFRESH_AHEAD_MS = 5 * 60 * 1e3;
|
|
19391
19384
|
var MAX_RETRIES = 3;
|
|
19392
19385
|
var RETRY_BASE_MS = 1e3;
|
|
19393
19386
|
var TokenManager = class {
|
|
19394
|
-
|
|
19387
|
+
sealClient;
|
|
19395
19388
|
tokenStore;
|
|
19396
19389
|
/** Token 过期前提前刷新的时间 (ms) */
|
|
19397
19390
|
refreshAheadMs;
|
|
19391
|
+
/** 自动注册的参数 */
|
|
19392
|
+
autoRegisterParams;
|
|
19398
19393
|
// ── 内存缓存 ──
|
|
19399
19394
|
/** 内存中缓存的 access_token */
|
|
19400
19395
|
cachedToken = null;
|
|
@@ -19408,10 +19403,11 @@ var TokenManager = class {
|
|
|
19408
19403
|
/** 定时刷新器句柄 */
|
|
19409
19404
|
refreshTimer = null;
|
|
19410
19405
|
constructor(deps) {
|
|
19411
|
-
this.
|
|
19406
|
+
this.sealClient = deps.sealClient;
|
|
19412
19407
|
this.tokenStore = deps.tokenStore;
|
|
19413
19408
|
this.refreshAheadMs = deps.refreshAheadMs ?? DEFAULT_REFRESH_AHEAD_MS;
|
|
19414
|
-
|
|
19409
|
+
this.autoRegisterParams = deps.autoRegisterParams;
|
|
19410
|
+
log18.info("TokenManager initialized", { refreshAheadMs: this.refreshAheadMs });
|
|
19415
19411
|
}
|
|
19416
19412
|
// ── 核心方法 ──
|
|
19417
19413
|
/**
|
|
@@ -19429,7 +19425,7 @@ var TokenManager = class {
|
|
|
19429
19425
|
return this.cachedToken;
|
|
19430
19426
|
}
|
|
19431
19427
|
if (this.refreshLock) {
|
|
19432
|
-
|
|
19428
|
+
log18.debug("Waiting for concurrent token acquisition");
|
|
19433
19429
|
return this.refreshLock;
|
|
19434
19430
|
}
|
|
19435
19431
|
this.refreshLock = this._acquireTokenWithRetry();
|
|
@@ -19439,6 +19435,13 @@ var TokenManager = class {
|
|
|
19439
19435
|
this.refreshLock = null;
|
|
19440
19436
|
}
|
|
19441
19437
|
}
|
|
19438
|
+
/**
|
|
19439
|
+
* 令牌自省 — 暂不实现,后续补充。
|
|
19440
|
+
* 返回的 identity_id 用于 gate.ts 的 anti-loop 检查 (bot 自己的 ID)。
|
|
19441
|
+
*/
|
|
19442
|
+
async introspect() {
|
|
19443
|
+
throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
|
|
19444
|
+
}
|
|
19442
19445
|
/** 检查当前令牌是否包含指定的 scope */
|
|
19443
19446
|
hasScope(scope) {
|
|
19444
19447
|
if (!this.cachedScope) return false;
|
|
@@ -19455,13 +19458,13 @@ var TokenManager = class {
|
|
|
19455
19458
|
this.cachedToken = null;
|
|
19456
19459
|
this.cachedScope = null;
|
|
19457
19460
|
this.currentTokenData = null;
|
|
19458
|
-
|
|
19461
|
+
log18.info("Token revoked and cleared");
|
|
19459
19462
|
}
|
|
19460
19463
|
/** 清理定时器和并发锁 — 优雅关闭时调用 */
|
|
19461
19464
|
shutdown() {
|
|
19462
19465
|
this.clearRefreshTimer();
|
|
19463
19466
|
this.refreshLock = null;
|
|
19464
|
-
|
|
19467
|
+
log18.info("TokenManager shutdown");
|
|
19465
19468
|
}
|
|
19466
19469
|
// ── 内部方法 ──
|
|
19467
19470
|
/**
|
|
@@ -19469,26 +19472,26 @@ var TokenManager = class {
|
|
|
19469
19472
|
*
|
|
19470
19473
|
* 重试策略:
|
|
19471
19474
|
* - 网络错误 / 5xx → 重试 (最多 3 次)
|
|
19472
|
-
* - 401/403 → 不重试 (
|
|
19475
|
+
* - 401/403 → 不重试 (client_secret 可能无效)
|
|
19473
19476
|
*/
|
|
19474
19477
|
async _acquireTokenWithRetry() {
|
|
19475
19478
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
19476
19479
|
try {
|
|
19477
19480
|
return await this._acquireToken();
|
|
19478
19481
|
} catch (err) {
|
|
19479
|
-
if (err instanceof
|
|
19480
|
-
|
|
19482
|
+
if (err instanceof SealApiError && (err.status === 401 || err.status === 403)) {
|
|
19483
|
+
log18.error("Token acquire failed with auth error, not retrying", { status: err.status });
|
|
19481
19484
|
throw err;
|
|
19482
19485
|
}
|
|
19483
19486
|
if (attempt === MAX_RETRIES) {
|
|
19484
|
-
|
|
19487
|
+
log18.error("Token acquire failed after all retries", { attempts: attempt + 1 });
|
|
19485
19488
|
throw new TokenAcquireError(
|
|
19486
19489
|
`Token acquire failed after ${attempt + 1} attempts`,
|
|
19487
19490
|
{ cause: err instanceof Error ? err : void 0 }
|
|
19488
19491
|
);
|
|
19489
19492
|
}
|
|
19490
19493
|
const delay = RETRY_BASE_MS * Math.pow(2, attempt);
|
|
19491
|
-
|
|
19494
|
+
log18.warn("Token acquire failed, retrying", {
|
|
19492
19495
|
attempt: attempt + 1,
|
|
19493
19496
|
maxRetries: MAX_RETRIES,
|
|
19494
19497
|
retryInMs: delay,
|
|
@@ -19504,27 +19507,63 @@ var TokenManager = class {
|
|
|
19504
19507
|
*
|
|
19505
19508
|
* 流程:
|
|
19506
19509
|
* 1. 尝试从 TokenStore 加载缓存
|
|
19507
|
-
* 2.
|
|
19508
|
-
* 3.
|
|
19509
|
-
* 4.
|
|
19510
|
+
* 2. 检查是否需要注册客户端
|
|
19511
|
+
* 3. 调用 SealClient.getToken() 获取新 Token
|
|
19512
|
+
* 4. 保存到内存缓存 + TokenStore
|
|
19513
|
+
* 5. 设置定时刷新器
|
|
19510
19514
|
*/
|
|
19511
19515
|
async _acquireToken() {
|
|
19512
19516
|
try {
|
|
19513
19517
|
const stored = await this.tokenStore.load("token");
|
|
19514
19518
|
if (stored && !this.isTokenHardExpired(stored)) {
|
|
19515
|
-
|
|
19519
|
+
log18.info("Token loaded from store (still valid)");
|
|
19516
19520
|
this.updateCache(stored);
|
|
19517
19521
|
this.scheduleRefresh(stored);
|
|
19518
19522
|
return stored.accessToken;
|
|
19519
19523
|
}
|
|
19520
19524
|
if (stored) {
|
|
19521
|
-
|
|
19525
|
+
log18.info("Stored token has expired, acquiring new one");
|
|
19522
19526
|
}
|
|
19523
19527
|
} catch {
|
|
19524
|
-
|
|
19528
|
+
log18.warn("Failed to load token from store, acquiring new one");
|
|
19529
|
+
}
|
|
19530
|
+
if (!this.sealClient.isRegistered()) {
|
|
19531
|
+
try {
|
|
19532
|
+
const savedCreds = await this.tokenStore.load("credentials");
|
|
19533
|
+
if (savedCreds?.clientId && savedCreds?.clientSecret) {
|
|
19534
|
+
this.sealClient.setCredentials(
|
|
19535
|
+
savedCreds.clientId,
|
|
19536
|
+
savedCreds.clientSecret
|
|
19537
|
+
);
|
|
19538
|
+
log18.info("Credentials loaded from store");
|
|
19539
|
+
}
|
|
19540
|
+
} catch {
|
|
19541
|
+
log18.debug("No saved credentials found");
|
|
19542
|
+
}
|
|
19543
|
+
}
|
|
19544
|
+
if (!this.sealClient.isRegistered()) {
|
|
19545
|
+
if (!this.autoRegisterParams) {
|
|
19546
|
+
throw new Error("SealClient not registered and autoRegisterParams not provided. Call setCredentials() or enable autoRegister.");
|
|
19547
|
+
}
|
|
19548
|
+
log18.info("Auto-registering OAuth client");
|
|
19549
|
+
const registration = await this.sealClient.register({
|
|
19550
|
+
clientName: this.autoRegisterParams.clientName,
|
|
19551
|
+
scopes: this.autoRegisterParams.scopes,
|
|
19552
|
+
tokenSettings: this.autoRegisterParams.tokenSettings ? {
|
|
19553
|
+
accessTokenTtl: this.autoRegisterParams.tokenSettings.accessTokenTtl,
|
|
19554
|
+
refreshTokenTtl: this.autoRegisterParams.tokenSettings.refreshTokenTtl,
|
|
19555
|
+
accessTokenFormat: this.autoRegisterParams.tokenSettings.accessTokenFormat
|
|
19556
|
+
} : void 0
|
|
19557
|
+
});
|
|
19558
|
+
await this.tokenStore.save("credentials", {
|
|
19559
|
+
clientId: registration.clientId,
|
|
19560
|
+
clientSecret: registration.clientSecret,
|
|
19561
|
+
clientSecretExpiresAt: registration.clientSecretExpiresAt
|
|
19562
|
+
});
|
|
19563
|
+
log18.info("OAuth client registered and credentials saved");
|
|
19525
19564
|
}
|
|
19526
|
-
|
|
19527
|
-
const tokenData = await this.
|
|
19565
|
+
log18.info("Acquiring new access token");
|
|
19566
|
+
const tokenData = await this.sealClient.getToken();
|
|
19528
19567
|
this.updateCache(tokenData);
|
|
19529
19568
|
await this.tokenStore.save("token", tokenData);
|
|
19530
19569
|
this.scheduleRefresh(tokenData);
|
|
@@ -19554,22 +19593,22 @@ var TokenManager = class {
|
|
|
19554
19593
|
this.clearRefreshTimer();
|
|
19555
19594
|
const refreshInMs = tokenData.expiresAt - this.refreshAheadMs - Date.now();
|
|
19556
19595
|
if (refreshInMs <= 0) {
|
|
19557
|
-
|
|
19596
|
+
log18.debug("Token already within refresh window, not scheduling timer");
|
|
19558
19597
|
return;
|
|
19559
19598
|
}
|
|
19560
|
-
|
|
19599
|
+
log18.debug("Scheduling token refresh", {
|
|
19561
19600
|
refreshInMs,
|
|
19562
19601
|
refreshAt: new Date(Date.now() + refreshInMs).toISOString()
|
|
19563
19602
|
});
|
|
19564
19603
|
this.refreshTimer = setTimeout(async () => {
|
|
19565
19604
|
try {
|
|
19566
|
-
|
|
19605
|
+
log18.info("Scheduled token refresh triggered");
|
|
19567
19606
|
this.cachedToken = null;
|
|
19568
19607
|
this.currentTokenData = null;
|
|
19569
19608
|
await this.getValidToken();
|
|
19570
|
-
|
|
19609
|
+
log18.info("Scheduled token refresh completed");
|
|
19571
19610
|
} catch (err) {
|
|
19572
|
-
|
|
19611
|
+
log18.error("Scheduled token refresh failed", {
|
|
19573
19612
|
error: err instanceof Error ? err.message : String(err)
|
|
19574
19613
|
});
|
|
19575
19614
|
}
|
|
@@ -19597,13 +19636,13 @@ var wrapper_default = import_websocket.default;
|
|
|
19597
19636
|
|
|
19598
19637
|
// src/transport/ws-client.ts
|
|
19599
19638
|
var import_node_events = require("events");
|
|
19600
|
-
var
|
|
19639
|
+
var log19 = createLogger("transport/ws-client");
|
|
19601
19640
|
var WSClient = class extends import_node_events.EventEmitter {
|
|
19602
19641
|
/** 底层 WebSocket 实例 */
|
|
19603
19642
|
ws = null;
|
|
19604
19643
|
constructor() {
|
|
19605
19644
|
super();
|
|
19606
|
-
|
|
19645
|
+
log19.info("WSClient initialized");
|
|
19607
19646
|
}
|
|
19608
19647
|
/**
|
|
19609
19648
|
* 连接到 WebSocket 服务器并绑定事件。
|
|
@@ -19619,11 +19658,11 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19619
19658
|
this.ws = null;
|
|
19620
19659
|
}
|
|
19621
19660
|
const { url: url2, protocols, headers } = options;
|
|
19622
|
-
|
|
19661
|
+
log19.info("ws:connecting", { url: url2 });
|
|
19623
19662
|
return new Promise((resolve2, reject) => {
|
|
19624
19663
|
const ws = new wrapper_default(url2, protocols, { headers });
|
|
19625
19664
|
ws.on("open", () => {
|
|
19626
|
-
|
|
19665
|
+
log19.info("ws:connected", { url: url2 });
|
|
19627
19666
|
this.emit("open");
|
|
19628
19667
|
resolve2();
|
|
19629
19668
|
});
|
|
@@ -19631,11 +19670,11 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19631
19670
|
this.emit("message", data);
|
|
19632
19671
|
});
|
|
19633
19672
|
ws.on("close", (code, reason) => {
|
|
19634
|
-
|
|
19673
|
+
log19.info("ws:closed", { code, reason: reason.toString() });
|
|
19635
19674
|
this.emit("close", code, reason.toString());
|
|
19636
19675
|
});
|
|
19637
19676
|
ws.on("error", (err) => {
|
|
19638
|
-
|
|
19677
|
+
log19.error("ws:error", { error: err.message });
|
|
19639
19678
|
this.emit("error", err);
|
|
19640
19679
|
if (this.ws !== ws) {
|
|
19641
19680
|
reject(err);
|
|
@@ -19647,7 +19686,7 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19647
19686
|
/** 发送数据到服务器 — 前置检查连接状态 */
|
|
19648
19687
|
send(data) {
|
|
19649
19688
|
if (!this.ws || this.ws.readyState !== wrapper_default.OPEN) {
|
|
19650
|
-
|
|
19689
|
+
log19.warn("ws:send skipped, not connected", { readyState: this.ws?.readyState });
|
|
19651
19690
|
return;
|
|
19652
19691
|
}
|
|
19653
19692
|
this.ws.send(data);
|
|
@@ -19658,7 +19697,7 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19658
19697
|
this.ws.removeAllListeners();
|
|
19659
19698
|
this.ws.close(code, reason);
|
|
19660
19699
|
this.ws = null;
|
|
19661
|
-
|
|
19700
|
+
log19.info("ws:close requested", { code, reason });
|
|
19662
19701
|
}
|
|
19663
19702
|
}
|
|
19664
19703
|
/** 当前连接状态 (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED) */
|
|
@@ -19668,7 +19707,7 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19668
19707
|
};
|
|
19669
19708
|
|
|
19670
19709
|
// src/transport/dedup.ts
|
|
19671
|
-
var
|
|
19710
|
+
var log20 = createLogger("transport/dedup");
|
|
19672
19711
|
var MessageDedup = class {
|
|
19673
19712
|
/** 去重缓存生存时间 (ms) */
|
|
19674
19713
|
ttlMs;
|
|
@@ -19679,7 +19718,7 @@ var MessageDedup = class {
|
|
|
19679
19718
|
constructor(config2) {
|
|
19680
19719
|
this.ttlMs = config2?.ttlMs ?? 3e5;
|
|
19681
19720
|
this.maxEntries = config2?.maxEntries ?? 5e3;
|
|
19682
|
-
|
|
19721
|
+
log20.info("MessageDedup initialized", { ttlMs: this.ttlMs, maxEntries: this.maxEntries });
|
|
19683
19722
|
}
|
|
19684
19723
|
/**
|
|
19685
19724
|
* 检查消息是否重复。
|
|
@@ -19715,7 +19754,7 @@ var MessageDedup = class {
|
|
|
19715
19754
|
|
|
19716
19755
|
// src/transport/message-pipe.ts
|
|
19717
19756
|
var import_node_crypto2 = require("crypto");
|
|
19718
|
-
var
|
|
19757
|
+
var log21 = createLogger("transport/message-pipe");
|
|
19719
19758
|
var MessagePipe = class {
|
|
19720
19759
|
wsClient;
|
|
19721
19760
|
dedup;
|
|
@@ -19734,15 +19773,15 @@ var MessagePipe = class {
|
|
|
19734
19773
|
this.messageServiceBaseUrl = deps.messageServiceBaseUrl;
|
|
19735
19774
|
this.wsClient.on("message", (rawData) => {
|
|
19736
19775
|
this.handleInbound(rawData).catch((err) => {
|
|
19737
|
-
|
|
19776
|
+
log21.error("inbound:error", { step: "handleInbound", error: err.message });
|
|
19738
19777
|
});
|
|
19739
19778
|
});
|
|
19740
|
-
|
|
19779
|
+
log21.info("MessagePipe initialized");
|
|
19741
19780
|
}
|
|
19742
19781
|
/** 注册入站消息回调 — 解密后的消息会通过此回调传给 L4 层 */
|
|
19743
19782
|
onMessage(callback) {
|
|
19744
19783
|
this.messageCallback = callback;
|
|
19745
|
-
|
|
19784
|
+
log21.info("Inbound message callback registered");
|
|
19746
19785
|
}
|
|
19747
19786
|
/**
|
|
19748
19787
|
* 出站发送 — 通过 HTTP API 发送消息到 IM 服务器。
|
|
@@ -19762,7 +19801,7 @@ var MessagePipe = class {
|
|
|
19762
19801
|
}
|
|
19763
19802
|
const result = await this.callMessageApi("/messages/v1/send", body, "outbound");
|
|
19764
19803
|
if (!result) return;
|
|
19765
|
-
|
|
19804
|
+
log21.info("outbound:sent", {
|
|
19766
19805
|
chatId: msg.chatId,
|
|
19767
19806
|
senderId: msg.senderId,
|
|
19768
19807
|
msgType: msg.msgType,
|
|
@@ -19786,7 +19825,7 @@ var MessagePipe = class {
|
|
|
19786
19825
|
};
|
|
19787
19826
|
const result = await this.callMessageApi("/messages/v1/recall", body, "recall");
|
|
19788
19827
|
if (!result) return;
|
|
19789
|
-
|
|
19828
|
+
log21.info("recall:sent", {
|
|
19790
19829
|
messageId: params.messageId,
|
|
19791
19830
|
requestId: result.request_id
|
|
19792
19831
|
});
|
|
@@ -19813,7 +19852,7 @@ var MessagePipe = class {
|
|
|
19813
19852
|
body: JSON.stringify(body)
|
|
19814
19853
|
});
|
|
19815
19854
|
if (!resp.ok) {
|
|
19816
|
-
|
|
19855
|
+
log21.error(`${logTag}:http-error`, {
|
|
19817
19856
|
status: resp.status,
|
|
19818
19857
|
statusText: resp.statusText,
|
|
19819
19858
|
url: url2
|
|
@@ -19822,7 +19861,7 @@ var MessagePipe = class {
|
|
|
19822
19861
|
}
|
|
19823
19862
|
const result = await resp.json();
|
|
19824
19863
|
if (result.code !== 0) {
|
|
19825
|
-
|
|
19864
|
+
log21.error(`${logTag}:api-error`, {
|
|
19826
19865
|
code: result.code,
|
|
19827
19866
|
msg: result.msg,
|
|
19828
19867
|
requestId: result.request_id
|
|
@@ -19831,7 +19870,7 @@ var MessagePipe = class {
|
|
|
19831
19870
|
}
|
|
19832
19871
|
return result;
|
|
19833
19872
|
} catch (err) {
|
|
19834
|
-
|
|
19873
|
+
log21.error(`${logTag}:network-error`, {
|
|
19835
19874
|
url: url2,
|
|
19836
19875
|
error: err.message
|
|
19837
19876
|
});
|
|
@@ -19853,7 +19892,7 @@ var MessagePipe = class {
|
|
|
19853
19892
|
try {
|
|
19854
19893
|
frame = JSON.parse(String(rawData));
|
|
19855
19894
|
} catch (err) {
|
|
19856
|
-
|
|
19895
|
+
log21.warn("inbound:json-parse-error", { error: err.message });
|
|
19857
19896
|
return;
|
|
19858
19897
|
}
|
|
19859
19898
|
const timestamp = frame["X-CTQ-Timestamp"];
|
|
@@ -19864,7 +19903,7 @@ var MessagePipe = class {
|
|
|
19864
19903
|
const signatureInput = timestamp + nonce + frame.data;
|
|
19865
19904
|
const expected = (0, import_node_crypto2.createHmac)("sha256", token).update(signatureInput).digest("hex");
|
|
19866
19905
|
if (expected !== signature) {
|
|
19867
|
-
|
|
19906
|
+
log21.warn("inbound:signature-fail", { timestamp, nonce });
|
|
19868
19907
|
return;
|
|
19869
19908
|
}
|
|
19870
19909
|
}
|
|
@@ -19878,37 +19917,30 @@ var MessagePipe = class {
|
|
|
19878
19917
|
try {
|
|
19879
19918
|
callbackData = JSON.parse(dataStr);
|
|
19880
19919
|
} catch (err) {
|
|
19881
|
-
|
|
19920
|
+
log21.warn("inbound:data-parse-error", { error: err.message });
|
|
19882
19921
|
return;
|
|
19883
19922
|
}
|
|
19884
|
-
if (callbackData.eventType !== "
|
|
19885
|
-
|
|
19923
|
+
if (callbackData.eventType !== "MessageReceived") {
|
|
19924
|
+
log21.debug("inbound:event-ignored", { eventType: callbackData.eventType });
|
|
19886
19925
|
return;
|
|
19887
19926
|
}
|
|
19888
|
-
const msg =
|
|
19889
|
-
|
|
19890
|
-
chatId: callbackData.groupId || callbackData.userId,
|
|
19891
|
-
senderId: callbackData.userId,
|
|
19892
|
-
msgType: callbackData.type,
|
|
19893
|
-
content: JSON.stringify(callbackData.content),
|
|
19894
|
-
timestamp: Date.now()
|
|
19895
|
-
};
|
|
19896
|
-
log22.debug("inbound:frame", { messageId: msg.messageId, eventType: callbackData.eventType });
|
|
19927
|
+
const msg = callbackData.content;
|
|
19928
|
+
log21.debug("inbound:frame", { messageId: msg.messageId, eventType: callbackData.eventType });
|
|
19897
19929
|
if (this.dedup.isDuplicate(msg.messageId)) {
|
|
19898
|
-
|
|
19930
|
+
log21.debug("inbound:dedup-hit", { messageId: msg.messageId });
|
|
19899
19931
|
return;
|
|
19900
19932
|
}
|
|
19901
|
-
|
|
19933
|
+
log21.info("inbound:verified", { messageId: msg.messageId, chatId: msg.chatId });
|
|
19902
19934
|
if (this.messageCallback) {
|
|
19903
19935
|
this.messageCallback(msg);
|
|
19904
19936
|
} else {
|
|
19905
|
-
|
|
19937
|
+
log21.warn("inbound:no-callback", { messageId: msg.messageId });
|
|
19906
19938
|
}
|
|
19907
19939
|
}
|
|
19908
19940
|
};
|
|
19909
19941
|
|
|
19910
19942
|
// src/transport/connection-manager.ts
|
|
19911
|
-
var
|
|
19943
|
+
var log22 = createLogger("transport/connection-manager");
|
|
19912
19944
|
function sleep2(ms) {
|
|
19913
19945
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
19914
19946
|
}
|
|
@@ -19938,7 +19970,7 @@ var ConnectionManager = class {
|
|
|
19938
19970
|
reconnectMaxMs: options?.reconnectMaxMs ?? 6e4,
|
|
19939
19971
|
maxReconnectAttempts: options?.maxReconnectAttempts ?? 10
|
|
19940
19972
|
};
|
|
19941
|
-
|
|
19973
|
+
log22.info("ConnectionManager initialized", this.options);
|
|
19942
19974
|
}
|
|
19943
19975
|
/**
|
|
19944
19976
|
* 启动连接 — 连接 WS + 开始心跳 + 注册重连逻辑
|
|
@@ -19953,33 +19985,29 @@ var ConnectionManager = class {
|
|
|
19953
19985
|
this.running = true;
|
|
19954
19986
|
this.reconnectAttempts = 0;
|
|
19955
19987
|
const token = await tokenFn();
|
|
19956
|
-
const wsUrl = url2.replace(/\/+$/, "") + "/events/stream";
|
|
19957
19988
|
await this.client.connect({
|
|
19958
|
-
url:
|
|
19989
|
+
url: url2,
|
|
19959
19990
|
token,
|
|
19960
|
-
headers: {
|
|
19961
|
-
"Authorization": token,
|
|
19962
|
-
...this.appId ? { "X-App-ID": this.appId } : {}
|
|
19963
|
-
}
|
|
19991
|
+
headers: this.appId ? { "X-App-ID": this.appId } : void 0
|
|
19964
19992
|
});
|
|
19965
19993
|
this.client.on("close", () => {
|
|
19966
19994
|
if (this.running) {
|
|
19967
|
-
|
|
19995
|
+
log22.warn("ws:disconnected, scheduling reconnect");
|
|
19968
19996
|
this.scheduleReconnect();
|
|
19969
19997
|
}
|
|
19970
19998
|
});
|
|
19971
19999
|
this.client.on("error", (err) => {
|
|
19972
|
-
|
|
20000
|
+
log22.error("ws:connection-error", { error: err.message });
|
|
19973
20001
|
});
|
|
19974
20002
|
this.startHeartbeat();
|
|
19975
|
-
|
|
20003
|
+
log22.info("ConnectionManager started \u2713", { url: url2 });
|
|
19976
20004
|
}
|
|
19977
20005
|
/** 启动心跳保活 — 定时检查连接状态 */
|
|
19978
20006
|
startHeartbeat() {
|
|
19979
20007
|
this.stopHeartbeat();
|
|
19980
20008
|
this.heartbeatTimer = setInterval(() => {
|
|
19981
20009
|
if (this.client.readyState !== wrapper_default.OPEN) {
|
|
19982
|
-
|
|
20010
|
+
log22.warn("heartbeat: connection not open, scheduling reconnect");
|
|
19983
20011
|
this.scheduleReconnect();
|
|
19984
20012
|
}
|
|
19985
20013
|
}, this.options.heartbeatIntervalMs);
|
|
@@ -20007,34 +20035,30 @@ var ConnectionManager = class {
|
|
|
20007
20035
|
this.options.reconnectBaseMs * 2 ** this.reconnectAttempts,
|
|
20008
20036
|
this.options.reconnectMaxMs
|
|
20009
20037
|
);
|
|
20010
|
-
|
|
20038
|
+
log22.info("ws:reconnecting", { attempt: this.reconnectAttempts + 1, delayMs: delay });
|
|
20011
20039
|
await sleep2(delay);
|
|
20012
20040
|
if (!this.running) return;
|
|
20013
20041
|
this.reconnectAttempts++;
|
|
20014
20042
|
try {
|
|
20015
20043
|
const token = await this.tokenFn();
|
|
20016
|
-
const wsUrl = this.url.replace(/\/+$/, "") + "/events/stream";
|
|
20017
20044
|
await this.client.connect({
|
|
20018
|
-
url:
|
|
20045
|
+
url: this.url,
|
|
20019
20046
|
token,
|
|
20020
|
-
headers: {
|
|
20021
|
-
"Authorization": token,
|
|
20022
|
-
...this.appId ? { "X-App-ID": this.appId } : {}
|
|
20023
|
-
}
|
|
20047
|
+
headers: this.appId ? { "X-App-ID": this.appId } : void 0
|
|
20024
20048
|
});
|
|
20025
20049
|
this.reconnectAttempts = 0;
|
|
20026
20050
|
this.startHeartbeat();
|
|
20027
|
-
|
|
20051
|
+
log22.info("ws:reconnected", { attempt: this.reconnectAttempts, url: this.url });
|
|
20028
20052
|
return;
|
|
20029
20053
|
} catch (err) {
|
|
20030
|
-
|
|
20054
|
+
log22.warn("ws:reconnect-failed", {
|
|
20031
20055
|
attempt: this.reconnectAttempts,
|
|
20032
20056
|
error: err.message
|
|
20033
20057
|
});
|
|
20034
20058
|
}
|
|
20035
20059
|
}
|
|
20036
20060
|
if (this.running) {
|
|
20037
|
-
|
|
20061
|
+
log22.error("ws:max-reconnect-reached", {
|
|
20038
20062
|
maxAttempts: this.options.maxReconnectAttempts
|
|
20039
20063
|
});
|
|
20040
20064
|
}
|
|
@@ -20044,7 +20068,7 @@ var ConnectionManager = class {
|
|
|
20044
20068
|
this.running = false;
|
|
20045
20069
|
this.stopHeartbeat();
|
|
20046
20070
|
this.client.close(1e3, "shutdown");
|
|
20047
|
-
|
|
20071
|
+
log22.info("ConnectionManager stopped");
|
|
20048
20072
|
}
|
|
20049
20073
|
/** 当前是否已连接 (WebSocket readyState === OPEN) */
|
|
20050
20074
|
get isConnected() {
|
|
@@ -20053,7 +20077,7 @@ var ConnectionManager = class {
|
|
|
20053
20077
|
};
|
|
20054
20078
|
|
|
20055
20079
|
// src/push/cockatoo-client.ts
|
|
20056
|
-
var
|
|
20080
|
+
var log23 = createLogger("push/cockatoo-client");
|
|
20057
20081
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
20058
20082
|
var CockatooPushError = class extends Error {
|
|
20059
20083
|
constructor(status, body, endpoint) {
|
|
@@ -20087,7 +20111,7 @@ var CockatooClient = class {
|
|
|
20087
20111
|
const base = config2.endpoint.replace(/\/+$/, "");
|
|
20088
20112
|
this.pushUrl = `${base}/api/v1/push`;
|
|
20089
20113
|
this.healthUrl = `${base}/health`;
|
|
20090
|
-
|
|
20114
|
+
log23.info("CockatooClient initialized", { endpoint: config2.endpoint });
|
|
20091
20115
|
}
|
|
20092
20116
|
/**
|
|
20093
20117
|
* 推送消息到 Cockatoo 服务。
|
|
@@ -20118,7 +20142,7 @@ var CockatooClient = class {
|
|
|
20118
20142
|
}
|
|
20119
20143
|
throw new CockatooPushError(resp.status, body, this.pushUrl);
|
|
20120
20144
|
}
|
|
20121
|
-
|
|
20145
|
+
log23.debug("push:sent", { type: payload.type });
|
|
20122
20146
|
}
|
|
20123
20147
|
/**
|
|
20124
20148
|
* 执行一次健康检查。
|
|
@@ -20136,11 +20160,11 @@ var CockatooClient = class {
|
|
|
20136
20160
|
});
|
|
20137
20161
|
const isHealthy = resp.ok;
|
|
20138
20162
|
if (!isHealthy) {
|
|
20139
|
-
|
|
20163
|
+
log23.warn("health-check:unhealthy", { status: resp.status });
|
|
20140
20164
|
}
|
|
20141
20165
|
return isHealthy;
|
|
20142
20166
|
} catch (err) {
|
|
20143
|
-
|
|
20167
|
+
log23.warn("health-check:error", { error: err.message });
|
|
20144
20168
|
return false;
|
|
20145
20169
|
}
|
|
20146
20170
|
}
|
|
@@ -20152,7 +20176,7 @@ var CockatooClient = class {
|
|
|
20152
20176
|
this.healthy = await this.healthCheck();
|
|
20153
20177
|
} catch {
|
|
20154
20178
|
this.healthy = false;
|
|
20155
|
-
|
|
20179
|
+
log23.warn("Cockatoo health check failed");
|
|
20156
20180
|
}
|
|
20157
20181
|
}, this.config.healthCheckIntervalMs ?? 6e4);
|
|
20158
20182
|
if (typeof this.healthCheckTimer === "object" && "unref" in this.healthCheckTimer) {
|
|
@@ -20173,7 +20197,7 @@ var CockatooClient = class {
|
|
|
20173
20197
|
};
|
|
20174
20198
|
|
|
20175
20199
|
// src/push/push-queue.ts
|
|
20176
|
-
var
|
|
20200
|
+
var log24 = createLogger("push/push-queue");
|
|
20177
20201
|
var RETRY_BASE_MS2 = 1e3;
|
|
20178
20202
|
var PushQueue = class {
|
|
20179
20203
|
client;
|
|
@@ -20215,7 +20239,7 @@ var PushQueue = class {
|
|
|
20215
20239
|
this.unhealthyRetryMs = config2?.unhealthyRetryMs ?? 5e3;
|
|
20216
20240
|
this.drainOnStop = config2?.drainOnStop ?? false;
|
|
20217
20241
|
this.drainTimeoutMs = config2?.drainTimeoutMs ?? 5e3;
|
|
20218
|
-
|
|
20242
|
+
log24.info("PushQueue initialized", {
|
|
20219
20243
|
maxSize: this.maxSize,
|
|
20220
20244
|
retryAttempts: this.retryAttempts,
|
|
20221
20245
|
processIntervalMs: this.processIntervalMs
|
|
@@ -20226,7 +20250,7 @@ var PushQueue = class {
|
|
|
20226
20250
|
start() {
|
|
20227
20251
|
if (this.running) return;
|
|
20228
20252
|
this.running = true;
|
|
20229
|
-
|
|
20253
|
+
log24.info("PushQueue started");
|
|
20230
20254
|
this.scheduleNext(0);
|
|
20231
20255
|
}
|
|
20232
20256
|
/**
|
|
@@ -20240,10 +20264,10 @@ var PushQueue = class {
|
|
|
20240
20264
|
this.running = false;
|
|
20241
20265
|
this.clearTimer();
|
|
20242
20266
|
if (this.drainOnStop && this.queue.length > 0) {
|
|
20243
|
-
|
|
20267
|
+
log24.info("PushQueue draining", { remaining: this.queue.length });
|
|
20244
20268
|
await this.drain();
|
|
20245
20269
|
}
|
|
20246
|
-
|
|
20270
|
+
log24.info("PushQueue stopped", {
|
|
20247
20271
|
remaining: this.queue.length,
|
|
20248
20272
|
sent: this.sentCount,
|
|
20249
20273
|
dropped: this.droppedCount,
|
|
@@ -20258,13 +20282,13 @@ var PushQueue = class {
|
|
|
20258
20282
|
if (this.queue.length >= this.maxSize) {
|
|
20259
20283
|
const dropped = this.queue.shift();
|
|
20260
20284
|
this.droppedCount++;
|
|
20261
|
-
|
|
20285
|
+
log24.warn("queue:full, dropping oldest", {
|
|
20262
20286
|
droppedType: dropped?.payload.type,
|
|
20263
20287
|
queueSize: this.queue.length
|
|
20264
20288
|
});
|
|
20265
20289
|
}
|
|
20266
20290
|
this.queue.push({ payload, retries: 0, eligibleAt: 0 });
|
|
20267
|
-
|
|
20291
|
+
log24.debug("queue:enqueued", { type: payload.type, queueSize: this.queue.length });
|
|
20268
20292
|
}
|
|
20269
20293
|
/** 当前队列长度 */
|
|
20270
20294
|
get size() {
|
|
@@ -20289,7 +20313,7 @@ var PushQueue = class {
|
|
|
20289
20313
|
this.clearTimer();
|
|
20290
20314
|
this.processTimer = setTimeout(() => {
|
|
20291
20315
|
this.tick().catch((err) => {
|
|
20292
|
-
|
|
20316
|
+
log24.error("tick:unexpected-error", { error: err.message });
|
|
20293
20317
|
if (this.running) {
|
|
20294
20318
|
this.scheduleNext(this.idleIntervalMs);
|
|
20295
20319
|
}
|
|
@@ -20311,7 +20335,7 @@ var PushQueue = class {
|
|
|
20311
20335
|
async tick() {
|
|
20312
20336
|
if (!this.running) return;
|
|
20313
20337
|
if (!this.client.isHealthy) {
|
|
20314
|
-
|
|
20338
|
+
log24.debug("tick:service-unhealthy, pausing");
|
|
20315
20339
|
this.scheduleNext(this.unhealthyRetryMs);
|
|
20316
20340
|
return;
|
|
20317
20341
|
}
|
|
@@ -20331,7 +20355,7 @@ var PushQueue = class {
|
|
|
20331
20355
|
try {
|
|
20332
20356
|
await this.processItem(item);
|
|
20333
20357
|
} catch (err) {
|
|
20334
|
-
|
|
20358
|
+
log24.error("tick:process-unexpected", { error: err.message });
|
|
20335
20359
|
} finally {
|
|
20336
20360
|
this.processing = false;
|
|
20337
20361
|
}
|
|
@@ -20350,14 +20374,14 @@ var PushQueue = class {
|
|
|
20350
20374
|
try {
|
|
20351
20375
|
await this.client.push(item.payload);
|
|
20352
20376
|
this.sentCount++;
|
|
20353
|
-
|
|
20377
|
+
log24.debug("push:success", {
|
|
20354
20378
|
type: item.payload.type,
|
|
20355
20379
|
retries: item.retries
|
|
20356
20380
|
});
|
|
20357
20381
|
} catch (err) {
|
|
20358
20382
|
if (err instanceof CockatooPushError && err.isClientError) {
|
|
20359
20383
|
this.droppedCount++;
|
|
20360
|
-
|
|
20384
|
+
log24.warn("push:4xx-dropped", {
|
|
20361
20385
|
type: item.payload.type,
|
|
20362
20386
|
status: err.status,
|
|
20363
20387
|
retries: item.retries
|
|
@@ -20366,7 +20390,7 @@ var PushQueue = class {
|
|
|
20366
20390
|
}
|
|
20367
20391
|
if (item.retries >= this.retryAttempts) {
|
|
20368
20392
|
this.droppedCount++;
|
|
20369
|
-
|
|
20393
|
+
log24.error("push:max-retries-dropped", {
|
|
20370
20394
|
type: item.payload.type,
|
|
20371
20395
|
retries: item.retries,
|
|
20372
20396
|
error: err.message
|
|
@@ -20379,7 +20403,7 @@ var PushQueue = class {
|
|
|
20379
20403
|
item.eligibleAt = Date.now() + backoffMs;
|
|
20380
20404
|
this.queue.push(item);
|
|
20381
20405
|
const errorDetail = err instanceof CockatooPushError ? `HTTP ${err.status}` : err.message;
|
|
20382
|
-
|
|
20406
|
+
log24.warn("push:retry-enqueued", {
|
|
20383
20407
|
type: item.payload.type,
|
|
20384
20408
|
retries: item.retries,
|
|
20385
20409
|
backoffMs,
|
|
@@ -20403,22 +20427,22 @@ var PushQueue = class {
|
|
|
20403
20427
|
try {
|
|
20404
20428
|
await this.client.push(item.payload);
|
|
20405
20429
|
this.sentCount++;
|
|
20406
|
-
|
|
20430
|
+
log24.debug("drain:sent", { type: item.payload.type });
|
|
20407
20431
|
} catch (err) {
|
|
20408
20432
|
this.droppedCount++;
|
|
20409
|
-
|
|
20433
|
+
log24.warn("drain:dropped", {
|
|
20410
20434
|
type: item.payload.type,
|
|
20411
20435
|
error: err.message
|
|
20412
20436
|
});
|
|
20413
20437
|
}
|
|
20414
20438
|
}
|
|
20415
20439
|
if (this.queue.length > 0) {
|
|
20416
|
-
|
|
20440
|
+
log24.warn("drain:timeout", {
|
|
20417
20441
|
remaining: this.queue.length,
|
|
20418
20442
|
timeoutMs: this.drainTimeoutMs
|
|
20419
20443
|
});
|
|
20420
20444
|
} else {
|
|
20421
|
-
|
|
20445
|
+
log24.info("drain:complete");
|
|
20422
20446
|
}
|
|
20423
20447
|
}
|
|
20424
20448
|
// ── 内部工具 ──
|
|
@@ -20432,32 +20456,43 @@ var PushQueue = class {
|
|
|
20432
20456
|
};
|
|
20433
20457
|
|
|
20434
20458
|
// src/index.ts
|
|
20435
|
-
var
|
|
20459
|
+
var log25 = createLogger("plugin");
|
|
20436
20460
|
async function startPlugin(accountConfig, internalOverrides) {
|
|
20437
20461
|
const config2 = buildPluginConfig(accountConfig, internalOverrides);
|
|
20438
|
-
|
|
20462
|
+
log25.info("Config built \u2713", { pluginId: config2.pluginId });
|
|
20439
20463
|
let cryptoEngine;
|
|
20440
20464
|
if (config2.crypto.enabled) {
|
|
20441
|
-
cryptoEngine = new CryptoEngine(
|
|
20465
|
+
cryptoEngine = new CryptoEngine();
|
|
20442
20466
|
await cryptoEngine.init();
|
|
20443
|
-
|
|
20467
|
+
log25.info("Crypto initialized \u2713");
|
|
20444
20468
|
} else {
|
|
20445
20469
|
cryptoEngine = CryptoEngine.createPassthrough();
|
|
20446
|
-
|
|
20447
|
-
}
|
|
20448
|
-
const
|
|
20449
|
-
|
|
20450
|
-
|
|
20451
|
-
|
|
20470
|
+
log25.info("Crypto passthrough mode \u2713 (disabled by config)");
|
|
20471
|
+
}
|
|
20472
|
+
const sealClient = new SealClient({
|
|
20473
|
+
serverUrl: config2.auth.serverUrl,
|
|
20474
|
+
clientName: config2.auth.clientName,
|
|
20475
|
+
clientId: config2.auth.clientId,
|
|
20476
|
+
clientSecret: config2.auth.clientSecret,
|
|
20477
|
+
scopes: config2.auth.scopes
|
|
20452
20478
|
});
|
|
20453
|
-
const tokenStorePath = (0,
|
|
20479
|
+
const tokenStorePath = (0, import_node_path2.join)((0, import_node_os.homedir)(), TOKEN_STORE_DIR, "tokens");
|
|
20454
20480
|
const tokenStore = new TokenStore(tokenStorePath, cryptoEngine);
|
|
20455
20481
|
const tokenManager = new TokenManager({
|
|
20456
|
-
|
|
20482
|
+
sealClient,
|
|
20457
20483
|
tokenStore,
|
|
20458
|
-
refreshAheadMs: config2.auth.refreshAheadMs
|
|
20484
|
+
refreshAheadMs: config2.auth.refreshAheadMs,
|
|
20485
|
+
autoRegisterParams: config2.auth.autoRegister ? {
|
|
20486
|
+
clientName: config2.auth.clientName,
|
|
20487
|
+
scopes: config2.auth.scopes,
|
|
20488
|
+
tokenSettings: config2.auth.tokenSettings ? {
|
|
20489
|
+
accessTokenTtl: config2.auth.tokenSettings.accessTokenTtl,
|
|
20490
|
+
refreshTokenTtl: config2.auth.tokenSettings.refreshTokenTtl,
|
|
20491
|
+
accessTokenFormat: config2.auth.tokenSettings.accessTokenFormat
|
|
20492
|
+
} : void 0
|
|
20493
|
+
} : void 0
|
|
20459
20494
|
});
|
|
20460
|
-
|
|
20495
|
+
log25.info("Auth initialized \u2713");
|
|
20461
20496
|
let pushQueue = null;
|
|
20462
20497
|
let cockatooClient = null;
|
|
20463
20498
|
if (config2.push?.enabled) {
|
|
@@ -20472,7 +20507,7 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20472
20507
|
});
|
|
20473
20508
|
cockatooClient.startHealthCheck();
|
|
20474
20509
|
pushQueue.start();
|
|
20475
|
-
|
|
20510
|
+
log25.info("Push initialized \u2713");
|
|
20476
20511
|
}
|
|
20477
20512
|
const wsClient = new WSClient();
|
|
20478
20513
|
const dedup = new MessageDedup(config2.transport.dedup);
|
|
@@ -20490,8 +20525,8 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20490
20525
|
reconnectMaxMs: config2.transport.reconnectMaxMs,
|
|
20491
20526
|
maxReconnectAttempts: config2.transport.maxReconnectAttempts
|
|
20492
20527
|
});
|
|
20493
|
-
|
|
20494
|
-
|
|
20528
|
+
log25.info("Transport initialized \u2713");
|
|
20529
|
+
log25.info("Plugin started \u2713");
|
|
20495
20530
|
return {
|
|
20496
20531
|
config: config2,
|
|
20497
20532
|
messagePipe,
|
|
@@ -20499,22 +20534,22 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20499
20534
|
tokenManager,
|
|
20500
20535
|
pushQueue,
|
|
20501
20536
|
shutdown: async () => {
|
|
20502
|
-
|
|
20537
|
+
log25.info("Shutting down...");
|
|
20503
20538
|
await connectionManager.stop();
|
|
20504
20539
|
if (pushQueue) await pushQueue.stop();
|
|
20505
20540
|
if (cockatooClient) cockatooClient.stopHealthCheck();
|
|
20506
20541
|
tokenManager.shutdown();
|
|
20507
|
-
|
|
20542
|
+
log25.info("Shutdown complete \u2713");
|
|
20508
20543
|
}
|
|
20509
20544
|
};
|
|
20510
20545
|
}
|
|
20511
20546
|
var plugin = {
|
|
20512
|
-
id:
|
|
20547
|
+
id: PLUGIN_ID,
|
|
20513
20548
|
name: "\u91CF\u5B50\u5BC6\u4FE1",
|
|
20514
20549
|
description: "Quantum-encrypted IM channel plugin",
|
|
20515
20550
|
register(api) {
|
|
20516
20551
|
api.registerChannel({ plugin: quantumImPlugin });
|
|
20517
|
-
|
|
20552
|
+
log25.info("plugin registered \u2713");
|
|
20518
20553
|
}
|
|
20519
20554
|
};
|
|
20520
20555
|
var index_default = plugin;
|