···11-import { SHORTCODE } from '$lib/constants';
22-import { parse, getDomain } from 'tldts';
11+import { SHORTCODE } from '$lib/constants'; // Import constants for shortcode configuration
22+import { parse, getDomain } from 'tldts'; // Import utility functions for domain parsing
3344-const BASE_CHARS = SHORTCODE.CHARS;
55-const BASE = BASE_CHARS.length;
44+// Constants related to the character set for encoding
55+const BASE_CHARS = SHORTCODE.CHARS; // Charset for the shortcode encoding
66+const BASE = BASE_CHARS.length; // Base (the number of unique characters in the charset)
6788+// Hashes a given string into a bigint value
79function hashString(text: string): bigint {
88- let hash = 1469598103934665603n;
1010+ let hash = 1469598103934665603n; // FNV-1a hash initialisation value
911 for (let i = 0; i < text.length; i++) {
1010- const char = BigInt(text.charCodeAt(i));
1111- hash = (hash ^ char) * 1099511628211n;
1212+ const char = BigInt(text.charCodeAt(i)); // Convert each character to a bigint
1313+ hash = (hash ^ char) * 1099511628211n; // FNV-1a hashing algorithm
1214 }
1313- return hash < 0n ? -hash : hash;
1515+ return hash < 0n ? -hash : hash; // Ensure the hash is positive
1416}
15171818+// Converts a number (bigint) into a base encoded string with a given length
1619function toBase(num: bigint, length: number, seed = ''): string {
1717- let encoded = '';
2020+ let encoded = ''; // The resulting encoded string
1821 let n = num;
1922 for (let i = 0; i < length; i++) {
2023 let rem: bigint;
2424+ // Calculate remainder and divide to get the next digit
2125 if (n > 0n) {
2226 rem = n % BigInt(BASE);
2327 n = n / BigInt(BASE);
2428 } else {
2929+ // Fallback if number is 0, use a hash for deterministic behaviour
2530 const fallback = hashString(num.toString() + '::' + seed + '::' + i.toString());
2631 rem = fallback % BigInt(BASE);
2732 }
2828- encoded = BASE_CHARS[Number(rem)] + encoded;
3333+ encoded = BASE_CHARS[Number(rem)] + encoded; // Prepend the character for this base value
2934 }
3035 return encoded;
3136}
32373838+// Normalises a URL by ensuring it's well-formed and canonical
3339function normaliseUrl(url: string): string {
3440 try {
4141+ // Ensure the URL starts with 'https://' and parse it
3542 const parsed = new URL(url.startsWith('http') ? url : `https://${url}`);
3636- parsed.hash = '';
4343+ parsed.hash = ''; // Remove hash fragment
37444545+ // Sort URL query parameters alphabetically
3846 const sortedParams = [...parsed.searchParams.entries()].sort((a, b) =>
3947 a[0].localeCompare(b[0])
4048 );
4141- parsed.search = '';
4242- for (const [key, value] of sortedParams) parsed.searchParams.append(key, value);
4949+ parsed.search = ''; // Clear existing search parameters
5050+ for (const [key, value] of sortedParams) parsed.searchParams.append(key, value); // Rebuild query string
43514444- parsed.hostname = parsed.hostname.toLowerCase();
4545- parsed.protocol = 'https:';
4646- return parsed.toString();
5252+ parsed.hostname = parsed.hostname.toLowerCase(); // Convert hostname to lowercase
5353+ parsed.protocol = 'https:'; // Ensure HTTPS protocol is used
5454+ return parsed.toString(); // Return the normalised URL as a string
4755 } catch (e) {
5656+ // If URL parsing fails, return the original URL (trimmed)
4857 return url.trim();
4958 }
5059}
51606161+// Extracts the base domain from a URL
5262function getBaseDomain(url: string): string {
5363 try {
6464+ // Use tldts library to get the domain from the URL
5465 const domain = getDomain(url, { allowPrivateDomains: false });
5566 if (domain) return domain.toLowerCase();
56676868+ // Fallback to manual parsing if tldts fails
5769 const parsed = parse(url, { extractHostname: true });
5870 return (parsed.hostname ?? '').toLowerCase();
5971 } catch (e) {
7272+ // Return an empty string if domain extraction fails
6073 return '';
6174 }
6275}
63767777+// Main function to encode a URL into a shortcode of specified length
6478export function encodeUrl(url: string, length: number = SHORTCODE.DEFAULT_LENGTH): string {
7979+ // Validate and adjust the length of the shortcode
6580 if (!Number.isInteger(length) || length < 3) length = SHORTCODE.DEFAULT_LENGTH;
66816767- const DOMAIN_PREFIX_LENGTH = 2;
8282+ const DOMAIN_PREFIX_LENGTH = 2; // Number of characters used for the domain prefix
68838484+ // Normalise the URL and extract the base domain
6985 const normalised = normaliseUrl(url);
7086 const apex = getBaseDomain(normalised) || '';
71878888+ // Hash the domain to generate a prefix
7289 const domainHash = hashString(apex || normalised);
7390 const domainPrefix = toBase(domainHash, DOMAIN_PREFIX_LENGTH, 'domain');
74919292+ // Calculate the remaining length for the URL core and tail
7593 const remaining = Math.max(1, length - DOMAIN_PREFIX_LENGTH);
76947777- let hostname = '';
9595+ let hostname = ''; // The hostname portion of the URL
7896 try {
7979- hostname = new URL(normalised).hostname.toLowerCase();
9797+ hostname = new URL(normalised).hostname.toLowerCase(); // Try to extract hostname from normalised URL
8098 } catch (e) {
9999+ // Fallback if URL parsing fails
81100 try {
82101 hostname = new URL(url.startsWith('http') ? url : `https://${url}`).hostname.toLowerCase();
83102 } catch {
8484- hostname = '';
103103+ hostname = ''; // If both parsing attempts fail, leave hostname empty
85104 }
86105 }
8710688107 let subLevels: string[] = [];
108108+ // If there is a subdomain, split it into separate levels
89109 if (apex && hostname && hostname !== apex) {
9090- const sub = hostname.replace(new RegExp(`\.${apex}$`), '');
9191- subLevels = sub.split('.');
110110+ const sub = hostname.replace(new RegExp(`\.${apex}$`), ''); // Remove the apex domain
111111+ subLevels = sub.split('.'); // Split subdomains by '.'
92112 }
93113114114+ // URL core length is determined based on the remaining space after the domain prefix
94115 const MIN_URL_CORE = 1;
95116 const MIN_TAIL = 1;
9696- const tailLength = remaining;
117117+ const tailLength = remaining; // Length allocated to the tail portion of the shortcode
97118119119+ // Hash the normalised URL for the URL core portion of the shortcode
98120 const urlHash = hashString(normalised + '::url');
9999- const urlCoreLength = remaining - subLevels.length;
121121+ const urlCoreLength = remaining - subLevels.length; // Account for subdomain levels
100122 const urlCore = toBase(urlHash, Math.max(MIN_URL_CORE, urlCoreLength), 'url');
101123124124+ // Generate subdomain-based tail (if applicable)
102125 const subTail: string[] = [];
103103- const reversedSubLevels = subLevels.slice().reverse();
126126+ const reversedSubLevels = subLevels.slice().reverse(); // Reverse the subdomain levels for encoding
104127 for (let i = 0; i < reversedSubLevels.length; i++) {
105105- const h = hashString(reversedSubLevels[i] + '::sub');
106106- subTail.push(toBase(h, 1, 'sub' + i));
128128+ const h = hashString(reversedSubLevels[i] + '::sub'); // Hash the subdomain level
129129+ subTail.push(toBase(h, 1, 'sub' + i)); // Add to subTail
107130 }
108131132132+ // If no subdomain tail is generated, use a fallback hash for the tail
109133 let tail = subTail.join('');
110134 if (!tail) {
111135 const fallbackHash = hashString(normalised + '::fallback');
112136 tail = toBase(fallbackHash, tailLength, 'sub');
113137 }
114138139139+ // Combine domain prefix, URL core, and tail to form the final shortcode
115140 let out = domainPrefix + urlCore + tail;
116116- if (out.length > length) out = out.slice(0, length);
141141+ if (out.length > length) out = out.slice(0, length); // Trim to the desired length
117142 if (out.length < length) {
143143+ // Pad the shortcode if it is too short
118144 let pad = '';
119145 let i = 0;
120146 while (out.length + pad.length < length) {
···122148 pad += toBase(h, Math.min(4, length - out.length - pad.length), 'pad2' + i);
123149 i++;
124150 }
125125- out += pad.slice(0, length - out.length);
151151+ out += pad.slice(0, length - out.length); // Append padding to reach the correct length
126152 }
127153128128- // --- LOGGING MAX COMBINATIONS ---
129129- const maxCombinations = BigInt(BASE) ** BigInt(length);
154154+ // --- LOGGING MAX COMBINATIONS --- (for debugging purposes)
155155+ const maxCombinations = BigInt(BASE) ** BigInt(length); // Calculate the max possible combinations for the shortcode
130156 console.log(`[Shortcode Info] URL: ${url}`);
131157 console.log(`[Shortcode Info] Length: ${length}, Charset: ${BASE} chars`);
132158 console.log(`[Shortcode Info] Max possible combinations: ${maxCombinations.toString()}`);
···134160 `[Shortcode Info] Domain prefix: ${domainPrefix}, URL core: ${urlCore}, Subdomain tail: ${tail}`
135161 );
136162137137- return out;
163163+ return out; // Return the final encoded shortcode
138164}
139165166166+// Function to validate if a given shortcode is valid (contains only alphanumeric characters)
140167export function isValidShortcode(code: string): boolean {
141168 return /^[0-9a-zA-Z]+$/.test(code);
142169}
143170171171+// Function to calculate the maximum number of possible combinations for a shortcode of a given length
144172export function getMaxCombinations(length: number): number {
145145- return Math.pow(BASE, length);
173173+ return Math.pow(BASE, length); // BASE raised to the power of length
146174}