mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
/**
|
|
* Short-code generation.
|
|
*
|
|
* Strategy (mirrors what the Go backend will do):
|
|
* - "random" → base62 encoding of a monotonically increasing counter. This
|
|
* yields the SHORTEST possible string that is still globally
|
|
* unique (no collision checks, no wasted length). A counter of
|
|
* N needs ceil(log62(N)) chars: 62^4 ≈ 14.7M, 62^5 ≈ 916M.
|
|
* To avoid leaking sequence/volume, the counter is passed
|
|
* through a reversible bit-scramble before encoding.
|
|
* - "memorable" → 3 short, easy words joined by hyphens.
|
|
* - "custom" → user supplied (validated, authenticated only).
|
|
*/
|
|
|
|
const BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
export function encodeBase62(num: number): string {
|
|
if (num === 0) return BASE62[0];
|
|
let n = num;
|
|
let out = "";
|
|
while (n > 0) {
|
|
out = BASE62[n % 62] + out;
|
|
n = Math.floor(n / 62);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Reversible scramble so sequential counter values don't produce sequential
|
|
* codes. Knuth multiplicative hash over a 31-bit space with a coprime
|
|
* multiplier — keeps codes short while looking random.
|
|
*/
|
|
function scramble(id: number): number {
|
|
const PRIME = 2654435761; // 2^32 golden-ratio prime
|
|
const MOD = 0x7fffffff; // 2^31 - 1
|
|
return ((id + 1) * PRIME) % MOD;
|
|
}
|
|
|
|
export function codeFromCounter(counter: number): string {
|
|
// Bump the base so even the first link gets a pleasant 4-char code.
|
|
return encodeBase62(scramble(counter + 100_000));
|
|
}
|
|
|
|
const CUSTOM_RE = /^[a-zA-Z0-9_-]{3,32}$/;
|
|
|
|
export function validateCustomAlias(alias: string): string | null {
|
|
if (!alias) return "Pick an alias for your link.";
|
|
if (alias.length < 3) return "At least 3 characters.";
|
|
if (alias.length > 32) return "Keep it under 32 characters.";
|
|
if (!CUSTOM_RE.test(alias))
|
|
return "Only letters, numbers, hyphens and underscores.";
|
|
return null;
|
|
}
|
|
|
|
const URL_RE = /^https?:\/\/[^\s.]+\.[^\s]{2,}$/i;
|
|
|
|
export function normalizeUrl(input: string): string {
|
|
const trimmed = input.trim();
|
|
if (!trimmed) return "";
|
|
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
return `https://${trimmed}`;
|
|
}
|
|
|
|
export function isValidUrl(input: string): boolean {
|
|
return URL_RE.test(normalizeUrl(input));
|
|
}
|