| ... | ... | @@ -21,15 +21,35 @@ type LicenseManifestState = |
| 21 | 21 | | { readonly status: "error"; readonly message: string } |
| 22 | 22 | | { readonly status: "ready"; readonly manifest: ThirdPartyLicenseManifest }; |
| 23 | 23 | |
| 24 | | async function loadLicenseManifest(signal: AbortSignal): Promise<ThirdPartyLicenseManifest> { |
| 25 | | const response = await fetch( |
| 26 | | `${import.meta.env.BASE_URL.replace(/\/$/, "")}/third-party-licenses.json`, |
| 27 | | { signal }, |
| 28 | | ); |
| 29 | | if (!response.ok) { |
| 30 | | throw new Error(`The license manifest request failed with status ${String(response.status)}.`); |
| 24 | let licenseManifestPromise: Promise<ThirdPartyLicenseManifest> | undefined; |
| 25 | |
| 26 | async function fetchLicenseManifest(): Promise<ThirdPartyLicenseManifest> { |
| 27 | const signal = AbortSignal.timeout(30_000); |
| 28 | try { |
| 29 | const response = await fetch( |
| 30 | `${import.meta.env.BASE_URL.replace(/\/$/, "")}/third-party-licenses.json`, |
| 31 | { signal }, |
| 32 | ); |
| 33 | if (!response.ok) { |
| 34 | throw new Error( |
| 35 | `The license manifest request failed with status ${String(response.status)}.`, |
| 36 | ); |
| 37 | } |
| 38 | return decodeThirdPartyLicenseManifest((await response.json()) as unknown); |
| 39 | } catch (error) { |
| 40 | if (signal.aborted) { |
| 41 | throw new Error("The license manifest took too long to load.", { cause: error }); |
| 42 | } |
| 43 | throw error; |
| 31 | 44 | } |
| 32 | | return decodeThirdPartyLicenseManifest((await response.json()) as unknown); |
| 45 | } |
| 46 | |
| 47 | function loadLicenseManifest(): Promise<ThirdPartyLicenseManifest> { |
| 48 | licenseManifestPromise ??= fetchLicenseManifest().catch((error: unknown) => { |
| 49 | licenseManifestPromise = undefined; |
| 50 | throw error; |
| 51 | }); |
| 52 | return licenseManifestPromise; |
| 33 | 53 | } |
| 34 | 54 | |
| 35 | 55 | function LicenseNoticeRow({ |
| ... | ... | @@ -198,19 +218,23 @@ export function OpenSourceLicensesPanel() { |
| 198 | 218 | const [requestVersion, setRequestVersion] = useState(0); |
| 199 | 219 | |
| 200 | 220 | useEffect(() => { |
| 201 | | const controller = new AbortController(); |
| 221 | let cancelled = false; |
| 202 | 222 | setState({ status: "loading" }); |
| 203 | | void loadLicenseManifest(controller.signal).then( |
| 204 | | (manifest) => setState({ status: "ready", manifest }), |
| 223 | void loadLicenseManifest().then( |
| 224 | (manifest) => { |
| 225 | if (!cancelled) setState({ status: "ready", manifest }); |
| 226 | }, |
| 205 | 227 | (error: unknown) => { |
| 206 | | if (controller.signal.aborted) return; |
| 228 | if (cancelled) return; |
| 207 | 229 | setState({ |
| 208 | 230 | status: "error", |
| 209 | 231 | message: error instanceof Error ? error.message : "The license manifest could not load.", |
| 210 | 232 | }); |
| 211 | 233 | }, |
| 212 | 234 | ); |
| 213 | | return () => controller.abort(); |
| 235 | return () => { |
| 236 | cancelled = true; |
| 237 | }; |
| 214 | 238 | }, [requestVersion]); |
| 215 | 239 | |
| 216 | 240 | const entries = state.status === "ready" ? state.manifest.entries : []; |