mirror of
https://github.com/AgentDeskAI/browser-tools-mcp.git
synced 2025-12-09 05:50:39 +00:00
v1
This commit is contained in:
parent
6cf3f14403
commit
c615cc635f
65
browser-tools-mcp/README.md
Normal file
65
browser-tools-mcp/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Browser Tools MCP Server
|
||||
|
||||
A Model Context Protocol (MCP) server that provides AI-powered browser tools integration. This server works in conjunction with the Browser Tools Server to provide AI capabilities for browser debugging and analysis.
|
||||
|
||||
## Features
|
||||
|
||||
- MCP protocol implementation
|
||||
- Browser console log access
|
||||
- Network request analysis
|
||||
- Screenshot capture capabilities
|
||||
- Element selection and inspection
|
||||
- Real-time browser state monitoring
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx @agentdeskai/browser-tools-mcp
|
||||
```
|
||||
|
||||
Or install globally:
|
||||
|
||||
```bash
|
||||
npm install -g @agentdeskai/browser-tools-mcp
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. First, make sure the Browser Tools Server is running:
|
||||
|
||||
```bash
|
||||
npx @agentdeskai/browser-tools-server
|
||||
```
|
||||
|
||||
2. Then start the MCP server:
|
||||
|
||||
```bash
|
||||
npx @agentdeskai/browser-tools-mcp
|
||||
```
|
||||
|
||||
3. The MCP server will connect to the Browser Tools Server and provide the following capabilities:
|
||||
|
||||
- Console log retrieval
|
||||
- Network request monitoring
|
||||
- Screenshot capture
|
||||
- Element selection
|
||||
- Browser state analysis
|
||||
|
||||
## MCP Functions
|
||||
|
||||
The server provides the following MCP functions:
|
||||
|
||||
- `mcp_getConsoleLogs` - Retrieve browser console logs
|
||||
- `mcp_getConsoleErrors` - Get browser console errors
|
||||
- `mcp_getNetworkErrors` - Get network error logs
|
||||
- `mcp_getNetworkSuccess` - Get successful network requests
|
||||
- `mcp_getNetworkLogs` - Get all network logs
|
||||
- `mcp_getSelectedElement` - Get the currently selected DOM element
|
||||
|
||||
## Integration
|
||||
|
||||
This server is designed to work with AI tools and platforms that support the Model Context Protocol (MCP). It provides a standardized interface for AI models to interact with browser state and debugging information.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@ -1,13 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import path from "path";
|
||||
import { z } from "zod";
|
||||
// import { z } from "zod";
|
||||
// import fs from "fs";
|
||||
|
||||
// Create the MCP server
|
||||
const server = new McpServer({
|
||||
name: "AI Browser Connector",
|
||||
version: "1.0.0",
|
||||
name: "Browsert Tools MCP",
|
||||
version: "1.0.9",
|
||||
});
|
||||
|
||||
// Function to get the port from the .port file
|
||||
@ -71,19 +73,19 @@ server.tool("getNetworkErrors", "Check our network ERROR logs", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
// Return all XHR/fetch requests
|
||||
server.tool("getNetworkSuccess", "Check our network SUCCESS logs", async () => {
|
||||
const response = await fetch(`http://127.0.0.1:${PORT}/all-xhr`);
|
||||
const json = await response.json();
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(json, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
// // Return all XHR/fetch requests
|
||||
// server.tool("getNetworkSuccess", "Check our network SUCCESS logs", async () => {
|
||||
// const response = await fetch(`http://127.0.0.1:${PORT}/all-xhr`);
|
||||
// const json = await response.json();
|
||||
// return {
|
||||
// content: [
|
||||
// {
|
||||
// type: "text",
|
||||
// text: JSON.stringify(json, null, 2),
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// });
|
||||
|
||||
// Return all XHR/fetch requests
|
||||
server.tool("getNetworkLogs", "Check ALL our network logs", async () => {
|
||||
@ -105,12 +107,12 @@ server.tool(
|
||||
"Take a screenshot of the current browser tab",
|
||||
async () => {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${PORT}/screenshot`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const response = await fetch(
|
||||
`http://127.0.0.1:${PORT}/capture-screenshot`,
|
||||
{
|
||||
method: "POST",
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
@ -168,6 +170,22 @@ server.tool(
|
||||
}
|
||||
);
|
||||
|
||||
// Add new tool for wiping logs
|
||||
server.tool("wipeLogs", "Wipe all browser logs from memory", async () => {
|
||||
const response = await fetch(`http://127.0.0.1:${PORT}/wipelogs`, {
|
||||
method: "POST",
|
||||
});
|
||||
const json = await response.json();
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: json.message,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Start receiving messages on stdio
|
||||
(async () => {
|
||||
const transport = new StdioServerTransport();
|
||||
@ -1,34 +1,37 @@
|
||||
{
|
||||
"name": "mcp-server",
|
||||
"version": "1.0.0",
|
||||
"name": "@agentdeskai/browser-tools-mcp",
|
||||
"version": "1.0.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mcp-server",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"name": "@agentdeskai/browser-tools-mcp",
|
||||
"version": "1.0.9",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||
"@types/ws": "^8.5.14",
|
||||
"body-parser": "^1.20.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"llm-cost": "^1.0.5",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"bin": {
|
||||
"browser-tools-mcp": "dist/mcp-server.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/body-parser": "^1.19.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/ws": "^8.5.14",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.4.1.tgz",
|
||||
"integrity": "sha512-wS6YC4lkUZ9QpP+/7NBTlVNiEvsnyl0xF7rRusLF+RsG0xDPc/zWR7fEEyhKnnNutGsDAZh59l/AeoWGwIb1+g==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.5.0.tgz",
|
||||
"integrity": "sha512-IJ+5iVVs8FCumIHxWqpwgkwOzyhtHVKy45s6Ug7Dv0MfRpaYisH8QQ87rIWeWdOzlk8sfhitZ7HCyQZk7d6b8w==",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"eventsource": "^3.0.2",
|
||||
@ -40,31 +43,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
||||
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.6.3",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
@ -130,9 +108,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
|
||||
"integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
|
||||
"version": "22.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
|
||||
"integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
}
|
||||
@ -174,6 +153,7 @@
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz",
|
||||
"integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@ -218,6 +198,20 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@ -227,9 +221,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
||||
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
@ -761,19 +755,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
||||
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"iconv-lite": "0.6.3",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@ -970,7 +975,8 @@
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
@ -1017,9 +1023,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||
"version": "3.24.2",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
||||
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
44
browser-tools-mcp/package.json
Normal file
44
browser-tools-mcp/package.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@agentdeskai/browser-tools-mcp",
|
||||
"version": "1.0.9",
|
||||
"description": "MCP (Model Context Protocol) server for browser tools integration",
|
||||
"main": "dist/mcp-server.js",
|
||||
"bin": {
|
||||
"browser-tools-mcp": "dist/mcp-server.js"
|
||||
},
|
||||
"scripts": {
|
||||
"inspect": "tsc && npx @modelcontextprotocol/inspector node -- dist/mcp-server.js",
|
||||
"inspect-live": "npx @modelcontextprotocol/inspector npx -- @agentdeskai/browser-tools-mcp",
|
||||
"build": "tsc",
|
||||
"start": "tsc && node dist/mcp-server.js",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"browser",
|
||||
"tools",
|
||||
"debugging",
|
||||
"ai",
|
||||
"chrome",
|
||||
"extension"
|
||||
],
|
||||
"author": "AgentDesk AI",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"llm-cost": "^1.0.5",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.5.14",
|
||||
"@types/body-parser": "^1.19.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
15
browser-tools-mcp/tsconfig.json
Normal file
15
browser-tools-mcp/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
68
browser-tools-server/README.md
Normal file
68
browser-tools-server/README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Browser Tools Server
|
||||
|
||||
A powerful browser tools server for capturing and managing browser events, logs, and screenshots. This server works in conjunction with the Browser Tools Chrome Extension to provide comprehensive browser debugging capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- Console log capture
|
||||
- Network request monitoring
|
||||
- Screenshot capture
|
||||
- Element selection tracking
|
||||
- WebSocket real-time communication
|
||||
- Configurable log limits and settings
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx @agentdeskai/browser-tools-server
|
||||
```
|
||||
|
||||
Or install globally:
|
||||
|
||||
```bash
|
||||
npm install -g @agentdeskai/browser-tools-server
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Start the server:
|
||||
|
||||
```bash
|
||||
npx @agentdeskai/browser-tools-server
|
||||
```
|
||||
|
||||
2. The server will start on port 3025 by default
|
||||
|
||||
3. Install and enable the Browser Tools Chrome Extension
|
||||
|
||||
4. The server exposes the following endpoints:
|
||||
|
||||
- `/console-logs` - Get console logs
|
||||
- `/console-errors` - Get console errors
|
||||
- `/network-errors` - Get network error logs
|
||||
- `/network-success` - Get successful network requests
|
||||
- `/all-xhr` - Get all network requests
|
||||
- `/screenshot` - Capture screenshots
|
||||
- `/selected-element` - Get currently selected DOM element
|
||||
|
||||
## API Documentation
|
||||
|
||||
### GET Endpoints
|
||||
|
||||
- `GET /console-logs` - Returns recent console logs
|
||||
- `GET /console-errors` - Returns recent console errors
|
||||
- `GET /network-errors` - Returns recent network errors
|
||||
- `GET /network-success` - Returns recent successful network requests
|
||||
- `GET /all-xhr` - Returns all recent network requests
|
||||
- `GET /selected-element` - Returns the currently selected DOM element
|
||||
|
||||
### POST Endpoints
|
||||
|
||||
- `POST /extension-log` - Receive logs from the extension
|
||||
- `POST /screenshot` - Capture and save screenshots
|
||||
- `POST /selected-element` - Update the selected element
|
||||
- `POST /wipelogs` - Clear all stored logs
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
686
browser-tools-server/browser-connector.ts
Normal file
686
browser-tools-server/browser-connector.ts
Normal file
@ -0,0 +1,686 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import bodyParser from "body-parser";
|
||||
import { tokenizeAndEstimateCost } from "llm-cost";
|
||||
import WebSocket from "ws";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { IncomingMessage } from "http";
|
||||
import { Socket } from "net";
|
||||
import os from "os";
|
||||
|
||||
// Function to get default downloads folder
|
||||
function getDefaultDownloadsFolder(): string {
|
||||
const homeDir = os.homedir();
|
||||
// Downloads folder is typically the same path on Windows, macOS, and Linux
|
||||
const downloadsPath = path.join(homeDir, "Downloads", "mcp-screenshots");
|
||||
return downloadsPath;
|
||||
}
|
||||
|
||||
// We store logs in memory
|
||||
const consoleLogs: any[] = [];
|
||||
const consoleErrors: any[] = [];
|
||||
const networkErrors: any[] = [];
|
||||
const networkSuccess: any[] = [];
|
||||
const allXhr: any[] = [];
|
||||
|
||||
// Add settings state
|
||||
let currentSettings = {
|
||||
logLimit: 50,
|
||||
queryLimit: 30000,
|
||||
showRequestHeaders: false,
|
||||
showResponseHeaders: false,
|
||||
model: "claude-3-sonnet",
|
||||
stringSizeLimit: 500,
|
||||
maxLogSize: 20000,
|
||||
screenshotPath: getDefaultDownloadsFolder(),
|
||||
};
|
||||
|
||||
// Add new storage for selected element
|
||||
let selectedElement: any = null;
|
||||
|
||||
// Add new state for tracking screenshot requests
|
||||
interface ScreenshotCallback {
|
||||
resolve: (value: { data: string; path?: string }) => void;
|
||||
reject: (reason: Error) => void;
|
||||
}
|
||||
|
||||
const screenshotCallbacks = new Map<string, ScreenshotCallback>();
|
||||
|
||||
const app = express();
|
||||
const PORT = 3025;
|
||||
|
||||
app.use(cors());
|
||||
// Increase JSON body parser limit to 50MB to handle large screenshots
|
||||
app.use(bodyParser.json({ limit: "50mb" }));
|
||||
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
||||
|
||||
// Helper to recursively truncate strings in any data structure
|
||||
function truncateStringsInData(data: any, maxLength: number): any {
|
||||
if (typeof data === "string") {
|
||||
return data.length > maxLength
|
||||
? data.substring(0, maxLength) + "... (truncated)"
|
||||
: data;
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) => truncateStringsInData(item, maxLength));
|
||||
}
|
||||
|
||||
if (typeof data === "object" && data !== null) {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = truncateStringsInData(value, maxLength);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Helper to safely parse and process JSON strings
|
||||
function processJsonString(jsonString: string, maxLength: number): string {
|
||||
try {
|
||||
// Try to parse the string as JSON
|
||||
const parsed = JSON.parse(jsonString);
|
||||
// Process any strings within the parsed JSON
|
||||
const processed = truncateStringsInData(parsed, maxLength);
|
||||
// Stringify the processed data
|
||||
return JSON.stringify(processed);
|
||||
} catch (e) {
|
||||
// If it's not valid JSON, treat it as a regular string
|
||||
return truncateStringsInData(jsonString, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to process logs based on settings
|
||||
function processLogsWithSettings(logs: any[]) {
|
||||
return logs.map((log) => {
|
||||
const processedLog = { ...log };
|
||||
|
||||
if (log.type === "network-request") {
|
||||
// Handle headers visibility
|
||||
if (!currentSettings.showRequestHeaders) {
|
||||
delete processedLog.requestHeaders;
|
||||
}
|
||||
if (!currentSettings.showResponseHeaders) {
|
||||
delete processedLog.responseHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
return processedLog;
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to calculate size of a log entry
|
||||
function calculateLogSize(log: any): number {
|
||||
return JSON.stringify(log).length;
|
||||
}
|
||||
|
||||
// Helper to truncate logs based on character limit
|
||||
function truncateLogsToQueryLimit(logs: any[]): any[] {
|
||||
if (logs.length === 0) return logs;
|
||||
|
||||
// First process logs according to current settings
|
||||
const processedLogs = processLogsWithSettings(logs);
|
||||
|
||||
let currentSize = 0;
|
||||
const result = [];
|
||||
|
||||
for (const log of processedLogs) {
|
||||
const logSize = calculateLogSize(log);
|
||||
|
||||
// Check if adding this log would exceed the limit
|
||||
if (currentSize + logSize > currentSettings.queryLimit) {
|
||||
console.log(
|
||||
`Reached query limit (${currentSize}/${currentSettings.queryLimit}), truncating logs`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add log and update size
|
||||
result.push(log);
|
||||
currentSize += logSize;
|
||||
console.log(`Added log of size ${logSize}, total size now: ${currentSize}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Endpoint for the extension to POST data
|
||||
app.post("/extension-log", (req, res) => {
|
||||
console.log("\n=== Received Extension Log ===");
|
||||
console.log("Request body:", {
|
||||
dataType: req.body.data?.type,
|
||||
timestamp: req.body.data?.timestamp,
|
||||
hasSettings: !!req.body.settings,
|
||||
});
|
||||
|
||||
const { data, settings } = req.body;
|
||||
|
||||
// Update settings if provided
|
||||
if (settings) {
|
||||
console.log("Updating settings:", settings);
|
||||
currentSettings = {
|
||||
...currentSettings,
|
||||
...settings,
|
||||
};
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
console.log("Warning: No data received in log request");
|
||||
res.status(400).json({ status: "error", message: "No data provided" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Processing ${data.type} log entry`);
|
||||
|
||||
switch (data.type) {
|
||||
case "console-log":
|
||||
console.log("Adding console log:", {
|
||||
level: data.level,
|
||||
message:
|
||||
data.message?.substring(0, 100) +
|
||||
(data.message?.length > 100 ? "..." : ""),
|
||||
timestamp: data.timestamp,
|
||||
});
|
||||
consoleLogs.push(data);
|
||||
if (consoleLogs.length > currentSettings.logLimit) {
|
||||
console.log(
|
||||
`Console logs exceeded limit (${currentSettings.logLimit}), removing oldest entry`
|
||||
);
|
||||
consoleLogs.shift();
|
||||
}
|
||||
break;
|
||||
case "console-error":
|
||||
console.log("Adding console error:", {
|
||||
level: data.level,
|
||||
message:
|
||||
data.message?.substring(0, 100) +
|
||||
(data.message?.length > 100 ? "..." : ""),
|
||||
timestamp: data.timestamp,
|
||||
});
|
||||
consoleErrors.push(data);
|
||||
if (consoleErrors.length > currentSettings.logLimit) {
|
||||
console.log(
|
||||
`Console errors exceeded limit (${currentSettings.logLimit}), removing oldest entry`
|
||||
);
|
||||
consoleErrors.shift();
|
||||
}
|
||||
break;
|
||||
case "network-request":
|
||||
const logEntry = {
|
||||
url: data.url,
|
||||
method: data.method,
|
||||
status: data.status,
|
||||
timestamp: data.timestamp,
|
||||
};
|
||||
console.log("Adding network request:", logEntry);
|
||||
|
||||
// Route network requests based on status code
|
||||
if (data.status >= 400) {
|
||||
networkErrors.push(data);
|
||||
if (networkErrors.length > currentSettings.logLimit) {
|
||||
console.log(
|
||||
`Network errors exceeded limit (${currentSettings.logLimit}), removing oldest entry`
|
||||
);
|
||||
networkErrors.shift();
|
||||
}
|
||||
} else {
|
||||
networkSuccess.push(data);
|
||||
if (networkSuccess.length > currentSettings.logLimit) {
|
||||
console.log(
|
||||
`Network success logs exceeded limit (${currentSettings.logLimit}), removing oldest entry`
|
||||
);
|
||||
networkSuccess.shift();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "selected-element":
|
||||
console.log("Updating selected element:", {
|
||||
tagName: data.element?.tagName,
|
||||
id: data.element?.id,
|
||||
className: data.element?.className,
|
||||
});
|
||||
selectedElement = data.element;
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown log type:", data.type);
|
||||
}
|
||||
|
||||
console.log("Current log counts:", {
|
||||
consoleLogs: consoleLogs.length,
|
||||
consoleErrors: consoleErrors.length,
|
||||
networkErrors: networkErrors.length,
|
||||
networkSuccess: networkSuccess.length,
|
||||
});
|
||||
console.log("=== End Extension Log ===\n");
|
||||
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
|
||||
// Update GET endpoints to use the new function
|
||||
app.get("/console-logs", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(consoleLogs);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/console-errors", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(consoleErrors);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/network-errors", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(networkErrors);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/network-success", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(networkSuccess);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/all-xhr", (req, res) => {
|
||||
// Merge and sort network success and error logs by timestamp
|
||||
const mergedLogs = [...networkSuccess, ...networkErrors].sort(
|
||||
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
||||
);
|
||||
const truncatedLogs = truncateLogsToQueryLimit(mergedLogs);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
// Add new endpoint for selected element
|
||||
app.post("/selected-element", (req, res) => {
|
||||
const { data } = req.body;
|
||||
selectedElement = data;
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
|
||||
app.get("/selected-element", (req, res) => {
|
||||
res.json(selectedElement || { message: "No element selected" });
|
||||
});
|
||||
|
||||
app.get("/.port", (req, res) => {
|
||||
res.send(PORT.toString());
|
||||
});
|
||||
|
||||
// Add function to clear all logs
|
||||
function clearAllLogs() {
|
||||
console.log("Wiping all logs...");
|
||||
consoleLogs.length = 0;
|
||||
consoleErrors.length = 0;
|
||||
networkErrors.length = 0;
|
||||
networkSuccess.length = 0;
|
||||
allXhr.length = 0;
|
||||
selectedElement = null;
|
||||
console.log("All logs have been wiped");
|
||||
}
|
||||
|
||||
// Add endpoint to wipe logs
|
||||
app.post("/wipelogs", (req, res) => {
|
||||
clearAllLogs();
|
||||
res.json({ status: "ok", message: "All logs cleared successfully" });
|
||||
});
|
||||
|
||||
interface ScreenshotMessage {
|
||||
type: "screenshot-data" | "screenshot-error";
|
||||
data?: string;
|
||||
path?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class BrowserConnector {
|
||||
private wss: WebSocket.Server;
|
||||
private activeConnection: WebSocket | null = null;
|
||||
private app: express.Application;
|
||||
private server: any;
|
||||
|
||||
constructor(app: express.Application, server: any) {
|
||||
this.app = app;
|
||||
this.server = server;
|
||||
|
||||
// Initialize WebSocket server using the existing HTTP server
|
||||
this.wss = new WebSocket.Server({
|
||||
noServer: true,
|
||||
path: "/extension-ws",
|
||||
});
|
||||
|
||||
// Register the capture-screenshot endpoint
|
||||
this.app.post(
|
||||
"/capture-screenshot",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
console.log(
|
||||
"Browser Connector: Received request to /capture-screenshot endpoint"
|
||||
);
|
||||
console.log("Browser Connector: Request body:", req.body);
|
||||
console.log(
|
||||
"Browser Connector: Active WebSocket connection:",
|
||||
!!this.activeConnection
|
||||
);
|
||||
await this.captureScreenshot(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
// Handle upgrade requests for WebSocket
|
||||
this.server.on(
|
||||
"upgrade",
|
||||
(request: IncomingMessage, socket: Socket, head: Buffer) => {
|
||||
if (request.url === "/extension-ws") {
|
||||
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
this.wss.emit("connection", ws, request);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.wss.on("connection", (ws) => {
|
||||
console.log("Chrome extension connected via WebSocket");
|
||||
this.activeConnection = ws;
|
||||
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
const data = JSON.parse(message.toString());
|
||||
// Log message without the base64 data
|
||||
console.log("Received WebSocket message:", {
|
||||
...data,
|
||||
data: data.data ? "[base64 data]" : undefined,
|
||||
});
|
||||
|
||||
// Handle screenshot response
|
||||
if (data.type === "screenshot-data" && data.data) {
|
||||
console.log("Received screenshot data");
|
||||
console.log("Screenshot path from extension:", data.path);
|
||||
// Get the most recent callback since we're not using requestId anymore
|
||||
const callbacks = Array.from(screenshotCallbacks.values());
|
||||
if (callbacks.length > 0) {
|
||||
const callback = callbacks[0];
|
||||
console.log("Found callback, resolving promise");
|
||||
// Pass both the data and path to the resolver
|
||||
callback.resolve({ data: data.data, path: data.path });
|
||||
screenshotCallbacks.clear(); // Clear all callbacks
|
||||
} else {
|
||||
console.log("No callbacks found for screenshot");
|
||||
}
|
||||
}
|
||||
// Handle screenshot error
|
||||
else if (data.type === "screenshot-error") {
|
||||
console.log("Received screenshot error:", data.error);
|
||||
const callbacks = Array.from(screenshotCallbacks.values());
|
||||
if (callbacks.length > 0) {
|
||||
const callback = callbacks[0];
|
||||
callback.reject(
|
||||
new Error(data.error || "Screenshot capture failed")
|
||||
);
|
||||
screenshotCallbacks.clear(); // Clear all callbacks
|
||||
}
|
||||
} else {
|
||||
console.log("Unhandled message type:", data.type);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing WebSocket message:", error);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("Chrome extension disconnected");
|
||||
if (this.activeConnection === ws) {
|
||||
this.activeConnection = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add screenshot endpoint
|
||||
this.app.post(
|
||||
"/screenshot",
|
||||
(req: express.Request, res: express.Response): void => {
|
||||
console.log(
|
||||
"Browser Connector: Received request to /screenshot endpoint"
|
||||
);
|
||||
console.log("Browser Connector: Request body:", req.body);
|
||||
try {
|
||||
console.log("Received screenshot capture request");
|
||||
const { data, path: outputPath } = req.body;
|
||||
|
||||
if (!data) {
|
||||
console.log("Screenshot request missing data");
|
||||
res.status(400).json({ error: "Missing screenshot data" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Use provided path or default to downloads folder
|
||||
const targetPath = outputPath || getDefaultDownloadsFolder();
|
||||
console.log(`Using screenshot path: ${targetPath}`);
|
||||
|
||||
// Remove the data:image/png;base64, prefix
|
||||
const base64Data = data.replace(/^data:image\/png;base64,/, "");
|
||||
|
||||
// Create the full directory path if it doesn't exist
|
||||
fs.mkdirSync(targetPath, { recursive: true });
|
||||
console.log(`Created/verified directory: ${targetPath}`);
|
||||
|
||||
// Generate a unique filename using timestamp
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const filename = `screenshot-${timestamp}.png`;
|
||||
const fullPath = path.join(targetPath, filename);
|
||||
console.log(`Saving screenshot to: ${fullPath}`);
|
||||
|
||||
// Write the file
|
||||
fs.writeFileSync(fullPath, base64Data, "base64");
|
||||
console.log("Screenshot saved successfully");
|
||||
|
||||
res.json({
|
||||
path: fullPath,
|
||||
filename: filename,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error("Error saving screenshot:", error);
|
||||
if (error instanceof Error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
} else {
|
||||
res.status(500).json({ error: "An unknown error occurred" });
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async handleScreenshot(req: express.Request, res: express.Response) {
|
||||
if (!this.activeConnection) {
|
||||
return res.status(503).json({ error: "Chrome extension not connected" });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
// Set up one-time message handler for this screenshot request
|
||||
const messageHandler = (message: WebSocket.Data) => {
|
||||
try {
|
||||
const response: ScreenshotMessage = JSON.parse(message.toString());
|
||||
|
||||
if (response.type === "screenshot-error") {
|
||||
reject(new Error(response.error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
response.type === "screenshot-data" &&
|
||||
response.data &&
|
||||
response.path
|
||||
) {
|
||||
// Remove the data:image/png;base64, prefix
|
||||
const base64Data = response.data.replace(
|
||||
/^data:image\/png;base64,/,
|
||||
""
|
||||
);
|
||||
|
||||
// Ensure the directory exists
|
||||
const dir = path.dirname(response.path);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
// Generate a unique filename using timestamp
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const filename = `screenshot-${timestamp}.png`;
|
||||
const fullPath = path.join(response.path, filename);
|
||||
|
||||
// Write the file
|
||||
fs.writeFileSync(fullPath, base64Data, "base64");
|
||||
resolve({
|
||||
path: fullPath,
|
||||
filename: filename,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
this.activeConnection?.removeListener("message", messageHandler);
|
||||
}
|
||||
};
|
||||
|
||||
// Add temporary message handler
|
||||
this.activeConnection?.on("message", messageHandler);
|
||||
|
||||
// Request screenshot
|
||||
this.activeConnection?.send(
|
||||
JSON.stringify({ type: "take-screenshot" })
|
||||
);
|
||||
|
||||
// Set timeout
|
||||
setTimeout(() => {
|
||||
this.activeConnection?.removeListener("message", messageHandler);
|
||||
reject(new Error("Screenshot timeout"));
|
||||
}, 30000); // 30 second timeout
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
} else {
|
||||
res.status(500).json({ error: "An unknown error occurred" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new endpoint for programmatic screenshot capture
|
||||
async captureScreenshot(req: express.Request, res: express.Response) {
|
||||
console.log("Browser Connector: Starting captureScreenshot method");
|
||||
console.log("Browser Connector: Request headers:", req.headers);
|
||||
console.log("Browser Connector: Request method:", req.method);
|
||||
|
||||
if (!this.activeConnection) {
|
||||
console.log(
|
||||
"Browser Connector: No active WebSocket connection to Chrome extension"
|
||||
);
|
||||
return res.status(503).json({ error: "Chrome extension not connected" });
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("Browser Connector: Starting screenshot capture...");
|
||||
const requestId = Date.now().toString();
|
||||
console.log("Browser Connector: Generated requestId:", requestId);
|
||||
|
||||
// Create promise that will resolve when we get the screenshot data
|
||||
const screenshotPromise = new Promise<{ data: string; path?: string }>(
|
||||
(resolve, reject) => {
|
||||
console.log(
|
||||
`Browser Connector: Setting up screenshot callback for requestId: ${requestId}`
|
||||
);
|
||||
// Store callback in map
|
||||
screenshotCallbacks.set(requestId, { resolve, reject });
|
||||
console.log(
|
||||
"Browser Connector: Current callbacks:",
|
||||
Array.from(screenshotCallbacks.keys())
|
||||
);
|
||||
|
||||
// Set timeout to clean up if we don't get a response
|
||||
setTimeout(() => {
|
||||
if (screenshotCallbacks.has(requestId)) {
|
||||
console.log(
|
||||
`Browser Connector: Screenshot capture timed out for requestId: ${requestId}`
|
||||
);
|
||||
screenshotCallbacks.delete(requestId);
|
||||
reject(
|
||||
new Error(
|
||||
"Screenshot capture timed out - no response from Chrome extension"
|
||||
)
|
||||
);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
);
|
||||
|
||||
// Send screenshot request to extension
|
||||
const message = JSON.stringify({
|
||||
type: "take-screenshot",
|
||||
requestId: requestId,
|
||||
});
|
||||
console.log(
|
||||
`Browser Connector: Sending WebSocket message to extension:`,
|
||||
message
|
||||
);
|
||||
this.activeConnection.send(message);
|
||||
|
||||
// Wait for screenshot data
|
||||
console.log("Browser Connector: Waiting for screenshot data...");
|
||||
const { data: base64Data, path: customPath } = await screenshotPromise;
|
||||
console.log("Browser Connector: Received screenshot data, saving...");
|
||||
console.log("Browser Connector: Custom path from extension:", customPath);
|
||||
|
||||
// Determine target path
|
||||
const targetPath =
|
||||
customPath ||
|
||||
currentSettings.screenshotPath ||
|
||||
getDefaultDownloadsFolder();
|
||||
console.log(`Browser Connector: Using path: ${targetPath}`);
|
||||
|
||||
if (!base64Data) {
|
||||
throw new Error("No screenshot data received from Chrome extension");
|
||||
}
|
||||
|
||||
fs.mkdirSync(targetPath, { recursive: true });
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const filename = `screenshot-${timestamp}.png`;
|
||||
const fullPath = path.join(targetPath, filename);
|
||||
|
||||
// Remove the data:image/png;base64, prefix if present
|
||||
const cleanBase64 = base64Data.replace(/^data:image\/png;base64,/, "");
|
||||
|
||||
// Save the file
|
||||
fs.writeFileSync(fullPath, cleanBase64, "base64");
|
||||
console.log(`Browser Connector: Screenshot saved to: ${fullPath}`);
|
||||
|
||||
res.json({
|
||||
path: fullPath,
|
||||
filename: filename,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
console.error(
|
||||
"Browser Connector: Error capturing screenshot:",
|
||||
errorMessage
|
||||
);
|
||||
res.status(500).json({
|
||||
error: errorMessage,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move the server creation before BrowserConnector instantiation
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`Aggregator listening on http://127.0.0.1:${PORT}`);
|
||||
});
|
||||
|
||||
// Initialize the browser connector with the existing app AND server
|
||||
const browserConnector = new BrowserConnector(app, server);
|
||||
|
||||
// Handle shutdown gracefully
|
||||
process.on("SIGINT", () => {
|
||||
server.close(() => {
|
||||
console.log("Server shut down");
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
1042
browser-tools-server/package-lock.json
generated
Normal file
1042
browser-tools-server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
browser-tools-server/package.json
Normal file
41
browser-tools-server/package.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@agentdeskai/browser-tools-server",
|
||||
"version": "1.0.5",
|
||||
"description": "A browser tools server for capturing and managing browser events, logs, and screenshots",
|
||||
"main": "dist/browser-connector.js",
|
||||
"bin": {
|
||||
"browser-tools-server": "./dist/browser-connector.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "tsc && node dist/browser-connector.js",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"browser",
|
||||
"tools",
|
||||
"debugging",
|
||||
"logging",
|
||||
"screenshots",
|
||||
"chrome",
|
||||
"extension"
|
||||
],
|
||||
"author": "AgentDesk AI",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"llm-cost": "^1.0.5",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.5.14",
|
||||
"@types/body-parser": "^1.19.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
15
browser-tools-server/tsconfig.json
Normal file
15
browser-tools-server/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>DevTools Logs</title>
|
||||
<title>BrowserTools MCP</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- DevTools extension script -->
|
||||
|
||||
@ -13,7 +13,10 @@ let settings = {
|
||||
|
||||
// Keep track of debugger state
|
||||
let isDebuggerAttached = false;
|
||||
let attachDebuggerRetries = 0;
|
||||
const currentTabId = chrome.devtools.inspectedWindow.tabId;
|
||||
const MAX_ATTACH_RETRIES = 3;
|
||||
const ATTACH_RETRY_DELAY = 1000; // 1 second
|
||||
|
||||
// Load saved settings on startup
|
||||
chrome.storage.local.get(["browserConnectorSettings"], (result) => {
|
||||
@ -22,10 +25,50 @@ chrome.storage.local.get(["browserConnectorSettings"], (result) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for settings updates
|
||||
chrome.runtime.onMessage.addListener((message) => {
|
||||
// Listen for settings updates and screenshot capture requests
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === "SETTINGS_UPDATED") {
|
||||
settings = message.settings;
|
||||
} else if (message.type === "CAPTURE_SCREENSHOT") {
|
||||
// Capture screenshot of the current tab
|
||||
chrome.tabs.captureVisibleTab(null, { format: "png" }, (dataUrl) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Screenshot capture failed:", chrome.runtime.lastError);
|
||||
sendResponse({
|
||||
success: false,
|
||||
error: chrome.runtime.lastError.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Send screenshot data to browser connector via HTTP POST
|
||||
fetch("http://127.0.0.1:3025/screenshot", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: dataUrl,
|
||||
path: settings.screenshotPath,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((result) => {
|
||||
if (result.error) {
|
||||
sendResponse({ success: false, error: result.error });
|
||||
} else {
|
||||
sendResponse({ success: true, path: result.path });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to save screenshot:", error);
|
||||
sendResponse({
|
||||
success: false,
|
||||
error: error.message || "Failed to save screenshot",
|
||||
});
|
||||
});
|
||||
});
|
||||
return true; // Required to use sendResponse asynchronously
|
||||
}
|
||||
});
|
||||
|
||||
@ -157,9 +200,17 @@ function processJsonString(jsonString, maxLength) {
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to send logs to browser-connector
|
||||
// Helper to send logs to browser-connector
|
||||
function sendToBrowserConnector(logData) {
|
||||
console.log("Original log data size:", JSON.stringify(logData).length);
|
||||
if (!logData) {
|
||||
console.error("No log data provided to sendToBrowserConnector");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Sending log data to browser connector:", {
|
||||
type: logData.type,
|
||||
timestamp: logData.timestamp,
|
||||
});
|
||||
|
||||
// Process any string fields that might contain JSON
|
||||
const processedData = { ...logData };
|
||||
@ -224,7 +275,6 @@ function sendToBrowserConnector(logData) {
|
||||
console.log("Final payload size:", finalPayloadSize);
|
||||
|
||||
if (finalPayloadSize > 1000000) {
|
||||
// 1MB warning threshold
|
||||
console.warn("Warning: Large payload detected:", finalPayloadSize);
|
||||
console.warn(
|
||||
"Payload preview:",
|
||||
@ -236,11 +286,38 @@ function sendToBrowserConnector(logData) {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
console.log("Successfully sent log to browser-connector");
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log("Browser connector response:", data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to send log to browser-connector:", error);
|
||||
});
|
||||
}
|
||||
|
||||
// Add function to wipe logs
|
||||
function wipeLogs() {
|
||||
fetch("http://127.0.0.1:3025/wipelogs", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}).catch((error) => {
|
||||
console.error("Failed to send log to browser-connector:", error);
|
||||
console.error("Failed to wipe logs:", error);
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for page refreshes
|
||||
chrome.devtools.network.onNavigated.addListener(() => {
|
||||
console.log("Page navigated/refreshed - wiping logs");
|
||||
wipeLogs();
|
||||
});
|
||||
|
||||
// 1) Listen for network requests
|
||||
chrome.devtools.network.onRequestFinished.addListener((request) => {
|
||||
if (request._resourceType === "xhr" || request._resourceType === "fetch") {
|
||||
@ -260,84 +337,65 @@ chrome.devtools.network.onRequestFinished.addListener((request) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Move the console message listener outside the panel creation
|
||||
const consoleMessageListener = (source, method, params) => {
|
||||
console.log("Debugger event:", method, source.tabId, currentTabId);
|
||||
|
||||
// Only process events for our tab
|
||||
if (source.tabId !== currentTabId) {
|
||||
console.log("Ignoring event from different tab");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "Console.messageAdded") {
|
||||
console.log("Console message received:", params.message);
|
||||
const entry = {
|
||||
type: params.message.level === "error" ? "console-error" : "console-log",
|
||||
level: params.message.level,
|
||||
message: params.message.text,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
console.log("Sending console entry:", entry);
|
||||
sendToBrowserConnector(entry);
|
||||
} else {
|
||||
console.log("Unhandled debugger method:", method);
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to attach debugger
|
||||
function attachDebugger() {
|
||||
if (isDebuggerAttached) {
|
||||
console.log("Debugger already attached");
|
||||
return;
|
||||
}
|
||||
async function attachDebugger() {
|
||||
// First check if we're already attached to this tab
|
||||
chrome.debugger.getTargets((targets) => {
|
||||
const isAlreadyAttached = targets.some(
|
||||
(target) => target.tabId === currentTabId && target.attached
|
||||
);
|
||||
|
||||
// Check if the tab still exists
|
||||
chrome.tabs.get(currentTabId, (tab) => {
|
||||
if (isAlreadyAttached) {
|
||||
console.log("Found existing debugger attachment, detaching first...");
|
||||
// Force detach first to ensure clean state
|
||||
chrome.debugger.detach({ tabId: currentTabId }, () => {
|
||||
// Ignore any errors during detach
|
||||
if (chrome.runtime.lastError) {
|
||||
console.log("Error during forced detach:", chrome.runtime.lastError);
|
||||
}
|
||||
// Now proceed with fresh attachment
|
||||
performAttach();
|
||||
});
|
||||
} else {
|
||||
// No existing attachment, proceed directly
|
||||
performAttach();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function performAttach() {
|
||||
console.log("Performing debugger attachment to tab:", currentTabId);
|
||||
chrome.debugger.attach({ tabId: currentTabId }, "1.3", () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.log("Tab no longer exists:", chrome.runtime.lastError);
|
||||
console.error("Failed to attach debugger:", chrome.runtime.lastError);
|
||||
isDebuggerAttached = false;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Attaching debugger to tab:", currentTabId);
|
||||
chrome.debugger.attach({ tabId: currentTabId }, "1.3", () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Failed to attach debugger:", chrome.runtime.lastError);
|
||||
isDebuggerAttached = false;
|
||||
return;
|
||||
}
|
||||
isDebuggerAttached = true;
|
||||
console.log("Debugger successfully attached");
|
||||
|
||||
isDebuggerAttached = true;
|
||||
console.log("Debugger successfully attached");
|
||||
// Add the event listener when attaching
|
||||
chrome.debugger.onEvent.addListener(consoleMessageListener);
|
||||
|
||||
// Add the event listener when attaching
|
||||
chrome.debugger.onEvent.addListener(consoleMessageListener);
|
||||
|
||||
chrome.debugger.sendCommand(
|
||||
{ tabId: currentTabId },
|
||||
"Console.enable",
|
||||
{},
|
||||
() => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"Failed to enable console:",
|
||||
chrome.runtime.lastError
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log("Console API successfully enabled");
|
||||
chrome.debugger.sendCommand(
|
||||
{ tabId: currentTabId },
|
||||
"Console.enable",
|
||||
{},
|
||||
() => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Failed to enable console:", chrome.runtime.lastError);
|
||||
return;
|
||||
}
|
||||
);
|
||||
});
|
||||
console.log("Console API successfully enabled");
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to detach debugger
|
||||
function detachDebugger() {
|
||||
if (!isDebuggerAttached) return;
|
||||
|
||||
// Remove the event listener when detaching
|
||||
// Remove the event listener first
|
||||
chrome.debugger.onEvent.removeListener(consoleMessageListener);
|
||||
|
||||
// Check if debugger is actually attached before trying to detach
|
||||
@ -354,9 +412,10 @@ function detachDebugger() {
|
||||
|
||||
chrome.debugger.detach({ tabId: currentTabId }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Failed to detach debugger:", chrome.runtime.lastError);
|
||||
isDebuggerAttached = false;
|
||||
return;
|
||||
console.warn(
|
||||
"Warning during debugger detach:",
|
||||
chrome.runtime.lastError
|
||||
);
|
||||
}
|
||||
isDebuggerAttached = false;
|
||||
console.log("Debugger detached");
|
||||
@ -364,20 +423,48 @@ function detachDebugger() {
|
||||
});
|
||||
}
|
||||
|
||||
// Move the console message listener outside the panel creation
|
||||
const consoleMessageListener = (source, method, params) => {
|
||||
// Only process events for our tab
|
||||
if (source.tabId !== currentTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === "Console.messageAdded") {
|
||||
console.log("Console message received:", params.message);
|
||||
const entry = {
|
||||
type: params.message.level === "error" ? "console-error" : "console-log",
|
||||
level: params.message.level,
|
||||
message: params.message.text,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
console.log("Sending console entry:", entry);
|
||||
sendToBrowserConnector(entry);
|
||||
}
|
||||
};
|
||||
|
||||
// 2) Use DevTools Protocol to capture console logs
|
||||
chrome.devtools.panels.create("Browser Logs", "", "panel.html", (panel) => {
|
||||
chrome.devtools.panels.create("BrowserToolsMCP", "", "panel.html", (panel) => {
|
||||
// Initial attach - we'll keep the debugger attached as long as DevTools is open
|
||||
attachDebugger();
|
||||
|
||||
// Add message passing to panel.js
|
||||
// Handle panel showing
|
||||
panel.onShown.addListener((panelWindow) => {
|
||||
panelWindow.postMessage({ type: "initializeSelectionButton" }, "*");
|
||||
if (!isDebuggerAttached) {
|
||||
attachDebugger();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Clean up only when the entire DevTools window is closed
|
||||
// Clean up when DevTools window is closed
|
||||
window.addEventListener("unload", () => {
|
||||
detachDebugger();
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
if (wsReconnectTimeout) {
|
||||
clearTimeout(wsReconnectTimeout);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to capture and send element data
|
||||
@ -439,62 +526,73 @@ function setupWebSocket() {
|
||||
|
||||
ws = new WebSocket("ws://localhost:3025/extension-ws");
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log("Chrome Extension: Received WebSocket message:", message);
|
||||
|
||||
if (message.type === "take-screenshot") {
|
||||
console.log("Chrome Extension: Taking screenshot...");
|
||||
// Capture screenshot of the current tab
|
||||
chrome.tabs.captureVisibleTab(null, { format: "png" }, (dataUrl) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"Chrome Extension: Screenshot capture failed:",
|
||||
chrome.runtime.lastError
|
||||
);
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "screenshot-error",
|
||||
error: chrome.runtime.lastError.message,
|
||||
requestId: message.requestId,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Chrome Extension: Screenshot captured successfully");
|
||||
// Just send the screenshot data, let the server handle paths
|
||||
const response = {
|
||||
type: "screenshot-data",
|
||||
data: dataUrl,
|
||||
requestId: message.requestId,
|
||||
// Only include path if it's configured in settings
|
||||
...(settings.screenshotPath && { path: settings.screenshotPath }),
|
||||
};
|
||||
|
||||
console.log("Chrome Extension: Sending screenshot data response", {
|
||||
...response,
|
||||
data: "[base64 data]",
|
||||
});
|
||||
|
||||
ws.send(JSON.stringify(response));
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Chrome Extension: Error processing WebSocket message:",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected");
|
||||
console.log("Chrome Extension: WebSocket connected");
|
||||
if (wsReconnectTimeout) {
|
||||
clearTimeout(wsReconnectTimeout);
|
||||
wsReconnectTimeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
if (message.type === "take-screenshot") {
|
||||
if (!settings.screenshotPath) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "screenshot-error",
|
||||
error: "Screenshot path not configured",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Capture screenshot of the current tab
|
||||
chrome.tabs.captureVisibleTab(null, { format: "png" }, (dataUrl) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "screenshot-error",
|
||||
error: chrome.runtime.lastError.message,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "screenshot-data",
|
||||
data: dataUrl,
|
||||
path: settings.screenshotPath,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing WebSocket message:", error);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log("WebSocket disconnected, attempting to reconnect...");
|
||||
console.log(
|
||||
"Chrome Extension: WebSocket disconnected, attempting to reconnect..."
|
||||
);
|
||||
wsReconnectTimeout = setTimeout(setupWebSocket, WS_RECONNECT_DELAY);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
console.error("Chrome Extension: WebSocket error:", error);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "AI Browser Connector",
|
||||
"name": "BrowserTools MCP",
|
||||
"version": "1.0.0",
|
||||
"description": "Captures console and network logs then sends them to a local aggregator for MCP usage",
|
||||
"description": "MCP tool for AI code editors to capture data from a browser such as console logs, network requests, screenshots and more",
|
||||
"manifest_version": 3,
|
||||
"devtools_page": "devtools.html",
|
||||
"permissions": [
|
||||
|
||||
@ -62,55 +62,122 @@
|
||||
margin-bottom: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.settings-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.settings-header h3 {
|
||||
margin: 0;
|
||||
}
|
||||
.settings-content {
|
||||
display: none;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.settings-content.visible {
|
||||
display: block;
|
||||
}
|
||||
.chevron {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.chevron.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.action-button {
|
||||
background-color: #4a4a4a;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.action-button:hover {
|
||||
background-color: #5a5a5a;
|
||||
}
|
||||
.action-button.danger {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.action-button.danger:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="settings-section">
|
||||
<h3>Log Settings</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="log-limit">Log Limit (number of logs)</label>
|
||||
<input type="number" id="log-limit" min="1" value="50">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="query-limit">Query Limit (characters)</label>
|
||||
<input type="number" id="query-limit" min="1" value="30000">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="string-size-limit">String Size Limit (characters)</label>
|
||||
<input type="number" id="string-size-limit" min="1" value="500">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max-log-size">Max Log Size (characters)</label>
|
||||
<input type="number" id="max-log-size" min="1000" value="20000">
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" id="show-request-headers">
|
||||
Include Request Headers
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" id="show-response-headers">
|
||||
Include Response Headers
|
||||
</label>
|
||||
<h3>Quick Actions</h3>
|
||||
<div class="quick-actions">
|
||||
<button id="capture-screenshot" class="action-button">
|
||||
Capture Screenshot
|
||||
</button>
|
||||
<button id="wipe-logs" class="action-button danger">
|
||||
Wipe All Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3>Screenshot Settings</h3>
|
||||
<div class="form-group">
|
||||
<label for="screenshot-path">Screenshot Save Path</label>
|
||||
<label for="screenshot-path">Provide a directory to save screenshots to (by default screenshots will be saved to your downloads folder if no path is provided)</label>
|
||||
<input type="text" id="screenshot-path" placeholder="/path/to/screenshots">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<div class="settings-header" id="advanced-settings-header">
|
||||
<h3>Advanced Settings</h3>
|
||||
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="settings-content" id="advanced-settings-content">
|
||||
<div class="form-group">
|
||||
<label for="log-limit">Log Limit (number of logs)</label>
|
||||
<input type="number" id="log-limit" min="1" value="50">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="query-limit">Query Limit (characters)</label>
|
||||
<input type="number" id="query-limit" min="1" value="30000">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="string-size-limit">String Size Limit (characters)</label>
|
||||
<input type="number" id="string-size-limit" min="1" value="500">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max-log-size">Max Log Size (characters)</label>
|
||||
<input type="number" id="max-log-size" min="1000" value="20000">
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" id="show-request-headers">
|
||||
Include Request Headers
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" id="show-response-headers">
|
||||
Include Response Headers
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="panel.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -29,6 +29,21 @@ const showResponseHeadersCheckbox = document.getElementById(
|
||||
);
|
||||
const maxLogSizeInput = document.getElementById("max-log-size");
|
||||
const screenshotPathInput = document.getElementById("screenshot-path");
|
||||
const captureScreenshotButton = document.getElementById("capture-screenshot");
|
||||
|
||||
// Initialize collapsible advanced settings
|
||||
const advancedSettingsHeader = document.getElementById(
|
||||
"advanced-settings-header"
|
||||
);
|
||||
const advancedSettingsContent = document.getElementById(
|
||||
"advanced-settings-content"
|
||||
);
|
||||
const chevronIcon = advancedSettingsHeader.querySelector(".chevron");
|
||||
|
||||
advancedSettingsHeader.addEventListener("click", () => {
|
||||
advancedSettingsContent.classList.toggle("visible");
|
||||
chevronIcon.classList.toggle("open");
|
||||
});
|
||||
|
||||
// Update UI from settings
|
||||
function updateUIFromSettings() {
|
||||
@ -86,3 +101,48 @@ screenshotPathInput.addEventListener("change", (e) => {
|
||||
settings.screenshotPath = e.target.value;
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
// Add screenshot capture functionality
|
||||
captureScreenshotButton.addEventListener("click", () => {
|
||||
captureScreenshotButton.textContent = "Capturing...";
|
||||
|
||||
// Send message to devtools.js to capture screenshot
|
||||
chrome.runtime.sendMessage({ type: "CAPTURE_SCREENSHOT" }, (response) => {
|
||||
if (!response) {
|
||||
captureScreenshotButton.textContent = "Failed to capture!";
|
||||
console.error("Screenshot capture failed: No response received");
|
||||
} else if (!response.success) {
|
||||
captureScreenshotButton.textContent = "Failed to capture!";
|
||||
console.error("Screenshot capture failed:", response.error);
|
||||
} else {
|
||||
captureScreenshotButton.textContent = "Screenshot captured!";
|
||||
}
|
||||
setTimeout(() => {
|
||||
captureScreenshotButton.textContent = "Capture Screenshot";
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
// Add wipe logs functionality
|
||||
const wipeLogsButton = document.getElementById("wipe-logs");
|
||||
wipeLogsButton.addEventListener("click", () => {
|
||||
fetch("http://127.0.0.1:3025/wipelogs", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((result) => {
|
||||
console.log("Logs wiped successfully:", result.message);
|
||||
wipeLogsButton.textContent = "Logs Wiped!";
|
||||
setTimeout(() => {
|
||||
wipeLogsButton.textContent = "Wipe All Logs";
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to wipe logs:", error);
|
||||
wipeLogsButton.textContent = "Failed to Wipe Logs";
|
||||
setTimeout(() => {
|
||||
wipeLogsButton.textContent = "Wipe All Logs";
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "mcp-server",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "tsc && node dist/browser-connector.js",
|
||||
"mcp": "tsc && node dist/mcp-server.js",
|
||||
"inspect": "tsc && npx @modelcontextprotocol/inspector node -- dist/mcp-server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"llm-cost": "^1.0.5",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.5.14",
|
||||
"@types/body-parser": "^1.19.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
@ -1,364 +0,0 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import bodyParser from "body-parser";
|
||||
import { tokenizeAndEstimateCost } from "llm-cost";
|
||||
import WebSocket from "ws";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { IncomingMessage } from "http";
|
||||
import { Socket } from "net";
|
||||
|
||||
// We store logs in memory
|
||||
const consoleLogs: any[] = [];
|
||||
const consoleErrors: any[] = [];
|
||||
const networkErrors: any[] = [];
|
||||
const networkSuccess: any[] = [];
|
||||
const allXhr: any[] = [];
|
||||
|
||||
// Add settings state
|
||||
let currentSettings = {
|
||||
logLimit: 50,
|
||||
queryLimit: 30000,
|
||||
showRequestHeaders: false,
|
||||
showResponseHeaders: false,
|
||||
model: "claude-3-sonnet",
|
||||
stringSizeLimit: 500,
|
||||
maxLogSize: 20000,
|
||||
};
|
||||
|
||||
// Add new storage for selected element
|
||||
let selectedElement: any = null;
|
||||
|
||||
const app = express();
|
||||
const PORT = 3025;
|
||||
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Helper to recursively truncate strings in any data structure
|
||||
function truncateStringsInData(data: any, maxLength: number): any {
|
||||
if (typeof data === "string") {
|
||||
return data.length > maxLength
|
||||
? data.substring(0, maxLength) + "... (truncated)"
|
||||
: data;
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) => truncateStringsInData(item, maxLength));
|
||||
}
|
||||
|
||||
if (typeof data === "object" && data !== null) {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = truncateStringsInData(value, maxLength);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Helper to safely parse and process JSON strings
|
||||
function processJsonString(jsonString: string, maxLength: number): string {
|
||||
try {
|
||||
// Try to parse the string as JSON
|
||||
const parsed = JSON.parse(jsonString);
|
||||
// Process any strings within the parsed JSON
|
||||
const processed = truncateStringsInData(parsed, maxLength);
|
||||
// Stringify the processed data
|
||||
return JSON.stringify(processed);
|
||||
} catch (e) {
|
||||
// If it's not valid JSON, treat it as a regular string
|
||||
return truncateStringsInData(jsonString, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to process logs based on settings
|
||||
function processLogsWithSettings(logs: any[]) {
|
||||
return logs.map((log) => {
|
||||
const processedLog = { ...log };
|
||||
|
||||
if (log.type === "network-request") {
|
||||
// Handle headers visibility
|
||||
if (!currentSettings.showRequestHeaders) {
|
||||
delete processedLog.requestHeaders;
|
||||
}
|
||||
if (!currentSettings.showResponseHeaders) {
|
||||
delete processedLog.responseHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
return processedLog;
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to calculate size of a log entry
|
||||
function calculateLogSize(log: any): number {
|
||||
return JSON.stringify(log).length;
|
||||
}
|
||||
|
||||
// Helper to truncate logs based on character limit
|
||||
function truncateLogsToQueryLimit(logs: any[]): any[] {
|
||||
if (logs.length === 0) return logs;
|
||||
|
||||
// First process logs according to current settings
|
||||
const processedLogs = processLogsWithSettings(logs);
|
||||
|
||||
let currentSize = 0;
|
||||
const result = [];
|
||||
|
||||
for (const log of processedLogs) {
|
||||
const logSize = calculateLogSize(log);
|
||||
|
||||
// Check if adding this log would exceed the limit
|
||||
if (currentSize + logSize > currentSettings.queryLimit) {
|
||||
console.log(
|
||||
`Reached query limit (${currentSize}/${currentSettings.queryLimit}), truncating logs`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add log and update size
|
||||
result.push(log);
|
||||
currentSize += logSize;
|
||||
console.log(`Added log of size ${logSize}, total size now: ${currentSize}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Endpoint for the extension to POST data
|
||||
app.post("/extension-log", (req, res) => {
|
||||
const { data, settings } = req.body;
|
||||
|
||||
// Update settings if provided
|
||||
if (settings) {
|
||||
currentSettings = {
|
||||
...currentSettings,
|
||||
...settings,
|
||||
};
|
||||
}
|
||||
|
||||
console.log("Received log:", data);
|
||||
switch (data.type) {
|
||||
case "console-log":
|
||||
consoleLogs.push(data);
|
||||
if (consoleLogs.length > currentSettings.logLimit) consoleLogs.shift();
|
||||
break;
|
||||
case "console-error":
|
||||
consoleErrors.push(data);
|
||||
if (consoleErrors.length > currentSettings.logLimit)
|
||||
consoleErrors.shift();
|
||||
break;
|
||||
case "network-request":
|
||||
// Route network requests based on status code
|
||||
if (data.status >= 400) {
|
||||
networkErrors.push(data);
|
||||
if (networkErrors.length > currentSettings.logLimit)
|
||||
networkErrors.shift();
|
||||
} else {
|
||||
networkSuccess.push(data);
|
||||
if (networkSuccess.length > currentSettings.logLimit)
|
||||
networkSuccess.shift();
|
||||
}
|
||||
break;
|
||||
case "selected-element":
|
||||
selectedElement = data.element;
|
||||
break;
|
||||
}
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
|
||||
// Update GET endpoints to use the new function
|
||||
app.get("/console-logs", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(consoleLogs);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/console-errors", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(consoleErrors);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/network-errors", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(networkErrors);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/network-success", (req, res) => {
|
||||
const truncatedLogs = truncateLogsToQueryLimit(networkSuccess);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
app.get("/all-xhr", (req, res) => {
|
||||
// Merge and sort network success and error logs by timestamp
|
||||
const mergedLogs = [...networkSuccess, ...networkErrors].sort(
|
||||
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
||||
);
|
||||
const truncatedLogs = truncateLogsToQueryLimit(mergedLogs);
|
||||
res.json(truncatedLogs);
|
||||
});
|
||||
|
||||
// Add new endpoint for selected element
|
||||
app.post("/selected-element", (req, res) => {
|
||||
const { data } = req.body;
|
||||
selectedElement = data;
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
|
||||
app.get("/selected-element", (req, res) => {
|
||||
res.json(selectedElement || { message: "No element selected" });
|
||||
});
|
||||
|
||||
app.get("/.port", (req, res) => {
|
||||
res.send(PORT.toString());
|
||||
});
|
||||
|
||||
interface ScreenshotMessage {
|
||||
type: "screenshot-data" | "screenshot-error";
|
||||
data?: string;
|
||||
path?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class BrowserConnector {
|
||||
private wss: WebSocket.Server;
|
||||
private activeConnection: WebSocket | null = null;
|
||||
private app: express.Application;
|
||||
private server: any;
|
||||
|
||||
constructor(app: express.Application, server: any) {
|
||||
this.app = app;
|
||||
this.server = server;
|
||||
|
||||
// Initialize WebSocket server using the existing HTTP server
|
||||
this.wss = new WebSocket.Server({
|
||||
noServer: true,
|
||||
path: "/extension-ws",
|
||||
});
|
||||
|
||||
// Handle upgrade requests for WebSocket
|
||||
this.server.on(
|
||||
"upgrade",
|
||||
(request: IncomingMessage, socket: Socket, head: Buffer) => {
|
||||
if (request.url === "/extension-ws") {
|
||||
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
this.wss.emit("connection", ws, request);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.wss.on("connection", (ws) => {
|
||||
console.log("Chrome extension connected via WebSocket");
|
||||
this.activeConnection = ws;
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("Chrome extension disconnected");
|
||||
if (this.activeConnection === ws) {
|
||||
this.activeConnection = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add screenshot endpoint
|
||||
this.app.post("/screenshot", async (req, res) => {
|
||||
await this.handleScreenshot(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
private async handleScreenshot(req: express.Request, res: express.Response) {
|
||||
if (!this.activeConnection) {
|
||||
return res.status(503).json({ error: "Chrome extension not connected" });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
// Set up one-time message handler for this screenshot request
|
||||
const messageHandler = (message: WebSocket.Data) => {
|
||||
try {
|
||||
const response: ScreenshotMessage = JSON.parse(message.toString());
|
||||
|
||||
if (response.type === "screenshot-error") {
|
||||
reject(new Error(response.error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
response.type === "screenshot-data" &&
|
||||
response.data &&
|
||||
response.path
|
||||
) {
|
||||
// Remove the data:image/png;base64, prefix
|
||||
const base64Data = response.data.replace(
|
||||
/^data:image\/png;base64,/,
|
||||
""
|
||||
);
|
||||
|
||||
// Ensure the directory exists
|
||||
const dir = path.dirname(response.path);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
// Generate a unique filename using timestamp
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const filename = `screenshot-${timestamp}.png`;
|
||||
const fullPath = path.join(response.path, filename);
|
||||
|
||||
// Write the file
|
||||
fs.writeFileSync(fullPath, base64Data, "base64");
|
||||
resolve({
|
||||
path: fullPath,
|
||||
filename: filename,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
this.activeConnection?.removeListener("message", messageHandler);
|
||||
}
|
||||
};
|
||||
|
||||
// Add temporary message handler
|
||||
this.activeConnection?.on("message", messageHandler);
|
||||
|
||||
// Request screenshot
|
||||
this.activeConnection?.send(
|
||||
JSON.stringify({ type: "take-screenshot" })
|
||||
);
|
||||
|
||||
// Set timeout
|
||||
setTimeout(() => {
|
||||
this.activeConnection?.removeListener("message", messageHandler);
|
||||
reject(new Error("Screenshot timeout"));
|
||||
}, 30000); // 30 second timeout
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
} else {
|
||||
res.status(500).json({ error: "An unknown error occurred" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move the server creation before BrowserConnector instantiation
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`Aggregator listening on http://127.0.0.1:${PORT}`);
|
||||
|
||||
// Write the port to a file so mcp-server can read it
|
||||
fs.writeFileSync(".port", PORT.toString());
|
||||
});
|
||||
|
||||
// Initialize the browser connector with the existing app AND server
|
||||
const browserConnector = new BrowserConnector(app, server);
|
||||
|
||||
// Handle shutdown gracefully
|
||||
process.on("SIGINT", () => {
|
||||
server.close(() => {
|
||||
console.log("Server shut down");
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
@ -1,111 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user