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,746 @@
1
+ /**
2
+ * PerformanceManager - Chromium Performance Metrics Collection
3
+ *
4
+ * Collects comprehensive performance data including:
5
+ * - Core Web Vitals (LCP, FID, CLS, TTFB, FCP, INP)
6
+ * - Navigation Timing API
7
+ * - Resource Timing API
8
+ * - Paint Timing API
9
+ * - Memory Usage
10
+ * - Network Waterfall
11
+ * - Lighthouse-style scoring (0-100)
12
+ *
13
+ * Usage:
14
+ * ```javascript
15
+ * const metrics = await performanceManager.collectMetrics(page);
16
+ * console.log(metrics.score); // 85/100
17
+ * console.log(metrics.coreWebVitals.lcp); // 2.5s
18
+ * ```
19
+ */
20
+ export class PerformanceManager {
21
+ constructor(plugin) {
22
+ this.plugin = plugin;
23
+ this.config = plugin.config.performance || {};
24
+
25
+ // Core Web Vitals thresholds (Google's standards)
26
+ this.thresholds = {
27
+ lcp: { good: 2500, needsImprovement: 4000 }, // Largest Contentful Paint (ms)
28
+ fid: { good: 100, needsImprovement: 300 }, // First Input Delay (ms)
29
+ cls: { good: 0.1, needsImprovement: 0.25 }, // Cumulative Layout Shift (score)
30
+ ttfb: { good: 800, needsImprovement: 1800 }, // Time to First Byte (ms)
31
+ fcp: { good: 1800, needsImprovement: 3000 }, // First Contentful Paint (ms)
32
+ inp: { good: 200, needsImprovement: 500 }, // Interaction to Next Paint (ms)
33
+ si: { good: 3400, needsImprovement: 5800 }, // Speed Index (ms)
34
+ tbt: { good: 200, needsImprovement: 600 }, // Total Blocking Time (ms)
35
+ tti: { good: 3800, needsImprovement: 7300 } // Time to Interactive (ms)
36
+ };
37
+
38
+ // Scoring weights (Lighthouse-inspired)
39
+ this.weights = {
40
+ lcp: 0.25,
41
+ fid: 0.10,
42
+ cls: 0.15,
43
+ ttfb: 0.10,
44
+ fcp: 0.10,
45
+ inp: 0.10,
46
+ tbt: 0.10,
47
+ tti: 0.10
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Collect all performance metrics from page
53
+ * @param {Page} page - Puppeteer page
54
+ * @param {Object} options - Collection options
55
+ * @returns {Promise<Object>} Performance report
56
+ */
57
+ async collectMetrics(page, options = {}) {
58
+ const {
59
+ waitForLoad = true,
60
+ collectResources = true,
61
+ collectMemory = true,
62
+ collectScreenshots = false,
63
+ customMetrics = null
64
+ } = options;
65
+
66
+ const startTime = Date.now();
67
+
68
+ try {
69
+ // Wait for page load if requested
70
+ if (waitForLoad) {
71
+ await page.waitForLoadState('load', { timeout: 30000 }).catch(() => {
72
+ // Continue even if timeout
73
+ });
74
+ }
75
+
76
+ // Inject Core Web Vitals measurement script
77
+ await this._injectWebVitalsScript(page);
78
+
79
+ // Wait for metrics to be available
80
+ await this._delay(1000);
81
+
82
+ // Collect all metrics
83
+ const [
84
+ coreWebVitals,
85
+ navigationTiming,
86
+ resourceTiming,
87
+ paintTiming,
88
+ memoryInfo
89
+ ] = await Promise.all([
90
+ this._collectCoreWebVitals(page),
91
+ this._collectNavigationTiming(page),
92
+ collectResources ? this._collectResourceTiming(page) : null,
93
+ this._collectPaintTiming(page),
94
+ collectMemory ? this._collectMemoryInfo(page) : null
95
+ ]);
96
+
97
+ // Collect custom metrics if provided
98
+ let customMetricsData = null;
99
+ if (customMetrics && typeof customMetrics === 'function') {
100
+ customMetricsData = await customMetrics(page);
101
+ }
102
+
103
+ // Calculate derived metrics
104
+ const derivedMetrics = this._calculateDerivedMetrics(navigationTiming, resourceTiming);
105
+
106
+ // Calculate scores
107
+ const scores = this._calculateScores({
108
+ ...coreWebVitals,
109
+ ...derivedMetrics
110
+ });
111
+
112
+ // Collect screenshots if requested
113
+ let screenshots = null;
114
+ if (collectScreenshots) {
115
+ screenshots = await this._collectScreenshots(page);
116
+ }
117
+
118
+ const collectionTime = Date.now() - startTime;
119
+
120
+ const report = {
121
+ url: page.url(),
122
+ timestamp: Date.now(),
123
+ collectionTime,
124
+
125
+ // Overall score (0-100)
126
+ score: scores.overall,
127
+
128
+ // Individual scores
129
+ scores: scores.individual,
130
+
131
+ // Core Web Vitals
132
+ coreWebVitals,
133
+
134
+ // Timing APIs
135
+ navigationTiming,
136
+ paintTiming,
137
+
138
+ // Resource data
139
+ resources: resourceTiming ? {
140
+ summary: this._summarizeResources(resourceTiming),
141
+ details: resourceTiming
142
+ } : null,
143
+
144
+ // Memory usage
145
+ memory: memoryInfo,
146
+
147
+ // Derived metrics
148
+ derived: derivedMetrics,
149
+
150
+ // Custom metrics
151
+ custom: customMetricsData,
152
+
153
+ // Screenshots
154
+ screenshots,
155
+
156
+ // Recommendations
157
+ recommendations: this._generateRecommendations({
158
+ ...coreWebVitals,
159
+ ...derivedMetrics
160
+ }, resourceTiming)
161
+ };
162
+
163
+ this.plugin.emit('performance.metricsCollected', {
164
+ url: page.url(),
165
+ score: report.score,
166
+ collectionTime
167
+ });
168
+
169
+ return report;
170
+
171
+ } catch (err) {
172
+ this.plugin.emit('performance.collectionFailed', {
173
+ url: page.url(),
174
+ error: err.message
175
+ });
176
+ throw err;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Inject Web Vitals measurement script
182
+ * @private
183
+ */
184
+ async _injectWebVitalsScript(page) {
185
+ await page.evaluateOnNewDocument(() => {
186
+ // Store Web Vitals metrics
187
+ window.__WEB_VITALS__ = {
188
+ lcp: null,
189
+ fid: null,
190
+ cls: null,
191
+ inp: null,
192
+ fcp: null,
193
+ ttfb: null
194
+ };
195
+
196
+ // LCP - Largest Contentful Paint
197
+ const lcpObserver = new PerformanceObserver((list) => {
198
+ const entries = list.getEntries();
199
+ const lastEntry = entries[entries.length - 1];
200
+ window.__WEB_VITALS__.lcp = lastEntry.renderTime || lastEntry.loadTime;
201
+ });
202
+ lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
203
+
204
+ // FID - First Input Delay
205
+ const fidObserver = new PerformanceObserver((list) => {
206
+ const entries = list.getEntries();
207
+ entries.forEach((entry) => {
208
+ if (!window.__WEB_VITALS__.fid) {
209
+ window.__WEB_VITALS__.fid = entry.processingStart - entry.startTime;
210
+ }
211
+ });
212
+ });
213
+ fidObserver.observe({ type: 'first-input', buffered: true });
214
+
215
+ // CLS - Cumulative Layout Shift
216
+ let clsValue = 0;
217
+ let clsEntries = [];
218
+ const clsObserver = new PerformanceObserver((list) => {
219
+ for (const entry of list.getEntries()) {
220
+ if (!entry.hadRecentInput) {
221
+ clsValue += entry.value;
222
+ clsEntries.push(entry);
223
+ }
224
+ }
225
+ window.__WEB_VITALS__.cls = clsValue;
226
+ });
227
+ clsObserver.observe({ type: 'layout-shift', buffered: true });
228
+
229
+ // INP - Interaction to Next Paint (new metric)
230
+ const inpObserver = new PerformanceObserver((list) => {
231
+ const entries = list.getEntries();
232
+ entries.forEach((entry) => {
233
+ const duration = entry.processingEnd - entry.startTime;
234
+ if (!window.__WEB_VITALS__.inp || duration > window.__WEB_VITALS__.inp) {
235
+ window.__WEB_VITALS__.inp = duration;
236
+ }
237
+ });
238
+ });
239
+ inpObserver.observe({ type: 'event', buffered: true, durationThreshold: 16 });
240
+
241
+ // Navigation Timing API v2
242
+ window.addEventListener('load', () => {
243
+ const navTiming = performance.getEntriesByType('navigation')[0];
244
+ if (navTiming) {
245
+ window.__WEB_VITALS__.ttfb = navTiming.responseStart - navTiming.requestStart;
246
+ }
247
+
248
+ // FCP - First Contentful Paint
249
+ const fcpEntry = performance.getEntriesByName('first-contentful-paint')[0];
250
+ if (fcpEntry) {
251
+ window.__WEB_VITALS__.fcp = fcpEntry.startTime;
252
+ }
253
+ });
254
+ });
255
+ }
256
+
257
+ /**
258
+ * Collect Core Web Vitals
259
+ * @private
260
+ */
261
+ async _collectCoreWebVitals(page) {
262
+ const vitals = await page.evaluate(() => {
263
+ return window.__WEB_VITALS__ || {};
264
+ });
265
+
266
+ return {
267
+ lcp: vitals.lcp || null,
268
+ fid: vitals.fid || null,
269
+ cls: vitals.cls || null,
270
+ inp: vitals.inp || null,
271
+ fcp: vitals.fcp || null,
272
+ ttfb: vitals.ttfb || null
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Collect Navigation Timing API metrics
278
+ * @private
279
+ */
280
+ async _collectNavigationTiming(page) {
281
+ return await page.evaluate(() => {
282
+ const nav = performance.getEntriesByType('navigation')[0];
283
+ if (!nav) return null;
284
+
285
+ return {
286
+ // DNS
287
+ dnsStart: nav.domainLookupStart,
288
+ dnsEnd: nav.domainLookupEnd,
289
+ dnsDuration: nav.domainLookupEnd - nav.domainLookupStart,
290
+
291
+ // TCP
292
+ tcpStart: nav.connectStart,
293
+ tcpEnd: nav.connectEnd,
294
+ tcpDuration: nav.connectEnd - nav.connectStart,
295
+
296
+ // TLS/SSL
297
+ tlsStart: nav.secureConnectionStart,
298
+ tlsDuration: nav.secureConnectionStart > 0
299
+ ? nav.connectEnd - nav.secureConnectionStart
300
+ : 0,
301
+
302
+ // Request/Response
303
+ requestStart: nav.requestStart,
304
+ responseStart: nav.responseStart,
305
+ responseEnd: nav.responseEnd,
306
+ requestDuration: nav.responseStart - nav.requestStart,
307
+ responseDuration: nav.responseEnd - nav.responseStart,
308
+
309
+ // DOM Processing
310
+ domInteractive: nav.domInteractive,
311
+ domContentLoaded: nav.domContentLoadedEventEnd,
312
+ domComplete: nav.domComplete,
313
+
314
+ // Load Events
315
+ loadEventStart: nav.loadEventStart,
316
+ loadEventEnd: nav.loadEventEnd,
317
+ loadEventDuration: nav.loadEventEnd - nav.loadEventStart,
318
+
319
+ // Total times
320
+ redirectTime: nav.redirectEnd - nav.redirectStart,
321
+ fetchTime: nav.responseEnd - nav.fetchStart,
322
+ totalTime: nav.loadEventEnd - nav.fetchStart,
323
+
324
+ // Transfer size
325
+ transferSize: nav.transferSize,
326
+ encodedBodySize: nav.encodedBodySize,
327
+ decodedBodySize: nav.decodedBodySize
328
+ };
329
+ });
330
+ }
331
+
332
+ /**
333
+ * Collect Resource Timing API metrics
334
+ * @private
335
+ */
336
+ async _collectResourceTiming(page) {
337
+ return await page.evaluate(() => {
338
+ const resources = performance.getEntriesByType('resource');
339
+
340
+ return resources.map(resource => ({
341
+ name: resource.name,
342
+ type: resource.initiatorType,
343
+ startTime: resource.startTime,
344
+ duration: resource.duration,
345
+ transferSize: resource.transferSize,
346
+ encodedBodySize: resource.encodedBodySize,
347
+ decodedBodySize: resource.decodedBodySize,
348
+
349
+ // Timing breakdown
350
+ dns: resource.domainLookupEnd - resource.domainLookupStart,
351
+ tcp: resource.connectEnd - resource.connectStart,
352
+ tls: resource.secureConnectionStart > 0
353
+ ? resource.connectEnd - resource.secureConnectionStart
354
+ : 0,
355
+ request: resource.responseStart - resource.requestStart,
356
+ response: resource.responseEnd - resource.responseStart,
357
+
358
+ // Caching
359
+ cached: resource.transferSize === 0 && resource.decodedBodySize > 0
360
+ }));
361
+ });
362
+ }
363
+
364
+ /**
365
+ * Collect Paint Timing API metrics
366
+ * @private
367
+ */
368
+ async _collectPaintTiming(page) {
369
+ return await page.evaluate(() => {
370
+ const paintEntries = performance.getEntriesByType('paint');
371
+ const result = {};
372
+
373
+ paintEntries.forEach(entry => {
374
+ result[entry.name] = entry.startTime;
375
+ });
376
+
377
+ return result;
378
+ });
379
+ }
380
+
381
+ /**
382
+ * Collect memory information
383
+ * @private
384
+ */
385
+ async _collectMemoryInfo(page) {
386
+ try {
387
+ const memoryInfo = await page.evaluate(() => {
388
+ if (performance.memory) {
389
+ return {
390
+ usedJSHeapSize: performance.memory.usedJSHeapSize,
391
+ totalJSHeapSize: performance.memory.totalJSHeapSize,
392
+ jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
393
+ usedPercent: (performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit) * 100
394
+ };
395
+ }
396
+ return null;
397
+ });
398
+
399
+ return memoryInfo;
400
+ } catch (err) {
401
+ return null;
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Calculate derived metrics
407
+ * @private
408
+ */
409
+ _calculateDerivedMetrics(navigationTiming, resourceTiming) {
410
+ if (!navigationTiming) return {};
411
+
412
+ const derived = {
413
+ // Time to Interactive (simplified calculation)
414
+ tti: navigationTiming.domInteractive,
415
+
416
+ // Total Blocking Time (simplified - would need long task API)
417
+ tbt: Math.max(0, navigationTiming.domContentLoaded - navigationTiming.domInteractive - 50),
418
+
419
+ // Speed Index (simplified approximation)
420
+ si: navigationTiming.domContentLoaded
421
+ };
422
+
423
+ // Calculate resource metrics if available
424
+ if (resourceTiming && resourceTiming.length > 0) {
425
+ const totalSize = resourceTiming.reduce((sum, r) => sum + (r.transferSize || 0), 0);
426
+ const totalRequests = resourceTiming.length;
427
+ const cachedRequests = resourceTiming.filter(r => r.cached).length;
428
+ const avgDuration = resourceTiming.reduce((sum, r) => sum + r.duration, 0) / totalRequests;
429
+
430
+ derived.resources = {
431
+ totalRequests,
432
+ totalSize,
433
+ cachedRequests,
434
+ cacheRate: cachedRequests / totalRequests,
435
+ avgDuration
436
+ };
437
+
438
+ // Breakdown by type
439
+ const byType = {};
440
+ resourceTiming.forEach(r => {
441
+ if (!byType[r.type]) {
442
+ byType[r.type] = { count: 0, size: 0, duration: 0 };
443
+ }
444
+ byType[r.type].count++;
445
+ byType[r.type].size += r.transferSize || 0;
446
+ byType[r.type].duration += r.duration;
447
+ });
448
+
449
+ derived.resourcesByType = Object.entries(byType).map(([type, data]) => ({
450
+ type,
451
+ count: data.count,
452
+ totalSize: data.size,
453
+ avgDuration: data.duration / data.count
454
+ }));
455
+ }
456
+
457
+ return derived;
458
+ }
459
+
460
+ /**
461
+ * Calculate performance scores
462
+ * @private
463
+ */
464
+ _calculateScores(metrics) {
465
+ const individual = {};
466
+ let weightedSum = 0;
467
+ let totalWeight = 0;
468
+
469
+ // Score each metric
470
+ Object.keys(this.thresholds).forEach(metric => {
471
+ const value = metrics[metric];
472
+ const threshold = this.thresholds[metric];
473
+ const weight = this.weights[metric] || 0;
474
+
475
+ if (value === null || value === undefined) {
476
+ individual[metric] = null;
477
+ return;
478
+ }
479
+
480
+ // Calculate score (0-100)
481
+ let score;
482
+ if (metric === 'cls') {
483
+ // CLS is different - lower is better, no milliseconds
484
+ if (value <= threshold.good) {
485
+ score = 100;
486
+ } else if (value <= threshold.needsImprovement) {
487
+ score = 50 + (50 * (threshold.needsImprovement - value) / (threshold.needsImprovement - threshold.good));
488
+ } else {
489
+ score = Math.max(0, 50 * (1 - (value - threshold.needsImprovement) / threshold.needsImprovement));
490
+ }
491
+ } else {
492
+ // Time-based metrics
493
+ if (value <= threshold.good) {
494
+ score = 100;
495
+ } else if (value <= threshold.needsImprovement) {
496
+ score = 50 + (50 * (threshold.needsImprovement - value) / (threshold.needsImprovement - threshold.good));
497
+ } else {
498
+ score = Math.max(0, 50 * (1 - (value - threshold.needsImprovement) / threshold.needsImprovement));
499
+ }
500
+ }
501
+
502
+ individual[metric] = Math.round(score);
503
+
504
+ // Add to weighted sum
505
+ if (weight > 0) {
506
+ weightedSum += score * weight;
507
+ totalWeight += weight;
508
+ }
509
+ });
510
+
511
+ // Calculate overall score
512
+ const overall = totalWeight > 0 ? Math.round(weightedSum / totalWeight) : null;
513
+
514
+ return {
515
+ overall,
516
+ individual
517
+ };
518
+ }
519
+
520
+ /**
521
+ * Summarize resource timing data
522
+ * @private
523
+ */
524
+ _summarizeResources(resources) {
525
+ const summary = {
526
+ total: resources.length,
527
+ byType: {},
528
+ totalSize: 0,
529
+ totalDuration: 0,
530
+ cached: 0,
531
+ slowest: []
532
+ };
533
+
534
+ // Group by type
535
+ resources.forEach(resource => {
536
+ const type = resource.type || 'other';
537
+ if (!summary.byType[type]) {
538
+ summary.byType[type] = { count: 0, size: 0, duration: 0 };
539
+ }
540
+ summary.byType[type].count++;
541
+ summary.byType[type].size += resource.transferSize || 0;
542
+ summary.byType[type].duration += resource.duration;
543
+
544
+ summary.totalSize += resource.transferSize || 0;
545
+ summary.totalDuration += resource.duration;
546
+ if (resource.cached) summary.cached++;
547
+ });
548
+
549
+ // Find slowest resources
550
+ summary.slowest = resources
551
+ .sort((a, b) => b.duration - a.duration)
552
+ .slice(0, 10)
553
+ .map(r => ({
554
+ name: r.name,
555
+ type: r.type,
556
+ duration: Math.round(r.duration),
557
+ size: r.transferSize
558
+ }));
559
+
560
+ return summary;
561
+ }
562
+
563
+ /**
564
+ * Generate performance recommendations
565
+ * @private
566
+ */
567
+ _generateRecommendations(metrics, resources) {
568
+ const recommendations = [];
569
+
570
+ // LCP recommendations
571
+ if (metrics.lcp && metrics.lcp > this.thresholds.lcp.needsImprovement) {
572
+ recommendations.push({
573
+ metric: 'lcp',
574
+ severity: 'high',
575
+ message: `LCP is ${Math.round(metrics.lcp)}ms (target: <${this.thresholds.lcp.good}ms)`,
576
+ suggestions: [
577
+ 'Optimize largest image/element loading',
578
+ 'Use lazy loading for below-the-fold content',
579
+ 'Reduce server response times',
580
+ 'Use CDN for static assets'
581
+ ]
582
+ });
583
+ }
584
+
585
+ // FID recommendations
586
+ if (metrics.fid && metrics.fid > this.thresholds.fid.needsImprovement) {
587
+ recommendations.push({
588
+ metric: 'fid',
589
+ severity: 'high',
590
+ message: `FID is ${Math.round(metrics.fid)}ms (target: <${this.thresholds.fid.good}ms)`,
591
+ suggestions: [
592
+ 'Break up long JavaScript tasks',
593
+ 'Use web workers for heavy computations',
594
+ 'Defer non-critical JavaScript',
595
+ 'Reduce JavaScript execution time'
596
+ ]
597
+ });
598
+ }
599
+
600
+ // CLS recommendations
601
+ if (metrics.cls && metrics.cls > this.thresholds.cls.needsImprovement) {
602
+ recommendations.push({
603
+ metric: 'cls',
604
+ severity: 'high',
605
+ message: `CLS is ${metrics.cls.toFixed(3)} (target: <${this.thresholds.cls.good})`,
606
+ suggestions: [
607
+ 'Set explicit width/height on images and videos',
608
+ 'Reserve space for ads and embeds',
609
+ 'Avoid inserting content above existing content',
610
+ 'Use transform animations instead of layout-triggering properties'
611
+ ]
612
+ });
613
+ }
614
+
615
+ // TTFB recommendations
616
+ if (metrics.ttfb && metrics.ttfb > this.thresholds.ttfb.needsImprovement) {
617
+ recommendations.push({
618
+ metric: 'ttfb',
619
+ severity: 'medium',
620
+ message: `TTFB is ${Math.round(metrics.ttfb)}ms (target: <${this.thresholds.ttfb.good}ms)`,
621
+ suggestions: [
622
+ 'Optimize server processing time',
623
+ 'Use server-side caching',
624
+ 'Use a CDN',
625
+ 'Reduce server redirects'
626
+ ]
627
+ });
628
+ }
629
+
630
+ // Resource recommendations
631
+ if (resources && resources.length > 0) {
632
+ const totalSize = resources.reduce((sum, r) => sum + (r.transferSize || 0), 0);
633
+ const uncachedSize = resources
634
+ .filter(r => !r.cached)
635
+ .reduce((sum, r) => sum + (r.transferSize || 0), 0);
636
+
637
+ if (totalSize > 5 * 1024 * 1024) { // 5MB
638
+ recommendations.push({
639
+ metric: 'resources',
640
+ severity: 'medium',
641
+ message: `Total page size is ${(totalSize / 1024 / 1024).toFixed(2)}MB`,
642
+ suggestions: [
643
+ 'Compress images and use modern formats (WebP, AVIF)',
644
+ 'Minify CSS and JavaScript',
645
+ 'Remove unused code',
646
+ 'Use code splitting'
647
+ ]
648
+ });
649
+ }
650
+
651
+ const cacheRate = (resources.length - resources.filter(r => !r.cached).length) / resources.length;
652
+ if (cacheRate < 0.5) {
653
+ recommendations.push({
654
+ metric: 'caching',
655
+ severity: 'low',
656
+ message: `Only ${(cacheRate * 100).toFixed(0)}% of resources are cached`,
657
+ suggestions: [
658
+ 'Set appropriate cache headers',
659
+ 'Use service workers for offline caching',
660
+ 'Implement browser caching strategy'
661
+ ]
662
+ });
663
+ }
664
+ }
665
+
666
+ return recommendations;
667
+ }
668
+
669
+ /**
670
+ * Collect screenshots at key moments
671
+ * @private
672
+ */
673
+ async _collectScreenshots(page) {
674
+ try {
675
+ const screenshots = {
676
+ final: await page.screenshot({ encoding: 'base64' })
677
+ };
678
+
679
+ return screenshots;
680
+ } catch (err) {
681
+ return null;
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Compare two performance reports
687
+ * @param {Object} baseline - Baseline report
688
+ * @param {Object} current - Current report
689
+ * @returns {Object} Comparison result
690
+ */
691
+ compareReports(baseline, current) {
692
+ const comparison = {
693
+ timestamp: Date.now(),
694
+ baseline: {
695
+ url: baseline.url,
696
+ timestamp: baseline.timestamp,
697
+ score: baseline.score
698
+ },
699
+ current: {
700
+ url: current.url,
701
+ timestamp: current.timestamp,
702
+ score: current.score
703
+ },
704
+ scoreDelta: current.score - baseline.score,
705
+ improvements: [],
706
+ regressions: []
707
+ };
708
+
709
+ // Compare Core Web Vitals
710
+ Object.keys(baseline.coreWebVitals).forEach(metric => {
711
+ const baselineValue = baseline.coreWebVitals[metric];
712
+ const currentValue = current.coreWebVitals[metric];
713
+
714
+ if (baselineValue && currentValue) {
715
+ const delta = currentValue - baselineValue;
716
+ const percentChange = (delta / baselineValue) * 100;
717
+
718
+ const change = {
719
+ metric,
720
+ baseline: baselineValue,
721
+ current: currentValue,
722
+ delta,
723
+ percentChange: percentChange.toFixed(2)
724
+ };
725
+
726
+ if (delta < 0) {
727
+ comparison.improvements.push(change);
728
+ } else if (delta > 0) {
729
+ comparison.regressions.push(change);
730
+ }
731
+ }
732
+ });
733
+
734
+ return comparison;
735
+ }
736
+
737
+ /**
738
+ * Delay helper
739
+ * @private
740
+ */
741
+ async _delay(ms) {
742
+ return new Promise(resolve => setTimeout(resolve, ms));
743
+ }
744
+ }
745
+
746
+ export default PerformanceManager;