50 lines
1.5 KiB
JavaScript
50 lines
1.5 KiB
JavaScript
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";
|