s3db.js 13.6.1 → 14.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -15
- package/dist/s3db.cjs +72446 -39022
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72172 -38790
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +85 -50
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/index.js +168 -40
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -0,0 +1,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;
|