@snack-kit/lib 0.1.0 → 0.2.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/cjs/chunk-YOWLTZM5.cjs +868 -0
- package/dist/cjs/chunk-YOWLTZM5.cjs.map +1 -0
- package/dist/cjs/debugger.cjs +2 -2
- package/dist/cjs/index.cjs +3 -3
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/es/chunk-4DLFIN3C.js +866 -0
- package/dist/es/chunk-4DLFIN3C.js.map +1 -0
- package/dist/es/debugger.js +1 -1
- package/dist/es/index.js +2 -2
- package/dist/es/index.js.map +1 -1
- package/dist/types/debugger.d.ts +22 -18
- package/dist/types/index.d.ts +1 -1
- package/dist/umd/debugger.global.js +662 -157
- package/dist/umd/debugger.global.js.map +1 -1
- package/dist/umd/index.global.js +663 -158
- package/dist/umd/index.global.js.map +1 -1
- package/package.json +1 -1
- package/dist/cjs/chunk-JBJEXFQC.cjs +0 -363
- package/dist/cjs/chunk-JBJEXFQC.cjs.map +0 -1
- package/dist/es/chunk-QBBEQHXG.js +0 -361
- package/dist/es/chunk-QBBEQHXG.js.map +0 -1
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
import { Get, Context } from './chunk-JQYH5FWE.js';
|
|
2
|
-
|
|
3
|
-
// src/debugger/debugger.ts
|
|
4
|
-
var STORAGE_PREFIX = "__snackkit_debugger__";
|
|
5
|
-
var KEY_GATEWAY = `${STORAGE_PREFIX}gateway`;
|
|
6
|
-
var KEY_TYPE = `${STORAGE_PREFIX}type`;
|
|
7
|
-
var KEY_SERVER = `${STORAGE_PREFIX}server`;
|
|
8
|
-
var PANEL_STYLE = `
|
|
9
|
-
#__snackkit_debugger__ {
|
|
10
|
-
position: fixed;
|
|
11
|
-
bottom: 16px;
|
|
12
|
-
right: 16px;
|
|
13
|
-
z-index: 99999;
|
|
14
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
15
|
-
font-size: 13px;
|
|
16
|
-
color: #e2e8f0;
|
|
17
|
-
}
|
|
18
|
-
#__snackkit_debugger__ .dbg-toggle {
|
|
19
|
-
width: 40px;
|
|
20
|
-
height: 40px;
|
|
21
|
-
border-radius: 50%;
|
|
22
|
-
background: #3b82f6;
|
|
23
|
-
border: none;
|
|
24
|
-
cursor: pointer;
|
|
25
|
-
display: flex;
|
|
26
|
-
align-items: center;
|
|
27
|
-
justify-content: center;
|
|
28
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
29
|
-
margin-left: auto;
|
|
30
|
-
color: #fff;
|
|
31
|
-
font-size: 18px;
|
|
32
|
-
}
|
|
33
|
-
#__snackkit_debugger__ .dbg-panel {
|
|
34
|
-
background: #1e293b;
|
|
35
|
-
border: 1px solid #334155;
|
|
36
|
-
border-radius: 8px;
|
|
37
|
-
padding: 12px;
|
|
38
|
-
width: 320px;
|
|
39
|
-
margin-bottom: 8px;
|
|
40
|
-
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
|
|
41
|
-
display: none;
|
|
42
|
-
}
|
|
43
|
-
#__snackkit_debugger__ .dbg-panel.open { display: block; }
|
|
44
|
-
#__snackkit_debugger__ .dbg-row {
|
|
45
|
-
margin-bottom: 8px;
|
|
46
|
-
display: flex;
|
|
47
|
-
flex-direction: column;
|
|
48
|
-
gap: 4px;
|
|
49
|
-
}
|
|
50
|
-
#__snackkit_debugger__ label {
|
|
51
|
-
font-size: 11px;
|
|
52
|
-
color: #94a3b8;
|
|
53
|
-
text-transform: uppercase;
|
|
54
|
-
letter-spacing: 0.05em;
|
|
55
|
-
}
|
|
56
|
-
#__snackkit_debugger__ select {
|
|
57
|
-
background: #0f172a;
|
|
58
|
-
border: 1px solid #334155;
|
|
59
|
-
border-radius: 4px;
|
|
60
|
-
color: #e2e8f0;
|
|
61
|
-
padding: 4px 8px;
|
|
62
|
-
font-size: 13px;
|
|
63
|
-
width: 100%;
|
|
64
|
-
cursor: pointer;
|
|
65
|
-
}
|
|
66
|
-
#__snackkit_debugger__ .dbg-ctx-list {
|
|
67
|
-
max-height: 160px;
|
|
68
|
-
overflow-y: auto;
|
|
69
|
-
border: 1px solid #334155;
|
|
70
|
-
border-radius: 4px;
|
|
71
|
-
background: #0f172a;
|
|
72
|
-
}
|
|
73
|
-
#__snackkit_debugger__ .dbg-ctx-item {
|
|
74
|
-
padding: 4px 8px;
|
|
75
|
-
border-bottom: 1px solid #1e293b;
|
|
76
|
-
cursor: pointer;
|
|
77
|
-
display: flex;
|
|
78
|
-
justify-content: space-between;
|
|
79
|
-
align-items: center;
|
|
80
|
-
}
|
|
81
|
-
#__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; }
|
|
82
|
-
#__snackkit_debugger__ .dbg-ctx-item:hover { background: #1e293b; }
|
|
83
|
-
#__snackkit_debugger__ .dbg-ctx-key { color: #60a5fa; }
|
|
84
|
-
#__snackkit_debugger__ .dbg-ctx-val { color: #94a3b8; font-size: 11px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
85
|
-
#__snackkit_debugger__ .dbg-status {
|
|
86
|
-
font-size: 11px;
|
|
87
|
-
color: #64748b;
|
|
88
|
-
margin-top: 4px;
|
|
89
|
-
min-height: 16px;
|
|
90
|
-
}
|
|
91
|
-
#__snackkit_debugger__ .dbg-status.ok { color: #22c55e; }
|
|
92
|
-
#__snackkit_debugger__ .dbg-status.err { color: #ef4444; }
|
|
93
|
-
`;
|
|
94
|
-
var Debugger = class _Debugger {
|
|
95
|
-
constructor(options) {
|
|
96
|
-
this.servers = [];
|
|
97
|
-
this.options = options;
|
|
98
|
-
this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways];
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* 初始化并渲染调试面板
|
|
102
|
-
*
|
|
103
|
-
* 调用后在页面右下角插入浮动按钮,点击展开调试面板。
|
|
104
|
-
* 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。
|
|
105
|
-
* 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。
|
|
106
|
-
*
|
|
107
|
-
* @param options 配置项
|
|
108
|
-
*
|
|
109
|
-
* @example 单网关
|
|
110
|
-
* ```ts
|
|
111
|
-
* import { Debugger } from '@snack-kit/lib/debugger'
|
|
112
|
-
*
|
|
113
|
-
* await Debugger.init({
|
|
114
|
-
* gateways: 'http://dev-gateway.example.com',
|
|
115
|
-
* })
|
|
116
|
-
* ```
|
|
117
|
-
*
|
|
118
|
-
* @example 多网关(开发/测试环境切换)
|
|
119
|
-
* ```ts
|
|
120
|
-
* await Debugger.init({
|
|
121
|
-
* gateways: [
|
|
122
|
-
* 'http://dev-gateway.example.com',
|
|
123
|
-
* 'http://test-gateway.example.com',
|
|
124
|
-
* ],
|
|
125
|
-
* timeout: 5000,
|
|
126
|
-
* })
|
|
127
|
-
* ```
|
|
128
|
-
*
|
|
129
|
-
* @example 仅在非生产环境加载
|
|
130
|
-
* ```ts
|
|
131
|
-
* if (import.meta.env.DEV) {
|
|
132
|
-
* const { Debugger } = await import('@snack-kit/lib/debugger')
|
|
133
|
-
* await Debugger.init({ gateways: 'http://dev-gateway.example.com' })
|
|
134
|
-
* }
|
|
135
|
-
* ```
|
|
136
|
-
*/
|
|
137
|
-
static async init(options) {
|
|
138
|
-
const instance = new _Debugger({ timeout: 1e4, ...options });
|
|
139
|
-
instance.injectStyle();
|
|
140
|
-
instance.buildDOM();
|
|
141
|
-
await instance.restoreState();
|
|
142
|
-
return instance;
|
|
143
|
-
}
|
|
144
|
-
/** 注入内联样式 */
|
|
145
|
-
injectStyle() {
|
|
146
|
-
if (document.getElementById("__snackkit_debugger_style__")) return;
|
|
147
|
-
const style = document.createElement("style");
|
|
148
|
-
style.id = "__snackkit_debugger_style__";
|
|
149
|
-
style.textContent = PANEL_STYLE;
|
|
150
|
-
document.head.appendChild(style);
|
|
151
|
-
}
|
|
152
|
-
/** 构建 DOM 结构 */
|
|
153
|
-
buildDOM() {
|
|
154
|
-
const root = document.createElement("div");
|
|
155
|
-
root.id = "__snackkit_debugger__";
|
|
156
|
-
const panel = document.createElement("div");
|
|
157
|
-
panel.className = "dbg-panel";
|
|
158
|
-
this.panelEl = panel;
|
|
159
|
-
const gwRow = this.createRow("\u7F51\u5173 (Gateway)");
|
|
160
|
-
const gwSelect = document.createElement("select");
|
|
161
|
-
this.gateways.forEach((gw) => {
|
|
162
|
-
const opt = document.createElement("option");
|
|
163
|
-
opt.value = gw;
|
|
164
|
-
opt.textContent = gw;
|
|
165
|
-
gwSelect.appendChild(opt);
|
|
166
|
-
});
|
|
167
|
-
gwSelect.addEventListener("change", () => this.onGatewayChange());
|
|
168
|
-
gwRow.appendChild(gwSelect);
|
|
169
|
-
this.gwSelect = gwSelect;
|
|
170
|
-
panel.appendChild(gwRow);
|
|
171
|
-
const typeRow = this.createRow("\u670D\u52A1\u7C7B\u578B (Type)");
|
|
172
|
-
const typeSelect = document.createElement("select");
|
|
173
|
-
typeSelect.addEventListener("change", () => this.onTypeChange());
|
|
174
|
-
typeRow.appendChild(typeSelect);
|
|
175
|
-
this.typeSelect = typeSelect;
|
|
176
|
-
panel.appendChild(typeRow);
|
|
177
|
-
const serverRow = this.createRow("\u670D\u52A1 (Server)");
|
|
178
|
-
const serverSelect = document.createElement("select");
|
|
179
|
-
serverSelect.addEventListener("change", () => this.onServerChange());
|
|
180
|
-
serverRow.appendChild(serverSelect);
|
|
181
|
-
this.serverSelect = serverSelect;
|
|
182
|
-
panel.appendChild(serverRow);
|
|
183
|
-
const ctxRow = this.createRow("\u8DEF\u7531 (Ctx) - \u70B9\u51FB\u590D\u5236");
|
|
184
|
-
const ctxList = document.createElement("div");
|
|
185
|
-
ctxList.className = "dbg-ctx-list";
|
|
186
|
-
ctxRow.appendChild(ctxList);
|
|
187
|
-
this.ctxList = ctxList;
|
|
188
|
-
panel.appendChild(ctxRow);
|
|
189
|
-
const status = document.createElement("div");
|
|
190
|
-
status.className = "dbg-status";
|
|
191
|
-
this.statusEl = status;
|
|
192
|
-
panel.appendChild(status);
|
|
193
|
-
const toggle = document.createElement("button");
|
|
194
|
-
toggle.className = "dbg-toggle";
|
|
195
|
-
toggle.textContent = "\u{1F41B}";
|
|
196
|
-
toggle.setAttribute("title", "Snack Debugger");
|
|
197
|
-
toggle.addEventListener("click", () => panel.classList.toggle("open"));
|
|
198
|
-
root.appendChild(panel);
|
|
199
|
-
root.appendChild(toggle);
|
|
200
|
-
document.body.appendChild(root);
|
|
201
|
-
}
|
|
202
|
-
createRow(label) {
|
|
203
|
-
const row = document.createElement("div");
|
|
204
|
-
row.className = "dbg-row";
|
|
205
|
-
const lbl = document.createElement("label");
|
|
206
|
-
lbl.textContent = label;
|
|
207
|
-
row.appendChild(lbl);
|
|
208
|
-
return row;
|
|
209
|
-
}
|
|
210
|
-
/** 恢复上次持久化的状态 */
|
|
211
|
-
async restoreState() {
|
|
212
|
-
const savedGw = localStorage.getItem(KEY_GATEWAY);
|
|
213
|
-
if (savedGw && this.gateways.includes(savedGw)) {
|
|
214
|
-
this.gwSelect.value = savedGw;
|
|
215
|
-
}
|
|
216
|
-
await this.loadServers(this.gwSelect.value);
|
|
217
|
-
const savedType = localStorage.getItem(KEY_TYPE);
|
|
218
|
-
if (savedType) {
|
|
219
|
-
this.typeSelect.value = savedType;
|
|
220
|
-
this.renderServerOptions(savedType);
|
|
221
|
-
}
|
|
222
|
-
const savedServer = localStorage.getItem(KEY_SERVER);
|
|
223
|
-
if (savedServer && this.serverSelect.querySelector(`option[value="${savedServer}"]`)) {
|
|
224
|
-
this.serverSelect.value = savedServer;
|
|
225
|
-
await this.applyServer(savedServer);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/** 网关切换处理 */
|
|
229
|
-
async onGatewayChange() {
|
|
230
|
-
const gw = this.gwSelect.value;
|
|
231
|
-
localStorage.setItem(KEY_GATEWAY, gw);
|
|
232
|
-
await this.loadServers(gw);
|
|
233
|
-
}
|
|
234
|
-
/** 服务类型切换处理 */
|
|
235
|
-
onTypeChange() {
|
|
236
|
-
const type = this.typeSelect.value;
|
|
237
|
-
localStorage.setItem(KEY_TYPE, type);
|
|
238
|
-
this.renderServerOptions(type);
|
|
239
|
-
}
|
|
240
|
-
/** 服务切换处理 */
|
|
241
|
-
async onServerChange() {
|
|
242
|
-
const key = this.serverSelect.value;
|
|
243
|
-
localStorage.setItem(KEY_SERVER, key);
|
|
244
|
-
await this.applyServer(key);
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* 从网关加载服务列表
|
|
248
|
-
* @param gwUrl 网关 URL
|
|
249
|
-
*/
|
|
250
|
-
async loadServers(gwUrl) {
|
|
251
|
-
this.setStatus("\u52A0\u8F7D\u4E2D...", "");
|
|
252
|
-
const result = await Get(`${gwUrl}/web-debug/host/list`, {
|
|
253
|
-
cache: true,
|
|
254
|
-
timeout: this.options.timeout
|
|
255
|
-
});
|
|
256
|
-
if (result.error) {
|
|
257
|
-
this.setStatus(`\u52A0\u8F7D\u5931\u8D25: ${result.error.message}`, "err");
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
this.servers = result.data ?? [];
|
|
261
|
-
this.renderTypeOptions();
|
|
262
|
-
this.setStatus(`\u5DF2\u52A0\u8F7D ${this.servers.length} \u4E2A\u670D\u52A1`, "ok");
|
|
263
|
-
}
|
|
264
|
-
/** 渲染服务类型选项 */
|
|
265
|
-
renderTypeOptions() {
|
|
266
|
-
const types = [...new Set(this.servers.map((s) => s.type))];
|
|
267
|
-
this.typeSelect.innerHTML = "";
|
|
268
|
-
const allOpt = document.createElement("option");
|
|
269
|
-
allOpt.value = "";
|
|
270
|
-
allOpt.textContent = "\u5168\u90E8";
|
|
271
|
-
this.typeSelect.appendChild(allOpt);
|
|
272
|
-
types.forEach((t) => {
|
|
273
|
-
const opt = document.createElement("option");
|
|
274
|
-
opt.value = t;
|
|
275
|
-
opt.textContent = t;
|
|
276
|
-
this.typeSelect.appendChild(opt);
|
|
277
|
-
});
|
|
278
|
-
this.renderServerOptions(this.typeSelect.value);
|
|
279
|
-
}
|
|
280
|
-
/** 根据类型渲染服务选项 */
|
|
281
|
-
renderServerOptions(type) {
|
|
282
|
-
const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers;
|
|
283
|
-
this.serverSelect.innerHTML = "";
|
|
284
|
-
const emptyOpt = document.createElement("option");
|
|
285
|
-
emptyOpt.value = "";
|
|
286
|
-
emptyOpt.textContent = "\u8BF7\u9009\u62E9\u670D\u52A1";
|
|
287
|
-
this.serverSelect.appendChild(emptyOpt);
|
|
288
|
-
filtered.forEach((s) => {
|
|
289
|
-
const opt = document.createElement("option");
|
|
290
|
-
opt.value = s.key;
|
|
291
|
-
opt.textContent = `${s.name} (${s.key})`;
|
|
292
|
-
this.serverSelect.appendChild(opt);
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* 切换目标服务,重新加载 Context 路由映射
|
|
297
|
-
* @param key 服务 key
|
|
298
|
-
*/
|
|
299
|
-
async applyServer(key) {
|
|
300
|
-
if (!key) {
|
|
301
|
-
this.renderCtxList({});
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
const server = this.servers.find((s) => s.key === key);
|
|
305
|
-
if (!server) return;
|
|
306
|
-
this.setStatus("\u5207\u6362\u670D\u52A1\u4E2D...", "");
|
|
307
|
-
try {
|
|
308
|
-
await Context.load(server.origin, this.options.timeout);
|
|
309
|
-
const ctxMap = {};
|
|
310
|
-
const res = await fetch(`${server.origin}/ngw/context`);
|
|
311
|
-
const raw = res.ok ? await res.json() : {};
|
|
312
|
-
for (const [k, v] of Object.entries(raw)) {
|
|
313
|
-
if (k !== "$info" && typeof v === "string") ctxMap[k] = v;
|
|
314
|
-
}
|
|
315
|
-
this.renderCtxList(ctxMap);
|
|
316
|
-
this.setStatus(`\u5DF2\u5207\u6362\u81F3 ${server.name}`, "ok");
|
|
317
|
-
} catch (err) {
|
|
318
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
319
|
-
this.setStatus(`\u5207\u6362\u5931\u8D25: ${msg}`, "err");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
/** 渲染路由列表 */
|
|
323
|
-
renderCtxList(ctxMap) {
|
|
324
|
-
this.ctxList.innerHTML = "";
|
|
325
|
-
const entries = Object.entries(ctxMap);
|
|
326
|
-
if (entries.length === 0) {
|
|
327
|
-
this.ctxList.textContent = "\u6682\u65E0\u8DEF\u7531";
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
entries.forEach(([k, v]) => {
|
|
331
|
-
const item = document.createElement("div");
|
|
332
|
-
item.className = "dbg-ctx-item";
|
|
333
|
-
const keySpan = document.createElement("span");
|
|
334
|
-
keySpan.className = "dbg-ctx-key";
|
|
335
|
-
keySpan.textContent = k;
|
|
336
|
-
const valSpan = document.createElement("span");
|
|
337
|
-
valSpan.className = "dbg-ctx-val";
|
|
338
|
-
valSpan.textContent = v;
|
|
339
|
-
valSpan.setAttribute("title", v);
|
|
340
|
-
item.appendChild(keySpan);
|
|
341
|
-
item.appendChild(valSpan);
|
|
342
|
-
item.addEventListener("click", () => this.copyText(k));
|
|
343
|
-
this.ctxList.appendChild(item);
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
/** 复制文本到剪贴板 */
|
|
347
|
-
copyText(text) {
|
|
348
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
349
|
-
this.setStatus(`\u5DF2\u590D\u5236: ${text}`, "ok");
|
|
350
|
-
setTimeout(() => this.setStatus("", ""), 2e3);
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
setStatus(msg, cls) {
|
|
354
|
-
this.statusEl.textContent = msg;
|
|
355
|
-
this.statusEl.className = `dbg-status${cls ? ` ${cls}` : ""}`;
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
export { Debugger };
|
|
360
|
-
//# sourceMappingURL=chunk-QBBEQHXG.js.map
|
|
361
|
-
//# sourceMappingURL=chunk-QBBEQHXG.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/debugger/debugger.ts"],"names":[],"mappings":"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","file":"chunk-QBBEQHXG.js","sourcesContent":["import { Get } from '../http/client'\nimport { Context } from '../http/context'\nimport type { ServerItem } from '../http/types'\nimport type { DebuggerOptions } from './types'\n\n/** localStorage key 前缀 */\nconst STORAGE_PREFIX = '__snackkit_debugger__'\nconst KEY_GATEWAY = `${STORAGE_PREFIX}gateway`\nconst KEY_TYPE = `${STORAGE_PREFIX}type`\nconst KEY_SERVER = `${STORAGE_PREFIX}server`\n\n/** 面板内联样式 */\nconst PANEL_STYLE = `\n #__snackkit_debugger__ {\n position: fixed;\n bottom: 16px;\n right: 16px;\n z-index: 99999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 13px;\n color: #e2e8f0;\n }\n #__snackkit_debugger__ .dbg-toggle {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: #3b82f6;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 8px rgba(0,0,0,0.3);\n margin-left: auto;\n color: #fff;\n font-size: 18px;\n }\n #__snackkit_debugger__ .dbg-panel {\n background: #1e293b;\n border: 1px solid #334155;\n border-radius: 8px;\n padding: 12px;\n width: 320px;\n margin-bottom: 8px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n display: none;\n }\n #__snackkit_debugger__ .dbg-panel.open { display: block; }\n #__snackkit_debugger__ .dbg-row {\n margin-bottom: 8px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n #__snackkit_debugger__ label {\n font-size: 11px;\n color: #94a3b8;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n #__snackkit_debugger__ select {\n background: #0f172a;\n border: 1px solid #334155;\n border-radius: 4px;\n color: #e2e8f0;\n padding: 4px 8px;\n font-size: 13px;\n width: 100%;\n cursor: pointer;\n }\n #__snackkit_debugger__ .dbg-ctx-list {\n max-height: 160px;\n overflow-y: auto;\n border: 1px solid #334155;\n border-radius: 4px;\n background: #0f172a;\n }\n #__snackkit_debugger__ .dbg-ctx-item {\n padding: 4px 8px;\n border-bottom: 1px solid #1e293b;\n cursor: pointer;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; }\n #__snackkit_debugger__ .dbg-ctx-item:hover { background: #1e293b; }\n #__snackkit_debugger__ .dbg-ctx-key { color: #60a5fa; }\n #__snackkit_debugger__ .dbg-ctx-val { color: #94a3b8; font-size: 11px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n #__snackkit_debugger__ .dbg-status {\n font-size: 11px;\n color: #64748b;\n margin-top: 4px;\n min-height: 16px;\n }\n #__snackkit_debugger__ .dbg-status.ok { color: #22c55e; }\n #__snackkit_debugger__ .dbg-status.err { color: #ef4444; }\n`\n\n/**\n * 浏览器端浮动调试面板\n *\n * 允许开发者在运行时:\n * - 选择调试网关 → 拉取服务清单\n * - 切换目标服务 → 重新加载 Context 路由映射\n * - 查看并复制可用的路由 ctx 列表\n *\n * 与 http 模块集成:使用 `Get()` 拉取服务清单,使用 `context.load()` 切换请求目标。\n *\n * @see [交互式 Demo](../demo/debugger.html)\n *\n * @example\n * ```ts\n * import { Debugger } from '@snack-kit/lib/debugger'\n *\n * await Debugger.init({ gateways: 'http://dev-gateway.example.com' })\n * ```\n */\nexport class Debugger {\n private options: Required<DebuggerOptions>\n private gateways: string[]\n private servers: ServerItem[] = []\n private panelEl!: HTMLElement\n private gwSelect!: HTMLSelectElement\n private typeSelect!: HTMLSelectElement\n private serverSelect!: HTMLSelectElement\n private ctxList!: HTMLElement\n private statusEl!: HTMLElement\n\n private constructor(options: Required<DebuggerOptions>) {\n this.options = options\n this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways]\n }\n\n /**\n * 初始化并渲染调试面板\n *\n * 调用后在页面右下角插入浮动按钮,点击展开调试面板。\n * 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。\n * 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。\n *\n * @param options 配置项\n *\n * @example 单网关\n * ```ts\n * import { Debugger } from '@snack-kit/lib/debugger'\n *\n * await Debugger.init({\n * gateways: 'http://dev-gateway.example.com',\n * })\n * ```\n *\n * @example 多网关(开发/测试环境切换)\n * ```ts\n * await Debugger.init({\n * gateways: [\n * 'http://dev-gateway.example.com',\n * 'http://test-gateway.example.com',\n * ],\n * timeout: 5000,\n * })\n * ```\n *\n * @example 仅在非生产环境加载\n * ```ts\n * if (import.meta.env.DEV) {\n * const { Debugger } = await import('@snack-kit/lib/debugger')\n * await Debugger.init({ gateways: 'http://dev-gateway.example.com' })\n * }\n * ```\n */\n static async init(options: DebuggerOptions): Promise<Debugger> {\n const instance = new Debugger({ timeout: 10000, ...options })\n instance.injectStyle()\n instance.buildDOM()\n await instance.restoreState()\n return instance\n }\n\n /** 注入内联样式 */\n private injectStyle(): void {\n if (document.getElementById('__snackkit_debugger_style__')) return\n const style = document.createElement('style')\n style.id = '__snackkit_debugger_style__'\n style.textContent = PANEL_STYLE\n document.head.appendChild(style)\n }\n\n /** 构建 DOM 结构 */\n private buildDOM(): void {\n const root = document.createElement('div')\n root.id = '__snackkit_debugger__'\n\n // 面板\n const panel = document.createElement('div')\n panel.className = 'dbg-panel'\n this.panelEl = panel\n\n // 网关选择\n const gwRow = this.createRow('网关 (Gateway)')\n const gwSelect = document.createElement('select')\n this.gateways.forEach((gw) => {\n const opt = document.createElement('option')\n opt.value = gw\n opt.textContent = gw\n gwSelect.appendChild(opt)\n })\n gwSelect.addEventListener('change', () => this.onGatewayChange())\n gwRow.appendChild(gwSelect)\n this.gwSelect = gwSelect\n panel.appendChild(gwRow)\n\n // 服务类型选择\n const typeRow = this.createRow('服务类型 (Type)')\n const typeSelect = document.createElement('select')\n typeSelect.addEventListener('change', () => this.onTypeChange())\n typeRow.appendChild(typeSelect)\n this.typeSelect = typeSelect\n panel.appendChild(typeRow)\n\n // 服务选择\n const serverRow = this.createRow('服务 (Server)')\n const serverSelect = document.createElement('select')\n serverSelect.addEventListener('change', () => this.onServerChange())\n serverRow.appendChild(serverSelect)\n this.serverSelect = serverSelect\n panel.appendChild(serverRow)\n\n // 路由列表\n const ctxRow = this.createRow('路由 (Ctx) - 点击复制')\n const ctxList = document.createElement('div')\n ctxList.className = 'dbg-ctx-list'\n ctxRow.appendChild(ctxList)\n this.ctxList = ctxList\n panel.appendChild(ctxRow)\n\n // 状态提示\n const status = document.createElement('div')\n status.className = 'dbg-status'\n this.statusEl = status\n panel.appendChild(status)\n\n // 折叠按钮\n const toggle = document.createElement('button')\n toggle.className = 'dbg-toggle'\n toggle.textContent = '🐛'\n toggle.setAttribute('title', 'Snack Debugger')\n toggle.addEventListener('click', () => panel.classList.toggle('open'))\n\n root.appendChild(panel)\n root.appendChild(toggle)\n document.body.appendChild(root)\n }\n\n private createRow(label: string): HTMLElement {\n const row = document.createElement('div')\n row.className = 'dbg-row'\n const lbl = document.createElement('label')\n lbl.textContent = label\n row.appendChild(lbl)\n return row\n }\n\n /** 恢复上次持久化的状态 */\n private async restoreState(): Promise<void> {\n const savedGw = localStorage.getItem(KEY_GATEWAY)\n if (savedGw && this.gateways.includes(savedGw)) {\n this.gwSelect.value = savedGw\n }\n await this.loadServers(this.gwSelect.value)\n\n const savedType = localStorage.getItem(KEY_TYPE)\n if (savedType) {\n this.typeSelect.value = savedType\n this.renderServerOptions(savedType)\n }\n\n const savedServer = localStorage.getItem(KEY_SERVER)\n if (savedServer && this.serverSelect.querySelector(`option[value=\"${savedServer}\"]`)) {\n this.serverSelect.value = savedServer\n await this.applyServer(savedServer)\n }\n }\n\n /** 网关切换处理 */\n private async onGatewayChange(): Promise<void> {\n const gw = this.gwSelect.value\n localStorage.setItem(KEY_GATEWAY, gw)\n await this.loadServers(gw)\n }\n\n /** 服务类型切换处理 */\n private onTypeChange(): void {\n const type = this.typeSelect.value\n localStorage.setItem(KEY_TYPE, type)\n this.renderServerOptions(type)\n }\n\n /** 服务切换处理 */\n private async onServerChange(): Promise<void> {\n const key = this.serverSelect.value\n localStorage.setItem(KEY_SERVER, key)\n await this.applyServer(key)\n }\n\n /**\n * 从网关加载服务列表\n * @param gwUrl 网关 URL\n */\n private async loadServers(gwUrl: string): Promise<void> {\n this.setStatus('加载中...', '')\n const result = await Get<ServerItem[]>(`${gwUrl}/web-debug/host/list`, {\n cache: true,\n timeout: this.options.timeout,\n })\n\n if (result.error) {\n this.setStatus(`加载失败: ${result.error.message}`, 'err')\n return\n }\n\n this.servers = result.data ?? []\n this.renderTypeOptions()\n this.setStatus(`已加载 ${this.servers.length} 个服务`, 'ok')\n }\n\n /** 渲染服务类型选项 */\n private renderTypeOptions(): void {\n const types = [...new Set(this.servers.map((s) => s.type))]\n this.typeSelect.innerHTML = ''\n const allOpt = document.createElement('option')\n allOpt.value = ''\n allOpt.textContent = '全部'\n this.typeSelect.appendChild(allOpt)\n types.forEach((t) => {\n const opt = document.createElement('option')\n opt.value = t\n opt.textContent = t\n this.typeSelect.appendChild(opt)\n })\n this.renderServerOptions(this.typeSelect.value)\n }\n\n /** 根据类型渲染服务选项 */\n private renderServerOptions(type: string): void {\n const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers\n this.serverSelect.innerHTML = ''\n const emptyOpt = document.createElement('option')\n emptyOpt.value = ''\n emptyOpt.textContent = '请选择服务'\n this.serverSelect.appendChild(emptyOpt)\n filtered.forEach((s) => {\n const opt = document.createElement('option')\n opt.value = s.key\n opt.textContent = `${s.name} (${s.key})`\n this.serverSelect.appendChild(opt)\n })\n }\n\n /**\n * 切换目标服务,重新加载 Context 路由映射\n * @param key 服务 key\n */\n private async applyServer(key: string): Promise<void> {\n if (!key) {\n this.renderCtxList({})\n return\n }\n const server = this.servers.find((s) => s.key === key)\n if (!server) return\n\n this.setStatus('切换服务中...', '')\n try {\n await Context.load(server.origin, this.options.timeout)\n // 路由映射已由 Context.load 解析,通过内部 store 驱动路由\n // 此处仅展示 Context 加载后可用的 ctx 条目(来自 /ngw/context 响应)\n const ctxMap: Record<string, string> = {}\n // Context 内部 store 不对外暴露迭代,通过重新请求获取展示数据\n const res = await fetch(`${server.origin}/ngw/context`)\n const raw: Record<string, unknown> = res.ok ? await res.json() : {}\n for (const [k, v] of Object.entries(raw)) {\n if (k !== '$info' && typeof v === 'string') ctxMap[k] = v\n }\n this.renderCtxList(ctxMap)\n this.setStatus(`已切换至 ${server.name}`, 'ok')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n this.setStatus(`切换失败: ${msg}`, 'err')\n }\n }\n\n /** 渲染路由列表 */\n private renderCtxList(ctxMap: Record<string, string>): void {\n this.ctxList.innerHTML = ''\n const entries = Object.entries(ctxMap)\n if (entries.length === 0) {\n this.ctxList.textContent = '暂无路由'\n return\n }\n entries.forEach(([k, v]) => {\n const item = document.createElement('div')\n item.className = 'dbg-ctx-item'\n const keySpan = document.createElement('span')\n keySpan.className = 'dbg-ctx-key'\n keySpan.textContent = k\n const valSpan = document.createElement('span')\n valSpan.className = 'dbg-ctx-val'\n valSpan.textContent = v\n valSpan.setAttribute('title', v)\n item.appendChild(keySpan)\n item.appendChild(valSpan)\n item.addEventListener('click', () => this.copyText(k))\n this.ctxList.appendChild(item)\n })\n }\n\n /** 复制文本到剪贴板 */\n private copyText(text: string): void {\n navigator.clipboard.writeText(text).then(() => {\n this.setStatus(`已复制: ${text}`, 'ok')\n setTimeout(() => this.setStatus('', ''), 2000)\n })\n }\n\n private setStatus(msg: string, cls: '' | 'ok' | 'err'): void {\n this.statusEl.textContent = msg\n this.statusEl.className = `dbg-status${cls ? ` ${cls}` : ''}`\n }\n}\n"]}
|