From 67dad45214a0dcda9d79abb4c731b7705ceb7f2d Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Mon, 29 Jul 2024 06:30:56 +0200 Subject: [PATCH] fix: fetch timeout for external requests to small (#881) * fix: fetch timeout for external requests to small * fix: format issue * fix: move clear timeout for fetch to finally --- packages/api/src/router/location.ts | 3 ++- packages/api/src/router/widgets/weather.ts | 3 ++- packages/common/src/fetch-with-timeout.ts | 16 ++++++++++++++++ packages/common/src/index.ts | 1 + packages/icons/package.json | 3 ++- .../src/repositories/github.icon-repository.ts | 4 +++- .../src/repositories/jsdelivr.icon-repository.ts | 4 +++- packages/ping/src/index.ts | 4 ++-- pnpm-lock.yaml | 3 +++ 9 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 packages/common/src/fetch-with-timeout.ts diff --git a/packages/api/src/router/location.ts b/packages/api/src/router/location.ts index 11e6e6770..c2060314b 100644 --- a/packages/api/src/router/location.ts +++ b/packages/api/src/router/location.ts @@ -1,3 +1,4 @@ +import { fetchWithTimeout } from "@homarr/common"; import type { z } from "@homarr/validation"; import { validation } from "@homarr/validation"; @@ -8,7 +9,7 @@ export const locationRouter = createTRPCRouter({ .input(validation.location.searchCity.input) .output(validation.location.searchCity.output) .query(async ({ input }) => { - const res = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`); + const res = await fetchWithTimeout(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`); return (await res.json()) as z.infer; }), }); diff --git a/packages/api/src/router/widgets/weather.ts b/packages/api/src/router/widgets/weather.ts index 665fdedd4..3a53157f8 100644 --- a/packages/api/src/router/widgets/weather.ts +++ b/packages/api/src/router/widgets/weather.ts @@ -1,10 +1,11 @@ +import { fetchWithTimeout } from "@homarr/common"; import { validation } from "@homarr/validation"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const weatherRouter = createTRPCRouter({ atLocation: publicProcedure.input(validation.widget.weather.atLocationInput).query(async ({ input }) => { - const res = await fetch( + const res = await fetchWithTimeout( `https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min¤t_weather=true&timezone=auto`, ); const json: unknown = await res.json(); diff --git a/packages/common/src/fetch-with-timeout.ts b/packages/common/src/fetch-with-timeout.ts new file mode 100644 index 000000000..48353415f --- /dev/null +++ b/packages/common/src/fetch-with-timeout.ts @@ -0,0 +1,16 @@ +/** + * Same as fetch, but with a timeout of 10 seconds. + * https://stackoverflow.com/questions/46946380/fetch-api-request-timeout + * @param param0 fetch arguments + * @returns fetch response + */ +export const fetchWithTimeout = (...[url, requestInit]: Parameters) => { + const controller = new AbortController(); + + // 10 seconds timeout: + const timeoutId = setTimeout(() => controller.abort(), 10000); + + return fetch(url, { signal: controller.signal, ...requestInit }).finally(() => { + clearTimeout(timeoutId); + }); +}; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 0b6d4cb65..45141ab45 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -8,3 +8,4 @@ export * from "./url"; export * from "./number"; export * from "./error"; export * from "./encryption"; +export * from "./fetch-with-timeout"; diff --git a/packages/icons/package.json b/packages/icons/package.json index fd72d8133..a6b345042 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -21,7 +21,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@homarr/log": "workspace:^0.1.0" + "@homarr/log": "workspace:^0.1.0", + "@homarr/common": "workspace:^0.1.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/icons/src/repositories/github.icon-repository.ts b/packages/icons/src/repositories/github.icon-repository.ts index 780f280da..695ade2a0 100644 --- a/packages/icons/src/repositories/github.icon-repository.ts +++ b/packages/icons/src/repositories/github.icon-repository.ts @@ -1,3 +1,5 @@ +import { fetchWithTimeout } from "@homarr/common"; + import type { IconRepositoryLicense } from "../types/icon-repository-license"; import type { RepositoryIconGroup } from "../types/repository-icon-group"; import { IconRepository } from "./icon-repository"; @@ -19,7 +21,7 @@ export class GitHubIconRepository extends IconRepository { throw new Error("Repository URLs are required for this repository"); } - const response = await fetch(this.repositoryIndexingUrl); + const response = await fetchWithTimeout(this.repositoryIndexingUrl); const listOfFiles = (await response.json()) as GitHubApiResponse; return { diff --git a/packages/icons/src/repositories/jsdelivr.icon-repository.ts b/packages/icons/src/repositories/jsdelivr.icon-repository.ts index 919cdd2ba..1c153d5b2 100644 --- a/packages/icons/src/repositories/jsdelivr.icon-repository.ts +++ b/packages/icons/src/repositories/jsdelivr.icon-repository.ts @@ -1,3 +1,5 @@ +import { fetchWithTimeout } from "@homarr/common"; + import type { IconRepositoryLicense } from "../types/icon-repository-license"; import type { RepositoryIconGroup } from "../types/repository-icon-group"; import { IconRepository } from "./icon-repository"; @@ -15,7 +17,7 @@ export class JsdelivrIconRepository extends IconRepository { } protected async getAllIconsInternalAsync(): Promise { - const response = await fetch(this.repositoryIndexingUrl); + const response = await fetchWithTimeout(this.repositoryIndexingUrl); const listOfFiles = (await response.json()) as JsdelivrApiResponse; return { diff --git a/packages/ping/src/index.ts b/packages/ping/src/index.ts index 3dafac233..93dbc88c4 100644 --- a/packages/ping/src/index.ts +++ b/packages/ping/src/index.ts @@ -1,9 +1,9 @@ -import { extractErrorMessage } from "@homarr/common"; +import { extractErrorMessage, fetchWithTimeout } from "@homarr/common"; import { logger } from "@homarr/log"; export const sendPingRequestAsync = async (url: string) => { try { - return await fetch(url).then((response) => ({ statusCode: response.status })); + return await fetchWithTimeout(url).then((response) => ({ statusCode: response.status })); } catch (error) { logger.error("packages/ping/src/index.ts:", error); return { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e50b55549..067208e38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -868,6 +868,9 @@ importers: packages/icons: dependencies: + '@homarr/common': + specifier: workspace:^0.1.0 + version: link:../common '@homarr/log': specifier: workspace:^0.1.0 version: link:../log