export function normalizeUrl(input) { try { const url = new URL(input); url.hash = ""; url.protocol = url.protocol.toLowerCase(); url.hostname = url.hostname.toLowerCase(); // Remove default ports if ((url.protocol === "http:" && url.port === "80") || (url.protocol === "https:" && url.port === "443")) { url.port = ""; } // Trim trailing slash on pathname (but keep root '/') if (url.pathname.length > 1 && url.pathname.endsWith("/")) { url.pathname = url.pathname.slice(0, -1); } // Drop common tracking params const trackingPrefixes = ["utm_", "spm", "gclid", "fbclid"]; for (const key of [...url.searchParams.keys()]) { const lowerKey = key.toLowerCase(); if (trackingPrefixes.some((p) => lowerKey.startsWith(p))) { url.searchParams.delete(key); } } // Sort params for stable output const sorted = [...url.searchParams.entries()].sort(([a], [b]) => a.localeCompare(b)); url.search = ""; for (const [k, v] of sorted) url.searchParams.append(k, v); return url.toString(); } catch { return input; } } export function computeUrlHash(normalizedUrl) { // Lightweight hash (non-crypto) for dedupe key; server may replace with crypto later. let hash = 2166136261; for (let i = 0; i < normalizedUrl.length; i++) { hash ^= normalizedUrl.charCodeAt(i); hash = Math.imul(hash, 16777619); } return (hash >>> 0).toString(16); } export { parseNetscapeBookmarkHtml } from "./bookmarkHtml.js";