refac
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
||||
export let baseUrl: string;
|
||||
export let port: number;
|
||||
export let path: string = '';
|
||||
@@ -23,7 +24,6 @@
|
||||
$: canGoForward = historyIndex < history.length - 1;
|
||||
|
||||
const pushHistory = (newPath: string) => {
|
||||
// Trim forward history when navigating from a non-tip position
|
||||
if (historyIndex < history.length - 1) {
|
||||
history = history.slice(0, historyIndex + 1);
|
||||
}
|
||||
@@ -35,6 +35,7 @@
|
||||
if (!canGoBack) return;
|
||||
historyIndex -= 1;
|
||||
path = history[historyIndex];
|
||||
syncUrlBar();
|
||||
iframeKey += 1;
|
||||
};
|
||||
|
||||
@@ -42,28 +43,26 @@
|
||||
if (!canGoForward) return;
|
||||
historyIndex += 1;
|
||||
path = history[historyIndex];
|
||||
syncUrlBar();
|
||||
iframeKey += 1;
|
||||
};
|
||||
|
||||
// ── URLs ─────────────────────────────────────────────────────────────
|
||||
$: proxyUrl = getPortProxyUrl(baseUrl, port, path);
|
||||
|
||||
// The proxy path prefix, e.g. "/proxy/8080/"
|
||||
$: proxyPathPrefix = (() => {
|
||||
try {
|
||||
return new URL(getPortProxyUrl(baseUrl, port, '')).pathname;
|
||||
return new URL(getPortProxyUrl(baseUrl, port, ''), window.location.origin).pathname;
|
||||
} catch {
|
||||
return `/proxy/${port}/`;
|
||||
}
|
||||
})();
|
||||
|
||||
// Display-friendly localhost URL for the address bar
|
||||
$: displayUrl = `localhost:${port}${path ? '/' + path : ''}`;
|
||||
$: urlInput = displayUrl;
|
||||
const makeDisplayUrl = (p: string) => `localhost:${port}${p ? '/' + p : ''}`;
|
||||
const syncUrlBar = () => { urlInput = makeDisplayUrl(path); };
|
||||
urlInput = makeDisplayUrl(path);
|
||||
|
||||
const refresh = () => {
|
||||
iframeKey += 1;
|
||||
};
|
||||
const refresh = () => { iframeKey += 1; };
|
||||
|
||||
const openExternal = () => {
|
||||
window.open(proxyUrl, '_blank', 'noopener,noreferrer');
|
||||
@@ -84,113 +83,36 @@
|
||||
path = newPath;
|
||||
pushHistory(path);
|
||||
}
|
||||
syncUrlBar();
|
||||
iframeKey += 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* After each iframe load:
|
||||
* 1. Sync the address bar with the current iframe path
|
||||
* 2. Inject a click interceptor to rewrite links BEFORE navigation
|
||||
* 3. Redirect if the page already escaped the proxy (fallback)
|
||||
* Read the iframe's current location and sync the URL bar.
|
||||
* If the iframe escaped the proxy prefix, redirect it back.
|
||||
*/
|
||||
const onIframeLoad = () => {
|
||||
isLoading = false;
|
||||
if (!iframeEl) return;
|
||||
try {
|
||||
const doc = iframeEl.contentDocument;
|
||||
const win = iframeEl.contentWindow;
|
||||
if (!doc || !win) return;
|
||||
|
||||
const loc = win.location;
|
||||
const iframePath = loc?.pathname ?? '';
|
||||
const iframeSearch = loc?.search ?? '';
|
||||
const iframeHash = loc?.hash ?? '';
|
||||
const loc = iframeEl.contentWindow?.location;
|
||||
if (!loc) return;
|
||||
const iframePath = loc.pathname ?? '';
|
||||
const iframeSearch = loc.search ?? '';
|
||||
const iframeHash = loc.hash ?? '';
|
||||
|
||||
if (iframePath.startsWith(proxyPathPrefix)) {
|
||||
// Still inside the proxy — sync address bar
|
||||
const relativePath = iframePath.slice(proxyPathPrefix.length) + iframeSearch + iframeHash;
|
||||
if (relativePath !== path) {
|
||||
path = relativePath;
|
||||
pushHistory(path);
|
||||
urlInput = displayUrl;
|
||||
syncUrlBar();
|
||||
}
|
||||
|
||||
// Inject click interceptor to rewrite links before navigation
|
||||
injectLinkInterceptor(doc, win);
|
||||
} else if (iframePath && iframePath !== 'about:blank') {
|
||||
// Escaped the proxy — redirect back through it
|
||||
const escapedPath = iframePath.replace(/^\//, '') + iframeSearch + iframeHash;
|
||||
const correctedUrl = getPortProxyUrl(baseUrl, port, escapedPath);
|
||||
loc?.replace(correctedUrl);
|
||||
}
|
||||
} catch {
|
||||
// Cross-origin — can't access
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject a click interceptor into the iframe document that rewrites
|
||||
* anchor hrefs to stay within the proxy path.
|
||||
*/
|
||||
const injectLinkInterceptor = (doc: Document, win: Window) => {
|
||||
// Skip if we already injected
|
||||
if ((win as any).__portPreviewIntercepted) return;
|
||||
(win as any).__portPreviewIntercepted = true;
|
||||
|
||||
const prefix = proxyPathPrefix;
|
||||
|
||||
// Intercept all click events on anchors
|
||||
doc.addEventListener('click', (e: MouseEvent) => {
|
||||
const anchor = (e.target as HTMLElement)?.closest?.('a');
|
||||
if (!anchor || !anchor.href) return;
|
||||
|
||||
try {
|
||||
const url = new URL(anchor.href, win.location.href);
|
||||
|
||||
// Only intercept same-origin links that escaped the proxy
|
||||
if (url.origin === win.location.origin && !url.pathname.startsWith(prefix)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Rewrite to go through proxy
|
||||
const rewrittenPath = prefix + url.pathname.replace(/^\//, '') + url.search + url.hash;
|
||||
win.location.href = url.origin + rewrittenPath;
|
||||
}
|
||||
} catch {
|
||||
// Invalid URL — let browser handle it
|
||||
}
|
||||
}, true); // Use capture phase to intercept before SPA routers
|
||||
|
||||
// Also intercept form submissions
|
||||
doc.addEventListener('submit', (e: Event) => {
|
||||
const form = e.target as HTMLFormElement;
|
||||
if (!form?.action) return;
|
||||
|
||||
try {
|
||||
const url = new URL(form.action, win.location.href);
|
||||
if (url.origin === win.location.origin && !url.pathname.startsWith(prefix)) {
|
||||
form.action = url.origin + prefix + url.pathname.replace(/^\//, '') + url.search;
|
||||
}
|
||||
} catch {}
|
||||
}, true);
|
||||
|
||||
// Intercept window.open and programmatic navigation
|
||||
const origOpen = win.open.bind(win);
|
||||
win.open = function(url?: string | URL, ...args: any[]) {
|
||||
if (url && typeof url === 'string') {
|
||||
try {
|
||||
const parsed = new URL(url, win.location.href);
|
||||
if (parsed.origin === win.location.origin && !parsed.pathname.startsWith(prefix)) {
|
||||
url = parsed.origin + prefix + parsed.pathname.replace(/^\//, '') + parsed.search + parsed.hash;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return origOpen(url, ...args);
|
||||
};
|
||||
};
|
||||
|
||||
const onIframeLoadStart = () => {
|
||||
isLoading = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full min-h-0">
|
||||
@@ -230,22 +152,16 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<!-- Refresh / Stop -->
|
||||
<!-- Refresh -->
|
||||
<Tooltip content={$i18n.t('Refresh')}>
|
||||
<button
|
||||
class="p-1 rounded text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 transition"
|
||||
on:click={refresh}
|
||||
aria-label={$i18n.t('Refresh')}
|
||||
>
|
||||
{#if isLoading}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-3.5 animate-spin">
|
||||
<path fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 0 1-9.201 2.466l-.312-.311h2.451a.75.75 0 0 0 0-1.5H4.5a.75.75 0 0 0-.75.75v3.75a.75.75 0 0 0 1.5 0v-2.127l.13.13a7 7 0 0 0 11.712-3.138.75.75 0 0 0-1.449-.39Zm-10.624-2.85a5.5 5.5 0 0 1 9.201-2.465l.312.31H11.75a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 .75-.75V3.42a.75.75 0 0 0-1.5 0v2.126l-.13-.129A7 7 0 0 0 3.239 8.555a.75.75 0 0 0 1.449.39Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-3.5">
|
||||
<path fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 0 1-9.201 2.466l-.312-.311h2.451a.75.75 0 0 0 0-1.5H4.5a.75.75 0 0 0-.75.75v3.75a.75.75 0 0 0 1.5 0v-2.127l.13.13a7 7 0 0 0 11.712-3.138.75.75 0 0 0-1.449-.39Zm-10.624-2.85a5.5 5.5 0 0 1 9.201-2.465l.312.31H11.75a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 .75-.75V3.42a.75.75 0 0 0-1.5 0v2.126l-.13-.129A7 7 0 0 0 3.239 8.555a.75.75 0 0 0 1.449.39Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{/if}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-3.5" class:animate-spin={isLoading}>
|
||||
<path fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 0 1-9.201 2.466l-.312-.311h2.451a.75.75 0 0 0 0-1.5H4.5a.75.75 0 0 0-.75.75v3.75a.75.75 0 0 0 1.5 0v-2.127l.13.13a7 7 0 0 0 11.712-3.138.75.75 0 0 0-1.449-.39Zm-10.624-2.85a5.5 5.5 0 0 1 9.201-2.465l.312.31H11.75a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 .75-.75V3.42a.75.75 0 0 0-1.5 0v2.126l-.13-.129A7 7 0 0 0 3.239 8.555a.75.75 0 0 0 1.449.39Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -275,7 +191,7 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<!-- Close preview -->
|
||||
<!-- Close -->
|
||||
<Tooltip content={$i18n.t('Close')}>
|
||||
<button
|
||||
class="p-1 rounded text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 transition"
|
||||
@@ -296,7 +212,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Iframe content -->
|
||||
<!-- Iframe -->
|
||||
{#key iframeKey}
|
||||
<iframe
|
||||
bind:this={iframeEl}
|
||||
|
||||
Reference in New Issue
Block a user