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() }