mirror of
https://github.com/dyzulk/twinpath-hotspot-themes.git
synced 2026-01-26 05:25:40 +07:00
282 lines
9.2 KiB
JavaScript
282 lines
9.2 KiB
JavaScript
let html5QrCode;
|
|
let scannedUrl = "";
|
|
|
|
function safePause() {
|
|
try {
|
|
if (html5QrCode && html5QrCode.isScanning) {
|
|
html5QrCode.pause();
|
|
}
|
|
} catch (e) {
|
|
console.warn("SafePause: Scanner already paused or not scanning", e);
|
|
}
|
|
}
|
|
|
|
function safeResume() {
|
|
try {
|
|
if (html5QrCode && html5QrCode.isScanning) {
|
|
html5QrCode.resume();
|
|
}
|
|
} catch (e) {
|
|
console.warn("SafeResume: Scanner not paused or not scanning", e);
|
|
}
|
|
}
|
|
|
|
function handleDecodedText(decodedText) {
|
|
console.log(`Scan result: ${decodedText}`);
|
|
|
|
let username = "";
|
|
let password = "";
|
|
let isUnauthorized = false;
|
|
let blockReason = ""; // Track why it was blocked
|
|
scannedUrl = "";
|
|
|
|
// Check if result is a URL
|
|
try {
|
|
if (decodedText.startsWith('http://') || decodedText.startsWith('https://')) {
|
|
const url = new URL(decodedText);
|
|
const hostname = url.hostname;
|
|
const currentHostname = window.location.hostname;
|
|
|
|
// SECURITY CHECK 1: Domain Whitelist
|
|
const isAllowed = (hostname === currentHostname) || (brandConfig.allowedDomains && brandConfig.allowedDomains.some(domain =>
|
|
hostname === domain || hostname.endsWith('.' + domain)
|
|
));
|
|
|
|
if (isAllowed) {
|
|
// SECURITY CHECK 2: MikroTik Standards (Must have at least username)
|
|
const searchParams = url.search || (decodedText.includes('?') ? '?' + decodedText.split('?')[1] : '');
|
|
const params = new URLSearchParams(searchParams);
|
|
|
|
if (params.has('username')) {
|
|
scannedUrl = decodedText; // Approved for redirection
|
|
username = params.get('username');
|
|
if (params.has('password')) {
|
|
password = params.get('password');
|
|
}
|
|
} else {
|
|
isUnauthorized = true;
|
|
blockReason = getTranslation('qr_err_invalid_url');
|
|
}
|
|
} else {
|
|
// ILLEGAL DOMAIN: Strict blocking
|
|
console.warn(`Blocked unauthorized domain: ${hostname}`);
|
|
isUnauthorized = true;
|
|
blockReason = getTranslation('qr_err_unauthorized') + `: ${hostname}`;
|
|
}
|
|
} else {
|
|
// NOT A URL: Block plain text / WiFi SSIDs per requirement
|
|
isUnauthorized = true;
|
|
blockReason = getTranslation('qr_err_invalid_content');
|
|
console.warn("Blocked non-URL content:", decodedText);
|
|
}
|
|
} catch (e) {
|
|
console.error("Error parsing QR URL:", e);
|
|
isUnauthorized = true;
|
|
blockReason = getTranslation('qr_err_parse');
|
|
}
|
|
|
|
// Fill inputs (only if authorized)
|
|
if (!isUnauthorized && username) {
|
|
if (activeScannerMode === 'check') {
|
|
console.log("Check mode: Direct fetch for", username);
|
|
closeQR();
|
|
checkVoucher(username);
|
|
return;
|
|
}
|
|
|
|
const voucherInput = document.getElementById('voucher-input');
|
|
const passField = document.getElementById('voucher-pass');
|
|
if (voucherInput) voucherInput.value = username;
|
|
if (passField) passField.value = password || username;
|
|
}
|
|
|
|
// Show confirmation overlay (Login Mode only)
|
|
const overlay = document.getElementById('qr-confirm-overlay');
|
|
const confirmUser = document.getElementById('confirm-user');
|
|
const connectBtn = document.querySelector('button[onclick="proceedSubmit()"]');
|
|
const confirmMsg = document.querySelector('[data-i18n="confirm_msg"]');
|
|
|
|
if (overlay && confirmUser) {
|
|
if (isUnauthorized) {
|
|
confirmUser.innerHTML = `<span style="color: #ff4d4d;">Blocked: ${blockReason}</span>`;
|
|
if (connectBtn) connectBtn.style.display = 'none';
|
|
} else {
|
|
confirmUser.innerText = username;
|
|
if (connectBtn) {
|
|
connectBtn.style.display = 'block';
|
|
connectBtn.innerText = getTranslation('connect_btn');
|
|
if (confirmMsg) confirmMsg.innerText = getTranslation('confirm_msg');
|
|
}
|
|
}
|
|
overlay.classList.remove('hidden');
|
|
}
|
|
|
|
// Pause camera scanning while confirming
|
|
safePause();
|
|
}
|
|
|
|
function cancelConfirm() {
|
|
const overlay = document.getElementById('qr-confirm-overlay');
|
|
if (overlay) overlay.classList.add('hidden');
|
|
|
|
safeResume();
|
|
}
|
|
|
|
function proceedSubmit() {
|
|
// Determine mode from active tab
|
|
const activeTab = document.querySelector('.tab-btn.active');
|
|
const isInfoMode = activeTab && activeTab.onclick.toString().includes('info');
|
|
|
|
if (isInfoMode) {
|
|
const username = document.getElementById('confirm-user').innerText;
|
|
checkVoucher(username);
|
|
closeQR();
|
|
return;
|
|
}
|
|
|
|
// If it's a URL, redirect directly
|
|
if (scannedUrl) {
|
|
console.log("Redirecting to scanned URL:", scannedUrl);
|
|
window.location.href = scannedUrl;
|
|
return;
|
|
}
|
|
|
|
// Switch to voucher mode for manual codes
|
|
setMode('voucher');
|
|
|
|
// Sync values to the actual form fields
|
|
const voucherInput = document.getElementById('voucher-input');
|
|
const voucherPass = document.getElementById('voucher-pass');
|
|
const form = document.login;
|
|
|
|
if (form && voucherInput) {
|
|
form.username.value = voucherInput.value;
|
|
form.password.value = voucherPass ? voucherPass.value : voucherInput.value;
|
|
}
|
|
|
|
// Close scanner
|
|
closeQR();
|
|
|
|
// Submit
|
|
const submitBtn = document.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.click();
|
|
}
|
|
}
|
|
|
|
function scanFromFile(event) {
|
|
try {
|
|
const file = event.target.files[0];
|
|
if (!file) {
|
|
console.log("No file selected.");
|
|
return;
|
|
}
|
|
|
|
console.log("File selected:", file.name, file.type, file.size);
|
|
|
|
// Ensure library is available
|
|
if (typeof Html5Qrcode === 'undefined') {
|
|
alert("QR Scanner library not loaded. Please wait or refresh.");
|
|
return;
|
|
}
|
|
|
|
// Pause camera gracefully
|
|
safePause();
|
|
|
|
// Show a loading state
|
|
const confirmUser = document.getElementById('confirm-user');
|
|
if (confirmUser) confirmUser.innerText = "Scanning file...";
|
|
|
|
// Hide overlay if it was open
|
|
const overlay = document.getElementById('qr-confirm-overlay');
|
|
if (overlay) overlay.classList.add('hidden');
|
|
|
|
// Reuse instance if possible, or create temporary one for file
|
|
const fileScanner = new Html5Qrcode("qr-file-reader");
|
|
|
|
fileScanner.scanFile(file, true)
|
|
.then(decodedText => {
|
|
console.log("Success scanning file:", decodedText);
|
|
handleDecodedText(decodedText);
|
|
fileScanner.clear(); // Cleanup
|
|
})
|
|
.catch(err => {
|
|
console.error(`Error scanning file: ${err}`);
|
|
let msg = "No QR Code found.";
|
|
if (typeof err === "string" && err.includes("not found")) {
|
|
msg = "QR Code not detected. Try a clearer or closer photo.";
|
|
}
|
|
alert(msg);
|
|
|
|
if (confirmUser) confirmUser.innerText = "";
|
|
|
|
// Resume camera
|
|
if (html5QrCode && html5QrCode.isScanning) {
|
|
html5QrCode.resume();
|
|
}
|
|
fileScanner.clear(); // Cleanup
|
|
});
|
|
|
|
// Reset input so searching for the same file again triggers change
|
|
event.target.value = "";
|
|
|
|
} catch (e) {
|
|
console.error("Fatal error in scanFromFile:", e);
|
|
const errorMsg = e.message || JSON.stringify(e) || e;
|
|
alert("An error occurred while opening the file: " + errorMsg);
|
|
|
|
// Try to resume camera if it crashed here
|
|
if (html5QrCode && html5QrCode.isScanning) {
|
|
html5QrCode.resume();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
let activeScannerMode = 'login'; // 'login' or 'check'
|
|
|
|
async function openQR(mode = 'login') {
|
|
activeScannerMode = mode;
|
|
console.log(`Opening QR Scanner in ${mode} mode`);
|
|
|
|
// Hide confirmation overlay when opening scanner to prevent legacy results from showing
|
|
document.getElementById('qr-confirm-overlay').classList.add('hidden');
|
|
|
|
const modal = document.getElementById('qr-scanner-modal');
|
|
modal.style.display = 'flex';
|
|
|
|
if (!html5QrCode) {
|
|
html5QrCode = new Html5Qrcode("reader");
|
|
}
|
|
|
|
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
|
|
|
|
html5QrCode.start(
|
|
{ facingMode: "environment" },
|
|
config,
|
|
handleDecodedText
|
|
).catch(err => {
|
|
console.error("Scanner start error:", err);
|
|
});
|
|
}
|
|
|
|
function closeQR() {
|
|
const modal = document.getElementById('qr-scanner-modal');
|
|
const overlay = document.getElementById('qr-confirm-overlay');
|
|
|
|
if (modal) modal.style.display = 'none';
|
|
if (overlay) overlay.classList.add('hidden');
|
|
|
|
if (html5QrCode) {
|
|
html5QrCode.stop().then(() => {
|
|
console.log("Scanner stopped");
|
|
}).catch((err) => {
|
|
// Ignore error if already stopped
|
|
});
|
|
}
|
|
}
|
|
|
|
|