scrapeless-mcp-server 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -27
- package/build/cf.js +27 -0
- package/build/config.js +14 -0
- package/build/context-manager.js +40 -0
- package/build/context.js +120 -0
- package/build/index.js +23 -0
- package/build/server.js +60 -0
- package/build/session-manager.js +76 -0
- package/build/tools/ai_scraper/aiScraper.js +634 -0
- package/build/tools/ai_scraper/api.js +27 -0
- package/build/tools/browser/browser.js +488 -0
- package/build/tools/crawl/api.js +26 -0
- package/build/tools/crawl/crawlCancel.js +19 -0
- package/build/tools/crawl/crawlResult.js +72 -0
- package/build/tools/crawl/crawlStart.js +138 -0
- package/build/tools/deepserp/googleSearch.js +37 -0
- package/build/tools/deepserp/googleTrends.js +208 -0
- package/build/tools/index.js +9 -0
- package/build/tools/llm_chat_scraper/api.js +27 -0
- package/build/tools/llm_chat_scraper/llmChatScraper.js +634 -0
- package/build/tools/universal/scrapeHtml.js +22 -0
- package/build/tools/universal/scrapeMarkdown.js +29 -0
- package/build/tools/universal/scrapeScreenshot.js +48 -0
- package/build/tools/utils.js +86 -0
- package/build/transport.js +120 -0
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Built on the open MCP standard, Scrapeless MCP Server seamlessly connects models
|
|
|
10
10
|
- **Browser automation** for page-level navigation and interaction
|
|
11
11
|
- **Scrape** dynamic, JS-heavy sites—export as HTML, Markdown, or screenshots
|
|
12
12
|
- **Crawl** entire websites by following links and capture each page in multiple formats
|
|
13
|
+
- **AI Scraper** Create an AI Scraper task for ChatGPT, Gemini, Perplexity, Copilot, Google AI Mode, Google AI Overview, Grok, or Alexa
|
|
13
14
|
|
|
14
15
|
Whether you're building an AI research assistant, a coding copilot, or autonomous web agents, this server provides the dynamic context and real-world data your workflows need—**without getting blocked**.
|
|
15
16
|
|
|
@@ -72,7 +73,7 @@ Scrapeless MCP Server supports both **Stdio** and **Streamable HTTP** transport
|
|
|
72
73
|
"command": "npx",
|
|
73
74
|
"args": ["-y", "scrapeless-mcp-server"],
|
|
74
75
|
"env": {
|
|
75
|
-
"
|
|
76
|
+
"SCRAPELESS_API_KEY": "YOUR_SCRAPELESS_KEY"
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -129,31 +130,32 @@ Customize browser session behavior with optional parameters. These can be set vi
|
|
|
129
130
|
|
|
130
131
|
## Supported MCP Tools
|
|
131
132
|
|
|
132
|
-
| Name | Description
|
|
133
|
-
|
|
134
|
-
| google_search | Universal information search engine.
|
|
135
|
-
| google_trends | Get trending search data from Google Trends.
|
|
136
|
-
| browser_create | Create or reuse a cloud browser session using Scrapeless.
|
|
137
|
-
| browser_close | Closes the current session by disconnecting the cloud browser.
|
|
138
|
-
| browser_goto | Navigate browser to a specified URL.
|
|
139
|
-
| browser_go_back | Go back one step in browser history.
|
|
140
|
-
| browser_go_forward | Go forward one step in browser history.
|
|
141
|
-
| browser_click | Click a specific element on the page.
|
|
142
|
-
| browser_type | Type text into a specified input field.
|
|
143
|
-
| browser_press_key | Simulate a key press.
|
|
144
|
-
| browser_wait_for | Wait for a specific page element to appear.
|
|
145
|
-
| browser_wait | Pause execution for a fixed duration.
|
|
146
|
-
| browser_screenshot | Capture a screenshot of the current page.
|
|
147
|
-
| browser_get_html | Get the full HTML of the current page.
|
|
148
|
-
| browser_get_text | Get all visible text from the current page.
|
|
149
|
-
| browser_scroll | Scroll to the bottom of the page.
|
|
150
|
-
| browser_scroll_to | Scroll a specific element into view.
|
|
151
|
-
| scrape_html | Scrape a URL and return its full HTML content.
|
|
152
|
-
| scrape_markdown | Scrape a URL and return its content as Markdown.
|
|
153
|
-
| scrape_screenshot | Capture a high-quality screenshot of any webpage.
|
|
154
|
-
| crawl_start | Start an asynchronous crawl job from a base URL and return its job id.
|
|
155
|
-
| crawl_cancel | Cancel an in-progress crawl job by its id.
|
|
156
|
-
| crawl_result | Poll a crawl job by its id until it completes and return the crawled data.
|
|
133
|
+
| Name | Description |
|
|
134
|
+
|--------------------|-------------------------------------------------------------------------------------------------------------------------|
|
|
135
|
+
| google_search | Universal information search engine. |
|
|
136
|
+
| google_trends | Get trending search data from Google Trends. |
|
|
137
|
+
| browser_create | Create or reuse a cloud browser session using Scrapeless. |
|
|
138
|
+
| browser_close | Closes the current session by disconnecting the cloud browser. |
|
|
139
|
+
| browser_goto | Navigate browser to a specified URL. |
|
|
140
|
+
| browser_go_back | Go back one step in browser history. |
|
|
141
|
+
| browser_go_forward | Go forward one step in browser history. |
|
|
142
|
+
| browser_click | Click a specific element on the page. |
|
|
143
|
+
| browser_type | Type text into a specified input field. |
|
|
144
|
+
| browser_press_key | Simulate a key press. |
|
|
145
|
+
| browser_wait_for | Wait for a specific page element to appear. |
|
|
146
|
+
| browser_wait | Pause execution for a fixed duration. |
|
|
147
|
+
| browser_screenshot | Capture a screenshot of the current page. |
|
|
148
|
+
| browser_get_html | Get the full HTML of the current page. |
|
|
149
|
+
| browser_get_text | Get all visible text from the current page. |
|
|
150
|
+
| browser_scroll | Scroll to the bottom of the page. |
|
|
151
|
+
| browser_scroll_to | Scroll a specific element into view. |
|
|
152
|
+
| scrape_html | Scrape a URL and return its full HTML content. |
|
|
153
|
+
| scrape_markdown | Scrape a URL and return its content as Markdown. |
|
|
154
|
+
| scrape_screenshot | Capture a high-quality screenshot of any webpage. |
|
|
155
|
+
| crawl_start | Start an asynchronous crawl job from a base URL and return its job id. |
|
|
156
|
+
| crawl_cancel | Cancel an in-progress crawl job by its id. |
|
|
157
|
+
| crawl_result | Poll a crawl job by its id until it completes and return the crawled data. |
|
|
158
|
+
| ai_scraper | Create an AI Scraper task for ChatGPT, Gemini, Perplexity, Copilot, Google AI Mode, Google AI Overview, Grok, or Alexa. |
|
|
157
159
|
|
|
158
160
|
## Security Best Practices
|
|
159
161
|
|
|
@@ -163,7 +165,7 @@ When using Scrapeless MCP Server with LLMs (like ChatGPT, Claude, or Cursor), it
|
|
|
163
165
|
|
|
164
166
|
- **Never pass raw scraped content directly into LLM prompts.** Raw HTML, JavaScript, or user-generated text may contain hidden injection payloads.
|
|
165
167
|
- **Sanitize and validate all extracted content.** Strip or escape potentially harmful tags and scripts before using content in downstream logic or AI models.
|
|
166
|
-
- **Prefer structured extraction
|
|
168
|
+
- **Prefer structured extraction to free-form text.** Use tools like `scrape_html`, `scrape_markdown`, or targeted `browser_get_text` with known-safe selectors to extract only the content you trust.
|
|
167
169
|
- **Apply domain or selector whitelisting** when scraping dynamically generated pages, to restrict data flow to known and trusted sources.
|
|
168
170
|
- **Log and monitor all outbound requests** made via browser or scraping tools, especially if you're handling sensitive data, tokens, or internal network access.
|
|
169
171
|
|
package/build/cf.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { McpAgent } from "agents/mcp";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { initMcpTools, serverOptions } from "./server.js";
|
|
4
|
+
import { API_KEY_NAME } from "./config.js";
|
|
5
|
+
export class CfMcpServer extends McpAgent {
|
|
6
|
+
server = new McpServer(serverOptions);
|
|
7
|
+
async init() {
|
|
8
|
+
initMcpTools(this.server, this.props.headers, this.props.apiKey);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export default {
|
|
12
|
+
fetch(request, env, ctx) {
|
|
13
|
+
const url = new URL(request.url);
|
|
14
|
+
const apiKeyHeader = request.headers.get(API_KEY_NAME);
|
|
15
|
+
if (!apiKeyHeader) {
|
|
16
|
+
return new Response(`Unauthorized: Missing ${API_KEY_NAME} header`, { status: 401 });
|
|
17
|
+
}
|
|
18
|
+
ctx.props = { apiKey: apiKeyHeader, headers: request.headers };
|
|
19
|
+
if (url.pathname === "/sse" || url.pathname === "/sse/message") {
|
|
20
|
+
return CfMcpServer.serveSSE("/sse").fetch(request, env, ctx);
|
|
21
|
+
}
|
|
22
|
+
if (url.pathname === "/mcp") {
|
|
23
|
+
return CfMcpServer.serve("/mcp").fetch(request, env, ctx);
|
|
24
|
+
}
|
|
25
|
+
return new Response("Not found", { status: 404 });
|
|
26
|
+
},
|
|
27
|
+
};
|
package/build/config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getParamValue } from "@chatmcp/sdk/utils/index.js";
|
|
2
|
+
export const API_KEY = process.env.SCRAPELESS_API_KEY?.trim() ||
|
|
3
|
+
getParamValue("SCRAPELESS_API_KEY") ||
|
|
4
|
+
process.env.SCRAPELESS_KEY?.trim() ||
|
|
5
|
+
getParamValue("SCRAPELESS_KEY");
|
|
6
|
+
export const BASE_URL = process.env.SCRAPELESS_BASE_URL?.trim() || "https://api.scrapeless.com";
|
|
7
|
+
export const API_KEY_NAME = "x-api-token";
|
|
8
|
+
export const ServerMode = getParamValue("mode") || "stdio";
|
|
9
|
+
export const ServerPort = getParamValue("port");
|
|
10
|
+
export const ServerHostname = getParamValue("hostname") || "0.0.0.0";
|
|
11
|
+
export const SCRAPELESS_CONFIG = {
|
|
12
|
+
baseApiUrl: BASE_URL,
|
|
13
|
+
apiKey: API_KEY,
|
|
14
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Context } from "./context.js";
|
|
2
|
+
import { API_KEY } from "./config.js";
|
|
3
|
+
export class ContextManager {
|
|
4
|
+
static instance;
|
|
5
|
+
contexts;
|
|
6
|
+
constructor() {
|
|
7
|
+
this.contexts = new Map();
|
|
8
|
+
}
|
|
9
|
+
static getInstance() {
|
|
10
|
+
if (!ContextManager.instance) {
|
|
11
|
+
ContextManager.instance = new ContextManager();
|
|
12
|
+
}
|
|
13
|
+
return ContextManager.instance;
|
|
14
|
+
}
|
|
15
|
+
getContext(apiKey) {
|
|
16
|
+
const key = apiKey ?? API_KEY;
|
|
17
|
+
if (!this.contexts.has(key)) {
|
|
18
|
+
console.log(`Creating new Context for API key: ${key?.substring(0, 8)}...`);
|
|
19
|
+
this.contexts.set(key, new Context(key));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log(`Reusing existing Context for API key: ${key?.substring(0, 8)}...`);
|
|
23
|
+
}
|
|
24
|
+
return this.contexts.get(key);
|
|
25
|
+
}
|
|
26
|
+
clearContext(apiKey) {
|
|
27
|
+
const key = apiKey ?? API_KEY;
|
|
28
|
+
if (this.contexts.has(key)) {
|
|
29
|
+
this.contexts.delete(key);
|
|
30
|
+
console.log(`Cleared Context for API key: ${key?.substring(0, 8)}...`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
clearAllContexts() {
|
|
34
|
+
this.contexts.clear();
|
|
35
|
+
console.log("Cleared all Contexts");
|
|
36
|
+
}
|
|
37
|
+
getContextCount() {
|
|
38
|
+
return this.contexts.size;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/build/context.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { SessionManager } from "./session-manager.js";
|
|
2
|
+
import { uuid } from "./tools/utils.js";
|
|
3
|
+
export class Context {
|
|
4
|
+
sessionManager;
|
|
5
|
+
currentSessionId = "default-session-id";
|
|
6
|
+
apiKey;
|
|
7
|
+
constructor(apiKey) {
|
|
8
|
+
this.sessionManager = SessionManager.getInstance();
|
|
9
|
+
this.apiKey = apiKey;
|
|
10
|
+
}
|
|
11
|
+
getSession(id) {
|
|
12
|
+
return this.sessionManager.getSession(`${id ?? this.currentSessionId}-${this.apiKey}`);
|
|
13
|
+
}
|
|
14
|
+
async run(tool, params, headers) {
|
|
15
|
+
if (!this.apiKey) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: `API key is missing.`,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (params.sessionId && params.sessionId !== this.currentSessionId) {
|
|
26
|
+
this.currentSessionId = params.sessionId;
|
|
27
|
+
}
|
|
28
|
+
const toolName = tool.name;
|
|
29
|
+
if (toolName === "browser_create") {
|
|
30
|
+
const newSessionId = uuid();
|
|
31
|
+
try {
|
|
32
|
+
await this.sessionManager.createSession(`${newSessionId}-${this.apiKey}`, this.apiKey, headers);
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `New browser session created with ID: ${newSessionId}`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: `Failed to create browser session. Please try again later.`,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (toolName === "browser_close") {
|
|
54
|
+
const sessionId = params?.sessionId;
|
|
55
|
+
if (!sessionId) {
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `Session ID is missing.`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
await this.sessionManager.closeSession(`${sessionId}-${this.apiKey}`);
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `Browser session with ID: ${sessionId} has been closed.`,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: `Failed to close browser session with ID: ${sessionId}. Please try again later.`,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let session = null;
|
|
88
|
+
session = this.sessionManager.getSession(`${this.currentSessionId}-${this.apiKey}`);
|
|
89
|
+
if (!session) {
|
|
90
|
+
const newSessionId = uuid();
|
|
91
|
+
await this.sessionManager.createSession(`${newSessionId}-${this.apiKey}`, this.apiKey);
|
|
92
|
+
this.currentSessionId = newSessionId;
|
|
93
|
+
}
|
|
94
|
+
let toolActionOutput = undefined;
|
|
95
|
+
let actionSucceeded = false;
|
|
96
|
+
try {
|
|
97
|
+
let action = undefined;
|
|
98
|
+
action = tool.handle;
|
|
99
|
+
if (action) {
|
|
100
|
+
toolActionOutput = await action(this, params);
|
|
101
|
+
actionSucceeded = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
if (actionSucceeded && toolActionOutput !== undefined) {
|
|
108
|
+
return toolActionOutput;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: `${toolName} action completed successfully.`,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { ServerPort, API_KEY, ServerHostname } from "./config.js";
|
|
3
|
+
import { startHttpTransport, startStdioTransport } from "./transport.js";
|
|
4
|
+
process.env.SCRAPELESS_IS_ONLINE = "true";
|
|
5
|
+
async function main() {
|
|
6
|
+
try {
|
|
7
|
+
if (ServerPort) {
|
|
8
|
+
// See use the /see endpoint
|
|
9
|
+
// Streamable HTTP use the /mcp endpoint
|
|
10
|
+
startHttpTransport(Number(ServerPort), ServerHostname);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
if (!API_KEY)
|
|
14
|
+
throw new Error("❌ Missing environment variable: SCRAPELESS_API_KEY");
|
|
15
|
+
startStdioTransport();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error("Fatal error in main():", error);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
main();
|
package/build/server.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ScrapelessClient } from "@scrapeless-ai/sdk";
|
|
3
|
+
import { SCRAPELESS_CONFIG, API_KEY } from "./config.js";
|
|
4
|
+
import * as toolsList from "./tools/index.js";
|
|
5
|
+
import * as browserTools from "./tools/browser/browser.js";
|
|
6
|
+
import { ContextManager } from "./context-manager.js";
|
|
7
|
+
export const serverOptions = {
|
|
8
|
+
name: "scrapeless-mcp-server",
|
|
9
|
+
version: "0.2.0",
|
|
10
|
+
capabilities: { resources: {}, tools: {} },
|
|
11
|
+
};
|
|
12
|
+
export const createMcpServer = (options) => {
|
|
13
|
+
const server = new McpServer(serverOptions);
|
|
14
|
+
initMcpTools(server, options?.headers, options?.apiKey);
|
|
15
|
+
return server.server;
|
|
16
|
+
};
|
|
17
|
+
export const initMcpTools = (server, headers, apiKey) => {
|
|
18
|
+
const getScrapelessClient = () => {
|
|
19
|
+
if (apiKey) {
|
|
20
|
+
return new ScrapelessClient({
|
|
21
|
+
apiKey: apiKey,
|
|
22
|
+
baseApiUrl: SCRAPELESS_CONFIG.baseApiUrl,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// Fallback for Stdio mode or when no API key is provided
|
|
26
|
+
return new ScrapelessClient(SCRAPELESS_CONFIG);
|
|
27
|
+
};
|
|
28
|
+
// tools registration
|
|
29
|
+
Object.values(toolsList).forEach((tool) => {
|
|
30
|
+
server.tool(tool.name, tool.description, tool.inputSchema, (params) => tool.handle(params, getScrapelessClient(), headers));
|
|
31
|
+
});
|
|
32
|
+
const context = ContextManager.getInstance().getContext(apiKey ?? API_KEY);
|
|
33
|
+
Object.values(browserTools).forEach((tool) => {
|
|
34
|
+
server.tool(tool.name, tool.description, tool.inputSchema, async (params) => {
|
|
35
|
+
const result = await context.run(tool, params, headers);
|
|
36
|
+
return result;
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
export class ServerList {
|
|
41
|
+
_servers = [];
|
|
42
|
+
_serverFactory;
|
|
43
|
+
constructor(serverFactory) {
|
|
44
|
+
this._serverFactory = serverFactory;
|
|
45
|
+
}
|
|
46
|
+
async create() {
|
|
47
|
+
const server = await this._serverFactory();
|
|
48
|
+
this._servers.push(server);
|
|
49
|
+
return server;
|
|
50
|
+
}
|
|
51
|
+
async close(server) {
|
|
52
|
+
const index = this._servers.indexOf(server);
|
|
53
|
+
if (index !== -1)
|
|
54
|
+
this._servers.splice(index, 1);
|
|
55
|
+
await server.close();
|
|
56
|
+
}
|
|
57
|
+
async closeAll() {
|
|
58
|
+
await Promise.all(this._servers.map((server) => server.close()));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Scrapeless } from "@scrapeless-ai/sdk";
|
|
2
|
+
import { getParamValue } from "@chatmcp/sdk/utils/index.js";
|
|
3
|
+
import puppeteer from "puppeteer-core";
|
|
4
|
+
export class SessionManager {
|
|
5
|
+
static instance;
|
|
6
|
+
sessions;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.sessions = new Map();
|
|
9
|
+
}
|
|
10
|
+
static getInstance() {
|
|
11
|
+
if (!SessionManager.instance) {
|
|
12
|
+
SessionManager.instance = new SessionManager();
|
|
13
|
+
}
|
|
14
|
+
return SessionManager.instance;
|
|
15
|
+
}
|
|
16
|
+
async createSession(id, apiKey, headers) {
|
|
17
|
+
if (!this.sessions.has(id)) {
|
|
18
|
+
this.sessions.set(id, { browser: null, page: null, closed: true });
|
|
19
|
+
}
|
|
20
|
+
const scrapelessClient = new Scrapeless({ apiKey });
|
|
21
|
+
const session = this.sessions.get(id);
|
|
22
|
+
const { browserWSEndpoint } = scrapelessClient.browser.create({
|
|
23
|
+
session_ttl: Number(process.env.BROWSER_SESSION_TTL ||
|
|
24
|
+
getParamValue("BROWSER_SESSION_TTL") ||
|
|
25
|
+
headers?.["x-browser-session-ttl"] ||
|
|
26
|
+
30000),
|
|
27
|
+
profile_id: process.env.BROWSER_PROFILE_ID ||
|
|
28
|
+
getParamValue("BROWSER_PROFILE_ID") ||
|
|
29
|
+
headers?.["x-browser-profile-id"] ||
|
|
30
|
+
"",
|
|
31
|
+
profile_persist: Boolean(process.env.BROWSER_PROFILE_PERSIST ||
|
|
32
|
+
getParamValue("BROWSER_PROFILE_PERSIST") ||
|
|
33
|
+
headers?.["x-browser-profile-persist"]),
|
|
34
|
+
});
|
|
35
|
+
const browser = await puppeteer.connect({
|
|
36
|
+
browserWSEndpoint,
|
|
37
|
+
defaultViewport: null,
|
|
38
|
+
});
|
|
39
|
+
const pages = await browser.pages();
|
|
40
|
+
const page = pages.length > 0 ? pages[0] : await browser.newPage();
|
|
41
|
+
browser.on("disconnected", () => {
|
|
42
|
+
session.closed = true;
|
|
43
|
+
session.browser = null;
|
|
44
|
+
session.page = null;
|
|
45
|
+
this.sessions.delete(id);
|
|
46
|
+
});
|
|
47
|
+
session.browser = browser;
|
|
48
|
+
session.page = page;
|
|
49
|
+
session.closed = false;
|
|
50
|
+
return session;
|
|
51
|
+
}
|
|
52
|
+
getSession(id) {
|
|
53
|
+
if (!this.sessions.has(id))
|
|
54
|
+
return null;
|
|
55
|
+
const session = this.sessions.get(id);
|
|
56
|
+
if (!session.browser || !session.browser?.connected) {
|
|
57
|
+
session.browser = null;
|
|
58
|
+
session.page = null;
|
|
59
|
+
session.closed = true;
|
|
60
|
+
this.sessions.delete(id);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return session;
|
|
64
|
+
}
|
|
65
|
+
async closeSession(id) {
|
|
66
|
+
const session = this.sessions.get(id);
|
|
67
|
+
if (session) {
|
|
68
|
+
session.page?.close().catch(() => { });
|
|
69
|
+
session.browser?.disconnect().catch(() => { });
|
|
70
|
+
session.closed = true;
|
|
71
|
+
session.browser = null;
|
|
72
|
+
session.page = null;
|
|
73
|
+
this.sessions.delete(id);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|