From 1684c7070bf326f31742a50a6680914b97bcb137 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 26 Sep 2025 23:55:40 -0400 Subject: [PATCH] Shorten bucket slugs and fix CORS refresh --- js/app.js | 39 ++++++++++++++++++++++++---- js/storage/r2-mgmt.js | 59 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/js/app.js b/js/app.js index 6a34a3e6..b25a7562 100644 --- a/js/app.js +++ b/js/app.js @@ -31,6 +31,7 @@ import { putCors, attachCustomDomainAndWait, setManagedDomain, + deriveShortSubdomain, } from "./storage/r2-mgmt.js"; import { makeR2Client, @@ -1573,11 +1574,17 @@ class bitvidApp { } deriveSubdomainForNpub(npub) { + try { + return deriveShortSubdomain(npub); + } catch (err) { + console.warn("Failed to derive short subdomain, falling back:", err); + } + const base = String(npub || "user") .toLowerCase() .replace(/[^a-z0-9-]/g, "") .replace(/^-+|[-]+$/g, ""); - return base.slice(0, 48) || "user"; + return base.slice(0, 32) || "user"; } async ensureBucketConfigForNpub(npub) { @@ -1617,6 +1624,28 @@ class bitvidApp { } catch (err) { console.warn("Failed to refresh bucket configuration:", err); } + } else if ( + accessKeyId && + secretAccessKey && + corsOrigins.length > 0 + ) { + try { + const s3 = makeR2Client({ + accountId, + accessKeyId, + secretAccessKey, + }); + await ensureBucketCors({ + s3, + bucket: entry.bucket, + origins: corsOrigins, + }); + } catch (err) { + console.warn( + "Failed to refresh bucket CORS via access keys:", + err + ); + } } return { entry, @@ -1821,17 +1850,17 @@ class bitvidApp { const publicUrl = buildPublicUrl(entry.publicBaseUrl, sampleKey); const fullPreview = `${entry.bucket} • ${publicUrl}`; - let displayHostAndPath = truncateMiddle(publicUrl, 96); + let displayHostAndPath = truncateMiddle(publicUrl, 72); try { const parsed = new URL(publicUrl); const cleanPath = parsed.pathname.replace(/^\//, ""); - const truncatedPath = truncateMiddle(cleanPath || sampleKey, 48); - displayHostAndPath = `${truncateMiddle(parsed.host, 48)}/${truncatedPath}`; + const truncatedPath = truncateMiddle(cleanPath || sampleKey, 32); + displayHostAndPath = `${truncateMiddle(parsed.host, 32)}/${truncatedPath}`; } catch (err) { // ignore URL parse issues and fall back to the raw string } - const truncatedBucket = truncateMiddle(entry.bucket, 40); + const truncatedBucket = truncateMiddle(entry.bucket, 28); el.textContent = `${truncatedBucket} • ${displayHostAndPath}`; el.setAttribute("title", fullPreview); } diff --git a/js/storage/r2-mgmt.js b/js/storage/r2-mgmt.js index 20434737..10a6ef81 100644 --- a/js/storage/r2-mgmt.js +++ b/js/storage/r2-mgmt.js @@ -1,6 +1,16 @@ // js/storage/r2-mgmt.js +function computeSlugHash(input) { + const text = String(input || ""); + let hash = 0; + for (let i = 0; i < text.length; i += 1) { + hash = Math.imul(31, hash) + text.charCodeAt(i); + } + return (hash >>> 0).toString(36); +} + export function sanitizeBucketName(npub) { - let value = String(npub || "") + const raw = String(npub || ""); + let value = raw .toLowerCase() .replace(/[^a-z0-9-]/g, "-") .replace(/-+/g, "-") @@ -10,6 +20,14 @@ export function sanitizeBucketName(npub) { value = "bitvid-user"; } + if (value.length > 32) { + const prefix = value.slice(0, 20).replace(/-+$/g, ""); + const hash = computeSlugHash(raw).slice(0, 8); + value = [prefix, hash].filter(Boolean).join("-"); + } + + value = value.replace(/-+/g, "-").replace(/^-+|[-]+$/g, ""); + if (!/^[a-z0-9]/.test(value)) { value = `bv-${value}`; } @@ -29,6 +47,45 @@ export function sanitizeBucketName(npub) { return value; } +export function deriveShortSubdomain(npub) { + const bucketSlug = sanitizeBucketName(npub); + const hash = computeSlugHash(npub).slice(0, 6); + + const prefix = bucketSlug + .replace(/^bv-/, "") + .slice(0, 18) + .replace(/-+$/g, ""); + + let candidate = [prefix, hash].filter(Boolean).join("-"); + candidate = candidate + .toLowerCase() + .replace(/[^a-z0-9-]/g, "-") + .replace(/-+/g, "-") + .replace(/^-+|[-]+$/g, ""); + + if (!candidate) { + candidate = "user"; + } + + if (candidate.length > 32) { + candidate = candidate.slice(0, 32).replace(/-+$/g, ""); + } + + if (!/^[a-z0-9]/.test(candidate)) { + candidate = `u${candidate}`; + } + + if (!/[a-z0-9]$/.test(candidate)) { + candidate = `${candidate}0`; + } + + while (candidate.length < 3) { + candidate += "0"; + } + + return candidate; +} + function buildError(error, fallbackMessage) { if (error instanceof Error) { return error;