Rename to hkt.sh

This commit is contained in:
mango
2026-03-21 01:10:53 +08:00
parent 76a263d0f9
commit 8f1171fe99
6676 changed files with 1724268 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
'use strict'
const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')
const withUtils = require('../_utils/withUtils')
/**
* Mock the `chrome.loadTimes` function if not available (e.g. when running headless).
* It's a deprecated (but unfortunately still existing) chrome specific API to fetch browser timings and connection info.
*
* Internally chromium switched the implementation to use the WebPerformance API,
* so we can do the same to create a fully functional mock. :-)
*
* Note: We're using the deprecated PerformanceTiming API instead of the new Navigation Timing Level 2 API on purpopse.
*
* @see https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated
* @see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming
* @see https://source.chromium.org/chromium/chromium/src/+/master:chrome/renderer/loadtimes_extension_bindings.cc;l=124?q=loadtimes&ss=chromium
* @see `chrome.csi` evasion
*
*/
class Plugin extends PuppeteerExtraPlugin {
constructor(opts = {}) {
super(opts)
}
get name() {
return 'stealth/evasions/chrome.loadTimes'
}
async onPageCreated(page) {
await withUtils(page).evaluateOnNewDocument(
(utils, { opts }) => {
if (!window.chrome) {
// Use the exact property descriptor found in headful Chrome
// fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`
Object.defineProperty(window, 'chrome', {
writable: true,
enumerable: true,
configurable: false, // note!
value: {} // We'll extend that later
})
}
// That means we're running headful and don't need to mock anything
if ('loadTimes' in window.chrome) {
return // Nothing to do here
}
// Check that the Navigation Timing API v1 + v2 is available, we need that
if (
!window.performance ||
!window.performance.timing ||
!window.PerformancePaintTiming
) {
return
}
const { performance } = window
// Some stuff is not available on about:blank as it requires a navigation to occur,
// let's harden the code to not fail then:
const ntEntryFallback = {
nextHopProtocol: 'h2',
type: 'other'
}
// The API exposes some funky info regarding the connection
const protocolInfo = {
get connectionInfo() {
const ntEntry =
performance.getEntriesByType('navigation')[0] || ntEntryFallback
return ntEntry.nextHopProtocol
},
get npnNegotiatedProtocol() {
// NPN is deprecated in favor of ALPN, but this implementation returns the
// HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.
const ntEntry =
performance.getEntriesByType('navigation')[0] || ntEntryFallback
return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)
? ntEntry.nextHopProtocol
: 'unknown'
},
get navigationType() {
const ntEntry =
performance.getEntriesByType('navigation')[0] || ntEntryFallback
return ntEntry.type
},
get wasAlternateProtocolAvailable() {
// The Alternate-Protocol header is deprecated in favor of Alt-Svc
// (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this
// should always return false.
return false
},
get wasFetchedViaSpdy() {
// SPDY is deprecated in favor of HTTP/2, but this implementation returns
// true for HTTP/2 or HTTP2+QUIC/39 as well.
const ntEntry =
performance.getEntriesByType('navigation')[0] || ntEntryFallback
return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)
},
get wasNpnNegotiated() {
// NPN is deprecated in favor of ALPN, but this implementation returns true
// for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.
const ntEntry =
performance.getEntriesByType('navigation')[0] || ntEntryFallback
return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)
}
}
const { timing } = window.performance
// Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3
function toFixed(num, fixed) {
var re = new RegExp('^-?\\d+(?:.\\d{0,' + (fixed || -1) + '})?')
return num.toString().match(re)[0]
}
const timingInfo = {
get firstPaintAfterLoadTime() {
// This was never actually implemented and always returns 0.
return 0
},
get requestTime() {
return timing.navigationStart / 1000
},
get startLoadTime() {
return timing.navigationStart / 1000
},
get commitLoadTime() {
return timing.responseStart / 1000
},
get finishDocumentLoadTime() {
return timing.domContentLoadedEventEnd / 1000
},
get finishLoadTime() {
return timing.loadEventEnd / 1000
},
get firstPaintTime() {
const fpEntry = performance.getEntriesByType('paint')[0] || {
startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`)
}
return toFixed(
(fpEntry.startTime + performance.timeOrigin) / 1000,
3
)
}
}
window.chrome.loadTimes = function() {
return {
...protocolInfo,
...timingInfo
}
}
utils.patchToString(window.chrome.loadTimes)
},
{
opts: this.opts
}
)
}
}
module.exports = function(pluginConfig) {
return new Plugin(pluginConfig)
}

View File

@@ -0,0 +1,63 @@
const test = require('ava')
const { vanillaPuppeteer, addExtra } = require('../../test/util')
const Plugin = require('.')
/* global chrome */
test('stealth: will add functional chrome.loadTimes function mock', async t => {
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin({}))
const browser = await puppeteer.launch({ headless: true })
const page = await browser.newPage()
const results = await page.evaluate(() => {
const loadTimes = window.chrome.loadTimes()
return {
loadTimes: {
exists: window.chrome && 'loadTimes' in window.chrome,
toString: chrome.loadTimes.toString()
},
dataOK: {
connectionInfo: 'connectionInfo' in loadTimes,
npnNegotiatedProtocol: 'npnNegotiatedProtocol' in loadTimes,
navigationType: 'navigationType' in loadTimes,
wasAlternateProtocolAvailable:
'wasAlternateProtocolAvailable' in loadTimes,
wasFetchedViaSpdy: 'wasFetchedViaSpdy' in loadTimes,
wasNpnNegotiated: 'wasNpnNegotiated' in loadTimes,
firstPaintAfterLoadTime: 'firstPaintAfterLoadTime' in loadTimes,
requestTime: 'requestTime' in loadTimes,
startLoadTime: 'startLoadTime' in loadTimes,
commitLoadTime: 'commitLoadTime' in loadTimes,
finishDocumentLoadTime: 'finishDocumentLoadTime' in loadTimes,
finishLoadTime: 'finishLoadTime' in loadTimes,
firstPaintTime: 'firstPaintTime' in loadTimes
}
}
})
t.deepEqual(results, {
loadTimes: {
exists: true,
toString: 'function () { [native code] }'
},
dataOK: {
commitLoadTime: true,
connectionInfo: true,
finishDocumentLoadTime: true,
finishLoadTime: true,
firstPaintAfterLoadTime: true,
firstPaintTime: true,
navigationType: true,
npnNegotiatedProtocol: true,
requestTime: true,
startLoadTime: true,
wasAlternateProtocolAvailable: true,
wasFetchedViaSpdy: true,
wasNpnNegotiated: true
}
})
})

View File

@@ -0,0 +1,4 @@
{
"private": true,
"main": "index.js"
}

View File

@@ -0,0 +1,28 @@
## API
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
#### Table of Contents
- [class: Plugin](#class-plugin)
### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/chrome.loadTimes/index.js#L23-L164)
- `opts` (optional, default `{}`)
**Extends: PuppeteerExtraPlugin**
Mock the `chrome.loadTimes` function if not available (e.g. when running headless).
It's a deprecated (but unfortunately still existing) chrome specific API to fetch browser timings and connection info.
Internally chromium switched the implementation to use the WebPerformance API,
so we can do the same to create a fully functional mock. :-)
Note: We're using the deprecated PerformanceTiming API instead of the new Navigation Timing Level 2 API on purpopse.
- **See: <https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated>**
- **See: <https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming>**
- **See: <https://source.chromium.org/chromium/chromium/src/+/master:chrome/renderer/loadtimes_extension_bindings.cc;l=124?q=loadtimes&ss=chromium>**
- **See: `chrome.csi` evasion**
---