chrome-cmd
Version:
Control Chrome from the command line - List tabs, execute JavaScript, and more
1,133 lines (1,096 loc) • 39.1 kB
JavaScript
;
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);
})();