grasp-sdk 0.1.8__py3-none-any.whl → 0.2.0a1__py3-none-any.whl

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.

Potentially problematic release.


This version of grasp-sdk might be problematic. Click here for more details.

Files changed (38) hide show
  1. examples/example_async_context.py +122 -0
  2. examples/example_binary_file_support.py +127 -0
  3. examples/example_grasp_usage.py +82 -0
  4. examples/example_readfile_usage.py +136 -0
  5. examples/grasp_terminal.py +110 -0
  6. examples/grasp_usage.py +111 -0
  7. examples/test_async_context.py +64 -0
  8. examples/test_get_replay_screenshots.py +90 -0
  9. examples/test_grasp_classes.py +80 -0
  10. examples/test_python_script.py +160 -0
  11. examples/test_removed_methods.py +80 -0
  12. examples/test_shutdown_deprecation.py +62 -0
  13. examples/test_terminal_updates.py +196 -0
  14. grasp_sdk/__init__.py +131 -239
  15. grasp_sdk/grasp/__init__.py +26 -0
  16. grasp_sdk/grasp/browser.py +69 -0
  17. grasp_sdk/grasp/index.py +122 -0
  18. grasp_sdk/grasp/server.py +250 -0
  19. grasp_sdk/grasp/session.py +108 -0
  20. grasp_sdk/grasp/terminal.py +32 -0
  21. grasp_sdk/grasp/utils.py +90 -0
  22. grasp_sdk/models/__init__.py +1 -1
  23. grasp_sdk/services/__init__.py +8 -1
  24. grasp_sdk/services/browser.py +92 -63
  25. grasp_sdk/services/filesystem.py +94 -0
  26. grasp_sdk/services/sandbox.py +391 -20
  27. grasp_sdk/services/terminal.py +177 -0
  28. grasp_sdk/utils/auth.py +6 -8
  29. {grasp_sdk-0.1.8.dist-info → grasp_sdk-0.2.0a1.dist-info}/METADATA +2 -3
  30. grasp_sdk-0.2.0a1.dist-info/RECORD +36 -0
  31. {grasp_sdk-0.1.8.dist-info → grasp_sdk-0.2.0a1.dist-info}/top_level.txt +1 -0
  32. grasp_sdk/sandbox/bootstrap-chrome-stable.mjs +0 -69
  33. grasp_sdk/sandbox/chrome-stable.mjs +0 -424
  34. grasp_sdk/sandbox/chromium.mjs +0 -395
  35. grasp_sdk/sandbox/http-proxy.mjs +0 -324
  36. grasp_sdk-0.1.8.dist-info/RECORD +0 -18
  37. {grasp_sdk-0.1.8.dist-info → grasp_sdk-0.2.0a1.dist-info}/WHEEL +0 -0
  38. {grasp_sdk-0.1.8.dist-info → grasp_sdk-0.2.0a1.dist-info}/entry_points.txt +0 -0
grasp_sdk/utils/auth.py CHANGED
@@ -16,8 +16,6 @@ except ImportError:
16
16
  if TYPE_CHECKING:
17
17
  from aiohttp import ClientSession
18
18
 
19
- from ..models import ISandboxConfig
20
-
21
19
 
22
20
  class AuthError(Exception):
23
21
  """Exception raised for authentication errors."""
@@ -69,11 +67,11 @@ async def login(token: str) -> Dict[str, Any]:
69
67
  raise AuthError(f"Unexpected error during authentication: {str(e)}")
70
68
 
71
69
 
72
- async def verify(config: ISandboxConfig) -> Dict[str, Any]:
73
- """Verifies the sandbox configuration and authenticates.
70
+ async def verify(config: Dict[str, str]) -> Dict[str, Any]:
71
+ """Verifies the configuration and authenticates.
74
72
 
75
73
  Args:
76
- config: Sandbox configuration containing the API key
74
+ config: Configuration containing the API key (format: {'key': 'your-key'})
77
75
 
78
76
  Returns:
79
77
  Dict[str, Any]: Authentication response
@@ -82,7 +80,7 @@ async def verify(config: ISandboxConfig) -> Dict[str, Any]:
82
80
  KeyVerificationError: If the key is missing or invalid
83
81
  AuthError: If authentication fails
