aslan-h5/src/utils/runtimeSecurity.js
2026-04-21 16:42:29 +08:00

131 lines
3.3 KiB
JavaScript

import { isDebugMode } from './env.js'
import { getCurrentHashPath, isPublicPath } from '@/config/security.js'
const DEVTOOLS_WIDTH_THRESHOLD = 160
const SECURITY_REDIRECT = '/#/not_app?message=Please%20open%20this%20page%20inside%20the%20official%20app'
function isEditableElement(target) {
return Boolean(
target?.closest?.(
'input, textarea, select, [contenteditable=""], [contenteditable="true"], [data-allow-browser-tools]',
),
)
}
function shouldProtectCurrentPage() {
return !isPublicPath(getCurrentHashPath())
}
function hasAppBridge() {
return Boolean(window.app || window.webkit || window.FlutterPageControl)
}
function isSuspiciousAutomation() {
const ua = navigator.userAgent || ''
return Boolean(
navigator.webdriver ||
window.__playwright__binding__ ||
window.__nightmare ||
window.callPhantom ||
/HeadlessChrome|PhantomJS|puppeteer|Playwright/i.test(ua),
)
}
function isDevtoolsOpen() {
return (
window.outerWidth - window.innerWidth > DEVTOOLS_WIDTH_THRESHOLD ||
window.outerHeight - window.innerHeight > DEVTOOLS_WIDTH_THRESHOLD
)
}
function redirectToNotApp() {
if (window.location.hash.startsWith('#/not_app')) {
return
}
window.location.replace(SECURITY_REDIRECT)
}
function guardShortcut(event) {
if (!shouldProtectCurrentPage() || isEditableElement(event.target)) {
return
}
const key = event.key?.toLowerCase()
const withCtrlOrMeta = event.ctrlKey || event.metaKey
const withShift = event.shiftKey
if (
event.key === 'F12' ||
(withCtrlOrMeta && withShift && ['i', 'j', 'c'].includes(key)) ||
(withCtrlOrMeta && ['u', 's'].includes(key))
) {
event.preventDefault()
event.stopPropagation()
}
}
function guardPointerEvent(event) {
if (!shouldProtectCurrentPage() || isEditableElement(event.target)) {
return
}
event.preventDefault()
event.stopPropagation()
}
function guardSelectionEvent(event) {
if (!shouldProtectCurrentPage() || isEditableElement(event.target)) {
return
}
event.preventDefault()
}
function syncSecurityState() {
const shouldBlur = shouldProtectCurrentPage() && !hasAppBridge() && isDevtoolsOpen()
document.documentElement.classList.toggle('security-devtools-open', shouldBlur)
}
function injectSecurityStyle() {
if (document.getElementById('runtime-security-style')) {
return
}
const style = document.createElement('style')
style.id = 'runtime-security-style'
style.textContent = `
html.security-devtools-open body {
filter: blur(10px);
pointer-events: none;
user-select: none;
}
`
document.head.appendChild(style)
}
export function installRuntimeSecurity() {
if (isDebugMode()) {
return
}
injectSecurityStyle()
if (shouldProtectCurrentPage() && isSuspiciousAutomation() && !hasAppBridge()) {
redirectToNotApp()
return
}
document.addEventListener('keydown', guardShortcut, true)
document.addEventListener('contextmenu', guardPointerEvent, true)
document.addEventListener('dragstart', guardPointerEvent, true)
document.addEventListener('copy', guardSelectionEvent, true)
document.addEventListener('selectstart', guardSelectionEvent, true)
window.addEventListener('resize', syncSecurityState, { passive: true })
window.addEventListener('hashchange', syncSecurityState, { passive: true })
syncSecurityState()
}