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