diff --git a/src/App.vue b/src/App.vue index 55804234a..f266cc52f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,7 +26,6 @@ diff --git a/src/components/views/OnlineModList.vue b/src/components/views/OnlineModList.vue index 6fa0a4f8f..44523979f 100644 --- a/src/components/views/OnlineModList.vue +++ b/src/components/views/OnlineModList.vue @@ -2,7 +2,7 @@
{ return `https://thunderstore.io/api/experimental/legacyprofile/get/${profileImportCode}/`; @@ -40,7 +41,10 @@ async function createProfile(payload: string): Promise> { - return await Axios.get(getProfileUrl(profileImportCode)); + const url = CdnProvider.addCdnQueryParameter( + getProfileUrl(profileImportCode) + ); + return await Axios.get(url); } export const ProfileApiClient = { diff --git a/src/utils/HttpUtils.ts b/src/utils/HttpUtils.ts new file mode 100644 index 000000000..73d177426 --- /dev/null +++ b/src/utils/HttpUtils.ts @@ -0,0 +1,38 @@ +import axios from "axios"; + +const newAbortSignal = (timeoutMs: number) => { + const abortController = new AbortController(); + setTimeout(() => abortController.abort(), timeoutMs); + return abortController.signal; +}; + +/** + * Return Axios instance with timeouts enabled. + * @param responseTimeout Time (in ms) the server has to generate a + * response once a connection is established. Defaults to 5 seconds. + * @param connectionTimeout Time (in ms) the request has in total, + * including opening the connection and receiving the response. + * Defaults to 10 seconds. + * @returns AxiosInstance + */ +export const getAxiosWithTimeouts = (responseTimeout = 5000, connectionTimeout = 10000) => { + const instance = axios.create({timeout: responseTimeout}); + + // Use interceptors to have a fresh abort signal for each request, + // so the instance can be shared by multiple requests. + instance.interceptors.request.use((config) => { + config.signal = newAbortSignal(connectionTimeout); + return config; + }); + + return instance; +}; + +export const isNetworkError = (responseOrError: unknown) => + responseOrError instanceof Error && responseOrError.message === "Network Error"; + +/** + * Is the Error thrown by Axios request caused by a response timeout? + */ +export const isResponseTimeout = (error: unknown) => + error instanceof Error && /timeout of (\d+)ms exceeded/i.test(error.message); diff --git a/src/utils/UrlUtils.ts b/src/utils/UrlUtils.ts new file mode 100644 index 000000000..bd7481586 --- /dev/null +++ b/src/utils/UrlUtils.ts @@ -0,0 +1,33 @@ +/** + * Append given search parameters to an URL which may or may not already + * have search parameters. + * + * Existing search parameters with a key present in the new parameters + * will be overwritten. + */ +export const addOrReplaceSearchParams = (url: string, paramString: string) => { + const newUrl = new URL(url); + newUrl.search = new URLSearchParams( + Object.assign( + {}, + Object.fromEntries(newUrl.searchParams), + Object.fromEntries(new URLSearchParams(paramString)) + ) + ).toString(); + return newUrl.href; +} + +/** + * Replace URL host, i.e. the domain and the port number. + * + * @param url e.g. "https://thunderstore.io/foo" + * @param domainAndPort e.g. "thunderstore.dev" or "thunderstore.dev:8080" + * @returns e.g. "https://thunderstore.dev:8080/foo" + */ +export const replaceHost = (url: string, domainAndPort: string) => { + const newValues = domainAndPort.split(":"); + const newUrl = new URL(url); + newUrl.hostname = newValues[0]; + newUrl.port = newValues[1] || ""; + return newUrl.href; +};