@snack-kit/lib 0.1.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/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @snack-kit/lib
2
+
3
+ Enterprise-grade utility library.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @snack-kit/lib
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { VERSION } from '@snack-kit/lib'
15
+ ```
16
+
17
+ ## Scripts
18
+
19
+ | Command | Description |
20
+ |---|---|
21
+ | `npm run build` | Build for production (ESM + CJS + types) |
22
+ | `npm run build:watch` | Build in watch mode |
23
+ | `npm run test` | Run tests |
24
+ | `npm run test:coverage` | Run tests with coverage |
25
+ | `npm run docs` | Generate API docs |
26
+ | `npm run lint` | Type-check source |
27
+ | `npm run release` | Build and publish to npm |
28
+
29
+ ## Documentation
30
+
31
+ - [Contributing](docs/CONTRIBUTING.md)
32
+ - [API Reference](docs/api/index.html) *(generated)*
33
+
34
+ ## License
35
+
36
+ MIT
@@ -0,0 +1,363 @@
1
+ 'use strict';
2
+
3
+ var chunkN7BJS6LI_cjs = require('./chunk-N7BJS6LI.cjs');
4
+
5
+ // src/debugger/debugger.ts
6
+ var STORAGE_PREFIX = "__snackkit_debugger__";
7
+ var KEY_GATEWAY = `${STORAGE_PREFIX}gateway`;
8
+ var KEY_TYPE = `${STORAGE_PREFIX}type`;
9
+ var KEY_SERVER = `${STORAGE_PREFIX}server`;
10
+ var PANEL_STYLE = `
11
+ #__snackkit_debugger__ {
12
+ position: fixed;
13
+ bottom: 16px;
14
+ right: 16px;
15
+ z-index: 99999;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
17
+ font-size: 13px;
18
+ color: #e2e8f0;
19
+ }
20
+ #__snackkit_debugger__ .dbg-toggle {
21
+ width: 40px;
22
+ height: 40px;
23
+ border-radius: 50%;
24
+ background: #3b82f6;
25
+ border: none;
26
+ cursor: pointer;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
31
+ margin-left: auto;
32
+ color: #fff;
33
+ font-size: 18px;
34
+ }
35
+ #__snackkit_debugger__ .dbg-panel {
36
+ background: #1e293b;
37
+ border: 1px solid #334155;
38
+ border-radius: 8px;
39
+ padding: 12px;
40
+ width: 320px;
41
+ margin-bottom: 8px;
42
+ box-shadow: 0 4px 16px rgba(0,0,0,0.4);
43
+ display: none;
44
+ }
45
+ #__snackkit_debugger__ .dbg-panel.open { display: block; }
46
+ #__snackkit_debugger__ .dbg-row {
47
+ margin-bottom: 8px;
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: 4px;
51
+ }
52
+ #__snackkit_debugger__ label {
53
+ font-size: 11px;
54
+ color: #94a3b8;
55
+ text-transform: uppercase;
56
+ letter-spacing: 0.05em;
57
+ }
58
+ #__snackkit_debugger__ select {
59
+ background: #0f172a;
60
+ border: 1px solid #334155;
61
+ border-radius: 4px;
62
+ color: #e2e8f0;
63
+ padding: 4px 8px;
64
+ font-size: 13px;
65
+ width: 100%;
66
+ cursor: pointer;
67
+ }
68
+ #__snackkit_debugger__ .dbg-ctx-list {
69
+ max-height: 160px;
70
+ overflow-y: auto;
71
+ border: 1px solid #334155;
72
+ border-radius: 4px;
73
+ background: #0f172a;
74
+ }
75
+ #__snackkit_debugger__ .dbg-ctx-item {
76
+ padding: 4px 8px;
77
+ border-bottom: 1px solid #1e293b;
78
+ cursor: pointer;
79
+ display: flex;
80
+ justify-content: space-between;
81
+ align-items: center;
82
+ }
83
+ #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; }
84
+ #__snackkit_debugger__ .dbg-ctx-item:hover { background: #1e293b; }
85
+ #__snackkit_debugger__ .dbg-ctx-key { color: #60a5fa; }
86
+ #__snackkit_debugger__ .dbg-ctx-val { color: #94a3b8; font-size: 11px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
87
+ #__snackkit_debugger__ .dbg-status {
88
+ font-size: 11px;
89
+ color: #64748b;
90
+ margin-top: 4px;
91
+ min-height: 16px;
92
+ }
93
+ #__snackkit_debugger__ .dbg-status.ok { color: #22c55e; }
94
+ #__snackkit_debugger__ .dbg-status.err { color: #ef4444; }
95
+ `;
96
+ var Debugger = class _Debugger {
97
+ constructor(options) {
98
+ this.servers = [];
99
+ this.options = options;
100
+ this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways];
101
+ }
102
+ /**
103
+ * 初始化并渲染调试面板
104
+ *
105
+ * 调用后在页面右下角插入浮动按钮,点击展开调试面板。
106
+ * 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。
107
+ * 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。
108
+ *
109
+ * @param options 配置项
110
+ *
111
+ * @example 单网关
112
+ * ```ts
113
+ * import { Debugger } from '@snack-kit/lib/debugger'
114
+ *
115
+ * await Debugger.init({
116
+ * gateways: 'http://dev-gateway.example.com',
117
+ * })
118
+ * ```
119
+ *
120
+ * @example 多网关(开发/测试环境切换)
121
+ * ```ts
122
+ * await Debugger.init({
123
+ * gateways: [
124
+ * 'http://dev-gateway.example.com',
125
+ * 'http://test-gateway.example.com',
126
+ * ],
127
+ * timeout: 5000,
128
+ * })
129
+ * ```
130
+ *
131
+ * @example 仅在非生产环境加载
132
+ * ```ts
133
+ * if (import.meta.env.DEV) {
134
+ * const { Debugger } = await import('@snack-kit/lib/debugger')
135
+ * await Debugger.init({ gateways: 'http://dev-gateway.example.com' })
136
+ * }
137
+ * ```
138
+ */
139
+ static async init(options) {
140
+ const instance = new _Debugger({ timeout: 1e4, ...options });
141
+ instance.injectStyle();
142
+ instance.buildDOM();
143
+ await instance.restoreState();
144
+ return instance;
145
+ }
146
+ /** 注入内联样式 */
147
+ injectStyle() {
148
+ if (document.getElementById("__snackkit_debugger_style__")) return;
149
+ const style = document.createElement("style");
150
+ style.id = "__snackkit_debugger_style__";
151
+ style.textContent = PANEL_STYLE;
152
+ document.head.appendChild(style);
153
+ }
154
+ /** 构建 DOM 结构 */
155
+ buildDOM() {
156
+ const root = document.createElement("div");
157
+ root.id = "__snackkit_debugger__";
158
+ const panel = document.createElement("div");
159
+ panel.className = "dbg-panel";
160
+ this.panelEl = panel;
161
+ const gwRow = this.createRow("\u7F51\u5173 (Gateway)");
162
+ const gwSelect = document.createElement("select");
163
+ this.gateways.forEach((gw) => {
164
+ const opt = document.createElement("option");
165
+ opt.value = gw;
166
+ opt.textContent = gw;
167
+ gwSelect.appendChild(opt);
168
+ });
169
+ gwSelect.addEventListener("change", () => this.onGatewayChange());
170
+ gwRow.appendChild(gwSelect);
171
+ this.gwSelect = gwSelect;
172
+ panel.appendChild(gwRow);
173
+ const typeRow = this.createRow("\u670D\u52A1\u7C7B\u578B (Type)");
174
+ const typeSelect = document.createElement("select");
175
+ typeSelect.addEventListener("change", () => this.onTypeChange());
176
+ typeRow.appendChild(typeSelect);
177
+ this.typeSelect = typeSelect;
178
+ panel.appendChild(typeRow);
179
+ const serverRow = this.createRow("\u670D\u52A1 (Server)");
180
+ const serverSelect = document.createElement("select");
181
+ serverSelect.addEventListener("change", () => this.onServerChange());
182
+ serverRow.appendChild(serverSelect);
183
+ this.serverSelect = serverSelect;
184
+ panel.appendChild(serverRow);
185
+ const ctxRow = this.createRow("\u8DEF\u7531 (Ctx) - \u70B9\u51FB\u590D\u5236");
186
+ const ctxList = document.createElement("div");
187
+ ctxList.className = "dbg-ctx-list";
188
+ ctxRow.appendChild(ctxList);
189
+ this.ctxList = ctxList;
190
+ panel.appendChild(ctxRow);
191
+ const status = document.createElement("div");
192
+ status.className = "dbg-status";
193
+ this.statusEl = status;
194
+ panel.appendChild(status);
195
+ const toggle = document.createElement("button");
196
+ toggle.className = "dbg-toggle";
197
+ toggle.textContent = "\u{1F41B}";
198
+ toggle.setAttribute("title", "Snack Debugger");
199
+ toggle.addEventListener("click", () => panel.classList.toggle("open"));
200
+ root.appendChild(panel);
201
+ root.appendChild(toggle);
202
+ document.body.appendChild(root);
203
+ }
204
+ createRow(label) {
205
+ const row = document.createElement("div");
206
+ row.className = "dbg-row";
207
+ const lbl = document.createElement("label");
208
+ lbl.textContent = label;
209
+ row.appendChild(lbl);
210
+ return row;
211
+ }
212
+ /** 恢复上次持久化的状态 */
213
+ async restoreState() {
214
+ const savedGw = localStorage.getItem(KEY_GATEWAY);
215
+ if (savedGw && this.gateways.includes(savedGw)) {
216
+ this.gwSelect.value = savedGw;
217
+ }
218
+ await this.loadServers(this.gwSelect.value);
219
+ const savedType = localStorage.getItem(KEY_TYPE);
220
+ if (savedType) {
221
+ this.typeSelect.value = savedType;
222
+ this.renderServerOptions(savedType);
223
+ }
224
+ const savedServer = localStorage.getItem(KEY_SERVER);
225
+ if (savedServer && this.serverSelect.querySelector(`option[value="${savedServer}"]`)) {
226
+ this.serverSelect.value = savedServer;
227
+ await this.applyServer(savedServer);
228
+ }
229
+ }
230
+ /** 网关切换处理 */
231
+ async onGatewayChange() {
232
+ const gw = this.gwSelect.value;
233
+ localStorage.setItem(KEY_GATEWAY, gw);
234
+ await this.loadServers(gw);
235
+ }
236
+ /** 服务类型切换处理 */
237
+ onTypeChange() {
238
+ const type = this.typeSelect.value;
239
+ localStorage.setItem(KEY_TYPE, type);
240
+ this.renderServerOptions(type);
241
+ }
242
+ /** 服务切换处理 */
243
+ async onServerChange() {
244
+ const key = this.serverSelect.value;
245
+ localStorage.setItem(KEY_SERVER, key);
246
+ await this.applyServer(key);
247
+ }
248
+ /**
249
+ * 从网关加载服务列表
250
+ * @param gwUrl 网关 URL
251
+ */
252
+ async loadServers(gwUrl) {
253
+ this.setStatus("\u52A0\u8F7D\u4E2D...", "");
254
+ const result = await chunkN7BJS6LI_cjs.Get(`${gwUrl}/web-debug/host/list`, {
255
+ cache: true,
256
+ timeout: this.options.timeout
257
+ });
258
+ if (result.error) {
259
+ this.setStatus(`\u52A0\u8F7D\u5931\u8D25: ${result.error.message}`, "err");
260
+ return;
261
+ }
262
+ this.servers = result.data ?? [];
263
+ this.renderTypeOptions();
264
+ this.setStatus(`\u5DF2\u52A0\u8F7D ${this.servers.length} \u4E2A\u670D\u52A1`, "ok");
265
+ }
266
+ /** 渲染服务类型选项 */
267
+ renderTypeOptions() {
268
+ const types = [...new Set(this.servers.map((s) => s.type))];
269
+ this.typeSelect.innerHTML = "";
270
+ const allOpt = document.createElement("option");
271
+ allOpt.value = "";
272
+ allOpt.textContent = "\u5168\u90E8";
273
+ this.typeSelect.appendChild(allOpt);
274
+ types.forEach((t) => {
275
+ const opt = document.createElement("option");
276
+ opt.value = t;
277
+ opt.textContent = t;
278
+ this.typeSelect.appendChild(opt);
279
+ });
280
+ this.renderServerOptions(this.typeSelect.value);
281
+ }
282
+ /** 根据类型渲染服务选项 */
283
+ renderServerOptions(type) {
284
+ const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers;
285
+ this.serverSelect.innerHTML = "";
286
+ const emptyOpt = document.createElement("option");
287
+ emptyOpt.value = "";
288
+ emptyOpt.textContent = "\u8BF7\u9009\u62E9\u670D\u52A1";
289
+ this.serverSelect.appendChild(emptyOpt);
290
+ filtered.forEach((s) => {
291
+ const opt = document.createElement("option");
292
+ opt.value = s.key;
293
+ opt.textContent = `${s.name} (${s.key})`;
294
+ this.serverSelect.appendChild(opt);
295
+ });
296
+ }
297
+ /**
298
+ * 切换目标服务,重新加载 Context 路由映射
299
+ * @param key 服务 key
300
+ */
301
+ async applyServer(key) {
302
+ if (!key) {
303
+ this.renderCtxList({});
304
+ return;
305
+ }
306
+ const server = this.servers.find((s) => s.key === key);
307
+ if (!server) return;
308
+ this.setStatus("\u5207\u6362\u670D\u52A1\u4E2D...", "");
309
+ try {
310
+ await chunkN7BJS6LI_cjs.Context.load(server.origin, this.options.timeout);
311
+ const ctxMap = {};
312
+ const res = await fetch(`${server.origin}/ngw/context`);
313
+ const raw = res.ok ? await res.json() : {};
314
+ for (const [k, v] of Object.entries(raw)) {
315
+ if (k !== "$info" && typeof v === "string") ctxMap[k] = v;
316
+ }
317
+ this.renderCtxList(ctxMap);
318
+ this.setStatus(`\u5DF2\u5207\u6362\u81F3 ${server.name}`, "ok");
319
+ } catch (err) {
320
+ const msg = err instanceof Error ? err.message : String(err);
321
+ this.setStatus(`\u5207\u6362\u5931\u8D25: ${msg}`, "err");
322
+ }
323
+ }
324
+ /** 渲染路由列表 */
325
+ renderCtxList(ctxMap) {
326
+ this.ctxList.innerHTML = "";
327
+ const entries = Object.entries(ctxMap);
328
+ if (entries.length === 0) {
329
+ this.ctxList.textContent = "\u6682\u65E0\u8DEF\u7531";
330
+ return;
331
+ }
332
+ entries.forEach(([k, v]) => {
333
+ const item = document.createElement("div");
334
+ item.className = "dbg-ctx-item";
335
+ const keySpan = document.createElement("span");
336
+ keySpan.className = "dbg-ctx-key";
337
+ keySpan.textContent = k;
338
+ const valSpan = document.createElement("span");
339
+ valSpan.className = "dbg-ctx-val";
340
+ valSpan.textContent = v;
341
+ valSpan.setAttribute("title", v);
342
+ item.appendChild(keySpan);
343
+ item.appendChild(valSpan);
344
+ item.addEventListener("click", () => this.copyText(k));
345
+ this.ctxList.appendChild(item);
346
+ });
347
+ }
348
+ /** 复制文本到剪贴板 */
349
+ copyText(text) {
350
+ navigator.clipboard.writeText(text).then(() => {
351
+ this.setStatus(`\u5DF2\u590D\u5236: ${text}`, "ok");
352
+ setTimeout(() => this.setStatus("", ""), 2e3);
353
+ });
354
+ }
355
+ setStatus(msg, cls) {
356
+ this.statusEl.textContent = msg;
357
+ this.statusEl.className = `dbg-status${cls ? ` ${cls}` : ""}`;
358
+ }
359
+ };
360
+
361
+ exports.Debugger = Debugger;
362
+ //# sourceMappingURL=chunk-JBJEXFQC.cjs.map
363
+ //# sourceMappingURL=chunk-JBJEXFQC.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/debugger/debugger.ts"],"names":["Get","Context"],"mappings":"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","file":"chunk-JBJEXFQC.cjs","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"]}
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ var axios = require('axios');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
8
+
9
+ // src/http/context.ts
10
+ var HttpContext = class {
11
+ constructor() {
12
+ this._store = /* @__PURE__ */ new Map();
13
+ this._loaded = false;
14
+ }
15
+ /**
16
+ * 加载服务地址映射
17
+ *
18
+ * 远程加载时,响应中的 `$info` 元数据字段会被单独存储,不计入路由映射表。
19
+ *
20
+ * @param source 远程加载传入网关 URL;本地注入传入键值对象
21
+ * @param timeout 远程加载超时(ms),默认 5000
22
+ *
23
+ * @example 远程加载
24
+ * ```ts
25
+ * await Context.load('http://172.16.32.155:20000', 3000)
26
+ * ```
27
+ *
28
+ * @example 本地注入
29
+ * ```ts
30
+ * await Context.load({ 'user-svc': 'http://user.api.com' })
31
+ * ```
32
+ */
33
+ async load(source, timeout = 5e3) {
34
+ if (typeof source === "string") {
35
+ const url = `${source}/ngw/context`;
36
+ const res = await axios__default.default.get(url, { timeout });
37
+ const data = res.data;
38
+ if (data && typeof data === "object") {
39
+ this._store.clear();
40
+ for (const [k, v] of Object.entries(data)) {
41
+ if (k === "$info") {
42
+ this._info = v;
43
+ } else if (typeof v === "string") {
44
+ this._store.set(k, v);
45
+ }
46
+ }
47
+ }
48
+ } else {
49
+ this._store.clear();
50
+ for (const [k, v] of Object.entries(source)) {
51
+ this._store.set(k, v);
52
+ }
53
+ }
54
+ this._loaded = true;
55
+ }
56
+ /**
57
+ * 获取指定 ctx key 对应的服务地址
58
+ * @param key ctx key,例如 `'ngw'`、`'osc'`
59
+ * @returns 服务地址,未找到时返回空字符串
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * await Context.load('http://172.16.32.155:20000')
64
+ * Context.get('osc') // 'http://172.16.32.155:20000/osc'
65
+ * Context.get('none') // ''
66
+ * ```
67
+ */
68
+ get(key) {
69
+ return this._store.get(key) ?? "";
70
+ }
71
+ /**
72
+ * 网关元数据(仅远程加载后可用)
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * await Context.load('http://172.16.32.155:20000')
77
+ * Context.info?.version // '4.6.2-r10'
78
+ * Context.info?.domain // 'http://172.16.32.155:20000'
79
+ * ```
80
+ */
81
+ get info() {
82
+ return this._info;
83
+ }
84
+ /**
85
+ * Context 是否已加载
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * Context.loaded // false
90
+ * await Context.load({ 'user-svc': 'http://user.api.com' })
91
+ * Context.loaded // true
92
+ * ```
93
+ */
94
+ get loaded() {
95
+ return this._loaded;
96
+ }
97
+ /**
98
+ * 清空所有映射并重置加载状态
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * Context.clear()
103
+ * Context.loaded // false
104
+ * ```
105
+ */
106
+ clear() {
107
+ this._store.clear();
108
+ this._loaded = false;
109
+ this._info = void 0;
110
+ }
111
+ };
112
+ var _instance = new HttpContext();
113
+ var Context = _instance;
114
+ var _loc = typeof globalThis !== "undefined" ? globalThis["location"] : void 0;
115
+ var Origin = _loc?.protocol && _loc?.host ? `${_loc.protocol}//${_loc.host}` : "";
116
+ var Ctx = _loc?.pathname ? _loc.pathname.split("/")[1] ?? "" : "";
117
+
118
+ // src/http/cancel.ts
119
+ var registry = /* @__PURE__ */ new Map();
120
+ function Register(id) {
121
+ if (registry.has(id)) {
122
+ registry.get(id).abort();
123
+ }
124
+ const controller = new AbortController();
125
+ registry.set(id, controller);
126
+ return controller;
127
+ }
128
+ function Cancel(id) {
129
+ const controller = registry.get(id);
130
+ if (!controller) return false;
131
+ controller.abort();
132
+ registry.delete(id);
133
+ return true;
134
+ }
135
+ function CancelAll() {
136
+ for (const controller of registry.values()) {
137
+ controller.abort();
138
+ }
139
+ registry.clear();
140
+ }
141
+ function Unregister(id) {
142
+ registry.delete(id);
143
+ }
144
+ function generateId() {
145
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
146
+ return crypto.randomUUID();
147
+ }
148
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
149
+ }
150
+ async function Request(config) {
151
+ const { ctx, cancelId: customCancelId, onCancelId, cache = false, ...axiosConfig } = config;
152
+ if (ctx && Context.loaded) {
153
+ const base = Context.get(ctx);
154
+ if (base) {
155
+ axiosConfig.baseURL = base;
156
+ }
157
+ }
158
+ if (!cache) {
159
+ const sep = (axiosConfig.url ?? "").includes("?") ? "&" : "?";
160
+ axiosConfig.url = `${axiosConfig.url ?? ""}${sep}_=${Date.now()}`;
161
+ }
162
+ const cancelId = customCancelId ?? generateId();
163
+ const controller = Register(cancelId);
164
+ axiosConfig.signal = controller.signal;
165
+ onCancelId?.(cancelId);
166
+ try {
167
+ const response = await axios__default.default.request({ ...axiosConfig });
168
+ return response;
169
+ } catch (err) {
170
+ if (axios.isAxiosError(err)) {
171
+ return {
172
+ error: {
173
+ status: err.response?.status,
174
+ message: err.message,
175
+ data: err.response?.data
176
+ }
177
+ };
178
+ }
179
+ const message = err instanceof Error ? err.message : String(err);
180
+ return { error: { message } };
181
+ } finally {
182
+ Unregister(cancelId);
183
+ }
184
+ }
185
+ function Get(url, config) {
186
+ return Request({ ...config, method: "GET", url });
187
+ }
188
+ function Post(url, data, config) {
189
+ return Request({ ...config, method: "POST", url, data });
190
+ }
191
+ function Put(url, data, config) {
192
+ return Request({ ...config, method: "PUT", url, data });
193
+ }
194
+ function Patch(url, data, config) {
195
+ return Request({ ...config, method: "PATCH", url, data });
196
+ }
197
+ function Del(url, config) {
198
+ return Request({ ...config, method: "DELETE", url });
199
+ }
200
+
201
+ exports.Cancel = Cancel;
202
+ exports.CancelAll = CancelAll;
203
+ exports.Context = Context;
204
+ exports.Ctx = Ctx;
205
+ exports.Del = Del;
206
+ exports.Get = Get;
207
+ exports.HttpContext = HttpContext;
208
+ exports.Origin = Origin;
209
+ exports.Patch = Patch;
210
+ exports.Post = Post;
211
+ exports.Put = Put;
212
+ exports.Request = Request;
213
+ //# sourceMappingURL=chunk-N7BJS6LI.cjs.map
214
+ //# sourceMappingURL=chunk-N7BJS6LI.cjs.map