Files
hcapEnv/src/sandbox/mocks/window.js
2026-02-21 18:27:49 +08:00

240 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* 总装window 沙盒
* 按 P0→P1→P2 顺序挂载所有 mock并用 Proxy 屏蔽 bot 字段
*/
const { createNative, nativeClass } = require('./native');
const { isBotKey } = require('./bot_shield');
const performanceMock = require('./performance');
const navigatorMock = require('./navigator');
const { RTCPeerConnection, OfflineAudioContext } = require('./webapi');
const { HTMLCanvasElement, CanvasRenderingContext2D } = require('./canvas');
const { cryptoMock, Storage, IDBFactory, Notification, atob, btoa } = require('./crypto');
const screenMock = require('./screen');
const HTMLDocument = require('./document');
// ── 基础 window 对象 ─────────────────────────────────────────
const _win = {
// ── P0: 核心 API ──────────────────────────────────────
performance: performanceMock,
navigator: navigatorMock,
screen: screenMock,
crypto: cryptoMock,
RTCPeerConnection,
webkitRTCPeerConnection: RTCPeerConnection,
OfflineAudioContext,
// ── P1: Canvas ────────────────────────────────────────
HTMLCanvasElement,
CanvasRenderingContext2D,
// ── P1: Storage / IDB ─────────────────────────────────
localStorage: new Storage(),
sessionStorage: new Storage(),
indexedDB: new IDBFactory(),
IDBFactory,
// ── P1: Notification ──────────────────────────────────
Notification,
// ── P1: atob / btoa ───────────────────────────────────
atob,
btoa,
// ── P1: Document ──────────────────────────────────────
document: new HTMLDocument(),
HTMLDocument,
// ── P2: 移动端触摸 → 桌面不存在 ──────────────────────
// ontouchstart: 不定义Proxy 返回 undefined
// ── 基础 JS 全局 ─────────────────────────────────────
Promise,
Object,
Array,
Function,
Number,
String,
Boolean,
Symbol,
Date,
RegExp,
Error,
Math,
JSON,
parseInt,
parseFloat,
isNaN,
isFinite,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,
escape,
unescape,
eval,
undefined,
Infinity,
NaN,
globalThis: null, // 在 Proxy 建好后回填
// ── 定时器Node 原生) ───────────────────────────────
setTimeout,
clearTimeout,
setInterval,
clearInterval,
queueMicrotask,
// ── 其他常见 window 属性 ──────────────────────────────
location: {
href: 'https://newassets.hcaptcha.com/captcha/v1/xxx/static/hcaptcha.html',
origin: 'https://newassets.hcaptcha.com',
protocol: 'https:',
host: 'newassets.hcaptcha.com',
hostname: 'newassets.hcaptcha.com',
port: '',
pathname: '/captcha/v1/xxx/static/hcaptcha.html',
search: '',
hash: '',
ancestorOrigins: { 0: 'https://b.stripecdn.com', 1: 'https://js.stripe.com', length: 2 },
},
innerWidth: 530,
innerHeight: 915,
outerWidth: 530,
outerHeight: 915,
devicePixelRatio: 2,
screenX: 0,
screenY: 0,
screenLeft: 0,
screenTop: 0,
scrollX: 0,
scrollY: 0,
pageXOffset: 0,
pageYOffset: 0,
closed: false,
name: '',
status: '',
opener: null,
parent: null, // 回填
top: null, // 回填
self: null, // 回填
frames: null, // 回填
length: 0,
isSecureContext: true,
crossOriginIsolated: false,
originAgentCluster: false,
history: {
length: 1,
state: null,
scrollRestoration: 'auto',
go: createNative('go', function () {}),
back: createNative('back', function () {}),
forward: createNative('forward', function () {}),
pushState: createNative('pushState', function () {}),
replaceState: createNative('replaceState', function () {}),
},
fetch: createNative('fetch', function (url, opts) {
// 沙盒里一般不真正发请求,返回 resolved 空 response
return Promise.resolve({
ok: true, status: 200,
json: () => Promise.resolve({}),
text: () => Promise.resolve(''),
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
});
}),
Request: createNative('Request', function (url, opts) { this.url = url; this.method = opts?.method || 'GET'; }),
Response: createNative('Response', function (body, opts) { this.status = opts?.status || 200; }),
Headers: createNative('Headers', function () { this._h = {}; }),
URL: createNative('URL', function (url, base) {
const u = new (require('url').URL)(url, base);
Object.assign(this, u);
}),
URLSearchParams,
addEventListener: createNative('addEventListener', function () {}),
removeEventListener: createNative('removeEventListener', function () {}),
dispatchEvent: createNative('dispatchEvent', function () { return true; }),
postMessage: createNative('postMessage', function () {}),
alert: createNative('alert', function () {}),
confirm: createNative('confirm', function () { return false; }),
prompt: createNative('prompt', function () { return null; }),
requestAnimationFrame: createNative('requestAnimationFrame', function (cb) { return setTimeout(cb, 16); }),
cancelAnimationFrame: createNative('cancelAnimationFrame', function (id) { clearTimeout(id); }),
requestIdleCallback: createNative('requestIdleCallback', function (cb) { return setTimeout(() => cb({ timeRemaining: () => 50, didTimeout: false }), 1); }),
cancelIdleCallback: createNative('cancelIdleCallback', function (id) { clearTimeout(id); }),
getComputedStyle: createNative('getComputedStyle', function () {
return new Proxy({}, { get: (_, p) => p === 'getPropertyValue' ? (() => '') : '' });
}),
structuredClone: createNative('structuredClone', (v) => JSON.parse(JSON.stringify(v))),
TextEncoder,
TextDecoder,
Uint8Array,
Int8Array,
Uint16Array,
Int16Array,
Uint32Array,
Int32Array,
Uint8ClampedArray,
Float32Array,
Float64Array,
ArrayBuffer,
DataView,
Map,
Set,
WeakMap,
WeakSet,
Proxy,
Reflect,
BigInt,
Symbol,
WebAssembly,
};
// ── 建 Proxy屏蔽 bot 字段 + 回填自引用 ────────────────────
const windowProxy = new Proxy(_win, {
get(target, prop) {
if (isBotKey(prop)) return undefined; // 🚨 bot 字段全部返回 undefined
const val = target[prop];
if (val === null && ['self','window','frames','parent','top','globalThis'].includes(prop)) {
return windowProxy;
}
return val;
},
has(target, prop) {
if (isBotKey(prop)) return false; // 拦截 'webdriver' in window
return prop in target;
},
set(target, prop, val) {
if (isBotKey(prop)) return true; // 静默丢弃 bot 字段的写入
target[prop] = val;
return true;
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(k => !isBotKey(k));
},
});
// 回填自引用
_win.self = windowProxy;
_win.window = windowProxy;
_win.globalThis = windowProxy;
_win.frames = windowProxy;
_win.parent = windowProxy;
_win.top = windowProxy;
module.exports = windowProxy;