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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user