131 lines
3.3 KiB
JavaScript
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()
|
|
}
|