@sfmc-bds/db-server 0.3.2 → 0.5.0
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/domain/qq-events.d.ts +22 -30
- package/dist/domain/qq-events.d.ts.map +1 -1
- package/dist/domain/qq-events.js +14 -69
- package/dist/domain/qq-events.js.map +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +2 -2
- package/dist/module-auth.d.ts +1 -1
- package/dist/module-auth.d.ts.map +1 -1
- package/dist/module-auth.js +3 -1
- package/dist/module-auth.js.map +2 -2
- package/dist/routes/qq-events.d.ts +4 -4
- package/dist/routes/qq-events.d.ts.map +1 -1
- package/dist/routes/qq-events.js +2 -11
- package/dist/routes/qq-events.js.map +2 -2
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +11 -8
- package/dist/service-registry.js.map +2 -2
- package/package.json +4 -4
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* domain/qq-events.ts — Minecraft
|
|
2
|
+
* domain/qq-events.ts — Minecraft 服务器事件群推送
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - 玩家进退与死亡(join / leave / death):在时间窗口内收集缓冲,合并为单条消息发送,避免刷屏
|
|
6
|
-
* - 关键运行态事件(crash / start / stop):先立即冲刷(flush)当前窗口内的待发送事件,随后立即单独推送
|
|
4
|
+
* 推送策略:所有事件(join / leave / death / crash / start / stop)收到即单条实时发送,不做窗口缓冲与合并。
|
|
7
5
|
*/
|
|
8
6
|
import { type QqEventsConfig } from "@sfmc-bds/sdk/node/config";
|
|
9
7
|
import type { OutboundConfig } from "./bridge.js";
|
|
@@ -15,10 +13,9 @@ export type QqEventPayload = {
|
|
|
15
13
|
detail?: string;
|
|
16
14
|
};
|
|
17
15
|
export type ResolvedQqEventsConfig = Required<QqEventsConfig>;
|
|
18
|
-
/** 窗口内条数上限:达到上限时提前触发 flush,避免单次积压过多内容。 */
|
|
19
|
-
export declare const MAX_WINDOW_EVENTS = 20;
|
|
20
16
|
/**
|
|
21
17
|
* 解析并填充 QQ 事件推送配置的默认值。
|
|
18
|
+
* 旧配置中遗留的 window_sec 不再读取;写回时随 qq_events 整体覆盖而被移除。
|
|
22
19
|
*
|
|
23
20
|
* @param raw 原始配置对象。
|
|
24
21
|
* @returns 规范化的完整配置对象。
|
|
@@ -32,41 +29,40 @@ export declare function resolveQqEventsConfig(raw: unknown): ResolvedQqEventsCon
|
|
|
32
29
|
*/
|
|
33
30
|
export declare function localizeCause(cause: string | undefined): string;
|
|
34
31
|
export declare function isEventTypeEnabled(cfg: ResolvedQqEventsConfig, type: QqEventType): boolean;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export type QqEventsAggregatorDeps = {
|
|
32
|
+
/**
|
|
33
|
+
* 事件推送器的依赖注入。
|
|
34
|
+
* 当前由 db-server index.ts 注入配置读取与出站配置;单元测试可注入 send 拦截发送。
|
|
35
|
+
*/
|
|
36
|
+
export type QqEventsDispatcherDeps = {
|
|
41
37
|
getConfig: () => ResolvedQqEventsConfig;
|
|
42
38
|
getOutbound: () => OutboundConfig;
|
|
43
|
-
/** 可选注入:供单元测试模拟时钟与定时器。 */
|
|
44
|
-
setTimeoutFn?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
45
|
-
clearTimeoutFn?: (id: ReturnType<typeof setTimeout>) => void;
|
|
46
39
|
send?: (text: string) => void;
|
|
47
40
|
};
|
|
48
41
|
/**
|
|
49
|
-
*
|
|
42
|
+
* 格式化单条玩家事件文本(上线 / 下线 / 死亡),每个事件独立成一条消息。
|
|
50
43
|
*
|
|
51
|
-
* @param
|
|
52
|
-
* @
|
|
44
|
+
* @param type 玩家事件类型。
|
|
45
|
+
* @param player 玩家名。
|
|
46
|
+
* @param cause 死亡原因(仅 death 使用,可为空)。
|
|
47
|
+
* @returns 单行通知文本,如「[MC事件] Steve 上线」「[MC事件] Bob 死亡(坠落)」。
|
|
53
48
|
*/
|
|
54
|
-
export declare function
|
|
49
|
+
export declare function formatPlayerBody(type: "join" | "leave" | "death", player: string, cause?: string): string;
|
|
55
50
|
/**
|
|
56
|
-
*
|
|
51
|
+
* 格式化 BDS 运行态事件文本(如 BDS 崩溃或启动就绪)。
|
|
57
52
|
*
|
|
58
|
-
* @param ev
|
|
53
|
+
* @param ev 运行态事件载荷。
|
|
59
54
|
* @returns 格式化后的事件通知文本。
|
|
60
55
|
*/
|
|
61
56
|
export declare function formatImmediateBody(ev: QqEventPayload): string;
|
|
62
57
|
export declare function normalizeEventPayload(raw: unknown): QqEventPayload | null;
|
|
63
58
|
/**
|
|
64
|
-
* 创建 QQ
|
|
59
|
+
* 创建 QQ 服务器事件推送器:每个事件收到后立即单条发送到群。
|
|
60
|
+
* 当前由 routes/qq-events.ts 的 POST /api/sfmc/qq/events 调用。
|
|
65
61
|
*
|
|
66
|
-
* @param deps
|
|
67
|
-
* @returns 包含
|
|
62
|
+
* @param deps 依赖注入对象(配置获取、出站配置及可选的发送实现)。
|
|
63
|
+
* @returns 包含 ingestOne / ingestMany 的推送器。
|
|
68
64
|
*/
|
|
69
|
-
export declare function
|
|
65
|
+
export declare function createQqEventsDispatcher(deps: QqEventsDispatcherDeps): {
|
|
70
66
|
ingestOne: (ev: QqEventPayload) => {
|
|
71
67
|
accepted: boolean;
|
|
72
68
|
reason?: string;
|
|
@@ -75,10 +71,6 @@ export declare function createQqEventsAggregator(deps: QqEventsAggregatorDeps):
|
|
|
75
71
|
accepted: number;
|
|
76
72
|
rejected: number;
|
|
77
73
|
};
|
|
78
|
-
flushWindow: () => void;
|
|
79
|
-
dispose: () => void;
|
|
80
|
-
pendingCount: () => number;
|
|
81
74
|
};
|
|
82
|
-
export type
|
|
83
|
-
export {};
|
|
75
|
+
export type QqEventsDispatcher = ReturnType<typeof createQqEventsDispatcher>;
|
|
84
76
|
//# sourceMappingURL=qq-events.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qq-events.d.ts","sourceRoot":"","sources":["../../src/domain/qq-events.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"qq-events.d.ts","sourceRoot":"","sources":["../../src/domain/qq-events.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAElF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;AA4C9D;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,CAW1E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK/D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,sBAAsB,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAG1F;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,sBAAsB,CAAC;IACxC,WAAW,EAAE,MAAM,cAAc,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CAW9D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAmBzE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,sBAAsB;oBAO5C,cAAc,KAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;wBAkBnD,cAAc,EAAE,KAAG;QAC5C,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB;EAYF;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
package/dist/domain/qq-events.js
CHANGED
|
@@ -2,9 +2,8 @@ import {
|
|
|
2
2
|
DEFAULT_QQ_EVENTS
|
|
3
3
|
} from "@sfmc-bds/sdk/node/config";
|
|
4
4
|
import { sendGroupOutbound } from "./bridge.js";
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const MAX_WINDOW_EVENTS = 20;
|
|
5
|
+
const PLAYER_TYPES = /* @__PURE__ */ new Set(["join", "leave", "death"]);
|
|
6
|
+
const LIFECYCLE_TYPES = /* @__PURE__ */ new Set(["crash", "start", "stop"]);
|
|
8
7
|
const CAUSE_ZH = {
|
|
9
8
|
anvil: "\u94C1\u7827",
|
|
10
9
|
blockExplosion: "\u65B9\u5757\u7206\u70B8",
|
|
@@ -42,10 +41,8 @@ const CAUSE_ZH = {
|
|
|
42
41
|
};
|
|
43
42
|
function resolveQqEventsConfig(raw) {
|
|
44
43
|
const o = raw && typeof raw === "object" ? raw : {};
|
|
45
|
-
const windowSec = Number(o.window_sec ?? DEFAULT_QQ_EVENTS.window_sec);
|
|
46
44
|
return {
|
|
47
45
|
enabled: o.enabled === void 0 ? DEFAULT_QQ_EVENTS.enabled : o.enabled === true,
|
|
48
|
-
window_sec: Number.isFinite(windowSec) && windowSec >= 5 ? Math.min(600, Math.floor(windowSec)) : DEFAULT_QQ_EVENTS.window_sec,
|
|
49
46
|
join: o.join === void 0 ? DEFAULT_QQ_EVENTS.join : o.join === true,
|
|
50
47
|
leave: o.leave === void 0 ? DEFAULT_QQ_EVENTS.leave : o.leave === true,
|
|
51
48
|
death: o.death === void 0 ? DEFAULT_QQ_EVENTS.death : o.death === true,
|
|
@@ -64,23 +61,11 @@ function isEventTypeEnabled(cfg, type) {
|
|
|
64
61
|
if (!cfg.enabled) return false;
|
|
65
62
|
return cfg[type] === true;
|
|
66
63
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
if (e.type === "join") joins.push(e.player);
|
|
73
|
-
else if (e.type === "leave") leaves.push(e.player);
|
|
74
|
-
else {
|
|
75
|
-
const c = localizeCause(e.cause);
|
|
76
|
-
deaths.push(c ? `${e.player}\uFF08${c}\uFF09` : e.player);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
const lines = ["[MC\u4E8B\u4EF6]"];
|
|
80
|
-
if (joins.length) lines.push(`\u4E0A\u7EBF\uFF1A${joins.join("\u3001")}`);
|
|
81
|
-
if (leaves.length) lines.push(`\u4E0B\u7EBF\uFF1A${leaves.join("\u3001")}`);
|
|
82
|
-
if (deaths.length) lines.push(`\u6B7B\u4EA1\uFF1A${deaths.join("\u3001")}`);
|
|
83
|
-
return lines.join("\n");
|
|
64
|
+
function formatPlayerBody(type, player, cause) {
|
|
65
|
+
if (type === "join") return `[MC\u4E8B\u4EF6] ${player} \u4E0A\u7EBF`;
|
|
66
|
+
if (type === "leave") return `[MC\u4E8B\u4EF6] ${player} \u4E0B\u7EBF`;
|
|
67
|
+
const c = localizeCause(cause);
|
|
68
|
+
return c ? `[MC\u4E8B\u4EF6] ${player} \u6B7B\u4EA1\uFF08${c}\uFF09` : `[MC\u4E8B\u4EF6] ${player} \u6B7B\u4EA1`;
|
|
84
69
|
}
|
|
85
70
|
function formatImmediateBody(ev) {
|
|
86
71
|
if (ev.type === "crash") {
|
|
@@ -98,7 +83,7 @@ function normalizeEventPayload(raw) {
|
|
|
98
83
|
if (!raw || typeof raw !== "object") return null;
|
|
99
84
|
const o = raw;
|
|
100
85
|
const type = String(o.type ?? "").trim();
|
|
101
|
-
if (!
|
|
86
|
+
if (!PLAYER_TYPES.has(type) && !LIFECYCLE_TYPES.has(type)) return null;
|
|
102
87
|
const out = { type };
|
|
103
88
|
if (o.player !== void 0) {
|
|
104
89
|
const player = String(o.player).trim();
|
|
@@ -114,55 +99,22 @@ function normalizeEventPayload(raw) {
|
|
|
114
99
|
}
|
|
115
100
|
return out;
|
|
116
101
|
}
|
|
117
|
-
function
|
|
118
|
-
const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;
|
|
119
|
-
const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;
|
|
102
|
+
function createQqEventsDispatcher(deps) {
|
|
120
103
|
const send = deps.send ?? ((text) => {
|
|
121
104
|
sendGroupOutbound(deps.getOutbound(), text);
|
|
122
105
|
});
|
|
123
|
-
let buffer = [];
|
|
124
|
-
let timer = null;
|
|
125
|
-
function clearTimer() {
|
|
126
|
-
if (timer != null) {
|
|
127
|
-
clearTimeoutFn(timer);
|
|
128
|
-
timer = null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function flushWindow() {
|
|
132
|
-
clearTimer();
|
|
133
|
-
if (buffer.length === 0) return;
|
|
134
|
-
const cfg = deps.getConfig();
|
|
135
|
-
const snapshot = buffer.filter((event) => isEventTypeEnabled(cfg, event.type));
|
|
136
|
-
buffer = [];
|
|
137
|
-
const text = formatWindowBody(snapshot);
|
|
138
|
-
if (text.split("\n").length > 1) send(text);
|
|
139
|
-
}
|
|
140
|
-
function scheduleFlush(windowSec) {
|
|
141
|
-
if (timer != null) return;
|
|
142
|
-
timer = setTimeoutFn(() => {
|
|
143
|
-
timer = null;
|
|
144
|
-
flushWindow();
|
|
145
|
-
}, windowSec * 1e3);
|
|
146
|
-
}
|
|
147
106
|
function ingestOne(ev) {
|
|
148
107
|
const cfg = deps.getConfig();
|
|
149
108
|
if (!isEventTypeEnabled(cfg, ev.type)) {
|
|
150
109
|
return { accepted: false, reason: "disabled" };
|
|
151
110
|
}
|
|
152
|
-
if (
|
|
153
|
-
flushWindow();
|
|
111
|
+
if (LIFECYCLE_TYPES.has(ev.type)) {
|
|
154
112
|
send(formatImmediateBody(ev));
|
|
155
113
|
return { accepted: true };
|
|
156
114
|
}
|
|
157
115
|
const player = String(ev.player ?? "").trim();
|
|
158
116
|
if (!player) return { accepted: false, reason: "missing_player" };
|
|
159
|
-
|
|
160
|
-
type: ev.type,
|
|
161
|
-
player,
|
|
162
|
-
...ev.cause ? { cause: ev.cause } : {}
|
|
163
|
-
});
|
|
164
|
-
scheduleFlush(cfg.window_sec);
|
|
165
|
-
if (buffer.length >= MAX_WINDOW_EVENTS) flushWindow();
|
|
117
|
+
send(formatPlayerBody(ev.type, player, ev.cause));
|
|
166
118
|
return { accepted: true };
|
|
167
119
|
}
|
|
168
120
|
function ingestMany(items) {
|
|
@@ -175,19 +127,12 @@ function createQqEventsAggregator(deps) {
|
|
|
175
127
|
}
|
|
176
128
|
return { accepted, rejected };
|
|
177
129
|
}
|
|
178
|
-
|
|
179
|
-
flushWindow();
|
|
180
|
-
}
|
|
181
|
-
function pendingCount() {
|
|
182
|
-
return buffer.length;
|
|
183
|
-
}
|
|
184
|
-
return { ingestOne, ingestMany, flushWindow, dispose, pendingCount };
|
|
130
|
+
return { ingestOne, ingestMany };
|
|
185
131
|
}
|
|
186
132
|
export {
|
|
187
|
-
|
|
188
|
-
createQqEventsAggregator,
|
|
133
|
+
createQqEventsDispatcher,
|
|
189
134
|
formatImmediateBody,
|
|
190
|
-
|
|
135
|
+
formatPlayerBody,
|
|
191
136
|
isEventTypeEnabled,
|
|
192
137
|
localizeCause,
|
|
193
138
|
normalizeEventPayload,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/domain/qq-events.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * domain/qq-events.ts \u2014 Minecraft \u670D\u52A1\u5668\u4E8B\u4EF6\u7FA4\u63A8\u9001\u805A\u5408\u8282\u6D41\u5668\n *\n * \u805A\u5408\u7B56\u7565\uFF1A\n * - \u73A9\u5BB6\u8FDB\u9000\u4E0E\u6B7B\u4EA1\uFF08join / leave / death\uFF09\uFF1A\u5728\u65F6\u95F4\u7A97\u53E3\u5185\u6536\u96C6\u7F13\u51B2\uFF0C\u5408\u5E76\u4E3A\u5355\u6761\u6D88\u606F\u53D1\u9001\uFF0C\u907F\u514D\u5237\u5C4F\n * - \u5173\u952E\u8FD0\u884C\u6001\u4E8B\u4EF6\uFF08crash / start / stop\uFF09\uFF1A\u5148\u7ACB\u5373\u51B2\u5237\uFF08flush\uFF09\u5F53\u524D\u7A97\u53E3\u5185\u7684\u5F85\u53D1\u9001\u4E8B\u4EF6\uFF0C\u968F\u540E\u7ACB\u5373\u5355\u72EC\u63A8\u9001\n */\n\nimport {\n DEFAULT_QQ_EVENTS,\n type QqEventsConfig,\n} from \"@sfmc-bds/sdk/node/config\";\n\nimport type { OutboundConfig } from \"./bridge.js\";\nimport { sendGroupOutbound } from \"./bridge.js\";\n\nexport type QqEventType = \"join\" | \"leave\" | \"death\" | \"crash\" | \"start\" | \"stop\";\n\nexport type QqEventPayload = {\n type: QqEventType;\n player?: string;\n cause?: string;\n detail?: string;\n};\n\nexport type ResolvedQqEventsConfig = Required<QqEventsConfig>;\n\nconst WINDOW_TYPES = new Set<QqEventType>([\"join\", \"leave\", \"death\"]);\nconst IMMEDIATE_TYPES = new Set<QqEventType>([\"crash\", \"start\", \"stop\"]);\n\n/** \u7A97\u53E3\u5185\u6761\u6570\u4E0A\u9650\uFF1A\u8FBE\u5230\u4E0A\u9650\u65F6\u63D0\u524D\u89E6\u53D1 flush\uFF0C\u907F\u514D\u5355\u6B21\u79EF\u538B\u8FC7\u591A\u5185\u5BB9\u3002 */\nexport const MAX_WINDOW_EVENTS = 20;\n\n/** SAPI damageSource.cause \u2192 \u4E2D\u6587\u6620\u5C04\u5B57\u5178\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u6807\u8BC6\uFF09\u3002 */\nconst CAUSE_ZH: Record<string, string> = {\n anvil: \"\u94C1\u7827\",\n blockExplosion: \"\u65B9\u5757\u7206\u70B8\",\n campfire: \"\u8425\u706B\",\n drowning: \"\u6EBA\u6C34\",\n entityAttack: \"\u751F\u7269\u653B\u51FB\",\n entityExplosion: \"\u5B9E\u4F53\u7206\u70B8\",\n fall: \"\u5760\u843D\",\n fallingBlock: \"\u843D\u77F3\",\n fire: \"\u706B\u7130\",\n fireTick: \"\u707C\u70E7\",\n flyIntoWall: \"\u649E\u5899\",\n freezing: \"\u51BB\u7ED3\",\n lava: \"\u5CA9\u6D46\",\n lightning: \"\u96F7\u51FB\",\n magic: \"\u9B54\u6CD5\",\n magma: \"\u5CA9\u6D46\u5757\",\n none: \"\u672A\u77E5\",\n override: \"\u5F3A\u5236\",\n piston: \"\u6D3B\u585E\",\n projectile: \"\u5F39\u5C04\u7269\",\n stalactite: \"\u949F\u4E73\u77F3\",\n stalagmite: \"\u77F3\u7B0B\",\n starve: \"\u9965\u997F\",\n suffocation: \"\u7A92\u606F\",\n suicide: \"\u81EA\u6740\",\n thorns: \"\u8346\u68D8\",\n void: \"\u865A\u7A7A\",\n wither: \"\u51CB\u96F6\",\n // \u5E38\u89C1\u751F\u7269\u540D\uFF08\u82E5\u4E0A\u5C42\u5C06\u5B9E\u4F53\u540D\u586B\u5145\u81F3 cause \u5B57\u6BB5\uFF09\n zombie: \"\u50F5\u5C38\",\n skeleton: \"\u9AB7\u9AC5\",\n creeper: \"\u82E6\u529B\u6015\",\n player: \"\u73A9\u5BB6\",\n};\n\n/**\n * \u89E3\u6790\u5E76\u586B\u5145 QQ \u4E8B\u4EF6\u63A8\u9001\u914D\u7F6E\u7684\u9ED8\u8BA4\u503C\u3002\n *\n * @param raw \u539F\u59CB\u914D\u7F6E\u5BF9\u8C61\u3002\n * @returns \u89C4\u8303\u5316\u7684\u5B8C\u6574\u914D\u7F6E\u5BF9\u8C61\u3002\n */\nexport function resolveQqEventsConfig(raw: unknown): ResolvedQqEventsConfig {\n const o = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n const windowSec = Number(o.window_sec ?? DEFAULT_QQ_EVENTS.window_sec);\n return {\n enabled: o.enabled === undefined ? DEFAULT_QQ_EVENTS.enabled : o.enabled === true,\n window_sec:\n Number.isFinite(windowSec) && windowSec >= 5\n ? Math.min(600, Math.floor(windowSec))\n : DEFAULT_QQ_EVENTS.window_sec,\n join: o.join === undefined ? DEFAULT_QQ_EVENTS.join : o.join === true,\n leave: o.leave === undefined ? DEFAULT_QQ_EVENTS.leave : o.leave === true,\n death: o.death === undefined ? DEFAULT_QQ_EVENTS.death : o.death === true,\n crash: o.crash === undefined ? DEFAULT_QQ_EVENTS.crash : o.crash === true,\n start: o.start === undefined ? DEFAULT_QQ_EVENTS.start : o.start === true,\n stop: o.stop === undefined ? DEFAULT_QQ_EVENTS.stop : o.stop === true,\n };\n}\n\n/**\n * \u5C06 SAPI \u539F\u751F\u4F24\u5BB3\u6765\u6E90\u4EE3\u7801\uFF08damageSource.cause\uFF09\u8F6C\u6362\u4E3A\u6613\u8BFB\u7684\u4E2D\u6587\u6587\u672C\u3002\n *\n * @param cause \u4F24\u5BB3\u6765\u6E90\u6807\u8BC6\u3002\n * @returns \u4E2D\u6587\u4F24\u5BB3\u63CF\u8FF0\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u4EE3\u7801\uFF09\u3002\n */\nexport function localizeCause(cause: string | undefined): string {\n if (!cause) return \"\";\n const key = cause.trim();\n if (!key) return \"\";\n return CAUSE_ZH[key] ?? CAUSE_ZH[key.toLowerCase()] ?? key;\n}\n\nexport function isEventTypeEnabled(cfg: ResolvedQqEventsConfig, type: QqEventType): boolean {\n if (!cfg.enabled) return false;\n return cfg[type] === true;\n}\n\ntype Buffered = {\n type: \"join\" | \"leave\" | \"death\";\n player: string;\n cause?: string;\n};\n\nexport type QqEventsAggregatorDeps = {\n getConfig: () => ResolvedQqEventsConfig;\n getOutbound: () => OutboundConfig;\n /** \u53EF\u9009\u6CE8\u5165\uFF1A\u4F9B\u5355\u5143\u6D4B\u8BD5\u6A21\u62DF\u65F6\u949F\u4E0E\u5B9A\u65F6\u5668\u3002 */\n setTimeoutFn?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n clearTimeoutFn?: (id: ReturnType<typeof setTimeout>) => void;\n send?: (text: string) => void;\n};\n\n/**\n * \u683C\u5F0F\u5316\u65F6\u95F4\u7A97\u53E3\u5185\u7684\u805A\u5408\u4E8B\u4EF6\u6587\u672C\u3002\n *\n * @param events \u7F13\u51B2\u533A\u5185\u7684\u4E8B\u4EF6\u5217\u8868\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u591A\u884C\u6D88\u606F\u5B57\u7B26\u4E32\u3002\n */\nexport function formatWindowBody(events: Buffered[]): string {\n const joins: string[] = [];\n const leaves: string[] = [];\n const deaths: string[] = [];\n for (const e of events) {\n if (e.type === \"join\") joins.push(e.player);\n else if (e.type === \"leave\") leaves.push(e.player);\n else {\n const c = localizeCause(e.cause);\n deaths.push(c ? `${e.player}\uFF08${c}\uFF09` : e.player);\n }\n }\n const lines: string[] = [\"[MC\u4E8B\u4EF6]\"];\n if (joins.length) lines.push(`\u4E0A\u7EBF\uFF1A${joins.join(\"\u3001\")}`);\n if (leaves.length) lines.push(`\u4E0B\u7EBF\uFF1A${leaves.join(\"\u3001\")}`);\n if (deaths.length) lines.push(`\u6B7B\u4EA1\uFF1A${deaths.join(\"\u3001\")}`);\n return lines.join(\"\\n\");\n}\n\n/**\n * \u683C\u5F0F\u5316\u9AD8\u4F18\u5148\u7EA7\u7684\u975E\u805A\u5408\u5373\u65F6\u4E8B\u4EF6\u6587\u672C\uFF08\u5982 BDS \u5D29\u6E83\u6216\u542F\u52A8\u5C31\u7EEA\uFF09\u3002\n *\n * @param ev \u5373\u65F6\u4E8B\u4EF6\u8F7D\u8377\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u4E8B\u4EF6\u901A\u77E5\u6587\u672C\u3002\n */\nexport function formatImmediateBody(ev: QqEventPayload): string {\n if (ev.type === \"crash\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (${d})` : \"[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA\";\n }\n if (ev.type === \"start\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (${d})` : \"[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8\";\n }\n if (ev.type === \"stop\") return \"[MC\u4E8B\u4EF6] BDS \u5DF2\u505C\u6B62\";\n return `[MC\u4E8B\u4EF6] ${ev.type}`;\n}\n\nexport function normalizeEventPayload(raw: unknown): QqEventPayload | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const type = String(o.type ?? \"\").trim() as QqEventType;\n if (!WINDOW_TYPES.has(type) && !IMMEDIATE_TYPES.has(type)) return null;\n const out: QqEventPayload = { type };\n if (o.player !== undefined) {\n const player = String(o.player).trim();\n if (player) out.player = player;\n }\n if (o.cause !== undefined) {\n const cause = String(o.cause).trim();\n if (cause) out.cause = cause;\n }\n if (o.detail !== undefined) {\n const detail = String(o.detail).trim();\n if (detail) out.detail = detail;\n }\n return out;\n}\n\n/**\n * \u521B\u5EFA QQ \u670D\u52A1\u5668\u4E8B\u4EF6\u805A\u5408\u8282\u6D41\u5668\u5B9E\u4F8B\u3002\n *\n * @param deps \u4F9D\u8D56\u6CE8\u5165\u5BF9\u8C61\uFF08\u5305\u542B\u914D\u7F6E\u83B7\u53D6\u3001\u51FA\u7AD9\u53D1\u9001\u53CA\u5B9A\u65F6\u5668\u5B9E\u73B0\uFF09\u3002\n * @returns \u5305\u542B push, flush, cancel, getPendingCount \u7B49\u65B9\u6CD5\u7684\u805A\u5408\u5668\u63A7\u5236\u5668\u3002\n */\nexport function createQqEventsAggregator(deps: QqEventsAggregatorDeps) {\n\n const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;\n const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;\n const send =\n deps.send ??\n ((text: string) => {\n sendGroupOutbound(deps.getOutbound(), text);\n });\n\n let buffer: Buffered[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function clearTimer(): void {\n if (timer != null) {\n clearTimeoutFn(timer);\n timer = null;\n }\n }\n\n function flushWindow(): void {\n clearTimer();\n if (buffer.length === 0) return;\n const cfg = deps.getConfig();\n const snapshot = buffer.filter((event) => isEventTypeEnabled(cfg, event.type));\n buffer = [];\n const text = formatWindowBody(snapshot);\n if (text.split(\"\\n\").length > 1) send(text);\n }\n\n function scheduleFlush(windowSec: number): void {\n if (timer != null) return;\n timer = setTimeoutFn(() => {\n timer = null;\n flushWindow();\n }, windowSec * 1000);\n }\n\n function ingestOne(ev: QqEventPayload): { accepted: boolean; reason?: string } {\n const cfg = deps.getConfig();\n if (!isEventTypeEnabled(cfg, ev.type)) {\n return { accepted: false, reason: \"disabled\" };\n }\n\n if (IMMEDIATE_TYPES.has(ev.type)) {\n flushWindow();\n send(formatImmediateBody(ev));\n return { accepted: true };\n }\n\n const player = String(ev.player ?? \"\").trim();\n if (!player) return { accepted: false, reason: \"missing_player\" };\n\n buffer.push({\n type: ev.type as \"join\" | \"leave\" | \"death\",\n player,\n ...(ev.cause ? { cause: ev.cause } : {}),\n });\n scheduleFlush(cfg.window_sec);\n if (buffer.length >= MAX_WINDOW_EVENTS) flushWindow();\n return { accepted: true };\n }\n\n function ingestMany(items: QqEventPayload[]): {\n accepted: number;\n rejected: number;\n } {\n let accepted = 0;\n let rejected = 0;\n for (const ev of items) {\n const r = ingestOne(ev);\n if (r.accepted) accepted += 1;\n else rejected += 1;\n }\n return { accepted, rejected };\n }\n\n /** \u6D4B\u8BD5 / \u5173\u505C\uFF1A\u51B2\u5237\u5E76\u6E05\u5B9A\u65F6\u5668 */\n function dispose(): void {\n flushWindow();\n }\n\n /** \u4EC5\u6D4B\u7528\uFF1A\u5F53\u524D\u7F13\u51B2\u957F\u5EA6 */\n function pendingCount(): number {\n return buffer.length;\n }\n\n return { ingestOne, ingestMany, flushWindow, dispose, pendingCount };\n}\n\nexport type QqEventsAggregator = ReturnType<typeof createQqEventsAggregator>;\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * domain/qq-events.ts \u2014 Minecraft \u670D\u52A1\u5668\u4E8B\u4EF6\u7FA4\u63A8\u9001\n *\n * \u63A8\u9001\u7B56\u7565\uFF1A\u6240\u6709\u4E8B\u4EF6\uFF08join / leave / death / crash / start / stop\uFF09\u6536\u5230\u5373\u5355\u6761\u5B9E\u65F6\u53D1\u9001\uFF0C\u4E0D\u505A\u7A97\u53E3\u7F13\u51B2\u4E0E\u5408\u5E76\u3002\n */\n\nimport {\n DEFAULT_QQ_EVENTS,\n type QqEventsConfig,\n} from \"@sfmc-bds/sdk/node/config\";\n\nimport type { OutboundConfig } from \"./bridge.js\";\nimport { sendGroupOutbound } from \"./bridge.js\";\n\nexport type QqEventType = \"join\" | \"leave\" | \"death\" | \"crash\" | \"start\" | \"stop\";\n\nexport type QqEventPayload = {\n type: QqEventType;\n player?: string;\n cause?: string;\n detail?: string;\n};\n\nexport type ResolvedQqEventsConfig = Required<QqEventsConfig>;\n\n/** \u73A9\u5BB6\u7C7B\u4E8B\u4EF6\uFF1A\u5FC5\u987B\u643A\u5E26 player \u5B57\u6BB5\u3002 */\nconst PLAYER_TYPES = new Set<QqEventType>([\"join\", \"leave\", \"death\"]);\n/** BDS \u8FD0\u884C\u6001\u4E8B\u4EF6\uFF1A\u65E0\u9700 player \u5B57\u6BB5\u3002 */\nconst LIFECYCLE_TYPES = new Set<QqEventType>([\"crash\", \"start\", \"stop\"]);\n\n/** SAPI damageSource.cause \u2192 \u4E2D\u6587\u6620\u5C04\u5B57\u5178\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u6807\u8BC6\uFF09\u3002 */\nconst CAUSE_ZH: Record<string, string> = {\n anvil: \"\u94C1\u7827\",\n blockExplosion: \"\u65B9\u5757\u7206\u70B8\",\n campfire: \"\u8425\u706B\",\n drowning: \"\u6EBA\u6C34\",\n entityAttack: \"\u751F\u7269\u653B\u51FB\",\n entityExplosion: \"\u5B9E\u4F53\u7206\u70B8\",\n fall: \"\u5760\u843D\",\n fallingBlock: \"\u843D\u77F3\",\n fire: \"\u706B\u7130\",\n fireTick: \"\u707C\u70E7\",\n flyIntoWall: \"\u649E\u5899\",\n freezing: \"\u51BB\u7ED3\",\n lava: \"\u5CA9\u6D46\",\n lightning: \"\u96F7\u51FB\",\n magic: \"\u9B54\u6CD5\",\n magma: \"\u5CA9\u6D46\u5757\",\n none: \"\u672A\u77E5\",\n override: \"\u5F3A\u5236\",\n piston: \"\u6D3B\u585E\",\n projectile: \"\u5F39\u5C04\u7269\",\n stalactite: \"\u949F\u4E73\u77F3\",\n stalagmite: \"\u77F3\u7B0B\",\n starve: \"\u9965\u997F\",\n suffocation: \"\u7A92\u606F\",\n suicide: \"\u81EA\u6740\",\n thorns: \"\u8346\u68D8\",\n void: \"\u865A\u7A7A\",\n wither: \"\u51CB\u96F6\",\n // \u5E38\u89C1\u751F\u7269\u540D\uFF08\u82E5\u4E0A\u5C42\u5C06\u5B9E\u4F53\u540D\u586B\u5145\u81F3 cause \u5B57\u6BB5\uFF09\n zombie: \"\u50F5\u5C38\",\n skeleton: \"\u9AB7\u9AC5\",\n creeper: \"\u82E6\u529B\u6015\",\n player: \"\u73A9\u5BB6\",\n};\n\n/**\n * \u89E3\u6790\u5E76\u586B\u5145 QQ \u4E8B\u4EF6\u63A8\u9001\u914D\u7F6E\u7684\u9ED8\u8BA4\u503C\u3002\n * \u65E7\u914D\u7F6E\u4E2D\u9057\u7559\u7684 window_sec \u4E0D\u518D\u8BFB\u53D6\uFF1B\u5199\u56DE\u65F6\u968F qq_events \u6574\u4F53\u8986\u76D6\u800C\u88AB\u79FB\u9664\u3002\n *\n * @param raw \u539F\u59CB\u914D\u7F6E\u5BF9\u8C61\u3002\n * @returns \u89C4\u8303\u5316\u7684\u5B8C\u6574\u914D\u7F6E\u5BF9\u8C61\u3002\n */\nexport function resolveQqEventsConfig(raw: unknown): ResolvedQqEventsConfig {\n const o = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n return {\n enabled: o.enabled === undefined ? DEFAULT_QQ_EVENTS.enabled : o.enabled === true,\n join: o.join === undefined ? DEFAULT_QQ_EVENTS.join : o.join === true,\n leave: o.leave === undefined ? DEFAULT_QQ_EVENTS.leave : o.leave === true,\n death: o.death === undefined ? DEFAULT_QQ_EVENTS.death : o.death === true,\n crash: o.crash === undefined ? DEFAULT_QQ_EVENTS.crash : o.crash === true,\n start: o.start === undefined ? DEFAULT_QQ_EVENTS.start : o.start === true,\n stop: o.stop === undefined ? DEFAULT_QQ_EVENTS.stop : o.stop === true,\n };\n}\n\n/**\n * \u5C06 SAPI \u539F\u751F\u4F24\u5BB3\u6765\u6E90\u4EE3\u7801\uFF08damageSource.cause\uFF09\u8F6C\u6362\u4E3A\u6613\u8BFB\u7684\u4E2D\u6587\u6587\u672C\u3002\n *\n * @param cause \u4F24\u5BB3\u6765\u6E90\u6807\u8BC6\u3002\n * @returns \u4E2D\u6587\u4F24\u5BB3\u63CF\u8FF0\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u4EE3\u7801\uFF09\u3002\n */\nexport function localizeCause(cause: string | undefined): string {\n if (!cause) return \"\";\n const key = cause.trim();\n if (!key) return \"\";\n return CAUSE_ZH[key] ?? CAUSE_ZH[key.toLowerCase()] ?? key;\n}\n\nexport function isEventTypeEnabled(cfg: ResolvedQqEventsConfig, type: QqEventType): boolean {\n if (!cfg.enabled) return false;\n return cfg[type] === true;\n}\n\n/**\n * \u4E8B\u4EF6\u63A8\u9001\u5668\u7684\u4F9D\u8D56\u6CE8\u5165\u3002\n * \u5F53\u524D\u7531 db-server index.ts \u6CE8\u5165\u914D\u7F6E\u8BFB\u53D6\u4E0E\u51FA\u7AD9\u914D\u7F6E\uFF1B\u5355\u5143\u6D4B\u8BD5\u53EF\u6CE8\u5165 send \u62E6\u622A\u53D1\u9001\u3002\n */\nexport type QqEventsDispatcherDeps = {\n getConfig: () => ResolvedQqEventsConfig;\n getOutbound: () => OutboundConfig;\n send?: (text: string) => void;\n};\n\n/**\n * \u683C\u5F0F\u5316\u5355\u6761\u73A9\u5BB6\u4E8B\u4EF6\u6587\u672C\uFF08\u4E0A\u7EBF / \u4E0B\u7EBF / \u6B7B\u4EA1\uFF09\uFF0C\u6BCF\u4E2A\u4E8B\u4EF6\u72EC\u7ACB\u6210\u4E00\u6761\u6D88\u606F\u3002\n *\n * @param type \u73A9\u5BB6\u4E8B\u4EF6\u7C7B\u578B\u3002\n * @param player \u73A9\u5BB6\u540D\u3002\n * @param cause \u6B7B\u4EA1\u539F\u56E0\uFF08\u4EC5 death \u4F7F\u7528\uFF0C\u53EF\u4E3A\u7A7A\uFF09\u3002\n * @returns \u5355\u884C\u901A\u77E5\u6587\u672C\uFF0C\u5982\u300C[MC\u4E8B\u4EF6] Steve \u4E0A\u7EBF\u300D\u300C[MC\u4E8B\u4EF6] Bob \u6B7B\u4EA1\uFF08\u5760\u843D\uFF09\u300D\u3002\n */\nexport function formatPlayerBody(type: \"join\" | \"leave\" | \"death\", player: string, cause?: string): string {\n if (type === \"join\") return `[MC\u4E8B\u4EF6] ${player} \u4E0A\u7EBF`;\n if (type === \"leave\") return `[MC\u4E8B\u4EF6] ${player} \u4E0B\u7EBF`;\n const c = localizeCause(cause);\n return c ? `[MC\u4E8B\u4EF6] ${player} \u6B7B\u4EA1\uFF08${c}\uFF09` : `[MC\u4E8B\u4EF6] ${player} \u6B7B\u4EA1`;\n}\n\n/**\n * \u683C\u5F0F\u5316 BDS \u8FD0\u884C\u6001\u4E8B\u4EF6\u6587\u672C\uFF08\u5982 BDS \u5D29\u6E83\u6216\u542F\u52A8\u5C31\u7EEA\uFF09\u3002\n *\n * @param ev \u8FD0\u884C\u6001\u4E8B\u4EF6\u8F7D\u8377\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u4E8B\u4EF6\u901A\u77E5\u6587\u672C\u3002\n */\nexport function formatImmediateBody(ev: QqEventPayload): string {\n if (ev.type === \"crash\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (${d})` : \"[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA\";\n }\n if (ev.type === \"start\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (${d})` : \"[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8\";\n }\n if (ev.type === \"stop\") return \"[MC\u4E8B\u4EF6] BDS \u5DF2\u505C\u6B62\";\n return `[MC\u4E8B\u4EF6] ${ev.type}`;\n}\n\nexport function normalizeEventPayload(raw: unknown): QqEventPayload | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const type = String(o.type ?? \"\").trim() as QqEventType;\n if (!PLAYER_TYPES.has(type) && !LIFECYCLE_TYPES.has(type)) return null;\n const out: QqEventPayload = { type };\n if (o.player !== undefined) {\n const player = String(o.player).trim();\n if (player) out.player = player;\n }\n if (o.cause !== undefined) {\n const cause = String(o.cause).trim();\n if (cause) out.cause = cause;\n }\n if (o.detail !== undefined) {\n const detail = String(o.detail).trim();\n if (detail) out.detail = detail;\n }\n return out;\n}\n\n/**\n * \u521B\u5EFA QQ \u670D\u52A1\u5668\u4E8B\u4EF6\u63A8\u9001\u5668\uFF1A\u6BCF\u4E2A\u4E8B\u4EF6\u6536\u5230\u540E\u7ACB\u5373\u5355\u6761\u53D1\u9001\u5230\u7FA4\u3002\n * \u5F53\u524D\u7531 routes/qq-events.ts \u7684 POST /api/sfmc/qq/events \u8C03\u7528\u3002\n *\n * @param deps \u4F9D\u8D56\u6CE8\u5165\u5BF9\u8C61\uFF08\u914D\u7F6E\u83B7\u53D6\u3001\u51FA\u7AD9\u914D\u7F6E\u53CA\u53EF\u9009\u7684\u53D1\u9001\u5B9E\u73B0\uFF09\u3002\n * @returns \u5305\u542B ingestOne / ingestMany \u7684\u63A8\u9001\u5668\u3002\n */\nexport function createQqEventsDispatcher(deps: QqEventsDispatcherDeps) {\n const send =\n deps.send ??\n ((text: string) => {\n sendGroupOutbound(deps.getOutbound(), text);\n });\n\n function ingestOne(ev: QqEventPayload): { accepted: boolean; reason?: string } {\n const cfg = deps.getConfig();\n if (!isEventTypeEnabled(cfg, ev.type)) {\n return { accepted: false, reason: \"disabled\" };\n }\n\n if (LIFECYCLE_TYPES.has(ev.type)) {\n send(formatImmediateBody(ev));\n return { accepted: true };\n }\n\n const player = String(ev.player ?? \"\").trim();\n if (!player) return { accepted: false, reason: \"missing_player\" };\n\n send(formatPlayerBody(ev.type as \"join\" | \"leave\" | \"death\", player, ev.cause));\n return { accepted: true };\n }\n\n function ingestMany(items: QqEventPayload[]): {\n accepted: number;\n rejected: number;\n } {\n let accepted = 0;\n let rejected = 0;\n for (const ev of items) {\n const r = ingestOne(ev);\n if (r.accepted) accepted += 1;\n else rejected += 1;\n }\n return { accepted, rejected };\n }\n\n return { ingestOne, ingestMany };\n}\n\nexport type QqEventsDispatcher = ReturnType<typeof createQqEventsDispatcher>;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiBH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA6B1C,QAAA,MAAM,GAAG,8BAAY,CAAC;AACtB,QAAA,MAAM,EAAE,oCAA4B,CAAC;AAGrC,QAAA,MAAM,KAAK,mCAAkB,CAAC;AAwB9B,QAAA,MAAM,gBAAgB,8DAA0E,CAAC;AAQjG,QAAA,MAAM,UAAU,0CAId,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiBH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA6B1C,QAAA,MAAM,GAAG,8BAAY,CAAC;AACtB,QAAA,MAAM,EAAE,oCAA4B,CAAC;AAGrC,QAAA,MAAM,KAAK,mCAAkB,CAAC;AAwB9B,QAAA,MAAM,gBAAgB,8DAA0E,CAAC;AAQjG,QAAA,MAAM,UAAU,0CAId,CAAC;AAQH,QAAA,MAAM,cAAc,gBAAyB,CAAC;AAC9C,QAAA,MAAM,eAAe,iBAAwB,CAAC;AAE9C,QAAA,MAAM,QAAQ,UAMZ,CAAC;AAufH,OAAO,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { log } from "./lib/log.js";
|
|
|
10
10
|
import { assertNodeVersion } from "./lib/runtime.js";
|
|
11
11
|
import { createQuery, openDatabase } from "./lib/sqlite.js";
|
|
12
12
|
import { loadManifestV2 } from "./manifest-loader.js";
|
|
13
|
-
import { buildModuleAuth, verifyModuleAuth } from "./module-auth.js";
|
|
13
|
+
import { buildModuleAuth, ensureModuleToken, persistModuleAuth, verifyModuleAuth } from "./module-auth.js";
|
|
14
14
|
import { syncModuleRuntimeState } from "./module-runtime-sync.js";
|
|
15
15
|
import { SchemaRegistry } from "./schema-registry.js";
|
|
16
16
|
import { createServer, startConsole } from "./server.js";
|
|
@@ -25,7 +25,7 @@ import { createModuleConfigRoutes } from "./routes/module-config-routes.js";
|
|
|
25
25
|
import { createServiceRoutes } from "./routes/service-routes.js";
|
|
26
26
|
import { forwardToQQBridge, makeOutboundConfig } from "./domain/bridge.js";
|
|
27
27
|
import { loadContentSnapshot } from "./domain/installed-content.js";
|
|
28
|
-
import {
|
|
28
|
+
import { createQqEventsDispatcher, resolveQqEventsConfig } from "./domain/qq-events.js";
|
|
29
29
|
import { body as sharedBody, json as sharedJson } from "./lib/http.js";
|
|
30
30
|
import { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from "./lib/module-state.js";
|
|
31
31
|
import { createConfigRoutes } from "./routes/config.js";
|
|
@@ -73,6 +73,10 @@ const moduleAuth = buildModuleAuth({
|
|
|
73
73
|
envAuthToken: env.AUTH_TOKEN,
|
|
74
74
|
enabledModuleIds: [...enabledSet]
|
|
75
75
|
});
|
|
76
|
+
if (ensureModuleToken(moduleAuth, PLATFORM_SERVICE_OWNER)) {
|
|
77
|
+
persistModuleAuth(env.DB_PATH, moduleAuth, env.AUTH_TOKEN);
|
|
78
|
+
log.info(`[auth] \u5DF2\u6D3E\u751F\u5E73\u53F0\u5BBF\u4E3B token: ${PLATFORM_SERVICE_OWNER}`);
|
|
79
|
+
}
|
|
76
80
|
const schemaRegistry = new SchemaRegistry(db);
|
|
77
81
|
const serviceRegistry = new ServiceRegistry();
|
|
78
82
|
const idempotent = createIdempotencyStore(db);
|
|
@@ -242,7 +246,7 @@ function writeJoinFlags(partial) {
|
|
|
242
246
|
const created = !readJson(qqLinkConfigFile());
|
|
243
247
|
ensureJson(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });
|
|
244
248
|
if (created) {
|
|
245
|
-
log.info(`\u5DF2\u5199\u5165\
|
|
249
|
+
log.info(`\u5DF2\u5199\u5165\u5E73\u53F0\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);
|
|
246
250
|
}
|
|
247
251
|
}
|
|
248
252
|
const qqJoinRoutes = createQqJoinRoutes({
|
|
@@ -281,14 +285,14 @@ function currentOutbound() {
|
|
|
281
285
|
QQ_GROUP_OPENID: env.QQ_GROUP_OPENID
|
|
282
286
|
});
|
|
283
287
|
}
|
|
284
|
-
const
|
|
288
|
+
const qqEventsDispatcher = createQqEventsDispatcher({
|
|
285
289
|
getConfig: readQqEventsSettings,
|
|
286
290
|
getOutbound: () => currentOutbound()
|
|
287
291
|
});
|
|
288
292
|
const qqEventsRoutes = createQqEventsRoutes({
|
|
289
293
|
body,
|
|
290
294
|
json,
|
|
291
|
-
|
|
295
|
+
dispatcher: qqEventsDispatcher,
|
|
292
296
|
getSettings: readQqEventsSettings,
|
|
293
297
|
setSettings: writeQqEventsSettings,
|
|
294
298
|
isAdmin: (openid, asGroupAdmin) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\nimport { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\nimport { SQL } from \"sql-template-strings\";\n\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { syncPlayerSession } from \"./domain/player-session.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { loadEnv } from \"./env.js\";\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { buildModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { TxRunner } from \"./tx-runner.js\";\n\nimport { configPath, ensureJson, patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport path, { join } from \"node:path\";\n\nimport { jsonV2Fail } from \"./routes/_shared.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\n\nimport { forwardToQQBridge, makeOutboundConfig } from \"./domain/bridge.js\";\nimport { loadContentSnapshot } from \"./domain/installed-content.js\";\nimport { createQqEventsAggregator, resolveQqEventsConfig, type ResolvedQqEventsConfig } from \"./domain/qq-events.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createContentRoutes } from \"./routes/content.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { createMessagesRoutes, gameChatForwardPayload, type ChatChannelQQFlags } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\nimport { createQqBindRoutes } from \"./routes/qq-bind.js\";\nimport { createQqEventsRoutes } from \"./routes/qq-events.js\";\nimport { createQqJoinRoutes, type JoinFeatureFlags } from \"./routes/qq-join.js\";\nimport { createStatusRoutes } from \"./routes/status.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>).map((m) => String(m.id || \"\")).filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find((m) => String(m.id) === id);\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<(typeof loadedManifest.modules)[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(`[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(`[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`);\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/**\n * QQ\u300C\u6A21\u5757 / \u8D44\u6E90\u5305\u300D\u5FEB\u7167\u3002\u6BCF\u6B21\u8BF7\u6C42\u73B0\u8BFB catalog \u4E0E\u78C1\u76D8\uFF0C\u88C5\u5305\u540E\u4E0D\u7528\u91CD\u542F db-server\u3002\n */\nfunction loadContentSnapshotForRequest() {\n const modules = (buildModuleList() ?? []).flatMap((raw) => {\n if (!raw || typeof raw !== \"object\") return [];\n const row = raw as { id?: unknown; display_name?: unknown; enabled?: unknown };\n const id = String(row.id ?? \"\").trim();\n if (!id) return [];\n return [\n {\n id,\n display_name: String(row.display_name || id),\n enabled: row.enabled !== false,\n },\n ];\n });\n const cfg = readJson<{ bds_path?: string }>(configPath(env.PROJECT_ROOT, \"bds_updater.json\"));\n const rawBds = typeof cfg?.bds_path === \"string\" ? cfg.bds_path.trim() : \"\";\n const bdsRoot = rawBds ? (path.isAbsolute(rawBds) ? rawBds : path.resolve(env.PROJECT_ROOT, rawBds)) : null;\n return loadContentSnapshot({\n packagesDir: join(env.MODULES_DIR, \"packages\"),\n modules,\n bdsRoot,\n });\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\n// \u6307\u6807\u72EC\u7ACB\u843D\u5E93\uFF0C\u907F\u514D\u5199\u5165\u4E1A\u52A1\u6A21\u5757\u6B63\u5728\u6301\u6709\u7684\u8DE8\u8BF7\u6C42\u4E8B\u52A1\u3002\nconst metricsDb = openDatabase(path.join(path.dirname(env.DB_PATH), \"runtime-metrics.sqlite\"));\nconst statusRoutes = createStatusRoutes({ query: createQuery(metricsDb) });\nfor (const name of PLATFORM_MONITORING_SERVICES) serviceRegistry.registerHandler(PLATFORM_SERVICE_OWNER, name, async () => {\n const response = await fetch(`http://127.0.0.1:${env.PORT}/api/sfmc/metrics`, { signal: AbortSignal.timeout(10_000) });\n if (!response.ok) throw new Error(\"\u5E73\u53F0\u8FD0\u884C\u6307\u6807\u8BFB\u53D6\u5931\u8D25\");\n const result = await response.json() as { current: { tps: number | null; chunkEstimate: number | null } | null };\n const tps = result.current?.tps ?? null;\n const grade = tps === null ? \"unknown\" : tps >= 19.5 ? \"green\" : tps >= 15 ? \"yellow\" : tps >= 10 ? \"gold\" : \"red\";\n if (name === \"tps.current\") return tps;\n if (name === \"tps.status\") return { tps, grade, text: `\u00A77[TPS] \u00A7f${tps?.toFixed(2) ?? \"\u672A\u77E5\"} \u00A77/ 20.00` };\n return result.current ? { ...result.current, grade, totalLoadedChunks: result.current.chunkEstimate } : null;\n});\nconst qqBindRoutes = createQqBindRoutes({ query, body, json });\n\n/** \u6A21\u5757 qq-link \u7684\u914D\u7F6E\u6587\u4EF6\uFF08\u975E SDK qq_config\uFF09 */\nconst QQ_LINK_CONFIG_KEY = \"qq_link\";\nconst QQ_LINK_DEFAULTS = {\n allowlist_enabled: true,\n require_approval: true,\n /** \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\uFF1B\u7F3A\u7701\u5F00\u542F\uFF0C\u4EC5\u6539\u672C\u6587\u4EF6\u53EF\u5173\u95ED\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199 */\n treat_group_admins_as_admins: true,\n} as const;\n\nfunction asConfigBool(v: unknown, fallback: boolean): boolean {\n if (v === undefined || v === null) return fallback;\n if (typeof v === \"boolean\") return v;\n const s = String(v).trim().toLowerCase();\n if (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") return true;\n if (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") return false;\n return fallback;\n}\n\nfunction qqLinkConfigFile(): string {\n return join(env.PROJECT_ROOT, \"configs\", `${QQ_LINK_CONFIG_KEY}.json`);\n}\n\nfunction readJoinFlags(): JoinFeatureFlags {\n // \u6A21\u5757\u914D\u7F6E configs/qq_link.json\uFF08\u975E SDK qq_config\uFF09\n const disk = ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n return {\n allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),\n requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),\n treatGroupAdminsAsAdmins: asConfigBool(\n disk.treat_group_admins_as_admins,\n QQ_LINK_DEFAULTS.treat_group_admins_as_admins\n ),\n };\n}\n\nfunction writeJoinFlags(partial: Partial<JoinFeatureFlags>): JoinFeatureFlags {\n const file = qqLinkConfigFile();\n ensureJson<Record<string, unknown>>(file, { ...QQ_LINK_DEFAULTS });\n const patch: Record<string, boolean> = {};\n if (partial.allowlistEnabled !== undefined) patch.allowlist_enabled = partial.allowlistEnabled;\n if (partial.requireApproval !== undefined) patch.require_approval = partial.requireApproval;\n // \u6545\u610F\u4E0D\u5199 treat_group_admins_as_admins\uFF1A\u4EC5\u4EBA\u5DE5\u6539 configs/qq_link.json\n if (Object.keys(patch).length > 0) {\n patchJson(file, patch);\n }\n return readJoinFlags();\n}\n\n// \u542F\u52A8\u5373\u843D\u76D8\u6A21\u5757\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u907F\u514D\u300C\u4ECE\u672A\u70B9\u8FC7\u914D\u7F6E/\u7533\u8BF7\u300D\u65F6 configs/qq_link.json \u4E0D\u5B58\u5728\n{\n const created = !readJson(qqLinkConfigFile());\n ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n if (created) {\n log.info(`\u5DF2\u5199\u5165\u6A21\u5757\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);\n }\n}\n\nconst qqJoinRoutes = createQqJoinRoutes({\n query,\n body,\n json,\n getAdminOpenids: () => {\n const raw = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n if (!Array.isArray(raw)) return [];\n return raw.map((x) => String(x).trim()).filter(Boolean);\n },\n getJoinFlags: () => readJoinFlags(),\n setJoinFlags: (partial) => writeJoinFlags(partial),\n});\n\nfunction readQqEventsSettings(): ResolvedQqEventsConfig {\n const disk = readJson<Record<string, unknown>>(join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\"));\n if (!disk) throw new Error(\"qq_config.json \u65E0\u6CD5\u8BFB\u53D6\uFF0C\u4E8B\u4EF6\u8BBE\u7F6E\u672A\u4FEE\u6539\");\n return resolveQqEventsConfig(disk.qq_events);\n}\n\nfunction writeQqEventsSettings(patch: Partial<ResolvedQqEventsConfig>): ResolvedQqEventsConfig {\n const file = join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\");\n const settings = { ...readQqEventsSettings(), ...patch };\n patchJson(file, { qq_events: settings });\n return settings;\n}\n\nfunction currentOutbound() {\n return makeOutboundConfig({\n QQ_BACKEND: env.QQ_BACKEND,\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n QQ_APP_ID: env.QQ_APP_ID,\n QQ_APP_SECRET: env.QQ_APP_SECRET,\n QQ_SANDBOX: env.QQ_SANDBOX,\n QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,\n });\n}\n\nconst qqEventsAggregator = createQqEventsAggregator({\n getConfig: readQqEventsSettings,\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n aggregator: qqEventsAggregator,\n getSettings: readQqEventsSettings,\n setSettings: writeQqEventsSettings,\n isAdmin: (openid, asGroupAdmin) => {\n const admins = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n return (\n (Array.isArray(admins) && admins.includes(openid)) || (asGroupAdmin && readJoinFlags().treatGroupAdminsAsAdmins)\n );\n },\n});\n\n/** \u8BFB\u53D6\u5355\u4E2A\u804A\u5929\u9891\u9053\u3002\u8868\u5C1A\u672A\u7531\u804A\u5929\u6A21\u5757\u5EFA\u51FA\u65F6\u8FD4\u56DE null\uFF0C\u4E0D\u8BA9\u8BF7\u6C42 500\u3002 */\nfunction readChatChannel(channelId: string): ChatChannelQQFlags | null {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels WHERE id = ${channelId}`);\n return Array.isArray(rows) ? ((rows[0] as ChatChannelQQFlags | undefined) ?? null) : null;\n } catch (err) {\n log.warn(`\u8BFB\u53D6\u804A\u5929\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** \u6D88\u606F\u6765\u6E90\u4E3A QQ \u7684\u9891\u9053\u3002QQ \u5165\u7AD9\u53EA\u6295\u5230\u8FD9\u4E9B\u9891\u9053\u3002 */\nfunction listQQSourceChannels(): ChatChannelQQFlags[] {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels`);\n if (!Array.isArray(rows)) return [];\n return (rows as ChatChannelQQFlags[]).filter((row) => row.source_qq === 1);\n } catch (err) {\n log.warn(`\u8BFB\u53D6 QQ \u6765\u6E90\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return [];\n }\n}\n\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n getChannelForQQ: readChatChannel,\n listQQSourceChannels,\n forwardToQQBridge: (channelId: string, prefix: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(currentOutbound(), channelId, prefix, fromName, content, fromId),\n});\n\n// \u6E38\u620F\u53D1\u8A00\u8D70 db.tx \u63D2\u5165\uFF0C\u4E0D\u7ECF\u8FC7 /api/sfmc/messages\u3002\u63D0\u4EA4\u540E\u518D\u6309\u9891\u9053\u5F00\u5173\u8F6C\u53D1\u5230 QQ\u3002\ntxRunner.setAfterCommit((inserts) => {\n for (const item of inserts) {\n if (item.table !== \"sfmc_chat_messages\") continue;\n const payload = gameChatForwardPayload(item.row, readChatChannel(String(item.row.channel_id ?? \"\")));\n if (!payload) continue;\n forwardToQQBridge(\n currentOutbound(),\n payload.channelId,\n payload.prefix,\n payload.fromName,\n payload.content,\n payload.fromId,\n );\n }\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst contentRoutes = createContentRoutes({\n json,\n loadSnapshot: loadContentSnapshotForRequest,\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (\n ctx: Record<string, unknown>\n) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n path === \"/api/sfmc/status\" ||\n path === \"/api/sfmc/qq/join/apply-queue\" ||\n path === \"/api/sfmc/qq/join/settings\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/content\" ||\n path === \"/api/sfmc/modules\" ||\n path === \"/api/sfmc/modules/catalog\" ||\n path.startsWith(\"/api/sfmc/modules/\") ||\n path === \"/api/sfmc/qq/bind/me\" ||\n path === \"/api/sfmc/qq/join/pending\"));\n // bind/join/events POST \u4E0E messages \u4E00\u6837\u8D70\u65E7 AUTH_TOKEN\uFF08\u82E5\u914D\u7F6E\uFF09\uFF1B\u6E38\u620F/\u6865\u540C\u4E3A loopback\n const NEEDS_AUTH =\n !PUBLIC_GET &&\n method !== \"GET\" &&\n !(\n path.startsWith(\"/api/sfmc/qq/bind/\") ||\n path.startsWith(\"/api/sfmc/qq/join/\") ||\n path.startsWith(\"/api/sfmc/qq/events\")\n );\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n if (path === \"/api/sfmc/player-session\" && method === \"POST\") {\n const data = (await body(req)) as Record<string, unknown>;\n const playerName = String(data.playerName ?? \"\").trim();\n const xuid = String(data.xuid ?? \"\").trim();\n if (!playerName || !xuid) {\n json(res, { success: false, error: \"playerName_and_xuid_required\" }, 400);\n return;\n }\n syncPlayerSession(query, { playerName, xuid });\n json(res, { success: true });\n return;\n }\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (\n moduleAuthCtx &&\n (path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))\n ) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as {\n path: string;\n method: string;\n params: URLSearchParams;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n };\n if (await contentRoutes(ctxBase)) return;\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await statusRoutes(ctxBase)) return;\n if (await qqBindRoutes(ctxBase)) return;\n if (await qqJoinRoutes(ctxBase)) return;\n if (await qqEventsRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, enabledManifests, env, moduleAuth, query, schemaRegistry, serviceRegistry, txRunner };\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n *\n * \u5DE5\u4F5C\u6D41:\n * 1. \u52A0\u8F7D env\n * 2. \u6821\u9A8C Node \u7248\u672C\n * 3. openDatabase + createPlatformTables(sfmc__audit / sfmc__idempotent)\n * 4. loadManifestV2() \u2014 \u5931\u8D25 = \u542F\u52A8\u5931\u8D25\n * 5. filterEnabled(loaded, lockFileEnabled)\n * 6. buildModuleAuth({auth_token, enabled_modules}) \u2190 \u4E0E DB \u540C\u76EE\u5F55 module-tokens.json\n * 7. \u5B9E\u4F8B\u5316:SchemaRegistry / ServiceRegistry / IdempotencyStore / TxRunner\n * 8. \u88C5\u914D v2 \u8DEF\u7531 (/api/sfmc/db/* + /api/sfmc/services* + /api/sfmc/configs/:key/*)\n * \u53E6\u4FDD\u7559 messages(qq-bridge) + config/modules/health \u5E73\u53F0\u8DEF\u7531\n * 9. createServer + listen\n *\n * \u9274\u6743(handle \u5C42):\n * - /api/sfmc/db/*, /api/sfmc/services*, /api/sfmc/configs/:key(/set|notify)*\n * \u2192 module token (Authorization: Bearer ...) + ?moduleId=...\n * - \u5176\u4ED6:env.AUTH_TOKEN \u65E7 auth(NEEDS_AUTH)\n */\n\nimport http from \"node:http\";\nimport { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\nimport { SQL } from \"sql-template-strings\";\n\nimport { createPlatformTables } from \"./db-tables.js\";\nimport { syncPlayerSession } from \"./domain/player-session.js\";\nimport { initSchema } from \"./domain/schema.js\";\nimport { loadEnv } from \"./env.js\";\nimport { createIdempotencyStore } from \"./lib/idempotency-store.js\";\nimport { log } from \"./lib/log.js\";\nimport { assertNodeVersion } from \"./lib/runtime.js\";\nimport { createQuery, openDatabase } from \"./lib/sqlite.js\";\nimport { loadManifestV2 } from \"./manifest-loader.js\";\nimport { buildModuleAuth, ensureModuleToken, persistModuleAuth, verifyModuleAuth } from \"./module-auth.js\";\nimport { syncModuleRuntimeState } from \"./module-runtime-sync.js\";\nimport { SchemaRegistry } from \"./schema-registry.js\";\nimport { createServer, startConsole } from \"./server.js\";\nimport { ServiceRegistry } from \"./service-registry.js\";\nimport { registerEnabledBuiltinServices } from \"./services/builtin-handlers.js\";\nimport { TxRunner } from \"./tx-runner.js\";\n\nimport { configPath, ensureJson, patchJson, readJson } from \"@sfmc-bds/sdk/node/config\";\nimport path, { join } from \"node:path\";\n\nimport { jsonV2Fail } from \"./routes/_shared.js\";\nimport { createDbRoutes } from \"./routes/db-routes.js\";\nimport { createModuleConfigRoutes } from \"./routes/module-config-routes.js\";\nimport { createServiceRoutes } from \"./routes/service-routes.js\";\n\nimport { forwardToQQBridge, makeOutboundConfig } from \"./domain/bridge.js\";\nimport { loadContentSnapshot } from \"./domain/installed-content.js\";\nimport { createQqEventsDispatcher, resolveQqEventsConfig, type ResolvedQqEventsConfig } from \"./domain/qq-events.js\";\nimport { body as sharedBody, json as sharedJson } from \"./lib/http.js\";\nimport { isEnabled, loadModuleLock, saveModuleLock, updateModuleState } from \"./lib/module-state.js\";\nimport { createConfigRoutes } from \"./routes/config.js\";\nimport { createContentRoutes } from \"./routes/content.js\";\nimport { createHealthRoutes } from \"./routes/health.js\";\nimport { createMessagesRoutes, gameChatForwardPayload, type ChatChannelQQFlags } from \"./routes/messages.js\";\nimport { createModuleRoutes } from \"./routes/modules.js\";\nimport { createQqBindRoutes } from \"./routes/qq-bind.js\";\nimport { createQqEventsRoutes } from \"./routes/qq-events.js\";\nimport { createQqJoinRoutes, type JoinFeatureFlags } from \"./routes/qq-join.js\";\nimport { createStatusRoutes } from \"./routes/status.js\";\n\nif (!assertNodeVersion(22, 13)) {\n process.exit(2);\n}\n\nconst env = loadEnv();\nconst db = openDatabase(env.DB_PATH);\ncreatePlatformTables(db); // sfmc__audit / sfmc__idempotent\ninitSchema(db); // \u5E73\u53F0\u4E1A\u52A1 bootstrap \u8868(players/world/chat/...)\u2014\u2014qq-bridge \u5728 SAPI defineTable \u4E4B\u524D\u5C31\u9700\u8981\nconst query = createQuery(db);\n\n// \u2500\u2500 v2 manifest \u52A0\u8F7D(\u5931\u8D25 = \u542F\u52A8\u5931\u8D25)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst loadedManifest = loadManifestV2(); // throws on violation\nlog.success(\n `[manifest v2] loaded ${Object.keys(loadedManifest.modules).length} modules; provides ${loadedManifest.providesMap.size} services`\n);\n\n// \u2500\u2500 enabled \u96C6\u5408(\u4ECE lock file)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst lockFile = loadModuleLock(env.MODULE_LOCK_PATH);\nconst moduleCatalog = readJson<{ modules?: unknown[] }>(env.MODULE_CATALOG_PATH) ?? { modules: [] };\nconst catalogIds = new Set(\n Array.isArray(moduleCatalog.modules)\n ? (moduleCatalog.modules as Array<{ id?: string }>).map((m) => String(m.id || \"\")).filter((id) => id.length > 0)\n : []\n);\nconst enabledSet = new Set<string>();\nfor (const id of Object.keys(loadedManifest.modules)) {\n if (!catalogIds.has(id)) continue;\n const catalogEntry = (moduleCatalog.modules as Array<Record<string, unknown>>).find((m) => String(m.id) === id);\n if (!catalogEntry) continue;\n const defaultEnabled = catalogEntry.enabledByDefault !== false;\n if (isEnabled(lockFile, id, defaultEnabled)) enabledSet.add(id);\n}\nconst enabledManifests = new Map<string, NonNullable<(typeof loadedManifest.modules)[string]>>();\nfor (const id of enabledSet) {\n const m = loadedManifest.modules[id];\n if (m) enabledManifests.set(id, m);\n}\nlog.info(`[manifest v2] enabled: ${[...enabledSet].sort().join(\", \") || \"(none)\"}`);\n\n// \u2500\u2500 \u6A21\u5757 HMAC token map(\u4E0E DB \u540C\u76EE\u5F55 module-tokens.json)\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst moduleAuth = buildModuleAuth({\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledModuleIds: [...enabledSet],\n});\n// \u5E73\u53F0\u5BBF\u4E3B\uFF08QQ \u7ED1\u5B9A/\u8D26\u53F7\u5FEB\u7167\uFF09\u9700\u8981\u7A33\u5B9A token\uFF0C\u7ECF configs/all.module_tokens \u4E0B\u53D1\u5230 BDS\nif (ensureModuleToken(moduleAuth, PLATFORM_SERVICE_OWNER)) {\n persistModuleAuth(env.DB_PATH, moduleAuth, env.AUTH_TOKEN);\n log.info(`[auth] \u5DF2\u6D3E\u751F\u5E73\u53F0\u5BBF\u4E3B token: ${PLATFORM_SERVICE_OWNER}`);\n}\n\n// \u2500\u2500 \u4E09\u4EF6\u5957 + \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst schemaRegistry = new SchemaRegistry(db);\nconst serviceRegistry = new ServiceRegistry();\nconst idempotent = createIdempotencyStore(db);\nconst txRunner = new TxRunner({\n db,\n query,\n schema: schemaRegistry,\n serviceRegistry,\n enabled: enabledManifests,\n});\n\n// \u2500\u2500 \u8FDB\u7A0B\u5185\u7F6E service handler(\u6269\u5C55\u70B9:BUILTIN_SERVICE_PLUGINS) \u2500\u2500\n{\n const plugins = registerEnabledBuiltinServices(serviceRegistry, { query, db }, enabledSet);\n if (plugins > 0) {\n log.success(`[service] registered ${serviceRegistry.list().length} handlers from ${plugins} builtin plugin(s)`);\n }\n}\n\nconst json = sharedJson;\nconst body = sharedBody;\n\n// \u2500\u2500 \u5DE5\u5177\u51FD\u6570 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadModuleCatalog() {\n return Array.isArray(moduleCatalog.modules) ? moduleCatalog.modules : [];\n}\n\nfunction buildModuleList() {\n const catalog = loadModuleCatalog();\n return catalog\n .map((raw) => {\n const entry =\n (raw as Record<string, unknown>).entry && typeof (raw as Record<string, unknown>).entry === \"object\"\n ? ((raw as Record<string, unknown>).entry as Record<string, unknown>)\n : {};\n const id = String((raw as Record<string, unknown>).id || \"\").trim();\n const configKey = String(\n (raw as Record<string, unknown>).configKey || (raw as Record<string, unknown>).config_key || \"\"\n ).trim();\n if (!id || !configKey) return null;\n const state = lockFile.modules[id];\n const enabled = isEnabled(lockFile, id, (raw as Record<string, unknown>).enabledByDefault !== false);\n return {\n id,\n module_id: id,\n name: configKey,\n configKey,\n config_key: configKey,\n display_name: String((raw as Record<string, unknown>).name || configKey),\n description: String((raw as Record<string, unknown>).description || \"\"),\n default_enabled: (raw as Record<string, unknown>).enabledByDefault !== false,\n can_disable: moduleCanDisable(raw as Record<string, unknown>),\n // ConfigManager \u8BA4 installed!==false;\u5DF2\u88C5\u5305\u9ED8\u8BA4 true\n installed: true,\n requires: Array.isArray((raw as Record<string, unknown>).requires)\n ? ((raw as Record<string, unknown>).requires as unknown[]).filter(Boolean).map(String)\n : [],\n optional: Array.isArray((raw as Record<string, unknown>).optional)\n ? ((raw as Record<string, unknown>).optional as unknown[]).filter(Boolean).map(String)\n : [],\n commands: Array.isArray((raw as Record<string, unknown>).commands)\n ? ((raw as Record<string, unknown>).commands as unknown[]).filter(Boolean).map(String)\n : [],\n entry: {\n kind: String(entry.kind || \"\"),\n path: String(entry.path || \"\"),\n init: String(entry.init || \"\"),\n },\n updated_at: state?.updatedAt ?? null,\n enabled: !!enabled,\n };\n })\n .filter(Boolean);\n}\n\n/**\n * QQ\u300C\u6A21\u5757 / \u8D44\u6E90\u5305\u300D\u5FEB\u7167\u3002\u6BCF\u6B21\u8BF7\u6C42\u73B0\u8BFB catalog \u4E0E\u78C1\u76D8\uFF0C\u88C5\u5305\u540E\u4E0D\u7528\u91CD\u542F db-server\u3002\n */\nfunction loadContentSnapshotForRequest() {\n const modules = (buildModuleList() ?? []).flatMap((raw) => {\n if (!raw || typeof raw !== \"object\") return [];\n const row = raw as { id?: unknown; display_name?: unknown; enabled?: unknown };\n const id = String(row.id ?? \"\").trim();\n if (!id) return [];\n return [\n {\n id,\n display_name: String(row.display_name || id),\n enabled: row.enabled !== false,\n },\n ];\n });\n const cfg = readJson<{ bds_path?: string }>(configPath(env.PROJECT_ROOT, \"bds_updater.json\"));\n const rawBds = typeof cfg?.bds_path === \"string\" ? cfg.bds_path.trim() : \"\";\n const bdsRoot = rawBds ? (path.isAbsolute(rawBds) ? rawBds : path.resolve(env.PROJECT_ROOT, rawBds)) : null;\n return loadContentSnapshot({\n packagesDir: join(env.MODULES_DIR, \"packages\"),\n modules,\n bdsRoot,\n });\n}\n\n/** catalog \u7701\u7565 canDisable \u65F6\u9ED8\u8BA4\u5141\u8BB8\u7981\u7528(\u4E0E buildModuleList.can_disable \u540C\u6E90)\u3002 */\nfunction moduleCanDisable(raw: Record<string, unknown>): boolean {\n return raw.canDisable !== false;\n}\n\nfunction resolveModuleByKey(key: string) {\n const k = String(key || \"\").trim();\n const catalog = loadModuleCatalog();\n const raw = catalog.find(\n (m) =>\n String((m as Record<string, unknown>).id || \"\") === k ||\n String((m as Record<string, unknown>).configKey || (m as Record<string, unknown>).config_key || \"\") === k\n ) as Record<string, unknown> | undefined;\n if (!raw) return null;\n const id = String(raw.id || \"\").trim();\n const configKey = String(raw.configKey || raw.config_key || \"\").trim();\n if (!id || !configKey) return null;\n /* LSP:\u4E0E buildModuleList.can_disable \u540C\u6E90 \u2014 \u7701\u7565\u5B57\u6BB5\u89C6\u4E3A\u53EF\u7981\u7528 */\n return { id, configKey, canDisable: moduleCanDisable(raw) };\n}\n\nfunction setModuleEnabled(mod: { id: string; canDisable: boolean }, enabled: boolean) {\n // \u76F4\u63A5\u66F4\u65B0\u542F\u52A8\u65F6\u7F13\u5B58\u7684 lockFile(\u800C\u975E\u53E6\u8BFB\u4E00\u4EFD\u65B0\u526F\u672C),\n // \u5426\u5219 buildModuleList() \u8BFB\u7684\u4ECD\u662F\u65E7\u7F13\u5B58,\u5BFC\u81F4\u542F\u505C\u540E enabled \u72B6\u6001\u4E0D\u7FFB\u8F6C\u3002\n updateModuleState(lockFile, mod.id, { enabled: !!enabled });\n // DRY:\u4E0E loadModuleLock \u5BF9\u79F0\u8D70 saveModuleLock,\u52FF\u6563\u843D writeJson\n saveModuleLock(env.MODULE_LOCK_PATH, lockFile);\n\n // DIP:\u70ED\u540C\u6B65 enabledSet / tokens / manifests / builtin handlers(\u4E0D\u91CD\u542F db-server)\n syncModuleRuntimeState({\n moduleId: mod.id,\n enabled: !!enabled,\n dbPath: env.DB_PATH,\n envAuthToken: env.AUTH_TOKEN,\n enabledSet,\n enabledManifests,\n loadedManifest,\n moduleAuth,\n serviceRegistry,\n builtinDeps: { query, db },\n });\n}\n\n// \u2500\u2500 \u5E73\u53F0\u8DEF\u7531(\u975E\u6A21\u5757\u4E1A\u52A1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst healthRoutes = createHealthRoutes();\n// \u6307\u6807\u72EC\u7ACB\u843D\u5E93\uFF0C\u907F\u514D\u5199\u5165\u4E1A\u52A1\u6A21\u5757\u6B63\u5728\u6301\u6709\u7684\u8DE8\u8BF7\u6C42\u4E8B\u52A1\u3002\nconst metricsDb = openDatabase(path.join(path.dirname(env.DB_PATH), \"runtime-metrics.sqlite\"));\nconst statusRoutes = createStatusRoutes({ query: createQuery(metricsDb) });\nfor (const name of PLATFORM_MONITORING_SERVICES) serviceRegistry.registerHandler(PLATFORM_SERVICE_OWNER, name, async () => {\n const response = await fetch(`http://127.0.0.1:${env.PORT}/api/sfmc/metrics`, { signal: AbortSignal.timeout(10_000) });\n if (!response.ok) throw new Error(\"\u5E73\u53F0\u8FD0\u884C\u6307\u6807\u8BFB\u53D6\u5931\u8D25\");\n const result = await response.json() as { current: { tps: number | null; chunkEstimate: number | null } | null };\n const tps = result.current?.tps ?? null;\n const grade = tps === null ? \"unknown\" : tps >= 19.5 ? \"green\" : tps >= 15 ? \"yellow\" : tps >= 10 ? \"gold\" : \"red\";\n if (name === \"tps.current\") return tps;\n if (name === \"tps.status\") return { tps, grade, text: `\u00A77[TPS] \u00A7f${tps?.toFixed(2) ?? \"\u672A\u77E5\"} \u00A77/ 20.00` };\n return result.current ? { ...result.current, grade, totalLoadedChunks: result.current.chunkEstimate } : null;\n});\nconst qqBindRoutes = createQqBindRoutes({ query, body, json });\n\n/** \u5E73\u53F0 QQ \u7ED1\u5B9A\u76F8\u5173\u914D\u7F6E\uFF08\u975E SDK qq_config\uFF1B\u539F qq-link \u6A21\u5757\u914D\u7F6E\u952E\uFF09 */\nconst QQ_LINK_CONFIG_KEY = \"qq_link\";\nconst QQ_LINK_DEFAULTS = {\n allowlist_enabled: true,\n require_approval: true,\n /** \u662F\u5426\u5C06 QQ \u7FA4\u4E3B/\u7FA4\u7BA1\u89C6\u4F5C SFMC \u7BA1\u7406\u5458\uFF1B\u7F3A\u7701\u5F00\u542F\uFF0C\u4EC5\u6539\u672C\u6587\u4EF6\u53EF\u5173\u95ED\uFF0CAPI/\u7FA4\u804A\u4E0D\u53EF\u5199 */\n treat_group_admins_as_admins: true,\n} as const;\n\nfunction asConfigBool(v: unknown, fallback: boolean): boolean {\n if (v === undefined || v === null) return fallback;\n if (typeof v === \"boolean\") return v;\n const s = String(v).trim().toLowerCase();\n if (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") return true;\n if (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") return false;\n return fallback;\n}\n\nfunction qqLinkConfigFile(): string {\n return join(env.PROJECT_ROOT, \"configs\", `${QQ_LINK_CONFIG_KEY}.json`);\n}\n\nfunction readJoinFlags(): JoinFeatureFlags {\n // \u5E73\u53F0\u914D\u7F6E configs/qq_link.json\uFF08\u975E SDK qq_config\uFF09\n const disk = ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n return {\n allowlistEnabled: asConfigBool(disk.allowlist_enabled, QQ_LINK_DEFAULTS.allowlist_enabled),\n requireApproval: asConfigBool(disk.require_approval, QQ_LINK_DEFAULTS.require_approval),\n treatGroupAdminsAsAdmins: asConfigBool(\n disk.treat_group_admins_as_admins,\n QQ_LINK_DEFAULTS.treat_group_admins_as_admins\n ),\n };\n}\n\nfunction writeJoinFlags(partial: Partial<JoinFeatureFlags>): JoinFeatureFlags {\n const file = qqLinkConfigFile();\n ensureJson<Record<string, unknown>>(file, { ...QQ_LINK_DEFAULTS });\n const patch: Record<string, boolean> = {};\n if (partial.allowlistEnabled !== undefined) patch.allowlist_enabled = partial.allowlistEnabled;\n if (partial.requireApproval !== undefined) patch.require_approval = partial.requireApproval;\n // \u6545\u610F\u4E0D\u5199 treat_group_admins_as_admins\uFF1A\u4EC5\u4EBA\u5DE5\u6539 configs/qq_link.json\n if (Object.keys(patch).length > 0) {\n patchJson(file, patch);\n }\n return readJoinFlags();\n}\n\n// \u542F\u52A8\u5373\u843D\u76D8\u5E73\u53F0\u9ED8\u8BA4\u914D\u7F6E\uFF0C\u907F\u514D\u300C\u4ECE\u672A\u70B9\u8FC7\u914D\u7F6E/\u7533\u8BF7\u300D\u65F6 configs/qq_link.json \u4E0D\u5B58\u5728\n{\n const created = !readJson(qqLinkConfigFile());\n ensureJson<Record<string, unknown>>(qqLinkConfigFile(), { ...QQ_LINK_DEFAULTS });\n if (created) {\n log.info(`\u5DF2\u5199\u5165\u5E73\u53F0\u914D\u7F6E\u9AA8\u67B6: configs/${QQ_LINK_CONFIG_KEY}.json`);\n }\n}\n\nconst qqJoinRoutes = createQqJoinRoutes({\n query,\n body,\n json,\n getAdminOpenids: () => {\n const raw = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n if (!Array.isArray(raw)) return [];\n return raw.map((x) => String(x).trim()).filter(Boolean);\n },\n getJoinFlags: () => readJoinFlags(),\n setJoinFlags: (partial) => writeJoinFlags(partial),\n});\n\nfunction readQqEventsSettings(): ResolvedQqEventsConfig {\n const disk = readJson<Record<string, unknown>>(join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\"));\n if (!disk) throw new Error(\"qq_config.json \u65E0\u6CD5\u8BFB\u53D6\uFF0C\u4E8B\u4EF6\u8BBE\u7F6E\u672A\u4FEE\u6539\");\n return resolveQqEventsConfig(disk.qq_events);\n}\n\nfunction writeQqEventsSettings(patch: Partial<ResolvedQqEventsConfig>): ResolvedQqEventsConfig {\n const file = join(env.PROJECT_ROOT, \"configs\", \"qq_config.json\");\n const settings = { ...readQqEventsSettings(), ...patch };\n patchJson(file, { qq_events: settings });\n return settings;\n}\n\nfunction currentOutbound() {\n return makeOutboundConfig({\n QQ_BACKEND: env.QQ_BACKEND,\n LLBOT_HOST: env.LLBOT_HOST,\n LLBOT_PORT: env.LLBOT_PORT,\n LLBOT_TOKEN: env.LLBOT_TOKEN,\n QQ_GROUP_ID: env.QQ_GROUP_ID,\n QQ_APP_ID: env.QQ_APP_ID,\n QQ_APP_SECRET: env.QQ_APP_SECRET,\n QQ_SANDBOX: env.QQ_SANDBOX,\n QQ_GROUP_OPENID: env.QQ_GROUP_OPENID,\n });\n}\n\nconst qqEventsDispatcher = createQqEventsDispatcher({\n getConfig: readQqEventsSettings,\n getOutbound: () => currentOutbound(),\n});\nconst qqEventsRoutes = createQqEventsRoutes({\n body,\n json,\n dispatcher: qqEventsDispatcher,\n getSettings: readQqEventsSettings,\n setSettings: writeQqEventsSettings,\n isAdmin: (openid, asGroupAdmin) => {\n const admins = (env.qqconfig[\"official\"] as { admin_openids?: unknown } | undefined)?.admin_openids;\n return (\n (Array.isArray(admins) && admins.includes(openid)) || (asGroupAdmin && readJoinFlags().treatGroupAdminsAsAdmins)\n );\n },\n});\n\n/** \u8BFB\u53D6\u5355\u4E2A\u804A\u5929\u9891\u9053\u3002\u8868\u5C1A\u672A\u7531\u804A\u5929\u6A21\u5757\u5EFA\u51FA\u65F6\u8FD4\u56DE null\uFF0C\u4E0D\u8BA9\u8BF7\u6C42 500\u3002 */\nfunction readChatChannel(channelId: string): ChatChannelQQFlags | null {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels WHERE id = ${channelId}`);\n return Array.isArray(rows) ? ((rows[0] as ChatChannelQQFlags | undefined) ?? null) : null;\n } catch (err) {\n log.warn(`\u8BFB\u53D6\u804A\u5929\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** \u6D88\u606F\u6765\u6E90\u4E3A QQ \u7684\u9891\u9053\u3002QQ \u5165\u7AD9\u53EA\u6295\u5230\u8FD9\u4E9B\u9891\u9053\u3002 */\nfunction listQQSourceChannels(): ChatChannelQQFlags[] {\n try {\n const rows = query(SQL`SELECT * FROM sfmc_chat_channels`);\n if (!Array.isArray(rows)) return [];\n return (rows as ChatChannelQQFlags[]).filter((row) => row.source_qq === 1);\n } catch (err) {\n log.warn(`\u8BFB\u53D6 QQ \u6765\u6E90\u9891\u9053\u5931\u8D25: ${(err as Error).message}`);\n return [];\n }\n}\n\nconst messagesRoutes = createMessagesRoutes({\n query,\n body,\n json,\n getChannelForQQ: readChatChannel,\n listQQSourceChannels,\n forwardToQQBridge: (channelId: string, prefix: string, fromName: string, content: string, fromId: string) =>\n forwardToQQBridge(currentOutbound(), channelId, prefix, fromName, content, fromId),\n});\n\n// \u6E38\u620F\u53D1\u8A00\u8D70 db.tx \u63D2\u5165\uFF0C\u4E0D\u7ECF\u8FC7 /api/sfmc/messages\u3002\u63D0\u4EA4\u540E\u518D\u6309\u9891\u9053\u5F00\u5173\u8F6C\u53D1\u5230 QQ\u3002\ntxRunner.setAfterCommit((inserts) => {\n for (const item of inserts) {\n if (item.table !== \"sfmc_chat_messages\") continue;\n const payload = gameChatForwardPayload(item.row, readChatChannel(String(item.row.channel_id ?? \"\")));\n if (!payload) continue;\n forwardToQQBridge(\n currentOutbound(),\n payload.channelId,\n payload.prefix,\n payload.fromName,\n payload.content,\n payload.fromId,\n );\n }\n});\nconst configRoutes = createConfigRoutes({\n json,\n projectRoot: env.PROJECT_ROOT,\n // DIP:\u8DEF\u7531\u4E0D\u8BFB lock/catalog/token \u6587\u4EF6,\u7531\u5165\u53E3\u6CE8\u5165\u4E0E /modules \u540C\u6E90\u6570\u636E\n listModules: () => buildModuleList() as Array<Record<string, unknown>>,\n getModuleTokens: () => ({ ...moduleAuth.tokens }),\n});\nconst contentRoutes = createContentRoutes({\n json,\n loadSnapshot: loadContentSnapshotForRequest,\n});\nconst moduleRoutesInstance = createModuleRoutes({\n loadModuleCatalog,\n buildModuleList: buildModuleList as unknown as () => Array<Record<string, unknown>>,\n resolveModuleByKey,\n setModuleEnabled,\n body,\n json,\n});\n\n// \u2500\u2500 v2 \u8DEF\u7531\u5DE5\u5382 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// \u7C7B\u578B\u65AD\u8A00\u6210 unknown \u51FD\u6570 \u2014 v2 routes \u7684 ctx \u7C7B\u578B\u4E0E RouteCtx \u4E0D\u517C\u5BB9,\n// \u4F46\u8C03\u7528\u5951\u7EA6({path, method, params, req, res, body?})\u662F\u7A33\u5B9A\u7684\u3002\nconst dbRoutes = createDbRoutes({ schemaRegistry, txRunner, idempotent, json }) as unknown as (\n ctx: Record<string, unknown>\n) => Promise<boolean>;\nconst serviceRoutes = createServiceRoutes({\n serviceRegistry,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\nconst moduleConfigRoutes = createModuleConfigRoutes({\n projectRoot: env.PROJECT_ROOT,\n enabled: enabledManifests,\n json,\n}) as unknown as (ctx: Record<string, unknown>) => Promise<boolean>;\n\n// \u2500\u2500 \u4E3B\u8BF7\u6C42\u5904\u7406\u5668 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = new URL(req.url || \"/\", `http://${req.headers.host || \"localhost\"}`);\n const path = url.pathname;\n const method = req.method || \"GET\";\n const params = url.searchParams;\n\n // loopback \u7ED1\u5B9A\n const remote = req.socket.remoteAddress || \"\";\n if (remote && !remote.startsWith(\"127.\") && remote !== \"::1\" && remote !== \"::ffff:127.\") {\n json(res, { success: false, error: \"forbidden\" }, 403);\n return;\n }\n\n // \u2500\u2500 \u9884\u8BFB body(\u6240\u6709\u8DEF\u7531\u5171\u4EAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n await body(req);\n\n // \u2500\u2500 v2 \u6A21\u5757\u8EAB\u4EFD\u6821\u9A8C \u2192 \u5199\u5165\u8DEF\u7531 ctx.moduleAuth(LoD:\u4E0D\u6302 req \u79C1\u6709\u5B57\u6BB5) \u2500\u2500\n // \u6CE8\u610F:`/api/sfmc/configs/all` \u662F\u65E7\u7684\u4E00\u6B21\u6027\u914D\u7F6E\u5FEB\u7167\u7AEF\u70B9(SAPI ConfigManager.init\n // \u542F\u52A8\u5FC5\u7528),\u4E0D\u5C5E\u4E8E v2 \u6A21\u5757\u914D\u7F6E\u547D\u540D\u7A7A\u95F4(configs/<\u6A21\u5757 configKey>),\u5FC5\u987B\u8C41\u514D,\n // \u5426\u5219\u4F1A\u88AB\u6A21\u5757\u9274\u6743\u7F51\u5173\u62E6\u6210 401,\u5BFC\u81F4\u63D2\u4EF6\u7AEF\u8D77\u4E0D\u6765\u3002\n const isLegacyConfigAll = path === \"/api/sfmc/configs/all\";\n const needsModuleAuth =\n path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+(?:\\/(?:set|notify))?$/.test(path) && !isLegacyConfigAll);\n let moduleAuthCtx: { id: string; permissions: string[] } | null = null;\n if (needsModuleAuth) {\n const id = verifyModuleAuth({\n headers: req.headers,\n params,\n auth: moduleAuth,\n enabledModuleIds: enabledSet,\n });\n if (!id) {\n // LSP: v2 \u6A21\u5757\u95E8\u4E0E service-routes \u540C\u7528 ok \u65B9\u8A00\n jsonV2Fail(res, \"unauthorized: module identity invalid\", 401, \"unauthorized\");\n return;\n }\n const manifest = enabledManifests.get(id);\n moduleAuthCtx = {\n id,\n permissions: manifest?.permissions ?? [],\n };\n } else {\n // \u2500\u2500 \u65E7 env token \u9274\u6743(\u53EA\u5BF9 POST/PUT \u751F\u6548) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const PUBLIC_GET =\n path === \"/api/health\" ||\n path === \"/api/sfmc/status\" ||\n path === \"/api/sfmc/qq/join/apply-queue\" ||\n path === \"/api/sfmc/qq/join/settings\" ||\n (method === \"GET\" &&\n (path === \"/api/sfmc/content\" ||\n path === \"/api/sfmc/modules\" ||\n path === \"/api/sfmc/modules/catalog\" ||\n path.startsWith(\"/api/sfmc/modules/\") ||\n path === \"/api/sfmc/qq/bind/me\" ||\n path === \"/api/sfmc/qq/join/pending\"));\n // bind/join/events POST \u4E0E messages \u4E00\u6837\u8D70\u65E7 AUTH_TOKEN\uFF08\u82E5\u914D\u7F6E\uFF09\uFF1B\u6E38\u620F/\u6865\u540C\u4E3A loopback\n const NEEDS_AUTH =\n !PUBLIC_GET &&\n method !== \"GET\" &&\n !(\n path.startsWith(\"/api/sfmc/qq/bind/\") ||\n path.startsWith(\"/api/sfmc/qq/join/\") ||\n path.startsWith(\"/api/sfmc/qq/events\")\n );\n if (env.AUTH_TOKEN && NEEDS_AUTH) {\n const auth = req.headers[\"authorization\"] || \"\";\n const provided = auth.startsWith(\"Bearer \") ? auth.slice(7) : (req.headers[\"x-db-token\"] as string) || \"\";\n if (provided !== env.AUTH_TOKEN) {\n json(res, { success: false, error: \"unauthorized\" }, 401);\n return;\n }\n }\n }\n\n try {\n if (path === \"/api/sfmc/player-session\" && method === \"POST\") {\n const data = (await body(req)) as Record<string, unknown>;\n const playerName = String(data.playerName ?? \"\").trim();\n const xuid = String(data.xuid ?? \"\").trim();\n if (!playerName || !xuid) {\n json(res, { success: false, error: \"playerName_and_xuid_required\" }, 400);\n return;\n }\n syncPlayerSession(query, { playerName, xuid });\n json(res, { success: true });\n return;\n }\n // \u2500\u2500 v2 \u8DEF\u7531(\u4F18\u5148\u5339\u914D) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (\n moduleAuthCtx &&\n (path.startsWith(\"/api/sfmc/db/\") ||\n path.startsWith(\"/api/sfmc/services\") ||\n (/^\\/api\\/sfmc\\/configs\\/[A-Za-z0-9_-]+/.test(path) && !isLegacyConfigAll))\n ) {\n const ctx: Record<string, unknown> = {\n path,\n method,\n params,\n req,\n res,\n moduleAuth: moduleAuthCtx,\n };\n // body \u5DF2\u5728\u4E0A\u9762 `await body(req)` \u9884\u8BFB\u5E76\u7F13\u5B58\u5230 req._bodyPromise;\n // \u8FD9\u91CC\u590D\u7528\u7F13\u5B58(\u539F\u5B9E\u73B0\u8BFB\u7684 req._body \u4ECE\u672A\u88AB\u8D4B\u503C,\u5BFC\u81F4\u6240\u6709 v2 \u8DEF\u7531 body \u6052\u4E3A\u7A7A)\u3002\n ctx[\"body\"] = await body(req);\n if (path.startsWith(\"/api/sfmc/db/\")) {\n if (await dbRoutes(ctx)) return;\n } else if (path.startsWith(\"/api/sfmc/services\")) {\n if (await serviceRoutes(ctx)) return;\n } else {\n if (await moduleConfigRoutes(ctx)) return;\n }\n }\n\n // \u2500\u2500 \u5E73\u53F0\u8DEF\u7531 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const ctxBase = { path, method, params, req, res } as {\n path: string;\n method: string;\n params: URLSearchParams;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n };\n if (await contentRoutes(ctxBase)) return;\n if (await moduleRoutesInstance(ctxBase)) return;\n if (await healthRoutes(ctxBase)) return;\n if (await statusRoutes(ctxBase)) return;\n if (await qqBindRoutes(ctxBase)) return;\n if (await qqJoinRoutes(ctxBase)) return;\n if (await qqEventsRoutes(ctxBase)) return;\n if (await messagesRoutes(ctxBase)) return;\n if (await configRoutes(ctxBase)) return;\n\n json(res, { success: false, error: \"not_found\" }, 404);\n } catch (err) {\n log.err(err, \"DogeDB\");\n json(res, { success: false, error: (err as Error).message }, 500);\n }\n}\n\n// \u2500\u2500 \u542F\u52A8 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst server = createServer({\n env: { PORT: env.PORT, HOST: env.HOST, AUTH_TOKEN: env.AUTH_TOKEN },\n handle,\n});\nstartConsole(server, db);\n\nexport { db, enabledManifests, env, moduleAuth, query, schemaRegistry, serviceRegistry, txRunner };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["path"]
|
|
7
7
|
}
|
package/dist/module-auth.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export declare function revokeModuleToken(auth: ModuleAuthMap, moduleId: string)
|
|
|
56
56
|
* 校验请求方身份:
|
|
57
57
|
* - Bearer 头 / X-Module-Token 二选一
|
|
58
58
|
* - ?moduleId=<id> 必须存在
|
|
59
|
-
* - 模块必须在 enabledModuleIds
|
|
59
|
+
* - 模块必须在 enabledModuleIds 集合中(平台宿主 `_platform` 除外)
|
|
60
60
|
* - token 必须 = deriveToken(moduleId, secret)
|
|
61
61
|
* 返回模块 ID 或 null。
|
|
62
62
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-auth.d.ts","sourceRoot":"","sources":["../src/module-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;
|
|
1
|
+
{"version":3,"file":"module-auth.d.ts","sourceRoot":"","sources":["../src/module-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,GAAG,aAAa,CAgChB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAInE;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAQjG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACxC,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC/B,GAAG,MAAM,GAAG,IAAI,CAyBhB"}
|
package/dist/module-auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PLATFORM_SERVICE_OWNER } from "@sfmc-bds/sdk/contracts";
|
|
1
2
|
import { readJson, writeJson } from "@sfmc-bds/sdk/node/config";
|
|
2
3
|
import { createHmac, randomBytes } from "node:crypto";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
@@ -68,7 +69,8 @@ function revokeModuleToken(auth, moduleId) {
|
|
|
68
69
|
}
|
|
69
70
|
function verifyModuleAuth(opts) {
|
|
70
71
|
const moduleId = opts.params.get("moduleId") || "";
|
|
71
|
-
if (!moduleId
|
|
72
|
+
if (!moduleId) return null;
|
|
73
|
+
if (moduleId !== PLATFORM_SERVICE_OWNER && !opts.enabledModuleIds.has(moduleId)) return null;
|
|
72
74
|
let provided = "";
|
|
73
75
|
const auth = opts.headers["authorization"];
|
|
74
76
|
const token = opts.headers["x-module-token"];
|
package/dist/module-auth.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/module-auth.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * module-auth.ts \u2014 \u6A21\u5757\u8EAB\u4EFD\u7684 HMAC token \u6D3E\u751F\u4E0E\u6821\u9A8C\n *\n * \u4E24\u5957\u5E76\u5B58:\n * - `env.AUTH_TOKEN` (config db_config.json http_auth):sfmc \u7BA1\u63A7 / \u5E73\u53F0\u5185\u90E8\u7528\n * - `moduleTokens[moduleId]`:db-server \u7ED9\u6BCF\u4E2A enabled \u6A21\u5757\u5728\u542F\u52A8\u65F6\u6D3E\u751F,\n * \u5199\u5230\u300C\u4E0E\u6570\u636E\u5E93\u540C\u76EE\u5F55\u300D\u7684 module-tokens.json\uFF08\u8DDF db_config.dbDir \u8D70\uFF0C\u52FF\u5199\u6B7B ROOT/data\uFF09,\n * SAPI \u7ECF loopback API \u6CE8\u5165 setDbModuleContext\u3002\n *\n * \u6D3E\u751F\u516C\u5F0F:\n * module_token = HMAC-SHA256(auth_secret, \"sfmc-module:\" + moduleId).hex\n *\n * auth_secret \u4F18\u5148\u7EA7:\n * 1. env.AUTH_TOKEN\n * 2. \u542F\u52A8\u65F6\u968F\u673A\u751F\u6210(\u65E0 AUTH_TOKEN \u65F6),\u5199\u5230 module-tokens.json,\n * \u53EA\u8B66\u544A\u4E00\u6B21(\u6BCF\u6B21\u91CD\u542F\u6A21\u5757 token \u90FD\u53D8,SAPI host \u5FC5\u987B\u91CD\u8BFB\u6587\u4EF6)\n *\n * \u6821\u9A8C:\n * - HTTP `Authorization: Bearer <module_token>` \u5934\n * - Query `?moduleId=<id>`\n * - \u4E24\u90FD\u5339\u914D = \u8C03\u7528\u65B9\u8EAB\u4EFD\n * - \u6821\u9A8C\u8C03\u7528\u65B9 moduleId \u5728 enabled \u5217\u8868(\u5426\u5219 403)\n */\n\nimport { readJson, writeJson, type TokenStore } from \"@sfmc-bds/sdk/node/config\";\nimport { createHmac, randomBytes } from \"node:crypto\";\nimport { dirname, join } from \"node:path\";\nimport { log } from \"./lib/log.js\";\n\nexport interface ModuleAuthMap {\n /** moduleId \u2192 hex token */\n tokens: Record<string, string>;\n /** \u7528\u4E8E\u6D3E\u751F token \u7684 secret (env.AUTH_TOKEN \u6216\u672C\u6B21\u542F\u52A8\u968F\u673A\u751F\u6210) */\n secret: string;\n}\n\nconst IDENT = /^[A-Za-z0-9_\\-]+$/;\n\nfunction safeId(id: string): string {\n if (!IDENT.test(id)) throw new Error(`[auth] invalid module id: ${id}`);\n return id;\n}\n\n/** token \u6587\u4EF6\u4E0E SQLite \u540C\u76EE\u5F55\uFF08\u7531 dbDir \u51B3\u5B9A\uFF0C\u907F\u514D\u5411\u5BFC\u9009\u76EE\u5F55\u524D\u5C31\u521B\u5EFA ROOT/data\uFF09 */\nexport function moduleTokensFile(dbPath: string): string {\n return join(dirname(dbPath), \"module-tokens.json\");\n}\n\nexport function deriveToken(moduleId: string, secret: string): string {\n return createHmac(\"sha256\", secret)\n .update(`sfmc-module:${safeId(moduleId)}`)\n .digest(\"hex\");\n}\n\n/**\n * \u542F\u52A8\u65F6\u8C03\u7528\u4E00\u6B21:\n * - \u628A env.AUTH_TOKEN \u5F53 secret\n * - \u7ED9\u6BCF\u4E2A enabled moduleId \u6D3E\u751F token\n * - \u5199 module-tokens.json\uFF08\u4E0E DB \u540C\u76EE\u5F55\uFF09\n * - \u63A7\u5236\u53F0\u6253\u5370\u6458\u8981(env var SFMC_LOG_MODULE_TOKEN=1 \u65F6\u6253\u5B8C\u6574)\n */\nexport function buildModuleAuth(opts: {\n dbPath: string;\n envAuthToken: string;\n enabledModuleIds: string[];\n}): ModuleAuthMap {\n const secret = opts.envAuthToken || randomBytes(32).toString(\"hex\");\n const tokens: Record<string, string> = {};\n for (const id of opts.enabledModuleIds) {\n tokens[id] = deriveToken(id, secret);\n }\n\n const outFile = moduleTokensFile(opts.dbPath);\n writeJson(outFile, {\n tokens,\n secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !opts.envAuthToken,\n });\n\n log.success(`[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`);\n if (!opts.envAuthToken) {\n log.warn(\"[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN,\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret;\");\n }\n if (process.env.SFMC_LOG_MODULE_TOKEN === \"1\") {\n log.info(\"[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):\");\n for (const [id, t] of Object.entries(tokens)) {\n log.info(` ${id} = ${t}`);\n }\n } else {\n const lines = Object.entries(tokens)\n .map(([id, t]) => ` ${id} = ${t.slice(0, 6)}\u2026${t.slice(-4)}`)\n .join(\"\\n\");\n log.info(`[auth] token \u6458\u8981:\\n${lines}`);\n }\n\n return { tokens, secret };\n}\n\nexport function loadModuleAuth(dbPath: string): ModuleAuthMap | null {\n const parsed = readJson<TokenStore>(moduleTokensFile(dbPath));\n if (!parsed || !parsed.tokens || !parsed.secret) return null;\n return { tokens: parsed.tokens, secret: parsed.secret };\n}\n\n/** \u628A\u5F53\u524D auth map \u5199\u56DE module-tokens.json(\u70ED\u66F4\u65B0 token \u65F6\u590D\u7528) */\nexport function persistModuleAuth(dbPath: string, auth: ModuleAuthMap, envAuthToken: string): void {\n const outFile = moduleTokensFile(dbPath);\n writeJson(outFile, {\n tokens: auth.tokens,\n secret: auth.secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !envAuthToken,\n } satisfies TokenStore);\n}\n\n/**\n * \u786E\u4FDD moduleId \u5728 auth.tokens \u4E2D\u6709\u6D3E\u751F\u503C(\u590D\u7528\u73B0\u6709 secret,\u52FF\u91CD\u5EFA\u968F\u673A secret)\u3002\n * @returns \u662F\u5426\u65B0\u5199\u5165\u4E86 token\n */\nexport function ensureModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (auth.tokens[moduleId]) return false;\n auth.tokens[moduleId] = deriveToken(moduleId, auth.secret);\n return true;\n}\n\n/** \u7981\u7528\u65F6\u64A4\u9500\u5185\u5B58\u4E2D\u7684 token(\u78C1\u76D8\u7531 persistModuleAuth \u540C\u6B65) */\nexport function revokeModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (!(moduleId in auth.tokens)) return false;\n delete auth.tokens[moduleId];\n return true;\n}\n\n/**\n * \u6821\u9A8C\u8BF7\u6C42\u65B9\u8EAB\u4EFD:\n * - Bearer \u5934 / X-Module-Token \u4E8C\u9009\u4E00\n * - ?moduleId=<id> \u5FC5\u987B\u5B58\u5728\n * - \u6A21\u5757\u5FC5\u987B\u5728 enabledModuleIds \u96C6\u5408\u4E2D\n * - token \u5FC5\u987B = deriveToken(moduleId, secret)\n * \u8FD4\u56DE\u6A21\u5757 ID \u6216 null\u3002\n */\nexport function verifyModuleAuth(opts: {\n headers: NodeJS.Dict<string | string[]>;\n params: URLSearchParams;\n auth: ModuleAuthMap;\n enabledModuleIds: Set<string>;\n}): string | null {\n const moduleId = opts.params.get(\"moduleId\") || \"\";\n if (!moduleId
|
|
5
|
-
"mappings": "AAwBA,SAAS,UAAU,iBAAkC;AACrD,SAAS,YAAY,mBAAmB;AACxC,SAAS,SAAS,YAAY;AAC9B,SAAS,WAAW;AASpB,MAAM,QAAQ;AAEd,SAAS,OAAO,IAAoB;AAClC,MAAI,CAAC,MAAM,KAAK,EAAE,EAAG,OAAM,IAAI,MAAM,6BAA6B,EAAE,EAAE;AACtE,SAAO;AACT;AAGO,SAAS,iBAAiB,QAAwB;AACvD,SAAO,KAAK,QAAQ,MAAM,GAAG,oBAAoB;AACnD;AAEO,SAAS,YAAY,UAAkB,QAAwB;AACpE,SAAO,WAAW,UAAU,MAAM,EAC/B,OAAO,eAAe,OAAO,QAAQ,CAAC,EAAE,EACxC,OAAO,KAAK;AACjB;AASO,SAAS,gBAAgB,MAId;AAChB,QAAM,SAAS,KAAK,gBAAgB,YAAY,EAAE,EAAE,SAAS,KAAK;AAClE,QAAM,SAAiC,CAAC;AACxC,aAAW,MAAM,KAAK,kBAAkB;AACtC,WAAO,EAAE,IAAI,YAAY,IAAI,MAAM;AAAA,EACrC;AAEA,QAAM,UAAU,iBAAiB,KAAK,MAAM;AAC5C,YAAU,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,iBAAiB,CAAC,KAAK;AAAA,EACzB,CAAC;AAED,MAAI,QAAQ,uBAAa,KAAK,iBAAiB,MAAM,2DAAwB,OAAO,EAAE;AACtF,MAAI,CAAC,KAAK,cAAc;AACtB,QAAI,KAAK,yGAA6C;AAAA,EACxD;AACA,MAAI,QAAQ,IAAI,0BAA0B,KAAK;AAC7C,QAAI,KAAK,iDAAiD;AAC1D,eAAW,CAAC,IAAI,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC5C,UAAI,KAAK,KAAK,EAAE,MAAM,CAAC,EAAE;AAAA,IAC3B;AAAA,EACF,OAAO;AACL,UAAM,QAAQ,OAAO,QAAQ,MAAM,EAChC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,SAAI,EAAE,MAAM,EAAE,CAAC,EAAE,EAC5D,KAAK,IAAI;AACZ,QAAI,KAAK;AAAA,EAAqB,KAAK,EAAE;AAAA,EACvC;AAEA,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEO,SAAS,eAAe,QAAsC;AACnE,QAAM,SAAS,SAAqB,iBAAiB,MAAM,CAAC;AAC5D,MAAI,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,OAAO,OAAQ,QAAO;AACxD,SAAO,EAAE,QAAQ,OAAO,QAAQ,QAAQ,OAAO,OAAO;AACxD;AAGO,SAAS,kBAAkB,QAAgB,MAAqB,cAA4B;AACjG,QAAM,UAAU,iBAAiB,MAAM;AACvC,YAAU,SAAS;AAAA,IACjB,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,IACb,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,iBAAiB,CAAC;AAAA,EACpB,CAAsB;AACxB;AAMO,SAAS,kBAAkB,MAAqB,UAA2B;AAChF,MAAI,KAAK,OAAO,QAAQ,EAAG,QAAO;AAClC,OAAK,OAAO,QAAQ,IAAI,YAAY,UAAU,KAAK,MAAM;AACzD,SAAO;AACT;AAGO,SAAS,kBAAkB,MAAqB,UAA2B;AAChF,MAAI,EAAE,YAAY,KAAK,QAAS,QAAO;AACvC,SAAO,KAAK,OAAO,QAAQ;AAC3B,SAAO;AACT;AAUO,SAAS,iBAAiB,MAKf;AAChB,QAAM,WAAW,KAAK,OAAO,IAAI,UAAU,KAAK;AAChD,MAAI,CAAC,
|
|
4
|
+
"sourcesContent": ["/**\n * module-auth.ts \u2014 \u6A21\u5757\u8EAB\u4EFD\u7684 HMAC token \u6D3E\u751F\u4E0E\u6821\u9A8C\n *\n * \u4E24\u5957\u5E76\u5B58:\n * - `env.AUTH_TOKEN` (config db_config.json http_auth):sfmc \u7BA1\u63A7 / \u5E73\u53F0\u5185\u90E8\u7528\n * - `moduleTokens[moduleId]`:db-server \u7ED9\u6BCF\u4E2A enabled \u6A21\u5757\u5728\u542F\u52A8\u65F6\u6D3E\u751F,\n * \u5199\u5230\u300C\u4E0E\u6570\u636E\u5E93\u540C\u76EE\u5F55\u300D\u7684 module-tokens.json\uFF08\u8DDF db_config.dbDir \u8D70\uFF0C\u52FF\u5199\u6B7B ROOT/data\uFF09,\n * SAPI \u7ECF loopback API \u6CE8\u5165 setDbModuleContext\u3002\n *\n * \u6D3E\u751F\u516C\u5F0F:\n * module_token = HMAC-SHA256(auth_secret, \"sfmc-module:\" + moduleId).hex\n *\n * auth_secret \u4F18\u5148\u7EA7:\n * 1. env.AUTH_TOKEN\n * 2. \u542F\u52A8\u65F6\u968F\u673A\u751F\u6210(\u65E0 AUTH_TOKEN \u65F6),\u5199\u5230 module-tokens.json,\n * \u53EA\u8B66\u544A\u4E00\u6B21(\u6BCF\u6B21\u91CD\u542F\u6A21\u5757 token \u90FD\u53D8,SAPI host \u5FC5\u987B\u91CD\u8BFB\u6587\u4EF6)\n *\n * \u6821\u9A8C:\n * - HTTP `Authorization: Bearer <module_token>` \u5934\n * - Query `?moduleId=<id>`\n * - \u4E24\u90FD\u5339\u914D = \u8C03\u7528\u65B9\u8EAB\u4EFD\n * - \u6821\u9A8C\u8C03\u7528\u65B9 moduleId \u5728 enabled \u5217\u8868(\u5426\u5219 403)\n */\n\nimport { PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\nimport { readJson, writeJson, type TokenStore } from \"@sfmc-bds/sdk/node/config\";\nimport { createHmac, randomBytes } from \"node:crypto\";\nimport { dirname, join } from \"node:path\";\nimport { log } from \"./lib/log.js\";\n\nexport interface ModuleAuthMap {\n /** moduleId \u2192 hex token */\n tokens: Record<string, string>;\n /** \u7528\u4E8E\u6D3E\u751F token \u7684 secret (env.AUTH_TOKEN \u6216\u672C\u6B21\u542F\u52A8\u968F\u673A\u751F\u6210) */\n secret: string;\n}\n\nconst IDENT = /^[A-Za-z0-9_\\-]+$/;\n\nfunction safeId(id: string): string {\n if (!IDENT.test(id)) throw new Error(`[auth] invalid module id: ${id}`);\n return id;\n}\n\n/** token \u6587\u4EF6\u4E0E SQLite \u540C\u76EE\u5F55\uFF08\u7531 dbDir \u51B3\u5B9A\uFF0C\u907F\u514D\u5411\u5BFC\u9009\u76EE\u5F55\u524D\u5C31\u521B\u5EFA ROOT/data\uFF09 */\nexport function moduleTokensFile(dbPath: string): string {\n return join(dirname(dbPath), \"module-tokens.json\");\n}\n\nexport function deriveToken(moduleId: string, secret: string): string {\n return createHmac(\"sha256\", secret)\n .update(`sfmc-module:${safeId(moduleId)}`)\n .digest(\"hex\");\n}\n\n/**\n * \u542F\u52A8\u65F6\u8C03\u7528\u4E00\u6B21:\n * - \u628A env.AUTH_TOKEN \u5F53 secret\n * - \u7ED9\u6BCF\u4E2A enabled moduleId \u6D3E\u751F token\n * - \u5199 module-tokens.json\uFF08\u4E0E DB \u540C\u76EE\u5F55\uFF09\n * - \u63A7\u5236\u53F0\u6253\u5370\u6458\u8981(env var SFMC_LOG_MODULE_TOKEN=1 \u65F6\u6253\u5B8C\u6574)\n */\nexport function buildModuleAuth(opts: {\n dbPath: string;\n envAuthToken: string;\n enabledModuleIds: string[];\n}): ModuleAuthMap {\n const secret = opts.envAuthToken || randomBytes(32).toString(\"hex\");\n const tokens: Record<string, string> = {};\n for (const id of opts.enabledModuleIds) {\n tokens[id] = deriveToken(id, secret);\n }\n\n const outFile = moduleTokensFile(opts.dbPath);\n writeJson(outFile, {\n tokens,\n secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !opts.envAuthToken,\n });\n\n log.success(`[auth] \u5DF2\u4E3A ${opts.enabledModuleIds.length} \u4E2A\u6A21\u5757\u6D3E\u751F HMAC token,\u5199\u5165 ${outFile}`);\n if (!opts.envAuthToken) {\n log.warn(\"[auth] \u672A\u914D\u7F6E env.AUTH_TOKEN,\u672C\u6B21\u542F\u52A8\u4F7F\u7528\u4E86\u968F\u673A secret;\");\n }\n if (process.env.SFMC_LOG_MODULE_TOKEN === \"1\") {\n log.info(\"[auth] module tokens (SFMC_LOG_MODULE_TOKEN=1):\");\n for (const [id, t] of Object.entries(tokens)) {\n log.info(` ${id} = ${t}`);\n }\n } else {\n const lines = Object.entries(tokens)\n .map(([id, t]) => ` ${id} = ${t.slice(0, 6)}\u2026${t.slice(-4)}`)\n .join(\"\\n\");\n log.info(`[auth] token \u6458\u8981:\\n${lines}`);\n }\n\n return { tokens, secret };\n}\n\nexport function loadModuleAuth(dbPath: string): ModuleAuthMap | null {\n const parsed = readJson<TokenStore>(moduleTokensFile(dbPath));\n if (!parsed || !parsed.tokens || !parsed.secret) return null;\n return { tokens: parsed.tokens, secret: parsed.secret };\n}\n\n/** \u628A\u5F53\u524D auth map \u5199\u56DE module-tokens.json(\u70ED\u66F4\u65B0 token \u65F6\u590D\u7528) */\nexport function persistModuleAuth(dbPath: string, auth: ModuleAuthMap, envAuthToken: string): void {\n const outFile = moduleTokensFile(dbPath);\n writeJson(outFile, {\n tokens: auth.tokens,\n secret: auth.secret,\n generatedAt: new Date().toISOString(),\n secretGenerated: !envAuthToken,\n } satisfies TokenStore);\n}\n\n/**\n * \u786E\u4FDD moduleId \u5728 auth.tokens \u4E2D\u6709\u6D3E\u751F\u503C(\u590D\u7528\u73B0\u6709 secret,\u52FF\u91CD\u5EFA\u968F\u673A secret)\u3002\n * @returns \u662F\u5426\u65B0\u5199\u5165\u4E86 token\n */\nexport function ensureModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (auth.tokens[moduleId]) return false;\n auth.tokens[moduleId] = deriveToken(moduleId, auth.secret);\n return true;\n}\n\n/** \u7981\u7528\u65F6\u64A4\u9500\u5185\u5B58\u4E2D\u7684 token(\u78C1\u76D8\u7531 persistModuleAuth \u540C\u6B65) */\nexport function revokeModuleToken(auth: ModuleAuthMap, moduleId: string): boolean {\n if (!(moduleId in auth.tokens)) return false;\n delete auth.tokens[moduleId];\n return true;\n}\n\n/**\n * \u6821\u9A8C\u8BF7\u6C42\u65B9\u8EAB\u4EFD:\n * - Bearer \u5934 / X-Module-Token \u4E8C\u9009\u4E00\n * - ?moduleId=<id> \u5FC5\u987B\u5B58\u5728\n * - \u6A21\u5757\u5FC5\u987B\u5728 enabledModuleIds \u96C6\u5408\u4E2D\uFF08\u5E73\u53F0\u5BBF\u4E3B `_platform` \u9664\u5916\uFF09\n * - token \u5FC5\u987B = deriveToken(moduleId, secret)\n * \u8FD4\u56DE\u6A21\u5757 ID \u6216 null\u3002\n */\nexport function verifyModuleAuth(opts: {\n headers: NodeJS.Dict<string | string[]>;\n params: URLSearchParams;\n auth: ModuleAuthMap;\n enabledModuleIds: Set<string>;\n}): string | null {\n const moduleId = opts.params.get(\"moduleId\") || \"\";\n if (!moduleId) return null;\n // \u5E73\u53F0\u5BBF\u4E3B\u4E0D\u5728\u6A21\u5757 lock \u7684 enabled \u5217\u8868\u91CC\uFF0C\u4F46\u4ECD\u9700\u6709\u6548 token\n if (moduleId !== PLATFORM_SERVICE_OWNER && !opts.enabledModuleIds.has(moduleId)) return null;\n\n let provided = \"\";\n const auth = opts.headers[\"authorization\"];\n const token = opts.headers[\"x-module-token\"];\n if (typeof auth === \"string\" && auth.startsWith(\"Bearer \")) {\n provided = auth.slice(7);\n } else if (typeof token === \"string\") {\n provided = token;\n }\n if (!provided) return null;\n\n const expected = opts.auth.tokens[moduleId];\n if (!expected) return null;\n // constant-time \u6BD4\u8F83\n if (provided.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < provided.length; i++) {\n diff |= provided.charCodeAt(i) ^ expected.charCodeAt(i);\n }\n return diff === 0 ? moduleId : null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
* routes/qq-events.ts — MC 事件入站
|
|
3
3
|
*
|
|
4
4
|
* POST /api/sfmc/qq/events — 单条或 { events: [...] }(≤100)
|
|
5
|
-
* GET/POST /api/sfmc/qq/events/settings —
|
|
5
|
+
* GET/POST /api/sfmc/qq/events/settings — 查询或修改推送开关
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { QqEventsDispatcher, ResolvedQqEventsConfig } from "../domain/qq-events.js";
|
|
8
8
|
interface Deps {
|
|
9
9
|
body: (req: import("http").IncomingMessage) => Promise<Record<string, unknown>>;
|
|
10
10
|
json: (res: import("http").ServerResponse, data: Record<string, unknown>, status?: number) => void;
|
|
11
|
-
|
|
11
|
+
dispatcher: QqEventsDispatcher;
|
|
12
12
|
getSettings: () => ResolvedQqEventsConfig;
|
|
13
13
|
setSettings: (patch: Partial<ResolvedQqEventsConfig>) => ResolvedQqEventsConfig;
|
|
14
14
|
isAdmin: (openid: string, asGroupAdmin: boolean) => boolean;
|
|
15
15
|
}
|
|
16
|
-
declare function createQqEventsRoutes({ body, json,
|
|
16
|
+
declare function createQqEventsRoutes({ body, json, dispatcher, getSettings, setSettings, isAdmin }: Deps): ({ path, method, req, res, }: {
|
|
17
17
|
path: string;
|
|
18
18
|
method: string;
|
|
19
19
|
params: URLSearchParams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qq-events.d.ts","sourceRoot":"","sources":["../../src/routes/qq-events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAkB,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGzG,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,EAAE,MAAM,sBAAsB,CAAC;IAC1C,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,sBAAsB,CAAC,KAAK,sBAAsB,CAAC;IAChF,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,CAAC;CAC7D;AAeD,iBAAS,oBAAoB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,IAAI,iCAM5F;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"qq-events.d.ts","sourceRoot":"","sources":["../../src/routes/qq-events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAkB,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGzG,UAAU,IAAI;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnG,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,EAAE,MAAM,sBAAsB,CAAC;IAC1C,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,sBAAsB,CAAC,KAAK,sBAAsB,CAAC;IAChF,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,CAAC;CAC7D;AAeD,iBAAS,oBAAoB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,IAAI,iCAM5F;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,OAAO,MAAM,EAAE,eAAe,CAAC;IACpC,GAAG,EAAE,OAAO,MAAM,EAAE,cAAc,CAAC;CACpC,KAAG,OAAO,CAAC,OAAO,CAAC,CAgDrB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
package/dist/routes/qq-events.js
CHANGED
|
@@ -7,7 +7,7 @@ function collectPayloads(data) {
|
|
|
7
7
|
const one = normalizeEventPayload(data);
|
|
8
8
|
return one ? [one] : [];
|
|
9
9
|
}
|
|
10
|
-
function createQqEventsRoutes({ body, json,
|
|
10
|
+
function createQqEventsRoutes({ body, json, dispatcher, getSettings, setSettings, isAdmin }) {
|
|
11
11
|
return async function handle({
|
|
12
12
|
path,
|
|
13
13
|
method,
|
|
@@ -27,15 +27,6 @@ function createQqEventsRoutes({ body, json, aggregator, getSettings, setSettings
|
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
const field = data2.field;
|
|
30
|
-
if (field === "window_sec") {
|
|
31
|
-
const seconds = data2.value;
|
|
32
|
-
if (typeof seconds !== "number" || !Number.isInteger(seconds) || seconds < 5 || seconds > 600) {
|
|
33
|
-
json(res, { success: false, error: "invalid_setting" }, 400);
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
json(res, { success: true, settings: setSettings({ window_sec: seconds }) });
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
30
|
if (!SWITCHES.some((key) => key === field) || typeof data2.value !== "boolean") {
|
|
40
31
|
json(res, { success: false, error: "invalid_setting" }, 400);
|
|
41
32
|
return true;
|
|
@@ -62,7 +53,7 @@ function createQqEventsRoutes({ body, json, aggregator, getSettings, setSettings
|
|
|
62
53
|
json(res, { success: false, error: "invalid" }, 400);
|
|
63
54
|
return true;
|
|
64
55
|
}
|
|
65
|
-
const result =
|
|
56
|
+
const result = dispatcher.ingestMany(payloads);
|
|
66
57
|
json(res, { success: true, ...result });
|
|
67
58
|
return true;
|
|
68
59
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/routes/qq-events.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * routes/qq-events.ts \u2014 MC \u4E8B\u4EF6\u5165\u7AD9\n *\n * POST /api/sfmc/qq/events \u2014 \u5355\u6761\u6216 { events: [...] }\uFF08\u2264100\uFF09\n * GET/POST /api/sfmc/qq/events/settings \u2014 \u67E5\u8BE2\u6216\u4FEE\u6539\u63A8\u9001\u5F00\u5173\
|
|
5
|
-
"mappings": "AAQA,SAAS,6BAA6B;AAWtC,MAAM,WAAW,CAAC,WAAW,QAAQ,SAAS,SAAS,SAAS,SAAS,MAAM;AAE/E,SAAS,gBAAgB,MAAiD;AACxE,MAAI,MAAM,QAAQ,KAAK,MAAM,GAAG;AAC9B,WAAQ,KAAK,OACV,IAAI,CAAC,MAAM,sBAAsB,CAAC,CAAC,EACnC,OAAO,CAAC,MAA2B,KAAK,IAAI;AAAA,EACjD;AAEA,QAAM,MAAM,sBAAsB,IAAI;AACtC,SAAO,MAAM,CAAC,GAAG,IAAI,CAAC;AACxB;AAEA,SAAS,qBAAqB,EAAE,MAAM,MAAM,YAAY,aAAa,aAAa,QAAQ,GAAS;AACjG,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,gCAAgC;AAC3C,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,SAAS,MAAM,UAAU,YAAY,EAAE,CAAC;AACpD,eAAO;AAAA,MACT;AACA,UAAI,WAAW,QAAQ;AACrB,cAAMA,QAAO,MAAM,KAAK,GAAG;AAC3B,cAAM,SAAS,OAAOA,MAAK,WAAW,WAAWA,MAAK,OAAO,KAAK,IAAI;AACtE,YAAI,CAAC,QAAQ,QAAQA,MAAK,mBAAmB,IAAI,GAAG;AAClD,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,iBAAO;AAAA,QACT;AACA,cAAM,QAAQA,MAAK;AACnB,YAAI,
|
|
4
|
+
"sourcesContent": ["/**\n * routes/qq-events.ts \u2014 MC \u4E8B\u4EF6\u5165\u7AD9\n *\n * POST /api/sfmc/qq/events \u2014 \u5355\u6761\u6216 { events: [...] }\uFF08\u2264100\uFF09\n * GET/POST /api/sfmc/qq/events/settings \u2014 \u67E5\u8BE2\u6216\u4FEE\u6539\u63A8\u9001\u5F00\u5173\n */\n\nimport type { QqEventsDispatcher, QqEventPayload, ResolvedQqEventsConfig } from \"../domain/qq-events.js\";\nimport { normalizeEventPayload } from \"../domain/qq-events.js\";\n\ninterface Deps {\n body: (req: import(\"http\").IncomingMessage) => Promise<Record<string, unknown>>;\n json: (res: import(\"http\").ServerResponse, data: Record<string, unknown>, status?: number) => void;\n dispatcher: QqEventsDispatcher;\n getSettings: () => ResolvedQqEventsConfig;\n setSettings: (patch: Partial<ResolvedQqEventsConfig>) => ResolvedQqEventsConfig;\n isAdmin: (openid: string, asGroupAdmin: boolean) => boolean;\n}\n\nconst SWITCHES = [\"enabled\", \"join\", \"leave\", \"death\", \"crash\", \"start\", \"stop\"] as const;\n\nfunction collectPayloads(data: Record<string, unknown>): QqEventPayload[] {\n if (Array.isArray(data.events)) {\n return (data.events as unknown[])\n .map((x) => normalizeEventPayload(x))\n .filter((x): x is QqEventPayload => x != null);\n }\n // \u5355\u6761\uFF1A\u9876\u5C42\u5373\u4E8B\u4EF6\n const one = normalizeEventPayload(data);\n return one ? [one] : [];\n}\n\nfunction createQqEventsRoutes({ body, json, dispatcher, getSettings, setSettings, isAdmin }: Deps) {\n return async function handle({\n path,\n method,\n req,\n res,\n }: {\n path: string;\n method: string;\n params: URLSearchParams;\n req: import(\"http\").IncomingMessage;\n res: import(\"http\").ServerResponse;\n }): Promise<boolean> {\n if (path === \"/api/sfmc/qq/events/settings\") {\n if (method === \"GET\") {\n json(res, { success: true, settings: getSettings() });\n return true;\n }\n if (method === \"POST\") {\n const data = await body(req);\n const openid = typeof data.openid === \"string\" ? data.openid.trim() : \"\";\n if (!isAdmin(openid, data.as_group_admin === true)) {\n json(res, { success: false, error: \"not_admin\" }, 403);\n return true;\n }\n const field = data.field;\n if (!SWITCHES.some((key) => key === field) || typeof data.value !== \"boolean\") {\n json(res, { success: false, error: \"invalid_setting\" }, 400);\n return true;\n }\n const settings = setSettings({ [field as (typeof SWITCHES)[number]]: data.value });\n json(res, { success: true, settings });\n return true;\n }\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n if (path !== \"/api/sfmc/qq/events\") return false;\n\n if (method !== \"POST\") {\n json(res, { success: false, error: \"not_found\" }, 404);\n return true;\n }\n\n const data = await body(req);\n if (Array.isArray(data.events) && (data.events as unknown[]).length > 100) {\n json(res, { success: false, error: \"too many requests\" }, 413);\n return true;\n }\n\n const payloads = collectPayloads(data);\n if (payloads.length === 0) {\n json(res, { success: false, error: \"invalid\" }, 400);\n return true;\n }\n\n const result = dispatcher.ingestMany(payloads);\n json(res, { success: true, ...result });\n return true;\n };\n}\n\nexport { createQqEventsRoutes };\n"],
|
|
5
|
+
"mappings": "AAQA,SAAS,6BAA6B;AAWtC,MAAM,WAAW,CAAC,WAAW,QAAQ,SAAS,SAAS,SAAS,SAAS,MAAM;AAE/E,SAAS,gBAAgB,MAAiD;AACxE,MAAI,MAAM,QAAQ,KAAK,MAAM,GAAG;AAC9B,WAAQ,KAAK,OACV,IAAI,CAAC,MAAM,sBAAsB,CAAC,CAAC,EACnC,OAAO,CAAC,MAA2B,KAAK,IAAI;AAAA,EACjD;AAEA,QAAM,MAAM,sBAAsB,IAAI;AACtC,SAAO,MAAM,CAAC,GAAG,IAAI,CAAC;AACxB;AAEA,SAAS,qBAAqB,EAAE,MAAM,MAAM,YAAY,aAAa,aAAa,QAAQ,GAAS;AACjG,SAAO,eAAe,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMqB;AACnB,QAAI,SAAS,gCAAgC;AAC3C,UAAI,WAAW,OAAO;AACpB,aAAK,KAAK,EAAE,SAAS,MAAM,UAAU,YAAY,EAAE,CAAC;AACpD,eAAO;AAAA,MACT;AACA,UAAI,WAAW,QAAQ;AACrB,cAAMA,QAAO,MAAM,KAAK,GAAG;AAC3B,cAAM,SAAS,OAAOA,MAAK,WAAW,WAAWA,MAAK,OAAO,KAAK,IAAI;AACtE,YAAI,CAAC,QAAQ,QAAQA,MAAK,mBAAmB,IAAI,GAAG;AAClD,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,iBAAO;AAAA,QACT;AACA,cAAM,QAAQA,MAAK;AACnB,YAAI,CAAC,SAAS,KAAK,CAAC,QAAQ,QAAQ,KAAK,KAAK,OAAOA,MAAK,UAAU,WAAW;AAC7E,eAAK,KAAK,EAAE,SAAS,OAAO,OAAO,kBAAkB,GAAG,GAAG;AAC3D,iBAAO;AAAA,QACT;AACA,cAAM,WAAW,YAAY,EAAE,CAAC,KAAkC,GAAGA,MAAK,MAAM,CAAC;AACjF,aAAK,KAAK,EAAE,SAAS,MAAM,SAAS,CAAC;AACrC,eAAO;AAAA,MACT;AACA,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AACA,QAAI,SAAS,sBAAuB,QAAO;AAE3C,QAAI,WAAW,QAAQ;AACrB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,YAAY,GAAG,GAAG;AACrD,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,KAAK,GAAG;AAC3B,QAAI,MAAM,QAAQ,KAAK,MAAM,KAAM,KAAK,OAAqB,SAAS,KAAK;AACzE,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,oBAAoB,GAAG,GAAG;AAC7D,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,gBAAgB,IAAI;AACrC,QAAI,SAAS,WAAW,GAAG;AACzB,WAAK,KAAK,EAAE,SAAS,OAAO,OAAO,UAAU,GAAG,GAAG;AACnD,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,WAAW,WAAW,QAAQ;AAC7C,SAAK,KAAK,EAAE,SAAS,MAAM,GAAG,OAAO,CAAC;AACtC,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": ["data"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../src/service-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,EAAE,EAAE,YAAY,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,EAAE,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAO/E,mBAAmB;AACnB,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAK5E;IAED;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEpC;IAED;;;;OAIG;IACH,IAAI,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAEhD;IAED;;;;;;;;;;;OAWG;IACG,QAAQ,CAEZ,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACtC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,EAAE,CAAC,EAAE,gBAAgB,GACpB,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../src/service-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,EAAE,EAAE,YAAY,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,EAAE,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAO/E,mBAAmB;AACnB,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAK5E;IAED;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEpC;IAED;;;;OAIG;IACH,IAAI,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAEhD;IAED;;;;;;;;;;;OAWG;IACG,QAAQ,CAEZ,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACtC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,EAAE,CAAC,EAAE,gBAAgB,GACpB,OAAO,CAAC,iBAAiB,CAAC,CA0C5B;CAEF;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IACxF,MAAM,EAAE,MAAM,CAAC;IACf,YACE,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,EACvF,MAAM,EAAE,MAAM,EAKf;CACF"}
|
package/dist/service-registry.js
CHANGED
|
@@ -51,17 +51,20 @@ class ServiceRegistry {
|
|
|
51
51
|
if (!platformService && !enabled.has(handler.moduleId)) {
|
|
52
52
|
throw new DispatchError(`service "${name}" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, "forbidden", 403);
|
|
53
53
|
}
|
|
54
|
+
const platformCaller = callerModuleId === PLATFORM_SERVICE_OWNER;
|
|
54
55
|
const caller = enabled.get(callerModuleId);
|
|
55
|
-
if (!caller) {
|
|
56
|
+
if (!platformCaller && !caller) {
|
|
56
57
|
throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, "forbidden", 403);
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
if (!platformCaller) {
|
|
60
|
+
const declared = caller.services.requires.find((r) => r.name === name);
|
|
61
|
+
if (!declared && callerModuleId !== handler.moduleId) {
|
|
62
|
+
throw new DispatchError(
|
|
63
|
+
`${callerModuleId} \u7684 manifest.services.requires \u672A\u58F0\u660E "${name}"`,
|
|
64
|
+
"not_in_requires",
|
|
65
|
+
403
|
|
66
|
+
);
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
69
|
try {
|
|
67
70
|
const result = await handler.handle({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/service-registry.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * service-registry.ts \u2014 \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\uFF08Service Registry\uFF09\n *\n * \u534F\u8BAE\u89C4\u8303\uFF1A\n * - HTTP GET `/api/sfmc/services/<name>?input=<urlencoded-json>`\n * - \u9274\u6743\u673A\u5236\uFF1ABearer module_token + \u67E5\u8BE2\u53C2\u6570 `?moduleId=<callerId>`\n *\n * \u8C03\u5EA6\u4E0E\u4E8B\u52A1\u8FB9\u754C\uFF1A\n * \u670D\u52A1\u5904\u7406\u5668\u6CE8\u518C\u5728 db-server \u8FDB\u7A0B\u5185\u5B58\u4E2D\u3002\n * \u5728\u5916\u5C42 `db.tx` \u4E8B\u52A1\u4F1A\u8BDD\u5185\u8C03\u7528 `tx.call` \u65F6\uFF0C\u4F1A\u901A\u8FC7 `ctx.tx = { query, db }` \u6CE8\u5165\u73B0\u6709\u4E8B\u52A1\u4E0A\u4E0B\u6587\uFF0C\n * \u5904\u7406\u5668\u590D\u7528\u5F53\u524D\u4E8B\u52A1\u8FDE\u63A5\uFF0C\u4E25\u7981\u91CD\u590D\u5F00\u542F\u5D4C\u5957\u4E8B\u52A1\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport type { QueryFn } from \"./lib/sqlite.js\";\nimport { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\n\nexport interface ServiceCallResult {\n ok: true;\n result: unknown;\n}\n\nexport interface ServiceTxContext {\n query: QueryFn;\n db: DatabaseSync;\n}\n\nexport interface ServiceDispatchContext {\n callerModuleId: string;\n payload: unknown;\n /** \u5916\u5C42 db.tx \u5DF2\u6253\u5F00\u65F6\u6CE8\u5165\uFF1B\u5904\u7406\u5668\u590D\u7528\u6B64\u8FDE\u63A5\uFF0C\u7981\u6B62\u5F00\u542F\u5D4C\u5957 BEGIN\u3002 */\n tx?: ServiceTxContext;\n}\n\nexport type ServiceHandler = (ctx: ServiceDispatchContext) => Promise<unknown>;\n\ninterface RegisteredHandler {\n moduleId: string;\n handle: ServiceHandler;\n}\n\n/** \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u7BA1\u7406\u5668\u3002 */\nexport class ServiceRegistry {\n private readonly handlers = new Map<string, RegisteredHandler>();\n\n /**\n * \u6CE8\u518C\u6307\u5B9A\u6A21\u5757\u63D0\u4F9B\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param moduleId \u670D\u52A1\u63D0\u4F9B\u65B9\u6A21\u5757 ID\u3002\n * @param name \u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param handle \u4E1A\u52A1\u5904\u7406\u51FD\u6570\u3002\n */\n registerHandler(moduleId: string, name: string, handle: ServiceHandler): void {\n if (this.handlers.has(name)) {\n throw new Error(`[service] \"${name}\" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C, ${moduleId} \u62A2\u6CE8`);\n }\n this.handlers.set(name, { moduleId, handle });\n }\n\n /**\n * \u6CE8\u9500\u6307\u5B9A\u540D\u79F0\u7684\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param name \u5F85\u6CE8\u9500\u7684\u670D\u52A1\u540D\u79F0\u3002\n */\n unregisterHandler(name: string): void {\n this.handlers.delete(name);\n }\n\n /**\n * \u83B7\u53D6\u5F53\u524D\u5DF2\u6CE8\u518C\u7684\u5168\u90E8\u8DE8\u6A21\u5757\u670D\u52A1\u5217\u8868\u3002\n *\n * @returns \u5305\u542B\u670D\u52A1\u540D\u4E0E\u6240\u5C5E\u6A21\u5757 ID \u7684\u5217\u8868\u3002\n */\n list(): Array<{ name: string; moduleId: string }> {\n return [...this.handlers.entries()].map(([name, h]) => ({ name, moduleId: h.moduleId }));\n }\n\n /**\n * \u8C03\u5EA6\u5E76\u6267\u884C\u6307\u5B9A\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u3002\n * \u5305\u542B\u8C03\u7528\u65B9\u4E0E\u63D0\u4F9B\u65B9\u542F\u505C\u72B6\u6001\u6821\u9A8C\u3001\u58F0\u660E\u5F0F\u5951\u7EA6\uFF08`services.requires`\uFF09\u5339\u914D\u53CA\u5F02\u5E38\u5206\u6D41\u3002\n *\n * @param enabled \u5F53\u524D\u6240\u6709\u5DF2\u542F\u7528\u7684\u6A21\u5757\u6E05\u5355\u5B57\u5178\u3002\n * @param callerModuleId \u53D1\u8D77\u8C03\u7528\u7684\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param name \u76EE\u6807\u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param payload \u4F20\u5165\u8C03\u7528\u7684\u53C2\u6570\u6570\u636E\u3002\n * @param tx \u53EF\u9009\u7684\u5F53\u524D\u4E8B\u52A1\u4E0A\u4E0B\u6587\u3002\n * @returns \u670D\u52A1\u6267\u884C\u8FD4\u56DE\u7ED3\u679C\u3002\n * @throws {DispatchError} \u670D\u52A1\u672A\u627E\u5230\u3001\u672A\u58F0\u660E\u4F9D\u8D56\u6216\u9274\u6743\u5931\u8D25\u65F6\u629B\u51FA\u3002\n */\n async dispatch(\n\n enabled: Map<string, ModuleManifestV2>,\n callerModuleId: string,\n name: string,\n payload: unknown,\n tx?: ServiceTxContext\n ): Promise<ServiceCallResult> {\n const handler = this.handlers.get(name);\n if (!handler) {\n throw new DispatchError(`service \"${name}\" \u672A\u6CE8\u518C`, \"no_such_service\", 404);\n }\n const platformService = handler.moduleId === PLATFORM_SERVICE_OWNER && PLATFORM_MONITORING_SERVICES.some(value => value === name);\n if (!platformService && !enabled.has(handler.moduleId)) {\n throw new DispatchError(`service \"${name}\" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, \"forbidden\", 403);\n }\n const caller = enabled.get(callerModuleId);\n if (!caller) {\n throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, \"forbidden\", 403);\n }\n const declared = caller
|
|
5
|
-
"mappings": "AAgBA,SAAS,8BAA8B,8BAA8B;AA2B9D,MAAM,gBAAgB;AAAA,EACV,WAAW,oBAAI,IAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/D,gBAAgB,UAAkB,MAAc,QAA8B;AAC5E,QAAI,KAAK,SAAS,IAAI,IAAI,GAAG;AAC3B,YAAM,IAAI,MAAM,cAAc,IAAI,kBAAQ,KAAK,SAAS,IAAI,IAAI,GAAG,QAAQ,kBAAQ,QAAQ,eAAK;AAAA,IAClG;AACA,SAAK,SAAS,IAAI,MAAM,EAAE,UAAU,OAAO,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,MAAoB;AACpC,SAAK,SAAS,OAAO,IAAI;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAkD;AAChD,WAAO,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,SAEJ,SACA,gBACA,MACA,SACA,IAC4B;AAC5B,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,mBAAmB,GAAG;AAAA,IACzE;AACA,UAAM,kBAAkB,QAAQ,aAAa,0BAA0B,6BAA6B,KAAK,WAAS,UAAU,IAAI;AAChI,QAAI,CAAC,mBAAmB,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;AACtD,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,QAAQ,QAAQ,mBAAc,aAAa,GAAG;AAAA,IACjG;
|
|
4
|
+
"sourcesContent": ["/**\n * service-registry.ts \u2014 \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\uFF08Service Registry\uFF09\n *\n * \u534F\u8BAE\u89C4\u8303\uFF1A\n * - HTTP GET `/api/sfmc/services/<name>?input=<urlencoded-json>`\n * - \u9274\u6743\u673A\u5236\uFF1ABearer module_token + \u67E5\u8BE2\u53C2\u6570 `?moduleId=<callerId>`\n *\n * \u8C03\u5EA6\u4E0E\u4E8B\u52A1\u8FB9\u754C\uFF1A\n * \u670D\u52A1\u5904\u7406\u5668\u6CE8\u518C\u5728 db-server \u8FDB\u7A0B\u5185\u5B58\u4E2D\u3002\n * \u5728\u5916\u5C42 `db.tx` \u4E8B\u52A1\u4F1A\u8BDD\u5185\u8C03\u7528 `tx.call` \u65F6\uFF0C\u4F1A\u901A\u8FC7 `ctx.tx = { query, db }` \u6CE8\u5165\u73B0\u6709\u4E8B\u52A1\u4E0A\u4E0B\u6587\uFF0C\n * \u5904\u7406\u5668\u590D\u7528\u5F53\u524D\u4E8B\u52A1\u8FDE\u63A5\uFF0C\u4E25\u7981\u91CD\u590D\u5F00\u542F\u5D4C\u5957\u4E8B\u52A1\u3002\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ModuleManifestV2 } from \"./manifest-loader.js\";\nimport type { QueryFn } from \"./lib/sqlite.js\";\nimport { PLATFORM_MONITORING_SERVICES, PLATFORM_SERVICE_OWNER } from \"@sfmc-bds/sdk/contracts\";\n\nexport interface ServiceCallResult {\n ok: true;\n result: unknown;\n}\n\nexport interface ServiceTxContext {\n query: QueryFn;\n db: DatabaseSync;\n}\n\nexport interface ServiceDispatchContext {\n callerModuleId: string;\n payload: unknown;\n /** \u5916\u5C42 db.tx \u5DF2\u6253\u5F00\u65F6\u6CE8\u5165\uFF1B\u5904\u7406\u5668\u590D\u7528\u6B64\u8FDE\u63A5\uFF0C\u7981\u6B62\u5F00\u542F\u5D4C\u5957 BEGIN\u3002 */\n tx?: ServiceTxContext;\n}\n\nexport type ServiceHandler = (ctx: ServiceDispatchContext) => Promise<unknown>;\n\ninterface RegisteredHandler {\n moduleId: string;\n handle: ServiceHandler;\n}\n\n/** \u8DE8\u6A21\u5757\u670D\u52A1\u6CE8\u518C\u8868\u7BA1\u7406\u5668\u3002 */\nexport class ServiceRegistry {\n private readonly handlers = new Map<string, RegisteredHandler>();\n\n /**\n * \u6CE8\u518C\u6307\u5B9A\u6A21\u5757\u63D0\u4F9B\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param moduleId \u670D\u52A1\u63D0\u4F9B\u65B9\u6A21\u5757 ID\u3002\n * @param name \u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param handle \u4E1A\u52A1\u5904\u7406\u51FD\u6570\u3002\n */\n registerHandler(moduleId: string, name: string, handle: ServiceHandler): void {\n if (this.handlers.has(name)) {\n throw new Error(`[service] \"${name}\" \u5DF2\u88AB ${this.handlers.get(name)?.moduleId} \u6CE8\u518C, ${moduleId} \u62A2\u6CE8`);\n }\n this.handlers.set(name, { moduleId, handle });\n }\n\n /**\n * \u6CE8\u9500\u6307\u5B9A\u540D\u79F0\u7684\u670D\u52A1\u5904\u7406\u5668\u3002\n *\n * @param name \u5F85\u6CE8\u9500\u7684\u670D\u52A1\u540D\u79F0\u3002\n */\n unregisterHandler(name: string): void {\n this.handlers.delete(name);\n }\n\n /**\n * \u83B7\u53D6\u5F53\u524D\u5DF2\u6CE8\u518C\u7684\u5168\u90E8\u8DE8\u6A21\u5757\u670D\u52A1\u5217\u8868\u3002\n *\n * @returns \u5305\u542B\u670D\u52A1\u540D\u4E0E\u6240\u5C5E\u6A21\u5757 ID \u7684\u5217\u8868\u3002\n */\n list(): Array<{ name: string; moduleId: string }> {\n return [...this.handlers.entries()].map(([name, h]) => ({ name, moduleId: h.moduleId }));\n }\n\n /**\n * \u8C03\u5EA6\u5E76\u6267\u884C\u6307\u5B9A\u7684\u8DE8\u6A21\u5757\u670D\u52A1\u3002\n * \u5305\u542B\u8C03\u7528\u65B9\u4E0E\u63D0\u4F9B\u65B9\u542F\u505C\u72B6\u6001\u6821\u9A8C\u3001\u58F0\u660E\u5F0F\u5951\u7EA6\uFF08`services.requires`\uFF09\u5339\u914D\u53CA\u5F02\u5E38\u5206\u6D41\u3002\n *\n * @param enabled \u5F53\u524D\u6240\u6709\u5DF2\u542F\u7528\u7684\u6A21\u5757\u6E05\u5355\u5B57\u5178\u3002\n * @param callerModuleId \u53D1\u8D77\u8C03\u7528\u7684\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param name \u76EE\u6807\u670D\u52A1\u5B8C\u6574\u540D\u79F0\u3002\n * @param payload \u4F20\u5165\u8C03\u7528\u7684\u53C2\u6570\u6570\u636E\u3002\n * @param tx \u53EF\u9009\u7684\u5F53\u524D\u4E8B\u52A1\u4E0A\u4E0B\u6587\u3002\n * @returns \u670D\u52A1\u6267\u884C\u8FD4\u56DE\u7ED3\u679C\u3002\n * @throws {DispatchError} \u670D\u52A1\u672A\u627E\u5230\u3001\u672A\u58F0\u660E\u4F9D\u8D56\u6216\u9274\u6743\u5931\u8D25\u65F6\u629B\u51FA\u3002\n */\n async dispatch(\n\n enabled: Map<string, ModuleManifestV2>,\n callerModuleId: string,\n name: string,\n payload: unknown,\n tx?: ServiceTxContext\n ): Promise<ServiceCallResult> {\n const handler = this.handlers.get(name);\n if (!handler) {\n throw new DispatchError(`service \"${name}\" \u672A\u6CE8\u518C`, \"no_such_service\", 404);\n }\n const platformService = handler.moduleId === PLATFORM_SERVICE_OWNER && PLATFORM_MONITORING_SERVICES.some(value => value === name);\n if (!platformService && !enabled.has(handler.moduleId)) {\n throw new DispatchError(`service \"${name}\" \u63D0\u4F9B\u65B9 ${handler.moduleId} \u672A enabled`, \"forbidden\", 403);\n }\n // \u5E73\u53F0\u5BBF\u4E3B\u53EF\u8C03\u7528\u4EFB\u610F\u5DF2\u6CE8\u518C\u670D\u52A1\uFF08QQ \u8D26\u53F7\u5FEB\u7167\u7B49\u53EF\u9009\u4F9D\u8D56\uFF09\uFF1B\u5176\u5B83\u8C03\u7528\u65B9\u4ECD\u8D70 enabled + requires\n const platformCaller = callerModuleId === PLATFORM_SERVICE_OWNER;\n const caller = enabled.get(callerModuleId);\n if (!platformCaller && !caller) {\n throw new DispatchError(`\u8C03\u7528\u65B9 ${callerModuleId} \u672A enabled`, \"forbidden\", 403);\n }\n if (!platformCaller) {\n const declared = caller!.services.requires.find((r) => r.name === name);\n // \u63D0\u4F9B\u65B9\u8C03\u81EA\u5DF1\u7684 service \u514D requires(\u4F8B\u5982 economy \u767D\u76AE\u4E66\u8C03 stats.monthly)\n if (!declared && callerModuleId !== handler.moduleId) {\n throw new DispatchError(\n `${callerModuleId} \u7684 manifest.services.requires \u672A\u58F0\u660E \"${name}\"`,\n \"not_in_requires\",\n 403\n );\n }\n }\n try {\n const result = await handler.handle({\n callerModuleId,\n payload,\n ...(tx ? { tx } : {}),\n });\n return { ok: true, result };\n } catch (e) {\n // \u4FDD\u7559 handler \u629B\u51FA\u7684\u9886\u57DF/\u9274\u6743\u9519\u8BEF\u5951\u7EA6,\u52FF\u538B\u6210 500 internal(LSP)\n if (e instanceof DispatchError) throw e;\n const err = e as Error & { code?: string; status?: number };\n if (typeof err.status === \"number\" && err.code) {\n throw new DispatchError(err.message, err.code as DispatchError[\"code\"], err.status);\n }\n throw new DispatchError(`handler \u629B\u9519: ${err.message}`, \"internal\", 500);\n }\n }\n\n}\n\nexport class DispatchError extends Error {\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\";\n status: number;\n constructor(\n message: string,\n code: \"no_such_service\" | \"not_in_requires\" | \"forbidden\" | \"internal\" | \"domain_error\",\n status: number\n ) {\n super(message);\n this.code = code;\n this.status = status;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAgBA,SAAS,8BAA8B,8BAA8B;AA2B9D,MAAM,gBAAgB;AAAA,EACV,WAAW,oBAAI,IAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/D,gBAAgB,UAAkB,MAAc,QAA8B;AAC5E,QAAI,KAAK,SAAS,IAAI,IAAI,GAAG;AAC3B,YAAM,IAAI,MAAM,cAAc,IAAI,kBAAQ,KAAK,SAAS,IAAI,IAAI,GAAG,QAAQ,kBAAQ,QAAQ,eAAK;AAAA,IAClG;AACA,SAAK,SAAS,IAAI,MAAM,EAAE,UAAU,OAAO,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,MAAoB;AACpC,SAAK,SAAS,OAAO,IAAI;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAkD;AAChD,WAAO,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,UAAU,EAAE,SAAS,EAAE;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,SAEJ,SACA,gBACA,MACA,SACA,IAC4B;AAC5B,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,mBAAmB,GAAG;AAAA,IACzE;AACA,UAAM,kBAAkB,QAAQ,aAAa,0BAA0B,6BAA6B,KAAK,WAAS,UAAU,IAAI;AAChI,QAAI,CAAC,mBAAmB,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;AACtD,YAAM,IAAI,cAAc,YAAY,IAAI,wBAAS,QAAQ,QAAQ,mBAAc,aAAa,GAAG;AAAA,IACjG;AAEA,UAAM,iBAAiB,mBAAmB;AAC1C,UAAM,SAAS,QAAQ,IAAI,cAAc;AACzC,QAAI,CAAC,kBAAkB,CAAC,QAAQ;AAC9B,YAAM,IAAI,cAAc,sBAAO,cAAc,mBAAc,aAAa,GAAG;AAAA,IAC7E;AACA,QAAI,CAAC,gBAAgB;AACnB,YAAM,WAAW,OAAQ,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAEtE,UAAI,CAAC,YAAY,mBAAmB,QAAQ,UAAU;AACpD,cAAM,IAAI;AAAA,UACR,GAAG,cAAc,0DAAsC,IAAI;AAAA,UAC3D;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,OAAO;AAAA,QAClC;AAAA,QACA;AAAA,QACA,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,MACrB,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,OAAO;AAAA,IAC5B,SAAS,GAAG;AAEV,UAAI,aAAa,cAAe,OAAM;AACtC,YAAM,MAAM;AACZ,UAAI,OAAO,IAAI,WAAW,YAAY,IAAI,MAAM;AAC9C,cAAM,IAAI,cAAc,IAAI,SAAS,IAAI,MAA+B,IAAI,MAAM;AAAA,MACpF;AACA,YAAM,IAAI,cAAc,yBAAe,IAAI,OAAO,IAAI,YAAY,GAAG;AAAA,IACvE;AAAA,EACF;AAEF;AAEO,MAAM,sBAAsB,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EACA,YACE,SACA,MACA,QACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sfmc-bds/db-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "SFMC SQLite HTTP REST backend — configs, modules, gameplay data",
|
|
5
5
|
"author": "Shiroha7z",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"sql-template-strings": "^2.2.2",
|
|
35
|
-
"@sfmc-bds/bds-tools": "0.2.
|
|
36
|
-
"@sfmc-bds/sdk": "0.
|
|
35
|
+
"@sfmc-bds/bds-tools": "0.2.8",
|
|
36
|
+
"@sfmc-bds/sdk": "0.4.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@sfmc-bds/tools": "0.2.
|
|
39
|
+
"@sfmc-bds/tools": "0.2.8"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public",
|