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;
+};