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.
Files changed (189) hide show
  1. package/README.md +56 -15
  2. package/dist/s3db.cjs +72446 -39022
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72172 -38790
  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 +85 -50
  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/route-context.js +601 -0
  34. package/src/plugins/api/index.js +168 -40
  35. package/src/plugins/api/routes/auth-routes.js +198 -30
  36. package/src/plugins/api/routes/resource-routes.js +19 -4
  37. package/src/plugins/api/server/health-manager.class.js +163 -0
  38. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  39. package/src/plugins/api/server/router.class.js +472 -0
  40. package/src/plugins/api/server.js +280 -1303
  41. package/src/plugins/api/utils/custom-routes.js +17 -5
  42. package/src/plugins/api/utils/guards.js +76 -17
  43. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  44. package/src/plugins/api/utils/openapi-generator.js +7 -6
  45. package/src/plugins/audit.plugin.js +30 -8
  46. package/src/plugins/backup.plugin.js +110 -14
  47. package/src/plugins/cache/cache.class.js +22 -5
  48. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  49. package/src/plugins/cache/memory-cache.class.js +211 -57
  50. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  51. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  52. package/src/plugins/cache/redis-cache.class.js +552 -0
  53. package/src/plugins/cache/s3-cache.class.js +17 -8
  54. package/src/plugins/cache.plugin.js +176 -61
  55. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  56. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  57. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  58. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  59. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  60. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  62. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/index.js +29 -8
  66. package/src/plugins/cloud-inventory/registry.js +64 -42
  67. package/src/plugins/cloud-inventory.plugin.js +240 -138
  68. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  69. package/src/plugins/concerns/resource-names.js +100 -0
  70. package/src/plugins/consumers/index.js +10 -2
  71. package/src/plugins/consumers/sqs-consumer.js +12 -2
  72. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  73. package/src/plugins/cookie-farm.errors.js +73 -0
  74. package/src/plugins/cookie-farm.plugin.js +869 -0
  75. package/src/plugins/costs.plugin.js +7 -1
  76. package/src/plugins/eventual-consistency/analytics.js +94 -19
  77. package/src/plugins/eventual-consistency/config.js +15 -7
  78. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  79. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  80. package/src/plugins/eventual-consistency/helpers.js +39 -14
  81. package/src/plugins/eventual-consistency/install.js +21 -2
  82. package/src/plugins/eventual-consistency/utils.js +32 -10
  83. package/src/plugins/fulltext.plugin.js +38 -11
  84. package/src/plugins/geo.plugin.js +61 -9
  85. package/src/plugins/identity/concerns/config.js +61 -0
  86. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  87. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  88. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  89. package/src/plugins/identity/concerns/token-generator.js +29 -4
  90. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  91. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  92. package/src/plugins/identity/drivers/index.js +18 -0
  93. package/src/plugins/identity/drivers/password-driver.js +122 -0
  94. package/src/plugins/identity/email-service.js +17 -2
  95. package/src/plugins/identity/index.js +413 -69
  96. package/src/plugins/identity/oauth2-server.js +413 -30
  97. package/src/plugins/identity/oidc-discovery.js +16 -8
  98. package/src/plugins/identity/rsa-keys.js +115 -35
  99. package/src/plugins/identity/server.js +166 -45
  100. package/src/plugins/identity/session-manager.js +53 -7
  101. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  102. package/src/plugins/identity/ui/routes.js +363 -255
  103. package/src/plugins/importer/index.js +153 -20
  104. package/src/plugins/index.js +9 -2
  105. package/src/plugins/kubernetes-inventory/index.js +6 -0
  106. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  107. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  108. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  109. package/src/plugins/metrics.plugin.js +64 -16
  110. package/src/plugins/ml/base-model.class.js +25 -15
  111. package/src/plugins/ml/regression-model.class.js +1 -1
  112. package/src/plugins/ml.errors.js +57 -25
  113. package/src/plugins/ml.plugin.js +28 -4
  114. package/src/plugins/namespace.js +210 -0
  115. package/src/plugins/plugin.class.js +180 -8
  116. package/src/plugins/puppeteer/console-monitor.js +729 -0
  117. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  118. package/src/plugins/puppeteer/network-monitor.js +816 -0
  119. package/src/plugins/puppeteer/performance-manager.js +746 -0
  120. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  121. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  122. package/src/plugins/puppeteer.errors.js +81 -0
  123. package/src/plugins/puppeteer.plugin.js +1327 -0
  124. package/src/plugins/queue-consumer.plugin.js +69 -14
  125. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  126. package/src/plugins/recon/concerns/command-runner.js +148 -0
  127. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  128. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  129. package/src/plugins/recon/concerns/process-manager.js +338 -0
  130. package/src/plugins/recon/concerns/report-generator.js +478 -0
  131. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  132. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  133. package/src/plugins/recon/config/defaults.js +321 -0
  134. package/src/plugins/recon/config/resources.js +370 -0
  135. package/src/plugins/recon/index.js +778 -0
  136. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  137. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  138. package/src/plugins/recon/managers/storage-manager.js +745 -0
  139. package/src/plugins/recon/managers/target-manager.js +274 -0
  140. package/src/plugins/recon/stages/asn-stage.js +314 -0
  141. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  142. package/src/plugins/recon/stages/dns-stage.js +107 -0
  143. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  144. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  145. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  146. package/src/plugins/recon/stages/http-stage.js +89 -0
  147. package/src/plugins/recon/stages/latency-stage.js +148 -0
  148. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  149. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  150. package/src/plugins/recon/stages/ports-stage.js +169 -0
  151. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  152. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  153. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  154. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  155. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  156. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  157. package/src/plugins/recon/stages/whois-stage.js +349 -0
  158. package/src/plugins/recon.plugin.js +75 -0
  159. package/src/plugins/recon.plugin.js.backup +2635 -0
  160. package/src/plugins/relation.errors.js +87 -14
  161. package/src/plugins/replicator.plugin.js +514 -137
  162. package/src/plugins/replicators/base-replicator.class.js +89 -1
  163. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  164. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  165. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  166. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  167. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  168. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  169. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  170. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  171. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  172. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  173. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  174. package/src/plugins/s3-queue.plugin.js +464 -65
  175. package/src/plugins/scheduler.plugin.js +20 -6
  176. package/src/plugins/state-machine.plugin.js +40 -9
  177. package/src/plugins/tfstate/base-driver.js +28 -4
  178. package/src/plugins/tfstate/errors.js +65 -10
  179. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  180. package/src/plugins/tfstate/index.js +163 -90
  181. package/src/plugins/tfstate/s3-driver.js +64 -6
  182. package/src/plugins/ttl.plugin.js +72 -17
  183. package/src/plugins/vector/distances.js +18 -12
  184. package/src/plugins/vector/kmeans.js +26 -4
  185. package/src/resource.class.js +115 -19
  186. package/src/testing/factory.class.js +20 -3
  187. package/src/testing/seeder.class.js +7 -1
  188. package/src/clients/memory-client.md +0 -917
  189. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,869 @@
