fix: 매니페스트 로드 실패 시 로컬 자산으로 안전하게 대체되지 않는 버그 수정
- normalizeMediaManifest에서 빈 데이터 수신 시 DEFAULT_MEDIA_MANIFEST를 활용하도록 수정 - 매니페스트 로드 실패 시 상세 정보(URL, 상태 코드)를 에러 메시지에 포함 - useMediaCatalog에서 fetch 에러 발생 시 명시적으로 로컬 기반 매니페스트를 적용하도록 보강
This commit is contained in:
@@ -36,17 +36,26 @@ export const mediaManifestApi = {
|
|||||||
return DEFAULT_MEDIA_MANIFEST;
|
return DEFAULT_MEDIA_MANIFEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(MEDIA_MANIFEST_URL, {
|
try {
|
||||||
method: 'GET',
|
const response = await fetch(MEDIA_MANIFEST_URL, {
|
||||||
cache: MEDIA_MANIFEST_FETCH_CACHE,
|
method: 'GET',
|
||||||
signal,
|
cache: MEDIA_MANIFEST_FETCH_CACHE,
|
||||||
});
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(copy.media.manifestLoadFailed);
|
throw new Error(`${copy.media.manifestLoadFailed} (${response.status} at ${MEDIA_MANIFEST_URL})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = (await response.json()) as Partial<MediaManifest>;
|
||||||
|
return normalizeMediaManifest(payload);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`${copy.media.manifestLoadFailed}: ${error instanceof Error ? error.message : String(error)} (URL: ${MEDIA_MANIFEST_URL})`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = (await response.json()) as Partial<MediaManifest>;
|
|
||||||
return normalizeMediaManifest(payload);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -99,18 +99,18 @@ const mergeSoundAssets = (manifest: MediaManifest) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const normalizeMediaManifest = (manifest: Partial<MediaManifest> | null | undefined): MediaManifest => {
|
export const normalizeMediaManifest = (manifest: Partial<MediaManifest> | null | undefined): MediaManifest => {
|
||||||
const mergedManifest: MediaManifest = {
|
const baseManifest: MediaManifest = {
|
||||||
version: manifest?.version ?? DEFAULT_MEDIA_MANIFEST.version,
|
version: manifest?.version ?? DEFAULT_MEDIA_MANIFEST.version,
|
||||||
updatedAt: manifest?.updatedAt ?? DEFAULT_MEDIA_MANIFEST.updatedAt,
|
updatedAt: manifest?.updatedAt ?? DEFAULT_MEDIA_MANIFEST.updatedAt,
|
||||||
cdnBaseUrl: manifest?.cdnBaseUrl ?? DEFAULT_MEDIA_MANIFEST.cdnBaseUrl,
|
cdnBaseUrl: manifest?.cdnBaseUrl ?? DEFAULT_MEDIA_MANIFEST.cdnBaseUrl,
|
||||||
scenes: manifest?.scenes ?? [],
|
scenes: manifest?.scenes ?? DEFAULT_MEDIA_MANIFEST.scenes,
|
||||||
sounds: manifest?.sounds ?? [],
|
sounds: manifest?.sounds ?? DEFAULT_MEDIA_MANIFEST.sounds,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...mergedManifest,
|
...baseManifest,
|
||||||
scenes: mergeSceneAssets(mergedManifest),
|
scenes: mergeSceneAssets(baseManifest),
|
||||||
sounds: mergeSoundAssets(mergedManifest),
|
sounds: mergeSoundAssets(baseManifest),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,18 @@ const readMediaManifest = async (signal?: AbortSignal): Promise<MediaCatalogLoad
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const nextError = error instanceof Error ? error.message : null;
|
// Only return abort errors up the chain if we're not using fallback
|
||||||
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextError = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
// Explicitly use normalize with null to get a pure fallback manifest
|
||||||
|
const fallbackManifest = normalizeMediaManifest(null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
manifest: manifestCache,
|
manifest: fallbackManifest,
|
||||||
error: nextError,
|
error: nextError,
|
||||||
usedFallbackManifest: true,
|
usedFallbackManifest: true,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user