mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-05 06:09:16 +01:00
refactor(http): move to core package (#4690)
This commit is contained in:
@@ -23,7 +23,6 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/boards": "workspace:^0.1.0",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { setGlobalDispatcher } from "undici";
|
||||
|
||||
import { LoggingAgent } from "@homarr/common/server";
|
||||
import { UndiciHttpAgent } from "@homarr/core/infrastructure/http";
|
||||
|
||||
const agent = new LoggingAgent();
|
||||
setGlobalDispatcher(agent);
|
||||
setGlobalDispatcher(new UndiciHttpAgent());
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-api": "workspace:^0.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
@@ -36,7 +36,7 @@ export const locationRouter = createTRPCRouter({
|
||||
.input(locationSearchCityInput)
|
||||
.output(locationSearchCityOutput)
|
||||
.query(async ({ input }) => {
|
||||
const res = await fetchWithTimeout(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
|
||||
const res = await fetchWithTimeoutAsync(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
|
||||
return (await res.json()) as z.infer<typeof locationSearchCityOutput>;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.41.1",
|
||||
"@auth/drizzle-adapter": "^1.11.1",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { OIDCConfig } from "@auth/core/providers";
|
||||
import type { Profile } from "@auth/core/types";
|
||||
import { customFetch } from "next-auth";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { env } from "../../env";
|
||||
import { createRedirectUri } from "../../redirect";
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: [],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "@homarr/certificates",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"undici": "7.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Logger } from "@homarr/core/infrastructure/logs";
|
||||
import type { ILogger } from "@homarr/core/infrastructure/logs";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { AnyRequestError } from "../request-error";
|
||||
import type { ResponseError } from "../response-error";
|
||||
|
||||
export abstract class HttpErrorHandler {
|
||||
protected logger: Logger;
|
||||
protected logger: ILogger;
|
||||
|
||||
constructor(type: string) {
|
||||
this.logger = createLogger({ module: "httpErrorHandler", type });
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Logger } from "@homarr/core/infrastructure/logs";
|
||||
import type { ILogger } from "@homarr/core/infrastructure/logs";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { ParseError } from "../parse-error";
|
||||
|
||||
export abstract class ParseErrorHandler {
|
||||
protected logger: Logger;
|
||||
protected logger: ILogger;
|
||||
constructor(type: string) {
|
||||
this.logger = createLogger({ module: "parseErrorHandler", type });
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import { Agent } from "undici";
|
||||
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
// The below import statement initializes dns-caching
|
||||
import "@homarr/core/infrastructure/dns/init";
|
||||
|
||||
const logger = createLogger({ module: "fetchAgent" });
|
||||
|
||||
export class LoggingAgent extends Agent {
|
||||
constructor(...props: ConstructorParameters<typeof Agent>) {
|
||||
super(...props);
|
||||
}
|
||||
|
||||
dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {
|
||||
const path = options.path
|
||||
.split("/")
|
||||
.map((segment) => (segment.length >= 32 && !segment.startsWith("?") ? "REDACTED" : segment))
|
||||
.join("/");
|
||||
const url = new URL(`${options.origin as string}${path}`);
|
||||
|
||||
// The below code should prevent sensitive data from being logged as
|
||||
// some integrations use query parameters for auth
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (value === "") return; // Skip empty values
|
||||
if (/^-?\d{1,12}$/.test(value)) return; // Skip small numbers
|
||||
if (value === "true" || value === "false") return; // Skip boolean values
|
||||
if (/^[a-zA-Z]{1,12}$/.test(value)) return; // Skip short strings
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return; // Skip dates
|
||||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) return; // Skip date times
|
||||
|
||||
url.searchParams.set(key, "REDACTED");
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`Dispatching request ${url.toString().replaceAll("=&", "&")} (${Object.keys(options.headers ?? {}).length} headers)`,
|
||||
);
|
||||
return super.dispatch(options, handler);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* 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<typeof fetch>) => {
|
||||
const controller = new AbortController();
|
||||
|
||||
// 10 seconds timeout:
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
return fetch(url, { signal: controller.signal, ...requestInit }).finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
};
|
||||
@@ -9,7 +9,6 @@ export * from "./id";
|
||||
export * from "./url";
|
||||
export * from "./number";
|
||||
export * from "./error";
|
||||
export * from "./fetch-with-timeout";
|
||||
export * from "./theme";
|
||||
export * from "./function";
|
||||
export * from "./id";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from "./security";
|
||||
export * from "./encryption";
|
||||
export * from "./user-agent";
|
||||
export * from "./fetch-agent";
|
||||
export * from "./errors";
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
"./infrastructure/certificates/hostnames/db/sqlite": "./src/infrastructure/certificates/hostnames/db/sqlite.ts",
|
||||
"./infrastructure/certificates/hostnames/db/mysql": "./src/infrastructure/certificates/hostnames/db/mysql.ts",
|
||||
"./infrastructure/certificates/hostnames/db/postgresql": "./src/infrastructure/certificates/hostnames/db/postgresql.ts",
|
||||
"./infrastructure/dns/init": "./src/infrastructure/dns/init.ts"
|
||||
"./infrastructure/dns/init": "./src/infrastructure/dns/init.ts",
|
||||
"./infrastructure/http": "./src/infrastructure/http/index.ts",
|
||||
"./infrastructure/http/timeout": "./src/infrastructure/http/timeout.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
|
||||
import { createDb } from "../../db";
|
||||
import { schema } from "./db/schema";
|
||||
|
||||
@@ -6,3 +8,5 @@ const db = createDb(schema);
|
||||
export const getTrustedCertificateHostnamesAsync = async () => {
|
||||
return await db.query.trustedCertificateHostnames.findMany();
|
||||
};
|
||||
|
||||
export type TrustedCertificateHostname = InferSelectModel<typeof schema.trustedCertificateHostnames>;
|
||||
|
||||
72
packages/core/src/infrastructure/http/http-agent.ts
Normal file
72
packages/core/src/infrastructure/http/http-agent.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import { Agent } from "undici";
|
||||
|
||||
import type { ILogger } from "@homarr/core/infrastructure/logs";
|
||||
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 {
|
||||
logger?: ILogger;
|
||||
}
|
||||
|
||||
export class UndiciHttpAgent extends Agent {
|
||||
private logger: ILogger;
|
||||
|
||||
constructor(props?: HttpAgentOptions) {
|
||||
super(props);
|
||||
|
||||
this.logger = props?.logger ?? createLogger({ module: "httpAgent" });
|
||||
}
|
||||
|
||||
dispatch(options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {
|
||||
this.logRequestDispatch(options);
|
||||
return super.dispatch(options, handler);
|
||||
}
|
||||
|
||||
private logRequestDispatch(options: Dispatcher.DispatchOptions) {
|
||||
const path = this.redactPathParams(options.path);
|
||||
let url = new URL(`${options.origin as string}${path}`);
|
||||
url = this.redactSearchParams(url);
|
||||
|
||||
this.logger.debug(
|
||||
`Dispatching request ${url.toString().replaceAll("=&", "&")} (${Object.keys(options.headers ?? {}).length} headers)`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact path parameters that are longer than 32 characters
|
||||
* This is to prevent sensitive data from being logged
|
||||
* @param path path of the request
|
||||
* @returns redacted path
|
||||
*/
|
||||
private redactPathParams(path: string): string {
|
||||
return path
|
||||
.split("/")
|
||||
.map((segment) => (segment.length >= 32 && !segment.startsWith("?") ? "REDACTED" : segment))
|
||||
.join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact sensitive search parameters from the URL.
|
||||
* It allows certain patterns to remain unredacted.
|
||||
* Like small numbers, booleans, short strings, dates, and date-times.
|
||||
* Some integrations use query parameters for auth.
|
||||
* @param url URL object of the request
|
||||
* @returns redacted URL object
|
||||
*/
|
||||
private redactSearchParams(url: URL): URL {
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (value === "") return; // Skip empty values
|
||||
if (/^-?\d{1,12}$/.test(value)) return; // Skip small numbers
|
||||
if (value === "true" || value === "false") return; // Skip boolean values
|
||||
if (/^[a-zA-Z]{1,12}$/.test(value)) return; // Skip short strings
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return; // Skip dates
|
||||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) return; // Skip date times
|
||||
|
||||
url.searchParams.set(key, "REDACTED");
|
||||
});
|
||||
return url;
|
||||
}
|
||||
}
|
||||
8
packages/core/src/infrastructure/http/index.ts
Normal file
8
packages/core/src/infrastructure/http/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export { UndiciHttpAgent } from "./http-agent";
|
||||
export {
|
||||
createAxiosCertificateInstanceAsync,
|
||||
createCertificateAgentAsync,
|
||||
createCustomCheckServerIdentity,
|
||||
createHttpsAgentAsync,
|
||||
fetchWithTrustedCertificatesAsync,
|
||||
} from "./request";
|
||||
@@ -5,16 +5,17 @@ import axios from "axios";
|
||||
import type { RequestInfo, RequestInit, Response } from "undici";
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { LoggingAgent } from "@homarr/common/server";
|
||||
import {
|
||||
getAllTrustedCertificatesAsync,
|
||||
getTrustedCertificateHostnamesAsync,
|
||||
} from "@homarr/core/infrastructure/certificates";
|
||||
import type { InferSelectModel } from "@homarr/db";
|
||||
import type { trustedCertificateHostnames } from "@homarr/db/schema";
|
||||
import { UndiciHttpAgent } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { TrustedCertificateHostname } from "../certificates/hostnames";
|
||||
import { withTimeoutAsync } from "./timeout";
|
||||
|
||||
export const createCustomCheckServerIdentity = (
|
||||
trustedHostnames: InferSelectModel<typeof trustedCertificateHostnames>[],
|
||||
trustedHostnames: TrustedCertificateHostname[],
|
||||
): typeof checkServerIdentity => {
|
||||
return (hostname, peerCertificate) => {
|
||||
const matchingTrustedHostnames = trustedHostnames.filter(
|
||||
@@ -32,7 +33,7 @@ export const createCertificateAgentAsync = async (override?: {
|
||||
ca: string | string[];
|
||||
checkServerIdentity: typeof checkServerIdentity;
|
||||
}) => {
|
||||
return new LoggingAgent({
|
||||
return new UndiciHttpAgent({
|
||||
connect: override ?? {
|
||||
ca: await getAllTrustedCertificatesAsync(),
|
||||
checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()),
|
||||
@@ -57,8 +58,23 @@ export const createAxiosCertificateInstanceAsync = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchWithTrustedCertificatesAsync = async (url: RequestInfo, options?: RequestInit): Promise<Response> => {
|
||||
export const fetchWithTrustedCertificatesAsync = async (
|
||||
url: RequestInfo,
|
||||
options?: RequestInit & { timeout?: number },
|
||||
): Promise<Response> => {
|
||||
const agent = await createCertificateAgentAsync(undefined);
|
||||
if (options?.timeout) {
|
||||
return await withTimeoutAsync(
|
||||
async (signal) =>
|
||||
fetch(url, {
|
||||
...options,
|
||||
signal,
|
||||
dispatcher: agent,
|
||||
}),
|
||||
options.timeout,
|
||||
);
|
||||
}
|
||||
|
||||
return fetch(url, {
|
||||
...options,
|
||||
dispatcher: agent,
|
||||
19
packages/core/src/infrastructure/http/timeout.ts
Normal file
19
packages/core/src/infrastructure/http/timeout.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Response as UndiciResponse } from "undici";
|
||||
|
||||
// https://stackoverflow.com/questions/46946380/fetch-api-request-timeout
|
||||
export const withTimeoutAsync = async <TResponse extends Response | UndiciResponse>(
|
||||
callback: (signal: AbortSignal) => Promise<TResponse>,
|
||||
timeout = 10000,
|
||||
) => {
|
||||
const controller = new AbortController();
|
||||
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
return await callback(controller.signal).finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchWithTimeoutAsync = async (...[url, requestInit]: Parameters<typeof fetch>) => {
|
||||
return await withTimeoutAsync((signal) => fetch(url, { ...requestInit, signal }));
|
||||
};
|
||||
@@ -15,4 +15,12 @@ interface DefaultMetadata {
|
||||
}
|
||||
|
||||
export const createLogger = (metadata: DefaultMetadata & Record<string, unknown>) => logger.child(metadata);
|
||||
export type Logger = winston.Logger;
|
||||
|
||||
type LogMethod = ((message: string, metadata?: Record<string, unknown>) => void) | ((error: unknown) => void);
|
||||
|
||||
export interface ILogger {
|
||||
debug: LogMethod;
|
||||
info: LogMethod;
|
||||
warn: LogMethod;
|
||||
error: LogMethod;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import * as logs from "@homarr/core/infrastructure/logs";
|
||||
import { UndiciHttpAgent } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { LoggingAgent } from "../fetch-agent";
|
||||
import { TestLogger } from "../logs";
|
||||
|
||||
vi.mock("undici", () => {
|
||||
return {
|
||||
@@ -16,37 +16,28 @@ vi.mock("undici", () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/logs", async () => {
|
||||
const actual: typeof logs = await vi.importActual("@homarr/core/infrastructure/logs");
|
||||
return {
|
||||
...actual,
|
||||
createLogger: vi.fn().mockReturnValue({
|
||||
debug: vi.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const REDACTED = "REDACTED";
|
||||
|
||||
const loggerMock = logs.createLogger({ module: "test" });
|
||||
|
||||
describe("LoggingAgent should log all requests", () => {
|
||||
describe("UndiciHttpAgent should log all requests", () => {
|
||||
test("should log all requests", () => {
|
||||
// Arrange
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
const logger = new TestLogger();
|
||||
const agent = new UndiciHttpAgent({ logger });
|
||||
|
||||
// Act
|
||||
agent.dispatch({ origin: "https://homarr.dev", path: "/", method: "GET" }, {});
|
||||
|
||||
// Assert
|
||||
expect(debugSpy).toHaveBeenCalledWith("Dispatching request https://homarr.dev/ (0 headers)");
|
||||
expect(logger.messages).toContainEqual({
|
||||
level: "debug",
|
||||
message: "Dispatching request https://homarr.dev/ (0 headers)",
|
||||
});
|
||||
});
|
||||
|
||||
test("should show amount of headers", () => {
|
||||
// Arrange
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
const logger = new TestLogger();
|
||||
const agent = new UndiciHttpAgent({ logger });
|
||||
|
||||
// Act
|
||||
agent.dispatch(
|
||||
@@ -63,7 +54,7 @@ describe("LoggingAgent should log all requests", () => {
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining("(2 headers)"));
|
||||
expect(logger.messages.at(-1)?.message).toContain("(2 headers)");
|
||||
});
|
||||
|
||||
test.each([
|
||||
@@ -81,14 +72,14 @@ describe("LoggingAgent should log all requests", () => {
|
||||
[`/${"a".repeat(32)}/?param=123`, `/${REDACTED}/?param=123`],
|
||||
])("should redact sensitive data in url https://homarr.dev%s", (path, expected) => {
|
||||
// Arrange
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
const logger = new TestLogger();
|
||||
const agent = new UndiciHttpAgent({ logger });
|
||||
|
||||
// Act
|
||||
agent.dispatch({ origin: "https://homarr.dev", path, method: "GET" }, {});
|
||||
|
||||
// Assert
|
||||
expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${expected} `));
|
||||
expect(logger.messages.at(-1)?.message).toContain(` https://homarr.dev${expected} `);
|
||||
});
|
||||
test.each([
|
||||
["empty", "/?empty"],
|
||||
@@ -100,13 +91,13 @@ describe("LoggingAgent should log all requests", () => {
|
||||
["date times", "/?datetime=2022-01-01T00:00:00.000Z"],
|
||||
])("should not redact values that are %s", (_reason, path) => {
|
||||
// Arrange
|
||||
const debugSpy = vi.spyOn(loggerMock, "debug");
|
||||
const agent = new LoggingAgent();
|
||||
const logger = new TestLogger();
|
||||
const agent = new UndiciHttpAgent({ logger });
|
||||
|
||||
// Act
|
||||
agent.dispatch({ origin: "https://homarr.dev", path, method: "GET" }, {});
|
||||
|
||||
// Assert
|
||||
expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining(` https://homarr.dev${path} `));
|
||||
expect(logger.messages.at(-1)?.message).toContain(` https://homarr.dev${path} `);
|
||||
});
|
||||
});
|
||||
49
packages/core/src/test/infrastructure/logs/index.ts
Normal file
49
packages/core/src/test/infrastructure/logs/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { ILogger } from "@homarr/core/infrastructure/logs";
|
||||
import type { LogLevel } from "@homarr/core/infrastructure/logs/constants";
|
||||
|
||||
interface LogMessage {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface LogError {
|
||||
level: LogLevel;
|
||||
error: unknown;
|
||||
}
|
||||
|
||||
type LogEntry = LogMessage | LogError;
|
||||
|
||||
export class TestLogger implements ILogger {
|
||||
public entries: LogEntry[] = [];
|
||||
public get messages(): LogMessage[] {
|
||||
return this.entries.filter((entry) => "message" in entry);
|
||||
}
|
||||
public get errors(): LogError[] {
|
||||
return this.entries.filter((entry) => "error" in entry);
|
||||
}
|
||||
|
||||
private log(level: LogLevel, param1: unknown, param2?: Record<string, unknown>): void {
|
||||
if (typeof param1 === "string") {
|
||||
this.entries.push({ level, message: param1, meta: param2 });
|
||||
} else {
|
||||
this.entries.push({ level, error: param1 });
|
||||
}
|
||||
}
|
||||
|
||||
debug(param1: unknown, param2?: Record<string, unknown>): void {
|
||||
this.log("debug", param1, param2);
|
||||
}
|
||||
|
||||
info(param1: unknown, param2?: Record<string, unknown>): void {
|
||||
this.log("info", param1, param2);
|
||||
}
|
||||
|
||||
warn(param1: unknown, param2?: Record<string, unknown>): void {
|
||||
this.log("warn", param1, param2);
|
||||
}
|
||||
|
||||
error(param1: unknown, param2?: Record<string, unknown>): void {
|
||||
this.log("error", param1, param2);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parse } from "path";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
@@ -23,7 +23,7 @@ export class GitHubIconRepository extends IconRepository {
|
||||
throw new Error("Repository URLs are required for this repository");
|
||||
}
|
||||
|
||||
const response = await fetchWithTimeout(this.repositoryIndexingUrl);
|
||||
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
|
||||
const listOfFiles = (await response.json()) as GitHubApiResponse;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parse } from "path";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import type { IconRepositoryLicense } from "../types/icon-repository-license";
|
||||
import type { RepositoryIconGroup } from "../types/repository-icon-group";
|
||||
@@ -19,7 +19,7 @@ export class JsdelivrIconRepository extends IconRepository {
|
||||
}
|
||||
|
||||
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
|
||||
const response = await fetchWithTimeout(this.repositoryIndexingUrl);
|
||||
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
|
||||
const listOfFiles = (await response.json()) as JsdelivrApiResponse;
|
||||
|
||||
return {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { createId } from "@homarr/common";
|
||||
import { decryptSecret, encryptSecret } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
import { createGetSetChannel } from "@homarr/redis";
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"@ctrl/qbittorrent": "^9.11.0",
|
||||
"@ctrl/transmission": "^7.4.0",
|
||||
"@gitbeaker/rest": "^43.8.0",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ParseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { AxiosInstance } from "axios";
|
||||
import type { Dispatcher } from "undici";
|
||||
import { fetch as undiciFetch } from "undici";
|
||||
|
||||
import { createAxiosCertificateInstanceAsync, createCertificateAgentAsync } from "@homarr/certificates/server";
|
||||
import { removeTrailingSlash } from "@homarr/common";
|
||||
import { createAxiosCertificateInstanceAsync, createCertificateAgentAsync } from "@homarr/core/infrastructure/http";
|
||||
import type { IntegrationSecretKind } from "@homarr/definitions";
|
||||
|
||||
import { HandleIntegrationErrors } from "./errors/decorator";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { X509Certificate } from "node:crypto";
|
||||
import tls from "node:tls";
|
||||
|
||||
import { createCustomCheckServerIdentity } from "@homarr/certificates/server";
|
||||
import { getPortFromUrl } from "@homarr/common";
|
||||
import {
|
||||
getAllTrustedCertificatesAsync,
|
||||
getTrustedCertificateHostnamesAsync,
|
||||
} from "@homarr/core/infrastructure/certificates";
|
||||
import { createCustomCheckServerIdentity } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationRequestErrorOfType } from "../errors/http/integration-request-error";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RequestInit, Response } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -5,7 +5,7 @@ import "@homarr/redis";
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { createChannelEventHistoryOld } from "../../../redis/src/lib/channel";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { fetch, RequestInit, Response } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import path from "path";
|
||||
import type { fetch as undiciFetch } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Deluge } from "@ctrl/deluge";
|
||||
import dayjs from "dayjs";
|
||||
import type { Dispatcher } from "undici";
|
||||
|
||||
import { createCertificateAgentAsync } from "@homarr/certificates/server";
|
||||
import { createCertificateAgentAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { HandleIntegrationErrors } from "../../base/errors/decorator";
|
||||
import { integrationOFetchHttpErrorHandler } from "../../base/errors/http";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { fetch as undiciFetch } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { QBittorrent } from "@ctrl/qbittorrent";
|
||||
import dayjs from "dayjs";
|
||||
import type { Dispatcher } from "undici";
|
||||
|
||||
import { createCertificateAgentAsync } from "@homarr/certificates/server";
|
||||
import { createCertificateAgentAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { HandleIntegrationErrors } from "../../base/errors/decorator";
|
||||
import { integrationOFetchHttpErrorHandler } from "../../base/errors/http";
|
||||
|
||||
@@ -2,8 +2,8 @@ import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import type { fetch as undiciFetch } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Transmission } from "@ctrl/transmission";
|
||||
import dayjs from "dayjs";
|
||||
import type { Dispatcher } from "undici";
|
||||
|
||||
import { createCertificateAgentAsync } from "@homarr/certificates/server";
|
||||
import { createCertificateAgentAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { HandleIntegrationErrors } from "../../base/errors/decorator";
|
||||
import { integrationOFetchHttpErrorHandler } from "../../base/errors/http";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BaseItemKind } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createAppAuth } from "@octokit/auth-app";
|
||||
import { Octokit, RequestError } from "octokit";
|
||||
import type { fetch } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createAppAuth } from "@octokit/auth-app";
|
||||
import { Octokit, RequestError as OctokitRequestError } from "octokit";
|
||||
import type { fetch } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createRequesterFn, defaultOptionsHandler } from "@gitbeaker/requester-u
|
||||
import type { FormattedResponse, RequestOptions, ResourceOptions } from "@gitbeaker/requester-utils";
|
||||
import { Gitlab } from "@gitbeaker/rest";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import z from "zod";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ICAL from "ical.js";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getUserApi } from "@jellyfin/sdk/lib/utils/api/user-api";
|
||||
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api/user-library-api";
|
||||
import type { AxiosInstance } from "axios";
|
||||
|
||||
import { createAxiosCertificateInstanceAsync } from "@homarr/certificates/server";
|
||||
import { createAxiosCertificateInstanceAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
import { integrationAxiosHttpErrorHandler } from "../base/errors/http";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { Integration } from "../../base/integration";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { Integration } from "../../base/integration";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { Integration } from "../../base/integration";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { RequestInit as NodeFetchRequestInit } from "node-fetch";
|
||||
import * as ical from "node-ical";
|
||||
import { DAVClient } from "tsdav";
|
||||
|
||||
import { createHttpsAgentAsync } from "@homarr/certificates/server";
|
||||
import { createHttpsAgentAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { Integration } from "../base/integration";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Headers, HeadersInit, fetch as undiciFetch, Response as UndiciResponse } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ParseError, ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { createChannelEventHistoryOld } from "../../../redis/src/lib/channel";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { removeTrailingSlash } from "@homarr/common";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationInput } from "../base/integration";
|
||||
import { PiHoleIntegrationV5 } from "./v5/pi-hole-integration-v5";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import { Integration } from "../../base/integration";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { fetch as undiciFetch, Response as UndiciResponse } from "undici";
|
||||
import type { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationInput, IntegrationTestingInput } from "../../base/integration";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { parseStringPromise } from "xml2js";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ParseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ImageProxy } from "@homarr/image-proxy";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Proxmox } from "proxmox-api";
|
||||
import proxmoxApi from "proxmox-api";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RequestInit, Response } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
|
||||
@@ -2,12 +2,12 @@ import type tls from "node:tls";
|
||||
import axios from "axios";
|
||||
import { HttpCookieAgent, HttpsCookieAgent } from "http-cookie-agent/http";
|
||||
|
||||
import { createCustomCheckServerIdentity } from "@homarr/certificates/server";
|
||||
import { getPortFromUrl } from "@homarr/common";
|
||||
import {
|
||||
getAllTrustedCertificatesAsync,
|
||||
getTrustedCertificateHostnamesAsync,
|
||||
} from "@homarr/core/infrastructure/certificates";
|
||||
import { createCustomCheckServerIdentity } from "@homarr/core/infrastructure/http";
|
||||
import type { SiteStats } from "@homarr/node-unifi";
|
||||
import Unifi from "@homarr/node-unifi";
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { extractErrorMessage } from "@homarr/common";
|
||||
import { LoggingAgent } from "@homarr/common/server";
|
||||
import { UndiciHttpAgent } from "@homarr/core/infrastructure/http";
|
||||
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
@@ -9,28 +10,21 @@ const logger = createLogger({ module: "ping" });
|
||||
|
||||
export const sendPingRequestAsync = async (url: string) => {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
|
||||
// 10 seconds timeout:
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
const start = performance.now();
|
||||
|
||||
return await fetch(url, {
|
||||
dispatcher: new LoggingAgent({
|
||||
connect: {
|
||||
rejectUnauthorized: false, // Ping should always work, even with untrusted certificates
|
||||
},
|
||||
}),
|
||||
signal: controller.signal,
|
||||
})
|
||||
.finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
})
|
||||
.then((response) => {
|
||||
const end = performance.now();
|
||||
const durationMs = end - start;
|
||||
return { statusCode: response.status, durationMs };
|
||||
return await withTimeoutAsync(async (signal) => {
|
||||
return await fetch(url, {
|
||||
dispatcher: new UndiciHttpAgent({
|
||||
connect: {
|
||||
rejectUnauthorized: false, // Ping should always work, even with untrusted certificates
|
||||
},
|
||||
}),
|
||||
signal,
|
||||
});
|
||||
}).then((response) => {
|
||||
const end = performance.now();
|
||||
const durationMs = end - start;
|
||||
return { statusCode: response.status, durationMs };
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(new ErrorWithMetadata("Failed to send ping request", { url }, { cause: error }));
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -11,7 +11,7 @@ export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHand
|
||||
async requestAsync(input: { domain: string; isBedrockServer: boolean }) {
|
||||
const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`;
|
||||
|
||||
const response = await fetchWithTimeout(`https://api.mcsrvstat.us${path}`);
|
||||
const response = await fetchWithTimeoutAsync(`https://api.mcsrvstat.us${path}`);
|
||||
return responseSchema.parse(await response.json());
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,7 +9,7 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "fetchStockPriceResult",
|
||||
widgetKind: "stockPrice",
|
||||
async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) {
|
||||
const response = await fetchWithTimeout(
|
||||
const response = await fetchWithTimeoutAsync(
|
||||
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
||||
);
|
||||
const data = dataSchema.parse(await response.json());
|
||||
|
||||
@@ -2,8 +2,8 @@ import dayjs from "dayjs";
|
||||
import { Octokit } from "octokit";
|
||||
import { compareSemVer, isValidSemVer } from "semver-parser";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
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: fetchWithTimeout,
|
||||
fetch: fetchWithTimeoutAsync,
|
||||
},
|
||||
});
|
||||
const releases = await octokit.rest.repos.listReleases({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,7 +9,7 @@ export const weatherRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "weatherAtLocation",
|
||||
widgetKind: "weather",
|
||||
async requestAsync(input: { latitude: number; longitude: number }) {
|
||||
const res = await fetchWithTimeout(
|
||||
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 json: unknown = await res.json();
|
||||
|
||||
49
pnpm-lock.yaml
generated
49
pnpm-lock.yaml
generated
@@ -133,9 +133,6 @@ importers:
|
||||
'@homarr/boards':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/boards
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/certificates
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/common
|
||||
@@ -575,9 +572,6 @@ importers:
|
||||
'@homarr/auth':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../auth
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../certificates
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
@@ -699,9 +693,6 @@ importers:
|
||||
'@auth/drizzle-adapter':
|
||||
specifier: ^1.11.1
|
||||
version: 1.11.1
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../certificates
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
@@ -795,37 +786,6 @@ importers:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
|
||||
packages/certificates:
|
||||
dependencies:
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
'@homarr/core':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../core
|
||||
'@homarr/db':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../db
|
||||
undici:
|
||||
specifier: 7.16.0
|
||||
version: 7.16.0
|
||||
devDependencies:
|
||||
'@homarr/eslint-config':
|
||||
specifier: workspace:^0.2.0
|
||||
version: link:../../tooling/eslint
|
||||
'@homarr/prettier-config':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/prettier
|
||||
'@homarr/tsconfig':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/typescript
|
||||
eslint:
|
||||
specifier: ^9.39.2
|
||||
version: 9.39.2
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
|
||||
packages/cli:
|
||||
dependencies:
|
||||
'@drizzle-team/brocli':
|
||||
@@ -1420,9 +1380,6 @@ importers:
|
||||
|
||||
packages/image-proxy:
|
||||
dependencies:
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../certificates
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
@@ -1469,9 +1426,6 @@ importers:
|
||||
'@gitbeaker/rest':
|
||||
specifier: ^43.8.0
|
||||
version: 43.8.0
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../certificates
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
@@ -1799,9 +1753,6 @@ importers:
|
||||
|
||||
packages/ping:
|
||||
dependencies:
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../certificates
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
|
||||
Reference in New Issue
Block a user