function createDebugContentView({ DEBUG_PANEL_ID, MODAL_ID, debugState, formatDebugBody, formatHeaderBlock, formatMetadataBlock, environment }) { const { document } = environment; const categories = [ ["all", "All"], ["authentication", "Authentication"], ["opportunities", "Opportunities"], ["products", "Products"], ["ai", "AI"], ["consumption", "Consumption"], ["installBase", "Installed Base"], ["serviceRequests", "Service Requests"], ["other", "Other"] ]; let selectedCategory = "all"; const expandedIds = new Set(); function getRequestCategory(entry) { if (/^request(?:TokenRelay|ConsumerTokenService|CustomerTokenService)/.test(entry.name)) return "authentication"; if (entry.name === "requestOpportunities") return "opportunities"; if (entry.name === "requestOpptyProducts") return "products"; if (entry.name === "requestGenerateAiSummary") return "ai"; if (entry.name === "requestInstallBase") return "installBase"; if (entry.name === "requestCustomerServiceRequest") return "serviceRequests"; if (/^request(?:AccountSummary|AccountPlans|ConsumptionService|ConsumptionPlanDetail|RatecardByPlan)/.test(entry.name)) return "consumption"; return "other"; } function getChronologicalRequests() { return [...debugState.requestLog].sort((a, b) => (new Date(a.startedAt).getTime() || 0) - (new Date(b.startedAt).getTime() || 0) || a.id - b.id); } function toggleDebugPanel() { const modal = document.getElementById(MODAL_ID); if (!modal || modal.hidden) { return; } const panel = document.getElementById(DEBUG_PANEL_ID); if (!panel) { return; } panel.hidden = !panel.hidden; renderDebugPanel(); } function renderDebugPanel() { const panel = document.getElementById(DEBUG_PANEL_ID); if (!panel || panel.hidden) { return; } for (const item of panel.querySelectorAll("details[data-request-id]")) { const id = item.getAttribute("data-request-id"); if (item.open) expandedIds.add(id); else expandedIds.delete(id); } const focusedCategory = document.activeElement && document.activeElement.getAttribute("data-debug-category"); const scrollTop = panel.scrollTop; panel.textContent = ""; const panelHeader = document.createElement("header"); panelHeader.className = "opportunities-extension-debug-panel-header"; const heading = document.createElement("div"); const title = document.createElement("h2"); title.textContent = "Debug Requests"; const summary = document.createElement("p"); summary.textContent = `${debugState.requestLog.length} request${debugState.requestLog.length === 1 ? "" : "s"} since modal opened.`; const running = debugState.requestLog.filter(entry => entry.status === "pending").length; if (running) summary.textContent += ` ${running} running.`; heading.append(title, summary); const visibilityButton = document.createElement("button"); visibilityButton.type = "button"; visibilityButton.className = "opportunities-extension-icon-button opportunities-extension-debug-visibility-button"; visibilityButton.setAttribute("aria-pressed", String(debugState.debugSecretsVisible)); visibilityButton.setAttribute("aria-label", debugState.debugSecretsVisible ? "Hide sensitive values" : "Show sensitive values"); visibilityButton.title = debugState.debugSecretsVisible ? "Hide sensitive values" : "Show sensitive values"; visibilityButton.append(createDebugVisibilityIcon(debugState.debugSecretsVisible)); visibilityButton.addEventListener("click", () => { debugState.debugSecretsVisible = !debugState.debugSecretsVisible; renderDebugPanel(); }); panelHeader.append(heading, visibilityButton); const entries = getChronologicalRequests(); const availableCategories = categories.filter(([key]) => key === "all" || entries.some(entry => getRequestCategory(entry) === key)); if (!availableCategories.some(([key]) => key === selectedCategory)) selectedCategory = "all"; const tabs = document.createElement("div"); tabs.className = "opportunities-extension-debug-tabs"; tabs.setAttribute("role", "tablist"); tabs.setAttribute("aria-label", "Request categories"); const tabButtons = []; availableCategories.forEach(([key, label], index) => { const categoryEntries = key === "all" ? entries : entries.filter(entry => getRequestCategory(entry) === key); const pending = categoryEntries.filter(entry => entry.status === "pending").length; const button = document.createElement("button"); button.type = "button"; button.id = `${DEBUG_PANEL_ID}-tab-${key}`; button.setAttribute("role", "tab"); button.setAttribute("data-debug-category", key); button.setAttribute("aria-selected", String(key === selectedCategory)); button.setAttribute("aria-controls", `${DEBUG_PANEL_ID}-requests`); button.tabIndex = key === selectedCategory ? 0 : -1; button.textContent = `${label} (${categoryEntries.length})${pending ? ` / ${pending} running` : ""}`; button.addEventListener("click", () => { selectedCategory = key; renderDebugPanel(); }); button.addEventListener("keydown", event => { let nextIndex; if (event.key === "ArrowRight") nextIndex = (index + 1) % availableCategories.length; else if (event.key === "ArrowLeft") nextIndex = (index + availableCategories.length - 1) % availableCategories.length; else if (event.key === "Home") nextIndex = 0; else if (event.key === "End") nextIndex = availableCategories.length - 1; else return; event.preventDefault(); selectedCategory = availableCategories[nextIndex][0]; renderDebugPanel(); }); tabs.append(button); tabButtons.push(button); }); const list = document.createElement("div"); list.className = "opportunities-extension-debug-list"; list.id = `${DEBUG_PANEL_ID}-requests`; list.setAttribute("role", "tabpanel"); list.setAttribute("aria-labelledby", `${DEBUG_PANEL_ID}-tab-${selectedCategory}`); list.tabIndex = 0; if (debugState.requestLog.length === 0) { const empty = document.createElement("div"); empty.className = "opportunities-extension-debug-empty"; empty.textContent = "No requests recorded yet."; list.append(empty); } entries.filter(entry => selectedCategory === "all" || getRequestCategory(entry) === selectedCategory).forEach((entry) => { const item = document.createElement("details"); item.className = `opportunities-extension-debug-item opportunities-extension-debug-item-${entry.status}`; item.setAttribute("data-request-id", String(entry.id)); item.open = expandedIds.has(String(entry.id)); const heading = document.createElement("summary"); heading.className = "opportunities-extension-debug-heading"; const name = document.createElement("strong"); name.textContent = `${entry.id}. ${entry.name}`; if (entry.name === "requestOpptyProducts" && entry.metadata.optyNumber) { name.textContent += ` (${entry.metadata.optyNumber}, offset ${entry.metadata.offset})`; } const httpStatus = document.createElement("span"); httpStatus.className = "opportunities-extension-debug-http-status"; httpStatus.textContent = entry.httpStatus ? String(entry.httpStatus) : "pending"; const status = document.createElement("span"); status.className = "opportunities-extension-debug-status"; status.textContent = entry.status === "pending" ? "running" : entry.status; const summaryMeta = document.createElement("span"); summaryMeta.className = "opportunities-extension-debug-summary-meta"; const startedTime = document.createElement("time"); startedTime.className = "opportunities-extension-debug-time"; startedTime.textContent = formatDebugTime(entry.startedAt); startedTime.title = `Started: ${entry.startedAt.toLocaleString()}`; summaryMeta.append(startedTime, httpStatus, status); heading.append(name, summaryMeta); const lines = [ `${entry.method} ${entry.url}`, `Started: ${formatDebugTime(entry.startedAt)}`, entry.completedAt ? `Completed: ${formatDebugTime(entry.completedAt)} (${entry.durationMs}ms)` : "Completed: pending", entry.httpStatus ? `HTTP: ${entry.httpStatus} ${entry.statusText}`.trim() : "", entry.error ? `Error: ${entry.error}` : "", `Response body truncated: ${entry.responseBodyTruncated ? "yes" : "no"}` ].filter(Boolean); const details = document.createElement("div"); details.className = "opportunities-extension-debug-details"; appendDebugBlock(details, "Request", lines.join("\n")); appendDebugBlock(details, "Request Metadata", formatMetadataBlock(entry.metadata)); appendDebugBlock(details, "Request Headers", formatHeaderBlock(entry.requestHeaders)); appendDebugBlock(details, "Request Body", formatDebugBody(entry.requestBody)); appendDebugBlock(details, "Response Headers", formatHeaderBlock(entry.responseHeaders)); appendDebugBlock(details, "Response Body", formatDebugBody(entry.responseBody)); item.append(heading, details); list.append(item); }); panel.append(panelHeader, tabs, list); if (focusedCategory) tabButtons.find(button => button.getAttribute("data-debug-category") === selectedCategory).focus({ preventScroll: true }); panel.scrollTop = scrollTop; } function createDebugVisibilityIcon(secretsVisible) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const eye = document.createElementNS("http://www.w3.org/2000/svg", "path"); const pupil = document.createElementNS("http://www.w3.org/2000/svg", "circle"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); eye.setAttribute("d", "M2.5 12s3.5-6.5 9.5-6.5 9.5 6.5 9.5 6.5-3.5 6.5-9.5 6.5S2.5 12 2.5 12Z"); pupil.setAttribute("cx", "12"); pupil.setAttribute("cy", "12"); pupil.setAttribute("r", "2.75"); [eye, pupil].forEach((shape) => { shape.setAttribute("fill", "none"); shape.setAttribute("stroke", "currentColor"); shape.setAttribute("stroke-linecap", "round"); shape.setAttribute("stroke-linejoin", "round"); shape.setAttribute("stroke-width", "1.7"); }); svg.append(eye, pupil); if (secretsVisible) { const slash = document.createElementNS("http://www.w3.org/2000/svg", "path"); slash.setAttribute("d", "M4 4l16 16"); slash.setAttribute("fill", "none"); slash.setAttribute("stroke", "currentColor"); slash.setAttribute("stroke-linecap", "round"); slash.setAttribute("stroke-width", "1.9"); svg.append(slash); } return svg; } function appendDebugBlock(container, label, value) { const block = document.createElement("section"); const heading = document.createElement("h3"); const content = document.createElement("pre"); heading.textContent = label; content.textContent = value; block.append(heading, content); container.append(block); } function formatDebugTime(date) { return date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } return { toggleDebugPanel, renderDebugPanel, createDebugVisibilityIcon, appendDebugBlock, formatDebugTime, getRequestCategory, getChronologicalRequests }; }