liangzimixin 0.2.5 → 0.2.7
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 +464 -452
- package/dist/index.d.cts +126 -162
- package/dist/setup-entry.cjs +20501 -0
- package/dist/setup-entry.d.cts +14 -0
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -2233,7 +2233,7 @@ var require_websocket = __commonJS({
|
|
|
2233
2233
|
var http = require("http");
|
|
2234
2234
|
var net = require("net");
|
|
2235
2235
|
var tls = require("tls");
|
|
2236
|
-
var { randomBytes:
|
|
2236
|
+
var { randomBytes: randomBytes2, createHash } = require("crypto");
|
|
2237
2237
|
var { Duplex, Readable } = require("stream");
|
|
2238
2238
|
var { URL: URL2 } = require("url");
|
|
2239
2239
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -2763,7 +2763,7 @@ var require_websocket = __commonJS({
|
|
|
2763
2763
|
}
|
|
2764
2764
|
}
|
|
2765
2765
|
const defaultPort = isSecure ? 443 : 80;
|
|
2766
|
-
const key =
|
|
2766
|
+
const key = randomBytes2(16).toString("base64");
|
|
2767
2767
|
const request = isSecure ? https.request : http.request;
|
|
2768
2768
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
2769
2769
|
let perMessageDeflate;
|
|
@@ -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_path3 = require("path");
|
|
3662
3662
|
var import_node_os = require("os");
|
|
3663
3663
|
|
|
3664
3664
|
// node_modules/zod/v4/classic/external.js
|
|
@@ -17454,6 +17454,14 @@ 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"),
|
|
17457
17465
|
/**
|
|
17458
17466
|
* 🟡 Bot 自己的用户 ID — 用于 anti-loop 检查
|
|
17459
17467
|
* 如果不填,需要通过其他 API 在运行时获取。
|
|
@@ -17465,6 +17473,8 @@ var AccountConfigSchema = external_exports.object({
|
|
|
17465
17473
|
quantumAccount: raw["quantum-account"],
|
|
17466
17474
|
quantumAppId: raw["quantum-appId"],
|
|
17467
17475
|
quantumAppSecret: raw["quantum-appSecret"],
|
|
17476
|
+
pin: raw.pin,
|
|
17477
|
+
quantumUrl: raw["quantum-url"],
|
|
17468
17478
|
botUserId: raw.botUserId
|
|
17469
17479
|
}));
|
|
17470
17480
|
var DEFAULT_INTERNAL_CONFIG = {
|
|
@@ -17483,10 +17493,7 @@ var DEFAULT_INTERNAL_CONFIG = {
|
|
|
17483
17493
|
}
|
|
17484
17494
|
},
|
|
17485
17495
|
auth: {
|
|
17486
|
-
serverUrl: process.env.LZMX_AUTH_URL || "https://
|
|
17487
|
-
clientName: "liangzimixin",
|
|
17488
|
-
scopes: ["openid", "im_sdk", "data_operate", "offline_access"],
|
|
17489
|
-
autoRegister: true,
|
|
17496
|
+
serverUrl: process.env.LZMX_AUTH_URL || "https://imtwo.zdxlz.com/open-apis/v1",
|
|
17490
17497
|
refreshAheadMs: 3e5
|
|
17491
17498
|
},
|
|
17492
17499
|
crypto: {
|
|
@@ -17974,7 +17981,7 @@ async function resolveAndUploadMedia(params) {
|
|
|
17974
17981
|
};
|
|
17975
17982
|
}
|
|
17976
17983
|
const msgType = fileType;
|
|
17977
|
-
const contentPayload = fileType === "file" ? {
|
|
17984
|
+
const contentPayload = fileType === "file" ? { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize } : { fileId: uploadResult.fileKey };
|
|
17978
17985
|
await messagePipe.sendMessage({
|
|
17979
17986
|
chatId,
|
|
17980
17987
|
senderId: chatId,
|
|
@@ -17984,7 +17991,7 @@ async function resolveAndUploadMedia(params) {
|
|
|
17984
17991
|
});
|
|
17985
17992
|
log3.info("media:sent", {
|
|
17986
17993
|
chatId,
|
|
17987
|
-
|
|
17994
|
+
fileId: uploadResult.fileKey,
|
|
17988
17995
|
msgType
|
|
17989
17996
|
});
|
|
17990
17997
|
return {
|
|
@@ -18111,20 +18118,20 @@ ${body}` : body || raw.content;
|
|
|
18111
18118
|
}
|
|
18112
18119
|
case "image": {
|
|
18113
18120
|
const c = parsed;
|
|
18114
|
-
fileId = c?.
|
|
18121
|
+
fileId = c?.fileId;
|
|
18115
18122
|
text = c?.altText ?? (fileId ? `` : "[image]");
|
|
18116
18123
|
break;
|
|
18117
18124
|
}
|
|
18118
18125
|
case "file": {
|
|
18119
18126
|
const c = parsed;
|
|
18120
|
-
fileId = c?.
|
|
18121
|
-
fileName = c?.
|
|
18127
|
+
fileId = c?.fileId;
|
|
18128
|
+
fileName = c?.fileName;
|
|
18122
18129
|
text = fileName ? `[File: ${fileName}]` : "[file]";
|
|
18123
18130
|
break;
|
|
18124
18131
|
}
|
|
18125
18132
|
case "voice": {
|
|
18126
18133
|
const c = parsed;
|
|
18127
|
-
fileId = c?.
|
|
18134
|
+
fileId = c?.fileId;
|
|
18128
18135
|
const durationMs = c?.duration;
|
|
18129
18136
|
const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
|
|
18130
18137
|
text = `[Voice${durationStr}]`;
|
|
@@ -18132,7 +18139,7 @@ ${body}` : body || raw.content;
|
|
|
18132
18139
|
}
|
|
18133
18140
|
case "video": {
|
|
18134
18141
|
const c = parsed;
|
|
18135
|
-
fileId = c?.
|
|
18142
|
+
fileId = c?.fileId;
|
|
18136
18143
|
const durationMs = c?.duration;
|
|
18137
18144
|
const durationStr = durationMs != null ? ` ${(durationMs / 1e3).toFixed(1)}s` : "";
|
|
18138
18145
|
text = `[Video${durationStr}]`;
|
|
@@ -18454,7 +18461,7 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
|
|
|
18454
18461
|
title: "\u91CF\u5B50\u5BC6\u4FE1 IM \u63D2\u4EF6\u914D\u7F6E",
|
|
18455
18462
|
description: "\u5BC6\u4FE1 IM Channel \u63D2\u4EF6\uFF0C\u652F\u6301\u91CF\u5B50\u52A0\u5BC6\u7684\u5B89\u5168\u5373\u65F6\u901A\u4FE1\u3002",
|
|
18456
18463
|
type: "object",
|
|
18457
|
-
required: ["appId", "appSecret", "quantum-account", "quantum-appId", "quantum-appSecret"],
|
|
18464
|
+
required: ["appId", "appSecret", "quantum-account", "quantum-appId", "quantum-appSecret", "pin"],
|
|
18458
18465
|
properties: {
|
|
18459
18466
|
appId: {
|
|
18460
18467
|
type: "string",
|
|
@@ -18488,6 +18495,19 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
|
|
|
18488
18495
|
minLength: 1,
|
|
18489
18496
|
format: "password"
|
|
18490
18497
|
},
|
|
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
|
+
},
|
|
18491
18511
|
botUserId: {
|
|
18492
18512
|
type: "string",
|
|
18493
18513
|
title: "Bot \u7528\u6237 ID",
|
|
@@ -18538,14 +18558,20 @@ function resolveAccount(cfg, accountId) {
|
|
|
18538
18558
|
|
|
18539
18559
|
// src/channel/onboarding.ts
|
|
18540
18560
|
var import_plugin_sdk2 = require("openclaw/plugin-sdk");
|
|
18561
|
+
function getChannelSection(cfg) {
|
|
18562
|
+
return cfg.channels?.[CHANNEL_ID];
|
|
18563
|
+
}
|
|
18564
|
+
function getDefaultAccount(cfg) {
|
|
18565
|
+
const section = getChannelSection(cfg);
|
|
18566
|
+
if (!section?.accounts) return void 0;
|
|
18567
|
+
return section.accounts.default;
|
|
18568
|
+
}
|
|
18541
18569
|
function isConfigured(cfg) {
|
|
18542
|
-
const
|
|
18543
|
-
if (!
|
|
18544
|
-
|
|
18545
|
-
if (!defaultAccount) return false;
|
|
18546
|
-
return AccountConfigSchema.safeParse(defaultAccount).success;
|
|
18570
|
+
const account = getDefaultAccount(cfg);
|
|
18571
|
+
if (!account) return false;
|
|
18572
|
+
return AccountConfigSchema.safeParse(account).success;
|
|
18547
18573
|
}
|
|
18548
|
-
var required2 = (v) =>
|
|
18574
|
+
var required2 = (v) => v.trim() ? void 0 : "\u4E0D\u80FD\u4E3A\u7A7A";
|
|
18549
18575
|
var quantumImOnboarding = {
|
|
18550
18576
|
channel: CHANNEL_ID,
|
|
18551
18577
|
async getStatus({ cfg }) {
|
|
@@ -18559,43 +18585,84 @@ var quantumImOnboarding = {
|
|
|
18559
18585
|
};
|
|
18560
18586
|
},
|
|
18561
18587
|
async configure({ cfg, prompter }) {
|
|
18588
|
+
const existing = getDefaultAccount(cfg) ?? {};
|
|
18589
|
+
const alreadyConfigured = isConfigured(cfg);
|
|
18590
|
+
if (alreadyConfigured) {
|
|
18591
|
+
const reconfigure = await prompter.confirm({
|
|
18592
|
+
message: "\u91CF\u5B50\u5BC6\u4FE1\u51ED\u636E\u5DF2\u914D\u7F6E\uFF0C\u662F\u5426\u91CD\u65B0\u914D\u7F6E\uFF1F",
|
|
18593
|
+
initialValue: false
|
|
18594
|
+
});
|
|
18595
|
+
if (!reconfigure) {
|
|
18596
|
+
return { cfg, accountId: import_plugin_sdk2.DEFAULT_ACCOUNT_ID };
|
|
18597
|
+
}
|
|
18598
|
+
}
|
|
18562
18599
|
await prompter.note(
|
|
18563
18600
|
[
|
|
18564
18601
|
"\u8BF7\u51C6\u5907\u4EE5\u4E0B\u4FE1\u606F:",
|
|
18565
18602
|
" 1. \u5E73\u53F0\u7533\u8BF7\u7684 appId \u548C appSecret",
|
|
18566
18603
|
" 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",
|
|
18567
18605
|
"",
|
|
18568
18606
|
"\u5982\u6709\u7591\u95EE\u8BF7\u8054\u7CFB\u5E73\u53F0\u7BA1\u7406\u5458\u83B7\u53D6\u3002"
|
|
18569
18607
|
].join("\n"),
|
|
18570
18608
|
"\u91CF\u5B50\u5BC6\u4FE1 \u2014 \u51ED\u636E\u914D\u7F6E"
|
|
18571
18609
|
);
|
|
18572
|
-
const existing = getChannelConfig(cfg)?.accounts?.default ?? {};
|
|
18573
18610
|
const appId = await prompter.text({
|
|
18574
18611
|
message: "\u5E94\u7528 ID (appId)",
|
|
18575
|
-
initialValue: existing.appId,
|
|
18612
|
+
initialValue: existing.appId ?? void 0,
|
|
18576
18613
|
validate: required2
|
|
18577
18614
|
});
|
|
18578
18615
|
const appSecret = await prompter.text({
|
|
18579
18616
|
message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
|
|
18580
|
-
initialValue: existing.appSecret,
|
|
18617
|
+
initialValue: existing.appSecret ?? void 0,
|
|
18581
18618
|
validate: required2
|
|
18582
18619
|
});
|
|
18583
18620
|
const quantumAccount = await prompter.text({
|
|
18584
18621
|
message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6 (quantum-account)",
|
|
18585
18622
|
placeholder: "\u5982: 17100001111",
|
|
18586
|
-
initialValue: existing["quantum-account"],
|
|
18623
|
+
initialValue: existing["quantum-account"] ?? void 0,
|
|
18587
18624
|
validate: required2
|
|
18588
18625
|
});
|
|
18589
18626
|
const quantumAppId = await prompter.text({
|
|
18590
18627
|
message: "\u91CF\u5B50\u670D\u52A1 ID (quantum-appId)",
|
|
18591
|
-
initialValue: existing["quantum-appId"],
|
|
18628
|
+
initialValue: existing["quantum-appId"] ?? void 0,
|
|
18592
18629
|
validate: required2
|
|
18593
18630
|
});
|
|
18594
18631
|
const quantumAppSecret = await prompter.text({
|
|
18595
18632
|
message: "\u91CF\u5B50\u670D\u52A1\u5BC6\u94A5 (quantum-appSecret)",
|
|
18596
|
-
initialValue: existing["quantum-appSecret"],
|
|
18633
|
+
initialValue: existing["quantum-appSecret"] ?? void 0,
|
|
18597
18634
|
validate: required2
|
|
18598
18635
|
});
|
|
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
|
+
}
|
|
18599
18666
|
const channels = cfg.channels ?? {};
|
|
18600
18667
|
const channelCfg = channels[CHANNEL_ID] ?? {};
|
|
18601
18668
|
const accounts = channelCfg.accounts ?? {};
|
|
@@ -18605,6 +18672,7 @@ var quantumImOnboarding = {
|
|
|
18605
18672
|
...channels,
|
|
18606
18673
|
[CHANNEL_ID]: {
|
|
18607
18674
|
...channelCfg,
|
|
18675
|
+
enabled: true,
|
|
18608
18676
|
accounts: {
|
|
18609
18677
|
...accounts,
|
|
18610
18678
|
default: {
|
|
@@ -18612,7 +18680,9 @@ var quantumImOnboarding = {
|
|
|
18612
18680
|
appSecret: appSecret.trim(),
|
|
18613
18681
|
"quantum-account": quantumAccount.trim(),
|
|
18614
18682
|
"quantum-appId": quantumAppId.trim(),
|
|
18615
|
-
"quantum-appSecret": quantumAppSecret.trim()
|
|
18683
|
+
"quantum-appSecret": quantumAppSecret.trim(),
|
|
18684
|
+
pin: pin.trim(),
|
|
18685
|
+
"quantum-url": quantumUrl.trim()
|
|
18616
18686
|
}
|
|
18617
18687
|
}
|
|
18618
18688
|
}
|
|
@@ -18621,19 +18691,16 @@ var quantumImOnboarding = {
|
|
|
18621
18691
|
await prompter.note("\u51ED\u636E\u5DF2\u4FDD\u5B58\uFF0C\u91CD\u542F gateway \u540E\u751F\u6548\u3002", "\u2713 \u914D\u7F6E\u5B8C\u6210");
|
|
18622
18692
|
return { cfg: next, accountId: import_plugin_sdk2.DEFAULT_ACCOUNT_ID };
|
|
18623
18693
|
},
|
|
18624
|
-
disable(cfg) {
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
...cfg,
|
|
18628
|
-
|
|
18629
|
-
...channels,
|
|
18630
|
-
|
|
18631
|
-
...channels[CHANNEL_ID] ?? {},
|
|
18632
|
-
enabled: false
|
|
18633
|
-
}
|
|
18694
|
+
disable: (cfg) => ({
|
|
18695
|
+
...cfg,
|
|
18696
|
+
channels: {
|
|
18697
|
+
...cfg.channels,
|
|
18698
|
+
[CHANNEL_ID]: {
|
|
18699
|
+
...cfg.channels?.[CHANNEL_ID] ?? {},
|
|
18700
|
+
enabled: false
|
|
18634
18701
|
}
|
|
18635
|
-
}
|
|
18636
|
-
}
|
|
18702
|
+
}
|
|
18703
|
+
})
|
|
18637
18704
|
};
|
|
18638
18705
|
|
|
18639
18706
|
// src/channel/plugin.ts
|
|
@@ -18798,104 +18865,83 @@ var quantumImPlugin = {
|
|
|
18798
18865
|
}
|
|
18799
18866
|
};
|
|
18800
18867
|
|
|
18801
|
-
// src/crypto/quantun-plug-
|
|
18802
|
-
var
|
|
18803
|
-
var
|
|
18804
|
-
var
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
* 初始化 Mock SDK
|
|
18811
|
-
* 幂等操作 — 重复调用不会报错
|
|
18812
|
-
*/
|
|
18813
|
-
async init() {
|
|
18814
|
-
if (initialized) return;
|
|
18815
|
-
initialized = true;
|
|
18816
|
-
log14.warn("\u26A0\uFE0F MOCK MODE \u2014 \u4F7F\u7528 AES-256-GCM \u6A21\u62DF\u91CF\u5B50\u52A0\u5BC6");
|
|
18817
|
-
},
|
|
18818
|
-
/**
|
|
18819
|
-
* 加密明文 → 密文 + 密钥标识
|
|
18820
|
-
*
|
|
18821
|
-
* 内部流程:
|
|
18822
|
-
* 1. 生成 12 字节随机 IV (确保每次加密结果不同)
|
|
18823
|
-
* 2. AES-256-GCM 加密明文
|
|
18824
|
-
* 3. 获取 16 字节 AuthTag (认证标签,防篡改)
|
|
18825
|
-
* 4. 拼接 [IV(12) | Tag(16) | Ciphertext] → Base64 编码
|
|
18826
|
-
*/
|
|
18827
|
-
async encrypt(plaintext, mode) {
|
|
18828
|
-
if (!initialized) {
|
|
18829
|
-
throw new Error("quantunPlug not initialized \u2014 call init() first");
|
|
18830
|
-
}
|
|
18831
|
-
const iv = (0, import_node_crypto.randomBytes)(12);
|
|
18832
|
-
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", MOCK_KEY, iv);
|
|
18833
|
-
const encrypted = Buffer.concat([
|
|
18834
|
-
cipher.update(plaintext, "utf-8"),
|
|
18835
|
-
cipher.final()
|
|
18836
|
-
]);
|
|
18837
|
-
const tag = cipher.getAuthTag();
|
|
18838
|
-
const combined = Buffer.concat([iv, tag, encrypted]);
|
|
18839
|
-
return {
|
|
18840
|
-
ciphertext: combined.toString("base64"),
|
|
18841
|
-
keyId: MOCK_KEY_ID
|
|
18842
|
-
};
|
|
18843
|
-
},
|
|
18844
|
-
/**
|
|
18845
|
-
* 解密密文 → 明文
|
|
18846
|
-
*
|
|
18847
|
-
* 内部流程:
|
|
18848
|
-
* 1. Base64 解码
|
|
18849
|
-
* 2. 拆分 IV(12) + Tag(16) + Ciphertext
|
|
18850
|
-
* 3. AES-256-GCM 解密 + AuthTag 验证
|
|
18851
|
-
* 4. 返回 UTF-8 明文
|
|
18852
|
-
*
|
|
18853
|
-
* @throws AuthTag 验证失败时抛出异常 (数据被篡改)
|
|
18854
|
-
*/
|
|
18855
|
-
async decrypt(ciphertext, keyId, mode) {
|
|
18856
|
-
if (!initialized) {
|
|
18857
|
-
throw new Error("quantunPlug not initialized \u2014 call init() first");
|
|
18858
|
-
}
|
|
18859
|
-
const combined = Buffer.from(ciphertext, "base64");
|
|
18860
|
-
const iv = combined.subarray(0, 12);
|
|
18861
|
-
const tag = combined.subarray(12, 28);
|
|
18862
|
-
const encrypted = combined.subarray(28);
|
|
18863
|
-
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", MOCK_KEY, iv);
|
|
18864
|
-
decipher.setAuthTag(tag);
|
|
18865
|
-
const decrypted = Buffer.concat([
|
|
18866
|
-
decipher.update(encrypted),
|
|
18867
|
-
decipher.final()
|
|
18868
|
-
// 此处 Tag 验证失败会抛出异常
|
|
18869
|
-
]);
|
|
18870
|
-
return {
|
|
18871
|
-
plaintext: decrypted.toString("utf-8")
|
|
18872
|
-
};
|
|
18873
|
-
}
|
|
18868
|
+
// src/crypto/quantun-plug-adapter.ts
|
|
18869
|
+
var import_node_module = require("module");
|
|
18870
|
+
var import_meta = {};
|
|
18871
|
+
var log14 = createLogger("crypto/quantun-plug-adapter");
|
|
18872
|
+
var SM4_MODE = {
|
|
18873
|
+
ECB_NOPADDING: 1,
|
|
18874
|
+
ECB_PKCS7PADDING: 2,
|
|
18875
|
+
CBC_NOPADDING: 3,
|
|
18876
|
+
CBC_PKCS7PADDING: 4
|
|
18874
18877
|
};
|
|
18875
|
-
var
|
|
18878
|
+
var DEFAULT_ENCRYPT_MODE = SM4_MODE.CBC_PKCS7PADDING;
|
|
18879
|
+
var require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
18880
|
+
var quantunPlug = null;
|
|
18881
|
+
try {
|
|
18882
|
+
quantunPlug = require2("./sdk/index.min.cjs");
|
|
18883
|
+
log14.info("Quantum SDK loaded from sdk/index.min.cjs");
|
|
18884
|
+
} catch (err) {
|
|
18885
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18886
|
+
log14.warn("Quantum SDK missing, only passthrough mode will work:", msg);
|
|
18887
|
+
}
|
|
18888
|
+
var quantun_plug_adapter_default = quantunPlug;
|
|
18889
|
+
|
|
18890
|
+
// src/crypto/quantum-config-writer.ts
|
|
18891
|
+
var import_node_fs = require("fs");
|
|
18892
|
+
var import_node_path = require("path");
|
|
18893
|
+
var import_node_url = require("url");
|
|
18894
|
+
var import_meta2 = {};
|
|
18895
|
+
var log15 = createLogger("crypto/quantum-config-writer");
|
|
18896
|
+
var QUANTUM_JSON_PATH = (0, import_node_path.join)(
|
|
18897
|
+
(0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta2.url)),
|
|
18898
|
+
"sdk",
|
|
18899
|
+
"quantum.json"
|
|
18900
|
+
);
|
|
18901
|
+
function writeQuantumConfig(credentials) {
|
|
18902
|
+
const sdkConfig = {
|
|
18903
|
+
account: credentials.quantumAccount,
|
|
18904
|
+
appID: credentials.quantumAppId,
|
|
18905
|
+
pin: credentials.pin,
|
|
18906
|
+
authCode: credentials.quantumAppSecret,
|
|
18907
|
+
url: credentials.quantumUrl
|
|
18908
|
+
};
|
|
18909
|
+
(0, import_node_fs.writeFileSync)(QUANTUM_JSON_PATH, JSON.stringify(sdkConfig, null, 2), "utf-8");
|
|
18910
|
+
log15.info("quantum.json written to", QUANTUM_JSON_PATH);
|
|
18911
|
+
}
|
|
18876
18912
|
|
|
18877
18913
|
// src/crypto/crypto-engine.ts
|
|
18878
|
-
var
|
|
18914
|
+
var log16 = createLogger("crypto/crypto-engine");
|
|
18879
18915
|
var PASSTHROUGH_KEY_ID = "passthrough";
|
|
18916
|
+
var FILE_CHUNK_SIZE = 10 * 1024 * 1024;
|
|
18917
|
+
var FILE_DECRYPT_CHUNK_SIZE = FILE_CHUNK_SIZE + 16;
|
|
18880
18918
|
var CryptoEngine = class _CryptoEngine {
|
|
18881
|
-
/** 底层量子加密 SDK
|
|
18919
|
+
/** 底层量子加密 SDK 实例, 透传模式下为 null */
|
|
18882
18920
|
plug;
|
|
18883
18921
|
/** SDK 是否已完成初始化 */
|
|
18884
|
-
initialized
|
|
18922
|
+
initialized;
|
|
18885
18923
|
/** 是否为透传模式 (不加密, 明文直接透传) */
|
|
18886
18924
|
passthrough;
|
|
18925
|
+
/** 用户凭据 — 用于生成 quantum.json (透传模式下为 undefined) */
|
|
18926
|
+
credentials;
|
|
18887
18927
|
/**
|
|
18888
|
-
* 构造加密引擎
|
|
18928
|
+
* 构造加密引擎
|
|
18889
18929
|
*
|
|
18890
|
-
*
|
|
18891
|
-
*
|
|
18930
|
+
* @param opts - 构造选项
|
|
18931
|
+
* @param opts.passthrough - true 为透传模式 (不加密); false 为加密模式 (默认)
|
|
18932
|
+
* @param opts.credentials - 用户凭据 (加密模式必需, 用于生成 quantum.json)
|
|
18892
18933
|
*
|
|
18893
|
-
*
|
|
18934
|
+
* 请使用静态工厂方法:
|
|
18935
|
+
* - new CryptoEngine({ credentials }) → 加密模式, 使用量子 SDK
|
|
18936
|
+
* - CryptoEngine.createPassthrough() → 透传模式, 无需 init()
|
|
18894
18937
|
*/
|
|
18895
|
-
constructor() {
|
|
18896
|
-
|
|
18897
|
-
this.passthrough =
|
|
18898
|
-
|
|
18938
|
+
constructor(opts = {}) {
|
|
18939
|
+
const { passthrough = false, credentials } = opts;
|
|
18940
|
+
this.passthrough = passthrough;
|
|
18941
|
+
this.credentials = credentials;
|
|
18942
|
+
this.plug = passthrough ? null : quantun_plug_adapter_default;
|
|
18943
|
+
this.initialized = passthrough;
|
|
18944
|
+
log16.info(`CryptoEngine created (${passthrough ? "passthrough mode \u2014 crypto disabled" : "quantum SDK mode"})`);
|
|
18899
18945
|
}
|
|
18900
18946
|
/**
|
|
18901
18947
|
* 创建透传模式的加密引擎 (静态工厂方法)
|
|
@@ -18909,17 +18955,12 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18909
18955
|
* 上层模块 (TokenStore, MessagePipe) 无需感知加密是否开启。
|
|
18910
18956
|
*/
|
|
18911
18957
|
static createPassthrough() {
|
|
18912
|
-
|
|
18913
|
-
instance.plug = null;
|
|
18914
|
-
instance.passthrough = true;
|
|
18915
|
-
instance.initialized = true;
|
|
18916
|
-
log15.info("CryptoEngine created (passthrough mode \u2014 crypto disabled)");
|
|
18917
|
-
return instance;
|
|
18958
|
+
return new _CryptoEngine({ passthrough: true });
|
|
18918
18959
|
}
|
|
18919
18960
|
/**
|
|
18920
18961
|
* 初始化加密引擎
|
|
18921
18962
|
*
|
|
18922
|
-
* 加密模式: 调用 SDK 的 init()
|
|
18963
|
+
* 加密模式: 调用 SDK 的 init() 方法完成密服入网、密钥协商等初始化操作。
|
|
18923
18964
|
* 透传模式: 空操作 (already initialized)。
|
|
18924
18965
|
* 幂等操作 — 重复调用安全。
|
|
18925
18966
|
*/
|
|
@@ -18929,29 +18970,37 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18929
18970
|
this.initialized = true;
|
|
18930
18971
|
return;
|
|
18931
18972
|
}
|
|
18973
|
+
if (!this.plug) {
|
|
18974
|
+
throw new Error("Quantum encryption SDK is missing. Cannot initialize in encryption mode (check dist/index.min.cjs).");
|
|
18975
|
+
}
|
|
18976
|
+
if (this.credentials) {
|
|
18977
|
+
writeQuantumConfig(this.credentials);
|
|
18978
|
+
}
|
|
18932
18979
|
await this.plug.init();
|
|
18933
18980
|
this.initialized = true;
|
|
18934
|
-
|
|
18981
|
+
log16.info("CryptoEngine initialized \u2713");
|
|
18935
18982
|
}
|
|
18936
18983
|
/**
|
|
18937
18984
|
* 加密明文
|
|
18938
18985
|
*
|
|
18939
|
-
* 加密模式: 调用底层 SDK
|
|
18986
|
+
* 加密模式: 调用底层 SDK 加密 (SM4_CBC_PKCS7PADDING), 返回密文 + keyId
|
|
18940
18987
|
* 透传模式: 直接返回明文作为 "密文", keyId = 'passthrough'
|
|
18941
18988
|
*
|
|
18989
|
+
* 注意: SDK 返回驼峰 cipherText, 此方法对外统一为小写 ciphertext
|
|
18990
|
+
*
|
|
18942
18991
|
* @param plaintext - 要加密的明文字符串
|
|
18943
18992
|
* @returns { ciphertext: 密文字符串, keyId: 密钥标识 }
|
|
18944
18993
|
* @throws 加密模式下未初始化时抛出错误
|
|
18945
18994
|
*/
|
|
18946
18995
|
async encrypt(plaintext) {
|
|
18947
|
-
this.ensureInitialized();
|
|
18948
18996
|
if (this.passthrough) {
|
|
18949
|
-
|
|
18997
|
+
log16.debug("Encrypt (passthrough)", { length: plaintext.length });
|
|
18950
18998
|
return { ciphertext: plaintext, keyId: PASSTHROUGH_KEY_ID };
|
|
18951
18999
|
}
|
|
18952
|
-
const
|
|
18953
|
-
|
|
18954
|
-
|
|
19000
|
+
const plug = this.requirePlug();
|
|
19001
|
+
const result = await plug.encrypt(plaintext, DEFAULT_ENCRYPT_MODE);
|
|
19002
|
+
log16.debug("Encrypted", { length: plaintext.length, keyId: result.keyId });
|
|
19003
|
+
return { ciphertext: result.cipherText, keyId: result.keyId };
|
|
18955
19004
|
}
|
|
18956
19005
|
/**
|
|
18957
19006
|
* 解密密文
|
|
@@ -18965,14 +19014,100 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18965
19014
|
* @throws 加密模式下未初始化、密文损坏或 keyId 不匹配时抛出错误
|
|
18966
19015
|
*/
|
|
18967
19016
|
async decrypt(ciphertext, keyId) {
|
|
18968
|
-
this.ensureInitialized();
|
|
18969
19017
|
if (this.passthrough) {
|
|
18970
|
-
|
|
19018
|
+
log16.debug("Decrypt (passthrough)", { keyId });
|
|
18971
19019
|
return ciphertext;
|
|
18972
19020
|
}
|
|
18973
|
-
const
|
|
18974
|
-
|
|
18975
|
-
|
|
19021
|
+
const plug = this.requirePlug();
|
|
19022
|
+
const result = await plug.decrypt(ciphertext, keyId, DEFAULT_ENCRYPT_MODE);
|
|
19023
|
+
log16.debug("Decrypted", { keyId });
|
|
19024
|
+
return result.plainText;
|
|
19025
|
+
}
|
|
19026
|
+
/**
|
|
19027
|
+
* 加密文件数据
|
|
19028
|
+
*
|
|
19029
|
+
* 自动处理 10MB 分片编排:
|
|
19030
|
+
* - ≤10MB: 单次调用 SDK encryptFile
|
|
19031
|
+
* - >10MB: 按 10MB 分片, 首片无需传密钥参数, 后续片传入前一片返回的 { keyId, sessionKey, fillKey }
|
|
19032
|
+
*
|
|
19033
|
+
* 透传模式: 直接返回原数据
|
|
19034
|
+
*
|
|
19035
|
+
* @param fileData - 文件数据 Buffer
|
|
19036
|
+
* @returns { fileBuffer: 加密后的文件 Buffer, keyId: 密钥标识 }
|
|
19037
|
+
*/
|
|
19038
|
+
async encryptFile(fileData) {
|
|
19039
|
+
if (this.passthrough) {
|
|
19040
|
+
log16.debug("EncryptFile (passthrough)", { size: fileData.length });
|
|
19041
|
+
return { fileBuffer: fileData, keyId: PASSTHROUGH_KEY_ID };
|
|
19042
|
+
}
|
|
19043
|
+
if (fileData.length === 0) {
|
|
19044
|
+
return { fileBuffer: Buffer.alloc(0), keyId: PASSTHROUGH_KEY_ID };
|
|
19045
|
+
}
|
|
19046
|
+
const chunks = [];
|
|
19047
|
+
const total = Math.ceil(fileData.length / FILE_CHUNK_SIZE);
|
|
19048
|
+
let keyId;
|
|
19049
|
+
let sessionKey;
|
|
19050
|
+
let fillKey;
|
|
19051
|
+
const plug = this.requirePlug();
|
|
19052
|
+
log16.debug("EncryptFile", { size: fileData.length, chunks: total });
|
|
19053
|
+
for (let i = 0; i < total; i++) {
|
|
19054
|
+
const start = i * FILE_CHUNK_SIZE;
|
|
19055
|
+
const end = Math.min(start + FILE_CHUNK_SIZE, fileData.length);
|
|
19056
|
+
const chunk = fileData.subarray(start, end);
|
|
19057
|
+
const result = await plug.encryptFile(chunk, DEFAULT_ENCRYPT_MODE, {
|
|
19058
|
+
keyId,
|
|
19059
|
+
sessionKey,
|
|
19060
|
+
fillKey
|
|
19061
|
+
});
|
|
19062
|
+
chunks.push(result.fileBuffer);
|
|
19063
|
+
keyId = result.keyId;
|
|
19064
|
+
sessionKey = result.sessionKey;
|
|
19065
|
+
fillKey = result.fillKey;
|
|
19066
|
+
}
|
|
19067
|
+
log16.debug("EncryptFile done", { keyId, chunks: total });
|
|
19068
|
+
return { fileBuffer: Buffer.concat(chunks), keyId: keyId ?? "" };
|
|
19069
|
+
}
|
|
19070
|
+
/**
|
|
19071
|
+
* 解密文件数据
|
|
19072
|
+
*
|
|
19073
|
+
* 自动处理 (10MB + 16) 分片编排:
|
|
19074
|
+
* - ≤(10MB+16): 单次调用 SDK decryptFile
|
|
19075
|
+
* - >(10MB+16): 按 (10MB+16) 分片, 首片传 keyId, 后续片传入前一片返回的 { sessionKey, fillKey }
|
|
19076
|
+
*
|
|
19077
|
+
* 透传模式: 直接返回原数据
|
|
19078
|
+
*
|
|
19079
|
+
* @param fileData - 加密后的文件 Buffer
|
|
19080
|
+
* @param keyId - 加密时返回的密钥标识
|
|
19081
|
+
* @returns 解密后的文件 Buffer
|
|
19082
|
+
*/
|
|
19083
|
+
async decryptFile(fileData, keyId) {
|
|
19084
|
+
if (this.passthrough) {
|
|
19085
|
+
log16.debug("DecryptFile (passthrough)", { keyId });
|
|
19086
|
+
return fileData;
|
|
19087
|
+
}
|
|
19088
|
+
if (fileData.length === 0) {
|
|
19089
|
+
return Buffer.alloc(0);
|
|
19090
|
+
}
|
|
19091
|
+
const plug = this.requirePlug();
|
|
19092
|
+
const chunks = [];
|
|
19093
|
+
const total = Math.ceil(fileData.length / FILE_DECRYPT_CHUNK_SIZE);
|
|
19094
|
+
let sessionKey;
|
|
19095
|
+
let fillKey;
|
|
19096
|
+
log16.debug("DecryptFile", { size: fileData.length, chunks: total, keyId });
|
|
19097
|
+
for (let i = 0; i < total; i++) {
|
|
19098
|
+
const start = i * FILE_DECRYPT_CHUNK_SIZE;
|
|
19099
|
+
const end = Math.min(start + FILE_DECRYPT_CHUNK_SIZE, fileData.length);
|
|
19100
|
+
const chunk = fileData.subarray(start, end);
|
|
19101
|
+
const result = await plug.decryptFile(chunk, keyId, DEFAULT_ENCRYPT_MODE, {
|
|
19102
|
+
sessionKey,
|
|
19103
|
+
fillKey
|
|
19104
|
+
});
|
|
19105
|
+
chunks.push(result.fileBuffer);
|
|
19106
|
+
sessionKey = result.sessionKey;
|
|
19107
|
+
fillKey = result.fillKey;
|
|
19108
|
+
}
|
|
19109
|
+
log16.debug("DecryptFile done", { keyId, chunks: total });
|
|
19110
|
+
return Buffer.concat(chunks);
|
|
18976
19111
|
}
|
|
18977
19112
|
/**
|
|
18978
19113
|
* 查询当前是否为透传模式
|
|
@@ -18982,25 +19117,26 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18982
19117
|
return this.passthrough;
|
|
18983
19118
|
}
|
|
18984
19119
|
/**
|
|
18985
|
-
*
|
|
18986
|
-
*
|
|
19120
|
+
* 确保引擎已初始化,同时返回非空 plug 实例
|
|
19121
|
+
* (通过 init() 守卫保证:加密模式下 init() 成功后 plug 一定非 null)
|
|
18987
19122
|
*/
|
|
18988
|
-
|
|
19123
|
+
requirePlug() {
|
|
18989
19124
|
if (!this.initialized) {
|
|
18990
19125
|
throw new Error("CryptoEngine not initialized \u2014 call init() first");
|
|
18991
19126
|
}
|
|
19127
|
+
return this.plug;
|
|
18992
19128
|
}
|
|
18993
19129
|
};
|
|
18994
19130
|
|
|
18995
19131
|
// src/auth/oauth-client.ts
|
|
18996
|
-
var
|
|
18997
|
-
var
|
|
19132
|
+
var log17 = createLogger("auth/oauth-client");
|
|
19133
|
+
var ApiError = class extends Error {
|
|
18998
19134
|
constructor(status, body, endpoint) {
|
|
18999
|
-
super(`
|
|
19135
|
+
super(`API error: ${status} on ${endpoint}`);
|
|
19000
19136
|
this.status = status;
|
|
19001
19137
|
this.body = body;
|
|
19002
19138
|
this.endpoint = endpoint;
|
|
19003
|
-
this.name = "
|
|
19139
|
+
this.name = "ApiError";
|
|
19004
19140
|
}
|
|
19005
19141
|
};
|
|
19006
19142
|
var TokenAcquireError = class extends Error {
|
|
@@ -19017,28 +19153,21 @@ function sleep(ms) {
|
|
|
19017
19153
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
19018
19154
|
}
|
|
19019
19155
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
19020
|
-
var
|
|
19156
|
+
var GRANT_TYPE = "client_credentials";
|
|
19157
|
+
var SCOPE = "client_credentials refresh_token";
|
|
19158
|
+
var OAuthClient = class {
|
|
19021
19159
|
baseUrl;
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
clientName;
|
|
19025
|
-
scopes;
|
|
19160
|
+
appId;
|
|
19161
|
+
appSecret;
|
|
19026
19162
|
constructor(config2) {
|
|
19027
|
-
this.baseUrl = config2.
|
|
19028
|
-
this.
|
|
19029
|
-
this.
|
|
19030
|
-
this.
|
|
19031
|
-
this.scopes = config2.scopes;
|
|
19032
|
-
log16.info("SealClient initialized", { serverUrl: this.baseUrl, clientName: config2.clientName });
|
|
19163
|
+
this.baseUrl = config2.baseUrl.replace(/\/+$/, "");
|
|
19164
|
+
this.appId = config2.appId;
|
|
19165
|
+
this.appSecret = config2.appSecret;
|
|
19166
|
+
log17.info("OAuthClient initialized", { baseUrl: this.baseUrl, appId: sanitize(this.appId) });
|
|
19033
19167
|
}
|
|
19034
19168
|
// ── 通用 HTTP 请求 ──
|
|
19035
19169
|
/**
|
|
19036
19170
|
* 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
|
|
19037
|
-
* @param method - HTTP 方法
|
|
19038
|
-
* @param path - 请求路径 (如 '/seal/client/register')
|
|
19039
|
-
* @param options - 请求选项
|
|
19040
|
-
* @returns 解析后的 JSON 响应
|
|
19041
|
-
* @throws SealApiError — HTTP 非 2xx 响应
|
|
19042
19171
|
*/
|
|
19043
19172
|
async request(method, path2, options = {}) {
|
|
19044
19173
|
const url2 = `${this.baseUrl}${path2}`;
|
|
@@ -19054,7 +19183,7 @@ var SealClient = class {
|
|
|
19054
19183
|
bodyStr = JSON.stringify(options.body);
|
|
19055
19184
|
}
|
|
19056
19185
|
}
|
|
19057
|
-
|
|
19186
|
+
log17.debug("HTTP request", { method, url: url2 });
|
|
19058
19187
|
const response = await fetch(url2, {
|
|
19059
19188
|
method,
|
|
19060
19189
|
headers,
|
|
@@ -19069,110 +19198,39 @@ var SealClient = class {
|
|
|
19069
19198
|
} catch {
|
|
19070
19199
|
parsedBody = responseBody;
|
|
19071
19200
|
}
|
|
19072
|
-
|
|
19073
|
-
throw new
|
|
19201
|
+
log17.error("HTTP error", { method, url: url2, status: response.status });
|
|
19202
|
+
throw new ApiError(response.status, parsedBody, path2);
|
|
19074
19203
|
}
|
|
19075
|
-
|
|
19204
|
+
log17.debug("HTTP response", { method, url: url2, status: response.status });
|
|
19076
19205
|
try {
|
|
19077
19206
|
return JSON.parse(responseBody);
|
|
19078
19207
|
} catch {
|
|
19079
|
-
throw new
|
|
19208
|
+
throw new ApiError(response.status, responseBody, path2);
|
|
19080
19209
|
}
|
|
19081
19210
|
}
|
|
19082
19211
|
// ── 公共方法 ──
|
|
19083
|
-
/** 判断是否已注册 (有 clientId + clientSecret) */
|
|
19084
|
-
isRegistered() {
|
|
19085
|
-
return !!(this.clientId && this.clientSecret);
|
|
19086
|
-
}
|
|
19087
|
-
/** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
|
|
19088
|
-
setCredentials(clientId, clientSecret) {
|
|
19089
|
-
this.clientId = clientId;
|
|
19090
|
-
this.clientSecret = clientSecret;
|
|
19091
|
-
log16.info("Credentials updated", { clientId: sanitize(clientId) });
|
|
19092
|
-
}
|
|
19093
19212
|
/**
|
|
19094
|
-
*
|
|
19213
|
+
* 获取访问令牌 → POST /auth/token (client_credentials)
|
|
19095
19214
|
*
|
|
19096
|
-
*
|
|
19097
|
-
*
|
|
19098
|
-
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
client_name: params.clientName,
|
|
19102
|
-
scopes: params.scopes,
|
|
19103
|
-
authentication_methods: params.authMethods ?? ["client_secret_post"],
|
|
19104
|
-
grant_types: params.grantTypes ?? ["authorization_code", "client_credentials", "refresh_token"],
|
|
19105
|
-
redirect_uris: params.redirectUris ?? ["https://placeholder.local"],
|
|
19106
|
-
logout_redirect_uris: [],
|
|
19107
|
-
client_settings: {
|
|
19108
|
-
"settings.client.require-authorization-consent": true
|
|
19109
|
-
}
|
|
19110
|
-
};
|
|
19111
|
-
if (params.tokenSettings) {
|
|
19112
|
-
const ts = {};
|
|
19113
|
-
if (params.tokenSettings.accessTokenTtl) {
|
|
19114
|
-
ts["settings.token.access-token-time-to-live"] = params.tokenSettings.accessTokenTtl;
|
|
19115
|
-
}
|
|
19116
|
-
if (params.tokenSettings.refreshTokenTtl) {
|
|
19117
|
-
ts["settings.token.refresh-token-time-to-live"] = params.tokenSettings.refreshTokenTtl;
|
|
19118
|
-
}
|
|
19119
|
-
if (params.tokenSettings.accessTokenFormat) {
|
|
19120
|
-
ts["settings.token.access-token-format"] = params.tokenSettings.accessTokenFormat;
|
|
19121
|
-
}
|
|
19122
|
-
if (params.tokenSettings.reuseRefreshTokens !== void 0) {
|
|
19123
|
-
ts["settings.token.reuse-refresh-tokens"] = params.tokenSettings.reuseRefreshTokens;
|
|
19124
|
-
}
|
|
19125
|
-
ts["settings.token.authorization-code-time-to-live"] = "300";
|
|
19126
|
-
body.token_settings = ts;
|
|
19127
|
-
}
|
|
19128
|
-
log16.info("Registering OAuth client", { clientName: params.clientName });
|
|
19129
|
-
const response = await this.request(
|
|
19130
|
-
"POST",
|
|
19131
|
-
"/seal/client/register",
|
|
19132
|
-
{ body, contentType: "json" }
|
|
19133
|
-
);
|
|
19134
|
-
const registration = {
|
|
19135
|
-
clientId: response.client_id,
|
|
19136
|
-
clientSecret: response.client_secret,
|
|
19137
|
-
clientSecretExpiresAt: response.client_secret_expires_at ?? "",
|
|
19138
|
-
clientIdIssuedAt: response.client_id_issued_at ?? "",
|
|
19139
|
-
scopes: response.scopes ?? params.scopes
|
|
19140
|
-
};
|
|
19141
|
-
this.setCredentials(registration.clientId, registration.clientSecret);
|
|
19142
|
-
log16.info("OAuth client registered", {
|
|
19143
|
-
clientId: sanitize(registration.clientId),
|
|
19144
|
-
clientSecret: sanitize(registration.clientSecret),
|
|
19145
|
-
scopes: registration.scopes,
|
|
19146
|
-
expiresAt: registration.clientSecretExpiresAt
|
|
19147
|
-
});
|
|
19148
|
-
return registration;
|
|
19149
|
-
}
|
|
19150
|
-
/**
|
|
19151
|
-
* 获取访问令牌 → POST /seal/oauth2/token (client_credentials)
|
|
19215
|
+
* 请求参数 (x-www-form-urlencoded):
|
|
19216
|
+
* - grant_type: client_credentials (写死)
|
|
19217
|
+
* - client_id: appId
|
|
19218
|
+
* - client_secret: appSecret
|
|
19219
|
+
* - scope: client_credentials refresh_token (写死)
|
|
19152
19220
|
*
|
|
19153
|
-
* 使用客户端凭证模式获取 Access Token。
|
|
19154
|
-
* 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
|
|
19155
|
-
*
|
|
19156
|
-
* @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
|
|
19157
19221
|
* @returns TokenData 包含 access_token、过期时间等
|
|
19158
|
-
* @throws
|
|
19159
|
-
* @throws SealApiError — HTTP 错误
|
|
19222
|
+
* @throws ApiError — HTTP 错误
|
|
19160
19223
|
*/
|
|
19161
|
-
async getToken(
|
|
19162
|
-
if (!this.clientId || !this.clientSecret) {
|
|
19163
|
-
throw new Error("Cannot get token: clientId and clientSecret are required. Call register() or setCredentials() first.");
|
|
19164
|
-
}
|
|
19224
|
+
async getToken() {
|
|
19165
19225
|
const params = new URLSearchParams();
|
|
19166
|
-
params.set("grant_type",
|
|
19167
|
-
params.set("client_id", this.
|
|
19168
|
-
params.set("client_secret", this.
|
|
19169
|
-
|
|
19170
|
-
|
|
19171
|
-
}
|
|
19172
|
-
log16.info("Requesting access token", { clientId: sanitize(this.clientId) });
|
|
19226
|
+
params.set("grant_type", GRANT_TYPE);
|
|
19227
|
+
params.set("client_id", this.appId);
|
|
19228
|
+
params.set("client_secret", this.appSecret);
|
|
19229
|
+
params.set("scope", SCOPE);
|
|
19230
|
+
log17.info("Requesting access token", { appId: sanitize(this.appId) });
|
|
19173
19231
|
const response = await this.request(
|
|
19174
19232
|
"POST",
|
|
19175
|
-
"/
|
|
19233
|
+
"/auth/token",
|
|
19176
19234
|
{ body: params, contentType: "form" }
|
|
19177
19235
|
);
|
|
19178
19236
|
const now = Date.now();
|
|
@@ -19183,32 +19241,28 @@ var SealClient = class {
|
|
|
19183
19241
|
expiresIn,
|
|
19184
19242
|
scope: response.scope ?? "",
|
|
19185
19243
|
refreshToken: void 0,
|
|
19186
|
-
// Seal 不返回 refresh_token
|
|
19187
19244
|
grantedAt: now,
|
|
19188
19245
|
expiresAt: now + expiresIn * 1e3
|
|
19189
19246
|
};
|
|
19190
|
-
|
|
19247
|
+
log17.info("Access token acquired", {
|
|
19191
19248
|
accessToken: sanitize(tokenData.accessToken),
|
|
19192
19249
|
expiresIn: tokenData.expiresIn,
|
|
19193
19250
|
scope: tokenData.scope
|
|
19194
19251
|
});
|
|
19195
19252
|
return tokenData;
|
|
19196
19253
|
}
|
|
19197
|
-
/**
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
*/
|
|
19201
|
-
async introspect(_token) {
|
|
19202
|
-
throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
|
|
19254
|
+
/** 获取 baseUrl (供其他模块复用) */
|
|
19255
|
+
getBaseUrl() {
|
|
19256
|
+
return this.baseUrl;
|
|
19203
19257
|
}
|
|
19204
19258
|
};
|
|
19205
19259
|
|
|
19206
19260
|
// src/auth/token-store.ts
|
|
19207
19261
|
var import_promises = require("fs/promises");
|
|
19208
|
-
var
|
|
19209
|
-
var
|
|
19210
|
-
var
|
|
19211
|
-
var
|
|
19262
|
+
var import_node_fs2 = require("fs");
|
|
19263
|
+
var import_node_path2 = require("path");
|
|
19264
|
+
var import_node_crypto = require("crypto");
|
|
19265
|
+
var log18 = createLogger("auth/token-store");
|
|
19212
19266
|
var TokenStore = class {
|
|
19213
19267
|
/** 存储目录路径 */
|
|
19214
19268
|
storageDir;
|
|
@@ -19223,7 +19277,7 @@ var TokenStore = class {
|
|
|
19223
19277
|
constructor(storageDir, crypto) {
|
|
19224
19278
|
this.storageDir = storageDir;
|
|
19225
19279
|
this.crypto = crypto;
|
|
19226
|
-
|
|
19280
|
+
log18.info("TokenStore initialized", { storageDir });
|
|
19227
19281
|
}
|
|
19228
19282
|
/**
|
|
19229
19283
|
* 确保存储目录存在并设置正确的权限。
|
|
@@ -19231,9 +19285,9 @@ var TokenStore = class {
|
|
|
19231
19285
|
*/
|
|
19232
19286
|
async ensureDir() {
|
|
19233
19287
|
if (this.dirEnsured) return;
|
|
19234
|
-
if (!(0,
|
|
19288
|
+
if (!(0, import_node_fs2.existsSync)(this.storageDir)) {
|
|
19235
19289
|
await (0, import_promises.mkdir)(this.storageDir, { recursive: true, mode: 448 });
|
|
19236
|
-
|
|
19290
|
+
log18.debug("Storage directory created", { dir: this.storageDir });
|
|
19237
19291
|
}
|
|
19238
19292
|
this.dirEnsured = true;
|
|
19239
19293
|
}
|
|
@@ -19242,7 +19296,7 @@ var TokenStore = class {
|
|
|
19242
19296
|
* @param key - 存储键名 (如 'token', 'credentials')
|
|
19243
19297
|
*/
|
|
19244
19298
|
filePath(key) {
|
|
19245
|
-
return (0,
|
|
19299
|
+
return (0, import_node_path2.join)(this.storageDir, `${key}.enc`);
|
|
19246
19300
|
}
|
|
19247
19301
|
/**
|
|
19248
19302
|
* 保存 Token — 加密写入文件
|
|
@@ -19263,12 +19317,12 @@ var TokenStore = class {
|
|
|
19263
19317
|
const storage = { ciphertext, keyId };
|
|
19264
19318
|
const storageJson = JSON.stringify(storage);
|
|
19265
19319
|
const targetPath = this.filePath(key);
|
|
19266
|
-
const tmpSuffix = (0,
|
|
19320
|
+
const tmpSuffix = (0, import_node_crypto.randomBytes)(4).toString("hex");
|
|
19267
19321
|
const tmpPath = `${targetPath}.${tmpSuffix}.tmp`;
|
|
19268
19322
|
try {
|
|
19269
19323
|
await (0, import_promises.writeFile)(tmpPath, storageJson, { mode: 384 });
|
|
19270
19324
|
await (0, import_promises.rename)(tmpPath, targetPath);
|
|
19271
|
-
|
|
19325
|
+
log18.debug("Token saved", { key, path: targetPath });
|
|
19272
19326
|
} catch (err) {
|
|
19273
19327
|
try {
|
|
19274
19328
|
await (0, import_promises.unlink)(tmpPath);
|
|
@@ -19290,8 +19344,8 @@ var TokenStore = class {
|
|
|
19290
19344
|
*/
|
|
19291
19345
|
async load(key) {
|
|
19292
19346
|
const filePath = this.filePath(key);
|
|
19293
|
-
if (!(0,
|
|
19294
|
-
|
|
19347
|
+
if (!(0, import_node_fs2.existsSync)(filePath)) {
|
|
19348
|
+
log18.debug("Token file not found", { key, path: filePath });
|
|
19295
19349
|
return null;
|
|
19296
19350
|
}
|
|
19297
19351
|
try {
|
|
@@ -19299,10 +19353,10 @@ var TokenStore = class {
|
|
|
19299
19353
|
const storage = JSON.parse(raw);
|
|
19300
19354
|
const json2 = await this.crypto.decrypt(storage.ciphertext, storage.keyId);
|
|
19301
19355
|
const data = JSON.parse(json2);
|
|
19302
|
-
|
|
19356
|
+
log18.debug("Token loaded", { key, path: filePath });
|
|
19303
19357
|
return data;
|
|
19304
19358
|
} catch (err) {
|
|
19305
|
-
|
|
19359
|
+
log18.warn("Failed to load token (corrupted or key mismatch), treating as empty", {
|
|
19306
19360
|
key,
|
|
19307
19361
|
error: err instanceof Error ? err.message : String(err)
|
|
19308
19362
|
});
|
|
@@ -19318,7 +19372,7 @@ var TokenStore = class {
|
|
|
19318
19372
|
const filePath = this.filePath(key);
|
|
19319
19373
|
try {
|
|
19320
19374
|
await (0, import_promises.unlink)(filePath);
|
|
19321
|
-
|
|
19375
|
+
log18.debug("Token file cleared", { key, path: filePath });
|
|
19322
19376
|
} catch (err) {
|
|
19323
19377
|
if (err.code !== "ENOENT") {
|
|
19324
19378
|
throw err;
|
|
@@ -19328,17 +19382,15 @@ var TokenStore = class {
|
|
|
19328
19382
|
};
|
|
19329
19383
|
|
|
19330
19384
|
// src/auth/token-manager.ts
|
|
19331
|
-
var
|
|
19385
|
+
var log19 = createLogger("auth/token-manager");
|
|
19332
19386
|
var DEFAULT_REFRESH_AHEAD_MS = 5 * 60 * 1e3;
|
|
19333
19387
|
var MAX_RETRIES = 3;
|
|
19334
19388
|
var RETRY_BASE_MS = 1e3;
|
|
19335
19389
|
var TokenManager = class {
|
|
19336
|
-
|
|
19390
|
+
oauthClient;
|
|
19337
19391
|
tokenStore;
|
|
19338
19392
|
/** Token 过期前提前刷新的时间 (ms) */
|
|
19339
19393
|
refreshAheadMs;
|
|
19340
|
-
/** 自动注册的参数 */
|
|
19341
|
-
autoRegisterParams;
|
|
19342
19394
|
// ── 内存缓存 ──
|
|
19343
19395
|
/** 内存中缓存的 access_token */
|
|
19344
19396
|
cachedToken = null;
|
|
@@ -19352,11 +19404,10 @@ var TokenManager = class {
|
|
|
19352
19404
|
/** 定时刷新器句柄 */
|
|
19353
19405
|
refreshTimer = null;
|
|
19354
19406
|
constructor(deps) {
|
|
19355
|
-
this.
|
|
19407
|
+
this.oauthClient = deps.oauthClient;
|
|
19356
19408
|
this.tokenStore = deps.tokenStore;
|
|
19357
19409
|
this.refreshAheadMs = deps.refreshAheadMs ?? DEFAULT_REFRESH_AHEAD_MS;
|
|
19358
|
-
|
|
19359
|
-
log18.info("TokenManager initialized", { refreshAheadMs: this.refreshAheadMs });
|
|
19410
|
+
log19.info("TokenManager initialized", { refreshAheadMs: this.refreshAheadMs });
|
|
19360
19411
|
}
|
|
19361
19412
|
// ── 核心方法 ──
|
|
19362
19413
|
/**
|
|
@@ -19374,7 +19425,7 @@ var TokenManager = class {
|
|
|
19374
19425
|
return this.cachedToken;
|
|
19375
19426
|
}
|
|
19376
19427
|
if (this.refreshLock) {
|
|
19377
|
-
|
|
19428
|
+
log19.debug("Waiting for concurrent token acquisition");
|
|
19378
19429
|
return this.refreshLock;
|
|
19379
19430
|
}
|
|
19380
19431
|
this.refreshLock = this._acquireTokenWithRetry();
|
|
@@ -19384,13 +19435,6 @@ var TokenManager = class {
|
|
|
19384
19435
|
this.refreshLock = null;
|
|
19385
19436
|
}
|
|
19386
19437
|
}
|
|
19387
|
-
/**
|
|
19388
|
-
* 令牌自省 — 暂不实现,后续补充。
|
|
19389
|
-
* 返回的 identity_id 用于 gate.ts 的 anti-loop 检查 (bot 自己的 ID)。
|
|
19390
|
-
*/
|
|
19391
|
-
async introspect() {
|
|
19392
|
-
throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
|
|
19393
|
-
}
|
|
19394
19438
|
/** 检查当前令牌是否包含指定的 scope */
|
|
19395
19439
|
hasScope(scope) {
|
|
19396
19440
|
if (!this.cachedScope) return false;
|
|
@@ -19407,13 +19451,13 @@ var TokenManager = class {
|
|
|
19407
19451
|
this.cachedToken = null;
|
|
19408
19452
|
this.cachedScope = null;
|
|
19409
19453
|
this.currentTokenData = null;
|
|
19410
|
-
|
|
19454
|
+
log19.info("Token revoked and cleared");
|
|
19411
19455
|
}
|
|
19412
19456
|
/** 清理定时器和并发锁 — 优雅关闭时调用 */
|
|
19413
19457
|
shutdown() {
|
|
19414
19458
|
this.clearRefreshTimer();
|
|
19415
19459
|
this.refreshLock = null;
|
|
19416
|
-
|
|
19460
|
+
log19.info("TokenManager shutdown");
|
|
19417
19461
|
}
|
|
19418
19462
|
// ── 内部方法 ──
|
|
19419
19463
|
/**
|
|
@@ -19421,26 +19465,26 @@ var TokenManager = class {
|
|
|
19421
19465
|
*
|
|
19422
19466
|
* 重试策略:
|
|
19423
19467
|
* - 网络错误 / 5xx → 重试 (最多 3 次)
|
|
19424
|
-
* - 401/403 → 不重试 (
|
|
19468
|
+
* - 401/403 → 不重试 (凭证可能无效)
|
|
19425
19469
|
*/
|
|
19426
19470
|
async _acquireTokenWithRetry() {
|
|
19427
19471
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
19428
19472
|
try {
|
|
19429
19473
|
return await this._acquireToken();
|
|
19430
19474
|
} catch (err) {
|
|
19431
|
-
if (err instanceof
|
|
19432
|
-
|
|
19475
|
+
if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
|
|
19476
|
+
log19.error("Token acquire failed with auth error, not retrying", { status: err.status });
|
|
19433
19477
|
throw err;
|
|
19434
19478
|
}
|
|
19435
19479
|
if (attempt === MAX_RETRIES) {
|
|
19436
|
-
|
|
19480
|
+
log19.error("Token acquire failed after all retries", { attempts: attempt + 1 });
|
|
19437
19481
|
throw new TokenAcquireError(
|
|
19438
19482
|
`Token acquire failed after ${attempt + 1} attempts`,
|
|
19439
19483
|
{ cause: err instanceof Error ? err : void 0 }
|
|
19440
19484
|
);
|
|
19441
19485
|
}
|
|
19442
19486
|
const delay = RETRY_BASE_MS * Math.pow(2, attempt);
|
|
19443
|
-
|
|
19487
|
+
log19.warn("Token acquire failed, retrying", {
|
|
19444
19488
|
attempt: attempt + 1,
|
|
19445
19489
|
maxRetries: MAX_RETRIES,
|
|
19446
19490
|
retryInMs: delay,
|
|
@@ -19456,63 +19500,27 @@ var TokenManager = class {
|
|
|
19456
19500
|
*
|
|
19457
19501
|
* 流程:
|
|
19458
19502
|
* 1. 尝试从 TokenStore 加载缓存
|
|
19459
|
-
* 2.
|
|
19460
|
-
* 3.
|
|
19461
|
-
* 4.
|
|
19462
|
-
* 5. 设置定时刷新器
|
|
19503
|
+
* 2. 调用 OAuthClient.getToken() 获取新 Token
|
|
19504
|
+
* 3. 保存到内存缓存 + TokenStore
|
|
19505
|
+
* 4. 设置定时刷新器
|
|
19463
19506
|
*/
|
|
19464
19507
|
async _acquireToken() {
|
|
19465
19508
|
try {
|
|
19466
19509
|
const stored = await this.tokenStore.load("token");
|
|
19467
19510
|
if (stored && !this.isTokenHardExpired(stored)) {
|
|
19468
|
-
|
|
19511
|
+
log19.info("Token loaded from store (still valid)");
|
|
19469
19512
|
this.updateCache(stored);
|
|
19470
19513
|
this.scheduleRefresh(stored);
|
|
19471
19514
|
return stored.accessToken;
|
|
19472
19515
|
}
|
|
19473
19516
|
if (stored) {
|
|
19474
|
-
|
|
19517
|
+
log19.info("Stored token has expired, acquiring new one");
|
|
19475
19518
|
}
|
|
19476
19519
|
} catch {
|
|
19477
|
-
|
|
19520
|
+
log19.warn("Failed to load token from store, acquiring new one");
|
|
19478
19521
|
}
|
|
19479
|
-
|
|
19480
|
-
|
|
19481
|
-
const savedCreds = await this.tokenStore.load("credentials");
|
|
19482
|
-
if (savedCreds?.clientId && savedCreds?.clientSecret) {
|
|
19483
|
-
this.sealClient.setCredentials(
|
|
19484
|
-
savedCreds.clientId,
|
|
19485
|
-
savedCreds.clientSecret
|
|
19486
|
-
);
|
|
19487
|
-
log18.info("Credentials loaded from store");
|
|
19488
|
-
}
|
|
19489
|
-
} catch {
|
|
19490
|
-
log18.debug("No saved credentials found");
|
|
19491
|
-
}
|
|
19492
|
-
}
|
|
19493
|
-
if (!this.sealClient.isRegistered()) {
|
|
19494
|
-
if (!this.autoRegisterParams) {
|
|
19495
|
-
throw new Error("SealClient not registered and autoRegisterParams not provided. Call setCredentials() or enable autoRegister.");
|
|
19496
|
-
}
|
|
19497
|
-
log18.info("Auto-registering OAuth client");
|
|
19498
|
-
const registration = await this.sealClient.register({
|
|
19499
|
-
clientName: this.autoRegisterParams.clientName,
|
|
19500
|
-
scopes: this.autoRegisterParams.scopes,
|
|
19501
|
-
tokenSettings: this.autoRegisterParams.tokenSettings ? {
|
|
19502
|
-
accessTokenTtl: this.autoRegisterParams.tokenSettings.accessTokenTtl,
|
|
19503
|
-
refreshTokenTtl: this.autoRegisterParams.tokenSettings.refreshTokenTtl,
|
|
19504
|
-
accessTokenFormat: this.autoRegisterParams.tokenSettings.accessTokenFormat
|
|
19505
|
-
} : void 0
|
|
19506
|
-
});
|
|
19507
|
-
await this.tokenStore.save("credentials", {
|
|
19508
|
-
clientId: registration.clientId,
|
|
19509
|
-
clientSecret: registration.clientSecret,
|
|
19510
|
-
clientSecretExpiresAt: registration.clientSecretExpiresAt
|
|
19511
|
-
});
|
|
19512
|
-
log18.info("OAuth client registered and credentials saved");
|
|
19513
|
-
}
|
|
19514
|
-
log18.info("Acquiring new access token");
|
|
19515
|
-
const tokenData = await this.sealClient.getToken();
|
|
19522
|
+
log19.info("Acquiring new access token via POST /auth/token");
|
|
19523
|
+
const tokenData = await this.oauthClient.getToken();
|
|
19516
19524
|
this.updateCache(tokenData);
|
|
19517
19525
|
await this.tokenStore.save("token", tokenData);
|
|
19518
19526
|
this.scheduleRefresh(tokenData);
|
|
@@ -19542,22 +19550,22 @@ var TokenManager = class {
|
|
|
19542
19550
|
this.clearRefreshTimer();
|
|
19543
19551
|
const refreshInMs = tokenData.expiresAt - this.refreshAheadMs - Date.now();
|
|
19544
19552
|
if (refreshInMs <= 0) {
|
|
19545
|
-
|
|
19553
|
+
log19.debug("Token already within refresh window, not scheduling timer");
|
|
19546
19554
|
return;
|
|
19547
19555
|
}
|
|
19548
|
-
|
|
19556
|
+
log19.debug("Scheduling token refresh", {
|
|
19549
19557
|
refreshInMs,
|
|
19550
19558
|
refreshAt: new Date(Date.now() + refreshInMs).toISOString()
|
|
19551
19559
|
});
|
|
19552
19560
|
this.refreshTimer = setTimeout(async () => {
|
|
19553
19561
|
try {
|
|
19554
|
-
|
|
19562
|
+
log19.info("Scheduled token refresh triggered");
|
|
19555
19563
|
this.cachedToken = null;
|
|
19556
19564
|
this.currentTokenData = null;
|
|
19557
19565
|
await this.getValidToken();
|
|
19558
|
-
|
|
19566
|
+
log19.info("Scheduled token refresh completed");
|
|
19559
19567
|
} catch (err) {
|
|
19560
|
-
|
|
19568
|
+
log19.error("Scheduled token refresh failed", {
|
|
19561
19569
|
error: err instanceof Error ? err.message : String(err)
|
|
19562
19570
|
});
|
|
19563
19571
|
}
|
|
@@ -19585,13 +19593,13 @@ var wrapper_default = import_websocket.default;
|
|
|
19585
19593
|
|
|
19586
19594
|
// src/transport/ws-client.ts
|
|
19587
19595
|
var import_node_events = require("events");
|
|
19588
|
-
var
|
|
19596
|
+
var log20 = createLogger("transport/ws-client");
|
|
19589
19597
|
var WSClient = class extends import_node_events.EventEmitter {
|
|
19590
19598
|
/** 底层 WebSocket 实例 */
|
|
19591
19599
|
ws = null;
|
|
19592
19600
|
constructor() {
|
|
19593
19601
|
super();
|
|
19594
|
-
|
|
19602
|
+
log20.info("WSClient initialized");
|
|
19595
19603
|
}
|
|
19596
19604
|
/**
|
|
19597
19605
|
* 连接到 WebSocket 服务器并绑定事件。
|
|
@@ -19607,11 +19615,11 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19607
19615
|
this.ws = null;
|
|
19608
19616
|
}
|
|
19609
19617
|
const { url: url2, protocols, headers } = options;
|
|
19610
|
-
|
|
19618
|
+
log20.info("ws:connecting", { url: url2 });
|
|
19611
19619
|
return new Promise((resolve2, reject) => {
|
|
19612
19620
|
const ws = new wrapper_default(url2, protocols, { headers });
|
|
19613
19621
|
ws.on("open", () => {
|
|
19614
|
-
|
|
19622
|
+
log20.info("ws:connected", { url: url2 });
|
|
19615
19623
|
this.emit("open");
|
|
19616
19624
|
resolve2();
|
|
19617
19625
|
});
|
|
@@ -19619,11 +19627,11 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19619
19627
|
this.emit("message", data);
|
|
19620
19628
|
});
|
|
19621
19629
|
ws.on("close", (code, reason) => {
|
|
19622
|
-
|
|
19630
|
+
log20.info("ws:closed", { code, reason: reason.toString() });
|
|
19623
19631
|
this.emit("close", code, reason.toString());
|
|
19624
19632
|
});
|
|
19625
19633
|
ws.on("error", (err) => {
|
|
19626
|
-
|
|
19634
|
+
log20.error("ws:error", { error: err.message });
|
|
19627
19635
|
this.emit("error", err);
|
|
19628
19636
|
if (this.ws !== ws) {
|
|
19629
19637
|
reject(err);
|
|
@@ -19635,7 +19643,7 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19635
19643
|
/** 发送数据到服务器 — 前置检查连接状态 */
|
|
19636
19644
|
send(data) {
|
|
19637
19645
|
if (!this.ws || this.ws.readyState !== wrapper_default.OPEN) {
|
|
19638
|
-
|
|
19646
|
+
log20.warn("ws:send skipped, not connected", { readyState: this.ws?.readyState });
|
|
19639
19647
|
return;
|
|
19640
19648
|
}
|
|
19641
19649
|
this.ws.send(data);
|
|
@@ -19646,7 +19654,7 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19646
19654
|
this.ws.removeAllListeners();
|
|
19647
19655
|
this.ws.close(code, reason);
|
|
19648
19656
|
this.ws = null;
|
|
19649
|
-
|
|
19657
|
+
log20.info("ws:close requested", { code, reason });
|
|
19650
19658
|
}
|
|
19651
19659
|
}
|
|
19652
19660
|
/** 当前连接状态 (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED) */
|
|
@@ -19656,7 +19664,7 @@ var WSClient = class extends import_node_events.EventEmitter {
|
|
|
19656
19664
|
};
|
|
19657
19665
|
|
|
19658
19666
|
// src/transport/dedup.ts
|
|
19659
|
-
var
|
|
19667
|
+
var log21 = createLogger("transport/dedup");
|
|
19660
19668
|
var MessageDedup = class {
|
|
19661
19669
|
/** 去重缓存生存时间 (ms) */
|
|
19662
19670
|
ttlMs;
|
|
@@ -19667,7 +19675,7 @@ var MessageDedup = class {
|
|
|
19667
19675
|
constructor(config2) {
|
|
19668
19676
|
this.ttlMs = config2?.ttlMs ?? 3e5;
|
|
19669
19677
|
this.maxEntries = config2?.maxEntries ?? 5e3;
|
|
19670
|
-
|
|
19678
|
+
log21.info("MessageDedup initialized", { ttlMs: this.ttlMs, maxEntries: this.maxEntries });
|
|
19671
19679
|
}
|
|
19672
19680
|
/**
|
|
19673
19681
|
* 检查消息是否重复。
|
|
@@ -19702,8 +19710,8 @@ var MessageDedup = class {
|
|
|
19702
19710
|
};
|
|
19703
19711
|
|
|
19704
19712
|
// src/transport/message-pipe.ts
|
|
19705
|
-
var
|
|
19706
|
-
var
|
|
19713
|
+
var import_node_crypto2 = require("crypto");
|
|
19714
|
+
var log22 = createLogger("transport/message-pipe");
|
|
19707
19715
|
var MessagePipe = class {
|
|
19708
19716
|
wsClient;
|
|
19709
19717
|
dedup;
|
|
@@ -19722,15 +19730,15 @@ var MessagePipe = class {
|
|
|
19722
19730
|
this.messageServiceBaseUrl = deps.messageServiceBaseUrl;
|
|
19723
19731
|
this.wsClient.on("message", (rawData) => {
|
|
19724
19732
|
this.handleInbound(rawData).catch((err) => {
|
|
19725
|
-
|
|
19733
|
+
log22.error("inbound:error", { step: "handleInbound", error: err.message });
|
|
19726
19734
|
});
|
|
19727
19735
|
});
|
|
19728
|
-
|
|
19736
|
+
log22.info("MessagePipe initialized");
|
|
19729
19737
|
}
|
|
19730
19738
|
/** 注册入站消息回调 — 解密后的消息会通过此回调传给 L4 层 */
|
|
19731
19739
|
onMessage(callback) {
|
|
19732
19740
|
this.messageCallback = callback;
|
|
19733
|
-
|
|
19741
|
+
log22.info("Inbound message callback registered");
|
|
19734
19742
|
}
|
|
19735
19743
|
/**
|
|
19736
19744
|
* 出站发送 — 通过 HTTP API 发送消息到 IM 服务器。
|
|
@@ -19750,7 +19758,7 @@ var MessagePipe = class {
|
|
|
19750
19758
|
}
|
|
19751
19759
|
const result = await this.callMessageApi("/messages/v1/send", body, "outbound");
|
|
19752
19760
|
if (!result) return;
|
|
19753
|
-
|
|
19761
|
+
log22.info("outbound:sent", {
|
|
19754
19762
|
chatId: msg.chatId,
|
|
19755
19763
|
senderId: msg.senderId,
|
|
19756
19764
|
msgType: msg.msgType,
|
|
@@ -19774,7 +19782,7 @@ var MessagePipe = class {
|
|
|
19774
19782
|
};
|
|
19775
19783
|
const result = await this.callMessageApi("/messages/v1/recall", body, "recall");
|
|
19776
19784
|
if (!result) return;
|
|
19777
|
-
|
|
19785
|
+
log22.info("recall:sent", {
|
|
19778
19786
|
messageId: params.messageId,
|
|
19779
19787
|
requestId: result.request_id
|
|
19780
19788
|
});
|
|
@@ -19801,7 +19809,7 @@ var MessagePipe = class {
|
|
|
19801
19809
|
body: JSON.stringify(body)
|
|
19802
19810
|
});
|
|
19803
19811
|
if (!resp.ok) {
|
|
19804
|
-
|
|
19812
|
+
log22.error(`${logTag}:http-error`, {
|
|
19805
19813
|
status: resp.status,
|
|
19806
19814
|
statusText: resp.statusText,
|
|
19807
19815
|
url: url2
|
|
@@ -19810,7 +19818,7 @@ var MessagePipe = class {
|
|
|
19810
19818
|
}
|
|
19811
19819
|
const result = await resp.json();
|
|
19812
19820
|
if (result.code !== 0) {
|
|
19813
|
-
|
|
19821
|
+
log22.error(`${logTag}:api-error`, {
|
|
19814
19822
|
code: result.code,
|
|
19815
19823
|
msg: result.msg,
|
|
19816
19824
|
requestId: result.request_id
|
|
@@ -19819,7 +19827,7 @@ var MessagePipe = class {
|
|
|
19819
19827
|
}
|
|
19820
19828
|
return result;
|
|
19821
19829
|
} catch (err) {
|
|
19822
|
-
|
|
19830
|
+
log22.error(`${logTag}:network-error`, {
|
|
19823
19831
|
url: url2,
|
|
19824
19832
|
error: err.message
|
|
19825
19833
|
});
|
|
@@ -19841,7 +19849,7 @@ var MessagePipe = class {
|
|
|
19841
19849
|
try {
|
|
19842
19850
|
frame = JSON.parse(String(rawData));
|
|
19843
19851
|
} catch (err) {
|
|
19844
|
-
|
|
19852
|
+
log22.warn("inbound:json-parse-error", { error: err.message });
|
|
19845
19853
|
return;
|
|
19846
19854
|
}
|
|
19847
19855
|
const timestamp = frame["X-CTQ-Timestamp"];
|
|
@@ -19850,9 +19858,9 @@ var MessagePipe = class {
|
|
|
19850
19858
|
if (timestamp && nonce && signature) {
|
|
19851
19859
|
const token = await this.tokenFn();
|
|
19852
19860
|
const signatureInput = timestamp + nonce + frame.data;
|
|
19853
|
-
const expected = (0,
|
|
19861
|
+
const expected = (0, import_node_crypto2.createHmac)("sha256", token).update(signatureInput).digest("hex");
|
|
19854
19862
|
if (expected !== signature) {
|
|
19855
|
-
|
|
19863
|
+
log22.warn("inbound:signature-fail", { timestamp, nonce });
|
|
19856
19864
|
return;
|
|
19857
19865
|
}
|
|
19858
19866
|
}
|
|
@@ -19866,30 +19874,37 @@ var MessagePipe = class {
|
|
|
19866
19874
|
try {
|
|
19867
19875
|
callbackData = JSON.parse(dataStr);
|
|
19868
19876
|
} catch (err) {
|
|
19869
|
-
|
|
19877
|
+
log22.warn("inbound:data-parse-error", { error: err.message });
|
|
19870
19878
|
return;
|
|
19871
19879
|
}
|
|
19872
|
-
if (callbackData.eventType !== "
|
|
19873
|
-
|
|
19880
|
+
if (callbackData.eventType !== "callback:direct") {
|
|
19881
|
+
log22.debug("inbound:event-ignored", { eventType: callbackData.eventType });
|
|
19874
19882
|
return;
|
|
19875
19883
|
}
|
|
19876
|
-
const msg =
|
|
19877
|
-
|
|
19884
|
+
const msg = {
|
|
19885
|
+
messageId: callbackData.msgUid,
|
|
19886
|
+
chatId: callbackData.groupId || callbackData.userId,
|
|
19887
|
+
senderId: callbackData.userId,
|
|
19888
|
+
msgType: callbackData.type,
|
|
19889
|
+
content: JSON.stringify(callbackData.content),
|
|
19890
|
+
timestamp: Date.now()
|
|
19891
|
+
};
|
|
19892
|
+
log22.debug("inbound:frame", { messageId: msg.messageId, eventType: callbackData.eventType });
|
|
19878
19893
|
if (this.dedup.isDuplicate(msg.messageId)) {
|
|
19879
|
-
|
|
19894
|
+
log22.debug("inbound:dedup-hit", { messageId: msg.messageId });
|
|
19880
19895
|
return;
|
|
19881
19896
|
}
|
|
19882
|
-
|
|
19897
|
+
log22.info("inbound:verified", { messageId: msg.messageId, chatId: msg.chatId });
|
|
19883
19898
|
if (this.messageCallback) {
|
|
19884
19899
|
this.messageCallback(msg);
|
|
19885
19900
|
} else {
|
|
19886
|
-
|
|
19901
|
+
log22.warn("inbound:no-callback", { messageId: msg.messageId });
|
|
19887
19902
|
}
|
|
19888
19903
|
}
|
|
19889
19904
|
};
|
|
19890
19905
|
|
|
19891
19906
|
// src/transport/connection-manager.ts
|
|
19892
|
-
var
|
|
19907
|
+
var log23 = createLogger("transport/connection-manager");
|
|
19893
19908
|
function sleep2(ms) {
|
|
19894
19909
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
19895
19910
|
}
|
|
@@ -19919,7 +19934,7 @@ var ConnectionManager = class {
|
|
|
19919
19934
|
reconnectMaxMs: options?.reconnectMaxMs ?? 6e4,
|
|
19920
19935
|
maxReconnectAttempts: options?.maxReconnectAttempts ?? 10
|
|
19921
19936
|
};
|
|
19922
|
-
|
|
19937
|
+
log23.info("ConnectionManager initialized", this.options);
|
|
19923
19938
|
}
|
|
19924
19939
|
/**
|
|
19925
19940
|
* 启动连接 — 连接 WS + 开始心跳 + 注册重连逻辑
|
|
@@ -19934,29 +19949,33 @@ var ConnectionManager = class {
|
|
|
19934
19949
|
this.running = true;
|
|
19935
19950
|
this.reconnectAttempts = 0;
|
|
19936
19951
|
const token = await tokenFn();
|
|
19952
|
+
const wsUrl = url2.replace(/\/+$/, "") + "/events/stream";
|
|
19937
19953
|
await this.client.connect({
|
|
19938
|
-
url:
|
|
19954
|
+
url: wsUrl,
|
|
19939
19955
|
token,
|
|
19940
|
-
headers:
|
|
19956
|
+
headers: {
|
|
19957
|
+
"Authorization": token,
|
|
19958
|
+
...this.appId ? { "X-App-ID": this.appId } : {}
|
|
19959
|
+
}
|
|
19941
19960
|
});
|
|
19942
19961
|
this.client.on("close", () => {
|
|
19943
19962
|
if (this.running) {
|
|
19944
|
-
|
|
19963
|
+
log23.warn("ws:disconnected, scheduling reconnect");
|
|
19945
19964
|
this.scheduleReconnect();
|
|
19946
19965
|
}
|
|
19947
19966
|
});
|
|
19948
19967
|
this.client.on("error", (err) => {
|
|
19949
|
-
|
|
19968
|
+
log23.error("ws:connection-error", { error: err.message });
|
|
19950
19969
|
});
|
|
19951
19970
|
this.startHeartbeat();
|
|
19952
|
-
|
|
19971
|
+
log23.info("ConnectionManager started \u2713", { url: url2 });
|
|
19953
19972
|
}
|
|
19954
19973
|
/** 启动心跳保活 — 定时检查连接状态 */
|
|
19955
19974
|
startHeartbeat() {
|
|
19956
19975
|
this.stopHeartbeat();
|
|
19957
19976
|
this.heartbeatTimer = setInterval(() => {
|
|
19958
19977
|
if (this.client.readyState !== wrapper_default.OPEN) {
|
|
19959
|
-
|
|
19978
|
+
log23.warn("heartbeat: connection not open, scheduling reconnect");
|
|
19960
19979
|
this.scheduleReconnect();
|
|
19961
19980
|
}
|
|
19962
19981
|
}, this.options.heartbeatIntervalMs);
|
|
@@ -19984,30 +20003,34 @@ var ConnectionManager = class {
|
|
|
19984
20003
|
this.options.reconnectBaseMs * 2 ** this.reconnectAttempts,
|
|
19985
20004
|
this.options.reconnectMaxMs
|
|
19986
20005
|
);
|
|
19987
|
-
|
|
20006
|
+
log23.info("ws:reconnecting", { attempt: this.reconnectAttempts + 1, delayMs: delay });
|
|
19988
20007
|
await sleep2(delay);
|
|
19989
20008
|
if (!this.running) return;
|
|
19990
20009
|
this.reconnectAttempts++;
|
|
19991
20010
|
try {
|
|
19992
20011
|
const token = await this.tokenFn();
|
|
20012
|
+
const wsUrl = this.url.replace(/\/+$/, "") + "/events/stream";
|
|
19993
20013
|
await this.client.connect({
|
|
19994
|
-
url:
|
|
20014
|
+
url: wsUrl,
|
|
19995
20015
|
token,
|
|
19996
|
-
headers:
|
|
20016
|
+
headers: {
|
|
20017
|
+
"Authorization": token,
|
|
20018
|
+
...this.appId ? { "X-App-ID": this.appId } : {}
|
|
20019
|
+
}
|
|
19997
20020
|
});
|
|
19998
20021
|
this.reconnectAttempts = 0;
|
|
19999
20022
|
this.startHeartbeat();
|
|
20000
|
-
|
|
20023
|
+
log23.info("ws:reconnected", { attempt: this.reconnectAttempts, url: this.url });
|
|
20001
20024
|
return;
|
|
20002
20025
|
} catch (err) {
|
|
20003
|
-
|
|
20026
|
+
log23.warn("ws:reconnect-failed", {
|
|
20004
20027
|
attempt: this.reconnectAttempts,
|
|
20005
20028
|
error: err.message
|
|
20006
20029
|
});
|
|
20007
20030
|
}
|
|
20008
20031
|
}
|
|
20009
20032
|
if (this.running) {
|
|
20010
|
-
|
|
20033
|
+
log23.error("ws:max-reconnect-reached", {
|
|
20011
20034
|
maxAttempts: this.options.maxReconnectAttempts
|
|
20012
20035
|
});
|
|
20013
20036
|
}
|
|
@@ -20017,7 +20040,7 @@ var ConnectionManager = class {
|
|
|
20017
20040
|
this.running = false;
|
|
20018
20041
|
this.stopHeartbeat();
|
|
20019
20042
|
this.client.close(1e3, "shutdown");
|
|
20020
|
-
|
|
20043
|
+
log23.info("ConnectionManager stopped");
|
|
20021
20044
|
}
|
|
20022
20045
|
/** 当前是否已连接 (WebSocket readyState === OPEN) */
|
|
20023
20046
|
get isConnected() {
|
|
@@ -20026,7 +20049,7 @@ var ConnectionManager = class {
|
|
|
20026
20049
|
};
|
|
20027
20050
|
|
|
20028
20051
|
// src/push/cockatoo-client.ts
|
|
20029
|
-
var
|
|
20052
|
+
var log24 = createLogger("push/cockatoo-client");
|
|
20030
20053
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
20031
20054
|
var CockatooPushError = class extends Error {
|
|
20032
20055
|
constructor(status, body, endpoint) {
|
|
@@ -20060,7 +20083,7 @@ var CockatooClient = class {
|
|
|
20060
20083
|
const base = config2.endpoint.replace(/\/+$/, "");
|
|
20061
20084
|
this.pushUrl = `${base}/api/v1/push`;
|
|
20062
20085
|
this.healthUrl = `${base}/health`;
|
|
20063
|
-
|
|
20086
|
+
log24.info("CockatooClient initialized", { endpoint: config2.endpoint });
|
|
20064
20087
|
}
|
|
20065
20088
|
/**
|
|
20066
20089
|
* 推送消息到 Cockatoo 服务。
|
|
@@ -20091,7 +20114,7 @@ var CockatooClient = class {
|
|
|
20091
20114
|
}
|
|
20092
20115
|
throw new CockatooPushError(resp.status, body, this.pushUrl);
|
|
20093
20116
|
}
|
|
20094
|
-
|
|
20117
|
+
log24.debug("push:sent", { type: payload.type });
|
|
20095
20118
|
}
|
|
20096
20119
|
/**
|
|
20097
20120
|
* 执行一次健康检查。
|
|
@@ -20109,11 +20132,11 @@ var CockatooClient = class {
|
|
|
20109
20132
|
});
|
|
20110
20133
|
const isHealthy = resp.ok;
|
|
20111
20134
|
if (!isHealthy) {
|
|
20112
|
-
|
|
20135
|
+
log24.warn("health-check:unhealthy", { status: resp.status });
|
|
20113
20136
|
}
|
|
20114
20137
|
return isHealthy;
|
|
20115
20138
|
} catch (err) {
|
|
20116
|
-
|
|
20139
|
+
log24.warn("health-check:error", { error: err.message });
|
|
20117
20140
|
return false;
|
|
20118
20141
|
}
|
|
20119
20142
|
}
|
|
@@ -20125,7 +20148,7 @@ var CockatooClient = class {
|
|
|
20125
20148
|
this.healthy = await this.healthCheck();
|
|
20126
20149
|
} catch {
|
|
20127
20150
|
this.healthy = false;
|
|
20128
|
-
|
|
20151
|
+
log24.warn("Cockatoo health check failed");
|
|
20129
20152
|
}
|
|
20130
20153
|
}, this.config.healthCheckIntervalMs ?? 6e4);
|
|
20131
20154
|
if (typeof this.healthCheckTimer === "object" && "unref" in this.healthCheckTimer) {
|
|
@@ -20146,7 +20169,7 @@ var CockatooClient = class {
|
|
|
20146
20169
|
};
|
|
20147
20170
|
|
|
20148
20171
|
// src/push/push-queue.ts
|
|
20149
|
-
var
|
|
20172
|
+
var log25 = createLogger("push/push-queue");
|
|
20150
20173
|
var RETRY_BASE_MS2 = 1e3;
|
|
20151
20174
|
var PushQueue = class {
|
|
20152
20175
|
client;
|
|
@@ -20188,7 +20211,7 @@ var PushQueue = class {
|
|
|
20188
20211
|
this.unhealthyRetryMs = config2?.unhealthyRetryMs ?? 5e3;
|
|
20189
20212
|
this.drainOnStop = config2?.drainOnStop ?? false;
|
|
20190
20213
|
this.drainTimeoutMs = config2?.drainTimeoutMs ?? 5e3;
|
|
20191
|
-
|
|
20214
|
+
log25.info("PushQueue initialized", {
|
|
20192
20215
|
maxSize: this.maxSize,
|
|
20193
20216
|
retryAttempts: this.retryAttempts,
|
|
20194
20217
|
processIntervalMs: this.processIntervalMs
|
|
@@ -20199,7 +20222,7 @@ var PushQueue = class {
|
|
|
20199
20222
|
start() {
|
|
20200
20223
|
if (this.running) return;
|
|
20201
20224
|
this.running = true;
|
|
20202
|
-
|
|
20225
|
+
log25.info("PushQueue started");
|
|
20203
20226
|
this.scheduleNext(0);
|
|
20204
20227
|
}
|
|
20205
20228
|
/**
|
|
@@ -20213,10 +20236,10 @@ var PushQueue = class {
|
|
|
20213
20236
|
this.running = false;
|
|
20214
20237
|
this.clearTimer();
|
|
20215
20238
|
if (this.drainOnStop && this.queue.length > 0) {
|
|
20216
|
-
|
|
20239
|
+
log25.info("PushQueue draining", { remaining: this.queue.length });
|
|
20217
20240
|
await this.drain();
|
|
20218
20241
|
}
|
|
20219
|
-
|
|
20242
|
+
log25.info("PushQueue stopped", {
|
|
20220
20243
|
remaining: this.queue.length,
|
|
20221
20244
|
sent: this.sentCount,
|
|
20222
20245
|
dropped: this.droppedCount,
|
|
@@ -20231,13 +20254,13 @@ var PushQueue = class {
|
|
|
20231
20254
|
if (this.queue.length >= this.maxSize) {
|
|
20232
20255
|
const dropped = this.queue.shift();
|
|
20233
20256
|
this.droppedCount++;
|
|
20234
|
-
|
|
20257
|
+
log25.warn("queue:full, dropping oldest", {
|
|
20235
20258
|
droppedType: dropped?.payload.type,
|
|
20236
20259
|
queueSize: this.queue.length
|
|
20237
20260
|
});
|
|
20238
20261
|
}
|
|
20239
20262
|
this.queue.push({ payload, retries: 0, eligibleAt: 0 });
|
|
20240
|
-
|
|
20263
|
+
log25.debug("queue:enqueued", { type: payload.type, queueSize: this.queue.length });
|
|
20241
20264
|
}
|
|
20242
20265
|
/** 当前队列长度 */
|
|
20243
20266
|
get size() {
|
|
@@ -20262,7 +20285,7 @@ var PushQueue = class {
|
|
|
20262
20285
|
this.clearTimer();
|
|
20263
20286
|
this.processTimer = setTimeout(() => {
|
|
20264
20287
|
this.tick().catch((err) => {
|
|
20265
|
-
|
|
20288
|
+
log25.error("tick:unexpected-error", { error: err.message });
|
|
20266
20289
|
if (this.running) {
|
|
20267
20290
|
this.scheduleNext(this.idleIntervalMs);
|
|
20268
20291
|
}
|
|
@@ -20284,7 +20307,7 @@ var PushQueue = class {
|
|
|
20284
20307
|
async tick() {
|
|
20285
20308
|
if (!this.running) return;
|
|
20286
20309
|
if (!this.client.isHealthy) {
|
|
20287
|
-
|
|
20310
|
+
log25.debug("tick:service-unhealthy, pausing");
|
|
20288
20311
|
this.scheduleNext(this.unhealthyRetryMs);
|
|
20289
20312
|
return;
|
|
20290
20313
|
}
|
|
@@ -20304,7 +20327,7 @@ var PushQueue = class {
|
|
|
20304
20327
|
try {
|
|
20305
20328
|
await this.processItem(item);
|
|
20306
20329
|
} catch (err) {
|
|
20307
|
-
|
|
20330
|
+
log25.error("tick:process-unexpected", { error: err.message });
|
|
20308
20331
|
} finally {
|
|
20309
20332
|
this.processing = false;
|
|
20310
20333
|
}
|
|
@@ -20323,14 +20346,14 @@ var PushQueue = class {
|
|
|
20323
20346
|
try {
|
|
20324
20347
|
await this.client.push(item.payload);
|
|
20325
20348
|
this.sentCount++;
|
|
20326
|
-
|
|
20349
|
+
log25.debug("push:success", {
|
|
20327
20350
|
type: item.payload.type,
|
|
20328
20351
|
retries: item.retries
|
|
20329
20352
|
});
|
|
20330
20353
|
} catch (err) {
|
|
20331
20354
|
if (err instanceof CockatooPushError && err.isClientError) {
|
|
20332
20355
|
this.droppedCount++;
|
|
20333
|
-
|
|
20356
|
+
log25.warn("push:4xx-dropped", {
|
|
20334
20357
|
type: item.payload.type,
|
|
20335
20358
|
status: err.status,
|
|
20336
20359
|
retries: item.retries
|
|
@@ -20339,7 +20362,7 @@ var PushQueue = class {
|
|
|
20339
20362
|
}
|
|
20340
20363
|
if (item.retries >= this.retryAttempts) {
|
|
20341
20364
|
this.droppedCount++;
|
|
20342
|
-
|
|
20365
|
+
log25.error("push:max-retries-dropped", {
|
|
20343
20366
|
type: item.payload.type,
|
|
20344
20367
|
retries: item.retries,
|
|
20345
20368
|
error: err.message
|
|
@@ -20352,7 +20375,7 @@ var PushQueue = class {
|
|
|
20352
20375
|
item.eligibleAt = Date.now() + backoffMs;
|
|
20353
20376
|
this.queue.push(item);
|
|
20354
20377
|
const errorDetail = err instanceof CockatooPushError ? `HTTP ${err.status}` : err.message;
|
|
20355
|
-
|
|
20378
|
+
log25.warn("push:retry-enqueued", {
|
|
20356
20379
|
type: item.payload.type,
|
|
20357
20380
|
retries: item.retries,
|
|
20358
20381
|
backoffMs,
|
|
@@ -20376,22 +20399,22 @@ var PushQueue = class {
|
|
|
20376
20399
|
try {
|
|
20377
20400
|
await this.client.push(item.payload);
|
|
20378
20401
|
this.sentCount++;
|
|
20379
|
-
|
|
20402
|
+
log25.debug("drain:sent", { type: item.payload.type });
|
|
20380
20403
|
} catch (err) {
|
|
20381
20404
|
this.droppedCount++;
|
|
20382
|
-
|
|
20405
|
+
log25.warn("drain:dropped", {
|
|
20383
20406
|
type: item.payload.type,
|
|
20384
20407
|
error: err.message
|
|
20385
20408
|
});
|
|
20386
20409
|
}
|
|
20387
20410
|
}
|
|
20388
20411
|
if (this.queue.length > 0) {
|
|
20389
|
-
|
|
20412
|
+
log25.warn("drain:timeout", {
|
|
20390
20413
|
remaining: this.queue.length,
|
|
20391
20414
|
timeoutMs: this.drainTimeoutMs
|
|
20392
20415
|
});
|
|
20393
20416
|
} else {
|
|
20394
|
-
|
|
20417
|
+
log25.info("drain:complete");
|
|
20395
20418
|
}
|
|
20396
20419
|
}
|
|
20397
20420
|
// ── 内部工具 ──
|
|
@@ -20405,43 +20428,32 @@ var PushQueue = class {
|
|
|
20405
20428
|
};
|
|
20406
20429
|
|
|
20407
20430
|
// src/index.ts
|
|
20408
|
-
var
|
|
20431
|
+
var log26 = createLogger("plugin");
|
|
20409
20432
|
async function startPlugin(accountConfig, internalOverrides) {
|
|
20410
20433
|
const config2 = buildPluginConfig(accountConfig, internalOverrides);
|
|
20411
|
-
|
|
20434
|
+
log26.info("Config built \u2713", { pluginId: config2.pluginId });
|
|
20412
20435
|
let cryptoEngine;
|
|
20413
20436
|
if (config2.crypto.enabled) {
|
|
20414
|
-
cryptoEngine = new CryptoEngine();
|
|
20437
|
+
cryptoEngine = new CryptoEngine({ credentials: accountConfig });
|
|
20415
20438
|
await cryptoEngine.init();
|
|
20416
|
-
|
|
20439
|
+
log26.info("Crypto initialized \u2713");
|
|
20417
20440
|
} else {
|
|
20418
20441
|
cryptoEngine = CryptoEngine.createPassthrough();
|
|
20419
|
-
|
|
20420
|
-
}
|
|
20421
|
-
const
|
|
20422
|
-
|
|
20423
|
-
|
|
20424
|
-
|
|
20425
|
-
clientSecret: config2.auth.clientSecret,
|
|
20426
|
-
scopes: config2.auth.scopes
|
|
20442
|
+
log26.info("Crypto passthrough mode \u2713 (disabled by config)");
|
|
20443
|
+
}
|
|
20444
|
+
const oauthClient = new OAuthClient({
|
|
20445
|
+
baseUrl: config2.auth.serverUrl,
|
|
20446
|
+
appId: accountConfig.appId,
|
|
20447
|
+
appSecret: accountConfig.appSecret
|
|
20427
20448
|
});
|
|
20428
|
-
const tokenStorePath = (0,
|
|
20449
|
+
const tokenStorePath = (0, import_node_path3.join)((0, import_node_os.homedir)(), TOKEN_STORE_DIR, "tokens");
|
|
20429
20450
|
const tokenStore = new TokenStore(tokenStorePath, cryptoEngine);
|
|
20430
20451
|
const tokenManager = new TokenManager({
|
|
20431
|
-
|
|
20452
|
+
oauthClient,
|
|
20432
20453
|
tokenStore,
|
|
20433
|
-
refreshAheadMs: config2.auth.refreshAheadMs
|
|
20434
|
-
autoRegisterParams: config2.auth.autoRegister ? {
|
|
20435
|
-
clientName: config2.auth.clientName,
|
|
20436
|
-
scopes: config2.auth.scopes,
|
|
20437
|
-
tokenSettings: config2.auth.tokenSettings ? {
|
|
20438
|
-
accessTokenTtl: config2.auth.tokenSettings.accessTokenTtl,
|
|
20439
|
-
refreshTokenTtl: config2.auth.tokenSettings.refreshTokenTtl,
|
|
20440
|
-
accessTokenFormat: config2.auth.tokenSettings.accessTokenFormat
|
|
20441
|
-
} : void 0
|
|
20442
|
-
} : void 0
|
|
20454
|
+
refreshAheadMs: config2.auth.refreshAheadMs
|
|
20443
20455
|
});
|
|
20444
|
-
|
|
20456
|
+
log26.info("Auth initialized \u2713");
|
|
20445
20457
|
let pushQueue = null;
|
|
20446
20458
|
let cockatooClient = null;
|
|
20447
20459
|
if (config2.push?.enabled) {
|
|
@@ -20456,7 +20468,7 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20456
20468
|
});
|
|
20457
20469
|
cockatooClient.startHealthCheck();
|
|
20458
20470
|
pushQueue.start();
|
|
20459
|
-
|
|
20471
|
+
log26.info("Push initialized \u2713");
|
|
20460
20472
|
}
|
|
20461
20473
|
const wsClient = new WSClient();
|
|
20462
20474
|
const dedup = new MessageDedup(config2.transport.dedup);
|
|
@@ -20474,8 +20486,8 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20474
20486
|
reconnectMaxMs: config2.transport.reconnectMaxMs,
|
|
20475
20487
|
maxReconnectAttempts: config2.transport.maxReconnectAttempts
|
|
20476
20488
|
});
|
|
20477
|
-
|
|
20478
|
-
|
|
20489
|
+
log26.info("Transport initialized \u2713");
|
|
20490
|
+
log26.info("Plugin started \u2713");
|
|
20479
20491
|
return {
|
|
20480
20492
|
config: config2,
|
|
20481
20493
|
messagePipe,
|
|
@@ -20483,12 +20495,12 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20483
20495
|
tokenManager,
|
|
20484
20496
|
pushQueue,
|
|
20485
20497
|
shutdown: async () => {
|
|
20486
|
-
|
|
20498
|
+
log26.info("Shutting down...");
|
|
20487
20499
|
await connectionManager.stop();
|
|
20488
20500
|
if (pushQueue) await pushQueue.stop();
|
|
20489
20501
|
if (cockatooClient) cockatooClient.stopHealthCheck();
|
|
20490
20502
|
tokenManager.shutdown();
|
|
20491
|
-
|
|
20503
|
+
log26.info("Shutdown complete \u2713");
|
|
20492
20504
|
}
|
|
20493
20505
|
};
|
|
20494
20506
|
}
|
|
@@ -20498,7 +20510,7 @@ var plugin = {
|
|
|
20498
20510
|
description: "Quantum-encrypted IM channel plugin",
|
|
20499
20511
|
register(api) {
|
|
20500
20512
|
api.registerChannel({ plugin: quantumImPlugin });
|
|
20501
|
-
|
|
20513
|
+
log26.info("plugin registered \u2713");
|
|
20502
20514
|
}
|
|
20503
20515
|
};
|
|
20504
20516
|
var index_default = plugin;
|