import type { PublicAssetManifest } from './types.ts'; const syntheticOrigin = 'https://portfolio-assets.invalid'; const normalizeAssetBase = (base: string): string => { if ( !base.startsWith('/') || base.startsWith('//') || /[\u0000-\u001f\u007f\\%?#]/u.test(base) || base.split('/').some((segment) => segment === '.' || segment === '..') ) { throw new Error('Asset URL base must be a safe absolute path.'); } const resolved = new URL(base, syntheticOrigin); if (resolved.origin !== syntheticOrigin) { throw new Error('Asset URL base must remain under the synthetic origin.'); } const normalizedPath = resolved.pathname.replace(/\/+/gu, '/'); return normalizedPath.endsWith('/') ? normalizedPath : `${normalizedPath}/`; }; export const assetUrl = (manifest: PublicAssetManifest, assetId: string, base = '/assets/'): string => { const asset = manifest.assets.find((candidate) => candidate.id === assetId); if (asset === undefined) { throw new Error(`Unknown asset ID: ${assetId}`); } return `${normalizeAssetBase(base)}${encodeURIComponent(asset.fileName)}`; };