/** * Where an uploaded image is fetched from. * * Image paths are stored in the database as site-relative — `/uploads/.jpg` * — and that is deliberate: a stored value outlives any hostname it might be * baked with, and rewriting them would be a migration to undo the day the host * changes. So the origin is joined on here instead, from a value the server * sends at runtime (#103). * * Empty base means the app's own origin, which is the default and is what local * development has — there is no second hostname on a laptop. Point it at one in * production and user-supplied files stop sharing an origin with the * application. * * The base is cached in a module variable rather than threaded through context, * because it is a deployment constant: one value, fetched once, never changing * while the tab is open. Anything rendered before the fetch resolves gets the * relative path, which still works — the app's origin goes on serving these * files, hardened, and the separate host points at the same directory. It * simply misses the isolation for that first paint. */ let base = ''; /** * Called once, from the config the app already fetches at startup. Trailing * slashes are trimmed on the server, and again here, so that a base configured * either way joins cleanly with a path that always begins with one. */ export function setUploadsBase(value: string | undefined | null): void { // Trimmed with a loop rather than a `/+$/` regex, which backtracks. let trimmed = value ?? ''; while (trimmed.endsWith('/')) trimmed = trimmed.slice(0, -1); base = trimmed; } export function uploadUrl(storedPath: string): string { // Absolute already, or empty. Either way there is nothing to join: returning // it untouched means a value that was somehow stored absolute keeps working // rather than being mangled into a nonsense URL. if (!base || !storedPath.startsWith('/')) return storedPath; return `${base}${storedPath}`; }