fix(app): stop reprobing image format support on every build (#1976)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:56:26 +02:00
1 parent 66791d3ca2
commit 7a33b3198a
1 file changed
+5 -99
@@ -1,16 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import Config from '@app/features/app/config/Config';
import {isSvgMimeType, normalizeImageMimeType} from '@app/features/expressions/utils/ImageUploadFileUtils';
import AppStorage from '@app/features/platform/state/PersistentStorage';
import {useEffect, useState, useSyncExternalStore} from 'react';
export type MediaFormat = 'avif' | 'webp' | 'apng' | 'jxl';
export interface CapabilityResult {
interface CapabilityResult {
avif: boolean;
webp: boolean;
apng: boolean;
jxl: boolean;
probedAt: number;
uaKey: string;
@@ -19,16 +14,12 @@ export interface CapabilityResult {
const AVIF_PROBE =
'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgIYAQQUDAJEBQYJlAQUAAAAB1FmDuOmIs=';
const WEBP_PROBE = 'data:image/webp;base64,UklGRhwAAABXRUJQVlA4TBAAAAAvAAAAAAfQ//73v/+BiOh/AAA=';
const APNG_PROBE =
'data:image/apng;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAALOZWzgAAAANSURBVAjXY/j//z8DAAj8Av6IXwbgAAAAAElFTkSuQmCC';
const JXL_PROBE = 'data:image/jxl;base64,/wr6PwH4TWFvLnVMkM4=';
const STORAGE_KEY = 'fluxer:media_caps:v1';
const PROBE_TIMEOUT_MS = 1500;
function buildUaKey(): string {
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : 'ssr';
const version = Config.PUBLIC_BUILD_VERSION ?? 'dev';
return `${version}:${ua}`;
return typeof navigator !== 'undefined' ? navigator.userAgent : 'ssr';
}
function isRecord(value: unknown): value is Record<string, unknown> {
@@ -67,7 +58,6 @@ function readCached(): CapabilityResult | null {
return {
avif: parsed.avif === true,
webp: parsed.webp === true,
apng: parsed.apng === true,
jxl: parsed.jxl === true,
probedAt: typeof parsed.probedAt === 'number' ? parsed.probedAt : 0,
uaKey: parsed.uaKey,
@@ -86,28 +76,16 @@ function writeCached(result: CapabilityResult): void {
let cached: CapabilityResult | null = null;
let inflight: Promise<CapabilityResult> | null = null;
const listeners = new Set<() => void>();
function notify(): void {
for (const fn of listeners) fn();
}
async function runProbes(): Promise<CapabilityResult> {
const [avif, webp, apng, jxl] = await Promise.all([
probeOne(AVIF_PROBE),
probeOne(WEBP_PROBE),
probeOne(APNG_PROBE),
probeOne(JXL_PROBE),
]);
return {avif, webp, apng, jxl, probedAt: Date.now(), uaKey: buildUaKey()};
const [avif, webp, jxl] = await Promise.all([probeOne(AVIF_PROBE), probeOne(WEBP_PROBE), probeOne(JXL_PROBE)]);
return {avif, webp, jxl, probedAt: Date.now(), uaKey: buildUaKey()};
}
export async function probeMediaCapabilities(): Promise<CapabilityResult> {
async function probeMediaCapabilities(): Promise<CapabilityResult> {
if (cached) return cached;
const fromStorage = readCached();
if (fromStorage) {
cached = fromStorage;
notify();
return fromStorage;
}
if (inflight) return inflight;
@@ -115,65 +93,12 @@ export async function probeMediaCapabilities(): Promise<CapabilityResult> {
const result = await runProbes();
cached = result;
writeCached(result);
notify();
inflight = null;
return result;
})();
return inflight;
}
export function getMediaCapabilitiesSync(): CapabilityResult | null {
if (cached) return cached;
cached = readCached();
return cached;
}
type CroppableAssetKind = 'avatar' | 'guild_icon' | 'banner' | 'splash' | 'embed_splash' | 'emoji' | 'sticker';
function pickAnimated(kind: CroppableAssetKind, caps: CapabilityResult): MediaFormat | null {
switch (kind) {
case 'emoji':
case 'sticker':
case 'avatar':
case 'guild_icon':
case 'banner':
case 'splash':
case 'embed_splash':
if (caps.webp) return 'webp';
if (caps.apng) return 'apng';
return null;
}
}
function pickStatic(kind: CroppableAssetKind, caps: CapabilityResult): MediaFormat | null {
switch (kind) {
case 'avatar':
case 'guild_icon':
case 'banner':
case 'splash':
case 'embed_splash':
if (caps.avif) return 'avif';
if (caps.webp) return 'webp';
return null;
case 'emoji':
case 'sticker':
if (caps.webp) return 'webp';
if (caps.avif) return 'avif';
return null;
}
}
export const MediaCapabilities = {
probe: probeMediaCapabilities,
getSync: getMediaCapabilitiesSync,
bestFormatFor(kind: CroppableAssetKind, animated: boolean): MediaFormat | null {
const caps = getMediaCapabilitiesSync();
if (!caps) return null;
return animated ? pickAnimated(kind, caps) : pickStatic(kind, caps);
},
canCropFormat,
};
export async function canCropFormat(mime: string): Promise<boolean> {
const m = normalizeImageMimeType(mime);
if (m === 'image/png' || m === 'image/apng' || m === 'image/jpeg' || m === 'image/gif') {
@@ -203,22 +128,3 @@ async function canDecodeViaImage(mime: string): Promise<boolean> {
return false;
}
}
export function useCapabilityHint(): CapabilityResult | null {
const subscribe = (cb: () => void): (() => void) => {
listeners.add(cb);
return () => {
listeners.delete(cb);
};
};
const getSnapshot = (): CapabilityResult | null => cached;
const getServerSnapshot = (): CapabilityResult | null => null;
const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
const [bootstrapped, setBootstrapped] = useState(value);
useEffect(() => {
if (!bootstrapped) {
void probeMediaCapabilities().then((r) => setBootstrapped(r));
}
}, [bootstrapped]);
return value ?? bootstrapped;
}