1
+ import { Plugin } from './plugin.class.js';
2
+ import { PuppeteerPlugin } from './puppeteer.plugin.js';
3
+ import { requirePluginDependency } from './concerns/plugin-dependencies.js';
4
+ import tryFn from '../concerns/try-fn.js';
5
+ import { resolveResourceName } from './concerns/resource-names.js';
6
+ import { CookieFarmError, PersonaNotFoundError } from './cookie-farm.errors.js';
7
+
8
+ /**
9
+ * CookieFarmPlugin - Persona Factory for Professional Web Scraping
10
+ *
11
+ * Creates and manages "personas" - complete browser identities with:
12
+ * - Cookies (farmed and aged)
13
+ * - Proxy binding (immutable)
14
+ * - User agent
15
+ * - Viewport configuration
16
+ * - Reputation score
17
+ * - Quality rating
18
+ *
19
+ * Use Cases:
20
+ * - Generate hundreds of trusted personas
21
+ * - Warmup sessions automatically
22
+ * - Rotate based on quality/age
23
+ * - Export/import for scaling
24
+ * - Integration with CrawlerPlugin
25
+ *
26
+ * @extends Plugin
27
+ */
28
+ export class CookieFarmPlugin extends Plugin {
29
+ constructor(options = {}) {
30
+ super(options);
31
+
32
+ const resourceNamesOption = options.resourceNames || {};
33
+
34
+ // Default configuration
35
+ this.config = {
36
+ // Persona generation
37
+ generation: {
38
+ count: 10, // Initial personas to generate
39
+ proxies: [], // List of proxies to use
40
+ userAgentStrategy: 'random', // 'random' | 'desktop-only' | 'mobile-only'
41
+ viewportStrategy: 'varied', // 'varied' | 'fixed' | 'desktop-only'
42
+ ...options.generation
43
+ },
44
+
45
+ // Warmup process
46
+ warmup: {
47
+ enabled: true,
48
+ sites: [
49
+ 'https://www.google.com',
50
+ 'https://www.youtube.com',
51
+ 'https://www.wikipedia.org',
52
+ 'https://www.reddit.com',
53
+ 'https://www.amazon.com'
54
+ ],
55
+ sitesPerPersona: 5,
56
+ randomOrder: true,
57
+ timePerSite: { min: 10000, max: 20000 },
58
+ interactions: {
59
+ scroll: true,
60
+ hover: true,
61
+ click: false // Safer - avoid accidental navigation
62
+ },
63
+ ...options.warmup
64
+ },
65
+
66
+ // Quality scoring
67
+ quality: {
68
+ enabled: true,
69
+ factors: {
70
+ age: 0.3, // 30% weight
71
+ successRate: 0.4, // 40% weight
72
+ requestCount: 0.2, // 20% weight
73
+ warmupCompleted: 0.1 // 10% weight
74
+ },
75
+ thresholds: {
76
+ high: 0.8, // >= 80% = high quality
77
+ medium: 0.5, // >= 50% = medium quality
78
+ low: 0 // < 50% = low quality
79
+ },
80
+ ...options.quality
81
+ },
82
+
83
+ // Rotation strategy
84
+ rotation: {
85
+ enabled: true,
86
+ maxAge: 86400000, // 24 hours
87
+ maxRequests: 200,
88
+ minQualityScore: 0.3,
89
+ retireOnFailureRate: 0.3, // Retire if success rate < 30%
90
+ ...options.rotation
91
+ },
92
+
93
+ // Storage
94
+ storage: {
95
+ resource: 'cookie_farm_personas',
96
+ encrypt: true,
97
+ ...options.storage
98
+ },
99
+
100
+ // Export/Import
101
+ export: {
102
+ format: 'json', // 'json' | 'csv'
103
+ includeCredentials: false, // Mask proxy credentials
104
+ ...options.export
105
+ },
106
+
107
+ // Stealth mode (anti-detection)
108
+ stealth: {
109
+ enabled: true,
110
+ timingProfile: 'normal', // 'very-slow' | 'slow' | 'normal' | 'fast'
111
+ consistentFingerprint: true, // Maintain consistent fingerprint per persona
112
+ executeJSChallenges: true, // Auto-solve JS challenges
113
+ humanBehavior: true, // Simulate mouse/scroll/typing
114
+ requestPacing: true, // Throttle requests to avoid rate limits
115
+ geoConsistency: true, // Match timezone/language to proxy geo
116
+ ...options.stealth
117
+ }
118
+ };
119
+
120
+ this._storageResourceDescriptor = {
121
+ defaultName: 'plg_cookie_farm_personas',
122
+ override: resourceNamesOption.personas || options.storage?.resource
123
+ };
124
+ this.config.storage.resource = this._resolveStorageResourceName();
125
+
126
+ // Internal state
127
+ this.puppeteerPlugin = null;
128
+ this.stealthManager = null;
129
+ this.personaPool = new Map(); // personaId -> persona object
130
+ this.initialized = false;
131
+ }
132
+
133
+ _resolveStorageResourceName() {
134
+ return resolveResourceName('cookiefarm', this._storageResourceDescriptor, {
135
+ namespace: this.namespace
136
+ });
137
+ }
138
+
139
+ onNamespaceChanged() {
140
+ if (this.config?.storage) {
141
+ this.config.storage.resource = this._resolveStorageResourceName();
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Install plugin and validate dependencies
147
+ */
148
+ async onInstall() {
149
+ // Validate PuppeteerPlugin is installed (namespaced aware)
150
+ const puppeteerPlugin = this._findPuppeteerDependency();
151
+
152
+ if (!puppeteerPlugin) {
153
+ throw new CookieFarmError('PuppeteerPlugin is required before installing CookieFarmPlugin', {
154
+ pluginName: 'CookieFarmPlugin',
155
+ operation: 'onInstall',
156
+ statusCode: 400,
157
+ retriable: false,
158
+ suggestion: 'Install PuppeteerPlugin in db configuration before adding CookieFarmPlugin.'
159
+ });
160
+ }
161
+
162
+ this.puppeteerPlugin = puppeteerPlugin;
163
+
164
+ // Create personas storage resource
165
+ await this._setupPersonaStorage();
166
+
167
+ this.emit('cookieFarm.installed');
168
+ }
169
+
170
+ /**
171
+ * Locate PuppeteerPlugin dependency respecting namespaces
172
+ * @private
173
+ */
174
+ _findPuppeteerDependency() {
175
+ const registries = [
176
+ this.database?.plugins,
177
+ this.database?.pluginRegistry,
178
+ Array.isArray(this.database?.pluginList) ? this.database.pluginList : null
179
+ ].filter(Boolean);
180
+
181
+ const candidates = [];
182
+ for (const registry of registries) {
183
+ if (Array.isArray(registry)) {
184
+ candidates.push(...registry);
185
+ } else {
186
+ candidates.push(...Object.values(registry));
187
+ }
188
+ }
189
+
190
+ if (candidates.length === 0) {
191
+ return null;
192
+ }
193
+
194
+ const sameNamespace = candidates.find(
195
+ plugin => plugin instanceof PuppeteerPlugin &&
196
+ (plugin.namespace === this.namespace || !this.namespace)
197
+ );
198
+ if (sameNamespace) return sameNamespace;
199
+
200
+ return candidates.find(plugin => plugin instanceof PuppeteerPlugin) || null;
201
+ }
202
+
203
+ /**
204
+ * Start plugin
205
+ */
206
+ async onStart() {
207
+ if (this.initialized) return;
208
+
209
+ // Initialize StealthManager if enabled
210
+ if (this.config.stealth.enabled) {
211
+ const { StealthManager } = await import('./puppeteer/stealth-manager.js');
212
+ this.stealthManager = new StealthManager(this);
213
+ this.emit('cookieFarm.stealthEnabled');
214
+ }
215
+
216
+ // Load existing personas from storage
217
+ await this._loadPersonaPool();
218
+
219
+ // Generate initial personas if pool is empty
220
+ if (this.personaPool.size === 0 && this.config.generation.count > 0) {
221
+ this.emit('cookieFarm.generatingInitialPersonas', {
222
+ count: this.config.generation.count
223
+ });
224
+
225
+ await this.generatePersonas(this.config.generation.count);
226
+ }
227
+
228
+ this.initialized = true;
229
+ this.emit('cookieFarm.started', {
230
+ personaCount: this.personaPool.size,
231
+ stealthEnabled: this.config.stealth.enabled
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Stop plugin
237
+ */
238
+ async onStop() {
239
+ this.initialized = false;
240
+ this.emit('cookieFarm.stopped');
241
+ }
242
+
243
+ /**
244
+ * Uninstall plugin
245
+ */
246
+ async onUninstall(options = {}) {
247
+ await this.onStop();
248
+ this.emit('cookieFarm.uninstalled');
249
+ }
250
+
251
+ /**
252
+ * Setup persona storage resource
253
+ * @private
254
+ */
255
+ async _setupPersonaStorage() {
256
+ const resourceName = this.config.storage.resource;
257
+
258
+ try {
259
+ await this.database.getResource(resourceName);
260
+ return;
261
+ } catch (err) {
262
+ if (!['ResourceNotFoundError', 'ResourceNotFound'].includes(err?.name)) {
263
+ throw err;
264
+ }
265
+ }
266
+
267
+ const [created, createErr] = await tryFn(() => this.database.createResource({
268
+ name: resourceName,
269
+ attributes: {
270
+ personaId: 'string|required',
271
+ sessionId: 'string|required',
272
+ proxyId: 'string|optional',
273
+ userAgent: 'string|required',
274
+ viewport: {
275
+ width: 'number|required',
276
+ height: 'number|required',
277
+ deviceScaleFactor: 'number'
278
+ },
279
+ cookies: 'array',
280
+ fingerprint: {
281
+ proxy: 'string',
282
+ userAgent: 'string',
283
+ viewport: 'string'
284
+ },
285
+ reputation: {
286
+ successCount: 'number',
287
+ failCount: 'number',
288
+ successRate: 'number',
289
+ totalRequests: 'number'
290
+ },
291
+ quality: {
292
+ score: 'number',
293
+ rating: 'string',
294
+ lastCalculated: 'number'
295
+ },
296
+ metadata: {
297
+ createdAt: 'number',
298
+ lastUsed: 'number',
299
+ expiresAt: 'number',
300
+ age: 'number',
301
+ warmupCompleted: 'boolean',
302
+ retired: 'boolean'
303
+ }
304
+ },
305
+ timestamps: true,
306
+ behavior: 'body-only',
307
+ partitions: {
308
+ byQuality: {
309
+ fields: { 'quality.rating': 'string' }
310
+ },
311
+ byProxy: {
312
+ fields: { proxyId: 'string' }
313
+ },
314
+ byRetirement: {
315
+ fields: { 'metadata.retired': 'boolean' }
316
+ }
317
+ }
318
+ }));
319
+
320
+ if (!created) {
321
+ const existing = this.database.resources?.[resourceName];
322
+ if (!existing) {
323
+ throw createErr;
324
+ }
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Load persona pool from storage
330
+ * @private
331
+ */
332
+ async _loadPersonaPool() {
333
+ const storage = await this.database.getResource(this.config.storage.resource);
334
+ const personas = await storage.list({ limit: 1000 });
335
+
336
+ for (const persona of personas) {
337
+ this.personaPool.set(persona.personaId, persona);
338
+ }
339
+
340
+ this.emit('cookieFarm.personasLoaded', {
341
+ count: this.personaPool.size
342
+ });
343
+ }
344
+
345
+ /**
346
+ * Generate new personas
347
+ * @param {number} count - Number of personas to generate
348
+ * @param {Object} options - Generation options
349
+ * @returns {Promise<Array>}
350
+ */
351
+ async generatePersonas(count = 1, options = {}) {
352
+ const {
353
+ proxies = this.config.generation.proxies,
354
+ warmup = this.config.warmup.enabled
355
+ } = options;
356
+
357
+ const generatedPersonas = [];
358
+
359
+ for (let i = 0; i < count; i++) {
360
+ const persona = await this._createPersona(proxies);
361
+ generatedPersonas.push(persona);
362
+
363
+ this.emit('cookieFarm.personaCreated', {
364
+ personaId: persona.personaId,
365
+ proxyId: persona.proxyId
366
+ });
367
+
368
+ // Warmup if enabled
369
+ if (warmup) {
370
+ await this.warmupPersona(persona.personaId);
371
+ }
372
+ }
373
+
374
+ this.emit('cookieFarm.personasGenerated', {
375
+ count: generatedPersonas.length
376
+ });
377
+
378
+ return generatedPersonas;
379
+ }
380
+
381
+ /**
382
+ * Create a single persona
383
+ * @private
384
+ * @param {Array} proxies - Available proxies
385
+ * @returns {Promise<Object>}
386
+ */
387
+ async _createPersona(proxies = []) {
388
+ const personaId = `persona_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
389
+ const sessionId = `session_${personaId}`;
390
+
391
+ // Generate user agent
392
+ const userAgent = this._generateUserAgent();
393
+
394
+ // Generate viewport
395
+ const viewport = this._generateViewport();
396
+
397
+ // Create fingerprint
398
+ const fingerprint = {
399
+ userAgent: userAgent,
400
+ viewport: `${viewport.width}x${viewport.height}`,
401
+ proxy: null
402
+ };
403
+
404
+ // Assign proxy if available
405
+ let proxyId = null;
406
+ if (proxies.length > 0 && this.puppeteerPlugin.config.proxy.enabled) {
407
+ // This will create immutable binding
408
+ const proxy = this.puppeteerPlugin.proxyManager.getProxyForSession(sessionId, true);
409
+ proxyId = proxy?.id || null;
410
+ fingerprint.proxy = proxyId;
411
+ }
412
+
413
+ const persona = {
414
+ personaId,
415
+ sessionId,
416
+ proxyId,
417
+ userAgent,
418
+ viewport,
419
+ cookies: [],
420
+ fingerprint,
421
+ reputation: {
422
+ successCount: 0,
423
+ failCount: 0,
424
+ successRate: 1.0,
425
+ totalRequests: 0
426
+ },
427
+ quality: {
428
+ score: 0,
429
+ rating: 'low',
430
+ lastCalculated: Date.now()
431
+ },
432
+ metadata: {
433
+ createdAt: Date.now(),
434
+ lastUsed: null,
435
+ expiresAt: Date.now() + this.config.rotation.maxAge,
436
+ age: 0,
437
+ warmupCompleted: false,
438
+ retired: false
439
+ }
440
+ };
441
+
442
+ // Save to storage
443
+ const storage = this.database.getResource(this.config.storage.resource);
444
+ await storage.insert(persona);
445
+
446
+ // Add to pool
447
+ this.personaPool.set(personaId, persona);
448
+
449
+ return persona;
450
+ }
451
+
452
+ /**
453
+ * Generate user agent based on strategy
454
+ * @private
455
+ */
456
+ _generateUserAgent() {
457
+ const strategy = this.config.generation.userAgentStrategy;
458
+
459
+ // Use PuppeteerPlugin's user agent generation
460
+ // For now, return a default - will enhance later
461
+ const desktopAgents = [
462
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
463
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
464
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
465
+ ];
466
+
467
+ return desktopAgents[Math.floor(Math.random() * desktopAgents.length)];
468
+ }
469
+
470
+ /**
471
+ * Generate viewport based on strategy
472
+ * @private
473
+ */
474
+ _generateViewport() {
475
+ const strategy = this.config.generation.viewportStrategy;
476
+
477
+ const viewports = [
478
+ { width: 1920, height: 1080, deviceScaleFactor: 1 },
479
+ { width: 1680, height: 1050, deviceScaleFactor: 1 },
480
+ { width: 1440, height: 900, deviceScaleFactor: 1 },
481
+ { width: 1366, height: 768, deviceScaleFactor: 1 },
482
+ { width: 1280, height: 800, deviceScaleFactor: 1 }
483
+ ];
484
+
485
+ return viewports[Math.floor(Math.random() * viewports.length)];
486
+ }
487
+
488
+ /**
489
+ * Warmup a persona by visiting trusted sites
490
+ * @param {string} personaId - Persona identifier
491
+ * @returns {Promise<void>}
492
+ */
493
+ async warmupPersona(personaId) {
494
+ const persona = this.personaPool.get(personaId);
495
+ if (!persona) {
496
+ throw new PersonaNotFoundError(personaId);
497
+ }
498
+
499
+ if (persona.metadata.warmupCompleted) {
500
+ this.emit('cookieFarm.warmupSkipped', {
501
+ personaId,
502
+ reason: 'already completed'
503
+ });
504
+ return;
505
+ }
506
+
507
+ this.emit('cookieFarm.warmupStarted', { personaId });
508
+
509
+ const sites = [...this.config.warmup.sites];
510
+ const sitesToVisit = sites.slice(0, this.config.warmup.sitesPerPersona);
511
+
512
+ if (this.config.warmup.randomOrder) {
513
+ sitesToVisit.sort(() => Math.random() - 0.5);
514
+ }
515
+
516
+ for (const url of sitesToVisit) {
517
+ try {
518
+ await this._visitSite(persona, url);
519
+
520
+ this.emit('cookieFarm.warmupSiteCompleted', {
521
+ personaId,
522
+ url
523
+ });
524
+ } catch (err) {
525
+ this.emit('cookieFarm.warmupSiteFailed', {
526
+ personaId,
527
+ url,
528
+ error: err.message
529
+ });
530
+ }
531
+ }
532
+
533
+ // Mark warmup as completed
534
+ persona.metadata.warmupCompleted = true;
535
+
536
+ // Recalculate quality
537
+ await this._calculateQuality(persona);
538
+
539
+ // Save to storage
540
+ await this._savePersona(persona);
541
+
542
+ this.emit('cookieFarm.warmupCompleted', { personaId });
543
+ }
544
+
545
+ /**
546
+ * Visit a site with persona
547
+ * @private
548
+ */
549
+ async _visitSite(persona, url) {
550
+ const page = await this.puppeteerPlugin.navigate(url, {
551
+ useSession: persona.sessionId
552
+ });
553
+
554
+ // Random time on site
555
+ const timeOnSite = this.config.warmup.timePerSite.min +
556
+ Math.random() * (this.config.warmup.timePerSite.max - this.config.warmup.timePerSite.min);
557
+
558
+ // Perform interactions
559
+ if (this.config.warmup.interactions.scroll) {
560
+ await page.humanScroll({ direction: 'down' });
561
+ await this._delay(1000);
562
+ }
563
+
564
+ if (this.config.warmup.interactions.hover) {
565
+ try {
566
+ const elements = await page.$$('a, button');
567
+ if (elements.length > 0) {
568
+ const randomElement = elements[Math.floor(Math.random() * elements.length)];
569
+ await randomElement.hover();
570
+ }
571
+ } catch (err) {
572
+ // Ignore hover errors
573
+ }
574
+ }
575
+
576
+ // Wait remaining time
577
+ await this._delay(timeOnSite);
578
+
579
+ // Update reputation
580
+ persona.reputation.successCount++;
581
+ persona.reputation.totalRequests++;
582
+ persona.reputation.successRate = persona.reputation.successCount / persona.reputation.totalRequests;
583
+ persona.metadata.lastUsed = Date.now();
584
+
585
+ await page.close();
586
+ }
587
+
588
+ /**
589
+ * Calculate quality score for persona
590
+ * @private
591
+ */
592
+ async _calculateQuality(persona) {
593
+ if (!this.config.quality.enabled) {
594
+ return;
595
+ }
596
+
597
+ const factors = this.config.quality.factors;
598
+ let score = 0;
599
+
600
+ // Age factor (older = better, up to 24h)
601
+ const ageInHours = persona.metadata.age / (1000 * 60 * 60);
602
+ const ageScore = Math.min(ageInHours / 24, 1);
603
+ score += ageScore * factors.age;
604
+
605
+ // Success rate factor
606
+ score += persona.reputation.successRate * factors.successRate;
607
+
608
+ // Request count factor (more requests = better, up to maxRequests)
609
+ const requestScore = Math.min(
610
+ persona.reputation.totalRequests / this.config.rotation.maxRequests,
611
+ 1
612
+ );
613
+ score += requestScore * factors.requestCount;
614
+
615
+ // Warmup completed factor
616
+ const warmupScore = persona.metadata.warmupCompleted ? 1 : 0;
617
+ score += warmupScore * factors.warmupCompleted;
618
+
619
+ // Normalize to 0-1
620
+ persona.quality.score = score;
621
+
622
+ // Determine rating
623
+ const thresholds = this.config.quality.thresholds;
624
+ if (score >= thresholds.high) {
625
+ persona.quality.rating = 'high';
626
+ } else if (score >= thresholds.medium) {
627
+ persona.quality.rating = 'medium';
628
+ } else {
629
+ persona.quality.rating = 'low';
630
+ }
631
+
632
+ persona.quality.lastCalculated = Date.now();
633
+ }
634
+
635
+ /**
636
+ * Save persona to storage
637
+ * @private
638
+ */
639
+ async _savePersona(persona) {
640
+ persona.metadata.age = Date.now() - persona.metadata.createdAt;
641
+
642
+ const storage = this.database.getResource(this.config.storage.resource);
643
+ await storage.update(persona.id, persona);
644
+ }
645
+
646
+ /**
647
+ * Get persona by criteria
648
+ * @param {Object} criteria - Selection criteria
649
+ * @returns {Promise<Object|null>}
650
+ */
651
+ async getPersona(criteria = {}) {
652
+ const {
653
+ quality = null, // 'low' | 'medium' | 'high'
654
+ minQualityScore = 0,
655
+ proxyId = null,
656
+ excludeRetired = true
657
+ } = criteria;
658
+
659
+ const candidates = Array.from(this.personaPool.values()).filter(persona => {
660
+ // Exclude retired
661
+ if (excludeRetired && persona.metadata.retired) {
662
+ return false;
663
+ }
664
+
665
+ // Quality filter
666
+ if (quality && persona.quality.rating !== quality) {
667
+ return false;
668
+ }
669
+
670
+ // Min quality score
671
+ if (persona.quality.score < minQualityScore) {
672
+ return false;
673
+ }
674
+
675
+ // Proxy filter
676
+ if (proxyId && persona.proxyId !== proxyId) {
677
+ return false;
678
+ }
679
+
680
+ return true;
681
+ });
682
+
683
+ if (candidates.length === 0) {
684
+ return null;
685
+ }
686
+
687
+ // Sort by quality score descending
688
+ candidates.sort((a, b) => b.quality.score - a.quality.score);
689
+
690
+ return candidates[0];
691
+ }
692
+
693
+ /**
694
+ * Record persona usage
695
+ * @param {string} personaId - Persona identifier
696
+ * @param {Object} result - Usage result
697
+ */
698
+ async recordUsage(personaId, result = {}) {
699
+ const { success = true } = result;
700
+
701
+ const persona = this.personaPool.get(personaId);
702
+ if (!persona) {
703
+ throw new PersonaNotFoundError(personaId);
704
+ }
705
+
706
+ persona.reputation.totalRequests++;
707
+ persona.metadata.lastUsed = Date.now();
708
+
709
+ if (success) {
710
+ persona.reputation.successCount++;
711
+ } else {
712
+ persona.reputation.failCount++;
713
+ }
714
+
715
+ persona.reputation.successRate = persona.reputation.successCount / persona.reputation.totalRequests;
716
+
717
+ // Recalculate quality
718
+ await this._calculateQuality(persona);
719
+
720
+ // Check if should retire
721
+ if (this._shouldRetire(persona)) {
722
+ await this.retirePersona(personaId);
723
+ } else {
724
+ await this._savePersona(persona);
725
+ }
726
+
727
+ this.emit('cookieFarm.usageRecorded', {
728
+ personaId,
729
+ success,
730
+ quality: persona.quality
731
+ });
732
+ }
733
+
734
+ /**
735
+ * Check if persona should be retired
736
+ * @private
737
+ */
738
+ _shouldRetire(persona) {
739
+ if (!this.config.rotation.enabled) {
740
+ return false;
741
+ }
742
+
743
+ // Check age
744
+ if (Date.now() > persona.metadata.expiresAt) {
745
+ return true;
746
+ }
747
+
748
+ // Check request count
749
+ if (persona.reputation.totalRequests >= this.config.rotation.maxRequests) {
750
+ return true;
751
+ }
752
+
753
+ // Check success rate
754
+ if (persona.reputation.successRate < this.config.rotation.retireOnFailureRate) {
755
+ return true;
756
+ }
757
+
758
+ // Check quality score
759
+ if (persona.quality.score < this.config.rotation.minQualityScore) {
760
+ return true;
761
+ }
762
+
763
+ return false;
764
+ }
765
+
766
+ /**
767
+ * Retire a persona
768
+ * @param {string} personaId - Persona identifier
769
+ */
770
+ async retirePersona(personaId) {
771
+ const persona = this.personaPool.get(personaId);
772
+ if (!persona) {
773
+ throw new PersonaNotFoundError(personaId);
774
+ }
775
+
776
+ persona.metadata.retired = true;
777
+ await this._savePersona(persona);
778
+
779
+ this.emit('cookieFarm.personaRetired', {
780
+ personaId,
781
+ reason: 'rotation policy'
782
+ });
783
+ }
784
+
785
+ /**
786
+ * Get statistics
787
+ * @returns {Promise<Object>}
788
+ */
789
+ async getStats() {
790
+ const personas = Array.from(this.personaPool.values());
791
+
792
+ const stats = {
793
+ total: personas.length,
794
+ active: 0,
795
+ retired: 0,
796
+ byQuality: { high: 0, medium: 0, low: 0 },
797
+ byProxy: {},
798
+ warmupCompleted: 0,
799
+ averageQualityScore: 0,
800
+ averageSuccessRate: 0,
801
+ totalRequests: 0
802
+ };
803
+
804
+ let qualitySum = 0;
805
+ let successRateSum = 0;
806
+
807
+ for (const persona of personas) {
808
+ if (persona.metadata.retired) {
809
+ stats.retired++;
810
+ } else {
811
+ stats.active++;
812
+ }
813
+
814
+ stats.byQuality[persona.quality.rating]++;
815
+
816
+ if (persona.proxyId) {
817
+ stats.byProxy[persona.proxyId] = (stats.byProxy[persona.proxyId] || 0) + 1;
818
+ }
819
+
820
+ if (persona.metadata.warmupCompleted) {
821
+ stats.warmupCompleted++;
822
+ }
823
+
824
+ qualitySum += persona.quality.score;
825
+ successRateSum += persona.reputation.successRate;
826
+ stats.totalRequests += persona.reputation.totalRequests;
827
+ }
828
+
829
+ if (personas.length > 0) {
830
+ stats.averageQualityScore = qualitySum / personas.length;
831
+ stats.averageSuccessRate = successRateSum / personas.length;
832
+ }
833
+
834
+ return stats;
835
+ }
836
+
837
+ /**
838
+ * Export personas
839
+ * @param {Object} options - Export options
840
+ * @returns {Promise<Array>}
841
+ */
842
+ async exportPersonas(options = {}) {
843
+ const { includeRetired = false, format = this.config.export.format } = options;
844
+
845
+ const personas = Array.from(this.personaPool.values())
846
+ .filter(persona => includeRetired || !persona.metadata.retired);
847
+
848
+ // Mask credentials if needed
849
+ if (!this.config.export.includeCredentials) {
850
+ personas.forEach(persona => {
851
+ if (persona.fingerprint.proxy) {
852
+ persona.fingerprint.proxy = persona.proxyId; // Just ID, not full URL
853
+ }
854
+ });
855
+ }
856
+
857
+ return personas;
858
+ }
859
+
860
+ /**
861
+ * Delay helper
862
+ * @private
863
+ */
864
+ async _delay(ms) {
865
+ return new Promise(resolve => setTimeout(resolve, ms));
866
+ }
867
+ }
868
+
869
+ export default CookieFarmPlugin;