mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
feat: first commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
Reference in New Issue
Block a user