This commit is contained in:
Flatlogic Bot 2026-02-01 17:51:10 +00:00
parent afb69ac6af
commit 2cefb14500
2 changed files with 293 additions and 179 deletions

View File

@ -2,39 +2,132 @@ const express = require('express');
const router = express.Router();
const axios = require('axios');
const Helpers = require('../helpers');
const qs = require('qs');
router.all('/', Helpers.wrapAsync(async (req, res) => {
const targetUrl = req.query.url;
if (!targetUrl) return res.status(400).send('URL is required');
const proxyHandler = Helpers.wrapAsync(async (req, res) => {
let targetUrl = req.query.url;
// 1. Handle additional query parameters
// If we have ?url=https://site.com&q=foo, we want to fetch https://site.com?q=foo
if (targetUrl) {
try {
const urlObj = new URL(targetUrl);
Object.keys(req.query).forEach(key => {
if (key !== 'url') {
urlObj.searchParams.set(key, req.query[key]);
}
});
targetUrl = urlObj.href;
} catch (e) {
// Might be relative, handle below
}
}
// 2. Handle Path-based URLs (e.g. /api/proxy/https://google.com)
if (!targetUrl || !targetUrl.startsWith('http')) {
const originalUrl = req.originalUrl;
const marker = '/api/proxy/';
if (originalUrl.includes(marker)) {
const remainder = originalUrl.split(marker)[1];
if (remainder && remainder.startsWith('http')) {
targetUrl = remainder;
} else if (remainder && req.headers.referer) {
// 3. Smart Path Resolution via Referer
try {
const refUrl = new URL(req.headers.referer);
const parentTarget = refUrl.searchParams.get('url');
if (parentTarget) {
targetUrl = new URL(remainder, parentTarget).href;
}
} catch (e) {}
}
}
}
if (!targetUrl || !targetUrl.startsWith('http')) {
console.warn('[Proxy] No valid target URL found for:', req.originalUrl);
// If we can't find a URL, but we have a referer, maybe the browser followed a relative link
// that the proxy didn't catch. Try to redirect them back into the proxy.
if (req.headers.referer && req.headers.referer.includes('/api/proxy')) {
try {
const refUrl = new URL(req.headers.referer);
const parentTarget = refUrl.searchParams.get('url');
if (parentTarget) {
const absoluteUrl = new URL(req.url.replace('/api/proxy', ''), parentTarget).href;
return res.redirect(`/api/proxy?url=${encodeURIComponent(absoluteUrl)}`);
}
} catch (e) {}
}
return res.status(400).send(`
<div style="background:#1a1a1a; color:#ccc; padding:20px; font-family:sans-serif; border-radius:8px; margin:20px; border:1px solid #333;">
<h2 style="color:#ff4d4d; margin-top:0;">URL Required</h2>
<p>The stealth proxy needs a target URL to work.</p>
<div style="background:#000; padding:15px; border-radius:4px; border:1px solid #444;">
<code>${req.originalUrl}</code>
</div>
<p style="font-size:14px; color:#888; margin-top:20px;">Try entering a full URL in the address bar, e.g., <b>https://google.com</b></p>
<button onclick="window.history.back()" style="background:#333; color:#fff; border:none; padding:8px 16px; border-radius:4px; cursor:pointer;">Go Back</button>
</div>
`);
}
try {
const method = req.method;
const headers = { ...req.headers };
const headers = {};
// Remove host and other headers that might cause issues
delete headers.host;
delete headers.origin;
delete headers.referer;
// Essential headers to forward
const forwardHeaders = [
'accept', 'accept-language', 'accept-encoding', 'user-agent', 'content-type', 'range', 'authorization'
];
// Set a common User-Agent
headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36';
forwardHeaders.forEach(h => {
if (req.headers[h]) {
headers[h] = req.headers[h];
}
});
const response = await axios({
// Ensure a realistic User-Agent
if (!headers['user-agent']) {
headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36';
}
// Strip Referer and Origin to avoid target site security blocks
delete headers['referer'];
delete headers['origin'];
console.log(`[Proxy] ${method} -> ${targetUrl}`);
const axiosConfig = {
url: targetUrl,
method: method,
headers: headers,
data: req.body,
params: req.params,
responseType: 'arraybuffer',
validateStatus: () => true,
validateStatus: () => true,
maxRedirects: 10,
timeout: 15000,
});
timeout: 30000,
decompress: true
};
const contentType = response.headers['content-type'] || '';
if (method !== 'GET' && method !== 'HEAD' && req.body) {
if (headers['content-type'] && headers['content-type'].includes('application/x-www-form-urlencoded')) {
axiosConfig.data = typeof req.body === 'string' ? req.body : qs.stringify(req.body);
} else {
axiosConfig.data = req.body;
}
}
const response = await axios(axiosConfig);
const finalUrl = response.request.res.responseUrl || targetUrl;
// Copy headers but strip security and encoding ones
const contentType = response.headers['content-type'] || '';
// Handle Redirects manually if axios didn't (though maxRedirects is 10)
if (response.status >= 300 && response.status < 400 && response.headers.location) {
const redirUrl = new URL(response.headers.location, finalUrl).href;
return res.redirect(`/api/proxy?url=${encodeURIComponent(redirUrl)}`);
}
// Forward headers from target, stripping security ones
Object.keys(response.headers).forEach(key => {
const lowerKey = key.toLowerCase();
if (![
@ -45,88 +138,147 @@ router.all('/', Helpers.wrapAsync(async (req, res) => {
'transfer-encoding',
'connection',
'strict-transport-security',
'x-content-type-options'
'x-content-type-options',
'set-cookie',
'access-control-allow-origin'
].includes(lowerKey)) {
res.setHeader(key, response.headers[key]);
}
});
// Ensure we don't have caching issues during dev
res.setHeader('Cache-Control', 'no-store');
// Force CORS for the proxy
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
let data = response.data;
if (contentType.includes('text/html')) {
let html = data.toString('utf-8');
const parsedUrl = new URL(finalUrl);
const origin = parsedUrl.origin;
const baseUrl = origin + parsedUrl.pathname;
// Inject <base> and frame-busting neutralization
let origin = '';
try {
const parsedUrl = new URL(finalUrl);
origin = parsedUrl.origin;
} catch (e) {
origin = targetUrl;
}
const headInjection = `
<base href="${origin}/">
<script>
// Neutralize frame-busting
(function() {
var originalOpen = window.open;
window.open = function(url, name, specs) {
if (!url) return originalOpen(url, name, specs);
window.location.href = "/api/proxy?url=" + encodeURIComponent(new URL(url, location.href).href);
return null;
};
Object.defineProperty(window, 'top', { get: function() { return window.self; } });
Object.defineProperty(window, 'parent', { get: function() { return window.self; } });
Object.defineProperty(document, 'referrer', { get: function() { return ''; } });
// Override location.replace and assign to stay in proxy
var originalReplace = window.location.replace;
window.location.replace = function(url) {
window.location.href = "/api/proxy?url=" + encodeURIComponent(new URL(url, location.href).href);
};
try {
var PROXY_BASE = window.location.origin + "/api/proxy?url=";
function toProxyUrl(url) {
if (!url || typeof url !== 'string') return url;
if (url.startsWith('javascript:') || url.startsWith('data:') || url.startsWith('mailto:') || url.startsWith('tel:')) return url;
try {
var absUrl = new URL(url, location.href).href;
// Don't proxy if already proxied
if (absUrl.includes("/api/proxy?url=")) return url;
return PROXY_BASE + encodeURIComponent(absUrl);
} catch(e) { return url; }
}
// Intercept window.open
var originalOpen = window.open;
window.open = function(url, name, specs) {
if (!url) return originalOpen(url, name, specs);
window.location.href = toProxyUrl(url);
return null;
};
// Frame busting protection
Object.defineProperty(window, 'top', { get: function() { return window.self; } });
Object.defineProperty(window, 'parent', { get: function() { return window.self; } });
// Intercept location changes
var originalReplace = window.location.replace;
window.location.replace = function(url) {
window.location.href = toProxyUrl(url);
};
// Intercept form submissions
window.addEventListener('submit', function(e) {
var form = e.target;
if (form.action && !form.action.includes("/api/proxy")) {
form.action = toProxyUrl(form.action);
}
}, true);
// Intercept all link clicks (dynamic links)
window.addEventListener('click', function(e) {
var target = e.target.closest('a');
if (target && target.href && !target.href.includes("/api/proxy")) {
if (target.target === '_blank' || target.target === '_parent' || target.target === '_top') {
target.target = '_self';
}
}
}, true);
// Periodic check to fix dynamically added elements
setInterval(function() {
document.querySelectorAll('a[href]:not([data-proxied]), form[action]:not([data-proxied])').forEach(function(el) {
var attr = el.tagName === 'A' ? 'href' : 'action';
var val = el.getAttribute(attr);
if (val && !val.includes('/api/proxy') && !val.startsWith('#') && !val.startsWith('javascript:')) {
el.setAttribute(attr, toProxyUrl(val));
el.setAttribute('data-proxied', 'true');
}
});
}, 2000);
} catch(e) { console.error("Proxy injection error:", e); }
})();
</script>
`;
if (html.includes('<head>')) {
html = html.replace('<head>', `<head>${headInjection}`);
} else if (html.includes('<html>')) {
html = html.replace('<html>', `<html><head>${headInjection}</head>`);
// Inject headInjection
if (html.toLowerCase().includes('<head>')) {
html = html.replace(/<head>/i, `<head>${headInjection}`);
} else if (html.toLowerCase().includes('<html>')) {
html = html.replace(/<html>/i, `<html><head>${headInjection}</head>`);
} else {
html = headInjection + html;
}
// More aggressive link and resource rewriting
// This helps with relative paths that <base> might miss in some cases
// especially in scripts or attributes we don't handle well
// Rewrite links to stay in proxy
html = html.replace(/(href|src|action)="\s*((?!#|javascript|mailto|tel|data:|https?:\/\/).*?)\s*"/gi, (match, attr, p1) => {
// Aggressive rewrite for absolute and relative URLs
html = html.replace(/(href|src|action)=["'](.*?)["']/gi, (match, attr, p1) => {
if (!p1 || p1.startsWith('#') || p1.startsWith('javascript:') || p1.startsWith('data:') || p1.startsWith('mailto:')) return match;
try {
const absoluteUrl = new URL(p1, finalUrl).href;
if (attr.toLowerCase() === 'href' || attr.toLowerCase() === 'action') {
return `${attr}="/api/proxy?url=${encodeURIComponent(absoluteUrl)}"`;
}
// For src, we might not want to proxy everything (images/scripts) as it adds load
// but if the site blocks cross-origin, we must.
// Let's proxy scripts and iframes.
if (match.toLowerCase().includes('<script') || match.toLowerCase().includes('<iframe')) {
const lowerAttr = attr.toLowerCase();
// Proxy links, forms, and scripts/iframes
if (lowerAttr === 'href' || lowerAttr === 'action' || match.toLowerCase().includes('<script') || match.toLowerCase().includes('<iframe')) {
if (p1.includes('/api/proxy')) return match;
return `${attr}="/api/proxy?url=${encodeURIComponent(absoluteUrl)}"`;
}
return match;
} catch(e) {
return match;
}
} catch(e) { return match; }
});
res.status(response.status).send(html);
} else {
// For non-HTML content (images, JS, CSS), just send it
res.status(response.status).send(data);
}
} catch (error) {
res.status(500).send(`Proxy error: ${error.message}`);
console.error(`[Proxy] Critical Error:`, error.message);
res.status(500).send(`
<div style="background:#1a1a1a; color:#ff4d4d; padding:20px; font-family:sans-serif; border-radius:8px; margin:20px; border:1px solid #333;">
<h2 style="margin-top:0;">Proxy Error</h2>
<p>Failed to load the requested page.</p>
<code style="background:#000; padding:10px; display:block; border-radius:4px;">${error.message}</code>
<button onclick="window.location.reload()" style="margin-top:15px; background:#3ea6ff; color:#fff; border:none; padding:8px 16px; border-radius:4px; cursor:pointer;">Retry</button>
</div>
`);
}
}));
});
router.all('/', proxyHandler);
router.all('/:any*', proxyHandler);
module.exports = router;

View File

@ -33,7 +33,6 @@ type AppPropsWithLayout = AppProps & {
}
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page);
const router = useRouter();
const [stepsEnabled, setStepsEnabled] = React.useState(false);
@ -57,7 +56,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
}
);
// TODO: Remove this code in future releases
React.useEffect(() => {
const allowedOrigin = (() => {
if (!document.referrer) {
@ -112,40 +110,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
}, []);
React.useEffect(() => {
// Tour is disabled by default in generated projects.
return;
const isCompleted = (stepKey: string) => {
return localStorage.getItem(`completed_${stepKey}`) === 'true';
};
if (router.pathname === '/login' && !isCompleted('loginSteps')) {
setSteps(loginSteps);
setStepName('loginSteps');
setStepsEnabled(true);
}else if (router.pathname === '/dashboard' && !isCompleted('appSteps')) {
setTimeout(() => {
setSteps(appSteps);
setStepName('appSteps');
setStepsEnabled(true);
}, 1000);
} else if (router.pathname === '/users/users-list' && !isCompleted('usersSteps')) {
setTimeout(() => {
setSteps(usersSteps);
setStepName('usersSteps');
setStepsEnabled(true);
}, 1000);
} else if (router.pathname === '/roles/roles-list' && !isCompleted('rolesSteps')) {
setTimeout(() => {
setSteps(rolesSteps);
setStepName('rolesSteps');
setStepsEnabled(true);
}, 1000);
} else {
setSteps([]);
setStepsEnabled(false);
}
}, [router.pathname]);
// Stealth search trigger: press 'g' 5 times in 5 seconds
React.useEffect(() => {
let keyPresses: number[] = [];
const handleKeyDown = (e: KeyboardEvent) => {
@ -162,34 +129,35 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
if (keyPresses.length >= 5) {
const win = window.open('about:blank', '_blank');
if (win) {
const proxyUrl = window.location.origin + '/api/proxy?url=';
const apiBase = process.env.NEXT_PUBLIC_BACK_API || '/api';
const proxyUrl = window.location.origin + apiBase + '/proxy?url=';
const content = `
<html>
<head>
<title>Zen Browser</title>
<style>
body { background: #0f0f0f; margin: 0; font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; display: flex; flex-direction: column; height: 100vh; overflow: hidden; color: #f1f1f1; }
.header { background: #212121; border-bottom: 1px solid #333; display: flex; flex-direction: column; }
body { background: #0f0f0f; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; display: flex; flex-direction: column; height: 100vh; overflow: hidden; color: #f1f1f1; }
.header { background: #1e1e1e; border-bottom: 1px solid #333; display: flex; flex-direction: column; box-shadow: 0 4px 12px rgba(0,0,0,0.5); z-index: 10; }
.tab-bar { display: flex; padding: 8px 8px 0; gap: 4px; overflow-x: auto; background: #121212; align-items: flex-end; }
.tab { padding: 8px 16px; background: #2a2a2a; border-radius: 10px 10px 0 0; cursor: pointer; display: flex; align-items: center; gap: 12px; font-size: 13px; min-width: 140px; max-width: 220px; white-space: nowrap; overflow: hidden; border: 1px solid transparent; border-bottom: none; transition: all 0.2s; color: #999; position: relative; }
.tab { padding: 8px 16px; background: #2a2a2a; border-radius: 8px 8px 0 0; cursor: pointer; display: flex; align-items: center; gap: 12px; font-size: 13px; min-width: 120px; max-width: 200px; white-space: nowrap; overflow: hidden; border: 1px solid transparent; border-bottom: none; transition: all 0.2s; color: #888; position: relative; }
.tab:hover { background: #333; color: #ccc; }
.tab.active { background: #212121; color: #fff; font-weight: 500; border-color: #333; border-bottom-color: #212121; margin-bottom: -1px; z-index: 1; }
.tab .close { font-size: 14px; color: #666; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: 0.2s; }
.tab.active { background: #1e1e1e; color: #fff; font-weight: 500; border-color: #333; border-bottom-color: #1e1e1e; margin-bottom: -1px; z-index: 1; border-top: 2px solid #3ea6ff; }
.tab .close { font-size: 14px; color: #666; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: 0.2s; }
.tab .close:hover { background: #444; color: #fff; }
.search-bar { padding: 12px 20px; display: flex; gap: 16px; align-items: center; background: #212121; }
.nav-btns { display: flex; gap: 8px; }
.nav-btn { cursor: pointer; color: #aaa; padding: 6px; border-radius: 50%; display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; transition: 0.2s; }
.search-bar { padding: 8px 16px; display: flex; gap: 10px; align-items: center; background: #1e1e1e; }
.nav-btns { display: flex; gap: 2px; }
.nav-btn { cursor: pointer; color: #aaa; padding: 6px; border-radius: 6px; display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; transition: 0.2s; }
.nav-btn:hover { background: #333; color: #fff; }
.nav-btn.disabled { color: #444; cursor: default; }
.nav-btn.disabled { color: #444; cursor: default; pointer-events: none; }
form { flex-grow: 1; position: relative; }
input { padding: 10px 20px; font-size: 14px; border: 1px solid #333; border-radius: 24px; width: 100%; outline: none; background: #121212; color: #fff; transition: all 0.3s; box-sizing: border-box; }
input:focus { background: #181818; border-color: #555; box-shadow: 0 0 0 2px rgba(255,255,255,0.05); }
.new-tab-btn { cursor: pointer; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; color: #aaa; margin-left: 8px; margin-bottom: 4px; transition: 0.2s; }
input { padding: 8px 16px; font-size: 13px; border: 1px solid #333; border-radius: 18px; width: 100%; outline: none; background: #121212; color: #fff; transition: all 0.3s; box-sizing: border-box; }
input:focus { border-color: #3ea6ff; background: #000; box-shadow: 0 0 0 2px rgba(62, 166, 255, 0.2); }
.new-tab-btn { cursor: pointer; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; color: #888; margin-left: 6px; margin-bottom: 6px; transition: 0.2s; }
.new-tab-btn:hover { background: #333; color: #fff; }
.iframe-container { flex-grow: 1; position: relative; background: #fff; }
iframe { border: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; display: none; background: #fff; }
iframe.active { display: block; }
.loader { position: absolute; bottom: 0; left: 0; height: 2px; background: #3ea6ff; transition: width 0.3s; width: 0; z-index: 100; }
.loader { position: absolute; bottom: 0; left: 0; height: 3px; background: #3ea6ff; transition: width 0.4s; width: 0; z-index: 100; box-shadow: 0 0 8px rgba(62, 166, 255, 0.5); }
</style>
</head>
<body>
@ -200,26 +168,32 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<div class='search-bar'>
<div class='nav-btns'>
<div class='nav-btn' id='backBtn' onclick='goBack()' title='Back'></div>
<div class='nav-btn' id='forwardBtn' onclick='goForward()' title='Forward'></div>
<div class='nav-btn' onclick='reloadTab()' title='Reload'></div>
<div class='nav-btn' onclick='goHome()' title='Home'>🏠</div>
</div>
<form onsubmit='handleSearch(event); return false;'>
<input id='searchInput' placeholder='Search or type a URL' autofocus autocomplete='off' />
<div id='loader' class='loader'></div>
</form>
</div>
</div>
<div class='iframe-container' id='iframeContainer'>
<div id='loader' class='loader'></div>
</div>
<div class='iframe-container' id='iframeContainer'></div>
<script>
var tabs = [];
var activeTabId = null;
const PROXY_BASE = "${proxyUrl}";
function getRawUrl(proxiedUrl) {
if (proxiedUrl.startsWith(PROXY_BASE)) {
return decodeURIComponent(proxiedUrl.substring(PROXY_BASE.length));
}
try {
var url = new URL(proxiedUrl);
var raw = url.searchParams.get('url');
if (raw) return raw;
// Handle path-based
if (proxiedUrl.includes('/api/proxy/')) {
return proxiedUrl.split('/api/proxy/')[1];
}
} catch(e) {}
return proxiedUrl;
}
@ -265,20 +239,25 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
var iframe = document.createElement('iframe');
iframe.id = 'frame-' + id;
iframe.src = PROXY_BASE + encodeURIComponent(url);
iframe.setAttribute('allow', 'autoplay; encrypted-media; camera; microphone; fullscreen; clipboard-write');
iframe.setAttribute('allow', 'autoplay; encrypted-media; camera; microphone; fullscreen; clipboard-write; geolocation');
iframe.onload = function() {
document.getElementById('loader').style.width = '0';
try {
// Try to update title and URL if same-origin (proxy)
var frameUrl = iframe.contentWindow.location.href;
var rawUrl = getRawUrl(frameUrl);
if (rawUrl && rawUrl !== 'about:blank') {
if (rawUrl && rawUrl !== 'about:blank' && !rawUrl.includes('error-overlay')) {
if (tabObj.history[tabObj.historyIndex] !== rawUrl) {
tabObj.history = tabObj.history.slice(0, tabObj.historyIndex + 1);
tabObj.history.push(rawUrl);
tabObj.historyIndex++;
}
tabObj.url = rawUrl;
if (activeTabId === id) {
document.getElementById('searchInput').value = rawUrl;
updateNavButtons();
}
// Attempt to get page title
if (iframe.contentDocument && iframe.contentDocument.title) {
titleSpan.innerText = iframe.contentDocument.title;
}
@ -303,19 +282,17 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
var tab = tabs.find(t => t.id === id);
if (tab) {
document.getElementById('searchInput').value = (tab.url.includes('google.com/search?igu=1')) ? '' : tab.url;
document.getElementById('searchInput').value = tab.url;
updateNavButtons();
}
}
function updateNavButtons() {
var tab = tabs.find(t => t.id === activeTabId);
var backBtn = document.getElementById('backBtn');
if (tab && tab.historyIndex > 0) {
backBtn.classList.remove('disabled');
} else {
backBtn.classList.add('disabled');
}
if (!tab) return;
document.getElementById('backBtn').classList.toggle('disabled', tab.historyIndex <= 0);
document.getElementById('forwardBtn').classList.toggle('disabled', tab.historyIndex >= tab.history.length - 1);
}
function removeTab(id) {
@ -340,14 +317,29 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
var tab = tabs.find(t => t.id === activeTabId);
if (tab && tab.historyIndex > 0) {
tab.historyIndex--;
var prevUrl = tab.history[tab.historyIndex];
tab.url = prevUrl;
document.getElementById('frame-' + activeTabId).src = PROXY_BASE + encodeURIComponent(prevUrl);
document.getElementById('searchInput').value = prevUrl;
updateNavButtons();
loadUrlInTab(tab, tab.history[tab.historyIndex], true);
}
}
function goForward() {
var tab = tabs.find(t => t.id === activeTabId);
if (tab && tab.historyIndex < tab.history.length - 1) {
tab.historyIndex++;
loadUrlInTab(tab, tab.history[tab.historyIndex], true);
}
}
function loadUrlInTab(tab, url, skipHistory) {
tab.url = url;
var iframe = document.getElementById('frame-' + tab.id);
iframe.src = PROXY_BASE + encodeURIComponent(url);
if (activeTabId === tab.id) {
document.getElementById('searchInput').value = url;
}
document.getElementById('loader').style.width = '40%';
updateNavButtons();
}
function reloadTab() {
if (activeTabId) {
var frame = document.getElementById('frame-' + activeTabId);
@ -357,59 +349,33 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
}
function goHome() {
var url = 'https://www.google.com/search?igu=1';
if (activeTabId) {
var tab = tabs.find(t => t.id === activeTabId);
tab.url = url;
document.getElementById('frame-' + activeTabId).src = PROXY_BASE + encodeURIComponent(url);
document.getElementById('searchInput').value = '';
document.getElementById('el-' + activeTabId).querySelector('.title').innerText = 'Google';
}
handleSearch({ preventDefault: () => {}, target: null }, 'https://www.google.com/search?igu=1');
}
function handleSearch(e) {
function handleSearch(e, forcedUrl) {
if (e) e.preventDefault();
var input = document.getElementById('searchInput');
var query = input.value.trim();
var query = forcedUrl || input.value.trim();
if (!query) return false;
document.getElementById('loader').style.width = '30%';
var url;
if (query.match(/^(https?:\\/\\/)/i)) {
url = query;
} else if (query.includes('.') && !query.includes(' ')) {
url = 'https://' + query;
} else {
url = "https://www.google.com/search?igu=1&q=" + encodeURIComponent(query);
var url = query;
if (!query.match(/^(https?:\\/\\/)/i)) {
if (query.includes('.') && !query.includes(' ')) {
url = 'https://' + query;
} else {
url = "https://www.google.com/search?igu=1&q=" + encodeURIComponent(query);
}
}
// YouTube specific optimization: prefer mobile version for better proxy compatibility
if (url.includes('youtube.com') && !url.includes('m.youtube.com')) {
url = url.replace('www.youtube.com', 'm.youtube.com');
}
if (!activeTabId) {
addTab(url, query);
addTab(url);
} else {
var tab = tabs.find(t => t.id === activeTabId);
var iframe = document.getElementById('frame-' + activeTabId);
// Push to history
tab.history = tab.history.slice(0, tab.historyIndex + 1);
tab.history.push(url);
tab.historyIndex++;
tab.url = url;
iframe.src = PROXY_BASE + encodeURIComponent(url);
var tabEl = document.getElementById('el-' + activeTabId);
if (tabEl) {
var displayTitle = query;
if (url.includes('google.com/search')) displayTitle = query;
else if (url.length > 20) displayTitle = url.substring(0, 17) + '...';
tabEl.querySelector('.title').innerText = displayTitle;
}
updateNavButtons();
loadUrlInTab(tab, url);
}
input.blur();
return false;
@ -445,12 +411,11 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const imageHeight = '960'
return (
<Provider store={store}>
<Provider store={store}>
{getLayout(
<>
<Head>
<meta name="description" content={description} />
<meta property="og:url" content={url} />
<meta property="og:site_name" content="https://flatlogic.com/" />
<meta property="og:title" content={title} />
@ -459,17 +424,14 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content={imageWidth} />
<meta property="og:image:height" content={imageHeight} />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image:src" content={image} />
<meta property="twitter:image:width" content={imageWidth} />
<meta property="twitter:image:height" content={imageHeight} />
<link rel="icon" href="/favicon.svg" />
</Head>
<ErrorBoundary>
<Component {...pageProps} />
</ErrorBoundary>
@ -486,4 +448,4 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
)
}
export default appWithTranslation(MyApp);
export default appWithTranslation(MyApp);