@snack-kit/lib
Advanced tools
| 'use strict'; | ||
| var chunkN7BJS6LI_cjs = require('./chunk-N7BJS6LI.cjs'); | ||
| // src/debugger/debugger.ts | ||
| var STORAGE_PREFIX = "__snackkit_debugger__"; | ||
| var KEY_GATEWAY = `${STORAGE_PREFIX}gateway`; | ||
| var KEY_TYPE = `${STORAGE_PREFIX}type`; | ||
| var KEY_SERVER = `${STORAGE_PREFIX}server`; | ||
| function isChinese() { | ||
| return /^zh/i.test(navigator.language ?? ""); | ||
| } | ||
| var T = { | ||
| gateway: () => isChinese() ? "\u7F51\u5173" : "Gateway", | ||
| tag: () => isChinese() ? "\u6807\u7B7E" : "Tag", | ||
| server: () => isChinese() ? "\u670D\u52A1" : "Server", | ||
| ctx: () => isChinese() ? "\u4E0A\u4E0B\u6587" : "Ctx", | ||
| ctxLabel: () => isChinese() ? "\u4E0A\u4E0B\u6587 \u2014 \u70B9\u51FB\u884C\u590D\u5236 key" : "Ctx \u2014 click row to copy key", | ||
| search: () => isChinese() ? "\u641C\u7D22..." : "Search...", | ||
| noMatch: () => isChinese() ? "\u65E0\u5339\u914D\u9879" : "No results", | ||
| allTags: () => isChinese() ? "\u5168\u90E8\u6807\u7B7E" : "All tags", | ||
| selectGateway: () => isChinese() ? "\u9009\u62E9\u7F51\u5173" : "Select gateway", | ||
| selectServer: () => isChinese() ? "\u9009\u62E9\u670D\u52A1" : "Select server", | ||
| filterCtx: () => isChinese() ? "\u8FC7\u6EE4\u8DEF\u7531..." : "Filter routes...", | ||
| selectFirst: () => isChinese() ? "\u2190 \u8BF7\u5148\u9009\u62E9\u670D\u52A1" : "\u2190 Select a server first", | ||
| loading: () => isChinese() ? "\u52A0\u8F7D\u670D\u52A1\u5217\u8868..." : "Loading...", | ||
| loadFailed: (m) => isChinese() ? `\u52A0\u8F7D\u5931\u8D25: ${m}` : `Failed: ${m}`, | ||
| loaded: (n) => isChinese() ? `\u5DF2\u52A0\u8F7D ${n} \u4E2A\u670D\u52A1` : `${n} servers loaded`, | ||
| switching: () => isChinese() ? "\u5207\u6362\u670D\u52A1\u4E2D..." : "Switching...", | ||
| switchFailed: (m) => isChinese() ? `\u5207\u6362\u5931\u8D25: ${m}` : `Failed: ${m}`, | ||
| routes: (name, n) => isChinese() ? `${name} \xB7 ${n} \u6761\u8DEF\u7531` : `${name} \xB7 ${n} routes`, | ||
| copied: (k) => isChinese() ? `\u5DF2\u590D\u5236: ${k}` : `Copied: ${k}`, | ||
| copyAll: () => isChinese() ? "\u590D\u5236\u5168\u90E8 JSON" : "Copy all JSON", | ||
| copyAllDone: () => isChinese() ? "\u5DF2\u590D\u5236\u4E3A JSON" : "Copied as JSON" | ||
| }; | ||
| var PANEL_STYLE = ` | ||
| #__snackkit_debugger__ { | ||
| position: fixed; | ||
| bottom: 20px; | ||
| right: 20px; | ||
| z-index: 99999; | ||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif; | ||
| font-size: 13px; | ||
| color: #b8ccec; | ||
| user-select: none; | ||
| } | ||
| /* \u2500\u2500 \u6D6E\u52A8\u6309\u94AE \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-toggle { | ||
| width: 42px; | ||
| height: 42px; | ||
| border-radius: 50%; | ||
| background: linear-gradient(135deg, #4f8ef7, #6d6ff5); | ||
| border: none; | ||
| cursor: pointer; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| box-shadow: 0 4px 14px rgba(79,142,247,0.45); | ||
| margin-left: auto; | ||
| font-size: 20px; | ||
| transition: transform 0.15s, box-shadow 0.15s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-toggle:hover { | ||
| transform: scale(1.08); | ||
| box-shadow: 0 6px 20px rgba(79,142,247,0.55); | ||
| } | ||
| #__snackkit_debugger__ .dbg-toggle:active { transform: scale(0.94); } | ||
| /* \u2500\u2500 \u9762\u677F\u4E3B\u4F53 \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-panel { | ||
| background: #1e2638; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 12px; | ||
| width: 440px; | ||
| margin-bottom: 10px; | ||
| box-shadow: 0 8px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.05); | ||
| overflow: visible; | ||
| opacity: 0; | ||
| transform: translateY(8px) scale(0.98); | ||
| pointer-events: none; | ||
| transition: opacity 0.18s ease, transform 0.18s ease; | ||
| } | ||
| #__snackkit_debugger__ .dbg-panel.open { | ||
| opacity: 1; | ||
| transform: translateY(0) scale(1); | ||
| pointer-events: auto; | ||
| } | ||
| /* \u2500\u2500 \u9762\u677F Header \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-header { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| padding: 10px 14px; | ||
| background: #171e2e; | ||
| border-bottom: 1px solid #2d3a55; | ||
| border-radius: 12px 12px 0 0; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-left { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 7px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-dot { | ||
| width: 7px; | ||
| height: 7px; | ||
| border-radius: 50%; | ||
| background: #34d399; | ||
| box-shadow: 0 0 6px rgba(52,211,153,0.6); | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-dot.loading { | ||
| background: #fbbf24; | ||
| box-shadow: 0 0 6px rgba(251,191,36,0.6); | ||
| animation: dbg-pulse 1s ease-in-out infinite; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-dot.err { | ||
| background: #f87171; | ||
| box-shadow: 0 0 6px rgba(248,113,113,0.6); | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-title { | ||
| font-size: 12px; | ||
| font-weight: 600; | ||
| color: #dce8fa; | ||
| letter-spacing: 0.03em; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-meta { | ||
| font-size: 11px; | ||
| color: #4d6080; | ||
| } | ||
| /* \u2500\u2500 \u9762\u677F\u5185\u5BB9\u533A \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-body { | ||
| padding: 12px 14px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 10px; | ||
| height: 420px; | ||
| overflow: visible; | ||
| } | ||
| /* \u2500\u2500 \u5B57\u6BB5\u884C \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-field { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 5px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-field-header { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| } | ||
| #__snackkit_debugger__ .dbg-label { | ||
| font-size: 10px; | ||
| font-weight: 600; | ||
| color: #5e72a0; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.07em; | ||
| } | ||
| /* \u2500\u2500 \u4E00\u952E\u590D\u5236\u5168\u90E8 JSON \u6309\u94AE \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-copy-all { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 4px; | ||
| background: transparent; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 4px; | ||
| padding: 2px 7px; | ||
| color: #5e72a0; | ||
| font-size: 10px; | ||
| font-family: inherit; | ||
| cursor: pointer; | ||
| transition: border-color 0.15s, color 0.15s, background 0.15s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-copy-all:hover { | ||
| border-color: #4f8ef7; | ||
| color: #7aacfa; | ||
| background: rgba(79,142,247,0.08); | ||
| } | ||
| #__snackkit_debugger__ .dbg-copy-all.done { | ||
| border-color: #34d399; | ||
| color: #34d399; | ||
| } | ||
| /* \u2500\u2500 \u81EA\u5B9A\u4E49\u4E0B\u62C9 \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-select { | ||
| position: relative; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-trigger { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| gap: 6px; | ||
| background: #252f45; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 6px; | ||
| padding: 6px 10px; | ||
| cursor: pointer; | ||
| transition: border-color 0.15s, background 0.15s; | ||
| min-height: 32px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-trigger:hover { border-color: #3d5070; background: #2d3a55; } | ||
| #__snackkit_debugger__ .dbg-select.open .dbg-select-trigger { | ||
| border-color: #4f8ef7; | ||
| background: #2d3a55; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-val { | ||
| flex: 1; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| font-size: 12px; | ||
| color: #b8ccec; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-val.placeholder { color: #3d5070; } | ||
| #__snackkit_debugger__ .dbg-select-arrow { | ||
| color: #3d5070; | ||
| font-size: 10px; | ||
| flex-shrink: 0; | ||
| transition: transform 0.15s, color 0.15s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select.open .dbg-select-arrow { | ||
| transform: rotate(180deg); | ||
| color: #4f8ef7; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-dropdown { | ||
| position: absolute; | ||
| bottom: calc(100% + 4px); | ||
| left: 0; | ||
| right: 0; | ||
| background: #1e2638; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 8px; | ||
| box-shadow: 0 -8px 24px rgba(0,0,0,0.35); | ||
| overflow: hidden; | ||
| opacity: 0; | ||
| transform: translateY(4px); | ||
| pointer-events: none; | ||
| transition: opacity 0.14s, transform 0.14s; | ||
| z-index: 100000; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select.open .dbg-select-dropdown { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| pointer-events: auto; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-search { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| padding: 7px 10px; | ||
| border-bottom: 1px solid #2d3a55; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-search-icon { color: #3d5070; font-size: 11px; flex-shrink: 0; } | ||
| #__snackkit_debugger__ .dbg-select-search input { | ||
| flex: 1; | ||
| background: transparent; | ||
| border: none; | ||
| outline: none; | ||
| color: #b8ccec; | ||
| font-size: 12px; | ||
| font-family: inherit; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-search input::placeholder { color: #3d5070; } | ||
| #__snackkit_debugger__ .dbg-select-list { | ||
| max-height: 160px; | ||
| overflow-y: auto; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar { width: 3px; } | ||
| #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-track { background: transparent; } | ||
| #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; } | ||
| #__snackkit_debugger__ .dbg-select-option { | ||
| padding: 7px 10px; | ||
| cursor: pointer; | ||
| font-size: 12px; | ||
| color: #8aa4c8; | ||
| transition: background 0.1s, color 0.1s; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-option:hover { background: #2d3a55; color: #dce8fa; } | ||
| #__snackkit_debugger__ .dbg-select-option.active { color: #7aacfa; background: #1e3060; } | ||
| #__snackkit_debugger__ .dbg-select-option.hidden { display: none; } | ||
| #__snackkit_debugger__ .dbg-select-empty { | ||
| padding: 12px 10px; | ||
| font-size: 12px; | ||
| color: #3d5070; | ||
| text-align: center; | ||
| } | ||
| /* \u2500\u2500 \u8DEF\u7531\u5217\u8868 \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-ctx-field { | ||
| flex: 1; | ||
| min-height: 0; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 5px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-wrap { | ||
| flex: 1; | ||
| min-height: 0; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 6px; | ||
| overflow: hidden; | ||
| background: #252f45; | ||
| display: flex; | ||
| flex-direction: column; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-search { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| padding: 6px 10px; | ||
| border-bottom: 1px solid #2d3a55; | ||
| background: #1e2638; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-search input { | ||
| flex: 1; | ||
| background: transparent; | ||
| border: none; | ||
| outline: none; | ||
| color: #b8ccec; | ||
| font-size: 12px; | ||
| font-family: inherit; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-search input::placeholder { color: #3d5070; } | ||
| #__snackkit_debugger__ .dbg-ctx-list { | ||
| flex: 1; | ||
| min-height: 0; | ||
| overflow-y: auto; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar { width: 3px; } | ||
| #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-track { background: transparent; } | ||
| #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; } | ||
| #__snackkit_debugger__ .dbg-ctx-item { | ||
| padding: 5px 10px; | ||
| cursor: pointer; | ||
| display: grid; | ||
| grid-template-columns: minmax(80px, 148px) 1fr auto; | ||
| gap: 8px; | ||
| align-items: center; | ||
| border-bottom: 1px solid #1e2638; | ||
| transition: background 0.1s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover { background: #2d3a55; } | ||
| #__snackkit_debugger__ .dbg-ctx-item.hidden { display: none; } | ||
| #__snackkit_debugger__ .dbg-ctx-key { | ||
| font-size: 12px; | ||
| color: #7aacfa; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-val { | ||
| font-size: 11px; | ||
| color: #4d6080; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| transition: color 0.1s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-val { color: #7a90b0; } | ||
| #__snackkit_debugger__ .dbg-ctx-copy { | ||
| font-size: 11px; | ||
| color: #3d5070; | ||
| opacity: 0; | ||
| flex-shrink: 0; | ||
| transition: opacity 0.1s, color 0.1s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-copy { opacity: 1; color: #5e72a0; } | ||
| #__snackkit_debugger__ .dbg-ctx-item.copied .dbg-ctx-copy { color: #34d399; opacity: 1; } | ||
| #__snackkit_debugger__ .dbg-ctx-empty { | ||
| padding: 16px 10px; | ||
| font-size: 12px; | ||
| color: #3d5070; | ||
| text-align: center; | ||
| } | ||
| /* \u2500\u2500 \u5E95\u90E8\u72B6\u6001\u680F \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-footer { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| padding: 7px 14px; | ||
| background: #171e2e; | ||
| border-top: 1px solid #2d3a55; | ||
| border-radius: 0 0 12px 12px; | ||
| font-size: 11px; | ||
| color: #4d6080; | ||
| min-height: 30px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-footer.ok { color: #34d399; } | ||
| #__snackkit_debugger__ .dbg-footer.err { color: #f87171; } | ||
| #__snackkit_debugger__ .dbg-footer-dot { | ||
| width: 4px; | ||
| height: 4px; | ||
| border-radius: 50%; | ||
| background: currentColor; | ||
| flex-shrink: 0; | ||
| } | ||
| @keyframes dbg-pulse { | ||
| 0%, 100% { opacity: 1; } | ||
| 50% { opacity: 0.35; } | ||
| } | ||
| `; | ||
| var SearchSelect = class { | ||
| constructor(onChange) { | ||
| this.options = []; | ||
| this.selected = ""; | ||
| this.onChange = onChange; | ||
| this.el = document.createElement("div"); | ||
| this.el.className = "dbg-select"; | ||
| this.trigger = document.createElement("div"); | ||
| this.trigger.className = "dbg-select-trigger"; | ||
| this.valEl = document.createElement("span"); | ||
| this.valEl.className = "dbg-select-val placeholder"; | ||
| const arrow = document.createElement("span"); | ||
| arrow.className = "dbg-select-arrow"; | ||
| arrow.textContent = "\u25BE"; | ||
| this.trigger.appendChild(this.valEl); | ||
| this.trigger.appendChild(arrow); | ||
| this.dropdown = document.createElement("div"); | ||
| this.dropdown.className = "dbg-select-dropdown"; | ||
| const searchWrap = document.createElement("div"); | ||
| searchWrap.className = "dbg-select-search"; | ||
| const searchIcon = document.createElement("span"); | ||
| searchIcon.className = "dbg-select-search-icon"; | ||
| searchIcon.textContent = "\u2315"; | ||
| this.searchInput = document.createElement("input"); | ||
| this.searchInput.type = "text"; | ||
| this.searchInput.placeholder = T.search(); | ||
| searchWrap.appendChild(searchIcon); | ||
| searchWrap.appendChild(this.searchInput); | ||
| this.listEl = document.createElement("div"); | ||
| this.listEl.className = "dbg-select-list"; | ||
| this.dropdown.appendChild(searchWrap); | ||
| this.dropdown.appendChild(this.listEl); | ||
| this.el.appendChild(this.trigger); | ||
| this.el.appendChild(this.dropdown); | ||
| this.bindEvents(); | ||
| } | ||
| bindEvents() { | ||
| this.trigger.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| const isOpen = this.el.classList.contains("open"); | ||
| document.querySelectorAll("#__snackkit_debugger__ .dbg-select.open").forEach((el) => { | ||
| if (el !== this.el) el.classList.remove("open"); | ||
| }); | ||
| this.el.classList.toggle("open", !isOpen); | ||
| if (!isOpen) { | ||
| this.searchInput.value = ""; | ||
| this.filterOptions(""); | ||
| setTimeout(() => this.searchInput.focus(), 50); | ||
| } | ||
| }); | ||
| this.searchInput.addEventListener("input", () => { | ||
| this.filterOptions(this.searchInput.value); | ||
| }); | ||
| this.searchInput.addEventListener("click", (e) => e.stopPropagation()); | ||
| document.addEventListener("click", () => { | ||
| this.el.classList.remove("open"); | ||
| }); | ||
| } | ||
| filterOptions(query) { | ||
| const q = query.toLowerCase(); | ||
| let visibleCount = 0; | ||
| this.listEl.querySelectorAll(".dbg-select-option").forEach((opt) => { | ||
| const match = !q || opt.textContent.toLowerCase().includes(q); | ||
| opt.classList.toggle("hidden", !match); | ||
| if (match) visibleCount++; | ||
| }); | ||
| let emptyEl = this.listEl.querySelector(".dbg-select-empty"); | ||
| if (visibleCount === 0) { | ||
| if (!emptyEl) { | ||
| emptyEl = document.createElement("div"); | ||
| emptyEl.className = "dbg-select-empty"; | ||
| emptyEl.textContent = T.noMatch(); | ||
| this.listEl.appendChild(emptyEl); | ||
| } | ||
| } else { | ||
| emptyEl?.remove(); | ||
| } | ||
| } | ||
| /** 更新选项列表 */ | ||
| setOptions(options, placeholder) { | ||
| this.options = options; | ||
| this.listEl.innerHTML = ""; | ||
| this.valEl.textContent = placeholder; | ||
| this.valEl.className = "dbg-select-val placeholder"; | ||
| this.selected = ""; | ||
| options.forEach(({ value, label }) => { | ||
| const opt = document.createElement("div"); | ||
| opt.className = "dbg-select-option"; | ||
| opt.dataset["value"] = value; | ||
| opt.textContent = label; | ||
| opt.setAttribute("title", label); | ||
| opt.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| this.select(value, label); | ||
| this.el.classList.remove("open"); | ||
| }); | ||
| this.listEl.appendChild(opt); | ||
| }); | ||
| } | ||
| /** 选中某个值(不触发 onChange) */ | ||
| setValue(value) { | ||
| const opt = this.options.find((o) => o.value === value); | ||
| if (opt) this.select(opt.value, opt.label, false); | ||
| } | ||
| select(value, label, emit = true) { | ||
| this.selected = value; | ||
| this.valEl.textContent = label; | ||
| this.valEl.className = "dbg-select-val"; | ||
| this.listEl.querySelectorAll(".dbg-select-option").forEach((el) => { | ||
| el.classList.toggle("active", el.dataset["value"] === value); | ||
| }); | ||
| if (emit) this.onChange(value); | ||
| } | ||
| getValue() { | ||
| return this.selected; | ||
| } | ||
| getElement() { | ||
| return this.el; | ||
| } | ||
| }; | ||
| var Debugger = class _Debugger { | ||
| constructor(options) { | ||
| this.servers = []; | ||
| this.currentCtxMap = {}; | ||
| this.options = options; | ||
| this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways]; | ||
| } | ||
| /** | ||
| * 初始化并渲染调试面板 | ||
| * | ||
| * 调用后在页面右下角插入浮动按钮,点击展开调试面板。 | ||
| * 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。 | ||
| * 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。 | ||
| * | ||
| * @param options 配置项 | ||
| * | ||
| * @example 单网关 | ||
| * ```ts | ||
| * import { Debugger } from '@snack-kit/lib/debugger' | ||
| * | ||
| * await Debugger.init({ | ||
| * gateways: 'http://dev-gateway.example.com', | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 多网关(开发/测试环境切换) | ||
| * ```ts | ||
| * await Debugger.init({ | ||
| * gateways: [ | ||
| * 'http://dev-gateway.example.com', | ||
| * 'http://test-gateway.example.com', | ||
| * ], | ||
| * timeout: 5000, | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 仅在非生产环境加载 | ||
| * ```ts | ||
| * if (import.meta.env.DEV) { | ||
| * const { Debugger } = await import('@snack-kit/lib/debugger') | ||
| * await Debugger.init({ gateways: 'http://dev-gateway.example.com' }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| static async init(options) { | ||
| const instance = new _Debugger({ timeout: 1e4, ...options }); | ||
| instance.injectStyle(); | ||
| instance.buildDOM(); | ||
| await instance.restoreState(); | ||
| return instance; | ||
| } | ||
| /** 注入内联样式 */ | ||
| injectStyle() { | ||
| if (document.getElementById("__snackkit_debugger_style__")) return; | ||
| const style = document.createElement("style"); | ||
| style.id = "__snackkit_debugger_style__"; | ||
| style.textContent = PANEL_STYLE; | ||
| document.head.appendChild(style); | ||
| } | ||
| /** 构建 DOM 结构 */ | ||
| buildDOM() { | ||
| const root = document.createElement("div"); | ||
| root.id = "__snackkit_debugger__"; | ||
| const panel = document.createElement("div"); | ||
| panel.className = "dbg-panel"; | ||
| this.panelEl = panel; | ||
| const header = document.createElement("div"); | ||
| header.className = "dbg-header"; | ||
| const headerLeft = document.createElement("div"); | ||
| headerLeft.className = "dbg-header-left"; | ||
| this.headerDot = document.createElement("span"); | ||
| this.headerDot.className = "dbg-header-dot loading"; | ||
| const headerTitle = document.createElement("span"); | ||
| headerTitle.className = "dbg-header-title"; | ||
| headerTitle.textContent = "Snack Kit Debugger"; | ||
| headerLeft.appendChild(this.headerDot); | ||
| headerLeft.appendChild(headerTitle); | ||
| header.appendChild(headerLeft); | ||
| panel.appendChild(header); | ||
| const body = document.createElement("div"); | ||
| body.className = "dbg-body"; | ||
| this.gwVersion = document.createElement("span"); | ||
| this.gwVersion.className = "dbg-header-meta"; | ||
| this.gwSelect = new SearchSelect((val) => this.onGatewayChange(val)); | ||
| this.gwSelect.setOptions(this.gateways.map((g) => ({ value: g, label: g })), T.selectGateway()); | ||
| body.appendChild(this.createFieldWithAction(T.gateway(), this.gwVersion, this.gwSelect.getElement())); | ||
| this.typeSelect = new SearchSelect((val) => this.onTypeChange(val)); | ||
| body.appendChild(this.createField(T.tag(), this.typeSelect.getElement())); | ||
| this.serverSelect = new SearchSelect((val) => this.onServerChange(val)); | ||
| body.appendChild(this.createField(T.server(), this.serverSelect.getElement())); | ||
| const ctxWrap = document.createElement("div"); | ||
| ctxWrap.className = "dbg-ctx-wrap"; | ||
| const ctxSearch = document.createElement("div"); | ||
| ctxSearch.className = "dbg-ctx-search"; | ||
| const ctxSearchIcon = document.createElement("span"); | ||
| ctxSearchIcon.className = "dbg-select-search-icon"; | ||
| ctxSearchIcon.textContent = "\u2315"; | ||
| this.ctxSearchInput = document.createElement("input"); | ||
| this.ctxSearchInput.type = "text"; | ||
| this.ctxSearchInput.placeholder = T.filterCtx(); | ||
| this.ctxSearchInput.addEventListener("input", () => this.filterCtx(this.ctxSearchInput.value)); | ||
| ctxSearch.appendChild(ctxSearchIcon); | ||
| ctxSearch.appendChild(this.ctxSearchInput); | ||
| this.ctxList = document.createElement("div"); | ||
| this.ctxList.className = "dbg-ctx-list"; | ||
| ctxWrap.appendChild(ctxSearch); | ||
| ctxWrap.appendChild(this.ctxList); | ||
| this.copyAllBtn = document.createElement("button"); | ||
| this.copyAllBtn.className = "dbg-copy-all"; | ||
| this.copyAllBtn.textContent = T.copyAll(); | ||
| 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({...})"); | ||
| this.copyAllBtn.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| this.copyAllCtx(); | ||
| }); | ||
| const ctxField = this.createFieldWithAction(T.ctxLabel(), this.copyAllBtn, ctxWrap); | ||
| ctxField.classList.add("dbg-ctx-field"); | ||
| body.appendChild(ctxField); | ||
| panel.appendChild(body); | ||
| const footer = document.createElement("div"); | ||
| footer.className = "dbg-footer"; | ||
| this.footerEl = footer; | ||
| panel.appendChild(footer); | ||
| const toggle = document.createElement("button"); | ||
| toggle.className = "dbg-toggle"; | ||
| toggle.textContent = "\u{1F41B}"; | ||
| toggle.setAttribute("title", "Snack Debugger"); | ||
| toggle.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| panel.classList.toggle("open"); | ||
| }); | ||
| root.appendChild(panel); | ||
| root.appendChild(toggle); | ||
| document.body.appendChild(root); | ||
| } | ||
| /** 创建普通字段行(label + content) */ | ||
| createField(label, content) { | ||
| const field = document.createElement("div"); | ||
| field.className = "dbg-field"; | ||
| const lbl = document.createElement("div"); | ||
| lbl.className = "dbg-label"; | ||
| lbl.textContent = label; | ||
| field.appendChild(lbl); | ||
| field.appendChild(content); | ||
| return field; | ||
| } | ||
| /** 创建带操作按钮的字段行(label + action button + content) */ | ||
| createFieldWithAction(label, action, content) { | ||
| const field = document.createElement("div"); | ||
| field.className = "dbg-field"; | ||
| const fieldHeader = document.createElement("div"); | ||
| fieldHeader.className = "dbg-field-header"; | ||
| const lbl = document.createElement("div"); | ||
| lbl.className = "dbg-label"; | ||
| lbl.textContent = label; | ||
| fieldHeader.appendChild(lbl); | ||
| fieldHeader.appendChild(action); | ||
| field.appendChild(fieldHeader); | ||
| field.appendChild(content); | ||
| return field; | ||
| } | ||
| /** 恢复上次持久化的状态 */ | ||
| async restoreState() { | ||
| const savedGw = localStorage.getItem(KEY_GATEWAY); | ||
| const gw = savedGw && this.gateways.includes(savedGw) ? savedGw : this.gateways[0]; | ||
| this.gwSelect.setValue(gw); | ||
| await this.loadServers(gw); | ||
| const savedType = localStorage.getItem(KEY_TYPE); | ||
| if (savedType) { | ||
| this.typeSelect.setValue(savedType); | ||
| this.renderServerOptions(savedType); | ||
| } | ||
| const savedServer = localStorage.getItem(KEY_SERVER); | ||
| if (savedServer && this.servers.find((s) => s.key === savedServer)) { | ||
| this.serverSelect.setValue(savedServer); | ||
| await this.applyServer(savedServer); | ||
| } | ||
| } | ||
| async onGatewayChange(gw) { | ||
| localStorage.setItem(KEY_GATEWAY, gw); | ||
| this.gwVersion.textContent = ""; | ||
| await this.loadServers(gw); | ||
| } | ||
| onTypeChange(type) { | ||
| localStorage.setItem(KEY_TYPE, type); | ||
| this.renderServerOptions(type); | ||
| } | ||
| async onServerChange(key) { | ||
| localStorage.setItem(KEY_SERVER, key); | ||
| await this.applyServer(key); | ||
| } | ||
| /** 从网关加载服务列表 */ | ||
| async loadServers(gwUrl) { | ||
| this.setHeader("loading"); | ||
| this.setFooter(T.loading(), ""); | ||
| const result = await chunkN7BJS6LI_cjs.Get(`${gwUrl}/web-debug/host/list`, { | ||
| cache: true, | ||
| timeout: this.options.timeout | ||
| }); | ||
| if (result.error) { | ||
| this.setHeader("err"); | ||
| this.setFooter(T.loadFailed(result.error.message), "err"); | ||
| return; | ||
| } | ||
| this.servers = result.data ?? []; | ||
| this.renderTypeOptions(); | ||
| this.setHeader("ok"); | ||
| this.setFooter(T.loaded(this.servers.length), "ok"); | ||
| } | ||
| /** 渲染标签选项 */ | ||
| renderTypeOptions() { | ||
| const types = [...new Set(this.servers.map((s) => s.type).filter(Boolean))]; | ||
| const opts = [{ value: "", label: T.allTags() }, ...types.map((t) => ({ value: t, label: t }))]; | ||
| this.typeSelect.setOptions(opts, T.allTags()); | ||
| this.typeSelect.setValue(""); | ||
| this.renderServerOptions(""); | ||
| } | ||
| /** 根据标签渲染服务选项 */ | ||
| renderServerOptions(type) { | ||
| const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers; | ||
| this.serverSelect.setOptions( | ||
| filtered.map((s) => ({ value: s.key, label: `${s.name} (${s.key})` })), | ||
| T.selectServer() | ||
| ); | ||
| this.renderCtxList({}); | ||
| } | ||
| /** 切换目标服务,重新加载 Context 路由映射 */ | ||
| async applyServer(key) { | ||
| if (!key) { | ||
| this.renderCtxList({}); | ||
| return; | ||
| } | ||
| const server = this.servers.find((s) => s.key === key); | ||
| if (!server) return; | ||
| this.setFooter(T.switching(), ""); | ||
| try { | ||
| await chunkN7BJS6LI_cjs.Context.load(server.origin, this.options.timeout); | ||
| const res = await fetch(`${server.origin}/ngw/context`); | ||
| const raw = res.ok ? await res.json() : {}; | ||
| const ctxMap = {}; | ||
| for (const [k, v] of Object.entries(raw)) { | ||
| if (k !== "$info" && typeof v === "string") ctxMap[k] = v; | ||
| } | ||
| const info = chunkN7BJS6LI_cjs.Context.info; | ||
| if (info) this.gwVersion.textContent = `v${info.version}`; | ||
| this.renderCtxList(ctxMap); | ||
| const count = Object.keys(ctxMap).length; | ||
| this.setFooter(T.routes(server.name, count), "ok"); | ||
| } catch (err) { | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| this.setFooter(T.switchFailed(msg), "err"); | ||
| } | ||
| } | ||
| /** 渲染路由列表 */ | ||
| renderCtxList(ctxMap) { | ||
| this.currentCtxMap = ctxMap; | ||
| this.ctxList.innerHTML = ""; | ||
| this.ctxSearchInput.value = ""; | ||
| const entries = Object.entries(ctxMap); | ||
| if (entries.length === 0) { | ||
| const empty = document.createElement("div"); | ||
| empty.className = "dbg-ctx-empty"; | ||
| empty.textContent = T.selectFirst(); | ||
| this.ctxList.appendChild(empty); | ||
| return; | ||
| } | ||
| entries.forEach(([k, v]) => { | ||
| const item = document.createElement("div"); | ||
| item.className = "dbg-ctx-item"; | ||
| item.dataset["key"] = k; | ||
| const keySpan = document.createElement("span"); | ||
| keySpan.className = "dbg-ctx-key"; | ||
| keySpan.textContent = k; | ||
| keySpan.setAttribute("title", k); | ||
| const valSpan = document.createElement("span"); | ||
| valSpan.className = "dbg-ctx-val"; | ||
| valSpan.textContent = v; | ||
| valSpan.setAttribute("title", `${k} \u2192 ${v}`); | ||
| const copyIcon = document.createElement("span"); | ||
| copyIcon.className = "dbg-ctx-copy"; | ||
| copyIcon.textContent = "\u2398"; | ||
| item.appendChild(keySpan); | ||
| item.appendChild(valSpan); | ||
| item.appendChild(copyIcon); | ||
| item.addEventListener("click", () => this.copyCtxItem(item, k)); | ||
| this.ctxList.appendChild(item); | ||
| }); | ||
| } | ||
| /** 过滤路由列表 */ | ||
| filterCtx(query) { | ||
| const q = query.toLowerCase(); | ||
| this.ctxList.querySelectorAll(".dbg-ctx-item").forEach((item) => { | ||
| const key = item.dataset["key"] ?? ""; | ||
| item.classList.toggle("hidden", !!q && !key.toLowerCase().includes(q)); | ||
| }); | ||
| } | ||
| /** 复制单个 ctx key */ | ||
| copyCtxItem(item, text) { | ||
| navigator.clipboard.writeText(text).then(() => { | ||
| item.classList.add("copied"); | ||
| const icon = item.querySelector(".dbg-ctx-copy"); | ||
| if (icon) icon.textContent = "\u2713"; | ||
| setTimeout(() => { | ||
| item.classList.remove("copied"); | ||
| const ic = item.querySelector(".dbg-ctx-copy"); | ||
| if (ic) ic.textContent = "\u2398"; | ||
| }, 1500); | ||
| this.setFooter(T.copied(text), "ok"); | ||
| setTimeout(() => this.setFooter("", ""), 2e3); | ||
| }); | ||
| } | ||
| /** 一键复制全部上下文为 JSON */ | ||
| copyAllCtx() { | ||
| const entries = Object.keys(this.currentCtxMap); | ||
| if (entries.length === 0) return; | ||
| const json = JSON.stringify(this.currentCtxMap, null, 2); | ||
| navigator.clipboard.writeText(json).then(() => { | ||
| this.copyAllBtn.textContent = "\u2713 " + T.copyAllDone(); | ||
| this.copyAllBtn.classList.add("done"); | ||
| setTimeout(() => { | ||
| this.copyAllBtn.textContent = T.copyAll(); | ||
| this.copyAllBtn.classList.remove("done"); | ||
| }, 2e3); | ||
| this.setFooter(T.copyAllDone(), "ok"); | ||
| setTimeout(() => this.setFooter("", ""), 2500); | ||
| }); | ||
| } | ||
| setHeader(state) { | ||
| this.headerDot.className = `dbg-header-dot${state !== "ok" ? ` ${state}` : ""}`; | ||
| } | ||
| setFooter(msg, cls) { | ||
| this.footerEl.innerHTML = msg ? `<span class="dbg-footer-dot"></span>${msg}` : ""; | ||
| this.footerEl.className = `dbg-footer${cls ? ` ${cls}` : ""}`; | ||
| } | ||
| }; | ||
| exports.Debugger = Debugger; | ||
| //# sourceMappingURL=chunk-YOWLTZM5.cjs.map | ||
| //# sourceMappingURL=chunk-YOWLTZM5.cjs.map |
| {"version":3,"sources":["../../src/debugger/debugger.ts"],"names":["Get","Context"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","file":"chunk-YOWLTZM5.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/** 是否为中文环境 */\nfunction isChinese(): boolean {\n return /^zh/i.test(navigator.language ?? '')\n}\n\n/** i18n 文案 */\nconst T = {\n gateway: () => isChinese() ? '网关' : 'Gateway',\n tag: () => isChinese() ? '标签' : 'Tag',\n server: () => isChinese() ? '服务' : 'Server',\n ctx: () => isChinese() ? '上下文' : 'Ctx',\n ctxLabel: () => isChinese() ? '上下文 — 点击行复制 key' : 'Ctx — click row to copy key',\n search: () => isChinese() ? '搜索...' : 'Search...',\n noMatch: () => isChinese() ? '无匹配项' : 'No results',\n allTags: () => isChinese() ? '全部标签' : 'All tags',\n selectGateway: () => isChinese() ? '选择网关' : 'Select gateway',\n selectServer: () => isChinese() ? '选择服务' : 'Select server',\n filterCtx: () => isChinese() ? '过滤路由...' : 'Filter routes...',\n selectFirst: () => isChinese() ? '← 请先选择服务' : '← Select a server first',\n loading: () => isChinese() ? '加载服务列表...' : 'Loading...',\n loadFailed: (m: string) => isChinese() ? `加载失败: ${m}` : `Failed: ${m}`,\n loaded: (n: number) => isChinese() ? `已加载 ${n} 个服务` : `${n} servers loaded`,\n switching: () => isChinese() ? '切换服务中...' : 'Switching...',\n switchFailed: (m: string) => isChinese() ? `切换失败: ${m}` : `Failed: ${m}`,\n routes: (name: string, n: number) => isChinese() ? `${name} · ${n} 条路由` : `${name} · ${n} routes`,\n copied: (k: string) => isChinese() ? `已复制: ${k}` : `Copied: ${k}`,\n copyAll: () => isChinese() ? '复制全部 JSON' : 'Copy all JSON',\n copyAllDone: () => isChinese() ? '已复制为 JSON' : 'Copied as JSON',\n}\n\n/** 面板内联样式 */\nconst PANEL_STYLE = `\n #__snackkit_debugger__ {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 99999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;\n font-size: 13px;\n color: #b8ccec;\n user-select: none;\n }\n\n /* ── 浮动按钮 ── */\n #__snackkit_debugger__ .dbg-toggle {\n width: 42px;\n height: 42px;\n border-radius: 50%;\n background: linear-gradient(135deg, #4f8ef7, #6d6ff5);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 14px rgba(79,142,247,0.45);\n margin-left: auto;\n font-size: 20px;\n transition: transform 0.15s, box-shadow 0.15s;\n }\n #__snackkit_debugger__ .dbg-toggle:hover {\n transform: scale(1.08);\n box-shadow: 0 6px 20px rgba(79,142,247,0.55);\n }\n #__snackkit_debugger__ .dbg-toggle:active { transform: scale(0.94); }\n\n /* ── 面板主体 ── */\n #__snackkit_debugger__ .dbg-panel {\n background: #1e2638;\n border: 1px solid #2d3a55;\n border-radius: 12px;\n width: 440px;\n margin-bottom: 10px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.05);\n overflow: visible;\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n pointer-events: none;\n transition: opacity 0.18s ease, transform 0.18s ease;\n }\n #__snackkit_debugger__ .dbg-panel.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n\n /* ── 面板 Header ── */\n #__snackkit_debugger__ .dbg-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 14px;\n background: #171e2e;\n border-bottom: 1px solid #2d3a55;\n border-radius: 12px 12px 0 0;\n }\n #__snackkit_debugger__ .dbg-header-left {\n display: flex;\n align-items: center;\n gap: 7px;\n }\n #__snackkit_debugger__ .dbg-header-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: #34d399;\n box-shadow: 0 0 6px rgba(52,211,153,0.6);\n }\n #__snackkit_debugger__ .dbg-header-dot.loading {\n background: #fbbf24;\n box-shadow: 0 0 6px rgba(251,191,36,0.6);\n animation: dbg-pulse 1s ease-in-out infinite;\n }\n #__snackkit_debugger__ .dbg-header-dot.err {\n background: #f87171;\n box-shadow: 0 0 6px rgba(248,113,113,0.6);\n }\n #__snackkit_debugger__ .dbg-header-title {\n font-size: 12px;\n font-weight: 600;\n color: #dce8fa;\n letter-spacing: 0.03em;\n }\n #__snackkit_debugger__ .dbg-header-meta {\n font-size: 11px;\n color: #4d6080;\n }\n\n /* ── 面板内容区 ── */\n #__snackkit_debugger__ .dbg-body {\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n height: 420px;\n overflow: visible;\n }\n\n /* ── 字段行 ── */\n #__snackkit_debugger__ .dbg-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n }\n #__snackkit_debugger__ .dbg-field-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n #__snackkit_debugger__ .dbg-label {\n font-size: 10px;\n font-weight: 600;\n color: #5e72a0;\n text-transform: uppercase;\n letter-spacing: 0.07em;\n }\n\n /* ── 一键复制全部 JSON 按钮 ── */\n #__snackkit_debugger__ .dbg-copy-all {\n display: flex;\n align-items: center;\n gap: 4px;\n background: transparent;\n border: 1px solid #2d3a55;\n border-radius: 4px;\n padding: 2px 7px;\n color: #5e72a0;\n font-size: 10px;\n font-family: inherit;\n cursor: pointer;\n transition: border-color 0.15s, color 0.15s, background 0.15s;\n }\n #__snackkit_debugger__ .dbg-copy-all:hover {\n border-color: #4f8ef7;\n color: #7aacfa;\n background: rgba(79,142,247,0.08);\n }\n #__snackkit_debugger__ .dbg-copy-all.done {\n border-color: #34d399;\n color: #34d399;\n }\n\n /* ── 自定义下拉 ── */\n #__snackkit_debugger__ .dbg-select {\n position: relative;\n }\n #__snackkit_debugger__ .dbg-select-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n background: #252f45;\n border: 1px solid #2d3a55;\n border-radius: 6px;\n padding: 6px 10px;\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n min-height: 32px;\n }\n #__snackkit_debugger__ .dbg-select-trigger:hover { border-color: #3d5070; background: #2d3a55; }\n #__snackkit_debugger__ .dbg-select.open .dbg-select-trigger {\n border-color: #4f8ef7;\n background: #2d3a55;\n }\n #__snackkit_debugger__ .dbg-select-val {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n color: #b8ccec;\n }\n #__snackkit_debugger__ .dbg-select-val.placeholder { color: #3d5070; }\n #__snackkit_debugger__ .dbg-select-arrow {\n color: #3d5070;\n font-size: 10px;\n flex-shrink: 0;\n transition: transform 0.15s, color 0.15s;\n }\n #__snackkit_debugger__ .dbg-select.open .dbg-select-arrow {\n transform: rotate(180deg);\n color: #4f8ef7;\n }\n #__snackkit_debugger__ .dbg-select-dropdown {\n position: absolute;\n bottom: calc(100% + 4px);\n left: 0;\n right: 0;\n background: #1e2638;\n border: 1px solid #2d3a55;\n border-radius: 8px;\n box-shadow: 0 -8px 24px rgba(0,0,0,0.35);\n overflow: hidden;\n opacity: 0;\n transform: translateY(4px);\n pointer-events: none;\n transition: opacity 0.14s, transform 0.14s;\n z-index: 100000;\n }\n #__snackkit_debugger__ .dbg-select.open .dbg-select-dropdown {\n opacity: 1;\n transform: translateY(0);\n pointer-events: auto;\n }\n #__snackkit_debugger__ .dbg-select-search {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 7px 10px;\n border-bottom: 1px solid #2d3a55;\n }\n #__snackkit_debugger__ .dbg-select-search-icon { color: #3d5070; font-size: 11px; flex-shrink: 0; }\n #__snackkit_debugger__ .dbg-select-search input {\n flex: 1;\n background: transparent;\n border: none;\n outline: none;\n color: #b8ccec;\n font-size: 12px;\n font-family: inherit;\n }\n #__snackkit_debugger__ .dbg-select-search input::placeholder { color: #3d5070; }\n #__snackkit_debugger__ .dbg-select-list {\n max-height: 160px;\n overflow-y: auto;\n }\n #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar { width: 3px; }\n #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-track { background: transparent; }\n #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; }\n #__snackkit_debugger__ .dbg-select-option {\n padding: 7px 10px;\n cursor: pointer;\n font-size: 12px;\n color: #8aa4c8;\n transition: background 0.1s, color 0.1s;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n #__snackkit_debugger__ .dbg-select-option:hover { background: #2d3a55; color: #dce8fa; }\n #__snackkit_debugger__ .dbg-select-option.active { color: #7aacfa; background: #1e3060; }\n #__snackkit_debugger__ .dbg-select-option.hidden { display: none; }\n #__snackkit_debugger__ .dbg-select-empty {\n padding: 12px 10px;\n font-size: 12px;\n color: #3d5070;\n text-align: center;\n }\n\n /* ── 路由列表 ── */\n #__snackkit_debugger__ .dbg-ctx-field {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 5px;\n }\n #__snackkit_debugger__ .dbg-ctx-wrap {\n flex: 1;\n min-height: 0;\n border: 1px solid #2d3a55;\n border-radius: 6px;\n overflow: hidden;\n background: #252f45;\n display: flex;\n flex-direction: column;\n }\n #__snackkit_debugger__ .dbg-ctx-search {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border-bottom: 1px solid #2d3a55;\n background: #1e2638;\n }\n #__snackkit_debugger__ .dbg-ctx-search input {\n flex: 1;\n background: transparent;\n border: none;\n outline: none;\n color: #b8ccec;\n font-size: 12px;\n font-family: inherit;\n }\n #__snackkit_debugger__ .dbg-ctx-search input::placeholder { color: #3d5070; }\n #__snackkit_debugger__ .dbg-ctx-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar { width: 3px; }\n #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-track { background: transparent; }\n #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; }\n #__snackkit_debugger__ .dbg-ctx-item {\n padding: 5px 10px;\n cursor: pointer;\n display: grid;\n grid-template-columns: minmax(80px, 148px) 1fr auto;\n gap: 8px;\n align-items: center;\n border-bottom: 1px solid #1e2638;\n transition: background 0.1s;\n }\n #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; }\n #__snackkit_debugger__ .dbg-ctx-item:hover { background: #2d3a55; }\n #__snackkit_debugger__ .dbg-ctx-item.hidden { display: none; }\n #__snackkit_debugger__ .dbg-ctx-key {\n font-size: 12px;\n color: #7aacfa;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n #__snackkit_debugger__ .dbg-ctx-val {\n font-size: 11px;\n color: #4d6080;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n transition: color 0.1s;\n }\n #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-val { color: #7a90b0; }\n #__snackkit_debugger__ .dbg-ctx-copy {\n font-size: 11px;\n color: #3d5070;\n opacity: 0;\n flex-shrink: 0;\n transition: opacity 0.1s, color 0.1s;\n }\n #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-copy { opacity: 1; color: #5e72a0; }\n #__snackkit_debugger__ .dbg-ctx-item.copied .dbg-ctx-copy { color: #34d399; opacity: 1; }\n #__snackkit_debugger__ .dbg-ctx-empty {\n padding: 16px 10px;\n font-size: 12px;\n color: #3d5070;\n text-align: center;\n }\n\n /* ── 底部状态栏 ── */\n #__snackkit_debugger__ .dbg-footer {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n background: #171e2e;\n border-top: 1px solid #2d3a55;\n border-radius: 0 0 12px 12px;\n font-size: 11px;\n color: #4d6080;\n min-height: 30px;\n }\n #__snackkit_debugger__ .dbg-footer.ok { color: #34d399; }\n #__snackkit_debugger__ .dbg-footer.err { color: #f87171; }\n #__snackkit_debugger__ .dbg-footer-dot {\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n flex-shrink: 0;\n }\n\n @keyframes dbg-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.35; }\n }\n`\n\n/** 可搜索的自定义下拉组件 */\nclass SearchSelect {\n private el: HTMLElement\n private trigger: HTMLElement\n private valEl: HTMLElement\n private dropdown: HTMLElement\n private searchInput: HTMLInputElement\n private listEl: HTMLElement\n private options: Array<{ value: string; label: string }> = []\n private selected = ''\n private onChange: (value: string) => void\n\n constructor(onChange: (value: string) => void) {\n this.onChange = onChange\n this.el = document.createElement('div')\n this.el.className = 'dbg-select'\n\n // 触发器\n this.trigger = document.createElement('div')\n this.trigger.className = 'dbg-select-trigger'\n this.valEl = document.createElement('span')\n this.valEl.className = 'dbg-select-val placeholder'\n const arrow = document.createElement('span')\n arrow.className = 'dbg-select-arrow'\n arrow.textContent = '▾'\n this.trigger.appendChild(this.valEl)\n this.trigger.appendChild(arrow)\n\n // 下拉面板\n this.dropdown = document.createElement('div')\n this.dropdown.className = 'dbg-select-dropdown'\n\n // 搜索框\n const searchWrap = document.createElement('div')\n searchWrap.className = 'dbg-select-search'\n const searchIcon = document.createElement('span')\n searchIcon.className = 'dbg-select-search-icon'\n searchIcon.textContent = '⌕'\n this.searchInput = document.createElement('input')\n this.searchInput.type = 'text'\n this.searchInput.placeholder = T.search()\n searchWrap.appendChild(searchIcon)\n searchWrap.appendChild(this.searchInput)\n\n this.listEl = document.createElement('div')\n this.listEl.className = 'dbg-select-list'\n\n this.dropdown.appendChild(searchWrap)\n this.dropdown.appendChild(this.listEl)\n this.el.appendChild(this.trigger)\n this.el.appendChild(this.dropdown)\n\n this.bindEvents()\n }\n\n private bindEvents(): void {\n this.trigger.addEventListener('click', (e) => {\n e.stopPropagation()\n const isOpen = this.el.classList.contains('open')\n document.querySelectorAll('#__snackkit_debugger__ .dbg-select.open').forEach((el) => {\n if (el !== this.el) el.classList.remove('open')\n })\n this.el.classList.toggle('open', !isOpen)\n if (!isOpen) {\n this.searchInput.value = ''\n this.filterOptions('')\n setTimeout(() => this.searchInput.focus(), 50)\n }\n })\n\n this.searchInput.addEventListener('input', () => {\n this.filterOptions(this.searchInput.value)\n })\n\n this.searchInput.addEventListener('click', (e) => e.stopPropagation())\n\n document.addEventListener('click', () => {\n this.el.classList.remove('open')\n })\n }\n\n private filterOptions(query: string): void {\n const q = query.toLowerCase()\n let visibleCount = 0\n this.listEl.querySelectorAll<HTMLElement>('.dbg-select-option').forEach((opt) => {\n const match = !q || opt.textContent!.toLowerCase().includes(q)\n opt.classList.toggle('hidden', !match)\n if (match) visibleCount++\n })\n let emptyEl = this.listEl.querySelector<HTMLElement>('.dbg-select-empty')\n if (visibleCount === 0) {\n if (!emptyEl) {\n emptyEl = document.createElement('div')\n emptyEl.className = 'dbg-select-empty'\n emptyEl.textContent = T.noMatch()\n this.listEl.appendChild(emptyEl)\n }\n } else {\n emptyEl?.remove()\n }\n }\n\n /** 更新选项列表 */\n setOptions(options: Array<{ value: string; label: string }>, placeholder: string): void {\n this.options = options\n this.listEl.innerHTML = ''\n this.valEl.textContent = placeholder\n this.valEl.className = 'dbg-select-val placeholder'\n this.selected = ''\n\n options.forEach(({ value, label }) => {\n const opt = document.createElement('div')\n opt.className = 'dbg-select-option'\n opt.dataset['value'] = value\n opt.textContent = label\n opt.setAttribute('title', label)\n opt.addEventListener('click', (e) => {\n e.stopPropagation()\n this.select(value, label)\n this.el.classList.remove('open')\n })\n this.listEl.appendChild(opt)\n })\n }\n\n /** 选中某个值(不触发 onChange) */\n setValue(value: string): void {\n const opt = this.options.find((o) => o.value === value)\n if (opt) this.select(opt.value, opt.label, false)\n }\n\n private select(value: string, label: string, emit = true): void {\n this.selected = value\n this.valEl.textContent = label\n this.valEl.className = 'dbg-select-val'\n this.listEl.querySelectorAll('.dbg-select-option').forEach((el) => {\n el.classList.toggle('active', (el as HTMLElement).dataset['value'] === value)\n })\n if (emit) this.onChange(value)\n }\n\n getValue(): string { return this.selected }\n getElement(): HTMLElement { return this.el }\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 currentCtxMap: Record<string, string> = {}\n private panelEl!: HTMLElement\n private headerDot!: HTMLElement\n private gwVersion!: HTMLElement\n private footerEl!: HTMLElement\n private copyAllBtn!: HTMLButtonElement\n private gwSelect!: SearchSelect\n private typeSelect!: SearchSelect\n private serverSelect!: SearchSelect\n private ctxList!: HTMLElement\n private ctxSearchInput!: HTMLInputElement\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 // Header\n const header = document.createElement('div')\n header.className = 'dbg-header'\n const headerLeft = document.createElement('div')\n headerLeft.className = 'dbg-header-left'\n this.headerDot = document.createElement('span')\n this.headerDot.className = 'dbg-header-dot loading'\n const headerTitle = document.createElement('span')\n headerTitle.className = 'dbg-header-title'\n headerTitle.textContent = 'Snack Kit Debugger'\n headerLeft.appendChild(this.headerDot)\n headerLeft.appendChild(headerTitle)\n header.appendChild(headerLeft)\n panel.appendChild(header)\n\n // Body\n const body = document.createElement('div')\n body.className = 'dbg-body'\n\n // 网关选择(标签行右侧显示版本号)\n this.gwVersion = document.createElement('span')\n this.gwVersion.className = 'dbg-header-meta'\n this.gwSelect = new SearchSelect((val) => this.onGatewayChange(val))\n this.gwSelect.setOptions(this.gateways.map((g) => ({ value: g, label: g })), T.selectGateway())\n body.appendChild(this.createFieldWithAction(T.gateway(), this.gwVersion, this.gwSelect.getElement()))\n\n // 标签(类型)选择\n this.typeSelect = new SearchSelect((val) => this.onTypeChange(val))\n body.appendChild(this.createField(T.tag(), this.typeSelect.getElement()))\n\n // 服务选择\n this.serverSelect = new SearchSelect((val) => this.onServerChange(val))\n body.appendChild(this.createField(T.server(), this.serverSelect.getElement()))\n\n // 路由列表(含一键复制全部 JSON 按钮)\n const ctxWrap = document.createElement('div')\n ctxWrap.className = 'dbg-ctx-wrap'\n\n const ctxSearch = document.createElement('div')\n ctxSearch.className = 'dbg-ctx-search'\n const ctxSearchIcon = document.createElement('span')\n ctxSearchIcon.className = 'dbg-select-search-icon'\n ctxSearchIcon.textContent = '⌕'\n this.ctxSearchInput = document.createElement('input')\n this.ctxSearchInput.type = 'text'\n this.ctxSearchInput.placeholder = T.filterCtx()\n this.ctxSearchInput.addEventListener('input', () => this.filterCtx(this.ctxSearchInput.value))\n ctxSearch.appendChild(ctxSearchIcon)\n ctxSearch.appendChild(this.ctxSearchInput)\n\n this.ctxList = document.createElement('div')\n this.ctxList.className = 'dbg-ctx-list'\n\n ctxWrap.appendChild(ctxSearch)\n ctxWrap.appendChild(this.ctxList)\n\n // 一键复制全部 JSON 按钮\n this.copyAllBtn = document.createElement('button')\n this.copyAllBtn.className = 'dbg-copy-all'\n this.copyAllBtn.textContent = T.copyAll()\n this.copyAllBtn.setAttribute('title', isChinese()\n ? '将全部上下文复制为 JSON,可作为 Context.load({...}) 的参数'\n : 'Copy all ctx entries as JSON for Context.load({...})')\n this.copyAllBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n this.copyAllCtx()\n })\n\n const ctxField = this.createFieldWithAction(T.ctxLabel(), this.copyAllBtn, ctxWrap)\n ctxField.classList.add('dbg-ctx-field')\n body.appendChild(ctxField)\n\n panel.appendChild(body)\n\n // Footer 状态栏\n const footer = document.createElement('div')\n footer.className = 'dbg-footer'\n this.footerEl = footer\n panel.appendChild(footer)\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', (e) => {\n e.stopPropagation()\n panel.classList.toggle('open')\n })\n\n root.appendChild(panel)\n root.appendChild(toggle)\n document.body.appendChild(root)\n }\n\n /** 创建普通字段行(label + content) */\n private createField(label: string, content: HTMLElement): HTMLElement {\n const field = document.createElement('div')\n field.className = 'dbg-field'\n const lbl = document.createElement('div')\n lbl.className = 'dbg-label'\n lbl.textContent = label\n field.appendChild(lbl)\n field.appendChild(content)\n return field\n }\n\n /** 创建带操作按钮的字段行(label + action button + content) */\n private createFieldWithAction(label: string, action: HTMLElement, content: HTMLElement): HTMLElement {\n const field = document.createElement('div')\n field.className = 'dbg-field'\n const fieldHeader = document.createElement('div')\n fieldHeader.className = 'dbg-field-header'\n const lbl = document.createElement('div')\n lbl.className = 'dbg-label'\n lbl.textContent = label\n fieldHeader.appendChild(lbl)\n fieldHeader.appendChild(action)\n field.appendChild(fieldHeader)\n field.appendChild(content)\n return field\n }\n\n /** 恢复上次持久化的状态 */\n private async restoreState(): Promise<void> {\n const savedGw = localStorage.getItem(KEY_GATEWAY)\n const gw = (savedGw && this.gateways.includes(savedGw)) ? savedGw : this.gateways[0]\n this.gwSelect.setValue(gw)\n await this.loadServers(gw)\n\n const savedType = localStorage.getItem(KEY_TYPE)\n if (savedType) {\n this.typeSelect.setValue(savedType)\n this.renderServerOptions(savedType)\n }\n\n const savedServer = localStorage.getItem(KEY_SERVER)\n if (savedServer && this.servers.find((s) => s.key === savedServer)) {\n this.serverSelect.setValue(savedServer)\n await this.applyServer(savedServer)\n }\n }\n\n private async onGatewayChange(gw: string): Promise<void> {\n localStorage.setItem(KEY_GATEWAY, gw)\n this.gwVersion.textContent = ''\n await this.loadServers(gw)\n }\n\n private onTypeChange(type: string): void {\n localStorage.setItem(KEY_TYPE, type)\n this.renderServerOptions(type)\n }\n\n private async onServerChange(key: string): Promise<void> {\n localStorage.setItem(KEY_SERVER, key)\n await this.applyServer(key)\n }\n\n /** 从网关加载服务列表 */\n private async loadServers(gwUrl: string): Promise<void> {\n this.setHeader('loading')\n this.setFooter(T.loading(), '')\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.setHeader('err')\n this.setFooter(T.loadFailed(result.error.message), 'err')\n return\n }\n\n this.servers = result.data ?? []\n this.renderTypeOptions()\n this.setHeader('ok')\n this.setFooter(T.loaded(this.servers.length), 'ok')\n }\n\n /** 渲染标签选项 */\n private renderTypeOptions(): void {\n const types = [...new Set(this.servers.map((s) => s.type).filter(Boolean))]\n const opts = [{ value: '', label: T.allTags() }, ...types.map((t) => ({ value: t, label: t }))]\n this.typeSelect.setOptions(opts, T.allTags())\n this.typeSelect.setValue('')\n this.renderServerOptions('')\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.setOptions(\n filtered.map((s) => ({ value: s.key, label: `${s.name} (${s.key})` })),\n T.selectServer(),\n )\n this.renderCtxList({})\n }\n\n /** 切换目标服务,重新加载 Context 路由映射 */\n private async applyServer(key: string): Promise<void> {\n if (!key) { this.renderCtxList({}); return }\n const server = this.servers.find((s) => s.key === key)\n if (!server) return\n\n this.setFooter(T.switching(), '')\n try {\n await Context.load(server.origin, this.options.timeout)\n const res = await fetch(`${server.origin}/ngw/context`)\n const raw: Record<string, unknown> = res.ok ? await res.json() : {}\n const ctxMap: Record<string, string> = {}\n for (const [k, v] of Object.entries(raw)) {\n if (k !== '$info' && typeof v === 'string') ctxMap[k] = v\n }\n const info = Context.info\n if (info) this.gwVersion.textContent = `v${info.version}`\n this.renderCtxList(ctxMap)\n const count = Object.keys(ctxMap).length\n this.setFooter(T.routes(server.name, count), 'ok')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n this.setFooter(T.switchFailed(msg), 'err')\n }\n }\n\n /** 渲染路由列表 */\n private renderCtxList(ctxMap: Record<string, string>): void {\n this.currentCtxMap = ctxMap\n this.ctxList.innerHTML = ''\n this.ctxSearchInput.value = ''\n const entries = Object.entries(ctxMap)\n if (entries.length === 0) {\n const empty = document.createElement('div')\n empty.className = 'dbg-ctx-empty'\n empty.textContent = T.selectFirst()\n this.ctxList.appendChild(empty)\n return\n }\n entries.forEach(([k, v]) => {\n const item = document.createElement('div')\n item.className = 'dbg-ctx-item'\n item.dataset['key'] = k\n\n const keySpan = document.createElement('span')\n keySpan.className = 'dbg-ctx-key'\n keySpan.textContent = k\n keySpan.setAttribute('title', k)\n\n const valSpan = document.createElement('span')\n valSpan.className = 'dbg-ctx-val'\n valSpan.textContent = v\n valSpan.setAttribute('title', `${k} → ${v}`)\n\n const copyIcon = document.createElement('span')\n copyIcon.className = 'dbg-ctx-copy'\n copyIcon.textContent = '⎘'\n\n item.appendChild(keySpan)\n item.appendChild(valSpan)\n item.appendChild(copyIcon)\n item.addEventListener('click', () => this.copyCtxItem(item, k))\n this.ctxList.appendChild(item)\n })\n }\n\n /** 过滤路由列表 */\n private filterCtx(query: string): void {\n const q = query.toLowerCase()\n this.ctxList.querySelectorAll<HTMLElement>('.dbg-ctx-item').forEach((item) => {\n const key = item.dataset['key'] ?? ''\n item.classList.toggle('hidden', !!q && !key.toLowerCase().includes(q))\n })\n }\n\n /** 复制单个 ctx key */\n private copyCtxItem(item: HTMLElement, text: string): void {\n navigator.clipboard.writeText(text).then(() => {\n item.classList.add('copied')\n const icon = item.querySelector('.dbg-ctx-copy')\n if (icon) icon.textContent = '✓'\n setTimeout(() => {\n item.classList.remove('copied')\n const ic = item.querySelector('.dbg-ctx-copy')\n if (ic) ic.textContent = '⎘'\n }, 1500)\n this.setFooter(T.copied(text), 'ok')\n setTimeout(() => this.setFooter('', ''), 2000)\n })\n }\n\n /** 一键复制全部上下文为 JSON */\n private copyAllCtx(): void {\n const entries = Object.keys(this.currentCtxMap)\n if (entries.length === 0) return\n const json = JSON.stringify(this.currentCtxMap, null, 2)\n navigator.clipboard.writeText(json).then(() => {\n this.copyAllBtn.textContent = '✓ ' + T.copyAllDone()\n this.copyAllBtn.classList.add('done')\n setTimeout(() => {\n this.copyAllBtn.textContent = T.copyAll()\n this.copyAllBtn.classList.remove('done')\n }, 2000)\n this.setFooter(T.copyAllDone(), 'ok')\n setTimeout(() => this.setFooter('', ''), 2500)\n })\n }\n\n private setHeader(state: 'ok' | 'err' | 'loading'): void {\n this.headerDot.className = `dbg-header-dot${state !== 'ok' ? ` ${state}` : ''}`\n }\n\n private setFooter(msg: string, cls: '' | 'ok' | 'err'): void {\n this.footerEl.innerHTML = msg\n ? `<span class=\"dbg-footer-dot\"></span>${msg}`\n : ''\n this.footerEl.className = `dbg-footer${cls ? ` ${cls}` : ''}`\n }\n}\n"]} |
| import { Get, Context } from './chunk-JQYH5FWE.js'; | ||
| // src/debugger/debugger.ts | ||
| var STORAGE_PREFIX = "__snackkit_debugger__"; | ||
| var KEY_GATEWAY = `${STORAGE_PREFIX}gateway`; | ||
| var KEY_TYPE = `${STORAGE_PREFIX}type`; | ||
| var KEY_SERVER = `${STORAGE_PREFIX}server`; | ||
| function isChinese() { | ||
| return /^zh/i.test(navigator.language ?? ""); | ||
| } | ||
| var T = { | ||
| gateway: () => isChinese() ? "\u7F51\u5173" : "Gateway", | ||
| tag: () => isChinese() ? "\u6807\u7B7E" : "Tag", | ||
| server: () => isChinese() ? "\u670D\u52A1" : "Server", | ||
| ctx: () => isChinese() ? "\u4E0A\u4E0B\u6587" : "Ctx", | ||
| ctxLabel: () => isChinese() ? "\u4E0A\u4E0B\u6587 \u2014 \u70B9\u51FB\u884C\u590D\u5236 key" : "Ctx \u2014 click row to copy key", | ||
| search: () => isChinese() ? "\u641C\u7D22..." : "Search...", | ||
| noMatch: () => isChinese() ? "\u65E0\u5339\u914D\u9879" : "No results", | ||
| allTags: () => isChinese() ? "\u5168\u90E8\u6807\u7B7E" : "All tags", | ||
| selectGateway: () => isChinese() ? "\u9009\u62E9\u7F51\u5173" : "Select gateway", | ||
| selectServer: () => isChinese() ? "\u9009\u62E9\u670D\u52A1" : "Select server", | ||
| filterCtx: () => isChinese() ? "\u8FC7\u6EE4\u8DEF\u7531..." : "Filter routes...", | ||
| selectFirst: () => isChinese() ? "\u2190 \u8BF7\u5148\u9009\u62E9\u670D\u52A1" : "\u2190 Select a server first", | ||
| loading: () => isChinese() ? "\u52A0\u8F7D\u670D\u52A1\u5217\u8868..." : "Loading...", | ||
| loadFailed: (m) => isChinese() ? `\u52A0\u8F7D\u5931\u8D25: ${m}` : `Failed: ${m}`, | ||
| loaded: (n) => isChinese() ? `\u5DF2\u52A0\u8F7D ${n} \u4E2A\u670D\u52A1` : `${n} servers loaded`, | ||
| switching: () => isChinese() ? "\u5207\u6362\u670D\u52A1\u4E2D..." : "Switching...", | ||
| switchFailed: (m) => isChinese() ? `\u5207\u6362\u5931\u8D25: ${m}` : `Failed: ${m}`, | ||
| routes: (name, n) => isChinese() ? `${name} \xB7 ${n} \u6761\u8DEF\u7531` : `${name} \xB7 ${n} routes`, | ||
| copied: (k) => isChinese() ? `\u5DF2\u590D\u5236: ${k}` : `Copied: ${k}`, | ||
| copyAll: () => isChinese() ? "\u590D\u5236\u5168\u90E8 JSON" : "Copy all JSON", | ||
| copyAllDone: () => isChinese() ? "\u5DF2\u590D\u5236\u4E3A JSON" : "Copied as JSON" | ||
| }; | ||
| var PANEL_STYLE = ` | ||
| #__snackkit_debugger__ { | ||
| position: fixed; | ||
| bottom: 20px; | ||
| right: 20px; | ||
| z-index: 99999; | ||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif; | ||
| font-size: 13px; | ||
| color: #b8ccec; | ||
| user-select: none; | ||
| } | ||
| /* \u2500\u2500 \u6D6E\u52A8\u6309\u94AE \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-toggle { | ||
| width: 42px; | ||
| height: 42px; | ||
| border-radius: 50%; | ||
| background: linear-gradient(135deg, #4f8ef7, #6d6ff5); | ||
| border: none; | ||
| cursor: pointer; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| box-shadow: 0 4px 14px rgba(79,142,247,0.45); | ||
| margin-left: auto; | ||
| font-size: 20px; | ||
| transition: transform 0.15s, box-shadow 0.15s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-toggle:hover { | ||
| transform: scale(1.08); | ||
| box-shadow: 0 6px 20px rgba(79,142,247,0.55); | ||
| } | ||
| #__snackkit_debugger__ .dbg-toggle:active { transform: scale(0.94); } | ||
| /* \u2500\u2500 \u9762\u677F\u4E3B\u4F53 \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-panel { | ||
| background: #1e2638; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 12px; | ||
| width: 440px; | ||
| margin-bottom: 10px; | ||
| box-shadow: 0 8px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.05); | ||
| overflow: visible; | ||
| opacity: 0; | ||
| transform: translateY(8px) scale(0.98); | ||
| pointer-events: none; | ||
| transition: opacity 0.18s ease, transform 0.18s ease; | ||
| } | ||
| #__snackkit_debugger__ .dbg-panel.open { | ||
| opacity: 1; | ||
| transform: translateY(0) scale(1); | ||
| pointer-events: auto; | ||
| } | ||
| /* \u2500\u2500 \u9762\u677F Header \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-header { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| padding: 10px 14px; | ||
| background: #171e2e; | ||
| border-bottom: 1px solid #2d3a55; | ||
| border-radius: 12px 12px 0 0; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-left { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 7px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-dot { | ||
| width: 7px; | ||
| height: 7px; | ||
| border-radius: 50%; | ||
| background: #34d399; | ||
| box-shadow: 0 0 6px rgba(52,211,153,0.6); | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-dot.loading { | ||
| background: #fbbf24; | ||
| box-shadow: 0 0 6px rgba(251,191,36,0.6); | ||
| animation: dbg-pulse 1s ease-in-out infinite; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-dot.err { | ||
| background: #f87171; | ||
| box-shadow: 0 0 6px rgba(248,113,113,0.6); | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-title { | ||
| font-size: 12px; | ||
| font-weight: 600; | ||
| color: #dce8fa; | ||
| letter-spacing: 0.03em; | ||
| } | ||
| #__snackkit_debugger__ .dbg-header-meta { | ||
| font-size: 11px; | ||
| color: #4d6080; | ||
| } | ||
| /* \u2500\u2500 \u9762\u677F\u5185\u5BB9\u533A \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-body { | ||
| padding: 12px 14px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 10px; | ||
| height: 420px; | ||
| overflow: visible; | ||
| } | ||
| /* \u2500\u2500 \u5B57\u6BB5\u884C \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-field { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 5px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-field-header { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| } | ||
| #__snackkit_debugger__ .dbg-label { | ||
| font-size: 10px; | ||
| font-weight: 600; | ||
| color: #5e72a0; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.07em; | ||
| } | ||
| /* \u2500\u2500 \u4E00\u952E\u590D\u5236\u5168\u90E8 JSON \u6309\u94AE \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-copy-all { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 4px; | ||
| background: transparent; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 4px; | ||
| padding: 2px 7px; | ||
| color: #5e72a0; | ||
| font-size: 10px; | ||
| font-family: inherit; | ||
| cursor: pointer; | ||
| transition: border-color 0.15s, color 0.15s, background 0.15s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-copy-all:hover { | ||
| border-color: #4f8ef7; | ||
| color: #7aacfa; | ||
| background: rgba(79,142,247,0.08); | ||
| } | ||
| #__snackkit_debugger__ .dbg-copy-all.done { | ||
| border-color: #34d399; | ||
| color: #34d399; | ||
| } | ||
| /* \u2500\u2500 \u81EA\u5B9A\u4E49\u4E0B\u62C9 \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-select { | ||
| position: relative; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-trigger { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| gap: 6px; | ||
| background: #252f45; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 6px; | ||
| padding: 6px 10px; | ||
| cursor: pointer; | ||
| transition: border-color 0.15s, background 0.15s; | ||
| min-height: 32px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-trigger:hover { border-color: #3d5070; background: #2d3a55; } | ||
| #__snackkit_debugger__ .dbg-select.open .dbg-select-trigger { | ||
| border-color: #4f8ef7; | ||
| background: #2d3a55; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-val { | ||
| flex: 1; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| font-size: 12px; | ||
| color: #b8ccec; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-val.placeholder { color: #3d5070; } | ||
| #__snackkit_debugger__ .dbg-select-arrow { | ||
| color: #3d5070; | ||
| font-size: 10px; | ||
| flex-shrink: 0; | ||
| transition: transform 0.15s, color 0.15s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select.open .dbg-select-arrow { | ||
| transform: rotate(180deg); | ||
| color: #4f8ef7; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-dropdown { | ||
| position: absolute; | ||
| bottom: calc(100% + 4px); | ||
| left: 0; | ||
| right: 0; | ||
| background: #1e2638; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 8px; | ||
| box-shadow: 0 -8px 24px rgba(0,0,0,0.35); | ||
| overflow: hidden; | ||
| opacity: 0; | ||
| transform: translateY(4px); | ||
| pointer-events: none; | ||
| transition: opacity 0.14s, transform 0.14s; | ||
| z-index: 100000; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select.open .dbg-select-dropdown { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| pointer-events: auto; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-search { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| padding: 7px 10px; | ||
| border-bottom: 1px solid #2d3a55; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-search-icon { color: #3d5070; font-size: 11px; flex-shrink: 0; } | ||
| #__snackkit_debugger__ .dbg-select-search input { | ||
| flex: 1; | ||
| background: transparent; | ||
| border: none; | ||
| outline: none; | ||
| color: #b8ccec; | ||
| font-size: 12px; | ||
| font-family: inherit; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-search input::placeholder { color: #3d5070; } | ||
| #__snackkit_debugger__ .dbg-select-list { | ||
| max-height: 160px; | ||
| overflow-y: auto; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar { width: 3px; } | ||
| #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-track { background: transparent; } | ||
| #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; } | ||
| #__snackkit_debugger__ .dbg-select-option { | ||
| padding: 7px 10px; | ||
| cursor: pointer; | ||
| font-size: 12px; | ||
| color: #8aa4c8; | ||
| transition: background 0.1s, color 0.1s; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| } | ||
| #__snackkit_debugger__ .dbg-select-option:hover { background: #2d3a55; color: #dce8fa; } | ||
| #__snackkit_debugger__ .dbg-select-option.active { color: #7aacfa; background: #1e3060; } | ||
| #__snackkit_debugger__ .dbg-select-option.hidden { display: none; } | ||
| #__snackkit_debugger__ .dbg-select-empty { | ||
| padding: 12px 10px; | ||
| font-size: 12px; | ||
| color: #3d5070; | ||
| text-align: center; | ||
| } | ||
| /* \u2500\u2500 \u8DEF\u7531\u5217\u8868 \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-ctx-field { | ||
| flex: 1; | ||
| min-height: 0; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 5px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-wrap { | ||
| flex: 1; | ||
| min-height: 0; | ||
| border: 1px solid #2d3a55; | ||
| border-radius: 6px; | ||
| overflow: hidden; | ||
| background: #252f45; | ||
| display: flex; | ||
| flex-direction: column; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-search { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| padding: 6px 10px; | ||
| border-bottom: 1px solid #2d3a55; | ||
| background: #1e2638; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-search input { | ||
| flex: 1; | ||
| background: transparent; | ||
| border: none; | ||
| outline: none; | ||
| color: #b8ccec; | ||
| font-size: 12px; | ||
| font-family: inherit; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-search input::placeholder { color: #3d5070; } | ||
| #__snackkit_debugger__ .dbg-ctx-list { | ||
| flex: 1; | ||
| min-height: 0; | ||
| overflow-y: auto; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar { width: 3px; } | ||
| #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-track { background: transparent; } | ||
| #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; } | ||
| #__snackkit_debugger__ .dbg-ctx-item { | ||
| padding: 5px 10px; | ||
| cursor: pointer; | ||
| display: grid; | ||
| grid-template-columns: minmax(80px, 148px) 1fr auto; | ||
| gap: 8px; | ||
| align-items: center; | ||
| border-bottom: 1px solid #1e2638; | ||
| transition: background 0.1s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover { background: #2d3a55; } | ||
| #__snackkit_debugger__ .dbg-ctx-item.hidden { display: none; } | ||
| #__snackkit_debugger__ .dbg-ctx-key { | ||
| font-size: 12px; | ||
| color: #7aacfa; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-val { | ||
| font-size: 11px; | ||
| color: #4d6080; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| transition: color 0.1s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-val { color: #7a90b0; } | ||
| #__snackkit_debugger__ .dbg-ctx-copy { | ||
| font-size: 11px; | ||
| color: #3d5070; | ||
| opacity: 0; | ||
| flex-shrink: 0; | ||
| transition: opacity 0.1s, color 0.1s; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-copy { opacity: 1; color: #5e72a0; } | ||
| #__snackkit_debugger__ .dbg-ctx-item.copied .dbg-ctx-copy { color: #34d399; opacity: 1; } | ||
| #__snackkit_debugger__ .dbg-ctx-empty { | ||
| padding: 16px 10px; | ||
| font-size: 12px; | ||
| color: #3d5070; | ||
| text-align: center; | ||
| } | ||
| /* \u2500\u2500 \u5E95\u90E8\u72B6\u6001\u680F \u2500\u2500 */ | ||
| #__snackkit_debugger__ .dbg-footer { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| padding: 7px 14px; | ||
| background: #171e2e; | ||
| border-top: 1px solid #2d3a55; | ||
| border-radius: 0 0 12px 12px; | ||
| font-size: 11px; | ||
| color: #4d6080; | ||
| min-height: 30px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-footer.ok { color: #34d399; } | ||
| #__snackkit_debugger__ .dbg-footer.err { color: #f87171; } | ||
| #__snackkit_debugger__ .dbg-footer-dot { | ||
| width: 4px; | ||
| height: 4px; | ||
| border-radius: 50%; | ||
| background: currentColor; | ||
| flex-shrink: 0; | ||
| } | ||
| @keyframes dbg-pulse { | ||
| 0%, 100% { opacity: 1; } | ||
| 50% { opacity: 0.35; } | ||
| } | ||
| `; | ||
| var SearchSelect = class { | ||
| constructor(onChange) { | ||
| this.options = []; | ||
| this.selected = ""; | ||
| this.onChange = onChange; | ||
| this.el = document.createElement("div"); | ||
| this.el.className = "dbg-select"; | ||
| this.trigger = document.createElement("div"); | ||
| this.trigger.className = "dbg-select-trigger"; | ||
| this.valEl = document.createElement("span"); | ||
| this.valEl.className = "dbg-select-val placeholder"; | ||
| const arrow = document.createElement("span"); | ||
| arrow.className = "dbg-select-arrow"; | ||
| arrow.textContent = "\u25BE"; | ||
| this.trigger.appendChild(this.valEl); | ||
| this.trigger.appendChild(arrow); | ||
| this.dropdown = document.createElement("div"); | ||
| this.dropdown.className = "dbg-select-dropdown"; | ||
| const searchWrap = document.createElement("div"); | ||
| searchWrap.className = "dbg-select-search"; | ||
| const searchIcon = document.createElement("span"); | ||
| searchIcon.className = "dbg-select-search-icon"; | ||
| searchIcon.textContent = "\u2315"; | ||
| this.searchInput = document.createElement("input"); | ||
| this.searchInput.type = "text"; | ||
| this.searchInput.placeholder = T.search(); | ||
| searchWrap.appendChild(searchIcon); | ||
| searchWrap.appendChild(this.searchInput); | ||
| this.listEl = document.createElement("div"); | ||
| this.listEl.className = "dbg-select-list"; | ||
| this.dropdown.appendChild(searchWrap); | ||
| this.dropdown.appendChild(this.listEl); | ||
| this.el.appendChild(this.trigger); | ||
| this.el.appendChild(this.dropdown); | ||
| this.bindEvents(); | ||
| } | ||
| bindEvents() { | ||
| this.trigger.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| const isOpen = this.el.classList.contains("open"); | ||
| document.querySelectorAll("#__snackkit_debugger__ .dbg-select.open").forEach((el) => { | ||
| if (el !== this.el) el.classList.remove("open"); | ||
| }); | ||
| this.el.classList.toggle("open", !isOpen); | ||
| if (!isOpen) { | ||
| this.searchInput.value = ""; | ||
| this.filterOptions(""); | ||
| setTimeout(() => this.searchInput.focus(), 50); | ||
| } | ||
| }); | ||
| this.searchInput.addEventListener("input", () => { | ||
| this.filterOptions(this.searchInput.value); | ||
| }); | ||
| this.searchInput.addEventListener("click", (e) => e.stopPropagation()); | ||
| document.addEventListener("click", () => { | ||
| this.el.classList.remove("open"); | ||
| }); | ||
| } | ||
| filterOptions(query) { | ||
| const q = query.toLowerCase(); | ||
| let visibleCount = 0; | ||
| this.listEl.querySelectorAll(".dbg-select-option").forEach((opt) => { | ||
| const match = !q || opt.textContent.toLowerCase().includes(q); | ||
| opt.classList.toggle("hidden", !match); | ||
| if (match) visibleCount++; | ||
| }); | ||
| let emptyEl = this.listEl.querySelector(".dbg-select-empty"); | ||
| if (visibleCount === 0) { | ||
| if (!emptyEl) { | ||
| emptyEl = document.createElement("div"); | ||
| emptyEl.className = "dbg-select-empty"; | ||
| emptyEl.textContent = T.noMatch(); | ||
| this.listEl.appendChild(emptyEl); | ||
| } | ||
| } else { | ||
| emptyEl?.remove(); | ||
| } | ||
| } | ||
| /** 更新选项列表 */ | ||
| setOptions(options, placeholder) { | ||
| this.options = options; | ||
| this.listEl.innerHTML = ""; | ||
| this.valEl.textContent = placeholder; | ||
| this.valEl.className = "dbg-select-val placeholder"; | ||
| this.selected = ""; | ||
| options.forEach(({ value, label }) => { | ||
| const opt = document.createElement("div"); | ||
| opt.className = "dbg-select-option"; | ||
| opt.dataset["value"] = value; | ||
| opt.textContent = label; | ||
| opt.setAttribute("title", label); | ||
| opt.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| this.select(value, label); | ||
| this.el.classList.remove("open"); | ||
| }); | ||
| this.listEl.appendChild(opt); | ||
| }); | ||
| } | ||
| /** 选中某个值(不触发 onChange) */ | ||
| setValue(value) { | ||
| const opt = this.options.find((o) => o.value === value); | ||
| if (opt) this.select(opt.value, opt.label, false); | ||
| } | ||
| select(value, label, emit = true) { | ||
| this.selected = value; | ||
| this.valEl.textContent = label; | ||
| this.valEl.className = "dbg-select-val"; | ||
| this.listEl.querySelectorAll(".dbg-select-option").forEach((el) => { | ||
| el.classList.toggle("active", el.dataset["value"] === value); | ||
| }); | ||
| if (emit) this.onChange(value); | ||
| } | ||
| getValue() { | ||
| return this.selected; | ||
| } | ||
| getElement() { | ||
| return this.el; | ||
| } | ||
| }; | ||
| var Debugger = class _Debugger { | ||
| constructor(options) { | ||
| this.servers = []; | ||
| this.currentCtxMap = {}; | ||
| this.options = options; | ||
| this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways]; | ||
| } | ||
| /** | ||
| * 初始化并渲染调试面板 | ||
| * | ||
| * 调用后在页面右下角插入浮动按钮,点击展开调试面板。 | ||
| * 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。 | ||
| * 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。 | ||
| * | ||
| * @param options 配置项 | ||
| * | ||
| * @example 单网关 | ||
| * ```ts | ||
| * import { Debugger } from '@snack-kit/lib/debugger' | ||
| * | ||
| * await Debugger.init({ | ||
| * gateways: 'http://dev-gateway.example.com', | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 多网关(开发/测试环境切换) | ||
| * ```ts | ||
| * await Debugger.init({ | ||
| * gateways: [ | ||
| * 'http://dev-gateway.example.com', | ||
| * 'http://test-gateway.example.com', | ||
| * ], | ||
| * timeout: 5000, | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 仅在非生产环境加载 | ||
| * ```ts | ||
| * if (import.meta.env.DEV) { | ||
| * const { Debugger } = await import('@snack-kit/lib/debugger') | ||
| * await Debugger.init({ gateways: 'http://dev-gateway.example.com' }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| static async init(options) { | ||
| const instance = new _Debugger({ timeout: 1e4, ...options }); | ||
| instance.injectStyle(); | ||
| instance.buildDOM(); | ||
| await instance.restoreState(); | ||
| return instance; | ||
| } | ||
| /** 注入内联样式 */ | ||
| injectStyle() { | ||
| if (document.getElementById("__snackkit_debugger_style__")) return; | ||
| const style = document.createElement("style"); | ||
| style.id = "__snackkit_debugger_style__"; | ||
| style.textContent = PANEL_STYLE; | ||
| document.head.appendChild(style); | ||
| } | ||
| /** 构建 DOM 结构 */ | ||
| buildDOM() { | ||
| const root = document.createElement("div"); | ||
| root.id = "__snackkit_debugger__"; | ||
| const panel = document.createElement("div"); | ||
| panel.className = "dbg-panel"; | ||
| this.panelEl = panel; | ||
| const header = document.createElement("div"); | ||
| header.className = "dbg-header"; | ||
| const headerLeft = document.createElement("div"); | ||
| headerLeft.className = "dbg-header-left"; | ||
| this.headerDot = document.createElement("span"); | ||
| this.headerDot.className = "dbg-header-dot loading"; | ||
| const headerTitle = document.createElement("span"); | ||
| headerTitle.className = "dbg-header-title"; | ||
| headerTitle.textContent = "Snack Kit Debugger"; | ||
| headerLeft.appendChild(this.headerDot); | ||
| headerLeft.appendChild(headerTitle); | ||
| header.appendChild(headerLeft); | ||
| panel.appendChild(header); | ||
| const body = document.createElement("div"); | ||
| body.className = "dbg-body"; | ||
| this.gwVersion = document.createElement("span"); | ||
| this.gwVersion.className = "dbg-header-meta"; | ||
| this.gwSelect = new SearchSelect((val) => this.onGatewayChange(val)); | ||
| this.gwSelect.setOptions(this.gateways.map((g) => ({ value: g, label: g })), T.selectGateway()); | ||
| body.appendChild(this.createFieldWithAction(T.gateway(), this.gwVersion, this.gwSelect.getElement())); | ||
| this.typeSelect = new SearchSelect((val) => this.onTypeChange(val)); | ||
| body.appendChild(this.createField(T.tag(), this.typeSelect.getElement())); | ||
| this.serverSelect = new SearchSelect((val) => this.onServerChange(val)); | ||
| body.appendChild(this.createField(T.server(), this.serverSelect.getElement())); | ||
| const ctxWrap = document.createElement("div"); | ||
| ctxWrap.className = "dbg-ctx-wrap"; | ||
| const ctxSearch = document.createElement("div"); | ||
| ctxSearch.className = "dbg-ctx-search"; | ||
| const ctxSearchIcon = document.createElement("span"); | ||
| ctxSearchIcon.className = "dbg-select-search-icon"; | ||
| ctxSearchIcon.textContent = "\u2315"; | ||
| this.ctxSearchInput = document.createElement("input"); | ||
| this.ctxSearchInput.type = "text"; | ||
| this.ctxSearchInput.placeholder = T.filterCtx(); | ||
| this.ctxSearchInput.addEventListener("input", () => this.filterCtx(this.ctxSearchInput.value)); | ||
| ctxSearch.appendChild(ctxSearchIcon); | ||
| ctxSearch.appendChild(this.ctxSearchInput); | ||
| this.ctxList = document.createElement("div"); | ||
| this.ctxList.className = "dbg-ctx-list"; | ||
| ctxWrap.appendChild(ctxSearch); | ||
| ctxWrap.appendChild(this.ctxList); | ||
| this.copyAllBtn = document.createElement("button"); | ||
| this.copyAllBtn.className = "dbg-copy-all"; | ||
| this.copyAllBtn.textContent = T.copyAll(); | ||
| 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({...})"); | ||
| this.copyAllBtn.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| this.copyAllCtx(); | ||
| }); | ||
| const ctxField = this.createFieldWithAction(T.ctxLabel(), this.copyAllBtn, ctxWrap); | ||
| ctxField.classList.add("dbg-ctx-field"); | ||
| body.appendChild(ctxField); | ||
| panel.appendChild(body); | ||
| const footer = document.createElement("div"); | ||
| footer.className = "dbg-footer"; | ||
| this.footerEl = footer; | ||
| panel.appendChild(footer); | ||
| const toggle = document.createElement("button"); | ||
| toggle.className = "dbg-toggle"; | ||
| toggle.textContent = "\u{1F41B}"; | ||
| toggle.setAttribute("title", "Snack Debugger"); | ||
| toggle.addEventListener("click", (e) => { | ||
| e.stopPropagation(); | ||
| panel.classList.toggle("open"); | ||
| }); | ||
| root.appendChild(panel); | ||
| root.appendChild(toggle); | ||
| document.body.appendChild(root); | ||
| } | ||
| /** 创建普通字段行(label + content) */ | ||
| createField(label, content) { | ||
| const field = document.createElement("div"); | ||
| field.className = "dbg-field"; | ||
| const lbl = document.createElement("div"); | ||
| lbl.className = "dbg-label"; | ||
| lbl.textContent = label; | ||
| field.appendChild(lbl); | ||
| field.appendChild(content); | ||
| return field; | ||
| } | ||
| /** 创建带操作按钮的字段行(label + action button + content) */ | ||
| createFieldWithAction(label, action, content) { | ||
| const field = document.createElement("div"); | ||
| field.className = "dbg-field"; | ||
| const fieldHeader = document.createElement("div"); | ||
| fieldHeader.className = "dbg-field-header"; | ||
| const lbl = document.createElement("div"); | ||
| lbl.className = "dbg-label"; | ||
| lbl.textContent = label; | ||
| fieldHeader.appendChild(lbl); | ||
| fieldHeader.appendChild(action); | ||
| field.appendChild(fieldHeader); | ||
| field.appendChild(content); | ||
| return field; | ||
| } | ||
| /** 恢复上次持久化的状态 */ | ||
| async restoreState() { | ||
| const savedGw = localStorage.getItem(KEY_GATEWAY); | ||
| const gw = savedGw && this.gateways.includes(savedGw) ? savedGw : this.gateways[0]; | ||
| this.gwSelect.setValue(gw); | ||
| await this.loadServers(gw); | ||
| const savedType = localStorage.getItem(KEY_TYPE); | ||
| if (savedType) { | ||
| this.typeSelect.setValue(savedType); | ||
| this.renderServerOptions(savedType); | ||
| } | ||
| const savedServer = localStorage.getItem(KEY_SERVER); | ||
| if (savedServer && this.servers.find((s) => s.key === savedServer)) { | ||
| this.serverSelect.setValue(savedServer); | ||
| await this.applyServer(savedServer); | ||
| } | ||
| } | ||
| async onGatewayChange(gw) { | ||
| localStorage.setItem(KEY_GATEWAY, gw); | ||
| this.gwVersion.textContent = ""; | ||
| await this.loadServers(gw); | ||
| } | ||
| onTypeChange(type) { | ||
| localStorage.setItem(KEY_TYPE, type); | ||
| this.renderServerOptions(type); | ||
| } | ||
| async onServerChange(key) { | ||
| localStorage.setItem(KEY_SERVER, key); | ||
| await this.applyServer(key); | ||
| } | ||
| /** 从网关加载服务列表 */ | ||
| async loadServers(gwUrl) { | ||
| this.setHeader("loading"); | ||
| this.setFooter(T.loading(), ""); | ||
| const result = await Get(`${gwUrl}/web-debug/host/list`, { | ||
| cache: true, | ||
| timeout: this.options.timeout | ||
| }); | ||
| if (result.error) { | ||
| this.setHeader("err"); | ||
| this.setFooter(T.loadFailed(result.error.message), "err"); | ||
| return; | ||
| } | ||
| this.servers = result.data ?? []; | ||
| this.renderTypeOptions(); | ||
| this.setHeader("ok"); | ||
| this.setFooter(T.loaded(this.servers.length), "ok"); | ||
| } | ||
| /** 渲染标签选项 */ | ||
| renderTypeOptions() { | ||
| const types = [...new Set(this.servers.map((s) => s.type).filter(Boolean))]; | ||
| const opts = [{ value: "", label: T.allTags() }, ...types.map((t) => ({ value: t, label: t }))]; | ||
| this.typeSelect.setOptions(opts, T.allTags()); | ||
| this.typeSelect.setValue(""); | ||
| this.renderServerOptions(""); | ||
| } | ||
| /** 根据标签渲染服务选项 */ | ||
| renderServerOptions(type) { | ||
| const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers; | ||
| this.serverSelect.setOptions( | ||
| filtered.map((s) => ({ value: s.key, label: `${s.name} (${s.key})` })), | ||
| T.selectServer() | ||
| ); | ||
| this.renderCtxList({}); | ||
| } | ||
| /** 切换目标服务,重新加载 Context 路由映射 */ | ||
| async applyServer(key) { | ||
| if (!key) { | ||
| this.renderCtxList({}); | ||
| return; | ||
| } | ||
| const server = this.servers.find((s) => s.key === key); | ||
| if (!server) return; | ||
| this.setFooter(T.switching(), ""); | ||
| try { | ||
| await Context.load(server.origin, this.options.timeout); | ||
| const res = await fetch(`${server.origin}/ngw/context`); | ||
| const raw = res.ok ? await res.json() : {}; | ||
| const ctxMap = {}; | ||
| for (const [k, v] of Object.entries(raw)) { | ||
| if (k !== "$info" && typeof v === "string") ctxMap[k] = v; | ||
| } | ||
| const info = Context.info; | ||
| if (info) this.gwVersion.textContent = `v${info.version}`; | ||
| this.renderCtxList(ctxMap); | ||
| const count = Object.keys(ctxMap).length; | ||
| this.setFooter(T.routes(server.name, count), "ok"); | ||
| } catch (err) { | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| this.setFooter(T.switchFailed(msg), "err"); | ||
| } | ||
| } | ||
| /** 渲染路由列表 */ | ||
| renderCtxList(ctxMap) { | ||
| this.currentCtxMap = ctxMap; | ||
| this.ctxList.innerHTML = ""; | ||
| this.ctxSearchInput.value = ""; | ||
| const entries = Object.entries(ctxMap); | ||
| if (entries.length === 0) { | ||
| const empty = document.createElement("div"); | ||
| empty.className = "dbg-ctx-empty"; | ||
| empty.textContent = T.selectFirst(); | ||
| this.ctxList.appendChild(empty); | ||
| return; | ||
| } | ||
| entries.forEach(([k, v]) => { | ||
| const item = document.createElement("div"); | ||
| item.className = "dbg-ctx-item"; | ||
| item.dataset["key"] = k; | ||
| const keySpan = document.createElement("span"); | ||
| keySpan.className = "dbg-ctx-key"; | ||
| keySpan.textContent = k; | ||
| keySpan.setAttribute("title", k); | ||
| const valSpan = document.createElement("span"); | ||
| valSpan.className = "dbg-ctx-val"; | ||
| valSpan.textContent = v; | ||
| valSpan.setAttribute("title", `${k} \u2192 ${v}`); | ||
| const copyIcon = document.createElement("span"); | ||
| copyIcon.className = "dbg-ctx-copy"; | ||
| copyIcon.textContent = "\u2398"; | ||
| item.appendChild(keySpan); | ||
| item.appendChild(valSpan); | ||
| item.appendChild(copyIcon); | ||
| item.addEventListener("click", () => this.copyCtxItem(item, k)); | ||
| this.ctxList.appendChild(item); | ||
| }); | ||
| } | ||
| /** 过滤路由列表 */ | ||
| filterCtx(query) { | ||
| const q = query.toLowerCase(); | ||
| this.ctxList.querySelectorAll(".dbg-ctx-item").forEach((item) => { | ||
| const key = item.dataset["key"] ?? ""; | ||
| item.classList.toggle("hidden", !!q && !key.toLowerCase().includes(q)); | ||
| }); | ||
| } | ||
| /** 复制单个 ctx key */ | ||
| copyCtxItem(item, text) { | ||
| navigator.clipboard.writeText(text).then(() => { | ||
| item.classList.add("copied"); | ||
| const icon = item.querySelector(".dbg-ctx-copy"); | ||
| if (icon) icon.textContent = "\u2713"; | ||
| setTimeout(() => { | ||
| item.classList.remove("copied"); | ||
| const ic = item.querySelector(".dbg-ctx-copy"); | ||
| if (ic) ic.textContent = "\u2398"; | ||
| }, 1500); | ||
| this.setFooter(T.copied(text), "ok"); | ||
| setTimeout(() => this.setFooter("", ""), 2e3); | ||
| }); | ||
| } | ||
| /** 一键复制全部上下文为 JSON */ | ||
| copyAllCtx() { | ||
| const entries = Object.keys(this.currentCtxMap); | ||
| if (entries.length === 0) return; | ||
| const json = JSON.stringify(this.currentCtxMap, null, 2); | ||
| navigator.clipboard.writeText(json).then(() => { | ||
| this.copyAllBtn.textContent = "\u2713 " + T.copyAllDone(); | ||
| this.copyAllBtn.classList.add("done"); | ||
| setTimeout(() => { | ||
| this.copyAllBtn.textContent = T.copyAll(); | ||
| this.copyAllBtn.classList.remove("done"); | ||
| }, 2e3); | ||
| this.setFooter(T.copyAllDone(), "ok"); | ||
| setTimeout(() => this.setFooter("", ""), 2500); | ||
| }); | ||
| } | ||
| setHeader(state) { | ||
| this.headerDot.className = `dbg-header-dot${state !== "ok" ? ` ${state}` : ""}`; | ||
| } | ||
| setFooter(msg, cls) { | ||
| this.footerEl.innerHTML = msg ? `<span class="dbg-footer-dot"></span>${msg}` : ""; | ||
| this.footerEl.className = `dbg-footer${cls ? ` ${cls}` : ""}`; | ||
| } | ||
| }; | ||
| export { Debugger }; | ||
| //# sourceMappingURL=chunk-4DLFIN3C.js.map | ||
| //# sourceMappingURL=chunk-4DLFIN3C.js.map |
| {"version":3,"sources":["../../src/debugger/debugger.ts"],"names":[],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","file":"chunk-4DLFIN3C.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/** 是否为中文环境 */\nfunction isChinese(): boolean {\n return /^zh/i.test(navigator.language ?? '')\n}\n\n/** i18n 文案 */\nconst T = {\n gateway: () => isChinese() ? '网关' : 'Gateway',\n tag: () => isChinese() ? '标签' : 'Tag',\n server: () => isChinese() ? '服务' : 'Server',\n ctx: () => isChinese() ? '上下文' : 'Ctx',\n ctxLabel: () => isChinese() ? '上下文 — 点击行复制 key' : 'Ctx — click row to copy key',\n search: () => isChinese() ? '搜索...' : 'Search...',\n noMatch: () => isChinese() ? '无匹配项' : 'No results',\n allTags: () => isChinese() ? '全部标签' : 'All tags',\n selectGateway: () => isChinese() ? '选择网关' : 'Select gateway',\n selectServer: () => isChinese() ? '选择服务' : 'Select server',\n filterCtx: () => isChinese() ? '过滤路由...' : 'Filter routes...',\n selectFirst: () => isChinese() ? '← 请先选择服务' : '← Select a server first',\n loading: () => isChinese() ? '加载服务列表...' : 'Loading...',\n loadFailed: (m: string) => isChinese() ? `加载失败: ${m}` : `Failed: ${m}`,\n loaded: (n: number) => isChinese() ? `已加载 ${n} 个服务` : `${n} servers loaded`,\n switching: () => isChinese() ? '切换服务中...' : 'Switching...',\n switchFailed: (m: string) => isChinese() ? `切换失败: ${m}` : `Failed: ${m}`,\n routes: (name: string, n: number) => isChinese() ? `${name} · ${n} 条路由` : `${name} · ${n} routes`,\n copied: (k: string) => isChinese() ? `已复制: ${k}` : `Copied: ${k}`,\n copyAll: () => isChinese() ? '复制全部 JSON' : 'Copy all JSON',\n copyAllDone: () => isChinese() ? '已复制为 JSON' : 'Copied as JSON',\n}\n\n/** 面板内联样式 */\nconst PANEL_STYLE = `\n #__snackkit_debugger__ {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 99999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;\n font-size: 13px;\n color: #b8ccec;\n user-select: none;\n }\n\n /* ── 浮动按钮 ── */\n #__snackkit_debugger__ .dbg-toggle {\n width: 42px;\n height: 42px;\n border-radius: 50%;\n background: linear-gradient(135deg, #4f8ef7, #6d6ff5);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 14px rgba(79,142,247,0.45);\n margin-left: auto;\n font-size: 20px;\n transition: transform 0.15s, box-shadow 0.15s;\n }\n #__snackkit_debugger__ .dbg-toggle:hover {\n transform: scale(1.08);\n box-shadow: 0 6px 20px rgba(79,142,247,0.55);\n }\n #__snackkit_debugger__ .dbg-toggle:active { transform: scale(0.94); }\n\n /* ── 面板主体 ── */\n #__snackkit_debugger__ .dbg-panel {\n background: #1e2638;\n border: 1px solid #2d3a55;\n border-radius: 12px;\n width: 440px;\n margin-bottom: 10px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.05);\n overflow: visible;\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n pointer-events: none;\n transition: opacity 0.18s ease, transform 0.18s ease;\n }\n #__snackkit_debugger__ .dbg-panel.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n\n /* ── 面板 Header ── */\n #__snackkit_debugger__ .dbg-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 14px;\n background: #171e2e;\n border-bottom: 1px solid #2d3a55;\n border-radius: 12px 12px 0 0;\n }\n #__snackkit_debugger__ .dbg-header-left {\n display: flex;\n align-items: center;\n gap: 7px;\n }\n #__snackkit_debugger__ .dbg-header-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: #34d399;\n box-shadow: 0 0 6px rgba(52,211,153,0.6);\n }\n #__snackkit_debugger__ .dbg-header-dot.loading {\n background: #fbbf24;\n box-shadow: 0 0 6px rgba(251,191,36,0.6);\n animation: dbg-pulse 1s ease-in-out infinite;\n }\n #__snackkit_debugger__ .dbg-header-dot.err {\n background: #f87171;\n box-shadow: 0 0 6px rgba(248,113,113,0.6);\n }\n #__snackkit_debugger__ .dbg-header-title {\n font-size: 12px;\n font-weight: 600;\n color: #dce8fa;\n letter-spacing: 0.03em;\n }\n #__snackkit_debugger__ .dbg-header-meta {\n font-size: 11px;\n color: #4d6080;\n }\n\n /* ── 面板内容区 ── */\n #__snackkit_debugger__ .dbg-body {\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n height: 420px;\n overflow: visible;\n }\n\n /* ── 字段行 ── */\n #__snackkit_debugger__ .dbg-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n }\n #__snackkit_debugger__ .dbg-field-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n #__snackkit_debugger__ .dbg-label {\n font-size: 10px;\n font-weight: 600;\n color: #5e72a0;\n text-transform: uppercase;\n letter-spacing: 0.07em;\n }\n\n /* ── 一键复制全部 JSON 按钮 ── */\n #__snackkit_debugger__ .dbg-copy-all {\n display: flex;\n align-items: center;\n gap: 4px;\n background: transparent;\n border: 1px solid #2d3a55;\n border-radius: 4px;\n padding: 2px 7px;\n color: #5e72a0;\n font-size: 10px;\n font-family: inherit;\n cursor: pointer;\n transition: border-color 0.15s, color 0.15s, background 0.15s;\n }\n #__snackkit_debugger__ .dbg-copy-all:hover {\n border-color: #4f8ef7;\n color: #7aacfa;\n background: rgba(79,142,247,0.08);\n }\n #__snackkit_debugger__ .dbg-copy-all.done {\n border-color: #34d399;\n color: #34d399;\n }\n\n /* ── 自定义下拉 ── */\n #__snackkit_debugger__ .dbg-select {\n position: relative;\n }\n #__snackkit_debugger__ .dbg-select-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n background: #252f45;\n border: 1px solid #2d3a55;\n border-radius: 6px;\n padding: 6px 10px;\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n min-height: 32px;\n }\n #__snackkit_debugger__ .dbg-select-trigger:hover { border-color: #3d5070; background: #2d3a55; }\n #__snackkit_debugger__ .dbg-select.open .dbg-select-trigger {\n border-color: #4f8ef7;\n background: #2d3a55;\n }\n #__snackkit_debugger__ .dbg-select-val {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n color: #b8ccec;\n }\n #__snackkit_debugger__ .dbg-select-val.placeholder { color: #3d5070; }\n #__snackkit_debugger__ .dbg-select-arrow {\n color: #3d5070;\n font-size: 10px;\n flex-shrink: 0;\n transition: transform 0.15s, color 0.15s;\n }\n #__snackkit_debugger__ .dbg-select.open .dbg-select-arrow {\n transform: rotate(180deg);\n color: #4f8ef7;\n }\n #__snackkit_debugger__ .dbg-select-dropdown {\n position: absolute;\n bottom: calc(100% + 4px);\n left: 0;\n right: 0;\n background: #1e2638;\n border: 1px solid #2d3a55;\n border-radius: 8px;\n box-shadow: 0 -8px 24px rgba(0,0,0,0.35);\n overflow: hidden;\n opacity: 0;\n transform: translateY(4px);\n pointer-events: none;\n transition: opacity 0.14s, transform 0.14s;\n z-index: 100000;\n }\n #__snackkit_debugger__ .dbg-select.open .dbg-select-dropdown {\n opacity: 1;\n transform: translateY(0);\n pointer-events: auto;\n }\n #__snackkit_debugger__ .dbg-select-search {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 7px 10px;\n border-bottom: 1px solid #2d3a55;\n }\n #__snackkit_debugger__ .dbg-select-search-icon { color: #3d5070; font-size: 11px; flex-shrink: 0; }\n #__snackkit_debugger__ .dbg-select-search input {\n flex: 1;\n background: transparent;\n border: none;\n outline: none;\n color: #b8ccec;\n font-size: 12px;\n font-family: inherit;\n }\n #__snackkit_debugger__ .dbg-select-search input::placeholder { color: #3d5070; }\n #__snackkit_debugger__ .dbg-select-list {\n max-height: 160px;\n overflow-y: auto;\n }\n #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar { width: 3px; }\n #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-track { background: transparent; }\n #__snackkit_debugger__ .dbg-select-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; }\n #__snackkit_debugger__ .dbg-select-option {\n padding: 7px 10px;\n cursor: pointer;\n font-size: 12px;\n color: #8aa4c8;\n transition: background 0.1s, color 0.1s;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n #__snackkit_debugger__ .dbg-select-option:hover { background: #2d3a55; color: #dce8fa; }\n #__snackkit_debugger__ .dbg-select-option.active { color: #7aacfa; background: #1e3060; }\n #__snackkit_debugger__ .dbg-select-option.hidden { display: none; }\n #__snackkit_debugger__ .dbg-select-empty {\n padding: 12px 10px;\n font-size: 12px;\n color: #3d5070;\n text-align: center;\n }\n\n /* ── 路由列表 ── */\n #__snackkit_debugger__ .dbg-ctx-field {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 5px;\n }\n #__snackkit_debugger__ .dbg-ctx-wrap {\n flex: 1;\n min-height: 0;\n border: 1px solid #2d3a55;\n border-radius: 6px;\n overflow: hidden;\n background: #252f45;\n display: flex;\n flex-direction: column;\n }\n #__snackkit_debugger__ .dbg-ctx-search {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border-bottom: 1px solid #2d3a55;\n background: #1e2638;\n }\n #__snackkit_debugger__ .dbg-ctx-search input {\n flex: 1;\n background: transparent;\n border: none;\n outline: none;\n color: #b8ccec;\n font-size: 12px;\n font-family: inherit;\n }\n #__snackkit_debugger__ .dbg-ctx-search input::placeholder { color: #3d5070; }\n #__snackkit_debugger__ .dbg-ctx-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar { width: 3px; }\n #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-track { background: transparent; }\n #__snackkit_debugger__ .dbg-ctx-list::-webkit-scrollbar-thumb { background: #2d3a55; border-radius: 2px; }\n #__snackkit_debugger__ .dbg-ctx-item {\n padding: 5px 10px;\n cursor: pointer;\n display: grid;\n grid-template-columns: minmax(80px, 148px) 1fr auto;\n gap: 8px;\n align-items: center;\n border-bottom: 1px solid #1e2638;\n transition: background 0.1s;\n }\n #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; }\n #__snackkit_debugger__ .dbg-ctx-item:hover { background: #2d3a55; }\n #__snackkit_debugger__ .dbg-ctx-item.hidden { display: none; }\n #__snackkit_debugger__ .dbg-ctx-key {\n font-size: 12px;\n color: #7aacfa;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n #__snackkit_debugger__ .dbg-ctx-val {\n font-size: 11px;\n color: #4d6080;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n transition: color 0.1s;\n }\n #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-val { color: #7a90b0; }\n #__snackkit_debugger__ .dbg-ctx-copy {\n font-size: 11px;\n color: #3d5070;\n opacity: 0;\n flex-shrink: 0;\n transition: opacity 0.1s, color 0.1s;\n }\n #__snackkit_debugger__ .dbg-ctx-item:hover .dbg-ctx-copy { opacity: 1; color: #5e72a0; }\n #__snackkit_debugger__ .dbg-ctx-item.copied .dbg-ctx-copy { color: #34d399; opacity: 1; }\n #__snackkit_debugger__ .dbg-ctx-empty {\n padding: 16px 10px;\n font-size: 12px;\n color: #3d5070;\n text-align: center;\n }\n\n /* ── 底部状态栏 ── */\n #__snackkit_debugger__ .dbg-footer {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n background: #171e2e;\n border-top: 1px solid #2d3a55;\n border-radius: 0 0 12px 12px;\n font-size: 11px;\n color: #4d6080;\n min-height: 30px;\n }\n #__snackkit_debugger__ .dbg-footer.ok { color: #34d399; }\n #__snackkit_debugger__ .dbg-footer.err { color: #f87171; }\n #__snackkit_debugger__ .dbg-footer-dot {\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: currentColor;\n flex-shrink: 0;\n }\n\n @keyframes dbg-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.35; }\n }\n`\n\n/** 可搜索的自定义下拉组件 */\nclass SearchSelect {\n private el: HTMLElement\n private trigger: HTMLElement\n private valEl: HTMLElement\n private dropdown: HTMLElement\n private searchInput: HTMLInputElement\n private listEl: HTMLElement\n private options: Array<{ value: string; label: string }> = []\n private selected = ''\n private onChange: (value: string) => void\n\n constructor(onChange: (value: string) => void) {\n this.onChange = onChange\n this.el = document.createElement('div')\n this.el.className = 'dbg-select'\n\n // 触发器\n this.trigger = document.createElement('div')\n this.trigger.className = 'dbg-select-trigger'\n this.valEl = document.createElement('span')\n this.valEl.className = 'dbg-select-val placeholder'\n const arrow = document.createElement('span')\n arrow.className = 'dbg-select-arrow'\n arrow.textContent = '▾'\n this.trigger.appendChild(this.valEl)\n this.trigger.appendChild(arrow)\n\n // 下拉面板\n this.dropdown = document.createElement('div')\n this.dropdown.className = 'dbg-select-dropdown'\n\n // 搜索框\n const searchWrap = document.createElement('div')\n searchWrap.className = 'dbg-select-search'\n const searchIcon = document.createElement('span')\n searchIcon.className = 'dbg-select-search-icon'\n searchIcon.textContent = '⌕'\n this.searchInput = document.createElement('input')\n this.searchInput.type = 'text'\n this.searchInput.placeholder = T.search()\n searchWrap.appendChild(searchIcon)\n searchWrap.appendChild(this.searchInput)\n\n this.listEl = document.createElement('div')\n this.listEl.className = 'dbg-select-list'\n\n this.dropdown.appendChild(searchWrap)\n this.dropdown.appendChild(this.listEl)\n this.el.appendChild(this.trigger)\n this.el.appendChild(this.dropdown)\n\n this.bindEvents()\n }\n\n private bindEvents(): void {\n this.trigger.addEventListener('click', (e) => {\n e.stopPropagation()\n const isOpen = this.el.classList.contains('open')\n document.querySelectorAll('#__snackkit_debugger__ .dbg-select.open').forEach((el) => {\n if (el !== this.el) el.classList.remove('open')\n })\n this.el.classList.toggle('open', !isOpen)\n if (!isOpen) {\n this.searchInput.value = ''\n this.filterOptions('')\n setTimeout(() => this.searchInput.focus(), 50)\n }\n })\n\n this.searchInput.addEventListener('input', () => {\n this.filterOptions(this.searchInput.value)\n })\n\n this.searchInput.addEventListener('click', (e) => e.stopPropagation())\n\n document.addEventListener('click', () => {\n this.el.classList.remove('open')\n })\n }\n\n private filterOptions(query: string): void {\n const q = query.toLowerCase()\n let visibleCount = 0\n this.listEl.querySelectorAll<HTMLElement>('.dbg-select-option').forEach((opt) => {\n const match = !q || opt.textContent!.toLowerCase().includes(q)\n opt.classList.toggle('hidden', !match)\n if (match) visibleCount++\n })\n let emptyEl = this.listEl.querySelector<HTMLElement>('.dbg-select-empty')\n if (visibleCount === 0) {\n if (!emptyEl) {\n emptyEl = document.createElement('div')\n emptyEl.className = 'dbg-select-empty'\n emptyEl.textContent = T.noMatch()\n this.listEl.appendChild(emptyEl)\n }\n } else {\n emptyEl?.remove()\n }\n }\n\n /** 更新选项列表 */\n setOptions(options: Array<{ value: string; label: string }>, placeholder: string): void {\n this.options = options\n this.listEl.innerHTML = ''\n this.valEl.textContent = placeholder\n this.valEl.className = 'dbg-select-val placeholder'\n this.selected = ''\n\n options.forEach(({ value, label }) => {\n const opt = document.createElement('div')\n opt.className = 'dbg-select-option'\n opt.dataset['value'] = value\n opt.textContent = label\n opt.setAttribute('title', label)\n opt.addEventListener('click', (e) => {\n e.stopPropagation()\n this.select(value, label)\n this.el.classList.remove('open')\n })\n this.listEl.appendChild(opt)\n })\n }\n\n /** 选中某个值(不触发 onChange) */\n setValue(value: string): void {\n const opt = this.options.find((o) => o.value === value)\n if (opt) this.select(opt.value, opt.label, false)\n }\n\n private select(value: string, label: string, emit = true): void {\n this.selected = value\n this.valEl.textContent = label\n this.valEl.className = 'dbg-select-val'\n this.listEl.querySelectorAll('.dbg-select-option').forEach((el) => {\n el.classList.toggle('active', (el as HTMLElement).dataset['value'] === value)\n })\n if (emit) this.onChange(value)\n }\n\n getValue(): string { return this.selected }\n getElement(): HTMLElement { return this.el }\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 currentCtxMap: Record<string, string> = {}\n private panelEl!: HTMLElement\n private headerDot!: HTMLElement\n private gwVersion!: HTMLElement\n private footerEl!: HTMLElement\n private copyAllBtn!: HTMLButtonElement\n private gwSelect!: SearchSelect\n private typeSelect!: SearchSelect\n private serverSelect!: SearchSelect\n private ctxList!: HTMLElement\n private ctxSearchInput!: HTMLInputElement\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 // Header\n const header = document.createElement('div')\n header.className = 'dbg-header'\n const headerLeft = document.createElement('div')\n headerLeft.className = 'dbg-header-left'\n this.headerDot = document.createElement('span')\n this.headerDot.className = 'dbg-header-dot loading'\n const headerTitle = document.createElement('span')\n headerTitle.className = 'dbg-header-title'\n headerTitle.textContent = 'Snack Kit Debugger'\n headerLeft.appendChild(this.headerDot)\n headerLeft.appendChild(headerTitle)\n header.appendChild(headerLeft)\n panel.appendChild(header)\n\n // Body\n const body = document.createElement('div')\n body.className = 'dbg-body'\n\n // 网关选择(标签行右侧显示版本号)\n this.gwVersion = document.createElement('span')\n this.gwVersion.className = 'dbg-header-meta'\n this.gwSelect = new SearchSelect((val) => this.onGatewayChange(val))\n this.gwSelect.setOptions(this.gateways.map((g) => ({ value: g, label: g })), T.selectGateway())\n body.appendChild(this.createFieldWithAction(T.gateway(), this.gwVersion, this.gwSelect.getElement()))\n\n // 标签(类型)选择\n this.typeSelect = new SearchSelect((val) => this.onTypeChange(val))\n body.appendChild(this.createField(T.tag(), this.typeSelect.getElement()))\n\n // 服务选择\n this.serverSelect = new SearchSelect((val) => this.onServerChange(val))\n body.appendChild(this.createField(T.server(), this.serverSelect.getElement()))\n\n // 路由列表(含一键复制全部 JSON 按钮)\n const ctxWrap = document.createElement('div')\n ctxWrap.className = 'dbg-ctx-wrap'\n\n const ctxSearch = document.createElement('div')\n ctxSearch.className = 'dbg-ctx-search'\n const ctxSearchIcon = document.createElement('span')\n ctxSearchIcon.className = 'dbg-select-search-icon'\n ctxSearchIcon.textContent = '⌕'\n this.ctxSearchInput = document.createElement('input')\n this.ctxSearchInput.type = 'text'\n this.ctxSearchInput.placeholder = T.filterCtx()\n this.ctxSearchInput.addEventListener('input', () => this.filterCtx(this.ctxSearchInput.value))\n ctxSearch.appendChild(ctxSearchIcon)\n ctxSearch.appendChild(this.ctxSearchInput)\n\n this.ctxList = document.createElement('div')\n this.ctxList.className = 'dbg-ctx-list'\n\n ctxWrap.appendChild(ctxSearch)\n ctxWrap.appendChild(this.ctxList)\n\n // 一键复制全部 JSON 按钮\n this.copyAllBtn = document.createElement('button')\n this.copyAllBtn.className = 'dbg-copy-all'\n this.copyAllBtn.textContent = T.copyAll()\n this.copyAllBtn.setAttribute('title', isChinese()\n ? '将全部上下文复制为 JSON,可作为 Context.load({...}) 的参数'\n : 'Copy all ctx entries as JSON for Context.load({...})')\n this.copyAllBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n this.copyAllCtx()\n })\n\n const ctxField = this.createFieldWithAction(T.ctxLabel(), this.copyAllBtn, ctxWrap)\n ctxField.classList.add('dbg-ctx-field')\n body.appendChild(ctxField)\n\n panel.appendChild(body)\n\n // Footer 状态栏\n const footer = document.createElement('div')\n footer.className = 'dbg-footer'\n this.footerEl = footer\n panel.appendChild(footer)\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', (e) => {\n e.stopPropagation()\n panel.classList.toggle('open')\n })\n\n root.appendChild(panel)\n root.appendChild(toggle)\n document.body.appendChild(root)\n }\n\n /** 创建普通字段行(label + content) */\n private createField(label: string, content: HTMLElement): HTMLElement {\n const field = document.createElement('div')\n field.className = 'dbg-field'\n const lbl = document.createElement('div')\n lbl.className = 'dbg-label'\n lbl.textContent = label\n field.appendChild(lbl)\n field.appendChild(content)\n return field\n }\n\n /** 创建带操作按钮的字段行(label + action button + content) */\n private createFieldWithAction(label: string, action: HTMLElement, content: HTMLElement): HTMLElement {\n const field = document.createElement('div')\n field.className = 'dbg-field'\n const fieldHeader = document.createElement('div')\n fieldHeader.className = 'dbg-field-header'\n const lbl = document.createElement('div')\n lbl.className = 'dbg-label'\n lbl.textContent = label\n fieldHeader.appendChild(lbl)\n fieldHeader.appendChild(action)\n field.appendChild(fieldHeader)\n field.appendChild(content)\n return field\n }\n\n /** 恢复上次持久化的状态 */\n private async restoreState(): Promise<void> {\n const savedGw = localStorage.getItem(KEY_GATEWAY)\n const gw = (savedGw && this.gateways.includes(savedGw)) ? savedGw : this.gateways[0]\n this.gwSelect.setValue(gw)\n await this.loadServers(gw)\n\n const savedType = localStorage.getItem(KEY_TYPE)\n if (savedType) {\n this.typeSelect.setValue(savedType)\n this.renderServerOptions(savedType)\n }\n\n const savedServer = localStorage.getItem(KEY_SERVER)\n if (savedServer && this.servers.find((s) => s.key === savedServer)) {\n this.serverSelect.setValue(savedServer)\n await this.applyServer(savedServer)\n }\n }\n\n private async onGatewayChange(gw: string): Promise<void> {\n localStorage.setItem(KEY_GATEWAY, gw)\n this.gwVersion.textContent = ''\n await this.loadServers(gw)\n }\n\n private onTypeChange(type: string): void {\n localStorage.setItem(KEY_TYPE, type)\n this.renderServerOptions(type)\n }\n\n private async onServerChange(key: string): Promise<void> {\n localStorage.setItem(KEY_SERVER, key)\n await this.applyServer(key)\n }\n\n /** 从网关加载服务列表 */\n private async loadServers(gwUrl: string): Promise<void> {\n this.setHeader('loading')\n this.setFooter(T.loading(), '')\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.setHeader('err')\n this.setFooter(T.loadFailed(result.error.message), 'err')\n return\n }\n\n this.servers = result.data ?? []\n this.renderTypeOptions()\n this.setHeader('ok')\n this.setFooter(T.loaded(this.servers.length), 'ok')\n }\n\n /** 渲染标签选项 */\n private renderTypeOptions(): void {\n const types = [...new Set(this.servers.map((s) => s.type).filter(Boolean))]\n const opts = [{ value: '', label: T.allTags() }, ...types.map((t) => ({ value: t, label: t }))]\n this.typeSelect.setOptions(opts, T.allTags())\n this.typeSelect.setValue('')\n this.renderServerOptions('')\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.setOptions(\n filtered.map((s) => ({ value: s.key, label: `${s.name} (${s.key})` })),\n T.selectServer(),\n )\n this.renderCtxList({})\n }\n\n /** 切换目标服务,重新加载 Context 路由映射 */\n private async applyServer(key: string): Promise<void> {\n if (!key) { this.renderCtxList({}); return }\n const server = this.servers.find((s) => s.key === key)\n if (!server) return\n\n this.setFooter(T.switching(), '')\n try {\n await Context.load(server.origin, this.options.timeout)\n const res = await fetch(`${server.origin}/ngw/context`)\n const raw: Record<string, unknown> = res.ok ? await res.json() : {}\n const ctxMap: Record<string, string> = {}\n for (const [k, v] of Object.entries(raw)) {\n if (k !== '$info' && typeof v === 'string') ctxMap[k] = v\n }\n const info = Context.info\n if (info) this.gwVersion.textContent = `v${info.version}`\n this.renderCtxList(ctxMap)\n const count = Object.keys(ctxMap).length\n this.setFooter(T.routes(server.name, count), 'ok')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n this.setFooter(T.switchFailed(msg), 'err')\n }\n }\n\n /** 渲染路由列表 */\n private renderCtxList(ctxMap: Record<string, string>): void {\n this.currentCtxMap = ctxMap\n this.ctxList.innerHTML = ''\n this.ctxSearchInput.value = ''\n const entries = Object.entries(ctxMap)\n if (entries.length === 0) {\n const empty = document.createElement('div')\n empty.className = 'dbg-ctx-empty'\n empty.textContent = T.selectFirst()\n this.ctxList.appendChild(empty)\n return\n }\n entries.forEach(([k, v]) => {\n const item = document.createElement('div')\n item.className = 'dbg-ctx-item'\n item.dataset['key'] = k\n\n const keySpan = document.createElement('span')\n keySpan.className = 'dbg-ctx-key'\n keySpan.textContent = k\n keySpan.setAttribute('title', k)\n\n const valSpan = document.createElement('span')\n valSpan.className = 'dbg-ctx-val'\n valSpan.textContent = v\n valSpan.setAttribute('title', `${k} → ${v}`)\n\n const copyIcon = document.createElement('span')\n copyIcon.className = 'dbg-ctx-copy'\n copyIcon.textContent = '⎘'\n\n item.appendChild(keySpan)\n item.appendChild(valSpan)\n item.appendChild(copyIcon)\n item.addEventListener('click', () => this.copyCtxItem(item, k))\n this.ctxList.appendChild(item)\n })\n }\n\n /** 过滤路由列表 */\n private filterCtx(query: string): void {\n const q = query.toLowerCase()\n this.ctxList.querySelectorAll<HTMLElement>('.dbg-ctx-item').forEach((item) => {\n const key = item.dataset['key'] ?? ''\n item.classList.toggle('hidden', !!q && !key.toLowerCase().includes(q))\n })\n }\n\n /** 复制单个 ctx key */\n private copyCtxItem(item: HTMLElement, text: string): void {\n navigator.clipboard.writeText(text).then(() => {\n item.classList.add('copied')\n const icon = item.querySelector('.dbg-ctx-copy')\n if (icon) icon.textContent = '✓'\n setTimeout(() => {\n item.classList.remove('copied')\n const ic = item.querySelector('.dbg-ctx-copy')\n if (ic) ic.textContent = '⎘'\n }, 1500)\n this.setFooter(T.copied(text), 'ok')\n setTimeout(() => this.setFooter('', ''), 2000)\n })\n }\n\n /** 一键复制全部上下文为 JSON */\n private copyAllCtx(): void {\n const entries = Object.keys(this.currentCtxMap)\n if (entries.length === 0) return\n const json = JSON.stringify(this.currentCtxMap, null, 2)\n navigator.clipboard.writeText(json).then(() => {\n this.copyAllBtn.textContent = '✓ ' + T.copyAllDone()\n this.copyAllBtn.classList.add('done')\n setTimeout(() => {\n this.copyAllBtn.textContent = T.copyAll()\n this.copyAllBtn.classList.remove('done')\n }, 2000)\n this.setFooter(T.copyAllDone(), 'ok')\n setTimeout(() => this.setFooter('', ''), 2500)\n })\n }\n\n private setHeader(state: 'ok' | 'err' | 'loading'): void {\n this.headerDot.className = `dbg-header-dot${state !== 'ok' ? ` ${state}` : ''}`\n }\n\n private setFooter(msg: string, cls: '' | 'ok' | 'err'): void {\n this.footerEl.innerHTML = msg\n ? `<span class=\"dbg-footer-dot\"></span>${msg}`\n : ''\n this.footerEl.className = `dbg-footer${cls ? ` ${cls}` : ''}`\n }\n}\n"]} |
| 'use strict'; | ||
| var chunkJBJEXFQC_cjs = require('./chunk-JBJEXFQC.cjs'); | ||
| var chunkYOWLTZM5_cjs = require('./chunk-YOWLTZM5.cjs'); | ||
| require('./chunk-N7BJS6LI.cjs'); | ||
@@ -10,5 +10,5 @@ | ||
| enumerable: true, | ||
| get: function () { return chunkJBJEXFQC_cjs.Debugger; } | ||
| get: function () { return chunkYOWLTZM5_cjs.Debugger; } | ||
| }); | ||
| //# sourceMappingURL=debugger.cjs.map | ||
| //# sourceMappingURL=debugger.cjs.map |
| 'use strict'; | ||
| require('./chunk-YY5WQN7B.cjs'); | ||
| var chunkJBJEXFQC_cjs = require('./chunk-JBJEXFQC.cjs'); | ||
| var chunkYOWLTZM5_cjs = require('./chunk-YOWLTZM5.cjs'); | ||
| var chunkN7BJS6LI_cjs = require('./chunk-N7BJS6LI.cjs'); | ||
| // package.json | ||
| var version = "0.1.0"; | ||
| var version = "0.2.0"; | ||
| Object.defineProperty(exports, "Debugger", { | ||
| enumerable: true, | ||
| get: function () { return chunkJBJEXFQC_cjs.Debugger; } | ||
| get: function () { return chunkYOWLTZM5_cjs.Debugger; } | ||
| }); | ||
@@ -14,0 +14,0 @@ Object.defineProperty(exports, "Cancel", { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../package.json"],"names":[],"mappings":";;;;;;;AAEE,IAAA,OAAA,GAAW","file":"index.cjs","sourcesContent":["{\n \"name\": \"@snack-kit/lib\",\n \"version\": \"0.1.0\",\n \"description\": \"Enterprise-grade utility library\",\n \"keywords\": [],\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/types/index.d.ts\",\n \"import\": \"./dist/es/index.js\",\n \"require\": \"./dist/cjs/index.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/types/http.d.ts\",\n \"import\": \"./dist/es/http.js\",\n \"require\": \"./dist/cjs/http.cjs\"\n },\n \"./debugger\": {\n \"types\": \"./dist/types/debugger.d.ts\",\n \"import\": \"./dist/es/debugger.js\",\n \"require\": \"./dist/cjs/debugger.cjs\"\n }\n },\n \"main\": \"./dist/cjs/index.cjs\",\n \"module\": \"./dist/es/index.js\",\n \"types\": \"./dist/types/index.d.ts\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"build:watch\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"docs\": \"typedoc && cp -r docs/demo docs/api/demo\",\n \"docs:watch\": \"typedoc --watch\",\n \"lint\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run build\",\n \"release\": \"npm run build && npm publish --access public\"\n },\n \"devDependencies\": {\n \"@vitest/coverage-v8\": \"^2.0.0\",\n \"tsup\": \"^8.0.0\",\n \"typedoc\": \"^0.26.0\",\n \"typescript\": \"^5.5.3\",\n \"vitest\": \"^2.0.0\"\n },\n \"dependencies\": {\n \"axios\": \"^1.13.6\"\n }\n}\n"]} | ||
| {"version":3,"sources":["../../package.json"],"names":[],"mappings":";;;;;;;AAEE,IAAA,OAAA,GAAW","file":"index.cjs","sourcesContent":["{\n \"name\": \"@snack-kit/lib\",\n \"version\": \"0.2.0\",\n \"description\": \"Enterprise-grade utility library\",\n \"keywords\": [],\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/types/index.d.ts\",\n \"import\": \"./dist/es/index.js\",\n \"require\": \"./dist/cjs/index.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/types/http.d.ts\",\n \"import\": \"./dist/es/http.js\",\n \"require\": \"./dist/cjs/http.cjs\"\n },\n \"./debugger\": {\n \"types\": \"./dist/types/debugger.d.ts\",\n \"import\": \"./dist/es/debugger.js\",\n \"require\": \"./dist/cjs/debugger.cjs\"\n }\n },\n \"main\": \"./dist/cjs/index.cjs\",\n \"module\": \"./dist/es/index.js\",\n \"types\": \"./dist/types/index.d.ts\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"build:watch\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"docs\": \"typedoc && cp -r docs/demo docs/api/demo\",\n \"docs:watch\": \"typedoc --watch\",\n \"lint\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run build\",\n \"release\": \"npm run build && npm publish --access public\"\n },\n \"devDependencies\": {\n \"@vitest/coverage-v8\": \"^2.0.0\",\n \"tsup\": \"^8.0.0\",\n \"typedoc\": \"^0.26.0\",\n \"typescript\": \"^5.5.3\",\n \"vitest\": \"^2.0.0\"\n },\n \"dependencies\": {\n \"axios\": \"^1.13.6\"\n }\n}\n"]} |
@@ -1,4 +0,4 @@ | ||
| export { Debugger } from './chunk-QBBEQHXG.js'; | ||
| export { Debugger } from './chunk-4DLFIN3C.js'; | ||
| import './chunk-JQYH5FWE.js'; | ||
| //# sourceMappingURL=debugger.js.map | ||
| //# sourceMappingURL=debugger.js.map |
+2
-2
| import './chunk-BEL6AFK4.js'; | ||
| export { Debugger } from './chunk-QBBEQHXG.js'; | ||
| export { Debugger } from './chunk-4DLFIN3C.js'; | ||
| export { Cancel, CancelAll, Context, Ctx, Del, Get, HttpContext, Origin, Patch, Post, Put, Request } from './chunk-JQYH5FWE.js'; | ||
| // package.json | ||
| var version = "0.1.0"; | ||
| var version = "0.2.0"; | ||
@@ -8,0 +8,0 @@ export { version as VERSION }; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../../package.json"],"names":[],"mappings":";;;;;AAEE,IAAA,OAAA,GAAW","file":"index.js","sourcesContent":["{\n \"name\": \"@snack-kit/lib\",\n \"version\": \"0.1.0\",\n \"description\": \"Enterprise-grade utility library\",\n \"keywords\": [],\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/types/index.d.ts\",\n \"import\": \"./dist/es/index.js\",\n \"require\": \"./dist/cjs/index.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/types/http.d.ts\",\n \"import\": \"./dist/es/http.js\",\n \"require\": \"./dist/cjs/http.cjs\"\n },\n \"./debugger\": {\n \"types\": \"./dist/types/debugger.d.ts\",\n \"import\": \"./dist/es/debugger.js\",\n \"require\": \"./dist/cjs/debugger.cjs\"\n }\n },\n \"main\": \"./dist/cjs/index.cjs\",\n \"module\": \"./dist/es/index.js\",\n \"types\": \"./dist/types/index.d.ts\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"build:watch\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"docs\": \"typedoc && cp -r docs/demo docs/api/demo\",\n \"docs:watch\": \"typedoc --watch\",\n \"lint\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run build\",\n \"release\": \"npm run build && npm publish --access public\"\n },\n \"devDependencies\": {\n \"@vitest/coverage-v8\": \"^2.0.0\",\n \"tsup\": \"^8.0.0\",\n \"typedoc\": \"^0.26.0\",\n \"typescript\": \"^5.5.3\",\n \"vitest\": \"^2.0.0\"\n },\n \"dependencies\": {\n \"axios\": \"^1.13.6\"\n }\n}\n"]} | ||
| {"version":3,"sources":["../../package.json"],"names":[],"mappings":";;;;;AAEE,IAAA,OAAA,GAAW","file":"index.js","sourcesContent":["{\n \"name\": \"@snack-kit/lib\",\n \"version\": \"0.2.0\",\n \"description\": \"Enterprise-grade utility library\",\n \"keywords\": [],\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/types/index.d.ts\",\n \"import\": \"./dist/es/index.js\",\n \"require\": \"./dist/cjs/index.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/types/http.d.ts\",\n \"import\": \"./dist/es/http.js\",\n \"require\": \"./dist/cjs/http.cjs\"\n },\n \"./debugger\": {\n \"types\": \"./dist/types/debugger.d.ts\",\n \"import\": \"./dist/es/debugger.js\",\n \"require\": \"./dist/cjs/debugger.cjs\"\n }\n },\n \"main\": \"./dist/cjs/index.cjs\",\n \"module\": \"./dist/es/index.js\",\n \"types\": \"./dist/types/index.d.ts\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"build:watch\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"docs\": \"typedoc && cp -r docs/demo docs/api/demo\",\n \"docs:watch\": \"typedoc --watch\",\n \"lint\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run build\",\n \"release\": \"npm run build && npm publish --access public\"\n },\n \"devDependencies\": {\n \"@vitest/coverage-v8\": \"^2.0.0\",\n \"tsup\": \"^8.0.0\",\n \"typedoc\": \"^0.26.0\",\n \"typescript\": \"^5.5.3\",\n \"vitest\": \"^2.0.0\"\n },\n \"dependencies\": {\n \"axios\": \"^1.13.6\"\n }\n}\n"]} |
+22
-18
@@ -34,3 +34,8 @@ /** | ||
| private servers; | ||
| private currentCtxMap; | ||
| private panelEl; | ||
| private headerDot; | ||
| private gwVersion; | ||
| private footerEl; | ||
| private copyAllBtn; | ||
| private gwSelect; | ||
@@ -40,3 +45,3 @@ private typeSelect; | ||
| private ctxList; | ||
| private statusEl; | ||
| private ctxSearchInput; | ||
| private constructor(); | ||
@@ -85,32 +90,31 @@ /** | ||
| private buildDOM; | ||
| private createRow; | ||
| /** 创建普通字段行(label + content) */ | ||
| private createField; | ||
| /** 创建带操作按钮的字段行(label + action button + content) */ | ||
| private createFieldWithAction; | ||
| /** 恢复上次持久化的状态 */ | ||
| private restoreState; | ||
| /** 网关切换处理 */ | ||
| private onGatewayChange; | ||
| /** 服务类型切换处理 */ | ||
| private onTypeChange; | ||
| /** 服务切换处理 */ | ||
| private onServerChange; | ||
| /** | ||
| * 从网关加载服务列表 | ||
| * @param gwUrl 网关 URL | ||
| */ | ||
| /** 从网关加载服务列表 */ | ||
| private loadServers; | ||
| /** 渲染服务类型选项 */ | ||
| /** 渲染标签选项 */ | ||
| private renderTypeOptions; | ||
| /** 根据类型渲染服务选项 */ | ||
| /** 根据标签渲染服务选项 */ | ||
| private renderServerOptions; | ||
| /** | ||
| * 切换目标服务,重新加载 Context 路由映射 | ||
| * @param key 服务 key | ||
| */ | ||
| /** 切换目标服务,重新加载 Context 路由映射 */ | ||
| private applyServer; | ||
| /** 渲染路由列表 */ | ||
| private renderCtxList; | ||
| /** 复制文本到剪贴板 */ | ||
| private copyText; | ||
| private setStatus; | ||
| /** 过滤路由列表 */ | ||
| private filterCtx; | ||
| /** 复制单个 ctx key */ | ||
| private copyCtxItem; | ||
| /** 一键复制全部上下文为 JSON */ | ||
| private copyAllCtx; | ||
| private setHeader; | ||
| private setFooter; | ||
| } | ||
| export { Debugger, type DebuggerOptions }; |
@@ -5,4 +5,4 @@ export { Cancel, CancelAll, Context, ContextInfo, Ctx, Del, Get, HttpContext, HttpError, HttpRequestConfig, HttpResult, Origin, Patch, Post, Put, Request, ServerItem } from './http.js'; | ||
| var version = "0.1.0"; | ||
| var version = "0.2.0"; | ||
| export { version as VERSION }; |
+1
-1
| { | ||
| "name": "@snack-kit/lib", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "Enterprise-grade utility library", | ||
@@ -5,0 +5,0 @@ "keywords": [], |
| 'use strict'; | ||
| var chunkN7BJS6LI_cjs = require('./chunk-N7BJS6LI.cjs'); | ||
| // src/debugger/debugger.ts | ||
| var STORAGE_PREFIX = "__snackkit_debugger__"; | ||
| var KEY_GATEWAY = `${STORAGE_PREFIX}gateway`; | ||
| var KEY_TYPE = `${STORAGE_PREFIX}type`; | ||
| var KEY_SERVER = `${STORAGE_PREFIX}server`; | ||
| var PANEL_STYLE = ` | ||
| #__snackkit_debugger__ { | ||
| position: fixed; | ||
| bottom: 16px; | ||
| right: 16px; | ||
| z-index: 99999; | ||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | ||
| font-size: 13px; | ||
| color: #e2e8f0; | ||
| } | ||
| #__snackkit_debugger__ .dbg-toggle { | ||
| width: 40px; | ||
| height: 40px; | ||
| border-radius: 50%; | ||
| background: #3b82f6; | ||
| border: none; | ||
| cursor: pointer; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| box-shadow: 0 2px 8px rgba(0,0,0,0.3); | ||
| margin-left: auto; | ||
| color: #fff; | ||
| font-size: 18px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-panel { | ||
| background: #1e293b; | ||
| border: 1px solid #334155; | ||
| border-radius: 8px; | ||
| padding: 12px; | ||
| width: 320px; | ||
| margin-bottom: 8px; | ||
| box-shadow: 0 4px 16px rgba(0,0,0,0.4); | ||
| display: none; | ||
| } | ||
| #__snackkit_debugger__ .dbg-panel.open { display: block; } | ||
| #__snackkit_debugger__ .dbg-row { | ||
| margin-bottom: 8px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| } | ||
| #__snackkit_debugger__ label { | ||
| font-size: 11px; | ||
| color: #94a3b8; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| } | ||
| #__snackkit_debugger__ select { | ||
| background: #0f172a; | ||
| border: 1px solid #334155; | ||
| border-radius: 4px; | ||
| color: #e2e8f0; | ||
| padding: 4px 8px; | ||
| font-size: 13px; | ||
| width: 100%; | ||
| cursor: pointer; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-list { | ||
| max-height: 160px; | ||
| overflow-y: auto; | ||
| border: 1px solid #334155; | ||
| border-radius: 4px; | ||
| background: #0f172a; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item { | ||
| padding: 4px 8px; | ||
| border-bottom: 1px solid #1e293b; | ||
| cursor: pointer; | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover { background: #1e293b; } | ||
| #__snackkit_debugger__ .dbg-ctx-key { color: #60a5fa; } | ||
| #__snackkit_debugger__ .dbg-ctx-val { color: #94a3b8; font-size: 11px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | ||
| #__snackkit_debugger__ .dbg-status { | ||
| font-size: 11px; | ||
| color: #64748b; | ||
| margin-top: 4px; | ||
| min-height: 16px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-status.ok { color: #22c55e; } | ||
| #__snackkit_debugger__ .dbg-status.err { color: #ef4444; } | ||
| `; | ||
| var Debugger = class _Debugger { | ||
| constructor(options) { | ||
| this.servers = []; | ||
| this.options = options; | ||
| this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways]; | ||
| } | ||
| /** | ||
| * 初始化并渲染调试面板 | ||
| * | ||
| * 调用后在页面右下角插入浮动按钮,点击展开调试面板。 | ||
| * 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。 | ||
| * 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。 | ||
| * | ||
| * @param options 配置项 | ||
| * | ||
| * @example 单网关 | ||
| * ```ts | ||
| * import { Debugger } from '@snack-kit/lib/debugger' | ||
| * | ||
| * await Debugger.init({ | ||
| * gateways: 'http://dev-gateway.example.com', | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 多网关(开发/测试环境切换) | ||
| * ```ts | ||
| * await Debugger.init({ | ||
| * gateways: [ | ||
| * 'http://dev-gateway.example.com', | ||
| * 'http://test-gateway.example.com', | ||
| * ], | ||
| * timeout: 5000, | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 仅在非生产环境加载 | ||
| * ```ts | ||
| * if (import.meta.env.DEV) { | ||
| * const { Debugger } = await import('@snack-kit/lib/debugger') | ||
| * await Debugger.init({ gateways: 'http://dev-gateway.example.com' }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| static async init(options) { | ||
| const instance = new _Debugger({ timeout: 1e4, ...options }); | ||
| instance.injectStyle(); | ||
| instance.buildDOM(); | ||
| await instance.restoreState(); | ||
| return instance; | ||
| } | ||
| /** 注入内联样式 */ | ||
| injectStyle() { | ||
| if (document.getElementById("__snackkit_debugger_style__")) return; | ||
| const style = document.createElement("style"); | ||
| style.id = "__snackkit_debugger_style__"; | ||
| style.textContent = PANEL_STYLE; | ||
| document.head.appendChild(style); | ||
| } | ||
| /** 构建 DOM 结构 */ | ||
| buildDOM() { | ||
| const root = document.createElement("div"); | ||
| root.id = "__snackkit_debugger__"; | ||
| const panel = document.createElement("div"); | ||
| panel.className = "dbg-panel"; | ||
| this.panelEl = panel; | ||
| const gwRow = this.createRow("\u7F51\u5173 (Gateway)"); | ||
| const gwSelect = document.createElement("select"); | ||
| this.gateways.forEach((gw) => { | ||
| const opt = document.createElement("option"); | ||
| opt.value = gw; | ||
| opt.textContent = gw; | ||
| gwSelect.appendChild(opt); | ||
| }); | ||
| gwSelect.addEventListener("change", () => this.onGatewayChange()); | ||
| gwRow.appendChild(gwSelect); | ||
| this.gwSelect = gwSelect; | ||
| panel.appendChild(gwRow); | ||
| const typeRow = this.createRow("\u670D\u52A1\u7C7B\u578B (Type)"); | ||
| const typeSelect = document.createElement("select"); | ||
| typeSelect.addEventListener("change", () => this.onTypeChange()); | ||
| typeRow.appendChild(typeSelect); | ||
| this.typeSelect = typeSelect; | ||
| panel.appendChild(typeRow); | ||
| const serverRow = this.createRow("\u670D\u52A1 (Server)"); | ||
| const serverSelect = document.createElement("select"); | ||
| serverSelect.addEventListener("change", () => this.onServerChange()); | ||
| serverRow.appendChild(serverSelect); | ||
| this.serverSelect = serverSelect; | ||
| panel.appendChild(serverRow); | ||
| const ctxRow = this.createRow("\u8DEF\u7531 (Ctx) - \u70B9\u51FB\u590D\u5236"); | ||
| const ctxList = document.createElement("div"); | ||
| ctxList.className = "dbg-ctx-list"; | ||
| ctxRow.appendChild(ctxList); | ||
| this.ctxList = ctxList; | ||
| panel.appendChild(ctxRow); | ||
| const status = document.createElement("div"); | ||
| status.className = "dbg-status"; | ||
| this.statusEl = status; | ||
| panel.appendChild(status); | ||
| const toggle = document.createElement("button"); | ||
| toggle.className = "dbg-toggle"; | ||
| toggle.textContent = "\u{1F41B}"; | ||
| toggle.setAttribute("title", "Snack Debugger"); | ||
| toggle.addEventListener("click", () => panel.classList.toggle("open")); | ||
| root.appendChild(panel); | ||
| root.appendChild(toggle); | ||
| document.body.appendChild(root); | ||
| } | ||
| createRow(label) { | ||
| const row = document.createElement("div"); | ||
| row.className = "dbg-row"; | ||
| const lbl = document.createElement("label"); | ||
| lbl.textContent = label; | ||
| row.appendChild(lbl); | ||
| return row; | ||
| } | ||
| /** 恢复上次持久化的状态 */ | ||
| async restoreState() { | ||
| const savedGw = localStorage.getItem(KEY_GATEWAY); | ||
| if (savedGw && this.gateways.includes(savedGw)) { | ||
| this.gwSelect.value = savedGw; | ||
| } | ||
| await this.loadServers(this.gwSelect.value); | ||
| const savedType = localStorage.getItem(KEY_TYPE); | ||
| if (savedType) { | ||
| this.typeSelect.value = savedType; | ||
| this.renderServerOptions(savedType); | ||
| } | ||
| const savedServer = localStorage.getItem(KEY_SERVER); | ||
| if (savedServer && this.serverSelect.querySelector(`option[value="${savedServer}"]`)) { | ||
| this.serverSelect.value = savedServer; | ||
| await this.applyServer(savedServer); | ||
| } | ||
| } | ||
| /** 网关切换处理 */ | ||
| async onGatewayChange() { | ||
| const gw = this.gwSelect.value; | ||
| localStorage.setItem(KEY_GATEWAY, gw); | ||
| await this.loadServers(gw); | ||
| } | ||
| /** 服务类型切换处理 */ | ||
| onTypeChange() { | ||
| const type = this.typeSelect.value; | ||
| localStorage.setItem(KEY_TYPE, type); | ||
| this.renderServerOptions(type); | ||
| } | ||
| /** 服务切换处理 */ | ||
| async onServerChange() { | ||
| const key = this.serverSelect.value; | ||
| localStorage.setItem(KEY_SERVER, key); | ||
| await this.applyServer(key); | ||
| } | ||
| /** | ||
| * 从网关加载服务列表 | ||
| * @param gwUrl 网关 URL | ||
| */ | ||
| async loadServers(gwUrl) { | ||
| this.setStatus("\u52A0\u8F7D\u4E2D...", ""); | ||
| const result = await chunkN7BJS6LI_cjs.Get(`${gwUrl}/web-debug/host/list`, { | ||
| cache: true, | ||
| timeout: this.options.timeout | ||
| }); | ||
| if (result.error) { | ||
| this.setStatus(`\u52A0\u8F7D\u5931\u8D25: ${result.error.message}`, "err"); | ||
| return; | ||
| } | ||
| this.servers = result.data ?? []; | ||
| this.renderTypeOptions(); | ||
| this.setStatus(`\u5DF2\u52A0\u8F7D ${this.servers.length} \u4E2A\u670D\u52A1`, "ok"); | ||
| } | ||
| /** 渲染服务类型选项 */ | ||
| renderTypeOptions() { | ||
| const types = [...new Set(this.servers.map((s) => s.type))]; | ||
| this.typeSelect.innerHTML = ""; | ||
| const allOpt = document.createElement("option"); | ||
| allOpt.value = ""; | ||
| allOpt.textContent = "\u5168\u90E8"; | ||
| this.typeSelect.appendChild(allOpt); | ||
| types.forEach((t) => { | ||
| const opt = document.createElement("option"); | ||
| opt.value = t; | ||
| opt.textContent = t; | ||
| this.typeSelect.appendChild(opt); | ||
| }); | ||
| this.renderServerOptions(this.typeSelect.value); | ||
| } | ||
| /** 根据类型渲染服务选项 */ | ||
| renderServerOptions(type) { | ||
| const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers; | ||
| this.serverSelect.innerHTML = ""; | ||
| const emptyOpt = document.createElement("option"); | ||
| emptyOpt.value = ""; | ||
| emptyOpt.textContent = "\u8BF7\u9009\u62E9\u670D\u52A1"; | ||
| this.serverSelect.appendChild(emptyOpt); | ||
| filtered.forEach((s) => { | ||
| const opt = document.createElement("option"); | ||
| opt.value = s.key; | ||
| opt.textContent = `${s.name} (${s.key})`; | ||
| this.serverSelect.appendChild(opt); | ||
| }); | ||
| } | ||
| /** | ||
| * 切换目标服务,重新加载 Context 路由映射 | ||
| * @param key 服务 key | ||
| */ | ||
| async applyServer(key) { | ||
| if (!key) { | ||
| this.renderCtxList({}); | ||
| return; | ||
| } | ||
| const server = this.servers.find((s) => s.key === key); | ||
| if (!server) return; | ||
| this.setStatus("\u5207\u6362\u670D\u52A1\u4E2D...", ""); | ||
| try { | ||
| await chunkN7BJS6LI_cjs.Context.load(server.origin, this.options.timeout); | ||
| const ctxMap = {}; | ||
| const res = await fetch(`${server.origin}/ngw/context`); | ||
| const raw = res.ok ? await res.json() : {}; | ||
| for (const [k, v] of Object.entries(raw)) { | ||
| if (k !== "$info" && typeof v === "string") ctxMap[k] = v; | ||
| } | ||
| this.renderCtxList(ctxMap); | ||
| this.setStatus(`\u5DF2\u5207\u6362\u81F3 ${server.name}`, "ok"); | ||
| } catch (err) { | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| this.setStatus(`\u5207\u6362\u5931\u8D25: ${msg}`, "err"); | ||
| } | ||
| } | ||
| /** 渲染路由列表 */ | ||
| renderCtxList(ctxMap) { | ||
| this.ctxList.innerHTML = ""; | ||
| const entries = Object.entries(ctxMap); | ||
| if (entries.length === 0) { | ||
| this.ctxList.textContent = "\u6682\u65E0\u8DEF\u7531"; | ||
| return; | ||
| } | ||
| entries.forEach(([k, v]) => { | ||
| const item = document.createElement("div"); | ||
| item.className = "dbg-ctx-item"; | ||
| const keySpan = document.createElement("span"); | ||
| keySpan.className = "dbg-ctx-key"; | ||
| keySpan.textContent = k; | ||
| const valSpan = document.createElement("span"); | ||
| valSpan.className = "dbg-ctx-val"; | ||
| valSpan.textContent = v; | ||
| valSpan.setAttribute("title", v); | ||
| item.appendChild(keySpan); | ||
| item.appendChild(valSpan); | ||
| item.addEventListener("click", () => this.copyText(k)); | ||
| this.ctxList.appendChild(item); | ||
| }); | ||
| } | ||
| /** 复制文本到剪贴板 */ | ||
| copyText(text) { | ||
| navigator.clipboard.writeText(text).then(() => { | ||
| this.setStatus(`\u5DF2\u590D\u5236: ${text}`, "ok"); | ||
| setTimeout(() => this.setStatus("", ""), 2e3); | ||
| }); | ||
| } | ||
| setStatus(msg, cls) { | ||
| this.statusEl.textContent = msg; | ||
| this.statusEl.className = `dbg-status${cls ? ` ${cls}` : ""}`; | ||
| } | ||
| }; | ||
| exports.Debugger = Debugger; | ||
| //# sourceMappingURL=chunk-JBJEXFQC.cjs.map | ||
| //# sourceMappingURL=chunk-JBJEXFQC.cjs.map |
| {"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"]} |
| import { Get, Context } from './chunk-JQYH5FWE.js'; | ||
| // src/debugger/debugger.ts | ||
| var STORAGE_PREFIX = "__snackkit_debugger__"; | ||
| var KEY_GATEWAY = `${STORAGE_PREFIX}gateway`; | ||
| var KEY_TYPE = `${STORAGE_PREFIX}type`; | ||
| var KEY_SERVER = `${STORAGE_PREFIX}server`; | ||
| var PANEL_STYLE = ` | ||
| #__snackkit_debugger__ { | ||
| position: fixed; | ||
| bottom: 16px; | ||
| right: 16px; | ||
| z-index: 99999; | ||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | ||
| font-size: 13px; | ||
| color: #e2e8f0; | ||
| } | ||
| #__snackkit_debugger__ .dbg-toggle { | ||
| width: 40px; | ||
| height: 40px; | ||
| border-radius: 50%; | ||
| background: #3b82f6; | ||
| border: none; | ||
| cursor: pointer; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| box-shadow: 0 2px 8px rgba(0,0,0,0.3); | ||
| margin-left: auto; | ||
| color: #fff; | ||
| font-size: 18px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-panel { | ||
| background: #1e293b; | ||
| border: 1px solid #334155; | ||
| border-radius: 8px; | ||
| padding: 12px; | ||
| width: 320px; | ||
| margin-bottom: 8px; | ||
| box-shadow: 0 4px 16px rgba(0,0,0,0.4); | ||
| display: none; | ||
| } | ||
| #__snackkit_debugger__ .dbg-panel.open { display: block; } | ||
| #__snackkit_debugger__ .dbg-row { | ||
| margin-bottom: 8px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| } | ||
| #__snackkit_debugger__ label { | ||
| font-size: 11px; | ||
| color: #94a3b8; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| } | ||
| #__snackkit_debugger__ select { | ||
| background: #0f172a; | ||
| border: 1px solid #334155; | ||
| border-radius: 4px; | ||
| color: #e2e8f0; | ||
| padding: 4px 8px; | ||
| font-size: 13px; | ||
| width: 100%; | ||
| cursor: pointer; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-list { | ||
| max-height: 160px; | ||
| overflow-y: auto; | ||
| border: 1px solid #334155; | ||
| border-radius: 4px; | ||
| background: #0f172a; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item { | ||
| padding: 4px 8px; | ||
| border-bottom: 1px solid #1e293b; | ||
| cursor: pointer; | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| } | ||
| #__snackkit_debugger__ .dbg-ctx-item:last-child { border-bottom: none; } | ||
| #__snackkit_debugger__ .dbg-ctx-item:hover { background: #1e293b; } | ||
| #__snackkit_debugger__ .dbg-ctx-key { color: #60a5fa; } | ||
| #__snackkit_debugger__ .dbg-ctx-val { color: #94a3b8; font-size: 11px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | ||
| #__snackkit_debugger__ .dbg-status { | ||
| font-size: 11px; | ||
| color: #64748b; | ||
| margin-top: 4px; | ||
| min-height: 16px; | ||
| } | ||
| #__snackkit_debugger__ .dbg-status.ok { color: #22c55e; } | ||
| #__snackkit_debugger__ .dbg-status.err { color: #ef4444; } | ||
| `; | ||
| var Debugger = class _Debugger { | ||
| constructor(options) { | ||
| this.servers = []; | ||
| this.options = options; | ||
| this.gateways = Array.isArray(options.gateways) ? options.gateways : [options.gateways]; | ||
| } | ||
| /** | ||
| * 初始化并渲染调试面板 | ||
| * | ||
| * 调用后在页面右下角插入浮动按钮,点击展开调试面板。 | ||
| * 面板支持:选择调试网关、切换目标服务、查看并复制路由 ctx。 | ||
| * 网关/服务选择通过 `localStorage` 持久化,刷新后自动恢复。 | ||
| * | ||
| * @param options 配置项 | ||
| * | ||
| * @example 单网关 | ||
| * ```ts | ||
| * import { Debugger } from '@snack-kit/lib/debugger' | ||
| * | ||
| * await Debugger.init({ | ||
| * gateways: 'http://dev-gateway.example.com', | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 多网关(开发/测试环境切换) | ||
| * ```ts | ||
| * await Debugger.init({ | ||
| * gateways: [ | ||
| * 'http://dev-gateway.example.com', | ||
| * 'http://test-gateway.example.com', | ||
| * ], | ||
| * timeout: 5000, | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @example 仅在非生产环境加载 | ||
| * ```ts | ||
| * if (import.meta.env.DEV) { | ||
| * const { Debugger } = await import('@snack-kit/lib/debugger') | ||
| * await Debugger.init({ gateways: 'http://dev-gateway.example.com' }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| static async init(options) { | ||
| const instance = new _Debugger({ timeout: 1e4, ...options }); | ||
| instance.injectStyle(); | ||
| instance.buildDOM(); | ||
| await instance.restoreState(); | ||
| return instance; | ||
| } | ||
| /** 注入内联样式 */ | ||
| injectStyle() { | ||
| if (document.getElementById("__snackkit_debugger_style__")) return; | ||
| const style = document.createElement("style"); | ||
| style.id = "__snackkit_debugger_style__"; | ||
| style.textContent = PANEL_STYLE; | ||
| document.head.appendChild(style); | ||
| } | ||
| /** 构建 DOM 结构 */ | ||
| buildDOM() { | ||
| const root = document.createElement("div"); | ||
| root.id = "__snackkit_debugger__"; | ||
| const panel = document.createElement("div"); | ||
| panel.className = "dbg-panel"; | ||
| this.panelEl = panel; | ||
| const gwRow = this.createRow("\u7F51\u5173 (Gateway)"); | ||
| const gwSelect = document.createElement("select"); | ||
| this.gateways.forEach((gw) => { | ||
| const opt = document.createElement("option"); | ||
| opt.value = gw; | ||
| opt.textContent = gw; | ||
| gwSelect.appendChild(opt); | ||
| }); | ||
| gwSelect.addEventListener("change", () => this.onGatewayChange()); | ||
| gwRow.appendChild(gwSelect); | ||
| this.gwSelect = gwSelect; | ||
| panel.appendChild(gwRow); | ||
| const typeRow = this.createRow("\u670D\u52A1\u7C7B\u578B (Type)"); | ||
| const typeSelect = document.createElement("select"); | ||
| typeSelect.addEventListener("change", () => this.onTypeChange()); | ||
| typeRow.appendChild(typeSelect); | ||
| this.typeSelect = typeSelect; | ||
| panel.appendChild(typeRow); | ||
| const serverRow = this.createRow("\u670D\u52A1 (Server)"); | ||
| const serverSelect = document.createElement("select"); | ||
| serverSelect.addEventListener("change", () => this.onServerChange()); | ||
| serverRow.appendChild(serverSelect); | ||
| this.serverSelect = serverSelect; | ||
| panel.appendChild(serverRow); | ||
| const ctxRow = this.createRow("\u8DEF\u7531 (Ctx) - \u70B9\u51FB\u590D\u5236"); | ||
| const ctxList = document.createElement("div"); | ||
| ctxList.className = "dbg-ctx-list"; | ||
| ctxRow.appendChild(ctxList); | ||
| this.ctxList = ctxList; | ||
| panel.appendChild(ctxRow); | ||
| const status = document.createElement("div"); | ||
| status.className = "dbg-status"; | ||
| this.statusEl = status; | ||
| panel.appendChild(status); | ||
| const toggle = document.createElement("button"); | ||
| toggle.className = "dbg-toggle"; | ||
| toggle.textContent = "\u{1F41B}"; | ||
| toggle.setAttribute("title", "Snack Debugger"); | ||
| toggle.addEventListener("click", () => panel.classList.toggle("open")); | ||
| root.appendChild(panel); | ||
| root.appendChild(toggle); | ||
| document.body.appendChild(root); | ||
| } | ||
| createRow(label) { | ||
| const row = document.createElement("div"); | ||
| row.className = "dbg-row"; | ||
| const lbl = document.createElement("label"); | ||
| lbl.textContent = label; | ||
| row.appendChild(lbl); | ||
| return row; | ||
| } | ||
| /** 恢复上次持久化的状态 */ | ||
| async restoreState() { | ||
| const savedGw = localStorage.getItem(KEY_GATEWAY); | ||
| if (savedGw && this.gateways.includes(savedGw)) { | ||
| this.gwSelect.value = savedGw; | ||
| } | ||
| await this.loadServers(this.gwSelect.value); | ||
| const savedType = localStorage.getItem(KEY_TYPE); | ||
| if (savedType) { | ||
| this.typeSelect.value = savedType; | ||
| this.renderServerOptions(savedType); | ||
| } | ||
| const savedServer = localStorage.getItem(KEY_SERVER); | ||
| if (savedServer && this.serverSelect.querySelector(`option[value="${savedServer}"]`)) { | ||
| this.serverSelect.value = savedServer; | ||
| await this.applyServer(savedServer); | ||
| } | ||
| } | ||
| /** 网关切换处理 */ | ||
| async onGatewayChange() { | ||
| const gw = this.gwSelect.value; | ||
| localStorage.setItem(KEY_GATEWAY, gw); | ||
| await this.loadServers(gw); | ||
| } | ||
| /** 服务类型切换处理 */ | ||
| onTypeChange() { | ||
| const type = this.typeSelect.value; | ||
| localStorage.setItem(KEY_TYPE, type); | ||
| this.renderServerOptions(type); | ||
| } | ||
| /** 服务切换处理 */ | ||
| async onServerChange() { | ||
| const key = this.serverSelect.value; | ||
| localStorage.setItem(KEY_SERVER, key); | ||
| await this.applyServer(key); | ||
| } | ||
| /** | ||
| * 从网关加载服务列表 | ||
| * @param gwUrl 网关 URL | ||
| */ | ||
| async loadServers(gwUrl) { | ||
| this.setStatus("\u52A0\u8F7D\u4E2D...", ""); | ||
| const result = await Get(`${gwUrl}/web-debug/host/list`, { | ||
| cache: true, | ||
| timeout: this.options.timeout | ||
| }); | ||
| if (result.error) { | ||
| this.setStatus(`\u52A0\u8F7D\u5931\u8D25: ${result.error.message}`, "err"); | ||
| return; | ||
| } | ||
| this.servers = result.data ?? []; | ||
| this.renderTypeOptions(); | ||
| this.setStatus(`\u5DF2\u52A0\u8F7D ${this.servers.length} \u4E2A\u670D\u52A1`, "ok"); | ||
| } | ||
| /** 渲染服务类型选项 */ | ||
| renderTypeOptions() { | ||
| const types = [...new Set(this.servers.map((s) => s.type))]; | ||
| this.typeSelect.innerHTML = ""; | ||
| const allOpt = document.createElement("option"); | ||
| allOpt.value = ""; | ||
| allOpt.textContent = "\u5168\u90E8"; | ||
| this.typeSelect.appendChild(allOpt); | ||
| types.forEach((t) => { | ||
| const opt = document.createElement("option"); | ||
| opt.value = t; | ||
| opt.textContent = t; | ||
| this.typeSelect.appendChild(opt); | ||
| }); | ||
| this.renderServerOptions(this.typeSelect.value); | ||
| } | ||
| /** 根据类型渲染服务选项 */ | ||
| renderServerOptions(type) { | ||
| const filtered = type ? this.servers.filter((s) => s.type === type) : this.servers; | ||
| this.serverSelect.innerHTML = ""; | ||
| const emptyOpt = document.createElement("option"); | ||
| emptyOpt.value = ""; | ||
| emptyOpt.textContent = "\u8BF7\u9009\u62E9\u670D\u52A1"; | ||
| this.serverSelect.appendChild(emptyOpt); | ||
| filtered.forEach((s) => { | ||
| const opt = document.createElement("option"); | ||
| opt.value = s.key; | ||
| opt.textContent = `${s.name} (${s.key})`; | ||
| this.serverSelect.appendChild(opt); | ||
| }); | ||
| } | ||
| /** | ||
| * 切换目标服务,重新加载 Context 路由映射 | ||
| * @param key 服务 key | ||
| */ | ||
| async applyServer(key) { | ||
| if (!key) { | ||
| this.renderCtxList({}); | ||
| return; | ||
| } | ||
| const server = this.servers.find((s) => s.key === key); | ||
| if (!server) return; | ||
| this.setStatus("\u5207\u6362\u670D\u52A1\u4E2D...", ""); | ||
| try { | ||
| await Context.load(server.origin, this.options.timeout); | ||
| const ctxMap = {}; | ||
| const res = await fetch(`${server.origin}/ngw/context`); | ||
| const raw = res.ok ? await res.json() : {}; | ||
| for (const [k, v] of Object.entries(raw)) { | ||
| if (k !== "$info" && typeof v === "string") ctxMap[k] = v; | ||
| } | ||
| this.renderCtxList(ctxMap); | ||
| this.setStatus(`\u5DF2\u5207\u6362\u81F3 ${server.name}`, "ok"); | ||
| } catch (err) { | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| this.setStatus(`\u5207\u6362\u5931\u8D25: ${msg}`, "err"); | ||
| } | ||
| } | ||
| /** 渲染路由列表 */ | ||
| renderCtxList(ctxMap) { | ||
| this.ctxList.innerHTML = ""; | ||
| const entries = Object.entries(ctxMap); | ||
| if (entries.length === 0) { | ||
| this.ctxList.textContent = "\u6682\u65E0\u8DEF\u7531"; | ||
| return; | ||
| } | ||
| entries.forEach(([k, v]) => { | ||
| const item = document.createElement("div"); | ||
| item.className = "dbg-ctx-item"; | ||
| const keySpan = document.createElement("span"); | ||
| keySpan.className = "dbg-ctx-key"; | ||
| keySpan.textContent = k; | ||
| const valSpan = document.createElement("span"); | ||
| valSpan.className = "dbg-ctx-val"; | ||
| valSpan.textContent = v; | ||
| valSpan.setAttribute("title", v); | ||
| item.appendChild(keySpan); | ||
| item.appendChild(valSpan); | ||
| item.addEventListener("click", () => this.copyText(k)); | ||
| this.ctxList.appendChild(item); | ||
| }); | ||
| } | ||
| /** 复制文本到剪贴板 */ | ||
| copyText(text) { | ||
| navigator.clipboard.writeText(text).then(() => { | ||
| this.setStatus(`\u5DF2\u590D\u5236: ${text}`, "ok"); | ||
| setTimeout(() => this.setStatus("", ""), 2e3); | ||
| }); | ||
| } | ||
| setStatus(msg, cls) { | ||
| this.statusEl.textContent = msg; | ||
| this.statusEl.className = `dbg-status${cls ? ` ${cls}` : ""}`; | ||
| } | ||
| }; | ||
| export { Debugger }; | ||
| //# sourceMappingURL=chunk-QBBEQHXG.js.map | ||
| //# sourceMappingURL=chunk-QBBEQHXG.js.map |
| {"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"]} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
4596195
4.58%50806
4.06%30
-3.23%