diff --git a/frontend/src/components/VErrorSection/VErrorSection.vue b/frontend/src/components/VErrorSection/VErrorSection.vue index 42363862e6a..51edfcf524e 100644 --- a/frontend/src/components/VErrorSection/VErrorSection.vue +++ b/frontend/src/components/VErrorSection/VErrorSection.vue @@ -18,10 +18,9 @@ diff --git a/frontend/src/constants/errors.ts b/frontend/src/constants/errors.ts index 9b48069ac10..7cf4057954e 100644 --- a/frontend/src/constants/errors.ts +++ b/frontend/src/constants/errors.ts @@ -5,7 +5,45 @@ export const NO_RESULT = "NO_RESULT" export const SERVER_TIMEOUT = "SERVER_TIMEOUT" +export const ECONNABORTED = "ECONNABORTED" -export const errorCodes = [NO_RESULT, SERVER_TIMEOUT] as const +export const ERR_UNKNOWN = "ERR_UNKNOWN" + +export const customErrorCodes = [ + NO_RESULT, + SERVER_TIMEOUT, + ECONNABORTED, + ERR_UNKNOWN, +] as const + +/** + * The error codes Axios uses. + * @see https://github.com/axios/axios/blob/9588fcdec8aca45c3ba2f7968988a5d03f23168c/lib/core/AxiosError.js#L57C2-L71 + */ +const axiosErrorCodes = [ + "ERR_BAD_OPTION_VALUE", + "ERR_BAD_OPTION", + "ECONNABORTED", + "ETIMEDOUT", + "ERR_NETWORK", + "ERR_FR_TOO_MANY_REDIRECTS", + "ERR_DEPRECATED", + "ERR_BAD_RESPONSE", + "ERR_BAD_REQUEST", + "ERR_CANCELED", + "ERR_NOT_SUPPORT", + "ERR_INVALID_URL", +] as const + +export const errorCodes = [...customErrorCodes, ...axiosErrorCodes] as const + +export const clientSideErrorCodes: readonly ErrorCode[] = [ + ECONNABORTED, + SERVER_TIMEOUT, + NO_RESULT, + ERR_UNKNOWN, + "ERR_NETWORK", + "ETIMEDOUT", +] as const export type ErrorCode = (typeof errorCodes)[number] diff --git a/frontend/src/middleware/search.ts b/frontend/src/middleware/search.ts index f0f2cd78c87..93a19c545b6 100644 --- a/frontend/src/middleware/search.ts +++ b/frontend/src/middleware/search.ts @@ -1,6 +1,7 @@ import { useSearchStore } from "~/stores/search" import { useMediaStore } from "~/stores/media" -import { NO_RESULT } from "~/constants/errors" + +import { handledClientSide } from "~/utils/errors" import type { Middleware } from "@nuxt/types" @@ -44,13 +45,7 @@ export const searchMiddleware: Middleware = async ({ const results = await mediaStore.fetchMedia() const fetchingError = mediaStore.fetchState.fetchingError - // NO_RESULTS and timeout are handled client-side, for other errors show server error page - if ( - !results && - fetchingError && - !fetchingError?.message?.includes(NO_RESULT) && - !fetchingError?.message?.includes("timeout") - ) { + if (!results && fetchingError && !handledClientSide(fetchingError)) { nuxtError(fetchingError) } } diff --git a/frontend/src/middleware/single-result.ts b/frontend/src/middleware/single-result.ts index ca9f2f81b65..83babf0218e 100644 --- a/frontend/src/middleware/single-result.ts +++ b/frontend/src/middleware/single-result.ts @@ -1,6 +1,7 @@ import { useSingleResultStore } from "~/stores/media/single-result" import { useSearchStore } from "~/stores/search" import { useRelatedMediaStore } from "~/stores/media/related-media" +import { isRetriable } from "~/utils/errors" import { AUDIO, IMAGE } from "~/constants/media" @@ -17,11 +18,15 @@ export const singleResultMiddleware: Middleware = async ({ if (process.server) { const media = await singleResultStore.fetch(mediaType, route.params.id) - await useRelatedMediaStore($pinia).fetchMedia(mediaType, route.params.id) if (!media) { - error(singleResultStore.fetchState.fetchingError ?? {}) + const fetchingError = singleResultStore.fetchState.fetchingError + + if (fetchingError && !isRetriable(fetchingError)) { + error(fetchingError ?? {}) + } } + await useRelatedMediaStore($pinia).fetchMedia(mediaType, route.params.id) } else { // Client-side rendering singleResultStore.setMediaById(mediaType, route.params.id) diff --git a/frontend/src/pages/audio/_id/index.vue b/frontend/src/pages/audio/_id/index.vue index 28afd835bd7..8091a2b9dfc 100644 --- a/frontend/src/pages/audio/_id/index.vue +++ b/frontend/src/pages/audio/_id/index.vue @@ -1,6 +1,11 @@