This commit is contained in:
dela
2026-02-21 18:27:49 +08:00
parent 0ac4b23f07
commit 5dc86ccfbf
270 changed files with 49508 additions and 4636 deletions

View File

@@ -1,150 +1,170 @@
'use strict';
/**
* Performance Mock
*
* Timing and performance metrics for fingerprinting.
* P0: Performance mock
* hsw 检测timing / timeOrigin / getEntriesByType('resource') / getEntriesByType('navigation')
*/
export function createPerformance(fingerprint = {}) {
const timeOrigin = Date.now() - (fingerprint.uptime || 10000);
const entries = [];
const { createNative } = require('./native');
return {
timeOrigin,
const NAV_START = Date.now() - 1200;
now() {
return Date.now() - timeOrigin;
},
const timingData = {
navigationStart: NAV_START,
fetchStart: NAV_START + 11,
domainLookupStart: NAV_START + 11,
domainLookupEnd: NAV_START + 11,
connectStart: NAV_START + 11,
secureConnectionStart: NAV_START + 11,
connectEnd: NAV_START + 11,
requestStart: NAV_START + 37,
responseStart: NAV_START + 47,
responseEnd: NAV_START + 114,
domLoading: NAV_START + 203,
domInteractive: NAV_START + 399,
domContentLoadedEventStart: NAV_START + 399,
domContentLoadedEventEnd: NAV_START + 399,
domComplete: NAV_START + 399,
loadEventStart: NAV_START + 399,
loadEventEnd: NAV_START + 399,
redirectStart: 0,
redirectEnd: 0,
unloadEventStart: 0,
unloadEventEnd: 0,
};
// Timing (deprecated but still used)
timing: {
navigationStart: timeOrigin,
unloadEventStart: 0,
unloadEventEnd: 0,
redirectStart: 0,
redirectEnd: 0,
fetchStart: timeOrigin + 1,
domainLookupStart: timeOrigin + 2,
domainLookupEnd: timeOrigin + 10,
connectStart: timeOrigin + 10,
connectEnd: timeOrigin + 50,
secureConnectionStart: timeOrigin + 20,
requestStart: timeOrigin + 50,
responseStart: timeOrigin + 100,
responseEnd: timeOrigin + 200,
domLoading: timeOrigin + 200,
domInteractive: timeOrigin + 500,
domContentLoadedEventStart: timeOrigin + 500,
domContentLoadedEventEnd: timeOrigin + 510,
domComplete: timeOrigin + 1000,
loadEventStart: timeOrigin + 1000,
loadEventEnd: timeOrigin + 1010,
},
// 模拟 resource 条目hsw 会查 checksiteconfig 请求痕迹)
const resourceEntries = [
{
name: 'https://api.hcaptcha.com/checksiteconfig?v=xxx&host=b.stripecdn.com&sitekey=xxx&sc=1&swa=1&spst=1',
entryType: 'resource',
initiatorType: 'xmlhttprequest',
startTime: 399.2,
duration: 643.1,
fetchStart: 399.2,
responseEnd: 1042.3,
transferSize: 0,
encodedBodySize: 0,
decodedBodySize: 0,
responseStatus: 200,
deliveryType: '',
nextHopProtocol: '',
contentEncoding: 'br',
workerStart: 0,
redirectStart: 0,
redirectEnd: 0,
domainLookupStart: 0,
domainLookupEnd: 0,
connectStart: 0,
secureConnectionStart: 0,
connectEnd: 0,
requestStart: 0,
responseStart: 0,
firstInterimResponseStart: 0,
finalResponseHeadersStart: 0, // P2 要求的字段
serverTiming: [],
renderBlockingStatus: 'non-blocking',
},
{
name: 'https://newassets.hcaptcha.com/c/xxx/hsw.js',
entryType: 'resource',
initiatorType: 'script',
deliveryType: 'cache',
nextHopProtocol: 'h2',
startTime: 1043.8,
duration: 5.7,
fetchStart: 1043.8,
domainLookupStart: 1043.8,
domainLookupEnd: 1043.8,
connectStart: 1043.8,
secureConnectionStart: 1043.8,
connectEnd: 1043.8,
requestStart: 1044.6,
responseStart: 1044.6,
firstInterimResponseStart: 1044.6,
finalResponseHeadersStart: 0,
responseEnd: 1049.5,
transferSize: 0,
encodedBodySize: 359059,
decodedBodySize: 829689,
responseStatus: 200,
contentEncoding: 'gzip',
workerStart: 0,
redirectStart: 0,
redirectEnd: 0,
serverTiming: [],
renderBlockingStatus: 'non-blocking',
},
];
// Navigation (deprecated)
navigation: {
type: 0, // TYPE_NAVIGATE
redirectCount: 0,
},
// 模拟 navigation 条目
const navigationEntry = {
name: 'https://newassets.hcaptcha.com/captcha/v1/xxx/static/hcaptcha.html',
entryType: 'navigation',
initiatorType: 'navigation',
deliveryType: 'cache',
nextHopProtocol: 'h2',
startTime: 0,
duration: 399.9,
fetchStart: 11.6,
domainLookupStart: 11.6,
domainLookupEnd: 11.6,
connectStart: 11.6,
secureConnectionStart: 11.6,
connectEnd: 11.6,
requestStart: 37.6,
responseStart: 47.4,
firstInterimResponseStart: 47.4,
finalResponseHeadersStart: 0,
responseEnd: 114.2,
transferSize: 0,
encodedBodySize: 167487,
decodedBodySize: 567885,
responseStatus: 200,
redirectStart: 0,
redirectEnd: 0,
unloadEventStart: 0,
unloadEventEnd: 0,
domInteractive: 399.4,
domContentLoadedEventStart: 399.5,
domContentLoadedEventEnd: 399.5,
domComplete: 399.8,
loadEventStart: 399.9,
loadEventEnd: 399.9,
type: 'navigate',
redirectCount: 0,
activationStart: 0,
criticalCHRestart: 0,
notRestoredReasons: null,
confidence: null,
serverTiming: [],
workerStart: 0,
contentEncoding: 'br',
renderBlockingStatus: 'non-blocking',
};
// Memory (Chrome-specific)
memory: {
jsHeapSizeLimit: 4294705152,
totalJSHeapSize: 35000000,
usedJSHeapSize: 25000000,
},
const performanceMock = {
timeOrigin: NAV_START,
timing: timingData,
navigation: { type: 0, redirectCount: 0 },
// Event counts (Chrome)
eventCounts: {
size: 0,
get: () => 0,
has: () => false,
keys: () => [][Symbol.iterator](),
values: () => [][Symbol.iterator](),
entries: () => [][Symbol.iterator](),
forEach: () => {},
[Symbol.iterator]: () => [][Symbol.iterator](),
},
getEntriesByType: createNative('getEntriesByType', function (type) {
if (type === 'resource') return resourceEntries;
if (type === 'navigation') return [navigationEntry];
return [];
}),
// Entry methods
getEntries() {
return [...entries];
},
getEntriesByName: createNative('getEntriesByName', function (name) {
return resourceEntries.filter(e => e.name === name);
}),
getEntriesByType(type) {
return entries.filter(e => e.entryType === type);
},
now: createNative('now', function () {
return Date.now() - NAV_START;
}),
getEntriesByName(name, type) {
return entries.filter(e =>
e.name === name && (!type || e.entryType === type)
);
},
mark: createNative('mark', function () {}),
measure: createNative('measure', function () {}),
clearMarks: createNative('clearMarks', function () {}),
clearMeasures: createNative('clearMeasures', function () {}),
};
// Marks and measures
mark(name, options) {
const entry = {
name,
entryType: 'mark',
startTime: this.now(),
duration: 0,
detail: options?.detail || null,
};
entries.push(entry);
return entry;
},
measure(name, startMark, endMark) {
const startTime = typeof startMark === 'string'
? (entries.find(e => e.name === startMark)?.startTime || 0)
: (startMark?.start || 0);
const endTime = typeof endMark === 'string'
? (entries.find(e => e.name === endMark)?.startTime || this.now())
: (endMark?.end || this.now());
const entry = {
name,
entryType: 'measure',
startTime,
duration: endTime - startTime,
};
entries.push(entry);
return entry;
},
clearMarks(name) {
if (name) {
const idx = entries.findIndex(e => e.name === name && e.entryType === 'mark');
if (idx > -1) entries.splice(idx, 1);
} else {
entries.splice(0, entries.length, ...entries.filter(e => e.entryType !== 'mark'));
}
},
clearMeasures(name) {
if (name) {
const idx = entries.findIndex(e => e.name === name && e.entryType === 'measure');
if (idx > -1) entries.splice(idx, 1);
} else {
entries.splice(0, entries.length, ...entries.filter(e => e.entryType !== 'measure'));
}
},
clearResourceTimings() {
entries.splice(0, entries.length, ...entries.filter(e => e.entryType !== 'resource'));
},
setResourceTimingBufferSize() {},
// Observer
observe() {},
// JSON
toJSON() {
return {
timeOrigin: this.timeOrigin,
timing: this.timing,
navigation: this.navigation,
};
},
};
}
module.exports = performanceMock;