UNPKG

chrome-cmd

Version:

Control Chrome from the command line - List tabs, execute JavaScript, and more

1,133 lines (1,096 loc) 39.1 kB
"use strict"; var ChromeExtension = (() => { // src/bridge/protocol/commands.ts var INTERNAL_COMMANDS = /* @__PURE__ */ new Set([ "PING" /* PING */, "RELOAD_EXTENSION" /* RELOAD_EXTENSION */, "GET_PROFILE_INFO" /* GET_PROFILE_INFO */, "START_LOGGING" /* START_LOGGING */, "STOP_LOGGING" /* STOP_LOGGING */, "CLEAR_TAB_LOGS" /* CLEAR_TAB_LOGS */, "CLEAR_TAB_REQUESTS" /* CLEAR_TAB_REQUESTS */, "CLICK_ELEMENT_BY_TEXT" /* CLICK_ELEMENT_BY_TEXT */, "keepalive" ]); function isInternalCommand(command) { return INTERNAL_COMMANDS.has(command); } // src/bridge/bridge.config.ts var BRIDGE_CONFIG = { PORT: 8765, HOST: "localhost", PORT_START: 8765, PORT_END: 8774, RECONNECT_BASE_DELAY: 1e3, MAX_RECONNECT_DELAY: 3e4, REGISTER_COMMAND_DELAY: 100, KEEPALIVE_INTERVAL: 3e4 }; // src/shared/constants/constants.ts var CLI_NAME = "chrome-cmd"; var BRIDGE_APP_NAME = "com.chrome_cli.bridge"; // src/extension/background/bridge-client.ts var bridgePort = null; var reconnectAttempts = 0; var keepaliveInterval = null; function updateConnectionStatus(connected) { chrome.storage.local.set({ bridgeConnected: connected }); console.log("[Background] Connection status updated:", connected ? "CONNECTED" : "DISCONNECTED"); const iconSuffix = connected ? "-connected" : "-disconnected"; chrome.action.setIcon({ path: { "16": `icons/icon16${iconSuffix}.png`, "48": `icons/icon48${iconSuffix}.png`, "128": `icons/icon128${iconSuffix}.png` } }); console.log("[Background] Icon updated:", iconSuffix); } async function sendRegisterCommand() { if (!bridgePort) { console.error("[Background] Cannot send REGISTER: bridgePort is null"); return; } const extensionId = chrome.runtime.id; const id = `register_${Date.now()}`; const installationId = await new Promise((resolve) => { chrome.storage.local.get(["installationId"], (result) => { if (result.installationId) { resolve(result.installationId); } else { const newId = crypto.randomUUID(); chrome.storage.local.set({ installationId: newId }, () => { resolve(newId); }); } }); }); let profileName = extensionId; try { const userInfo = await chrome.identity.getProfileUserInfo(); if (userInfo.email) { profileName = userInfo.email; } } catch (error) { console.error("[Background] Failed to get user email:", error); } console.log("[Background] Registering profile:", profileName); bridgePort.postMessage({ command: "REGISTER", id, data: { extensionId, installationId, profileName } }); } function connectToBridge(handleCommand2) { if (bridgePort) { console.log("[Background] Already connected to bridge, skipping..."); return; } try { bridgePort = chrome.runtime.connectNative(BRIDGE_APP_NAME); bridgePort.onMessage.addListener((message) => { console.log("[Background] Received from bridge:", message); handleCommand2(message); }); bridgePort.onDisconnect.addListener(() => { const lastError = chrome.runtime.lastError; console.log("[Background] Bridge disconnected:", lastError?.message || "Unknown reason"); bridgePort = null; updateConnectionStatus(false); if (keepaliveInterval) { clearInterval(keepaliveInterval); keepaliveInterval = null; } reconnectAttempts++; const delay = Math.min( BRIDGE_CONFIG.RECONNECT_BASE_DELAY * 2 ** (reconnectAttempts - 1), BRIDGE_CONFIG.MAX_RECONNECT_DELAY ); console.log(`[Background] Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...`); setTimeout(() => { connectToBridge(handleCommand2); }, delay); }); console.log("[Background] Connected to bridge"); reconnectAttempts = 0; setTimeout(async () => { if (bridgePort) { await sendRegisterCommand(); } }, BRIDGE_CONFIG.REGISTER_COMMAND_DELAY); if (keepaliveInterval) clearInterval(keepaliveInterval); keepaliveInterval = setInterval(() => { if (bridgePort) { try { bridgePort.postMessage({ command: "ping", id: `keepalive_${Date.now()}` }); } catch (error) { console.error("[Background] Keepalive failed:", error); } } }, BRIDGE_CONFIG.KEEPALIVE_INTERVAL); } catch (error) { console.error("[Background] Failed to connect to bridge:", error); updateConnectionStatus(false); reconnectAttempts++; const delay = Math.min( BRIDGE_CONFIG.RECONNECT_BASE_DELAY * 2 ** (reconnectAttempts - 1), BRIDGE_CONFIG.MAX_RECONNECT_DELAY ); setTimeout(() => { connectToBridge(handleCommand2); }, delay); } } function getBridgePort() { return bridgePort; } // src/extension/utils/extension-utils.ts function escapeJavaScriptString(text) { return text.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"); } function parseTabId(tabId) { return typeof tabId === "string" ? parseInt(tabId, 10) : tabId; } // src/extension/background/debugger-manager.ts var debuggerAttached = /* @__PURE__ */ new Set(); async function startLoggingTab(tabIdInt) { if (debuggerAttached.has(tabIdInt)) { console.log("[Background] Already logging tab", tabIdInt); return; } try { await chrome.debugger.attach({ tabId: tabIdInt }, "1.3"); debuggerAttached.add(tabIdInt); await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Console.enable"); await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Runtime.enable"); await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Log.enable"); await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Network.enable", { maxTotalBufferSize: 1e7, maxResourceBufferSize: 5e6, maxPostDataSize: 5e6 }); console.log("[Background] Started logging tab", tabIdInt); } catch (error) { debuggerAttached.delete(tabIdInt); throw error; } } async function stopLoggingTab(tabIdInt) { if (!debuggerAttached.has(tabIdInt)) { return; } try { await chrome.debugger.detach({ tabId: tabIdInt }); debuggerAttached.delete(tabIdInt); console.log("[Background] Stopped logging tab", tabIdInt); } catch (error) { console.error("[Background] Error stopping logging:", error); } } async function withDebugger(tabId, fn) { const wasAttached = debuggerAttached.has(tabId); if (!wasAttached) { await chrome.debugger.attach({ tabId }, "1.3"); debuggerAttached.add(tabId); } try { return await fn(); } finally { if (!wasAttached) { await chrome.debugger.detach({ tabId }); debuggerAttached.delete(tabId); } } } chrome.debugger.onDetach.addListener((source, reason) => { const tabId = source.tabId; if (tabId !== void 0) { debuggerAttached.delete(tabId); console.log("[Background] Debugger detached from tab", tabId, "reason:", reason); } }); // src/extension/background/logging-collector.ts var consoleLogs = /* @__PURE__ */ new Map(); var networkRequests = /* @__PURE__ */ new Map(); function initializeTabLogging(tabIdInt) { consoleLogs.set(tabIdInt, []); networkRequests.set(tabIdInt, []); } chrome.debugger.onEvent.addListener((source, method, params) => { const tabId = source.tabId; const MAX_REQUESTS_PER_TAB = 500; const MAX_LOGS_PER_TAB = 1e3; if (!tabId || !debuggerAttached.has(tabId)) { return; } if (method === "Runtime.consoleAPICalled") { const consoleParams = params; const logEntry = { type: consoleParams.type, timestamp: consoleParams.timestamp, args: consoleParams.args.map( (arg) => { if (arg.value !== void 0) { return arg.value; } if (arg.type === "object" || arg.type === "array") { if (arg.preview?.properties) { const obj = {}; for (const prop of arg.preview.properties) { obj[prop.name] = prop.value !== void 0 ? prop.value : prop.valuePreview?.description || prop.valuePreview?.type || "unknown"; } return obj; } if (arg.subtype === "array" && arg.preview && arg.preview.properties) { return arg.preview.properties.map((p) => p.value); } if (arg.description) { return arg.description; } } if (arg.description !== void 0) { return arg.description; } return String(arg.type || "unknown"); } ), stackTrace: consoleParams.stackTrace }; if (!consoleLogs.has(tabId)) { consoleLogs.set(tabId, []); } const logs = consoleLogs.get(tabId); if (logs) { logs.push(logEntry); if (logs.length > MAX_LOGS_PER_TAB) { logs.shift(); } } console.log(`[Background] Captured console.${consoleParams.type}`, tabId, logEntry.args); } if (method === "Runtime.exceptionThrown") { const exceptionParams = params; const logEntry = { type: "error", timestamp: exceptionParams.timestamp, args: [ exceptionParams.exceptionDetails.text || exceptionParams.exceptionDetails.exception?.description || "Error" ], stackTrace: exceptionParams.exceptionDetails.stackTrace }; if (!consoleLogs.has(tabId)) { consoleLogs.set(tabId, []); } const logs = consoleLogs.get(tabId); if (logs) { logs.push(logEntry); if (logs.length > MAX_LOGS_PER_TAB) { logs.shift(); } } console.log("[Background] Captured exception", tabId, logEntry.args); } if (method === "Log.entryAdded") { const logParams = params; const logEntry = { type: logParams.entry.level, timestamp: logParams.entry.timestamp, args: [logParams.entry.text], source: logParams.entry.source, url: logParams.entry.url, lineNumber: logParams.entry.lineNumber }; if (!consoleLogs.has(tabId)) { consoleLogs.set(tabId, []); } const logs = consoleLogs.get(tabId); if (logs) { logs.push(logEntry); if (logs.length > MAX_LOGS_PER_TAB) { logs.shift(); } } console.log("[Background] Captured log entry", tabId, logEntry.args); } if (method === "Network.requestWillBeSent") { const networkParams = params; const requestId = networkParams.requestId; const request = networkParams.request; const requestEntry = { requestId, url: request.url, method: request.method, headers: request.headers, postData: request.postData, timestamp: networkParams.timestamp, type: networkParams.type, initiator: networkParams.initiator }; if (!networkRequests.has(tabId)) { networkRequests.set(tabId, []); } const requests = networkRequests.get(tabId); if (requests) { requests.push(requestEntry); if (requests.length > MAX_REQUESTS_PER_TAB) { requests.shift(); } } console.log("[Background] Captured request", tabId, request.method, request.url); } if (method === "Network.requestWillBeSentExtraInfo") { const extraInfoParams = params; const requestId = extraInfoParams.requestId; if (!networkRequests.has(tabId)) { return; } const requests = networkRequests.get(tabId); if (requests) { const requestEntry = requests.find((r) => r.requestId === requestId); if (requestEntry) { requestEntry.headers = { ...requestEntry.headers, ...extraInfoParams.headers }; } } } if (method === "Network.responseReceived") { const responseParams = params; const requestId = responseParams.requestId; const response = responseParams.response; if (!networkRequests.has(tabId)) { return; } const requests = networkRequests.get(tabId); if (requests) { const requestEntry = requests.find((r) => r.requestId === requestId); if (requestEntry) { requestEntry.response = { status: response.status, statusText: response.statusText, headers: response.headers, mimeType: response.mimeType, timing: response.timing }; } } } if (method === "Network.responseReceivedExtraInfo") { const extraResponseParams = params; const requestId = extraResponseParams.requestId; if (!networkRequests.has(tabId)) { return; } const requests = networkRequests.get(tabId); if (requests) { const requestEntry = requests.find((r) => r.requestId === requestId); if (requestEntry?.response) { requestEntry.response.headers = { ...requestEntry.response.headers, ...extraResponseParams.headers }; } } } if (method === "Network.loadingFinished") { const finishedParams = params; const requestId = finishedParams.requestId; if (!networkRequests.has(tabId)) { return; } const requests = networkRequests.get(tabId); if (requests) { const requestEntry = requests.find((r) => r.requestId === requestId); if (requestEntry) { requestEntry.finished = true; requestEntry.encodedDataLength = finishedParams.encodedDataLength; const shouldCaptureBody = (requestEntry.type === "XHR" || requestEntry.type === "Fetch") && finishedParams.encodedDataLength < 1024 * 1024 * 5; if (shouldCaptureBody && requestEntry.response) { chrome.debugger.sendCommand({ tabId }, "Network.getResponseBody", { requestId }).then((response) => { const bodyResponse = response; if (bodyResponse.body) { requestEntry.responseBody = bodyResponse.body; requestEntry.responseBodyBase64 = bodyResponse.base64Encoded; } }).catch((error) => { const errorMsg = String(error?.message || error || ""); if (!errorMsg.includes("quota")) { console.warn("[Network.loadingFinished] Error getting body for:", requestEntry.url, errorMsg); } }); } } } } if (method === "Network.loadingFailed") { const failedParams = params; const requestId = failedParams.requestId; if (!networkRequests.has(tabId)) { return; } const requests = networkRequests.get(tabId); if (requests) { const requestEntry = requests.find((r) => r.requestId === requestId); if (requestEntry) { requestEntry.failed = true; requestEntry.errorText = failedParams.errorText; requestEntry.canceled = failedParams.canceled; } } } }); // src/extension/background/commands/inspection/get-logs.ts async function getTabLogs({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); if (!debuggerAttached.has(tabIdInt)) { throw new Error( `Debugger not attached to this tab. Use "${CLI_NAME} tabs select --tab <index>" first to start logging.` ); } const logs = consoleLogs.get(tabIdInt) || []; if (logs.length === 0) { return [ { type: "info", timestamp: Date.now(), message: "No console logs captured yet. Interact with the page to see new logs." } ]; } return logs; } // src/shared/utils/helpers/error-utils.ts function formatErrorMessage(error) { if (error instanceof Error) { return error.message; } return String(error); } // src/extension/background/commands/inspection/get-requests.ts async function getTabRequests({ tabId, includeCookies }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); if (!debuggerAttached.has(tabIdInt)) { throw new Error( `Debugger not attached to this tab. Use "${CLI_NAME} tabs select --tab <index>" first to start logging.` ); } const requests = networkRequests.get(tabIdInt) || []; if (requests.length === 0) { return [ { type: "info", timestamp: Date.now(), message: "No network requests captured yet. Reload the page or interact with it to see new requests." } ]; } if (includeCookies && debuggerAttached.has(tabIdInt)) { for (const request of requests) { if (request.headers.Cookie || request.headers.cookie) { continue; } try { const cookiesResponse = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Network.getCookies", { urls: [request.url] }); const cookies = cookiesResponse.cookies; if (cookies && cookies.length > 0) { const cookieHeader = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; "); if (cookieHeader) { request.headers = request.headers || {}; request.headers.Cookie = cookieHeader; } } } catch (error) { const errorMessage = formatErrorMessage(error); if (errorMessage.includes("quota exceeded")) { console.warn("[getTabRequests] Quota exceeded while getting cookies for:", request.url); break; } } } } return requests; } // src/extension/background/commands/inspection/get-storage.ts async function getTabStorage({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); try { const tab = await chrome.tabs.get(tabIdInt); const tabUrl = tab.url; return await withDebugger(tabIdInt, async () => { const cookiesResponse = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Network.getCookies", { urls: [tabUrl] }); const localStorageResult = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Runtime.evaluate", { expression: ` (() => { const items = {}; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); items[key] = localStorage.getItem(key); } return items; })() `, returnByValue: true }); const sessionStorageResult = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Runtime.evaluate", { expression: ` (() => { const items = {}; for (let i = 0; i < sessionStorage.length; i++) { const key = sessionStorage.key(i); items[key] = sessionStorage.getItem(key); } return items; })() `, returnByValue: true }); const cookiesResult = cookiesResponse; const localStorageEval = localStorageResult; const sessionStorageEval = sessionStorageResult; const cookies = (cookiesResult.cookies || []).map((cookie) => ({ name: cookie.name, value: cookie.value, domain: cookie.domain, path: cookie.path, expires: cookie.expires, size: cookie.size, httpOnly: cookie.httpOnly, secure: cookie.secure, sameSite: cookie.sameSite })); const localStorage = localStorageEval.result?.value || {}; const sessionStorage = sessionStorageEval.result?.value || {}; return { cookies, localStorage, sessionStorage }; }); } catch (error) { throw new Error(`Failed to get storage data: ${formatErrorMessage(error)}`); } } // src/extension/background/commands/inspection/screenshot.ts async function captureScreenshot({ tabId, format = "png", quality = 90, fullPage = true }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); const startTime = Date.now(); try { console.log("[Background] \u{1F50D} Capturing screenshot for tab", tabIdInt); const dataUrl = await withDebugger(tabIdInt, async () => { console.log("[Background] \u23F1\uFE0F Starting Page.captureScreenshot (", Date.now() - startTime, "ms )"); const screenshotParams = { format, optimizeForSpeed: true, fromSurface: true, captureBeyondViewport: fullPage }; if (format === "jpeg") { screenshotParams.quality = quality; } const result = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Page.captureScreenshot", screenshotParams); console.log( "[Background] \u23F1\uFE0F Screenshot captured! Size:", result.data.length, "bytes (", Date.now() - startTime, "ms )" ); return `data:image/${format};base64,${result.data}`; }); const totalTime = Date.now() - startTime; console.log("[Background] \u2705 TOTAL TIME:", totalTime, "ms (", (totalTime / 1e3).toFixed(2), "seconds )"); return { success: true, dataUrl, format, captureTimeMs: totalTime }; } catch (error) { const totalTime = Date.now() - startTime; console.error("[Background] \u274C Error capturing screenshot after", totalTime, "ms:", error); throw new Error(`Failed to capture screenshot: ${formatErrorMessage(error)}`); } } // src/extension/background/commands/interaction/click.ts async function clickElementByText({ tabId, text }) { if (!tabId) { throw new Error("tabId is required"); } if (!text) { throw new Error("text is required"); } const tabIdInt = parseTabId(tabId); try { const escapedText = escapeJavaScriptString(text); return await withDebugger(tabIdInt, async () => { const result = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Runtime.evaluate", { expression: ` (() => { const elements = Array.from(document.querySelectorAll('*')); const element = elements.find(el => { const text = Array.from(el.childNodes) .filter(node => node.nodeType === Node.TEXT_NODE) .map(node => node.textContent.trim()) .join(' '); return text === '${escapedText}' || el.textContent.trim() === '${escapedText}'; }); if (!element) { throw new Error('Element not found with text: ${escapedText}'); } element.click(); return { success: true }; })() `, returnByValue: true, awaitPromise: true }); const evaluateResult = result; if (evaluateResult.exceptionDetails) { throw new Error(evaluateResult.exceptionDetails.exception?.description || "Failed to click element by text"); } return { success: true }; }); } catch (error) { throw new Error(`Failed to click element by text: ${formatErrorMessage(error)}`); } } async function clickElement({ tabId, selector }) { if (!tabId) { throw new Error("tabId is required"); } if (!selector) { throw new Error("selector is required"); } const tabIdInt = parseTabId(tabId); try { return await withDebugger(tabIdInt, async () => { const result = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Runtime.evaluate", { expression: ` (() => { const element = document.querySelector('${selector}'); if (!element) { throw new Error('Element not found: ${selector}'); } element.click(); return { success: true }; })() `, returnByValue: true, awaitPromise: true }); const evaluateResult = result; if (evaluateResult.exceptionDetails) { throw new Error(evaluateResult.exceptionDetails.exception?.description || "Failed to click element"); } return { success: true }; }); } catch (error) { throw new Error(`Failed to click element: ${formatErrorMessage(error)}`); } } // src/extension/background/commands/interaction/execute-script.ts async function executeScript({ tabId, code }) { if (!tabId || !code) { throw new Error("tabId and code are required"); } const tabIdInt = parseTabId(tabId); console.log("[Background] Executing script in tab", tabIdInt, ":", code); return withDebugger(tabIdInt, async () => { const result = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Runtime.evaluate", { expression: code, returnByValue: true, awaitPromise: true }); console.log("[Background] Debugger result:", result); const evaluateResult = result; if (evaluateResult.exceptionDetails) { throw new Error(evaluateResult.exceptionDetails.exception?.description || "Script execution failed"); } return evaluateResult.result?.value; }); } // src/extension/background/commands/interaction/input.ts async function fillInput({ tabId, selector, value, submit = false }) { if (!tabId) { throw new Error("tabId is required"); } if (!selector) { throw new Error("selector is required"); } if (!value) { throw new Error("value is required"); } const tabIdInt = parseTabId(tabId); try { const escapedSelector = escapeJavaScriptString(selector); const escapedValue = escapeJavaScriptString(value); return await withDebugger(tabIdInt, async () => { const setValueResult = await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Runtime.evaluate", { expression: ` (() => { const element = document.querySelector('${escapedSelector}'); if (!element) { throw new Error('Element not found: ${escapedSelector}'); } element.focus(); const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, 'value' ).set; nativeInputValueSetter.call(element, '${escapedValue}'); element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); return element.value; })() `, returnByValue: true }); const evaluateResult = setValueResult; console.log("[Background] Input value set to:", evaluateResult.result?.value); if (submit) { const INPUT_SUBMIT_DELAY = 150; await new Promise((resolve) => setTimeout(resolve, INPUT_SUBMIT_DELAY)); await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Input.dispatchKeyEvent", { type: "rawKeyDown", key: "Enter", code: "Enter", windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13 }); await chrome.debugger.sendCommand({ tabId: tabIdInt }, "Input.dispatchKeyEvent", { type: "keyUp", key: "Enter", code: "Enter", windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13 }); console.log("[Background] Enter key pressed"); } return { success: true }; }); } catch (error) { throw new Error(`Failed to fill input: ${formatErrorMessage(error)}`); } } // src/extension/background/commands/internal/clear-logs.ts async function clearTabLogs({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); consoleLogs.set(tabIdInt, []); return { success: true, message: "Logs cleared" }; } // src/extension/background/commands/internal/clear-requests.ts async function clearTabRequests({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); networkRequests.set(tabIdInt, []); return { success: true, message: "Requests cleared" }; } // src/extension/background/commands/internal/get-profile-info.ts async function getProfileInfo() { try { const extensionInfo = await chrome.management.getSelf(); const profileName = extensionInfo.installType === "development" ? "Developer Profile" : "Chrome User"; let detectedProfileName = profileName; if (chrome.identity?.getProfileUserInfo) { try { const userInfo = await chrome.identity.getProfileUserInfo(); if (userInfo?.email) { detectedProfileName = userInfo.email; } } catch { } } return { profileName: detectedProfileName }; } catch (error) { console.error("[Background] Error getting profile info:", error); return { profileName: "Chrome User" }; } } // src/extension/background/commands/internal/ping.ts async function ping() { return { status: "ok", message: "pong" }; } // src/extension/background/commands/internal/reload-extension.ts async function reloadExtension() { console.log("[Background] Reloading extension..."); chrome.runtime.reload(); return { success: true, message: "Extension reloaded" }; } // src/extension/background/commands/internal/start-logging.ts async function startLogging({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); initializeTabLogging(tabIdInt); await startLoggingTab(tabIdInt); return { success: true, message: "Started logging console and network activity", tabId: tabIdInt, debuggerAttached: true }; } // src/extension/background/commands/internal/stop-logging.ts async function stopLogging({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); await stopLoggingTab(tabIdInt); return { success: true, message: "Stopped logging", tabId: tabIdInt, debuggerAttached: false }; } // src/extension/background/commands/navigation/close.ts async function closeTab({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } await chrome.tabs.remove(parseTabId(tabId)); return { success: true }; } // src/extension/background/commands/navigation/create.ts async function createTab({ url, active = true }) { const tab = await chrome.tabs.create({ url: url || "about:blank", active }); if (tab.id === void 0) { throw new Error("Created tab has no ID"); } return { success: true, tab: { windowId: tab.windowId ?? 0, tabId: tab.id, title: tab.title, url: tab.url } }; } // src/extension/background/commands/navigation/focus.ts async function activateTab({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); const tab = await chrome.tabs.get(tabIdInt); if (tab.windowId) { await chrome.windows.update(tab.windowId, { focused: true }); } await chrome.tabs.update(tabIdInt, { active: true }); return { success: true }; } // src/extension/background/commands/navigation/list.ts async function listTabs() { const windows = await chrome.windows.getAll({ populate: true }); const tabs = []; for (const window of windows) { if (!window.tabs || window.id === void 0) continue; for (const tab of window.tabs) { if (tab.id === void 0) continue; tabs.push({ windowId: window.id, tabId: tab.id, title: tab.title, url: tab.url, active: tab.active, index: tab.index }); } } return tabs; } // src/extension/background/commands/navigation/navigate.ts async function navigateTab({ tabId, url }) { if (!tabId) { throw new Error("tabId is required"); } if (!url) { throw new Error("url is required"); } await chrome.tabs.update(parseTabId(tabId), { url }); return { success: true }; } // src/extension/background/commands/navigation/refresh.ts async function reloadTab({ tabId }) { if (!tabId) { throw new Error("tabId is required"); } const tabIdInt = parseTabId(tabId); const wasLogging = debuggerAttached.has(tabIdInt); if (wasLogging) { initializeTabLogging(tabIdInt); } await chrome.tabs.reload(tabIdInt); if (wasLogging) { await new Promise((resolve) => setTimeout(resolve, 500)); await startLoggingTab(tabIdInt); } return { success: true }; } // src/extension/background/command-handlers.ts var commandHandlers = { ["TAB_LIST" /* TAB_LIST */]: async () => listTabs(), ["TAB_EXEC" /* TAB_EXEC */]: async (data) => executeScript(data), ["TAB_CLOSE" /* TAB_CLOSE */]: async (data) => closeTab(data), ["TAB_FOCUS" /* TAB_FOCUS */]: async (data) => activateTab(data), ["TAB_CREATE" /* TAB_CREATE */]: async (data) => createTab(data), ["TAB_REFRESH" /* TAB_REFRESH */]: async (data) => reloadTab(data), ["TAB_LOGS" /* TAB_LOGS */]: async (data) => getTabLogs(data), ["CLEAR_TAB_LOGS" /* CLEAR_TAB_LOGS */]: async (data) => clearTabLogs(data), ["TAB_REQUESTS" /* TAB_REQUESTS */]: async (data) => getTabRequests(data), ["CLEAR_TAB_REQUESTS" /* CLEAR_TAB_REQUESTS */]: async (data) => clearTabRequests(data), ["TAB_STORAGE" /* TAB_STORAGE */]: async (data) => getTabStorage(data), ["TAB_NAVIGATE" /* TAB_NAVIGATE */]: async (data) => navigateTab(data), ["TAB_SCREENSHOT" /* TAB_SCREENSHOT */]: async (data) => captureScreenshot(data), ["TAB_CLICK" /* TAB_CLICK */]: async (data) => clickElement(data), ["CLICK_ELEMENT_BY_TEXT" /* CLICK_ELEMENT_BY_TEXT */]: async (data) => clickElementByText(data), ["TAB_INPUT" /* TAB_INPUT */]: async (data) => fillInput(data), ["START_LOGGING" /* START_LOGGING */]: async (data) => startLogging(data), ["STOP_LOGGING" /* STOP_LOGGING */]: async (data) => stopLogging(data), ["RELOAD_EXTENSION" /* RELOAD_EXTENSION */]: async () => reloadExtension(), ["GET_PROFILE_INFO" /* GET_PROFILE_INFO */]: async () => getProfileInfo(), ["PING" /* PING */]: async () => ping() }; // src/extension/background/history-manager.ts async function saveCommandToHistory(command, data, result, success, executionTime, error) { if (isInternalCommand(command)) { return; } const isUserCommand = !isInternalCommand(command); const historyItem = { command, data, timestamp: Date.now(), result, success, executionTime, error, isUserCommand }; const storageResult = await chrome.storage.local.get(["commandHistory"]); const history = storageResult.commandHistory || []; history.push(historyItem); const MAX_HISTORY_ITEMS = 100; if (history.length > MAX_HISTORY_ITEMS) { history.shift(); } await chrome.storage.local.set({ commandHistory: history }); } // src/extension/background.ts async function dispatchCommand(request, handlers) { const handler = handlers[request.command]; if (!handler) { throw new Error(`No handler registered for command: ${request.command}`); } return handler(request.data); } async function handleCommand(message) { const { command, data = {}, id } = message; if (id.startsWith("register_") && "success" in message) { const response = message; if (response.success) { console.log("[Background] Registration successful"); updateConnectionStatus(true); } else { console.error("[Background] Registration failed:", response.error); updateConnectionStatus(false); } return; } const startTime = Date.now(); try { const request = { command, data }; const result = await dispatchCommand(request, commandHandlers); const executionTime = Date.now() - startTime; await saveCommandToHistory(command, data, result, true, executionTime); const bridgePort2 = getBridgePort(); if (bridgePort2) { bridgePort2.postMessage({ id, success: true, result }); } } catch (error) { const executionTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : String(error); await saveCommandToHistory(command, data, void 0, false, executionTime, errorMessage); const bridgePort2 = getBridgePort(); if (bridgePort2) { bridgePort2.postMessage({ id, success: false, error: errorMessage }); } } } chrome.tabs.onRemoved.addListener((tabId) => { debuggerAttached.delete(tabId); consoleLogs.delete(tabId); networkRequests.delete(tabId); console.log("[Background] Tab removed, cleaned up logs and requests:", tabId); }); chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { console.log("[Background] Received message from popup:", message); if (message.command === "RELOAD_EXTENSION" /* RELOAD_EXTENSION */) { const handler = commandHandlers["RELOAD_EXTENSION" /* RELOAD_EXTENSION */]; if (handler) { handler({}).then((result) => { sendResponse(result); }).catch((error) => { sendResponse({ success: false, error: error.message }); }); return true; } } sendResponse({ success: false, error: "Unknown command" }); return false; }); console.log("[Background] Service worker started"); updateConnectionStatus(false); connectToBridge(handleCommand); })();