84
82
  """
85
- if not config['key']:
83
+ if not config.get('key'):
86
84
  raise KeyVerificationError('Grasp key is required')
87
85
 
88
86
  try:
@@ -93,11 +91,11 @@ async def verify(config: ISandboxConfig) -> Dict[str, Any]:
93
91
  raise KeyVerificationError(f"Key verification failed: {str(e)}")
94
92
 
95
93
 
96
- def verify_sync(config: ISandboxConfig) -> Dict[str, Any]:
94
+ def verify_sync(config: Dict[str, str]) -> Dict[str, Any]:
97
95
  """Synchronous wrapper for the verify function.
98
96
 
99
97
  Args:
100
- config: Sandbox configuration containing the API key
98
+ config: Configuration containing the API key (format: {'key': 'your-key'})
101
99
 
102
100
  Returns:
103
101
  Dict[str, Any]: Authentication response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grasp_sdk
3
- Version: 0.1.8
3
+ Version: 0.2.0a1
4
4
  Summary: Python SDK for Grasp E2B - Browser automation and sandbox management
5
5
  Home-page: https://github.com/grasp-team/grasp-e2b
6
6
  Author: Grasp Team
@@ -27,6 +27,7 @@ Description-Content-Type: text/markdown
27
27
  Requires-Dist: aiohttp>=3.9.0
28
28
  Requires-Dist: python-dotenv>=1.0.0
29
29
  Requires-Dist: typing-extensions>=4.8.0
30
+ Requires-Dist: websockets>=12.0
30
31
  Requires-Dist: e2b>=1.5.0
31
32
  Requires-Dist: e2b-code-interpreter>=1.5.0
32
33
  Provides-Extra: dev
@@ -37,8 +38,6 @@ Requires-Dist: flake8>=6.0.0; extra == "dev"
37
38
  Requires-Dist: mypy>=1.0.0; extra == "dev"
38
39
  Provides-Extra: browser
39
40
  Requires-Dist: playwright>=1.40.0; extra == "browser"
40
- Provides-Extra: websocket
41
- Requires-Dist: websockets>=12.0; extra == "websocket"
42
41
  Provides-Extra: validation
43
42
  Requires-Dist: pydantic>=2.5.0; extra == "validation"
44
43
  Dynamic: author
