feat(http): add proxy support (#4721)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
@@ -36,7 +37,12 @@ export const locationRouter = createTRPCRouter({
|
||||
.input(locationSearchCityInput)
|
||||
.output(locationSearchCityOutput)
|
||||
.query(async ({ input }) => {
|
||||
const res = await fetchWithTimeoutAsync(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
|
||||
const res = await withTimeoutAsync(async (signal) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`,
|
||||
{ signal },
|
||||
);
|
||||
});
|
||||
return (await res.json()) as z.infer<typeof locationSearchCityOutput>;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import { Agent } from "undici";
|
||||
import { EnvHttpProxyAgent } from "undici";
|
||||
|
||||
import type { ILogger } from "@homarr/core/infrastructure/logs";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
@@ -7,16 +7,15 @@ import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
// The below import statement initializes dns-caching
|
||||
import "@homarr/core/infrastructure/dns/init";
|
||||
|
||||
interface HttpAgentOptions extends Agent.Options {
|
||||
interface HttpAgentOptions extends EnvHttpProxyAgent.Options {
|
||||
logger?: ILogger;
|
||||
}
|
||||
|
||||
export class UndiciHttpAgent extends Agent {
|
||||
export class UndiciHttpAgent extends EnvHttpProxyAgent {
|
||||
private logger: ILogger;
|
||||
|
||||
constructor(props?: HttpAgentOptions) {
|
||||
super(props);
|
||||
|
||||
this.logger = props?.logger ?? createLogger({ module: "httpAgent" });
|
||||
}
|
||||
|
||||
|
||||
@@ -42,12 +42,13 @@ export const createCertificateAgentAsync = async (override?: {
|
||||
};
|
||||
|
||||
export const createHttpsAgentAsync = async (override?: Pick<AgentOptions, "ca" | "checkServerIdentity">) => {
|
||||
return new HttpsAgent(
|
||||
override ?? {
|
||||
ca: await getAllTrustedCertificatesAsync(),
|
||||
checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()),
|
||||
},
|
||||
);
|
||||
return new HttpsAgent({
|
||||
ca: await getAllTrustedCertificatesAsync(),
|
||||
checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()),
|
||||
// Override the ca and checkServerIdentity if provided
|
||||
...override,
|
||||
proxyEnv: process.env,
|
||||
});
|
||||
};
|
||||
|
||||
export const createAxiosCertificateInstanceAsync = async (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { TestLogger } from "../logs";
|
||||
|
||||
vi.mock("undici", () => {
|
||||
return {
|
||||
Agent: class Agent {
|
||||
EnvHttpProxyAgent: class EnvHttpProxyAgent {
|
||||
dispatch(_options: Dispatcher.DispatchOptions, _handler: Dispatcher.DispatchHandler): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { parse } from "path";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
@@ -19,11 +20,12 @@ export class GitHubIconRepository extends IconRepository {
|
||||
}
|
||||
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
if (!this.repositoryIndexingUrl || !this.repositoryBlobUrlTemplate) {
|
||||
const url = this.repositoryIndexingUrl;
|
||||
if (!url || !this.repositoryBlobUrlTemplate) {
|
||||
throw new Error("Repository URLs are required for this repository");
|
||||
}
|
||||
|
||||
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
|
||||
const response = await withTimeoutAsync(async (signal) => fetchWithTrustedCertificatesAsync(url, { signal }));
|
||||
const listOfFiles = (await response.json()) as GitHubApiResponse;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { parse } from "path";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
@@ -19,7 +20,9 @@ export class JsdelivrIconRepository extends IconRepository {
|
||||
}
|
||||
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
|
||||
const response = await withTimeoutAsync(async (signal) =>
|
||||
fetchWithTrustedCertificatesAsync(this.repositoryIndexingUrl, { signal }),
|
||||
);
|
||||
const listOfFiles = (await response.json()) as JsdelivrApiResponse;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -11,7 +12,9 @@ export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHand
|
||||
async requestAsync(input: { domain: string; isBedrockServer: boolean }) {
|
||||
const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`;
|
||||
|
||||
const response = await fetchWithTimeoutAsync(`https://api.mcsrvstat.us${path}`);
|
||||
const response = await withTimeoutAsync(async (signal) =>
|
||||
fetchWithTrustedCertificatesAsync(`https://api.mcsrvstat.us${path}`, { signal }),
|
||||
);
|
||||
return responseSchema.parse(await response.json());
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,9 +10,12 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "fetchStockPriceResult",
|
||||
widgetKind: "stockPrice",
|
||||
async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) {
|
||||
const response = await fetchWithTimeoutAsync(
|
||||
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
||||
);
|
||||
const response = await withTimeoutAsync(async (signal) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
||||
{ signal },
|
||||
);
|
||||
});
|
||||
const data = dataSchema.parse(await response.json());
|
||||
|
||||
if ("error" in data) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Octokit } from "octokit";
|
||||
import { compareSemVer, isValidSemVer } from "semver-parser";
|
||||
|
||||
import { env } from "@homarr/common/env";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { createChannelWithLatestAndEvents } from "@homarr/redis";
|
||||
import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler";
|
||||
@@ -23,7 +23,7 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({
|
||||
|
||||
const octokit = new Octokit({
|
||||
request: {
|
||||
fetch: fetchWithTimeoutAsync,
|
||||
fetch: fetchWithTrustedCertificatesAsync,
|
||||
},
|
||||
});
|
||||
const releases = await octokit.rest.repos.listReleases({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,9 +10,12 @@ export const weatherRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "weatherAtLocation",
|
||||
widgetKind: "weather",
|
||||
async requestAsync(input: { latitude: number; longitude: number }) {
|
||||
const res = await fetchWithTimeoutAsync(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
||||
);
|
||||
const res = await withTimeoutAsync(async (signal) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
||||
{ signal },
|
||||
);
|
||||
});
|
||||
const json: unknown = await res.json();
|
||||
const weather = await atLocationOutput.parseAsync(json);
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user