@poolzin/pool-bot 2026.3.22 → 2026.3.24
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/CHANGELOG.md +111 -0
- package/dist/.buildstamp +1 -1
- package/dist/acp/bindings-store.js +209 -0
- package/dist/acp/control-plane/runtime-cache.js +54 -0
- package/dist/acp/control-plane/runtime-options.js +215 -0
- package/dist/acp/control-plane/session-actor-queue.js +36 -0
- package/dist/acp/policy.js +52 -0
- package/dist/acp/runtime/errors.js +47 -0
- package/dist/acp/runtime/registry.js +86 -0
- package/dist/acp/runtime/types.js +1 -0
- package/dist/acp/translator.js +97 -0
- package/dist/agents/btw.js +280 -0
- package/dist/agents/failover-error.js +145 -47
- package/dist/agents/fast-mode.js +24 -0
- package/dist/agents/live-model-errors.js +23 -0
- package/dist/agents/model-auth-env-vars.js +44 -0
- package/dist/agents/model-auth-markers.js +69 -0
- package/dist/agents/models-config.providers.discovery.js +180 -0
- package/dist/agents/models-config.providers.static.js +480 -0
- package/dist/auto-reply/reply/typing-policy.js +15 -0
- package/dist/browser/browser-profile-manager.js +319 -0
- package/dist/browser/cdp-proxy-bypass.js +129 -0
- package/dist/browser/cdp-timeouts.js +41 -0
- package/dist/browser/chrome-extension-validator.js +406 -0
- package/dist/browser/chrome-mcp-snapshot.js +222 -0
- package/dist/browser/chrome-mcp.js +421 -0
- package/dist/browser/chrome-mcp.snapshot.js +133 -0
- package/dist/browser/errors.js +67 -0
- package/dist/browser/form-fields.js +22 -0
- package/dist/browser/output-atomic.js +44 -0
- package/dist/browser/profile-capabilities.js +47 -0
- package/dist/browser/safe-filename.js +25 -0
- package/dist/browser/snapshot-roles.js +60 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/account-snapshot-fields.js +176 -0
- package/dist/channels/draft-stream-controls.js +89 -0
- package/dist/channels/inbound-debounce-policy.js +28 -0
- package/dist/channels/typing-lifecycle.js +39 -0
- package/dist/cli/program/command-registry.js +52 -0
- package/dist/commands/agent-binding.js +123 -0
- package/dist/commands/agents.commands.bind.js +280 -0
- package/dist/commands/backup-shared.js +186 -0
- package/dist/commands/backup-verify.js +236 -0
- package/dist/commands/backup.js +166 -0
- package/dist/commands/channel-account-context.js +15 -0
- package/dist/commands/channel-account.js +190 -0
- package/dist/commands/gateway-install-token.js +117 -0
- package/dist/commands/oauth-tls-preflight.js +121 -0
- package/dist/commands/ollama-setup.js +402 -0
- package/dist/commands/security-owner-only.js +86 -0
- package/dist/commands/self-hosted-provider-setup.js +207 -0
- package/dist/commands/session-store-targets.js +12 -0
- package/dist/commands/sessions-cleanup.js +97 -0
- package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/cron/cron-filters.js +150 -0
- package/dist/cron/heartbeat-policy.js +26 -0
- package/dist/gateway/device-pairing-security.js +197 -0
- package/dist/gateway/event-deduplication.js +167 -0
- package/dist/gateway/hooks-mapping.js +46 -7
- package/dist/gateway/run-tracker.js +253 -0
- package/dist/gateway/server-methods/nodes.js +14 -0
- package/dist/gateway/websocket-preauth-security.js +188 -0
- package/dist/hooks/module-loader.js +28 -0
- package/dist/infra/agent-command-binding.js +144 -0
- package/dist/infra/backup.js +328 -0
- package/dist/infra/channel-account-context.js +173 -0
- package/dist/infra/errors.js +53 -13
- package/dist/infra/exec-approvals-security.js +217 -0
- package/dist/infra/security/command-analyzer.js +257 -0
- package/dist/infra/session-cleanup.js +143 -0
- package/dist/plugins/loader.js +16 -8
- package/dist/security/external-content.js +51 -1
- package/dist/sessions/session-costs.js +228 -0
- package/dist/shared/param-key.js +16 -0
- package/dist/shared/poll-params.js +58 -0
- package/dist/shared/polls.js +55 -0
- package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
- package/docs/FEATURES.md +523 -0
- package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
- package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
- package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
- package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
- package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
- package/docs/MIKRODASH-ANALYSIS.md +412 -0
- package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
- package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
- package/docs/PHASE-7-SUMMARY.md +144 -0
- package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
- package/docs/PROJECT-FINAL-STATUS.md +237 -0
- package/docs/README.md +116 -0
- package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
- package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
- package/docs/channels/googlechat.md +235 -206
- package/docs/channels/irc.md +332 -0
- package/docs/channels/nostr.md +255 -168
- package/docs/components/command-palette.md +166 -0
- package/docs/components/login-gate.md +219 -0
- package/docs/getting-started/installation.md +191 -0
- package/docs/getting-started/introduction.md +120 -0
- package/docs/improvements/USAGE-GUIDE.md +359 -0
- package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
- package/docs/reference/deadcode-detection.md +72 -0
- package/extensions/acpx/node_modules/.bin/acpx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +4 -4
- package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
- package/extensions/googlechat/node_modules/.bin/tsc +21 -0
- package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
- package/extensions/googlechat/node_modules/.bin/vitest +21 -0
- package/extensions/googlechat/package.json +11 -28
- package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
- package/extensions/googlechat/src/googlechat-channel.ts +120 -0
- package/extensions/googlechat/src/index.ts +14 -0
- package/extensions/irc/node_modules/.bin/tsc +21 -0
- package/extensions/irc/node_modules/.bin/tsserver +21 -0
- package/extensions/irc/node_modules/.bin/vitest +21 -0
- package/extensions/irc/package.json +16 -8
- package/extensions/irc/src/index.ts +14 -0
- package/extensions/irc/src/irc-channel.test.ts +43 -0
- package/extensions/irc/src/irc-channel.ts +191 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
- package/extensions/keyed-async-queue/package.json +20 -0
- package/extensions/keyed-async-queue/src/index.ts +14 -0
- package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
- package/extensions/keyed-async-queue/src/queue.ts +200 -0
- package/extensions/memory-core/node_modules/.bin/tsc +21 -0
- package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
- package/extensions/memory-core/node_modules/.bin/vitest +21 -0
- package/extensions/memory-core/package.json +11 -8
- package/extensions/memory-core/src/index.ts +14 -0
- package/extensions/memory-core/src/memory-manager.test.ts +124 -0
- package/extensions/memory-core/src/memory-manager.ts +186 -0
- package/extensions/nostr/node_modules/.bin/tsc +2 -2
- package/extensions/nostr/node_modules/.bin/tsserver +2 -2
- package/extensions/nostr/node_modules/.bin/vitest +21 -0
- package/extensions/nostr/package.json +15 -24
- package/extensions/nostr/src/index.ts +14 -0
- package/extensions/nostr/src/nostr-channel.test.ts +55 -0
- package/extensions/nostr/src/nostr-channel.ts +228 -0
- package/extensions/page-agent/node_modules/.bin/vitest +2 -2
- package/extensions/test-utils/node_modules/.bin/jiti +21 -0
- package/extensions/test-utils/node_modules/.bin/playwright +21 -0
- package/extensions/test-utils/node_modules/.bin/tsx +21 -0
- package/extensions/test-utils/node_modules/.bin/vite +21 -0
- package/extensions/test-utils/node_modules/.bin/vitest +21 -0
- package/extensions/test-utils/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +2 -2
- package/package.json +2 -1
- package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
- package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
- package/extensions/memory-core/node_modules/.bin/poolbot +0 -21
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Profile Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple Chrome profiles with isolated user data directories.
|
|
5
|
+
* Enables parallel browser sessions with separate cookies, storage, and preferences.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import playwright from "playwright-core";
|
|
11
|
+
export class BrowserProfileManager {
|
|
12
|
+
profiles = new Map();
|
|
13
|
+
activeSessions = new Map();
|
|
14
|
+
baseDir;
|
|
15
|
+
IDLE_TIMEOUT_MS;
|
|
16
|
+
MAX_SESSIONS;
|
|
17
|
+
cleanupInterval = null;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.baseDir = options?.baseDir ?? path.join(os.tmpdir(), "poolbot-browser-profiles");
|
|
20
|
+
this.IDLE_TIMEOUT_MS = options?.idleTimeoutMs ?? 300000; // 5 minutes
|
|
21
|
+
this.MAX_SESSIONS = options?.maxSessions ?? 10;
|
|
22
|
+
if (options?.autoCleanup !== false) {
|
|
23
|
+
this.startCleanupInterval();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Initialize manager - create base directory
|
|
28
|
+
*/
|
|
29
|
+
async initialize() {
|
|
30
|
+
await fs.mkdir(this.baseDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a new browser profile
|
|
34
|
+
*/
|
|
35
|
+
async createProfile(config) {
|
|
36
|
+
if (this.profiles.has(config.name)) {
|
|
37
|
+
throw new Error(`Profile ${config.name} already exists`);
|
|
38
|
+
}
|
|
39
|
+
const userDataDir = config.userDataDir ?? path.join(this.baseDir, config.name);
|
|
40
|
+
await fs.mkdir(userDataDir, { recursive: true });
|
|
41
|
+
const profile = {
|
|
42
|
+
...config,
|
|
43
|
+
userDataDir,
|
|
44
|
+
preferences: {
|
|
45
|
+
// Default preferences
|
|
46
|
+
"download.default_directory": path.join(userDataDir, "Downloads"),
|
|
47
|
+
"plugins.always_open_pdf_externally": true,
|
|
48
|
+
credentials_enable_service: false,
|
|
49
|
+
"profile.password_manager_enabled": false,
|
|
50
|
+
"safebrowsing.enabled": true,
|
|
51
|
+
...config.preferences,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
this.profiles.set(config.name, profile);
|
|
55
|
+
await this.writePreferences(profile);
|
|
56
|
+
return profile;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get profile by name
|
|
60
|
+
*/
|
|
61
|
+
getProfile(name) {
|
|
62
|
+
return this.profiles.get(name);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Launch browser with specified profile
|
|
66
|
+
*/
|
|
67
|
+
async launchProfile(profileName) {
|
|
68
|
+
const profile = this.profiles.get(profileName);
|
|
69
|
+
if (!profile) {
|
|
70
|
+
throw new Error(`Profile ${profileName} not found`);
|
|
71
|
+
}
|
|
72
|
+
// Check if session already exists
|
|
73
|
+
const existing = this.activeSessions.get(profileName);
|
|
74
|
+
if (existing) {
|
|
75
|
+
existing.lastActivity = Date.now();
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
// Evict oldest session if at capacity
|
|
79
|
+
if (this.activeSessions.size >= this.MAX_SESSIONS) {
|
|
80
|
+
await this.evictOldestSession();
|
|
81
|
+
}
|
|
82
|
+
// Launch browser
|
|
83
|
+
const context = await playwright.chromium.launchPersistentContext(profile.userDataDir, {
|
|
84
|
+
headless: profile.headless ?? false,
|
|
85
|
+
viewport: profile.viewport ?? { width: 1280, height: 720 },
|
|
86
|
+
args: [
|
|
87
|
+
"--disable-web-security",
|
|
88
|
+
"--disable-features=IsolateOrigins,site-per-process",
|
|
89
|
+
"--disable-site-isolation-trials",
|
|
90
|
+
"--disable-blink-features=AutomationControlled",
|
|
91
|
+
"--no-first-run",
|
|
92
|
+
"--no-default-browser-check",
|
|
93
|
+
"--disable-dev-shm-usage",
|
|
94
|
+
"--disable-accelerated-2d-canvas",
|
|
95
|
+
"--disable-gpu",
|
|
96
|
+
],
|
|
97
|
+
proxy: profile.proxy
|
|
98
|
+
? {
|
|
99
|
+
server: `${profile.proxy.host}:${profile.proxy.port}`,
|
|
100
|
+
username: profile.proxy.username,
|
|
101
|
+
password: profile.proxy.password,
|
|
102
|
+
}
|
|
103
|
+
: undefined,
|
|
104
|
+
...profile.preferences,
|
|
105
|
+
});
|
|
106
|
+
const session = {
|
|
107
|
+
profile,
|
|
108
|
+
context,
|
|
109
|
+
browser: context.pages()[0]?.context().browser(),
|
|
110
|
+
startTime: Date.now(),
|
|
111
|
+
lastActivity: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
this.activeSessions.set(profileName, session);
|
|
114
|
+
return session;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Close a profile session
|
|
118
|
+
*/
|
|
119
|
+
async closeProfile(profileName) {
|
|
120
|
+
const session = this.activeSessions.get(profileName);
|
|
121
|
+
if (session) {
|
|
122
|
+
await session.context.close();
|
|
123
|
+
this.activeSessions.delete(profileName);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get active session
|
|
128
|
+
*/
|
|
129
|
+
getSession(profileName) {
|
|
130
|
+
return this.activeSessions.get(profileName);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get all active sessions
|
|
134
|
+
*/
|
|
135
|
+
getActiveSessions() {
|
|
136
|
+
return Array.from(this.activeSessions.values());
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Update session activity timestamp
|
|
140
|
+
*/
|
|
141
|
+
touchSession(profileName) {
|
|
142
|
+
const session = this.activeSessions.get(profileName);
|
|
143
|
+
if (session) {
|
|
144
|
+
session.lastActivity = Date.now();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get statistics
|
|
149
|
+
*/
|
|
150
|
+
getStats() {
|
|
151
|
+
const sessions = Array.from(this.activeSessions.values());
|
|
152
|
+
const oldest = sessions.length > 0 ? Math.min(...sessions.map((s) => s.startTime)) : null;
|
|
153
|
+
return {
|
|
154
|
+
totalProfiles: this.profiles.size,
|
|
155
|
+
activeSessions: sessions.length,
|
|
156
|
+
oldestSession: oldest,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cleanup idle sessions
|
|
161
|
+
*/
|
|
162
|
+
async cleanupIdleSessions() {
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
let removed = 0;
|
|
165
|
+
for (const [name, session] of this.activeSessions.entries()) {
|
|
166
|
+
if (now - session.lastActivity > this.IDLE_TIMEOUT_MS) {
|
|
167
|
+
await this.closeProfile(name);
|
|
168
|
+
removed++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return removed;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Destroy manager and close all sessions
|
|
175
|
+
*/
|
|
176
|
+
async destroy() {
|
|
177
|
+
if (this.cleanupInterval) {
|
|
178
|
+
clearInterval(this.cleanupInterval);
|
|
179
|
+
this.cleanupInterval = null;
|
|
180
|
+
}
|
|
181
|
+
const closePromises = Array.from(this.activeSessions.keys()).map((name) => this.closeProfile(name).catch(console.error));
|
|
182
|
+
await Promise.all(closePromises);
|
|
183
|
+
this.profiles.clear();
|
|
184
|
+
}
|
|
185
|
+
async writePreferences(profile) {
|
|
186
|
+
if (!profile.userDataDir)
|
|
187
|
+
return;
|
|
188
|
+
const preferencesPath = path.join(profile.userDataDir, "Default", "Preferences");
|
|
189
|
+
await fs.mkdir(path.dirname(preferencesPath), { recursive: true });
|
|
190
|
+
const preferences = {
|
|
191
|
+
profile: {
|
|
192
|
+
default_content_setting_values: {
|
|
193
|
+
notifications: 2, // Block notifications
|
|
194
|
+
geolocation: 2, // Block geolocation
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
...profile.preferences,
|
|
198
|
+
};
|
|
199
|
+
await fs.writeFile(preferencesPath, JSON.stringify(preferences, null, 2));
|
|
200
|
+
}
|
|
201
|
+
async evictOldestSession() {
|
|
202
|
+
let oldestName = null;
|
|
203
|
+
let oldestTime = Infinity;
|
|
204
|
+
for (const [name, session] of this.activeSessions.entries()) {
|
|
205
|
+
if (session.startTime < oldestTime) {
|
|
206
|
+
oldestTime = session.startTime;
|
|
207
|
+
oldestName = name;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (oldestName) {
|
|
211
|
+
await this.closeProfile(oldestName);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
startCleanupInterval() {
|
|
215
|
+
const cleanupIntervalMs = Math.max(30000, this.IDLE_TIMEOUT_MS / 10);
|
|
216
|
+
this.cleanupInterval = setInterval(() => {
|
|
217
|
+
this.cleanupIdleSessions().catch(console.error);
|
|
218
|
+
}, cleanupIntervalMs);
|
|
219
|
+
this.cleanupInterval.unref();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Navigation Guards for safe browsing
|
|
224
|
+
*/
|
|
225
|
+
export class GuardedNavigator {
|
|
226
|
+
blockedUrls;
|
|
227
|
+
allowedDomains;
|
|
228
|
+
maxRedirects;
|
|
229
|
+
timeout;
|
|
230
|
+
constructor(options) {
|
|
231
|
+
this.blockedUrls = options?.blockedUrls ?? [
|
|
232
|
+
/file:\/\/\//,
|
|
233
|
+
/chrome:\/\//,
|
|
234
|
+
/chrome-extension:\/\//,
|
|
235
|
+
/about:/,
|
|
236
|
+
];
|
|
237
|
+
this.allowedDomains = new Set(options?.allowedDomains ?? []);
|
|
238
|
+
this.maxRedirects = options?.maxRedirects ?? 5;
|
|
239
|
+
this.timeout = options?.timeout ?? 30000;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Navigate with security guards
|
|
243
|
+
*/
|
|
244
|
+
async navigate(context, url) {
|
|
245
|
+
try {
|
|
246
|
+
// Validate URL
|
|
247
|
+
const parsed = new URL(url);
|
|
248
|
+
if (!this.isAllowedDomain(parsed.hostname)) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
finalUrl: url,
|
|
252
|
+
status: 0,
|
|
253
|
+
redirects: 0,
|
|
254
|
+
error: `Domain ${parsed.hostname} not in allowlist`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (this.isBlockedUrl(url)) {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
finalUrl: url,
|
|
261
|
+
status: 0,
|
|
262
|
+
redirects: 0,
|
|
263
|
+
error: `URL matches blocked pattern`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// Navigate with timeout
|
|
267
|
+
const page = await context.newPage();
|
|
268
|
+
let redirects = 0;
|
|
269
|
+
let currentUrl = url;
|
|
270
|
+
const response = await page.goto(url, {
|
|
271
|
+
waitUntil: "networkidle",
|
|
272
|
+
timeout: this.timeout,
|
|
273
|
+
});
|
|
274
|
+
// Track redirects
|
|
275
|
+
while (currentUrl !== url && redirects < this.maxRedirects) {
|
|
276
|
+
redirects++;
|
|
277
|
+
currentUrl = page.url();
|
|
278
|
+
await page.waitForTimeout(100);
|
|
279
|
+
}
|
|
280
|
+
if (redirects >= this.maxRedirects) {
|
|
281
|
+
await page.close();
|
|
282
|
+
return {
|
|
283
|
+
success: false,
|
|
284
|
+
finalUrl: page.url(),
|
|
285
|
+
status: 0,
|
|
286
|
+
redirects,
|
|
287
|
+
error: `Too many redirects (${redirects})`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const status = response?.status() ?? 0;
|
|
291
|
+
const finalUrl = page.url();
|
|
292
|
+
// Don't close page - let caller manage it
|
|
293
|
+
return {
|
|
294
|
+
success: status >= 200 && status < 400,
|
|
295
|
+
finalUrl,
|
|
296
|
+
status,
|
|
297
|
+
redirects,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
finalUrl: url,
|
|
304
|
+
status: 0,
|
|
305
|
+
redirects: 0,
|
|
306
|
+
error: error.message,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
isAllowedDomain(hostname) {
|
|
311
|
+
if (this.allowedDomains.size === 0) {
|
|
312
|
+
return true; // No restrictions
|
|
313
|
+
}
|
|
314
|
+
return this.allowedDomains.has(hostname);
|
|
315
|
+
}
|
|
316
|
+
isBlockedUrl(url) {
|
|
317
|
+
return this.blockedUrls.some((re) => re.test(url));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy bypass for CDP (Chrome DevTools Protocol) localhost connections.
|
|
3
|
+
*
|
|
4
|
+
* When HTTP_PROXY / HTTPS_PROXY / ALL_PROXY environment variables are set,
|
|
5
|
+
* CDP connections to localhost/127.0.0.1 can be incorrectly routed through
|
|
6
|
+
* the proxy, causing browser control to fail.
|
|
7
|
+
*/
|
|
8
|
+
import http from "node:http";
|
|
9
|
+
import https from "node:https";
|
|
10
|
+
import { isLoopbackHost } from "../gateway/net.js";
|
|
11
|
+
import { hasProxyEnvConfigured } from "../infra/net/proxy-env.js";
|
|
12
|
+
/** HTTP agent that never uses a proxy — for localhost CDP connections. */
|
|
13
|
+
const directHttpAgent = new http.Agent();
|
|
14
|
+
const directHttpsAgent = new https.Agent();
|
|
15
|
+
/**
|
|
16
|
+
* Returns a plain (non-proxy) agent for WebSocket or HTTP connections
|
|
17
|
+
* when the target is a loopback address. Returns `undefined` otherwise
|
|
18
|
+
* so callers fall through to their default behaviour.
|
|
19
|
+
*/
|
|
20
|
+
export function getDirectAgentForCdp(url) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = new URL(url);
|
|
23
|
+
if (isLoopbackHost(parsed.hostname)) {
|
|
24
|
+
return parsed.protocol === "https:" || parsed.protocol === "wss:"
|
|
25
|
+
? directHttpsAgent
|
|
26
|
+
: directHttpAgent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// not a valid URL — let caller handle it
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns `true` when any proxy-related env var is set that could
|
|
36
|
+
* interfere with loopback connections.
|
|
37
|
+
*/
|
|
38
|
+
export function hasProxyEnv() {
|
|
39
|
+
return hasProxyEnvConfigured();
|
|
40
|
+
}
|
|
41
|
+
const LOOPBACK_ENTRIES = "localhost,127.0.0.1,[::1]";
|
|
42
|
+
function noProxyAlreadyCoversLocalhost() {
|
|
43
|
+
const current = process.env.NO_PROXY || process.env.no_proxy || "";
|
|
44
|
+
return (current.includes("localhost") && current.includes("127.0.0.1") && current.includes("[::1]"));
|
|
45
|
+
}
|
|
46
|
+
export async function withNoProxyForLocalhost(fn) {
|
|
47
|
+
return await withNoProxyForCdpUrl("http://127.0.0.1", fn);
|
|
48
|
+
}
|
|
49
|
+
function isLoopbackCdpUrl(url) {
|
|
50
|
+
try {
|
|
51
|
+
return isLoopbackHost(new URL(url).hostname);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
class NoProxyLeaseManager {
|
|
58
|
+
leaseCount = 0;
|
|
59
|
+
snapshot = null;
|
|
60
|
+
acquire(url) {
|
|
61
|
+
if (!isLoopbackCdpUrl(url) || !hasProxyEnv()) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (this.leaseCount === 0 && !noProxyAlreadyCoversLocalhost()) {
|
|
65
|
+
const noProxy = process.env.NO_PROXY;
|
|
66
|
+
const noProxyLower = process.env.no_proxy;
|
|
67
|
+
const current = noProxy || noProxyLower || "";
|
|
68
|
+
const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
|
|
69
|
+
process.env.NO_PROXY = applied;
|
|
70
|
+
process.env.no_proxy = applied;
|
|
71
|
+
this.snapshot = { noProxy, noProxyLower, applied };
|
|
72
|
+
}
|
|
73
|
+
this.leaseCount += 1;
|
|
74
|
+
let released = false;
|
|
75
|
+
return () => {
|
|
76
|
+
if (released) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
released = true;
|
|
80
|
+
this.release();
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
release() {
|
|
84
|
+
if (this.leaseCount <= 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.leaseCount -= 1;
|
|
88
|
+
if (this.leaseCount > 0 || !this.snapshot) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const { noProxy, noProxyLower, applied } = this.snapshot;
|
|
92
|
+
const currentNoProxy = process.env.NO_PROXY;
|
|
93
|
+
const currentNoProxyLower = process.env.no_proxy;
|
|
94
|
+
const untouched = currentNoProxy === applied &&
|
|
95
|
+
(currentNoProxyLower === applied || currentNoProxyLower === undefined);
|
|
96
|
+
if (untouched) {
|
|
97
|
+
if (noProxy !== undefined) {
|
|
98
|
+
process.env.NO_PROXY = noProxy;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
delete process.env.NO_PROXY;
|
|
102
|
+
}
|
|
103
|
+
if (noProxyLower !== undefined) {
|
|
104
|
+
process.env.no_proxy = noProxyLower;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
delete process.env.no_proxy;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.snapshot = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const noProxyLeaseManager = new NoProxyLeaseManager();
|
|
114
|
+
/**
|
|
115
|
+
* Scoped NO_PROXY bypass for loopback CDP URLs.
|
|
116
|
+
*
|
|
117
|
+
* This wrapper only mutates env vars for loopback destinations. On restore,
|
|
118
|
+
* it avoids clobbering external NO_PROXY changes that happened while calls
|
|
119
|
+
* were in-flight.
|
|
120
|
+
*/
|
|
121
|
+
export async function withNoProxyForCdpUrl(url, fn) {
|
|
122
|
+
const release = noProxyLeaseManager.acquire(url);
|
|
123
|
+
try {
|
|
124
|
+
return await fn();
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
release?.();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const CDP_HTTP_REQUEST_TIMEOUT_MS = 1500;
|
|
2
|
+
export const CDP_WS_HANDSHAKE_TIMEOUT_MS = 5000;
|
|
3
|
+
export const CDP_JSON_NEW_TIMEOUT_MS = 1500;
|
|
4
|
+
export const CHROME_REACHABILITY_TIMEOUT_MS = 500;
|
|
5
|
+
export const CHROME_WS_READY_TIMEOUT_MS = 800;
|
|
6
|
+
export const CHROME_BOOTSTRAP_PREFS_TIMEOUT_MS = 10_000;
|
|
7
|
+
export const CHROME_BOOTSTRAP_EXIT_TIMEOUT_MS = 5000;
|
|
8
|
+
export const CHROME_LAUNCH_READY_WINDOW_MS = 15_000;
|
|
9
|
+
export const CHROME_LAUNCH_READY_POLL_MS = 200;
|
|
10
|
+
export const CHROME_STOP_TIMEOUT_MS = 2500;
|
|
11
|
+
export const CHROME_STOP_PROBE_TIMEOUT_MS = 200;
|
|
12
|
+
export const CHROME_STDERR_HINT_MAX_CHARS = 2000;
|
|
13
|
+
export const PROFILE_HTTP_REACHABILITY_TIMEOUT_MS = 300;
|
|
14
|
+
export const PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS = 200;
|
|
15
|
+
export const PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS = 2000;
|
|
16
|
+
export const PROFILE_ATTACH_RETRY_TIMEOUT_MS = 1200;
|
|
17
|
+
export const PROFILE_POST_RESTART_WS_TIMEOUT_MS = 600;
|
|
18
|
+
function normalizeTimeoutMs(value) {
|
|
19
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return Math.max(1, Math.floor(value));
|
|
23
|
+
}
|
|
24
|
+
export function resolveCdpReachabilityTimeouts(params) {
|
|
25
|
+
const normalized = normalizeTimeoutMs(params.timeoutMs);
|
|
26
|
+
if (params.profileIsLoopback) {
|
|
27
|
+
const httpTimeoutMs = normalized ?? PROFILE_HTTP_REACHABILITY_TIMEOUT_MS;
|
|
28
|
+
const wsTimeoutMs = Math.max(PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS, Math.min(PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS, httpTimeoutMs * 2));
|
|
29
|
+
return { httpTimeoutMs, wsTimeoutMs };
|
|
30
|
+
}
|
|
31
|
+
if (normalized !== undefined) {
|
|
32
|
+
return {
|
|
33
|
+
httpTimeoutMs: Math.max(normalized, params.remoteHttpTimeoutMs),
|
|
34
|
+
wsTimeoutMs: Math.max(normalized * 2, params.remoteHandshakeTimeoutMs),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
httpTimeoutMs: params.remoteHttpTimeoutMs,
|
|
39
|
+
wsTimeoutMs: params.remoteHandshakeTimeoutMs,
|
|
40
|
+
};
|
|
41
|
+
}
|