s3db.js 13.6.1 → 14.0.2
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 +56 -15
- package/dist/s3db.cjs +72446 -39022
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72172 -38790
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +85 -50
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/index.js +168 -40
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -0,0 +1,1327 @@
|
|
|
1
|
+
import { Plugin } from './plugin.class.js';
|
|
2
|
+
import { requirePluginDependency } from './concerns/plugin-dependencies.js';
|
|
3
|
+
import { resolveResourceNames } from './concerns/resource-names.js';
|
|
4
|
+
import { getValidatedNamespace } from './namespace.js';
|
|
5
|
+
import tryFn from '../concerns/try-fn.js';
|
|
6
|
+
import { PluginError } from '../errors.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PuppeteerPlugin - Headless browser automation with anti-bot detection
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Browser pool management with tab recycling
|
|
13
|
+
* - Cookie farming and session management
|
|
14
|
+
* - Human behavior simulation (ghost-cursor)
|
|
15
|
+
* - Anti-detection (puppeteer-extra-plugin-stealth)
|
|
16
|
+
* - Random user agent generation
|
|
17
|
+
* - Performance optimization (resource blocking)
|
|
18
|
+
* - Proxy support
|
|
19
|
+
*
|
|
20
|
+
* @extends Plugin
|
|
21
|
+
*/
|
|
22
|
+
export class PuppeteerPlugin extends Plugin {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
super(options);
|
|
25
|
+
|
|
26
|
+
// Validate and set namespace (standardized)
|
|
27
|
+
this.namespace = getValidatedNamespace(options, '');
|
|
28
|
+
|
|
29
|
+
// Default configuration
|
|
30
|
+
this.config = {
|
|
31
|
+
// Browser Pool
|
|
32
|
+
pool: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
maxBrowsers: 5,
|
|
35
|
+
maxTabsPerBrowser: 10,
|
|
36
|
+
reuseTab: false,
|
|
37
|
+
closeOnIdle: true,
|
|
38
|
+
idleTimeout: 300000, // 5 minutes
|
|
39
|
+
...options.pool
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Browser Launch Options
|
|
43
|
+
launch: {
|
|
44
|
+
headless: true,
|
|
45
|
+
args: [
|
|
46
|
+
'--no-sandbox',
|
|
47
|
+
'--disable-setuid-sandbox',
|
|
48
|
+
'--disable-dev-shm-usage',
|
|
49
|
+
'--disable-accelerated-2d-canvas',
|
|
50
|
+
'--no-first-run',
|
|
51
|
+
'--no-zygote',
|
|
52
|
+
'--disable-gpu'
|
|
53
|
+
],
|
|
54
|
+
ignoreHTTPSErrors: true,
|
|
55
|
+
...options.launch
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Viewport & User Agent
|
|
59
|
+
viewport: {
|
|
60
|
+
width: 1920,
|
|
61
|
+
height: 1080,
|
|
62
|
+
deviceScaleFactor: 1,
|
|
63
|
+
randomize: true,
|
|
64
|
+
presets: ['desktop', 'laptop', 'tablet'],
|
|
65
|
+
...options.viewport
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// User Agent Management
|
|
69
|
+
userAgent: {
|
|
70
|
+
enabled: true,
|
|
71
|
+
random: true,
|
|
72
|
+
filters: {
|
|
73
|
+
deviceCategory: 'desktop',
|
|
74
|
+
...options.userAgent?.filters
|
|
75
|
+
},
|
|
76
|
+
custom: options.userAgent?.custom || null,
|
|
77
|
+
...options.userAgent
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Stealth Mode (Anti-Detection)
|
|
81
|
+
stealth: {
|
|
82
|
+
enabled: true,
|
|
83
|
+
enableEvasions: true,
|
|
84
|
+
...options.stealth
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Human Behavior Simulation
|
|
88
|
+
humanBehavior: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
mouse: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
bezierCurves: true,
|
|
93
|
+
overshoot: true,
|
|
94
|
+
jitter: true,
|
|
95
|
+
pathThroughElements: true,
|
|
96
|
+
...options.humanBehavior?.mouse
|
|
97
|
+
},
|
|
98
|
+
typing: {
|
|
99
|
+
enabled: true,
|
|
100
|
+
mistakes: true,
|
|
101
|
+
corrections: true,
|
|
102
|
+
pauseAfterWord: true,
|
|
103
|
+
speedVariation: true,
|
|
104
|
+
delayRange: [50, 150],
|
|
105
|
+
...options.humanBehavior?.typing
|
|
106
|
+
},
|
|
107
|
+
scrolling: {
|
|
108
|
+
enabled: true,
|
|
109
|
+
randomStops: true,
|
|
110
|
+
backScroll: true,
|
|
111
|
+
horizontalJitter: true,
|
|
112
|
+
...options.humanBehavior?.scrolling
|
|
113
|
+
},
|
|
114
|
+
...options.humanBehavior
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Cookie Management & Farming
|
|
118
|
+
cookies: {
|
|
119
|
+
enabled: true,
|
|
120
|
+
storage: {
|
|
121
|
+
resource: 'plg_puppeteer_cookies',
|
|
122
|
+
autoSave: true,
|
|
123
|
+
autoLoad: true,
|
|
124
|
+
encrypt: true,
|
|
125
|
+
...options.cookies?.storage
|
|
126
|
+
},
|
|
127
|
+
farming: {
|
|
128
|
+
enabled: true,
|
|
129
|
+
warmup: {
|
|
130
|
+
enabled: true,
|
|
131
|
+
pages: ['https://www.google.com', 'https://www.youtube.com', 'https://www.wikipedia.org'],
|
|
132
|
+
randomOrder: true,
|
|
133
|
+
timePerPage: { min: 5000, max: 15000 },
|
|
134
|
+
interactions: { scroll: true, click: true, hover: true },
|
|
135
|
+
...options.cookies?.farming?.warmup
|
|
136
|
+
},
|
|
137
|
+
rotation: {
|
|
138
|
+
enabled: true,
|
|
139
|
+
requestsPerCookie: 100,
|
|
140
|
+
maxAge: 86400000, // 24 hours
|
|
141
|
+
poolSize: 10,
|
|
142
|
+
...options.cookies?.farming?.rotation
|
|
143
|
+
},
|
|
144
|
+
reputation: {
|
|
145
|
+
enabled: true,
|
|
146
|
+
trackSuccess: true,
|
|
147
|
+
retireThreshold: 0.5,
|
|
148
|
+
ageBoost: true,
|
|
149
|
+
...options.cookies?.farming?.reputation
|
|
150
|
+
},
|
|
151
|
+
...options.cookies?.farming
|
|
152
|
+
},
|
|
153
|
+
...options.cookies
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Performance Optimization
|
|
157
|
+
performance: {
|
|
158
|
+
blockResources: {
|
|
159
|
+
enabled: true,
|
|
160
|
+
types: ['image', 'stylesheet', 'font', 'media'],
|
|
161
|
+
...options.performance?.blockResources
|
|
162
|
+
},
|
|
163
|
+
cacheEnabled: true,
|
|
164
|
+
javascriptEnabled: true,
|
|
165
|
+
...options.performance
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Network Monitoring (CDP)
|
|
169
|
+
networkMonitor: {
|
|
170
|
+
enabled: false, // Disabled by default (adds overhead)
|
|
171
|
+
persist: false, // Save to S3DB
|
|
172
|
+
filters: {
|
|
173
|
+
types: null, // ['image', 'script'] or null for all
|
|
174
|
+
statuses: null, // [404, 500] or null for all
|
|
175
|
+
minSize: null, // Only requests >= size (bytes)
|
|
176
|
+
maxSize: null, // Only requests <= size (bytes)
|
|
177
|
+
saveErrors: true, // Always save failed requests
|
|
178
|
+
saveLargeAssets: true, // Always save assets > 1MB
|
|
179
|
+
...options.networkMonitor?.filters
|
|
180
|
+
},
|
|
181
|
+
compression: {
|
|
182
|
+
enabled: true,
|
|
183
|
+
threshold: 10240, // Compress payloads > 10KB
|
|
184
|
+
...options.networkMonitor?.compression
|
|
185
|
+
},
|
|
186
|
+
...options.networkMonitor
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// Console Monitoring
|
|
190
|
+
consoleMonitor: {
|
|
191
|
+
enabled: false, // Disabled by default
|
|
192
|
+
persist: false, // Save to S3DB
|
|
193
|
+
filters: {
|
|
194
|
+
levels: null, // ['error', 'warning'] or null for all
|
|
195
|
+
excludePatterns: [], // Regex patterns to exclude
|
|
196
|
+
includeStackTraces: true,
|
|
197
|
+
includeSourceLocation: true,
|
|
198
|
+
captureNetwork: false, // Also capture network errors
|
|
199
|
+
...options.consoleMonitor?.filters
|
|
200
|
+
},
|
|
201
|
+
...options.consoleMonitor
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Screenshot & Recording
|
|
205
|
+
screenshot: {
|
|
206
|
+
fullPage: false,
|
|
207
|
+
type: 'png',
|
|
208
|
+
...options.screenshot
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// Proxy Support
|
|
212
|
+
proxy: {
|
|
213
|
+
enabled: false,
|
|
214
|
+
list: [], // Array of proxy URLs or objects
|
|
215
|
+
selectionStrategy: 'round-robin', // 'round-robin' | 'random' | 'least-used' | 'best-performance'
|
|
216
|
+
bypassList: [], // Domains to bypass proxy
|
|
217
|
+
healthCheck: {
|
|
218
|
+
enabled: true,
|
|
219
|
+
interval: 300000, // 5 minutes
|
|
220
|
+
testUrl: 'https://www.google.com',
|
|
221
|
+
timeout: 10000,
|
|
222
|
+
successRateThreshold: 0.3
|
|
223
|
+
},
|
|
224
|
+
// Legacy single proxy support (deprecated)
|
|
225
|
+
server: null,
|
|
226
|
+
username: null,
|
|
227
|
+
password: null,
|
|
228
|
+
...options.proxy
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// Error Handling & Retries
|
|
232
|
+
retries: {
|
|
233
|
+
enabled: true,
|
|
234
|
+
maxAttempts: 3,
|
|
235
|
+
backoff: 'exponential',
|
|
236
|
+
initialDelay: 1000,
|
|
237
|
+
...options.retries
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// Logging & Debugging
|
|
241
|
+
debug: {
|
|
242
|
+
enabled: false,
|
|
243
|
+
screenshots: false,
|
|
244
|
+
console: false,
|
|
245
|
+
network: false,
|
|
246
|
+
...options.debug
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const resourceNamesOption = options.resourceNames || {};
|
|
251
|
+
this._resourceDescriptors = {
|
|
252
|
+
cookies: {
|
|
253
|
+
defaultName: 'plg_puppeteer_cookies',
|
|
254
|
+
override: resourceNamesOption.cookies || options.cookies?.storage?.resource
|
|
255
|
+
},
|
|
256
|
+
consoleSessions: {
|
|
257
|
+
defaultName: 'plg_puppeteer_console_sessions',
|
|
258
|
+
override: resourceNamesOption.consoleSessions
|
|
259
|
+
},
|
|
260
|
+
consoleMessages: {
|
|
261
|
+
defaultName: 'plg_puppeteer_console_messages',
|
|
262
|
+
override: resourceNamesOption.consoleMessages
|
|
263
|
+
},
|
|
264
|
+
consoleErrors: {
|
|
265
|
+
defaultName: 'plg_puppeteer_console_errors',
|
|
266
|
+
override: resourceNamesOption.consoleErrors
|
|
267
|
+
},
|
|
268
|
+
networkSessions: {
|
|
269
|
+
defaultName: 'plg_puppeteer_network_sessions',
|
|
270
|
+
override: resourceNamesOption.networkSessions
|
|
271
|
+
},
|
|
272
|
+
networkRequests: {
|
|
273
|
+
defaultName: 'plg_puppeteer_network_requests',
|
|
274
|
+
override: resourceNamesOption.networkRequests
|
|
275
|
+
},
|
|
276
|
+
networkErrors: {
|
|
277
|
+
defaultName: 'plg_puppeteer_network_errors',
|
|
278
|
+
override: resourceNamesOption.networkErrors
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
this.resourceNames = this._resolveResourceNames();
|
|
282
|
+
|
|
283
|
+
this.config.cookies.storage.resource = this.resourceNames.cookies;
|
|
284
|
+
|
|
285
|
+
// Internal state
|
|
286
|
+
this.browserPool = [];
|
|
287
|
+
this.tabPool = new Map(); // Browser instance -> Set<Page>
|
|
288
|
+
this.browserIdleTimers = new Map(); // Browser instance -> timeout id
|
|
289
|
+
this.dedicatedBrowsers = new Set(); // Non-pooled browsers for cleanup
|
|
290
|
+
this.userAgentGenerator = null;
|
|
291
|
+
this.ghostCursor = null;
|
|
292
|
+
this.cookieManager = null;
|
|
293
|
+
this.proxyManager = null;
|
|
294
|
+
this.performanceManager = null;
|
|
295
|
+
this.networkMonitor = null;
|
|
296
|
+
this.consoleMonitor = null;
|
|
297
|
+
this.initialized = false;
|
|
298
|
+
|
|
299
|
+
if (this.config.pool.reuseTab) {
|
|
300
|
+
this.emit('puppeteer.configWarning', {
|
|
301
|
+
setting: 'pool.reuseTab',
|
|
302
|
+
message: 'pool.reuseTab is not supported yet and will be ignored.'
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
_resolveResourceNames() {
|
|
308
|
+
return resolveResourceNames('puppeteer', this._resourceDescriptors, {
|
|
309
|
+
namespace: this.namespace
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
onNamespaceChanged() {
|
|
314
|
+
this.resourceNames = this._resolveResourceNames();
|
|
315
|
+
if (this.config?.cookies?.storage) {
|
|
316
|
+
this.config.cookies.storage.resource = this.resourceNames.cookies;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Install plugin and validate dependencies
|
|
322
|
+
*/
|
|
323
|
+
async onInstall() {
|
|
324
|
+
// Validate required dependencies
|
|
325
|
+
requirePluginDependency('puppeteer', this.name);
|
|
326
|
+
requirePluginDependency('puppeteer-extra', this.name);
|
|
327
|
+
requirePluginDependency('puppeteer-extra-plugin-stealth', this.name);
|
|
328
|
+
requirePluginDependency('user-agents', this.name);
|
|
329
|
+
requirePluginDependency('ghost-cursor', this.name);
|
|
330
|
+
|
|
331
|
+
// Create cookie storage resource if enabled
|
|
332
|
+
if (this.config.cookies.enabled) {
|
|
333
|
+
await this._setupCookieStorage();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.emit('puppeteer.installed');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Start plugin and initialize browser pool
|
|
341
|
+
*/
|
|
342
|
+
async onStart() {
|
|
343
|
+
if (this.initialized) return;
|
|
344
|
+
|
|
345
|
+
// Import dependencies
|
|
346
|
+
await this._importDependencies();
|
|
347
|
+
|
|
348
|
+
// Initialize cookie manager
|
|
349
|
+
if (this.config.cookies.enabled) {
|
|
350
|
+
await this._initializeCookieManager();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Initialize proxy manager (requires cookie storage for binding restore)
|
|
354
|
+
if (this.config.proxy.enabled) {
|
|
355
|
+
await this._initializeProxyManager();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Initialize performance manager
|
|
359
|
+
await this._initializePerformanceManager();
|
|
360
|
+
|
|
361
|
+
// Initialize network monitor
|
|
362
|
+
if (this.config.networkMonitor.enabled) {
|
|
363
|
+
await this._initializeNetworkMonitor();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Initialize console monitor
|
|
367
|
+
if (this.config.consoleMonitor.enabled) {
|
|
368
|
+
await this._initializeConsoleMonitor();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Pre-warm browser pool if enabled
|
|
372
|
+
if (this.config.pool.enabled) {
|
|
373
|
+
await this._warmupBrowserPool();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.initialized = true;
|
|
377
|
+
this.emit('puppeteer.started');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Stop plugin and cleanup resources
|
|
382
|
+
*/
|
|
383
|
+
async onStop() {
|
|
384
|
+
await this._closeBrowserPool();
|
|
385
|
+
await this._closeDedicatedBrowsers();
|
|
386
|
+
this.initialized = false;
|
|
387
|
+
this.emit('puppeteer.stopped');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Uninstall plugin
|
|
392
|
+
*/
|
|
393
|
+
async onUninstall(options = {}) {
|
|
394
|
+
await this.onStop();
|
|
395
|
+
this.emit('puppeteer.uninstalled');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Import required dependencies (lazy loading)
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
async _importDependencies() {
|
|
403
|
+
const puppeteerModule = await import('puppeteer-extra');
|
|
404
|
+
const StealthPlugin = (await import('puppeteer-extra-plugin-stealth')).default;
|
|
405
|
+
const UserAgent = (await import('user-agents')).default;
|
|
406
|
+
const { createCursor } = await import('ghost-cursor');
|
|
407
|
+
|
|
408
|
+
// Setup puppeteer with stealth plugin
|
|
409
|
+
this.puppeteer = puppeteerModule.default || puppeteerModule;
|
|
410
|
+
|
|
411
|
+
if (this.config.stealth.enabled) {
|
|
412
|
+
this.puppeteer.use(StealthPlugin());
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Setup user agent generator
|
|
416
|
+
if (this.config.userAgent.enabled && this.config.userAgent.random) {
|
|
417
|
+
this.UserAgent = UserAgent;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Store ghost-cursor factory
|
|
421
|
+
this.createGhostCursor = createCursor;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Setup cookie storage resource
|
|
426
|
+
* @private
|
|
427
|
+
*/
|
|
428
|
+
async _setupCookieStorage() {
|
|
429
|
+
const resourceName = this.config.cookies.storage.resource;
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
await this.database.getResource(resourceName);
|
|
433
|
+
return;
|
|
434
|
+
} catch (err) {
|
|
435
|
+
// Resource missing, will create below
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const [created, createErr] = await tryFn(() => this.database.createResource({
|
|
439
|
+
name: resourceName,
|
|
440
|
+
attributes: {
|
|
441
|
+
sessionId: 'string|required',
|
|
442
|
+
cookies: 'array|required',
|
|
443
|
+
userAgent: 'string',
|
|
444
|
+
viewport: 'object',
|
|
445
|
+
proxyId: 'string|optional',
|
|
446
|
+
domain: 'string',
|
|
447
|
+
date: 'string',
|
|
448
|
+
reputation: {
|
|
449
|
+
successCount: 'number',
|
|
450
|
+
failCount: 'number',
|
|
451
|
+
successRate: 'number',
|
|
452
|
+
lastUsed: 'number'
|
|
453
|
+
},
|
|
454
|
+
metadata: {
|
|
455
|
+
createdAt: 'number',
|
|
456
|
+
expiresAt: 'number',
|
|
457
|
+
requestCount: 'number',
|
|
458
|
+
age: 'number'
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
timestamps: true,
|
|
462
|
+
behavior: 'body-only',
|
|
463
|
+
partitions: {
|
|
464
|
+
byProxy: { fields: { proxyId: 'string' } },
|
|
465
|
+
byDate: { fields: { date: 'string' } },
|
|
466
|
+
byDomain: { fields: { domain: 'string' } }
|
|
467
|
+
}
|
|
468
|
+
}));
|
|
469
|
+
|
|
470
|
+
if (!created) {
|
|
471
|
+
const existing = this.database.resources?.[resourceName];
|
|
472
|
+
if (!existing) {
|
|
473
|
+
throw createErr;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Initialize proxy manager
|
|
480
|
+
* @private
|
|
481
|
+
*/
|
|
482
|
+
async _initializeProxyManager() {
|
|
483
|
+
const { ProxyManager } = await import('./puppeteer/proxy-manager.js');
|
|
484
|
+
this.proxyManager = new ProxyManager(this);
|
|
485
|
+
await this.proxyManager.initialize();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Initialize cookie manager
|
|
490
|
+
* @private
|
|
491
|
+
*/
|
|
492
|
+
async _initializeCookieManager() {
|
|
493
|
+
const { CookieManager } = await import('./puppeteer/cookie-manager.js');
|
|
494
|
+
this.cookieManager = new CookieManager(this);
|
|
495
|
+
await this.cookieManager.initialize();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Initialize performance manager
|
|
500
|
+
* @private
|
|
501
|
+
*/
|
|
502
|
+
async _initializePerformanceManager() {
|
|
503
|
+
const { PerformanceManager } = await import('./puppeteer/performance-manager.js');
|
|
504
|
+
this.performanceManager = new PerformanceManager(this);
|
|
505
|
+
this.emit('puppeteer.performanceManager.initialized');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Initialize network monitor
|
|
510
|
+
* @private
|
|
511
|
+
*/
|
|
512
|
+
async _initializeNetworkMonitor() {
|
|
513
|
+
const { NetworkMonitor } = await import('./puppeteer/network-monitor.js');
|
|
514
|
+
this.networkMonitor = new NetworkMonitor(this);
|
|
515
|
+
|
|
516
|
+
// Initialize S3DB resources if persistence enabled
|
|
517
|
+
if (this.config.networkMonitor.persist) {
|
|
518
|
+
await this.networkMonitor.initialize();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
this.emit('puppeteer.networkMonitor.initialized');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Initialize console monitor
|
|
526
|
+
* @private
|
|
527
|
+
*/
|
|
528
|
+
async _initializeConsoleMonitor() {
|
|
529
|
+
const { ConsoleMonitor } = await import('./puppeteer/console-monitor.js');
|
|
530
|
+
this.consoleMonitor = new ConsoleMonitor(this);
|
|
531
|
+
|
|
532
|
+
// Initialize S3DB resources if persistence enabled
|
|
533
|
+
if (this.config.consoleMonitor.persist) {
|
|
534
|
+
await this.consoleMonitor.initialize();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
this.emit('puppeteer.consoleMonitor.initialized');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Warmup browser pool
|
|
542
|
+
* @private
|
|
543
|
+
*/
|
|
544
|
+
async _warmupBrowserPool() {
|
|
545
|
+
const poolSize = Math.min(this.config.pool.maxBrowsers, 2); // Start with 2 browsers
|
|
546
|
+
|
|
547
|
+
for (let i = 0; i < poolSize; i++) {
|
|
548
|
+
await this._createBrowser();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this.emit('puppeteer.poolWarmed', { size: this.browserPool.length });
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Create a new browser instance
|
|
556
|
+
* @private
|
|
557
|
+
* @param {Object} proxy - Optional proxy configuration
|
|
558
|
+
* @returns {Promise<Browser>}
|
|
559
|
+
*/
|
|
560
|
+
async _createBrowser(proxy = null) {
|
|
561
|
+
const launchOptions = {
|
|
562
|
+
...this.config.launch,
|
|
563
|
+
args: [...(this.config.launch.args || [])]
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// Add proxy args if provided
|
|
567
|
+
if (proxy && this.proxyManager) {
|
|
568
|
+
const proxyArgs = this.proxyManager.getProxyLaunchArgs(proxy);
|
|
569
|
+
launchOptions.args.push(...proxyArgs);
|
|
570
|
+
} else if (this.config.proxy.enabled && this.config.proxy.server) {
|
|
571
|
+
// Legacy single proxy support (deprecated)
|
|
572
|
+
launchOptions.args.push(`--proxy-server=${this.config.proxy.server}`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const browser = await this.puppeteer.launch(launchOptions);
|
|
576
|
+
|
|
577
|
+
// Only add to pool if no specific proxy (shared browser)
|
|
578
|
+
if (!proxy && this.config.pool.enabled) {
|
|
579
|
+
this.browserPool.push(browser);
|
|
580
|
+
this.tabPool.set(browser, new Set());
|
|
581
|
+
|
|
582
|
+
browser.on('disconnected', () => {
|
|
583
|
+
const index = this.browserPool.indexOf(browser);
|
|
584
|
+
if (index > -1) {
|
|
585
|
+
this.browserPool.splice(index, 1);
|
|
586
|
+
}
|
|
587
|
+
this.tabPool.delete(browser);
|
|
588
|
+
this._clearIdleTimer(browser);
|
|
589
|
+
this.dedicatedBrowsers.delete(browser);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return browser;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Get or create a browser instance
|
|
597
|
+
* @private
|
|
598
|
+
* @param {Object} proxy - Optional proxy configuration
|
|
599
|
+
* @returns {Promise<Browser>}
|
|
600
|
+
*/
|
|
601
|
+
async _getBrowser(proxy = null) {
|
|
602
|
+
// If proxy specified, create dedicated browser (not pooled)
|
|
603
|
+
if (proxy) {
|
|
604
|
+
return await this._createBrowser(proxy);
|
|
605
|
+
}
|
|
606
|
+
if (this.config.pool.enabled) {
|
|
607
|
+
// Find browser with available capacity
|
|
608
|
+
for (const browser of this.browserPool) {
|
|
609
|
+
const tabs = this.tabPool.get(browser);
|
|
610
|
+
if (!tabs || tabs.size < this.config.pool.maxTabsPerBrowser) {
|
|
611
|
+
return browser;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Create new browser if pool not full
|
|
616
|
+
if (this.browserPool.length < this.config.pool.maxBrowsers) {
|
|
617
|
+
return await this._createBrowser();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Use least loaded browser
|
|
621
|
+
let targetBrowser = this.browserPool[0];
|
|
622
|
+
let minTabs = this.tabPool.get(targetBrowser)?.size || 0;
|
|
623
|
+
|
|
624
|
+
for (const browser of this.browserPool.slice(1)) {
|
|
625
|
+
const tabs = this.tabPool.get(browser)?.size || 0;
|
|
626
|
+
if (tabs < minTabs) {
|
|
627
|
+
targetBrowser = browser;
|
|
628
|
+
minTabs = tabs;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return targetBrowser;
|
|
633
|
+
} else {
|
|
634
|
+
// No pooling - create new browser every time
|
|
635
|
+
return await this._createBrowser();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Close all browsers in pool
|
|
641
|
+
* @private
|
|
642
|
+
*/
|
|
643
|
+
async _closeBrowserPool() {
|
|
644
|
+
for (const browser of this.browserPool) {
|
|
645
|
+
this._clearIdleTimer(browser);
|
|
646
|
+
if (this.cookieManager) {
|
|
647
|
+
const tabs = this.tabPool.get(browser);
|
|
648
|
+
if (tabs) {
|
|
649
|
+
for (const page of tabs) {
|
|
650
|
+
if (!page || page._sessionSaved || !page._sessionId) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Skip if page already closed
|
|
655
|
+
if (typeof page.isClosed === 'function' && page.isClosed()) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
await this.cookieManager.saveSession(page, page._sessionId, {
|
|
661
|
+
success: !!page._navigationSuccess
|
|
662
|
+
});
|
|
663
|
+
page._sessionSaved = true;
|
|
664
|
+
} catch (err) {
|
|
665
|
+
page._sessionSaved = true;
|
|
666
|
+
this.emit('puppeteer.cookieSaveFailed', {
|
|
667
|
+
sessionId: page._sessionId,
|
|
668
|
+
error: err.message
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
await browser.close();
|
|
677
|
+
} catch (err) {
|
|
678
|
+
// Ignore errors during cleanup
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
this.browserPool = [];
|
|
682
|
+
this.tabPool.clear();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Clear idle timer for pooled browser
|
|
687
|
+
* @private
|
|
688
|
+
*/
|
|
689
|
+
_clearIdleTimer(browser) {
|
|
690
|
+
const timer = this.browserIdleTimers.get(browser);
|
|
691
|
+
if (timer) {
|
|
692
|
+
clearTimeout(timer);
|
|
693
|
+
this.browserIdleTimers.delete(browser);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Schedule pooled browser retirement when idle
|
|
699
|
+
* @private
|
|
700
|
+
*/
|
|
701
|
+
_scheduleIdleCloseIfNeeded(browser) {
|
|
702
|
+
if (!this.config.pool.closeOnIdle) return;
|
|
703
|
+
const tabs = this.tabPool.get(browser);
|
|
704
|
+
if (!tabs || tabs.size > 0) return;
|
|
705
|
+
if (this.browserIdleTimers.has(browser)) return;
|
|
706
|
+
|
|
707
|
+
const timeout = this.config.pool.idleTimeout || 300000;
|
|
708
|
+
const timer = setTimeout(async () => {
|
|
709
|
+
this.browserIdleTimers.delete(browser);
|
|
710
|
+
const currentTabs = this.tabPool.get(browser);
|
|
711
|
+
if (currentTabs && currentTabs.size === 0) {
|
|
712
|
+
await this._retireIdleBrowser(browser);
|
|
713
|
+
}
|
|
714
|
+
}, timeout);
|
|
715
|
+
|
|
716
|
+
if (typeof timer.unref === 'function') {
|
|
717
|
+
timer.unref();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
this.browserIdleTimers.set(browser, timer);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Retire pooled browser if still idle
|
|
725
|
+
* @private
|
|
726
|
+
* @param {Browser} browser
|
|
727
|
+
*/
|
|
728
|
+
async _retireIdleBrowser(browser) {
|
|
729
|
+
this.tabPool.delete(browser);
|
|
730
|
+
const index = this.browserPool.indexOf(browser);
|
|
731
|
+
if (index > -1) {
|
|
732
|
+
this.browserPool.splice(index, 1);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
await browser.close();
|
|
737
|
+
this.emit('puppeteer.browserRetired', { pooled: true });
|
|
738
|
+
} catch (err) {
|
|
739
|
+
this.emit('puppeteer.browserRetiredError', {
|
|
740
|
+
pooled: true,
|
|
741
|
+
error: err.message
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Close dedicated (non-pooled) browsers
|
|
748
|
+
* @private
|
|
749
|
+
*/
|
|
750
|
+
async _closeDedicatedBrowsers() {
|
|
751
|
+
for (const browser of Array.from(this.dedicatedBrowsers)) {
|
|
752
|
+
try {
|
|
753
|
+
await browser.close();
|
|
754
|
+
} catch (err) {
|
|
755
|
+
// Ignore errors during cleanup
|
|
756
|
+
} finally {
|
|
757
|
+
this.dedicatedBrowsers.delete(browser);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Generate random user agent
|
|
764
|
+
* @private
|
|
765
|
+
* @returns {string}
|
|
766
|
+
*/
|
|
767
|
+
_generateUserAgent() {
|
|
768
|
+
if (this.config.userAgent.custom) {
|
|
769
|
+
return this.config.userAgent.custom;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (this.config.userAgent.random && this.UserAgent) {
|
|
773
|
+
const userAgent = new this.UserAgent(this.config.userAgent.filters);
|
|
774
|
+
return userAgent.toString();
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Generate random viewport
|
|
782
|
+
* @private
|
|
783
|
+
* @returns {Object}
|
|
784
|
+
*/
|
|
785
|
+
_generateViewport() {
|
|
786
|
+
if (!this.config.viewport.randomize) {
|
|
787
|
+
return {
|
|
788
|
+
width: this.config.viewport.width,
|
|
789
|
+
height: this.config.viewport.height,
|
|
790
|
+
deviceScaleFactor: this.config.viewport.deviceScaleFactor
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Predefined viewport presets
|
|
795
|
+
const presets = {
|
|
796
|
+
desktop: [
|
|
797
|
+
{ width: 1920, height: 1080, deviceScaleFactor: 1 },
|
|
798
|
+
{ width: 1680, height: 1050, deviceScaleFactor: 1 },
|
|
799
|
+
{ width: 1600, height: 900, deviceScaleFactor: 1 },
|
|
800
|
+
{ width: 1440, height: 900, deviceScaleFactor: 1 },
|
|
801
|
+
{ width: 1366, height: 768, deviceScaleFactor: 1 }
|
|
802
|
+
],
|
|
803
|
+
laptop: [
|
|
804
|
+
{ width: 1440, height: 900, deviceScaleFactor: 1 },
|
|
805
|
+
{ width: 1366, height: 768, deviceScaleFactor: 1 },
|
|
806
|
+
{ width: 1280, height: 800, deviceScaleFactor: 1 }
|
|
807
|
+
],
|
|
808
|
+
tablet: [
|
|
809
|
+
{ width: 1024, height: 768, deviceScaleFactor: 2 },
|
|
810
|
+
{ width: 768, height: 1024, deviceScaleFactor: 2 }
|
|
811
|
+
]
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// Select preset categories
|
|
815
|
+
const categories = this.config.viewport.presets || ['desktop'];
|
|
816
|
+
const availablePresets = categories.flatMap(cat => presets[cat] || []);
|
|
817
|
+
|
|
818
|
+
return availablePresets[Math.floor(Math.random() * availablePresets.length)];
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* PUBLIC API
|
|
823
|
+
*/
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Navigate to URL with human behavior
|
|
827
|
+
* @param {string} url - URL to navigate to
|
|
828
|
+
* @param {Object} options - Navigation options
|
|
829
|
+
* @returns {Promise<Page>}
|
|
830
|
+
*/
|
|
831
|
+
async navigate(url, options = {}) {
|
|
832
|
+
const {
|
|
833
|
+
useSession = null,
|
|
834
|
+
screenshot = false,
|
|
835
|
+
waitUntil = 'networkidle2',
|
|
836
|
+
timeout = 30000
|
|
837
|
+
} = options;
|
|
838
|
+
|
|
839
|
+
// IMMUTABLE PROXY BINDING: Get proxy for session if proxy is enabled
|
|
840
|
+
let proxy = null;
|
|
841
|
+
let proxyId = null;
|
|
842
|
+
|
|
843
|
+
if (useSession && this.proxyManager) {
|
|
844
|
+
proxy = this.proxyManager.getProxyForSession(useSession, true);
|
|
845
|
+
proxyId = proxy?.id || null;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Get browser (with proxy if needed)
|
|
849
|
+
const browser = await this._getBrowser(proxy);
|
|
850
|
+
const page = await browser.newPage();
|
|
851
|
+
const isPooledBrowser = !proxy && this.config.pool.enabled;
|
|
852
|
+
|
|
853
|
+
if (isPooledBrowser) {
|
|
854
|
+
const tabs = this.tabPool.get(browser);
|
|
855
|
+
if (tabs) {
|
|
856
|
+
tabs.add(page);
|
|
857
|
+
this._clearIdleTimer(browser);
|
|
858
|
+
}
|
|
859
|
+
} else {
|
|
860
|
+
this.dedicatedBrowsers.add(browser);
|
|
861
|
+
browser.once('disconnected', () => {
|
|
862
|
+
this.dedicatedBrowsers.delete(browser);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Authenticate proxy if needed
|
|
867
|
+
if (proxy && this.proxyManager) {
|
|
868
|
+
await this.proxyManager.authenticateProxy(page, proxy);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Setup viewport
|
|
872
|
+
const viewport = this._generateViewport();
|
|
873
|
+
await page.setViewport(viewport);
|
|
874
|
+
|
|
875
|
+
// Setup user agent
|
|
876
|
+
const userAgent = this._generateUserAgent();
|
|
877
|
+
if (userAgent) {
|
|
878
|
+
await page.setUserAgent(userAgent);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Setup resource blocking for performance
|
|
882
|
+
if (this.config.performance.blockResources.enabled) {
|
|
883
|
+
await page.setRequestInterception(true);
|
|
884
|
+
page.on('request', (request) => {
|
|
885
|
+
if (this.config.performance.blockResources.types.includes(request.resourceType())) {
|
|
886
|
+
request.abort();
|
|
887
|
+
} else {
|
|
888
|
+
request.continue();
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Load cookies from session
|
|
894
|
+
if (useSession && this.cookieManager) {
|
|
895
|
+
await this.cookieManager.loadSession(page, useSession);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Setup ghost cursor for human behavior
|
|
899
|
+
let cursor = null;
|
|
900
|
+
if (this.config.humanBehavior.enabled && this.config.humanBehavior.mouse.enabled) {
|
|
901
|
+
cursor = this.createGhostCursor(page);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Navigate with error handling for proxy
|
|
905
|
+
let navigationSuccess = false;
|
|
906
|
+
try {
|
|
907
|
+
await page.goto(url, { waitUntil, timeout });
|
|
908
|
+
navigationSuccess = true;
|
|
909
|
+
|
|
910
|
+
// Record successful proxy usage
|
|
911
|
+
if (proxyId && this.proxyManager) {
|
|
912
|
+
this.proxyManager.recordProxyUsage(proxyId, true);
|
|
913
|
+
}
|
|
914
|
+
} catch (err) {
|
|
915
|
+
// Record failed proxy usage
|
|
916
|
+
if (proxyId && this.proxyManager) {
|
|
917
|
+
this.proxyManager.recordProxyUsage(proxyId, false);
|
|
918
|
+
}
|
|
919
|
+
throw err;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Take screenshot if requested
|
|
923
|
+
if (screenshot) {
|
|
924
|
+
const screenshotBuffer = await page.screenshot(this.config.screenshot);
|
|
925
|
+
page._screenshot = screenshotBuffer;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Attach helper methods to page
|
|
929
|
+
page._cursor = cursor;
|
|
930
|
+
page._userAgent = userAgent;
|
|
931
|
+
page._viewport = viewport;
|
|
932
|
+
page._proxyId = proxyId; // IMMUTABLE: Proxy binding stored on page
|
|
933
|
+
page._sessionId = useSession;
|
|
934
|
+
page._navigationSuccess = navigationSuccess;
|
|
935
|
+
page._sessionSaved = false;
|
|
936
|
+
|
|
937
|
+
// Add human behavior methods
|
|
938
|
+
if (this.config.humanBehavior.enabled) {
|
|
939
|
+
this._attachHumanBehaviorMethods(page);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
let hasSavedSession = false;
|
|
943
|
+
let browserClosed = false;
|
|
944
|
+
const originalClose = page.close?.bind(page) || (async () => {});
|
|
945
|
+
const shouldAutoCloseBrowser = !isPooledBrowser;
|
|
946
|
+
|
|
947
|
+
page.on('close', () => {
|
|
948
|
+
if (isPooledBrowser) {
|
|
949
|
+
const tabs = this.tabPool.get(browser);
|
|
950
|
+
tabs?.delete(page);
|
|
951
|
+
this._scheduleIdleCloseIfNeeded(browser);
|
|
952
|
+
} else {
|
|
953
|
+
this.dedicatedBrowsers.delete(browser);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
page.close = async (...closeArgs) => {
|
|
958
|
+
if (!hasSavedSession && useSession && this.cookieManager && !page._sessionSaved) {
|
|
959
|
+
try {
|
|
960
|
+
await this.cookieManager.saveSession(page, useSession, {
|
|
961
|
+
success: navigationSuccess
|
|
962
|
+
});
|
|
963
|
+
page._sessionSaved = true;
|
|
964
|
+
} catch (err) {
|
|
965
|
+
this.emit('puppeteer.cookieSaveFailed', {
|
|
966
|
+
sessionId: useSession,
|
|
967
|
+
error: err.message
|
|
968
|
+
});
|
|
969
|
+
page._sessionSaved = true;
|
|
970
|
+
} finally {
|
|
971
|
+
hasSavedSession = true;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
try {
|
|
976
|
+
const result = await originalClose(...closeArgs);
|
|
977
|
+
return result;
|
|
978
|
+
} finally {
|
|
979
|
+
if (isPooledBrowser) {
|
|
980
|
+
const tabs = this.tabPool.get(browser);
|
|
981
|
+
tabs?.delete(page);
|
|
982
|
+
this._scheduleIdleCloseIfNeeded(browser);
|
|
983
|
+
} else if (shouldAutoCloseBrowser && !browserClosed) {
|
|
984
|
+
try {
|
|
985
|
+
await browser.close();
|
|
986
|
+
this.emit('puppeteer.browserClosed', { pooled: false });
|
|
987
|
+
} catch (err) {
|
|
988
|
+
this.emit('puppeteer.browserCloseFailed', {
|
|
989
|
+
pooled: false,
|
|
990
|
+
error: err.message
|
|
991
|
+
});
|
|
992
|
+
} finally {
|
|
993
|
+
browserClosed = true;
|
|
994
|
+
this.dedicatedBrowsers.delete(browser);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
this.emit('puppeteer.navigate', {
|
|
1001
|
+
url,
|
|
1002
|
+
userAgent,
|
|
1003
|
+
viewport,
|
|
1004
|
+
proxyId,
|
|
1005
|
+
sessionId: useSession
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
return page;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Run handler with session-aware navigation helper
|
|
1013
|
+
* @param {string} sessionId - Session identifier
|
|
1014
|
+
* @param {Function} handler - Async function receiving the page instance
|
|
1015
|
+
* @param {Object} options - Navigate options (requires url)
|
|
1016
|
+
* @returns {Promise<*>}
|
|
1017
|
+
*/
|
|
1018
|
+
async withSession(sessionId, handler, options = {}) {
|
|
1019
|
+
if (!sessionId) {
|
|
1020
|
+
throw new PluginError('withSession requires a sessionId', {
|
|
1021
|
+
pluginName: 'PuppeteerPlugin',
|
|
1022
|
+
operation: 'withSession',
|
|
1023
|
+
statusCode: 400,
|
|
1024
|
+
retriable: false,
|
|
1025
|
+
suggestion: 'Pass a sessionId when invoking withSession so cookies/proxies can be resolved.'
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
if (typeof handler !== 'function') {
|
|
1029
|
+
throw new TypeError('withSession handler must be a function');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const { url, ...navigateOptions } = options;
|
|
1033
|
+
if (!url) {
|
|
1034
|
+
throw new PluginError('withSession requires an options.url value', {
|
|
1035
|
+
pluginName: 'PuppeteerPlugin',
|
|
1036
|
+
operation: 'withSession',
|
|
1037
|
+
statusCode: 400,
|
|
1038
|
+
retriable: false,
|
|
1039
|
+
suggestion: 'Provide options.url to navigate before executing the session handler.'
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
this.emit('puppeteer.withSession.start', { sessionId, url });
|
|
1044
|
+
|
|
1045
|
+
const page = await this.navigate(url, {
|
|
1046
|
+
...navigateOptions,
|
|
1047
|
+
useSession: sessionId
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
let handlerError = null;
|
|
1051
|
+
|
|
1052
|
+
try {
|
|
1053
|
+
const result = await handler(page, this);
|
|
1054
|
+
return result;
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
handlerError = err;
|
|
1057
|
+
throw err;
|
|
1058
|
+
} finally {
|
|
1059
|
+
try {
|
|
1060
|
+
await page.close();
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
this.emit('puppeteer.withSession.cleanupFailed', {
|
|
1063
|
+
sessionId,
|
|
1064
|
+
url,
|
|
1065
|
+
error: err.message
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
this.emit('puppeteer.withSession.finish', {
|
|
1070
|
+
sessionId,
|
|
1071
|
+
url,
|
|
1072
|
+
error: handlerError ? handlerError.message : null
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Attach human behavior methods to page
|
|
1079
|
+
* @private
|
|
1080
|
+
*/
|
|
1081
|
+
_attachHumanBehaviorMethods(page) {
|
|
1082
|
+
// Human click
|
|
1083
|
+
page.humanClick = async (selector, options = {}) => {
|
|
1084
|
+
const element = await page.$(selector);
|
|
1085
|
+
if (!element) {
|
|
1086
|
+
throw new PluginError(`Element not found: ${selector}`, {
|
|
1087
|
+
pluginName: 'PuppeteerPlugin',
|
|
1088
|
+
operation: 'humanClick',
|
|
1089
|
+
statusCode: 404,
|
|
1090
|
+
retriable: false,
|
|
1091
|
+
suggestion: 'Ensure the selector matches an element on the page before invoking humanClick.',
|
|
1092
|
+
metadata: { selector }
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (this.config.humanBehavior.mouse.pathThroughElements && page._cursor) {
|
|
1097
|
+
// Move through elements to destination
|
|
1098
|
+
await page._cursor.moveTo(selector);
|
|
1099
|
+
await page._cursor.click();
|
|
1100
|
+
} else {
|
|
1101
|
+
await element.click();
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
// Human move
|
|
1106
|
+
page.humanMoveTo = async (selector, options = {}) => {
|
|
1107
|
+
if (!page._cursor) {
|
|
1108
|
+
throw new PluginError('Ghost cursor not initialized', {
|
|
1109
|
+
pluginName: 'PuppeteerPlugin',
|
|
1110
|
+
operation: 'humanMoveTo',
|
|
1111
|
+
statusCode: 500,
|
|
1112
|
+
retriable: false,
|
|
1113
|
+
suggestion: 'Enable humanBehavior.mouse.enableGhostCursor in configuration before using humanMoveTo.'
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
await page._cursor.moveTo(selector);
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
// Human type
|
|
1121
|
+
page.humanType = async (selector, text, options = {}) => {
|
|
1122
|
+
const element = await page.$(selector);
|
|
1123
|
+
if (!element) {
|
|
1124
|
+
throw new PluginError(`Element not found: ${selector}`, {
|
|
1125
|
+
pluginName: 'PuppeteerPlugin',
|
|
1126
|
+
operation: 'humanMoveTo',
|
|
1127
|
+
statusCode: 404,
|
|
1128
|
+
retriable: false,
|
|
1129
|
+
suggestion: 'Ensure the selector is present on the page when calling humanMoveTo.',
|
|
1130
|
+
metadata: { selector }
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
await element.click();
|
|
1135
|
+
|
|
1136
|
+
if (this.config.humanBehavior.typing.mistakes) {
|
|
1137
|
+
// Type with mistakes and corrections
|
|
1138
|
+
await this._typeWithMistakes(page, text, options);
|
|
1139
|
+
} else {
|
|
1140
|
+
// Normal typing with delays
|
|
1141
|
+
const [min, max] = this.config.humanBehavior.typing.delayRange;
|
|
1142
|
+
await page.type(selector, text, {
|
|
1143
|
+
delay: min + Math.random() * (max - min)
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// Human scroll
|
|
1149
|
+
page.humanScroll = async (options = {}) => {
|
|
1150
|
+
const { distance = null, direction = 'down' } = options;
|
|
1151
|
+
|
|
1152
|
+
if (distance) {
|
|
1153
|
+
await page.evaluate((dist, dir) => {
|
|
1154
|
+
window.scrollBy(0, dir === 'down' ? dist : -dist);
|
|
1155
|
+
}, distance, direction);
|
|
1156
|
+
} else {
|
|
1157
|
+
// Scroll to bottom with random stops
|
|
1158
|
+
await this._scrollWithStops(page, direction);
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Type with random mistakes and corrections
|
|
1165
|
+
* @private
|
|
1166
|
+
*/
|
|
1167
|
+
async _typeWithMistakes(page, text, options = {}) {
|
|
1168
|
+
const words = text.split(' ');
|
|
1169
|
+
|
|
1170
|
+
for (let i = 0; i < words.length; i++) {
|
|
1171
|
+
const word = words[i];
|
|
1172
|
+
|
|
1173
|
+
// 20% chance of making a mistake
|
|
1174
|
+
if (Math.random() < 0.2 && word.length > 3) {
|
|
1175
|
+
// Type wrong letter
|
|
1176
|
+
const wrongPos = Math.floor(Math.random() * word.length);
|
|
1177
|
+
const wrongChar = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
1178
|
+
const wrongWord = word.slice(0, wrongPos) + wrongChar + word.slice(wrongPos + 1);
|
|
1179
|
+
|
|
1180
|
+
await page.keyboard.type(wrongWord, { delay: 100 });
|
|
1181
|
+
await this._randomDelay(200, 500);
|
|
1182
|
+
|
|
1183
|
+
// Delete and retype
|
|
1184
|
+
for (let j = 0; j < wrongWord.length; j++) {
|
|
1185
|
+
await page.keyboard.press('Backspace');
|
|
1186
|
+
await this._randomDelay(50, 100);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
await page.keyboard.type(word, { delay: 100 });
|
|
1190
|
+
} else {
|
|
1191
|
+
await page.keyboard.type(word, { delay: 100 });
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// Add space between words
|
|
1195
|
+
if (i < words.length - 1) {
|
|
1196
|
+
await page.keyboard.press('Space');
|
|
1197
|
+
await this._randomDelay(100, 300);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Scroll with random stops
|
|
1204
|
+
* @private
|
|
1205
|
+
*/
|
|
1206
|
+
async _scrollWithStops(page, direction = 'down') {
|
|
1207
|
+
const scrollHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
1208
|
+
const viewportHeight = await page.evaluate(() => window.innerHeight);
|
|
1209
|
+
const steps = Math.floor(scrollHeight / viewportHeight);
|
|
1210
|
+
|
|
1211
|
+
for (let i = 0; i < steps; i++) {
|
|
1212
|
+
await page.evaluate((dir, vh) => {
|
|
1213
|
+
window.scrollBy(0, dir === 'down' ? vh : -vh);
|
|
1214
|
+
}, direction, viewportHeight);
|
|
1215
|
+
|
|
1216
|
+
await this._randomDelay(500, 1500);
|
|
1217
|
+
|
|
1218
|
+
// Random back scroll
|
|
1219
|
+
if (this.config.humanBehavior.scrolling.backScroll && Math.random() < 0.1) {
|
|
1220
|
+
await page.evaluate(() => window.scrollBy(0, -100));
|
|
1221
|
+
await this._randomDelay(200, 500);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Random delay helper
|
|
1228
|
+
* @private
|
|
1229
|
+
*/
|
|
1230
|
+
async _randomDelay(min, max) {
|
|
1231
|
+
const delay = min + Math.random() * (max - min);
|
|
1232
|
+
return new Promise(resolve => setTimeout(resolve, delay));
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Farm cookies for a session
|
|
1237
|
+
* @param {string} sessionId - Session identifier
|
|
1238
|
+
* @returns {Promise<void>}
|
|
1239
|
+
*/
|
|
1240
|
+
async farmCookies(sessionId) {
|
|
1241
|
+
if (!this.cookieManager) {
|
|
1242
|
+
throw new PluginError('Cookie manager not initialized', {
|
|
1243
|
+
pluginName: 'PuppeteerPlugin',
|
|
1244
|
+
operation: 'farmCookies',
|
|
1245
|
+
statusCode: 500,
|
|
1246
|
+
retriable: false,
|
|
1247
|
+
suggestion: 'Enable cookieManager during plugin initialization before calling farmCookies.'
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
return await this.cookieManager.farmCookies(sessionId);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* Get cookie pool statistics
|
|
1256
|
+
* @returns {Promise<Object>}
|
|
1257
|
+
*/
|
|
1258
|
+
async getCookieStats() {
|
|
1259
|
+
if (!this.cookieManager) {
|
|
1260
|
+
throw new PluginError('Cookie manager not initialized', {
|
|
1261
|
+
pluginName: 'PuppeteerPlugin',
|
|
1262
|
+
operation: 'getCookieStats',
|
|
1263
|
+
statusCode: 500,
|
|
1264
|
+
retriable: false,
|
|
1265
|
+
suggestion: 'Ensure cookieManager is configured before requesting cookie stats.'
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
return await this.cookieManager.getStats();
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* Get proxy pool statistics
|
|
1274
|
+
* @returns {Array}
|
|
1275
|
+
*/
|
|
1276
|
+
getProxyStats() {
|
|
1277
|
+
if (!this.proxyManager) {
|
|
1278
|
+
throw new PluginError('Proxy manager not initialized', {
|
|
1279
|
+
pluginName: 'PuppeteerPlugin',
|
|
1280
|
+
operation: 'getProxyStats',
|
|
1281
|
+
statusCode: 500,
|
|
1282
|
+
retriable: false,
|
|
1283
|
+
suggestion: 'Configure proxyManager before attempting to read proxy statistics.'
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
return this.proxyManager.getProxyStats();
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Get session-proxy bindings
|
|
1292
|
+
* @returns {Array}
|
|
1293
|
+
*/
|
|
1294
|
+
getSessionProxyBindings() {
|
|
1295
|
+
if (!this.proxyManager) {
|
|
1296
|
+
throw new PluginError('Proxy manager not initialized', {
|
|
1297
|
+
pluginName: 'PuppeteerPlugin',
|
|
1298
|
+
operation: 'getSessionProxyBindings',
|
|
1299
|
+
statusCode: 500,
|
|
1300
|
+
retriable: false,
|
|
1301
|
+
suggestion: 'Initialize proxyManager before retrieving session-proxy bindings.'
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
return this.proxyManager.getSessionBindings();
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Check health of all proxies
|
|
1310
|
+
* @returns {Promise<Object>}
|
|
1311
|
+
*/
|
|
1312
|
+
async checkProxyHealth() {
|
|
1313
|
+
if (!this.proxyManager) {
|
|
1314
|
+
throw new PluginError('Proxy manager not initialized', {
|
|
1315
|
+
pluginName: 'PuppeteerPlugin',
|
|
1316
|
+
operation: 'checkProxyHealth',
|
|
1317
|
+
statusCode: 500,
|
|
1318
|
+
retriable: false,
|
|
1319
|
+
suggestion: 'Set up proxyManager before running health checks.'
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
return await this.proxyManager.checkAllProxies();
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
export default PuppeteerPlugin;
|