Refactor image URL handling: streamline image and asset CDN URL generation using new utility functions

This commit is contained in:
Alexa
2026-06-02 15:57:55 +03:00
parent f1a616e9c1
commit a2feb780c2
3 changed files with 28 additions and 14 deletions
+21 -5
View File
@@ -38,13 +38,18 @@ export interface PlayerDetail extends Player {
// 2020 watch uses (cropSquare=1 produces a face-zoomed square, sig
// matches the host's signing key id).
export function profileImageUrl(name: string | null | undefined, width = 96): string | null {
if (!name) return null;
const apex = adminApex();
const scheme = apex === 'localhost' ? 'http' : 'https';
return `${scheme}://img.${apex}/${encodeURIComponent(name)}?width=${width}&cropSquare=1&sig=p1`;
return imageCdnUrl(name, `width=${width}&cropSquare=1&sig=p1`);
}
function adminApex(): string {
export function imageCdnUrl(name: string | null | undefined, query?: string): string | null {
return subdomainUrl('img', name, query, false);
}
export function assetCdnUrl(path: string | null | undefined): string | null {
return subdomainUrl('cdn', path, undefined, true);
}
export function adminApex(): string {
if (typeof window === 'undefined') return 'rec.net';
const host = window.location.hostname.toLowerCase();
@@ -54,6 +59,17 @@ function adminApex(): string {
return parts.length > 2 ? parts.slice(1).join('.') : host;
}
function subdomainUrl(subdomain: string, path: string | null | undefined, query?: string, preserveSlashes = false): string | null {
if (!path) return null;
const apex = adminApex();
const scheme = apex === 'localhost' ? 'http' : 'https';
const suffix = query ? `?${query}` : '';
const encodedPath = preserveSlashes
? path.split('/').map(encodeURIComponent).join('/')
: encodeURIComponent(path);
return `${scheme}://${subdomain}.${apex}/${encodedPath}${suffix}`;
}
export interface Room {
id: number;
name: string;
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { api, get } from '../lib/api';
import type { CommunityBoardState, Player, Room } from '../lib/types';
import { assetCdnUrl, imageCdnUrl, type CommunityBoardState, type Player, type Room } from '../lib/types';
import { useApi } from '../lib/useApi';
import { PageHeader } from '../components/PageHeader';
import { useToast } from '../components/Toast';
@@ -276,8 +276,7 @@ function InstagramImageRow({
}
};
const apex = typeof window !== 'undefined' && window.location.host.endsWith('localhost') ? 'localhost' : 'rec.net';
const preview = img.imageName ? `https://img.${apex}/${encodeURIComponent(img.imageName)}?width=128&sig=p1` : null;
const preview = imageCdnUrl(img.imageName, 'width=128&sig=p1');
return (
<div className="rounded border border-ink-800 p-2 flex gap-2 items-start">
@@ -439,15 +438,14 @@ function VideoRow({
// because it overwrites thumbnailBlobName).
const [autoThumb, setAutoThumb] = useState(true);
const apex = typeof window !== 'undefined' && window.location.host.endsWith('localhost') ? 'localhost' : 'rec.net';
const thumbPreview = video.thumbnailBlobName ? `https://img.${apex}/${encodeURIComponent(video.thumbnailBlobName)}?width=160&sig=p1` : null;
// The watch fetches videos under cdn.{apex}/video/<BlobName>
const thumbPreview = imageCdnUrl(video.thumbnailBlobName, 'width=160&sig=p1');
// The watch fetches videos under cdn.<apex>/video/<BlobName>
// (RecNet/CommunityBoard.txt:1068 — `String.Concat("/video/", BlobName)`).
// Match that path here so the inline admin preview hits the same
// route the watch does; previously we built a bare `cdn.{apex}/<name>`
// URL which 404'd because CdnController's catch-all regex didn't
// include video extensions.
const videoUrl = video.blobName ? `https://cdn.${apex}/video/${encodeURIComponent(video.blobName)}` : null;
const videoUrl = assetCdnUrl(video.blobName ? `video/${video.blobName}` : null);
// Video file picker → POST /communityboard/video/upload (timeout
// generous: 100 MB at 5 MB/s is 20s of body time alone, plus S3
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { api, get } from '../lib/api';
import { imageCdnUrl } from '../lib/types';
import { PageHeader } from '../components/PageHeader';
import { useToast } from '../components/Toast';
import { Plus, Trash } from '../components/Icons';
@@ -185,8 +186,7 @@ function TipEditor({
}
};
const apex = typeof window !== 'undefined' && window.location.host.endsWith('localhost') ? 'localhost' : 'rec.net';
const preview = tip.imageName ? `https://img.${apex}/${encodeURIComponent(tip.imageName)}?width=160&sig=p1` : null;
const preview = imageCdnUrl(tip.imageName, 'width=160&sig=p1');
return (
<div className="card !p-4 space-y-3">