@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.
@@ -0,0 +1,868 @@
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
+ function isChinese() {
11
+ return /^zh/i.test(navigator.language ?? "");
12
+ }
13
+ var T = {
14
+ gateway: () => isChinese() ? "\u7F51\u5173" : "Gateway",
15
+ tag: () => isChinese() ? "\u6807\u7B7E" : "Tag",
16
+ server: () => isChinese() ? "\u670D\u52A1" : "Server",
17
+ ctx: () => isChinese() ? "\u4E0A\u4E0B\u6587" : "Ctx",
18
+ ctxLabel: () => isChinese() ? "\u4E0A\u4E0B\u6587 \u2014 \u70B9\u51FB\u884C\u590D\u5236 key" : "Ctx \u2014 click row to copy key",
19
+ search: () => isChinese() ? "\u641C\u7D22..." : "Search...",
20
+ noMatch: () => isChinese() ? "\u65E0\u5339\u914D\u9879" : "No results",
21
+ allTags: () => isChinese() ? "\u5168\u90E8\u6807\u7B7E" : "All tags",
22
+ selectGateway: () => isChinese() ? "\u9009\u62E9\u7F51\u5173" : "Select gateway",
23
+ selectServer: () => isChinese() ? "\u9009\u62E9\u670D\u52A1" : "Select server",
24
+ filterCtx: () => isChinese() ? "\u8FC7\u6EE4\u8DEF\u7531..." : "Filter routes...",
25
+ selectFirst: () => isChinese() ? "\u2190 \u8BF7\u5148\u9009\u62E9\u670D\u52A1" : "\u2190 Select a server first",
26
+ loading: () => isChinese() ? "\u52A0\u8F7D\u670D\u52A1\u5217\u8868..." : "Loading...",
27
+ loadFailed: (m) => isChinese() ? `\u52A0\u8F7D\u5931\u8D25: ${m}` : `Failed: ${m}`,
28
+ loaded: (n) => isChinese() ? `\u5DF2\u52A0\u8F7D ${n} \u4E2A\u670D\u52A1` : `${n} servers loaded`,
29
+ switching: () => isChinese() ? "\u5207\u6362\u670D\u52A1\u4E2D..." : "Switching...",
30
+ switchFailed: (m) => isChinese() ? `\u5207\u6362\u5931\u8D25: ${m}` : `Failed: ${m}`,
31
+ routes: (name, n) => isChinese() ? `${name} \xB7 ${n} \u6761\u8DEF\u7531` : `${name} \xB7 ${n} routes`,
32
+ copied: (k) => isChinese() ? `\u5DF2\u590D\u5236: ${k}` : `Copied: ${k}`,
33
+ copyAll: () => isChinese() ? "\u590D\u5236\u5168\u90E8 JSON" : "Copy all JSON",
34
+ copyAllDone: () => isChinese() ? "\u5DF2\u590D\u5236\u4E3A JSON" : "Copied as JSON"
35
+ };
36
+ var PANEL_STYLE = `
37
+ #__snackkit_debugger__ {
38
+ position: fixed;
39
+ bottom: 20px;
40
+ right: 20px;
41
+ z-index: 99999;
42
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
43
+ font-size: 13px;
44
+ color: #b8ccec;
45
+ user-select: none;
46
+ }
47
+
48
+ /* \u2500\u2500 \u6D6E\u52A8\u6309\u94AE \u2500\u2500 */
49
+ #__snackkit_debugger__ .dbg-toggle {
50
+ width: 42px;
51
+ height: 42px;
52
+ border-radius: 50%;
53
+ background: linear-gradient(135deg, #4f8ef7, #6d6ff5);
54
+ border: none;
55
+ cursor: pointer;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ box-shadow: 0 4px 14px rgba(79,142,247,0.45);
60
+ margin-left: auto;
61
+ font-size: 20px;
62
+ transition: transform 0.15s, box-shadow 0.15s;
63
+ }
64
+ #__snackkit_debugger__ .dbg-toggle:hover {
65
+ transform: scale(1.08);
66
+ box-shadow: 0 6px 20px rgba(79,142,247,0.55);
67
+ }
68
+ #__snackkit_debugger__ .dbg-toggle:active { transform: scale(0.94); }
69
+
70
+ /* \u2500\u2500 \u9762\u677F\u4E3B\u4F53 \u2500\u2500 */
71
+ #__snackkit_debugger__ .dbg-panel {
72
+ background: #1e2638;
73
+ border: 1px solid #2d3a55;
74
+ border-radius: 12px;
75
+ width: 440px;
76
+ margin-bottom: 10px;
77
+ box-shadow: 0 8px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.05);
78
+ overflow: visible;
79
+ opacity: 0;
80
+ transform: translateY(8px) scale(0.98);
81
+ pointer-events: none;
82
+ transition: opacity 0.18s ease, transform 0.18s ease;
83
+ }
84
+ #__snackkit_debugger__ .dbg-panel.open {
85
+ opacity: 1;
86
+ transform: translateY(0) scale(1);
87
+ pointer-events: auto;
88
+ }
89
+
90
+ /* \u2500\u2500 \u9762\u677F Header \u2500\u2500 */
91
+ #__snackkit_debugger__ .dbg-header {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ padding: 10px 14px;
96
+ background: #171e2e;
97
+ border-bottom: 1px solid #2d3a55;
98
+ border-radius: 12px 12px 0 0;
99
+ }
100
+ #__snackkit_debugger__ .dbg-header-left {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 7px;
104
+ }
105
+ #__snackkit_debugger__ .dbg-header-dot {
106
+ width: 7px;
107
+ height: 7px;
108
+ border-radius: 50%;
109
+ background: #34d399;
110
+ box-shadow: 0 0 6px rgba(52,211,153,0.6);
111
+ }
112
+ #__snackkit_debugger__ .dbg-header-dot.loading {
113
+ background: #fbbf24;
114
+ box-shadow: 0 0 6px rgba(251,191,36,0.6);
115
+ animation: dbg-pulse 1s ease-in-out infinite;
116
+ }
117
+ #__snackkit_debugger__ .dbg-header-dot.err {
118
+ background: #f87171;
119
+ box-shadow: 0 0 6px rgba(248,113,113,0.6);
120
+ }
121
+ #__snackkit_debugger__ .dbg-header-title {
122
+ font-size: 12px;
123
+ font-weight: 600;
124
+ color: #dce8fa;
125
+ letter-spacing: 0.03em;
126
+ }
127
+ #__snackkit_debugger__ .dbg-header-meta {
128
+ font-size: 11px;
129
+ color: #4d6080;
130
+ }
131
+
132
+ /* \u2500\u2500 \u9762\u677F\u5185\u5BB9\u533A \u2500\u2500 */
133
+ #__snackkit_debugger__ .dbg-body {
134
+ padding: 12px 14px;
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: 10px;
138
+ height: 420px;
139
+ overflow: visible;
140
+ }
141
+
142
+ /* \u2500\u2500 \u5B57\u6BB5\u884C \u2500\u2500 */
143
+ #__snackkit_debugger__ .dbg-field {
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 5px;
147
+ }
148
+ #__snackkit_debugger__ .dbg-field-header {
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: space-between;
152
+ }
153
+ #__snackkit_debugger__ .dbg-label {
154
+ font-size: 10px;
155
+ font-weight: 600;
156
+ color: #5e72a0;
157
+ text-transform: uppercase;
158
+ letter-spacing: 0.07em;
159
+ }
160
+
161
+ /* \u2500\u2500 \u4E00\u952E\u590D\u5236\u5168\u90E8 JSON \u6309\u94AE \u2500\u2500 */
162
+ #__snackkit_debugger__ .dbg-copy-all {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 4px;
166
+ background: transparent;
167
+ border: 1px solid #2d3a55;
168
+ border-radius: 4px;
169
+ padding: 2px 7px;
170
+ color: #5e72a0;
171
+ font-size: 10px;
172
+ font-family: inherit;
173
+ cursor: pointer;
174
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
175
+ }
176
+ #__snackkit_debugger__ .dbg-copy-all:hover {
177
+ border-color: #4f8ef7;
178
+ color: #7aacfa;
179
+ background: rgba(79,142,247,0.08);
180
+ }
181
+ #__snackkit_debugger__ .dbg-copy-all.done {
182
+ border-color: #34d399;
183
+ color: #34d399;
184
+ }
185
+
186
+ /* \u2500\u2500 \u81EA\u5B9A\u4E49\u4E0B\u62C9 \u2500\u2500 */
187
+ #__snackkit_debugger__ .dbg-select {
188
+ position: relative;
189
+ }
190
+ #__snackkit_debugger__ .dbg-select-trigger {
191
+ display: flex;
192
+ align-items: center;
193
+ justify-content: space-between;
194
+ gap: 6px;
195
+ background: #252f45;
196
+ border: 1px solid #2d3a55;
197
+ border-radius: 6px;
198
+ padding: 6px 10px;
199
+ cursor: pointer;
200
+ transition: border-color 0.15s, background 0.15s;
201
+ min-height: 32px;
202
+ }
203
+ #__snackkit_debugger__ .dbg-select-trigger:hover { border-color: #3d5070; background: #2d3a55; }
204
+ #__snackkit_debugger__ .dbg-select.open .dbg-select-trigger {
205
+ border-color: #4f8ef7;
206
+ background: #2d3a55;
207
+ }
208
+ #__snackkit_debugger__ .dbg-select-val {
209
+ flex: 1;
210
+ overflow: hidden;
211
+ text-overflow: ellipsis;
212
+ white-space: nowrap;
213
+ font-size: 12px;
214
+ color: #b8ccec;
215
+ }
216
+ #__snackkit_debugger__ .dbg-select-val.placeholder { color: #3d5070; }
217
+ #__snackkit_debugger__ .dbg-select-arrow {
218
+ color: #3d5070;
219
+ font-size: 10px;
220
+ flex-shrink: 0;
221
+ transition: transform 0.15s, color 0.15s;
222
+ }
223
+ #__snackkit_debugger__ .dbg-select.open .dbg-select-arrow {
224
+ transform: rotate(180deg);
225
+ color: #4f8ef7;
226
+ }
227
+ #__snackkit_debugger__ .dbg-select-dropdown {
228
+ position: absolute;
229
+ bottom: calc(100% + 4px);
230
+ left: 0;
231
+ right: 0;
232
+ background: #1e2638;
233
+ border: 1px solid #2d3a55;
234
+ border-radius: 8px;
235
+ box-shadow: 0 -8px 24px rgba(0,0,0,0.35);
236
+ overflow: hidden;
237
+ opacity: 0;
238
+ transform: translateY(4px);
239
+ pointer-events: none;
240
+ transition: opacity 0.14s, transform 0.14s;
241
+ z-index: 100000;
242
+ }
243
+ #__snackkit_debugger__ .dbg-select.open .dbg-select-dropdown {
244
+ opacity: 1;
245
+ transform: translateY(0);
246
+ pointer-events: auto;
247
+ }
248
+ #__snackkit_debugger__ .dbg-select-search {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 6px;
252
+ padding: 7px 10px;
253
+ border-bottom: 1px solid #2d3a55;
254
+ }
255
+ #__snackkit_debugger__ .dbg-select-search-icon { color: #3d5070; font-size: 11px; flex-shrink: 0; }
256
+ #__snackkit_debugger__ .dbg-select-search input {
257
+ flex: 1;
258
+ background: transparent;
259
+ border: none;
260
+ outline: none;
261
+ color: #b8ccec;
262
+ font-size: 12px;
263
+ font-family: inherit;
264
+ }
265
+ #__snackkit_debugger__ .dbg-select-search input::placeholder { color: #3d5070; }
266
+ #__snackkit_debugger__ .dbg-select-list {
267
+ max-height: 160px;
268
+ overflow-y: auto;
269
+ }
270
+ #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar { width: 3px; }
271
+ #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-track { background: transparent; }
272
+ #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; }
273
+ #__snackkit_debugger__ .dbg-select-option {
274
+ padding: 7px 10px;
275
+ cursor: pointer;
276
+ font-size: 12px;
277
+ color: #8aa4c8;
278
+ transition: background 0.1s, color 0.1s;
279
+ overflow: hidden;
280
+ text-overflow: ellipsis;
281
+ white-space: nowrap;
282
+ }
283
+ #__snackkit_debugger__ .dbg-select-option:hover { background: #2d3a55; color: #dce8fa; }
284
+ #__snackkit_debugger__ .dbg-select-option.active { color: #7aacfa; background: #1e3060; }
285
+ #__snackkit_debugger__ .dbg-select-option.hidden { display: none; }
286
+ #__snackkit_debugger__ .dbg-select-empty {
287
+ padding: 12px 10px;
288
+ font-size: 12px;
289
+ color: #3d5070;
290
+ text-align: center;
291
+ }
292
+
293
+ /* \u2500\u2500 \u8DEF\u7531\u5217\u8868 \u2500\u2500 */
294
+ #__snackkit_debugger__ .dbg-ctx-field {
295
+ flex: 1;
296
+ min-height: 0;
297
+ display: flex;
298
+ flex-direction: column;
299
+ gap: 5px;
300
+ }
301
+ #__snackkit_debugger__ .dbg-ctx-wrap {
302
+ flex: 1;
303
+ min-height: 0;
304
+ border: 1px solid #2d3a55;
305
+ border-radius: 6px;
306
+ overflow: hidden;
307
+ background: #252f45;
308
+ display: flex;
309
+ flex-direction: column;
310
+ }
311
+ #__snackkit_debugger__ .dbg-ctx-search {
312
+ display: flex;
313
+ align-items: center;
314
+ gap: 6px;
315
+ padding: 6px 10px;
316
+ border-bottom: 1px solid #2d3a55;
317
+ background: #1e2638;
318
+ }
319
+ #__snackkit_debugger__ .dbg-ctx-search input {
320
+ flex: 1;
321
+ background: transparent;
322
+ border: none;
323
+ outline: none;
324
+ color: #b8ccec;
325
+ font-size: 12px;
326
+ font-family: inherit;
327
+ }
328
+ #__snackkit_debugger__ .dbg-ctx-search input::placeholder { color: #3d5070; }
329
+ #__snackkit_debugger__ .dbg-ctx-list {
330
+ flex: 1;
331
+ min-height: 0;
332
+ overflow-y: auto;
333
+ }
334
+ #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar { width: 3px; }
335
+ #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-track { background: transparent; }
336
+ #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; }
337
+ #__snackkit_debugger__ .dbg-ctx-item {
338
+ padding: 5px 10px;
339
+ cursor: pointer;
340
+ display: grid;
341
+ grid-template-columns: minmax(80px, 148px) 1fr auto;
342
+ gap: 8px;
343
+ align-items: center;
344
+ border-bottom: 1px solid #1e2638;
345
+ transition: background 0.1s;
346
+ }
347
+ #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; }
348
+ #__snackkit_debugger__ .dbg-ctx-item:hover { background: #2d3a55; }
349
+ #__snackkit_debugger__ .dbg-ctx-item.hidden { display: none; }
350
+ #__snackkit_debugger__ .dbg-ctx-key {
351
+ font-size: 12px;
352
+ color: #7aacfa;
353
+ overflow: hidden;
354
+ text-overflow: ellipsis;
355
+ white-space: nowrap;
356
+ }
357
+ #__snackkit_debugger__ .dbg-ctx-val {
358
+ font-size: 11px;
359
+ color: #4d6080;
360
+ overflow: hidden;
361
+ text-overflow: ellipsis;
362
+ white-space: nowrap;
363
+ transition: color 0.1s;
364
+ }
365
+ #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-val { color: #7a90b0; }
366
+ #__snackkit_debugger__ .dbg-ctx-copy {
367
+ font-size: 11px;
368
+ color: #3d5070;
369
+ opacity: 0;
370
+ flex-shrink: 0;
371
+ transition: opacity 0.1s, color 0.1s;
372
+ }
373
+ #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-copy { opacity: 1; color: #5e72a0; }
374
+ #__snackkit_debugger__ .dbg-ctx-item.copied .dbg-ctx-copy { color: #34d399; opacity: 1; }
375
+ #__snackkit_debugger__ .dbg-ctx-empty {
376
+ padding: 16px 10px;
377
+ font-size: 12px;
378
+ color: #3d5070;
379
+ text-align: center;
380
+ }
381
+
382
+ /* \u2500\u2500 \u5E95\u90E8\u72B6\u6001\u680F \u2500\u2500 */
383
+ #__snackkit_debugger__ .dbg-footer {
384
+ display: flex;
385
+ align-items: center;
386
+ gap: 6px;
387
+ padding: 7px 14px;
388
+ background: #171e2e;
389
+ border-top: 1px solid #2d3a55;
390
+ border-radius: 0 0 12px 12px;
391
+ font-size: 11px;
392
+ color: #4d6080;
393
+ min-height: 30px;
394
+ }
395
+ #__snackkit_debugger__ .dbg-footer.ok { color: #34d399; }
396
+ #__snackkit_debugger__ .dbg-footer.err { color: #f87171; }
397
+ #__snackkit_debugger__ .dbg-footer-dot {
398
+ width: 4px;
399
+ height: 4px;
400
+ border-radius: 50%;
401
+ background: currentColor;
402
+ flex-shrink: 0;
403
+ }
404
+
405
+ @keyframes dbg-pulse {
406
+ 0%, 100% { opacity: 1; }
407
+ 50% { opacity: 0.35; }
408
+ }
409
+ `;
410
+ var SearchSelect = class {
411
+ constructor(onChange) {
412
+ this.options = [];
413
+ this.selected = "";
414
+ this.onChange = onChange;
415
+ this.el = document.createElement("div");
416
+ this.el.className = "dbg-select";
417
+ this.trigger = document.createElement("div");
418
+ this.trigger.className = "dbg-select-trigger";
419
+ this.valEl = document.createElement("span");
420
+ this.valEl.className = "dbg-select-val placeholder";
421
+ const arrow = document.createElement("span");
422
+ arrow.className = "dbg-select-arrow";
423
+ arrow.textContent = "\u25BE";
424
+ this.trigger.appendChild(this.valEl);
425
+ this.trigger.appendChild(arrow);
426
+ this.dropdown = document.createElement("div");
427
+ this.dropdown.className = "dbg-select-dropdown";
428
+ const searchWrap = document.createElement("div");
429
+ searchWrap.className = "dbg-select-search";
430
+ const searchIcon = document.createElement("span");
431
+ searchIcon.className = "dbg-select-search-icon";
432
+ searchIcon.textContent = "\u2315";
433
+ this.searchInput = document.createElement("input");
434
+ this.searchInput.type = "text";
435
+ this.searchInput.placeholder = T.search();
436
+ searchWrap.appendChild(searchIcon);
437
+ searchWrap.appendChild(this.searchInput);
438
+ this.listEl = document.createElement("div");
439
+ this.listEl.className = "dbg-select-list";
440
+ this.dropdown.appendChild(searchWrap);
441
+ this.dropdown.appendChild(this.listEl);
442
+ this.el.appendChild(this.trigger);
443
+ this.el.appendChild(this.dropdown);
444
+ this.bindEvents();
445
+ }
446
+ bindEvents() {
447
+ this.trigger.addEventListener("click", (e) => {
448
+ e.stopPropagation();
449
+ const isOpen = this.el.classList.contains("open");
450
+ document.querySelectorAll("#__snackkit_debugger__ .dbg-select.open").forEach((el) => {
451
+ if (el !== this.el) el.classList.remove("open");
452
+ });
453
+ this.el.classList.toggle("open", !isOpen);
454
+ if (!isOpen) {
455
+ this.searchInput.value = "";
456
+ this.filterOptions("");
457
+ setTimeout(() => this.searchInput.focus(), 50);
458
+ }
459
+ });
460
+ this.searchInput.addEventListener("input", () => {
461
+ this.filterOptions(this.searchInput.value);
462
+ });
463
+ this.searchInput.addEventListener("click", (e) => e.stopPropagation());
464
+ document.addEventListener("click", () => {
465
+ this.el.classList.remove("open");
466
+ });
467
+ }
468
+ filterOptions(query) {
469
+ const q = query.toLowerCase();
470
+ let visibleCount = 0;
471
+ this.listEl.querySelectorAll(".dbg-select-option").forEach((opt) => {
472
+ const match = !q || opt.textContent.toLowerCase().includes(q);
473
+ opt.classList.toggle("hidden", !match);
474
+ if (match) visibleCount++;
475
+ });
476
+ let emptyEl = this.listEl.querySelector(".dbg-select-empty");
477
+ if (visibleCount === 0) {
478
+ if (!emptyEl) {
479
+ emptyEl = document.createElement("div");
480
+ emptyEl.className = "dbg-select-empty";
481
+ emptyEl.textContent = T.noMatch();
482
+ this.listEl.appendChild(emptyEl);
483
+ }
484
+ } else {
485
+ emptyEl?.remove();
486
+ }
487
+ }
488
+ /** 更新选项列表 */
489
+ setOptions(options, placeholder) {
490
+ this.options = options;
491
+ this.listEl.innerHTML = "";
492
+ this.valEl.textContent = placeholder;
493
+ this.valEl.className = "dbg-select-val placeholder";
494
+ this.selected = "";
495
+ options.forEach(({ value, label }) => {
496
+ const opt = document.createElement("div");
497
+ opt.className = "dbg-select-option";
498
+ opt.dataset["value"] = value;
499
+ opt.textContent = label;
500
+ opt.setAttribute("title", label);
501
+ opt.addEventListener("click", (e) => {
502
+ e.stopPropagation();
503
+ this.select(value, label);
504
+ this.el.classList.remove("open");
505
+ });
506
+ this.listEl.appendChild(opt);
507
+ });
508
+ }
509
+ /** 选中某个值(不触发 onChange) */
510
+ setValue(value) {
511
+ const opt = this.options.find((o) => o.value === value);
512
+ if (opt) this.select(opt.value, opt.label, false);
513
+ }
514
+ select(value, label, emit = true) {
515
+ this.selected = value;
516
+ this.valEl.textContent = label;
517
+ this.valEl.className = "dbg-select-val";
518
+ this.listEl.querySelectorAll(".dbg-select-option").forEach((el) => {
519
+ el.classList.toggle("active", el.dataset["value"] === value);
520
+ });
521
+ if (emit) this.onChange(value);
522
+ }
523
+ getValue() {
524
+ return this.selected;
525
+ }
526
+ getElement() {
527
+ return this.el;
528
+ }
529
+ };
530
+ var Debugger = class _Debugger {
531
+ constructor(options) {
532
+ this.servers = [];
533
+ this.currentCtxMap = {};
534
+ this.options = options;
535
+ this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways];
536
+ }
537
+ /**
538
+ * 初始化并渲染调试面板
539
+ *
540
+ * 调用后在页面右下角插入浮动按钮,点击展开调试面板。
541
+ * 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。
542
+ * 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。
543
+ *
544
+ * @param options 配置项
545
+ *
546
+ * @example 单网关
547
+ * ```ts
548
+ * import { Debugger } from '@snack-kit/lib/debugger'
549
+ *
550
+ * await Debugger.init({
551
+ * gateways: 'http://dev-gateway.example.com',
552
+ * })
553
+ * ```
554
+ *
555
+ * @example 多网关(开发/测试环境切换)
556
+ * ```ts
557
+ * await Debugger.init({
558
+ * gateways: [
559
+ * 'http://dev-gateway.example.com',
560
+ * 'http://test-gateway.example.com',
561
+ * ],
562
+ * timeout: 5000,
563
+ * })
564
+ * ```
565
+ *
566
+ * @example 仅在非生产环境加载
567
+ * ```ts
568
+ * if (import.meta.env.DEV) {
569
+ * const { Debugger } = await import('@snack-kit/lib/debugger')
570
+ * await Debugger.init({ gateways: 'http://dev-gateway.example.com' })
571
+ * }
572
+ * ```
573
+ */
574
+ static async init(options) {
575
+ const instance = new _Debugger({ timeout: 1e4, ...options });
576
+ instance.injectStyle();
577
+ instance.buildDOM();
578
+ await instance.restoreState();
579
+ return instance;
580
+ }
581
+ /** 注入内联样式 */
582
+ injectStyle() {
583
+ if (document.getElementById("__snackkit_debugger_style__")) return;
584
+ const style = document.createElement("style");
585
+ style.id = "__snackkit_debugger_style__";
586
+ style.textContent = PANEL_STYLE;
587
+ document.head.appendChild(style);
588
+ }
589
+ /** 构建 DOM 结构 */
590
+ buildDOM() {
591
+ const root = document.createElement("div");
592
+ root.id = "__snackkit_debugger__";
593
+ const panel = document.createElement("div");
594
+ panel.className = "dbg-panel";
595
+ this.panelEl = panel;
596
+ const header = document.createElement("div");
597
+ header.className = "dbg-header";
598
+ const headerLeft = document.createElement("div");
599
+ headerLeft.className = "dbg-header-left";
600
+ this.headerDot = document.createElement("span");
601
+ this.headerDot.className = "dbg-header-dot loading";
602
+ const headerTitle = document.createElement("span");
603
+ headerTitle.className = "dbg-header-title";
604
+ headerTitle.textContent = "Snack Kit Debugger";
605
+ headerLeft.appendChild(this.headerDot);
606
+ headerLeft.appendChild(headerTitle);
607
+ header.appendChild(headerLeft);
608
+ panel.appendChild(header);
609
+ const body = document.createElement("div");
610
+ body.className = "dbg-body";
611
+ this.gwVersion = document.createElement("span");
612
+ this.gwVersion.className = "dbg-header-meta";
613
+ this.gwSelect = new SearchSelect((val) => this.onGatewayChange(val));
614
+ this.gwSelect.setOptions(this.gateways.map((g) => ({ value: g, label: g })), T.selectGateway());
615
+ body.appendChild(this.createFieldWithAction(T.gateway(), this.gwVersion, this.gwSelect.getElement()));
616
+ this.typeSelect = new SearchSelect((val) => this.onTypeChange(val));
617
+ body.appendChild(this.createField(T.tag(), this.typeSelect.getElement()));
618
+ this.serverSelect = new SearchSelect((val) => this.onServerChange(val));
619
+ body.appendChild(this.createField(T.server(), this.serverSelect.getElement()));
620
+ const ctxWrap = document.createElement("div");
621
+ ctxWrap.className = "dbg-ctx-wrap";
622
+ const ctxSearch = document.createElement("div");
623
+ ctxSearch.className = "dbg-ctx-search";
624
+ const ctxSearchIcon = document.createElement("span");
625
+ ctxSearchIcon.className = "dbg-select-search-icon";
626
+ ctxSearchIcon.textContent = "\u2315";
627
+ this.ctxSearchInput = document.createElement("input");
628
+ this.ctxSearchInput.type = "text";
629
+ this.ctxSearchInput.placeholder = T.filterCtx();
630
+ this.ctxSearchInput.addEventListener("input", () => this.filterCtx(this.ctxSearchInput.value));
631
+ ctxSearch.appendChild(ctxSearchIcon);
632
+ ctxSearch.appendChild(this.ctxSearchInput);
633
+ this.ctxList = document.createElement("div");
634
+ this.ctxList.className = "dbg-ctx-list";
635
+ ctxWrap.appendChild(ctxSearch);
636
+ ctxWrap.appendChild(this.ctxList);
637
+ this.copyAllBtn = document.createElement("button");
638
+ this.copyAllBtn.className = "dbg-copy-all";
639
+ this.copyAllBtn.textContent = T.copyAll();
640
+ this.copyAllBtn.setAttribute("title", isChinese() ? "\u5C06\u5168\u90E8\u4E0A\u4E0B\u6587\u590D\u5236\u4E3A JSON\uFF0C\u53EF\u4F5C\u4E3A Context.load({...}) \u7684\u53C2\u6570" : "Copy all ctx entries as JSON for Context.load({...})");
641
+ this.copyAllBtn.addEventListener("click", (e) => {
642
+ e.stopPropagation();
643
+ this.copyAllCtx();
644
+ });
645
+ const ctxField = this.createFieldWithAction(T.ctxLabel(), this.copyAllBtn, ctxWrap);
646
+ ctxField.classList.add("dbg-ctx-field");
647
+ body.appendChild(ctxField);
648
+ panel.appendChild(body);
649
+ const footer = document.createElement("div");
650
+ footer.className = "dbg-footer";
651
+ this.footerEl = footer;
652
+ panel.appendChild(footer);
653
+ const toggle = document.createElement("button");
654
+ toggle.className = "dbg-toggle";
655
+ toggle.textContent = "\u{1F41B}";
656
+ toggle.setAttribute("title", "Snack Debugger");
657
+ toggle.addEventListener("click", (e) => {
658
+ e.stopPropagation();
659
+ panel.classList.toggle("open");
660
+ });
661
+ root.appendChild(panel);
662
+ root.appendChild(toggle);
663
+ document.body.appendChild(root);
664
+ }
665
+ /** 创建普通字段行(label + content) */
666
+ createField(label, content) {
667
+ const field = document.createElement("div");
668
+ field.className = "dbg-field";
669
+ const lbl = document.createElement("div");
670
+ lbl.className = "dbg-label";
671
+ lbl.textContent = label;
672
+ field.appendChild(lbl);
673
+ field.appendChild(content);
674
+ return field;
675
+ }
676
+ /** 创建带操作按钮的字段行(label + action button + content) */
677
+ createFieldWithAction(label, action, content) {
678
+ const field = document.createElement("div");
679
+ field.className = "dbg-field";
680
+ const fieldHeader = document.createElement("div");
681
+ fieldHeader.className = "dbg-field-header";
682
+ const lbl = document.createElement("div");
683
+ lbl.className = "dbg-label";
684
+ lbl.textContent = label;
685
+ fieldHeader.appendChild(lbl);
686
+ fieldHeader.appendChild(action);
687
+ field.appendChild(fieldHeader);
688
+ field.appendChild(content);
689
+ return field;
690
+ }
691
+ /** 恢复上次持久化的状态 */
692
+ async restoreState() {
693
+ const savedGw = localStorage.getItem(KEY_GATEWAY);
694
+ const gw = savedGw && this.gateways.includes(savedGw) ? savedGw : this.gateways[0];
695
+ this.gwSelect.setValue(gw);
696
+ await this.loadServers(gw);
697
+ const savedType = localStorage.getItem(KEY_TYPE);
698
+ if (savedType) {
699
+ this.typeSelect.setValue(savedType);
700
+ this.renderServerOptions(savedType);
701
+ }
702
+ const savedServer = localStorage.getItem(KEY_SERVER);
703
+ if (savedServer && this.servers.find((s) => s.key === savedServer)) {
704
+ this.serverSelect.setValue(savedServer);
705
+ await this.applyServer(savedServer);
706
+ }
707
+ }
708
+ async onGatewayChange(gw) {
709
+ localStorage.setItem(KEY_GATEWAY, gw);
710
+ this.gwVersion.textContent = "";
711
+ await this.loadServers(gw);
712
+ }
713
+ onTypeChange(type) {
714
+ localStorage.setItem(KEY_TYPE, type);
715
+ this.renderServerOptions(type);
716
+ }
717
+ async onServerChange(key) {
718
+ localStorage.setItem(KEY_SERVER, key);
719
+ await this.applyServer(key);
720
+ }
721
+ /** 从网关加载服务列表 */
722
+ async loadServers(gwUrl) {
723
+ this.setHeader("loading");
724
+ this.setFooter(T.loading(), "");
725
+ const result = await chunkN7BJS6LI_cjs.Get(`${gwUrl}/web-debug/host/list`, {
726
+ cache: true,
727
+ timeout: this.options.timeout
728
+ });
729
+ if (result.error) {
730
+ this.setHeader("err");
731
+ this.setFooter(T.loadFailed(result.error.message), "err");
732
+ return;
733
+ }
734
+ this.servers = result.data ?? [];
735
+ this.renderTypeOptions();
736
+ this.setHeader("ok");
737
+ this.setFooter(T.loaded(this.servers.length), "ok");
738
+ }
739
+ /** 渲染标签选项 */
740
+ renderTypeOptions() {
741
+ const types = [...new Set(this.servers.map((s) => s.type).filter(Boolean))];
742
+ const opts = [{ value: "", label: T.allTags() }, ...types.map((t) => ({ value: t, label: t }))];
743
+ this.typeSelect.setOptions(opts, T.allTags());
744
+ this.typeSelect.setValue("");
745
+ this.renderServerOptions("");
746
+ }
747
+ /** 根据标签渲染服务选项 */
748
+ renderServerOptions(type) {
749
+ const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers;
750
+ this.serverSelect.setOptions(
751
+ filtered.map((s) => ({ value: s.key, label: `${s.name} (${s.key})` })),
752
+ T.selectServer()
753
+ );
754
+ this.renderCtxList({});
755
+ }
756
+ /** 切换目标服务,重新加载 Context 路由映射 */
757
+ async applyServer(key) {
758
+ if (!key) {
759
+ this.renderCtxList({});
760
+ return;
761
+ }
762
+ const server = this.servers.find((s) => s.key === key);
763
+ if (!server) return;
764
+ this.setFooter(T.switching(), "");
765
+ try {
766
+ await chunkN7BJS6LI_cjs.Context.load(server.origin, this.options.timeout);
767
+ const res = await fetch(`${server.origin}/ngw/context`);
768
+ const raw = res.ok ? await res.json() : {};
769
+ const ctxMap = {};
770
+ for (const [k, v] of Object.entries(raw)) {
771
+ if (k !== "$info" && typeof v === "string") ctxMap[k] = v;
772
+ }
773
+ const info = chunkN7BJS6LI_cjs.Context.info;
774
+ if (info) this.gwVersion.textContent = `v${info.version}`;
775
+ this.renderCtxList(ctxMap);
776
+ const count = Object.keys(ctxMap).length;
777
+ this.setFooter(T.routes(server.name, count), "ok");
778
+ } catch (err) {
779
+ const msg = err instanceof Error ? err.message : String(err);
780
+ this.setFooter(T.switchFailed(msg), "err");
781
+ }
782
+ }
783
+ /** 渲染路由列表 */
784
+ renderCtxList(ctxMap) {
785
+ this.currentCtxMap = ctxMap;
786
+ this.ctxList.innerHTML = "";
787
+ this.ctxSearchInput.value = "";
788
+ const entries = Object.entries(ctxMap);
789
+ if (entries.length === 0) {
790
+ const empty = document.createElement("div");
791
+ empty.className = "dbg-ctx-empty";
792
+ empty.textContent = T.selectFirst();
793
+ this.ctxList.appendChild(empty);
794
+ return;
795
+ }
796
+ entries.forEach(([k, v]) => {
797
+ const item = document.createElement("div");
798
+ item.className = "dbg-ctx-item";
799
+ item.dataset["key"] = k;
800
+ const keySpan = document.createElement("span");
801
+ keySpan.className = "dbg-ctx-key";
802
+ keySpan.textContent = k;
803
+ keySpan.setAttribute("title", k);
804
+ const valSpan = document.createElement("span");
805
+ valSpan.className = "dbg-ctx-val";
806
+ valSpan.textContent = v;
807
+ valSpan.setAttribute("title", `${k} \u2192 ${v}`);
808
+ const copyIcon = document.createElement("span");
809
+ copyIcon.className = "dbg-ctx-copy";
810
+ copyIcon.textContent = "\u2398";
811
+ item.appendChild(keySpan);
812
+ item.appendChild(valSpan);
813
+ item.appendChild(copyIcon);
814
+ item.addEventListener("click", () => this.copyCtxItem(item, k));
815
+ this.ctxList.appendChild(item);
816
+ });
817
+ }
818
+ /** 过滤路由列表 */
819
+ filterCtx(query) {
820
+ const q = query.toLowerCase();
821
+ this.ctxList.querySelectorAll(".dbg-ctx-item").forEach((item) => {
822
+ const key = item.dataset["key"] ?? "";
823
+ item.classList.toggle("hidden", !!q && !key.toLowerCase().includes(q));
824
+ });
825
+ }
826
+ /** 复制单个 ctx key */
827
+ copyCtxItem(item, text) {
828
+ navigator.clipboard.writeText(text).then(() => {
829
+ item.classList.add("copied");
830
+ const icon = item.querySelector(".dbg-ctx-copy");
831
+ if (icon) icon.textContent = "\u2713";
832
+ setTimeout(() => {
833
+ item.classList.remove("copied");
834
+ const ic = item.querySelector(".dbg-ctx-copy");
835
+ if (ic) ic.textContent = "\u2398";
836
+ }, 1500);
837
+ this.setFooter(T.copied(text), "ok");
838
+ setTimeout(() => this.setFooter("", ""), 2e3);
839
+ });
840
+ }
841
+ /** 一键复制全部上下文为 JSON */
842
+ copyAllCtx() {
843
+ const entries = Object.keys(this.currentCtxMap);
844
+ if (entries.length === 0) return;
845
+ const json = JSON.stringify(this.currentCtxMap, null, 2);
846
+ navigator.clipboard.writeText(json).then(() => {
847
+ this.copyAllBtn.textContent = "\u2713 " + T.copyAllDone();
848
+ this.copyAllBtn.classList.add("done");
849
+ setTimeout(() => {
850
+ this.copyAllBtn.textContent = T.copyAll();
851
+ this.copyAllBtn.classList.remove("done");
852
+ }, 2e3);
853
+ this.setFooter(T.copyAllDone(), "ok");
854
+ setTimeout(() => this.setFooter("", ""), 2500);
855
+ });
856
+ }
857
+ setHeader(state) {
858
+ this.headerDot.className = `dbg-header-dot${state !== "ok" ? ` ${state}` : ""}`;
859
+ }
860
+ setFooter(msg, cls) {
861
+ this.footerEl.innerHTML = msg ? `<span class="dbg-footer-dot"></span>${msg}` : "";
862
+ this.footerEl.className = `dbg-footer${cls ? ` ${cls}` : ""}`;
863
+ }
864
+ };
865
+
866
+ exports.Debugger = Debugger;
867
+ //# sourceMappingURL=chunk-YOWLTZM5.cjs.map
868
+ //# sourceMappingURL=chunk-YOWLTZM5.cjs.map