scrapeless-mcp-server 0.1.4 → 0.4.0
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/LICENSE +21 -0
- package/README.md +97 -14
- package/build/cf.js +27 -0
- package/build/config.js +9 -10
- package/build/context.js +120 -0
- package/build/index.js +13 -4
- package/build/server.js +56 -24
- package/build/session-manager.js +55 -0
- package/build/tools/browser/browser.js +444 -0
- package/build/tools/deepserp/googleFlights.js +188 -0
- package/build/tools/deepserp/googleScholar.js +74 -0
- package/build/tools/deepserp/googleSearch.js +269 -0
- package/build/tools/deepserp/googleTrends.js +208 -0
- package/build/tools/index.js +7 -0
- package/build/tools/universal/scrapeHtml.js +22 -0
- package/build/tools/universal/scrapeMarkdown.js +22 -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 +14 -4
- package/build/client.js +0 -27
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ Model Context Protocol (MCP) is an open protocol that enables seamless integrati
|
|
|
6
6
|
|
|
7
7
|
Seamlessly integrate real-time Google SERP(Google Search, Google Flight, Google Map, Google Jobs....) results into your LLM applications using the Scrapeless MCP server. This server acts as a bridge between LLMs (like ChatGPT, Claude, etc.) and Scrapeless's Google SERP, enabling dynamic context retrieval for AI workflows, chatbots, and research tools.
|
|
8
8
|
|
|
9
|
-
👉 Live MCP Endpoint:
|
|
9
|
+
👉 Live MCP Endpoint:
|
|
10
|
+
|
|
10
11
|
- [mcp.so](https://mcp.so/server/scrapelessMcpServer/scrapeless-ai)
|
|
11
12
|
- [glama.ai](https://glama.ai/mcp/servers/@scrapeless-ai/scrapeless-mcp-server)
|
|
12
13
|
|
|
@@ -18,31 +19,90 @@ This project provides several MCP servers that enable AI assistants like Claude
|
|
|
18
19
|
|
|
19
20
|
- Google Search
|
|
20
21
|
|
|
22
|
+
## Supported Transport Protocols
|
|
23
|
+
|
|
24
|
+
- **Stdio** `default`: The transport runs locally on your machine and communicates via standard input/output streams.
|
|
25
|
+
- **Streamable HTTP** `--mode=streamable_http`: The new MCP transport protocol that simplifies client-server communication through a single HTTP endpoint
|
|
26
|
+
|
|
21
27
|
## Tools
|
|
22
28
|
|
|
23
|
-
###
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
### Browser Tools
|
|
30
|
+
|
|
31
|
+
- `browser_goto`
|
|
32
|
+
Navigate browser to a specified URL.
|
|
33
|
+
|
|
34
|
+
- `browser_go_back`
|
|
35
|
+
Go back one step in browser history.
|
|
36
|
+
|
|
37
|
+
- `browser_go_forward`
|
|
38
|
+
Go forward one step in browser history.
|
|
39
|
+
|
|
40
|
+
- `browser_click`
|
|
41
|
+
Click a specific element on the page.
|
|
42
|
+
|
|
43
|
+
- `browser_type`
|
|
44
|
+
Type text into a specified input field.
|
|
45
|
+
|
|
46
|
+
- `browser_wait_for`
|
|
47
|
+
Wait for a specific page element to appear.
|
|
48
|
+
|
|
49
|
+
- `browser_wait`
|
|
50
|
+
Pause execution for a fixed duration.
|
|
51
|
+
|
|
52
|
+
- `browser_screenshot`
|
|
53
|
+
Capture a screenshot of the current page.
|
|
30
54
|
|
|
55
|
+
- `browser_get_html`
|
|
56
|
+
Get the full HTML of the current page.
|
|
57
|
+
|
|
58
|
+
- `browser_get_text`
|
|
59
|
+
Get all visible text from the current page.
|
|
60
|
+
|
|
61
|
+
- `browser_scroll`
|
|
62
|
+
Scroll to the bottom of the page.
|
|
63
|
+
|
|
64
|
+
- `browser_scroll_to`
|
|
65
|
+
Scroll a specific element into view.
|
|
66
|
+
|
|
67
|
+
### Universal Tools
|
|
68
|
+
|
|
69
|
+
- `scrape_html`
|
|
70
|
+
Scrape a URL and return its full HTML content.
|
|
71
|
+
- `scrape_markdown`
|
|
72
|
+
Scrape a URL and return its content as Markdown.
|
|
73
|
+
|
|
74
|
+
- `scrape_screenshot`
|
|
75
|
+
Capture a high-quality screenshot of any webpage.
|
|
76
|
+
|
|
77
|
+
### Deepserp Tools
|
|
78
|
+
|
|
79
|
+
- `google_search`
|
|
80
|
+
universal information search engine.retrieves any data information.explanatory queries (why, how).comparative analysis requests.
|
|
81
|
+
- `google_scholar`
|
|
82
|
+
Search for academic papers on Google Scholar.
|
|
83
|
+
|
|
84
|
+
- `google_flights`
|
|
85
|
+
Exclusive flight information query tool.
|
|
86
|
+
|
|
87
|
+
- `google_trends`
|
|
88
|
+
Get trending search data from Google Trends.
|
|
31
89
|
|
|
32
90
|
## Setup Guide
|
|
33
91
|
|
|
34
92
|
### 1. Get Scrapeless Key
|
|
93
|
+
|
|
35
94
|
1. Register at [Scrapeless](https://app.scrapeless.com/passport/register?utm_source=github&utm_medium=mcp)
|
|
36
95
|
2. [Get your free trial](https://app.scrapeless.com/landing/guide?utm_source=github&utm_medium=mcp)
|
|
37
96
|
3. [Generate API Key](https://app.scrapeless.com/dashboard/settings/api-key?utm_source=github&utm_medium=mcp)
|
|
38
97
|
|
|
39
|
-
|
|
40
98
|
### 2. Configure
|
|
41
99
|
|
|
100
|
+
#### Stdio
|
|
101
|
+
|
|
42
102
|
```json
|
|
43
103
|
{
|
|
44
104
|
"mcpServers": {
|
|
45
|
-
"
|
|
105
|
+
"ScrapelessMcpServer": {
|
|
46
106
|
"command": "npx",
|
|
47
107
|
"args": ["-y", "scrapeless-mcp-server"],
|
|
48
108
|
"env": {
|
|
@@ -53,17 +113,38 @@ This project provides several MCP servers that enable AI assistants like Claude
|
|
|
53
113
|
}
|
|
54
114
|
```
|
|
55
115
|
|
|
116
|
+
#### Streamable
|
|
117
|
+
|
|
118
|
+
1. Run locally
|
|
119
|
+
|
|
120
|
+
```shell
|
|
121
|
+
npx scrapeless-mcp-server --mode=streamable_http --SCRAPELESS_KEY=YOUR_SCRAPELESS_KEY
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
2. Add mcp configuration
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"mcpServers": {
|
|
129
|
+
"ScrapelessMcpServer": {
|
|
130
|
+
"type": "streamable-http",
|
|
131
|
+
"url": "http://127.0.0.1:9593/mcp",
|
|
132
|
+
"disabled": false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
56
137
|
|
|
57
138
|
## Example Queries
|
|
58
139
|
|
|
59
140
|
Here are some examples of how to use these servers with Claude Desktop:
|
|
60
141
|
|
|
61
142
|
### Google Search
|
|
143
|
+
|
|
62
144
|
```
|
|
63
145
|
Please search for "climate change solutions" and summarize the top results.
|
|
64
146
|
```
|
|
65
147
|
|
|
66
|
-
|
|
67
148
|
## Installation
|
|
68
149
|
|
|
69
150
|
### Prerequisites
|
|
@@ -74,22 +155,24 @@ Please search for "climate change solutions" and summarize the top results.
|
|
|
74
155
|
### Install from Source
|
|
75
156
|
|
|
76
157
|
1. Clone the repository:
|
|
158
|
+
|
|
77
159
|
```bash
|
|
78
160
|
git clone https://github.com/scrapeless-ai/scrapeless-mcp-server.git
|
|
79
161
|
cd scrapeless-mcp-server
|
|
80
162
|
```
|
|
81
163
|
|
|
82
164
|
2. Install dependencies:
|
|
165
|
+
|
|
83
166
|
```bash
|
|
84
167
|
npm install
|
|
85
168
|
```
|
|
86
169
|
|
|
87
|
-
|
|
88
170
|
3. Build the server:
|
|
171
|
+
|
|
89
172
|
```bash
|
|
90
173
|
npm run build
|
|
91
174
|
```
|
|
92
175
|
|
|
93
|
-
|
|
94
176
|
## Community
|
|
95
|
-
|
|
177
|
+
|
|
178
|
+
- [MCP Server Discord](https://backend.scrapeless.com/app/api/v1/public/links/discord)
|
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.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 };
|
|
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
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const
|
|
7
|
-
|
|
8
|
-
};
|
|
1
|
+
import { getParamValue } from "@chatmcp/sdk/utils/index.js";
|
|
2
|
+
export const API_KEY = process.env.SCRAPELESS_KEY?.trim() || getParamValue("SCRAPELESS_KEY");
|
|
3
|
+
export const BASE_URL = process.env.SCRAPELESS_BASE_URL?.trim() || "https://api.scrapeless.com";
|
|
4
|
+
export const API_KEY_NAME = "x-api-token";
|
|
5
|
+
export const ServerMode = getParamValue("mode") || "stdio";
|
|
6
|
+
export const ServerPort = getParamValue("port");
|
|
7
|
+
export const ServerHostname = getParamValue("hostname") || "0.0.0.0";
|
|
9
8
|
export const SCRAPELESS_CONFIG = {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
baseApiUrl: BASE_URL,
|
|
10
|
+
apiKey: API_KEY,
|
|
12
11
|
};
|
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 = new SessionManager();
|
|
9
|
+
this.apiKey = apiKey;
|
|
10
|
+
}
|
|
11
|
+
getSession(id) {
|
|
12
|
+
return this.sessionManager.getSession(`${id ?? this.currentSessionId}-${this.apiKey}`);
|
|
13
|
+
}
|
|
14
|
+
async run(tool, params) {
|
|
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);
|
|
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
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ServerPort, API_KEY, ServerHostname } from "./config.js";
|
|
3
|
+
import { startHttpTransport, startStdioTransport } from "./transport.js";
|
|
4
|
+
process.env.SCRAPELESS_IS_ONLINE = "true";
|
|
4
5
|
async function main() {
|
|
5
6
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
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_KEY");
|
|
15
|
+
startStdioTransport();
|
|
16
|
+
}
|
|
8
17
|
}
|
|
9
18
|
catch (error) {
|
|
10
19
|
console.error("Fatal error in main():", error);
|
package/build/server.js
CHANGED
|
@@ -1,28 +1,60 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
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 { Context } from './context.js';
|
|
7
|
+
export const serverOptions = {
|
|
7
8
|
name: "scrapeless-mcp-server",
|
|
8
|
-
version: "0.
|
|
9
|
+
version: "0.2.0",
|
|
9
10
|
capabilities: { resources: {}, tools: {} },
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
})),
|
|
11
|
+
};
|
|
12
|
+
export const createMcpServer = (apiKey) => {
|
|
13
|
+
const server = new McpServer(serverOptions);
|
|
14
|
+
initMcpTools(server, apiKey);
|
|
15
|
+
return server.server;
|
|
16
|
+
};
|
|
17
|
+
export const initMcpTools = (server, 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
27
|
};
|
|
28
|
-
|
|
28
|
+
// tools registration
|
|
29
|
+
Object.values(toolsList).forEach((tool) => {
|
|
30
|
+
server.tool(tool.name, tool.description, tool.inputSchema, (params) => tool.handle(params, getScrapelessClient()));
|
|
31
|
+
});
|
|
32
|
+
const context = new Context(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);
|
|
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,55 @@
|
|
|
1
|
+
import { Scrapeless } from "@scrapeless-ai/sdk";
|
|
2
|
+
import puppeteer from "puppeteer-core";
|
|
3
|
+
export class SessionManager {
|
|
4
|
+
sessions;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.sessions = new Map();
|
|
7
|
+
}
|
|
8
|
+
async createSession(id, apiKey) {
|
|
9
|
+
if (!this.sessions.has(id)) {
|
|
10
|
+
this.sessions.set(id, { browser: null, page: null, closed: true });
|
|
11
|
+
}
|
|
12
|
+
const scrapelessClient = new Scrapeless({ apiKey });
|
|
13
|
+
const session = this.sessions.get(id);
|
|
14
|
+
const { browserWSEndpoint } = scrapelessClient.browser.create();
|
|
15
|
+
const browser = await puppeteer.connect({
|
|
16
|
+
browserWSEndpoint,
|
|
17
|
+
defaultViewport: null,
|
|
18
|
+
});
|
|
19
|
+
const pages = await browser.pages();
|
|
20
|
+
const page = pages.length > 0 ? pages[0] : await browser.newPage();
|
|
21
|
+
browser.on("disconnected", () => {
|
|
22
|
+
session.closed = true;
|
|
23
|
+
session.browser = null;
|
|
24
|
+
session.page = null;
|
|
25
|
+
});
|
|
26
|
+
session.browser = browser;
|
|
27
|
+
session.page = page;
|
|
28
|
+
session.closed = false;
|
|
29
|
+
return session;
|
|
30
|
+
}
|
|
31
|
+
getSession(id) {
|
|
32
|
+
if (!this.sessions.has(id))
|
|
33
|
+
return null;
|
|
34
|
+
const session = this.sessions.get(id);
|
|
35
|
+
if (!session.browser || !session.browser?.connected) {
|
|
36
|
+
session.browser = null;
|
|
37
|
+
session.page = null;
|
|
38
|
+
session.closed = true;
|
|
39
|
+
this.sessions.delete(id);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return session;
|
|
43
|
+
}
|
|
44
|
+
async closeSession(id) {
|
|
45
|
+
const session = this.sessions.get(id);
|
|
46
|
+
if (session) {
|
|
47
|
+
session.page?.close().catch(() => { });
|
|
48
|
+
session.browser?.disconnect().catch(() => { });
|
|
49
|
+
session.closed = true;
|
|
50
|
+
session.browser = null;
|
|
51
|
+
session.page = null;
|
|
52
|
+
this.sessions.delete(id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|