fix: harden external link navigation in the web app

- add a shared external-navigation guard at the document level
- route onboarding, markdown, settings, and node source links through one helper
- stop browser fallback code from replacing the current app page on failures

Generated with Codex
This commit is contained in:
“BeeRad”
2026-03-17 18:20:37 +11:00
parent 213c054c9f
commit 7444599817
8 changed files with 164 additions and 41 deletions
+33 -24
View File
@@ -1,38 +1,47 @@
"use client";
const LOCAL_HOSTS = new Set(["127.0.0.1", "localhost", "0.0.0.0"]);
function resolveUrl(rawUrl: string): URL | null {
if (typeof window === "undefined") return null;
try {
return new URL(rawUrl, window.location.href);
} catch (error) {
console.error("[external] failed to parse url", rawUrl, error);
return null;
}
}
function isLocalAppUrl(url: URL) {
if (!["http:", "https:"].includes(url.protocol)) {
return false;
}
return LOCAL_HOSTS.has(url.hostname) || url.hostname === window.location.hostname;
}
export async function openExternalUrl(url: string) {
if (typeof window === "undefined") return;
try {
const newWindow = window.open(url, "_blank", "noopener,noreferrer");
if (!newWindow) {
window.location.href = url;
}
} catch (error) {
console.error("[external] window.open failed", error);
window.location.href = url;
const resolvedUrl = resolveUrl(url)?.toString() ?? url;
const newWindow = window.open(resolvedUrl, "_blank", "noopener,noreferrer");
if (!newWindow) {
throw new Error("browser_open_blocked");
}
}
export function shouldOpenExternally(rawUrl: string) {
if (typeof window === "undefined") return false;
try {
const resolved = new URL(rawUrl, window.location.href);
if (!["http:", "https:"].includes(resolved.protocol)) {
return false;
}
const localhostHosts = new Set([
window.location.hostname,
"127.0.0.1",
"localhost",
"0.0.0.0",
]);
return !localhostHosts.has(resolved.hostname);
} catch (error) {
console.error("[external] failed to parse url", rawUrl, error);
const resolved = resolveUrl(rawUrl);
if (!resolved) {
return false;
}
if (!["http:", "https:"].includes(resolved.protocol)) {
return false;
}
return !isLocalAppUrl(resolved);
}