s3db.js 13.6.0 → 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.
Files changed (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,478 @@
1
+ /**
2
+ * ProxyManager - Manages proxy pools and session-proxy binding
3
+ *
4
+ * Key Concept: IMMUTABLE BINDING
5
+ * Once a session is created with a proxy, they are bound forever.
6
+ * This prevents fingerprint leakage and maintains consistency.
7
+ *
8
+ * Features:
9
+ * - Proxy pool management
10
+ * - Round-robin, random, or least-used selection
11
+ * - Health monitoring
12
+ * - Session-proxy immutable binding
13
+ * - Proxy authentication support
14
+ */
15
+ import { BrowserPoolError } from '../puppeteer.errors.js';
16
+
17
+ export class ProxyManager {
18
+ constructor(plugin) {
19
+ this.plugin = plugin;
20
+ this.config = plugin.config.proxy;
21
+ this.storage = null;
22
+
23
+ // Proxy pool
24
+ this.proxies = []; // Array of proxy configs
25
+ this.proxyStats = new Map(); // proxyId -> { requests, failures, lastUsed, healthy }
26
+
27
+ // Session-Proxy binding (IMMUTABLE!)
28
+ this.sessionProxyMap = new Map(); // sessionId -> proxyId
29
+
30
+ // Proxy selection strategy
31
+ this.selectionStrategy = this.config.selectionStrategy || 'round-robin';
32
+ this.currentProxyIndex = 0;
33
+ }
34
+
35
+ /**
36
+ * Initialize proxy manager
37
+ */
38
+ async initialize() {
39
+ // Load proxies from config
40
+ if (this.config.enabled && this.config.list && this.config.list.length > 0) {
41
+ this.proxies = this.config.list.map((proxy, index) => ({
42
+ id: `proxy_${index}`,
43
+ ...this._parseProxy(proxy)
44
+ }));
45
+
46
+ // Initialize stats for each proxy
47
+ for (const proxy of this.proxies) {
48
+ this.proxyStats.set(proxy.id, {
49
+ requests: 0,
50
+ failures: 0,
51
+ successRate: 1.0,
52
+ lastUsed: 0,
53
+ healthy: true,
54
+ createdAt: Date.now()
55
+ });
56
+ }
57
+
58
+ this.plugin.emit('proxyManager.initialized', {
59
+ count: this.proxies.length,
60
+ strategy: this.selectionStrategy
61
+ });
62
+ }
63
+
64
+ // Load session-proxy bindings if storage exists
65
+ if (this.plugin.cookieManager && this.plugin.cookieManager.storage) {
66
+ await this._loadSessionProxyBindings();
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Parse proxy string or object
72
+ * @private
73
+ */
74
+ _parseProxy(proxy) {
75
+ if (typeof proxy === 'string') {
76
+ // Format: http://user:pass@host:port or http://host:port
77
+ const url = new URL(proxy);
78
+ return {
79
+ protocol: url.protocol.replace(':', ''),
80
+ host: url.hostname,
81
+ port: parseInt(url.port) || (url.protocol === 'https:' ? 443 : 80),
82
+ username: url.username || null,
83
+ password: url.password || null,
84
+ url: proxy
85
+ };
86
+ } else {
87
+ // Object format
88
+ const protocol = proxy.protocol || 'http';
89
+ const host = proxy.host;
90
+ const port = proxy.port || (protocol === 'https' ? 443 : 80);
91
+ const username = proxy.username || null;
92
+ const password = proxy.password || null;
93
+
94
+ // Build URL
95
+ let url = `${protocol}://`;
96
+ if (username && password) {
97
+ url += `${username}:${password}@`;
98
+ }
99
+ url += `${host}:${port}`;
100
+
101
+ return {
102
+ protocol,
103
+ host,
104
+ port,
105
+ username,
106
+ password,
107
+ url
108
+ };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Load session-proxy bindings from storage
114
+ * @private
115
+ */
116
+ async _loadSessionProxyBindings() {
117
+ const storage = this.plugin.cookieManager.storage;
118
+ const sessions = await storage.list({ limit: 1000 });
119
+
120
+ for (const session of sessions) {
121
+ if (session.proxyId) {
122
+ this.sessionProxyMap.set(session.sessionId, session.proxyId);
123
+ }
124
+ }
125
+
126
+ this.plugin.emit('proxyManager.bindingsLoaded', {
127
+ count: this.sessionProxyMap.size
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Get proxy for a session (respecting immutable binding)
133
+ * @param {string} sessionId - Session identifier
134
+ * @param {boolean} createIfMissing - Create new binding if session is new
135
+ * @returns {Object|null} - Proxy config or null
136
+ */
137
+ getProxyForSession(sessionId, createIfMissing = true) {
138
+ if (!this.config.enabled || this.proxies.length === 0) {
139
+ return null;
140
+ }
141
+
142
+ // Check if session already has a proxy bound (IMMUTABLE!)
143
+ if (this.sessionProxyMap.has(sessionId)) {
144
+ const proxyId = this.sessionProxyMap.get(sessionId);
145
+ const proxy = this.proxies.find(p => p.id === proxyId);
146
+
147
+ if (!proxy) {
148
+ throw new BrowserPoolError(`Proxy ${proxyId} bound to session ${sessionId} not found in pool`, {
149
+ operation: 'getProxyForSession',
150
+ retriable: false,
151
+ suggestion: 'Ensure proxies remain registered while sessions are active.',
152
+ proxyId,
153
+ sessionId
154
+ });
155
+ }
156
+
157
+ // Verify proxy is still healthy
158
+ const stats = this.proxyStats.get(proxyId);
159
+ if (!stats || !stats.healthy) {
160
+ throw new BrowserPoolError(`Proxy ${proxyId} bound to session ${sessionId} is unhealthy`, {
161
+ operation: 'getProxyForSession',
162
+ retriable: true,
163
+ suggestion: 'Rebind the session to a healthy proxy or refresh the proxy pool.',
164
+ proxyId,
165
+ sessionId
166
+ });
167
+ }
168
+
169
+ return proxy;
170
+ }
171
+
172
+ // New session - select proxy if createIfMissing
173
+ if (createIfMissing) {
174
+ const proxy = this._selectProxy();
175
+
176
+ if (proxy) {
177
+ // Create IMMUTABLE binding
178
+ this.sessionProxyMap.set(sessionId, proxy.id);
179
+
180
+ this.plugin.emit('proxyManager.sessionBound', {
181
+ sessionId,
182
+ proxyId: proxy.id,
183
+ proxyUrl: this._maskProxyUrl(proxy.url)
184
+ });
185
+
186
+ return proxy;
187
+ }
188
+ }
189
+
190
+ return null;
191
+ }
192
+
193
+ /**
194
+ * Select proxy based on strategy
195
+ * @private
196
+ */
197
+ _selectProxy() {
198
+ // Filter healthy proxies only
199
+ const healthyProxies = this.proxies.filter(proxy => {
200
+ const stats = this.proxyStats.get(proxy.id);
201
+ return stats ? stats.healthy : false;
202
+ });
203
+
204
+ if (healthyProxies.length === 0) {
205
+ throw new BrowserPoolError('No healthy proxies available', {
206
+ operation: '_selectProxy',
207
+ retriable: true,
208
+ suggestion: 'Add healthy proxies to the configuration or allow existing proxies to recover.',
209
+ available: this.proxies.length
210
+ });
211
+ }
212
+
213
+ let selectedProxy;
214
+
215
+ switch (this.selectionStrategy) {
216
+ case 'round-robin':
217
+ selectedProxy = healthyProxies[this.currentProxyIndex % healthyProxies.length];
218
+ this.currentProxyIndex++;
219
+ break;
220
+
221
+ case 'random':
222
+ selectedProxy = healthyProxies[Math.floor(Math.random() * healthyProxies.length)];
223
+ break;
224
+
225
+ case 'least-used':
226
+ // Select proxy with lowest request count
227
+ selectedProxy = healthyProxies.reduce((min, proxy) => {
228
+ const proxyStats = this.proxyStats.get(proxy.id);
229
+ const minStats = this.proxyStats.get(min.id);
230
+ return proxyStats.requests < minStats.requests ? proxy : min;
231
+ });
232
+ break;
233
+
234
+ case 'best-performance':
235
+ // Select proxy with highest success rate
236
+ selectedProxy = healthyProxies.reduce((best, proxy) => {
237
+ const proxyStats = this.proxyStats.get(proxy.id);
238
+ const bestStats = this.proxyStats.get(best.id);
239
+ return proxyStats.successRate > bestStats.successRate ? proxy : best;
240
+ });
241
+ break;
242
+
243
+ default:
244
+ selectedProxy = healthyProxies[0];
245
+ }
246
+
247
+ return selectedProxy;
248
+ }
249
+
250
+ /**
251
+ * Record proxy usage
252
+ * @param {string} proxyId - Proxy identifier
253
+ * @param {boolean} success - Whether request succeeded
254
+ */
255
+ recordProxyUsage(proxyId, success = true) {
256
+ const stats = this.proxyStats.get(proxyId);
257
+ if (!stats) return;
258
+
259
+ stats.requests++;
260
+ stats.lastUsed = Date.now();
261
+
262
+ if (success) {
263
+ // Update success rate with exponential moving average
264
+ stats.successRate = stats.successRate * 0.9 + 0.1;
265
+ } else {
266
+ stats.failures++;
267
+ stats.successRate = stats.successRate * 0.9;
268
+
269
+ // Mark unhealthy if success rate drops below threshold
270
+ const threshold = this.config.healthCheck?.successRateThreshold || 0.3;
271
+ if (stats.successRate < threshold) {
272
+ stats.healthy = false;
273
+ this.plugin.emit('proxyManager.proxyUnhealthy', {
274
+ proxyId,
275
+ successRate: stats.successRate
276
+ });
277
+ }
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Get proxy statistics
283
+ * @returns {Array}
284
+ */
285
+ getProxyStats() {
286
+ return this.proxies.map(proxy => {
287
+ const stats = this.proxyStats.get(proxy.id);
288
+ return {
289
+ proxyId: proxy.id,
290
+ url: this._maskProxyUrl(proxy.url),
291
+ ...stats,
292
+ boundSessions: Array.from(this.sessionProxyMap.entries())
293
+ .filter(([_, proxyId]) => proxyId === proxy.id)
294
+ .length
295
+ };
296
+ });
297
+ }
298
+
299
+ /**
300
+ * Get session-proxy bindings
301
+ * @returns {Array}
302
+ */
303
+ getSessionBindings() {
304
+ return Array.from(this.sessionProxyMap.entries()).map(([sessionId, proxyId]) => {
305
+ const proxy = this.proxies.find(p => p.id === proxyId);
306
+ return {
307
+ sessionId,
308
+ proxyId,
309
+ proxyUrl: proxy ? this._maskProxyUrl(proxy.url) : 'unknown'
310
+ };
311
+ });
312
+ }
313
+
314
+ /**
315
+ * Verify session-proxy binding integrity
316
+ * @param {string} sessionId - Session identifier
317
+ * @param {string} proxyId - Proxy identifier
318
+ * @returns {boolean}
319
+ */
320
+ verifyBinding(sessionId, proxyId) {
321
+ if (!this.sessionProxyMap.has(sessionId)) {
322
+ return false;
323
+ }
324
+
325
+ const boundProxyId = this.sessionProxyMap.get(sessionId);
326
+ return boundProxyId === proxyId;
327
+ }
328
+
329
+ /**
330
+ * Get proxy config for browser launch
331
+ * @param {Object} proxy - Proxy object
332
+ * @returns {Object} - Puppeteer proxy config
333
+ */
334
+ getProxyLaunchArgs(proxy) {
335
+ if (!proxy) return [];
336
+
337
+ const args = [`--proxy-server=${proxy.url}`];
338
+
339
+ // Add proxy bypass list if configured
340
+ if (this.config.bypassList && this.config.bypassList.length > 0) {
341
+ args.push(`--proxy-bypass-list=${this.config.bypassList.join(';')}`);
342
+ }
343
+
344
+ return args;
345
+ }
346
+
347
+ /**
348
+ * Authenticate proxy on page
349
+ * @param {Page} page - Puppeteer page
350
+ * @param {Object} proxy - Proxy object
351
+ */
352
+ async authenticateProxy(page, proxy) {
353
+ if (proxy.username && proxy.password) {
354
+ await page.authenticate({
355
+ username: proxy.username,
356
+ password: proxy.password
357
+ });
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Check proxy health
363
+ * @param {string} proxyId - Proxy identifier
364
+ * @returns {Promise<boolean>}
365
+ */
366
+ async checkProxyHealth(proxyId) {
367
+ const proxy = this.proxies.find(p => p.id === proxyId);
368
+ if (!proxy) return false;
369
+
370
+ const stats = this.proxyStats.get(proxyId);
371
+ if (!stats) return false;
372
+
373
+ try {
374
+ // Launch browser with this proxy
375
+ const browser = await this.plugin.puppeteer.launch({
376
+ ...this.plugin.config.launch,
377
+ args: [
378
+ ...this.plugin.config.launch.args,
379
+ ...this.getProxyLaunchArgs(proxy)
380
+ ]
381
+ });
382
+
383
+ const page = await browser.newPage();
384
+
385
+ // Authenticate if needed
386
+ await this.authenticateProxy(page, proxy);
387
+
388
+ // Try to fetch a test page
389
+ const testUrl = this.config.healthCheck?.testUrl || 'https://www.google.com';
390
+ const timeout = this.config.healthCheck?.timeout || 10000;
391
+
392
+ await page.goto(testUrl, { timeout });
393
+
394
+ await browser.close();
395
+
396
+ // Mark as healthy
397
+ stats.healthy = true;
398
+ stats.successRate = Math.min(stats.successRate + 0.1, 1.0);
399
+
400
+ this.plugin.emit('proxyManager.healthCheckPassed', {
401
+ proxyId,
402
+ url: this._maskProxyUrl(proxy.url)
403
+ });
404
+
405
+ return true;
406
+ } catch (err) {
407
+ // Mark as unhealthy
408
+ stats.healthy = false;
409
+ stats.failures++;
410
+
411
+ this.plugin.emit('proxyManager.healthCheckFailed', {
412
+ proxyId,
413
+ url: this._maskProxyUrl(proxy.url),
414
+ error: err.message
415
+ });
416
+
417
+ return false;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Run health checks on all proxies
423
+ * @returns {Promise<Object>}
424
+ */
425
+ async checkAllProxies() {
426
+ const results = {
427
+ total: this.proxies.length,
428
+ healthy: 0,
429
+ unhealthy: 0,
430
+ checks: []
431
+ };
432
+
433
+ for (const proxy of this.proxies) {
434
+ const isHealthy = await this.checkProxyHealth(proxy.id);
435
+ results.checks.push({
436
+ proxyId: proxy.id,
437
+ url: this._maskProxyUrl(proxy.url),
438
+ healthy: isHealthy
439
+ });
440
+
441
+ if (isHealthy) {
442
+ results.healthy++;
443
+ } else {
444
+ results.unhealthy++;
445
+ }
446
+ }
447
+
448
+ return results;
449
+ }
450
+
451
+ /**
452
+ * Mask proxy URL for logging (hide credentials)
453
+ * @private
454
+ */
455
+ _maskProxyUrl(url) {
456
+ try {
457
+ const parsed = new URL(url);
458
+ if (parsed.username) {
459
+ return `${parsed.protocol}//${parsed.username}:***@${parsed.host}`;
460
+ }
461
+ return url;
462
+ } catch {
463
+ return url;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Remove session-proxy binding (only for cleanup/testing)
469
+ * WARNING: This breaks immutability! Only use when deleting sessions.
470
+ * @param {string} sessionId - Session identifier
471
+ */
472
+ _removeBinding(sessionId) {
473
+ this.sessionProxyMap.delete(sessionId);
474
+ this.plugin.emit('proxyManager.bindingRemoved', { sessionId });
475
+ }
476
+ }
477
+
478
+ export default ProxyManager;