@@ -0,0 +1,36 @@
1
+ examples/example_async_context.py,sha256=W4PvU-Ck6wEc6c5L9S0i3RoeHhJ6TtqiZFRZKSVS8QU,4031
2
+ examples/example_binary_file_support.py,sha256=katDD0u8Wd0D3-8QbsNIK7-dnMwWQSPNdl3kqHOQgq0,4432
3
+ examples/example_grasp_usage.py,sha256=_mX25NLn6nF96MHU8LWq65sL5D4XSISYRDP6X3BQ48s,2731
4
+ examples/example_readfile_usage.py,sha256=2KM0lJoMdlroIogaZr_dIqsh_BOrc3GmY1AD2N-2nK8,4505
5
+ examples/grasp_terminal.py,sha256=dDstcnCs89paJ3o2SxAmHmI5_190RUzI_c8gX6aieUg,3415
6
+ examples/grasp_usage.py,sha256=kOq2eU51TX6C8oWXC19-JLWshCssBoc_8Y9h2Yv901Y,3806
7
+ examples/test_async_context.py,sha256=0iDq7ofGM8gUcH5m6MUrgLiRbaEka3mgKO-s9DWZdpM,2379
8
+ examples/test_get_replay_screenshots.py,sha256=fXK_lbQ7KvXC6rnLVwO_hORZXhhHLdpdOYNoDXEYaEs,2991
9
+ examples/test_grasp_classes.py,sha256=qeghDiw-91JN_2YerSfMLeMi4oJiJBeQ1NtlYzmK2AI,3148
10
+ examples/test_python_script.py,sha256=dqJSmvmrq2fdNXynJtMjqMXorrrM08ho6pFibIAZMpI,5265
11
+ examples/test_removed_methods.py,sha256=iW9Wxi8wRXdIffOGV_EjYOtDujOTyJoWpdMXTIExlOI,2808
12
+ examples/test_shutdown_deprecation.py,sha256=QbzLld_3Ur8MjOKSiOrfluW507HYtnfTbgbfwb-OYWo,2127
13
+ examples/test_terminal_updates.py,sha256=qh04AuM7T3TuwtOAHaH70YRY2ur88rVj3j-StlYFkQs,6688
14
+ grasp_sdk/__init__.py,sha256=Sjru_RR0LAOm84WY2fxpsHfcxRLI3kjmEO321Yj5t0E,5216
15
+ grasp_sdk/grasp/__init__.py,sha256=5i8Now_-re_FtijbkCJhQp-H_WOaV9ncj39vVjM2UKE,499
16
+ grasp_sdk/grasp/browser.py,sha256=wmt9W38s-__BTqBAODjvfWBg-1dGCvx4PfGTNy3Sj6U,2295
17
+ grasp_sdk/grasp/index.py,sha256=2p8mbrKiV0sC4YBr2ncUcF821BOOtFhh6T-aQUdR-ow,4103
18
+ grasp_sdk/grasp/server.py,sha256=arAYJZuJNozDuQTCX7HFgO9p9pA_TC-Sz5fz68jnKKY,8053
19
+ grasp_sdk/grasp/session.py,sha256=uIo5pQfyJp70fCfWSHiLow_UTVajTTJCIXjPb5IK40A,4023
20
+ grasp_sdk/grasp/terminal.py,sha256=7GhfhStfdKykjwgm1xQZDjlePUaEJ83Zls3QFRi5BPs,955
21
+ grasp_sdk/grasp/utils.py,sha256=j22tUI9yZacsdURObs-a5hxVM7sHsPBO4msRxdVb3MY,2691
22
+ grasp_sdk/models/__init__.py,sha256=7uza9Y0yk6rFpQmyjaw0dVDf5uvaho42biuU1RiUzYU,2636
23
+ grasp_sdk/services/__init__.py,sha256=tfiAU211-iMObTflMX4y4TYPU6uXr8g5J0tcc7hLzYs,540
24
+ grasp_sdk/services/browser.py,sha256=RPrLjK8GB92Q1ZmDr0cVooCmv8do8RgHShRY2_999As,15218
25
+ grasp_sdk/services/filesystem.py,sha256=XjeOgJJiPAUIBMvGLb8S9k1TBVan6ntl7Jg6PzNsGoc,3238
26
+ grasp_sdk/services/sandbox.py,sha256=nIR9Xb5CiwxbwneCa-i-YD60mL7UwMVO9EEhIdRuxac,35587
27
+ grasp_sdk/services/terminal.py,sha256=zBoWpOHxBM6Fi50SDd4fEypqBGd595UblLfp_912XBk,5807
28
+ grasp_sdk/utils/__init__.py,sha256=IQzRV-iZJXanSlaXBcgXBCcOXTVBCY6ZujxQpDTGW9w,816
29
+ grasp_sdk/utils/auth.py,sha256=W51xyswim48wy2x3xGFFOfWXFAmG4jAwh5wxDgWjKP4,7261
30
+ grasp_sdk/utils/config.py,sha256=NhHnUwmYOaaV_DZVep_BXsKoZPNT_-yRsiV04q83IXw,3967
31
+ grasp_sdk/utils/logger.py,sha256=k6WDmzL3cPTcQbiiTD4Q6fsDdUO_kyXQHz7nlmtv7G4,7228
32
+ grasp_sdk-0.2.0a1.dist-info/METADATA,sha256=8XBosDPayz3Im-418024HVby_0yUwiUs8-00d6T88fw,10121
33
+ grasp_sdk-0.2.0a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ grasp_sdk-0.2.0a1.dist-info/entry_points.txt,sha256=roDjUu4JR6b1tUtRmq018mw167AbfC5zQiOtv3o6IMo,45
35
+ grasp_sdk-0.2.0a1.dist-info/top_level.txt,sha256=TCCFvqgXTCdDpREPkfO5su7ymn5KEA0WuzSSDjvA8Es,19
36
+ grasp_sdk-0.2.0a1.dist-info/RECORD,,
@@ -1,69 +0,0 @@
1
- import { spawn } from 'child_process';
2
-
3
- // const asblockPlugin = '/home/user/.config/google-chrome/Default/Extensions/adblock';
4
-
5
- const args = [
6
- '--no-sandbox',
7
- '--disable-setuid-sandbox',
8
- '--disable-dev-shm-usage',
9
- '--disable-gpu',
10
- '--disable-software-rasterizer',
11
- '--user-data-dir=/home/user/.browser-context'
12
- ];
13
-
14
- args.push(
15
- // 避免缓存积累影响性能
16
- '--disable-application-cache',
17
-
18
- // 关闭所有硬件加速特性,防止 GPU 相关崩溃
19
- '--disable-accelerated-2d-canvas',
20
- '--disable-accelerated-video-decode',
21
-
22
- // 禁用后台渲染,减少无关资源消耗
23
- '--disable-background-timer-throttling',
24
- '--disable-backgrounding-occluded-windows',
25
- '--disable-renderer-backgrounding',
26
-
27
- // 避免过度日志影响性能
28
- '--disable-logging',
29
-
30
- // 禁用不必要的多媒体解码
31
- '--mute-audio',
32
-
33
- // 避免崩溃时弹窗
34
- '--no-default-browser-check',
35
- '--no-first-run',
36
- '--headless=new',
37
- );
38
-
39
- args.push(
40
- `--remote-debugging-port=9222`,
41
- '--remote-debugging-address=0.0.0.0',
42
- 'about:blank',
43
- );
44
-
45
- const chromePath = '/usr/bin/google-chrome';
46
-
47
- // 启动 Chrome 并启用远程调试
48
- const chrome = spawn(chromePath, args, {
49
- env: { ...process.env, DISPLAY: ':99' }
50
- });
51
-
52
- chrome.stdout.on('data', (data) => {
53
- console.log(`stdout: ${data}`);
54
- });
55
-
56
- chrome.stderr.on('data', (data) => {
57
- console.error(`stderr: ${data}`);
58
- });
59
-
60
- chrome.on('close', (code) => {
61
- console.log(`Chrome process exited with code ${code}`);
62
- });
63
-
64
- console.log('Browser launched and ready...');
65
-
66
- // 保持进程不退出,并监听中止信号
67
- process.stdin.resume();
68
- process.on('SIGINT', () => process.exit());
69
- process.on('SIGTERM', () => process.exit());
@@ -1,424 +0,0 @@
1
- import httpProxy from 'http-proxy';
2
- import http from 'http';
3
- import { spawn } from 'child_process';
4
-
5
- import { Logtail } from '@logtail/node';
6
- import * as Sentry from "@sentry/node";
7
-
8
- const logtail = new Logtail(process.env.BS_SOURCE_TOKEN, {
9
- endpoint: `https://${process.env.BS_INGESTING_HOST}`,
10
- });
11
-
12
- Sentry.init({
13
- dsn: process.env.SENTRY_DSN,
14
-
15
- // Setting this option to true will send default PII data to Sentry.
16
- // For example, automatic IP address collection on events
17
- sendDefaultPii: true,
18
- _experiments: {
19
- enableLogs: true, // 启用日志功能
20
- },
21
- });
22
-
23
- const logger = {
24
- info: async (message, context) => {
25
- Sentry.logger.info(message, context);
26
- return logtail.info(message, context);
27
- },
28
- warn: async (message, context) => {
29
- Sentry.logger.warn(message, context);
30
- return logtail.warn(message, context);
31
- },
32
- error: async (message, context) => {
33
- Sentry.logger.error(message, context);
34
- return logtail.error(message, context);
35
- },
36
- }
37
-
38
- function parseWebSocketFrame(buffer) {
39
- if (buffer.length < 2) {
40
- throw new Error('Incomplete WebSocket frame.');
41
- }
42
-
43
- const firstByte = buffer.readUInt8(0);
44
- const fin = (firstByte & 0x80) !== 0;
45
- const opcode = firstByte & 0x0f;
46
-
47
- // 仅处理文本帧(opcode 为 0x1)
48
- if (opcode !== 0x1) {
49
- throw new Error(`Unsupported opcode: ${opcode}`);
50
- }
51
-
52
- const secondByte = buffer.readUInt8(1);
53
- const isMasked = (secondByte & 0x80) !== 0;
54
- let payloadLength = secondByte & 0x7f;
55
- let offset = 2;
56
-
57
- if (payloadLength === 126) {
58
- if (buffer.length < offset + 2) {
59
- throw new Error('Incomplete extended payload length.');
60
- }
61
- payloadLength = buffer.readUInt16BE(offset);
62
- offset += 2;
63
- } else if (payloadLength === 127) {
64
- if (buffer.length < offset + 8) {
65
- throw new Error('Incomplete extended payload length.');
66
- }
67
- // 注意:JavaScript 无法精确表示超过 2^53 的整数
68
- const highBits = buffer.readUInt32BE(offset);
69
- const lowBits = buffer.readUInt32BE(offset + 4);
70
- payloadLength = highBits * 2 ** 32 + lowBits;
71
- offset += 8;
72
- }
73
-
74
- let maskingKey;
75
- if (isMasked) {
76
- if (buffer.length < offset + 4) {
77
- throw new Error('Incomplete masking key.');
78
- }
79
- maskingKey = buffer.slice(offset, offset + 4);
80
- offset += 4;
81
- }
82
-
83
- if (buffer.length < offset + payloadLength) {
84
- throw new Error('Incomplete payload data.');
85
- }
86
-
87
- const payloadData = buffer.slice(offset, offset + payloadLength);
88
-
89
- if (isMasked) {
90
- for (let i = 0; i < payloadLength; i++) {
91
- payloadData[i] ^= maskingKey[i % 4];
92
- }
93
- }
94
-
95
- return payloadData.toString('utf8');
96
- }
97
-
98
- const sandboxId = process.env.SANDBOX_ID;
99
- const cdpPort = Number(process.env.CDP_PORT);
100
- const headless = process.env.HEADLESS !== 'false';
101
- const enableAdblock = process.env.ADBLOCK !== 'false';
102
- const timeoutMS = process.env.SANBOX_TIMEOUT;
103
- const workspace = process.env.WORKSPACE;
104
- const keepAliveMS = Number(process.env.KEEP_ALIVE_MS) || 0;
105
- const args = [];
106
-
107
- try {
108
- const asblockPlugin = '/home/user/.config/google-chrome/Default/Extensions/adblock';
109
-
110
- args.push(
111
- '--no-sandbox',
112
- '--disable-setuid-sandbox',
113
- '--disable-dev-shm-usage',
114
- '--disable-gpu',
115
- '--disable-software-rasterizer',
116
- '--user-data-dir=/home/user/.browser-context'
117
- );
118
-
119
- args.push(
120
- // 避免缓存积累影响性能
121
- '--disable-application-cache',
122
-
123
- // 关闭所有硬件加速特性,防止 GPU 相关崩溃
124
- '--disable-accelerated-2d-canvas',
125
- '--disable-accelerated-video-decode',
126
-
127
- // 禁用后台渲染,减少无关资源消耗
128
- '--disable-background-timer-throttling',
129
- '--disable-backgrounding-occluded-windows',
130
- '--disable-renderer-backgrounding',
131
-
132
- // 避免过度日志影响性能
133
- '--disable-logging',
134
-
135
- // 禁用不必要的多媒体解码
136
- '--mute-audio',
137
-
138
- // 避免崩溃时弹窗
139
- '--no-default-browser-check',
140
- '--no-first-run',
141
- );
142
-
143
- if(headless) {
144
- args.push('--headless=new');
145
- }
146
-
147
- if(enableAdblock) {
148
- args.push(...[
149
- `--disable-extensions-except=${asblockPlugin}`,
150
- `--load-extension=${asblockPlugin}`,
151
- ])
152
- }
153
-
154
- args.push(...[
155
- `--remote-debugging-port=${cdpPort}`,
156
- '--remote-debugging-address=0.0.0.0',
157
- ...JSON.parse(process.env.BROWSER_ARGS),
158
- 'about:blank',
159
- ]);
160
-
161
- console.log(args);
162
-
163
- let chromePath = '/usr/bin/google-chrome';
164
-
165
- // 启动 Chrome 并启用远程调试
166
- const chrome = spawn(chromePath, args, {
167
- env: { ...process.env, DISPLAY: ':99' }
168
- });
169
-
170
- chrome.stdout.on('data', (data) => {
171
- console.log(`stdout: ${data}`);
172
- });
173
-
174
- chrome.stderr.on('data', (data) => {
175
- console.error(`stderr: ${data}`);
176
- });
177
-
178
- chrome.on('close', (code) => {
179
- console.log(`Chrome process exited with code ${code}`);
180
- });
181
-
182
- // Keep the process alive
183
- process.on('SIGTERM', async () => {
184
- console.log('Received SIGTERM, closing browser...');
185
- await logger.warn('Received SIGTERM signal', { sandboxId });
186
- Sentry.addBreadcrumb({
187
- category: 'process',
188
- message: 'Received SIGTERM signal',
189
- level: 'info',
190
- data: { sandboxId }
191
- });
192
- chrome.kill();
193
- process.exit(0);
194
- });
195
-
196
- process.on('SIGINT', async () => {
197
- console.log('Received SIGINT, closing browser...');
198
- await logger.warn('Received SIGINT signal', { sandboxId });
199
- Sentry.addBreadcrumb({
200
- category: 'process',
201
- message: 'Received SIGINT signal',
202
- level: 'info',
203
- data: { sandboxId }
204
- });
205
- chrome.kill();
206
- process.exit(0);
207
- });
208
-
209
- // 创建代理服务:从 ${this.config.cdpPort! + 1} 转发到 127.0.0.1:${this.config.cdpPort!}
210
- const proxy = httpProxy.createProxyServer({
211
- target: `http://127.0.0.1:${cdpPort}`,
212
- ws: true, // Enable WebSocket support
213
- changeOrigin: true
214
- });
215
-
216
- const clients = new Set();
217
-
218
- // 监听 WebSocket 事件
219
- proxy.on('open', () => {
220
- console.log('🔌 CDP WebSocket connection established');
221
- const wsId = Date.now();
222
- logger.info('CDP WebSocket connection established', { sandboxId, wsId });
223
- Sentry.addBreadcrumb({
224
- category: 'websocket',
225
- message: 'CDP connection established',
226
- level: 'info',
227
- data: { sandboxId }
228
- });
229
-
230
- clients.add(wsId);
231
- proxy.once('close', async (req, socket, head) => {
232
- console.log('🔒 CDP WebSocket connection closed');
233
- await logger.info('CDP WebSocket connection closed', { sandboxId });
234
- Sentry.addBreadcrumb({
235
- category: 'websocket',
236
- message: 'CDP connection closed',
237
- level: 'info',
238
- data: { sandboxId }
239
- });
240
- clients.delete(wsId);
241
-
242
- if(clients.size <= 0) {
243
- setTimeout(() => {
244
- if(clients.size <= 0) {
245
- console.log('❌ Force closed...', wsId);
246
- process.exit(0);
247
- }
248
- }, keepAliveMS);
249
- }
250
- });
251
- });
252
-
253
- proxy.on('proxyReqWs', (proxyReq, req, socket, options, head) => {
254
- console.log('📡 New CDP WebSocket connection request:', req.url);
255
- logger.info('New CDP WebSocket connection request', { url: req.url, sandboxId });
256
- Sentry.addBreadcrumb({
257
- category: 'websocket',
258
- message: 'New CDP connection request',
259
- level: 'info',
260
- data: { url: req.url, sandboxId }
261
- });
262
- });
263
-
264
- proxy.on('error', (err, req, res) => {
265
- console.error('❌ CDP WebSocket proxy error:', err);
266
- logger.error('CDP WebSocket proxy error', { error: err.message, url: req?.url, sandboxId });
267
- Sentry.captureException(err, {
268
- tags: { type: 'websocket_proxy_error', sandboxId },
269
- extra: { url: req?.url }
270
- });
271
- });
272
-
273
- const server = http.createServer(async (req, res) => {
274
- if (req.url === '/health') {
275
- res.end('ok');
276
- return;
277
- }
278
- if (req.url === '/json/version' || req.url === '/json/version/') {
279
- try {
280
- // 向本地 CDP 发请求,获取原始 JSON
281
- const jsonRes = await fetch(`http://127.0.0.1:${cdpPort}/json/version`);
282
- const data = await jsonRes.json();
283
- // 替换掉本地的 WebSocket 地址为代理暴露地址
284
- data.webSocketDebuggerUrl = data.webSocketDebuggerUrl.replace(
285
- `ws://127.0.0.1:${cdpPort}`,
286
- `wss://${req.headers.host}`
287
- );
288
- await logger.info('CDP version info requested', { url: req.url, response: data, sandboxId });
289
- Sentry.addBreadcrumb({
290
- category: 'http',
291
- message: 'CDP version info requested',
292
- level: 'info',
293
- data: { url: req.url, response: data, sandboxId }
294
- });
295
- res.writeHead(200, { 'Content-Type': 'application/json' });
296
- res.end(JSON.stringify(data));
297
- } catch(ex) {
298
- console.error('Failed to fetch CDP version:', ex.message);
299
- await logger.error('Failed to fetch CDP version', { error: ex.message, sandboxId });
300
- Sentry.captureException(ex, {
301
- tags: { type: 'cdp_version_error', sandboxId }
302
- });
303
- res.writeHead(500);
304
- res.end('Internal Server Error');
305
- }
306
- } else {
307
- proxy.web(req, res, {}, async (err) => {
308
- console.error('Proxy error:', err);
309
- await logger.error('HTTP proxy error', { error: err.message, url: req.url, sandboxId });
310
- Sentry.captureException(err, {
311
- tags: { type: 'proxy_error', sandboxId },
312
- extra: { url: req.url }
313
- });
314
- res.writeHead(502);
315
- res.end('Bad gateway');
316
- });
317
- }
318
- });
319
-
320
- server.on('upgrade', (req, socket, head) => {
321
- // 监听 WebSocket 数据
322
- let _buffers = [];
323
- socket.on('data', (data) => {
324
- let message = '';
325
- try {
326
- _buffers.push(data);
327
- // console.log(`💬 ${_buffers.length}`);
328
- message = parseWebSocketFrame(Buffer.concat(_buffers)); // 复制data不能破坏原始数据
329
- _buffers.length = 0;
330
- if (message.startsWith('{')){ // 只解析 JSON 消息
331
- const parsed = JSON.parse(message);
332
- console.log('📨 CDP WebSocket message:', parsed);
333
- logger.info('CDP WebSocket message received', {
334
- data: parsed,
335
- sandboxId: process.env.SANDBOX_ID,
336
- });
337
- Sentry.addBreadcrumb({
338
- category: 'websocket',
339
- message: 'CDP message received',
340
- level: 'debug',
341
- data: { ...parsed, sandboxId }
342
- });
343
- }
344
- } catch (err) {
345
- const msg = err.message;
346
- if(!msg.includes('Incomplete')) {
347
- // 记录解析错误
348
- console.warn('⚠️ Failed to parse CDP WebSocket message:', err.message, _buffers.length);
349
- _buffers.length = 0;
350
- Sentry.captureException(err, {
351
- tags: { type: 'websocket_error', sandboxId }
352
- });
353
- logger.warn('Failed to parse CDP WebSocket message', {
354
- error: err.message,
355
- data: message,
356
- sandboxId: process.env.SANDBOX_ID
357
- });
358
- }
359
- }
360
- });
361
-
362
- socket.on('error', (err) => {
363
- console.error('❌ CDP WebSocket error:', err);
364
- logger.error('CDP WebSocket error', { error: err.message, sandboxId });
365
- Sentry.captureException(err, {
366
- tags: { type: 'websocket_error', sandboxId }
367
- });
368
- });
369
-
370
- proxy.ws(req, socket, head);
371
- });
372
-
373
- server.listen(cdpPort + 1, '0.0.0.0', () => {
374
- console.log(`🎯 Proxy server listening on http://0.0.0.0:${cdpPort + 1} → http://127.0.0.1:${cdpPort}`);
375
- logger.info('Proxy server started', {
376
- port: cdpPort + 1,
377
- target: cdpPort,
378
- sandboxId,
379
- settings: {
380
- type: 'chromium',
381
- args,
382
- headless,
383
- enableAdblock,
384
- timeoutMS,
385
- workspace,
386
- sandboxId
387
- },
388
- });
389
- Sentry.addBreadcrumb({
390
- category: 'server',
391
- message: 'Proxy server started',
392
- level: 'info',
393
- data: {
394
- port: cdpPort + 1,
395
- target: cdpPort,
396
- sandboxId,
397
- settings: {
398
- type: 'chrome-stable',
399
- args,
400
- headless,
401
- enableAdblock,
402
- timeoutMS,
403
- workspace,
404
- sandboxId
405
- },
406
- }
407
- });
408
- });
409
- } catch(ex) {
410
- console.error('Failed to launch Browser:', ex);
411
- logger.error('Failed to launch Chrome', {
412
- error: ex.message,
413
- args,
414
- headless,
415
- cdpPort,
416
- enableAdblock,
417
- sandboxId
418
- });
419
- Sentry.captureException(ex, {
420
- tags: { type: 'launch_error', sandboxId },
421
- extra: { args, headless, cdpPort, enableAdblock }
422
- });
423
- process.exit(1);
424
- }