import { extractErrorMessage } from "@homarr/common"; import type { IntegrationSecretKind } from "@homarr/definitions"; import { logger } from "@homarr/log"; import type { TranslationObject } from "@homarr/translation"; import { z } from "@homarr/validation"; import { IntegrationTestConnectionError } from "./test-connection-error"; import type { IntegrationSecret } from "./types"; const causeSchema = z.object({ code: z.string(), }); export interface IntegrationInput { id: string; name: string; url: string; decryptedSecrets: IntegrationSecret[]; } export abstract class Integration { constructor(protected integration: IntegrationInput) {} protected getSecretValue(kind: IntegrationSecretKind) { const secret = this.integration.decryptedSecrets.find((secret) => secret.kind === kind); if (!secret) { throw new Error(`No secret of kind ${kind} was found`); } return secret.value; } /** * Test the connection to the integration * @throws {IntegrationTestConnectionError} if the connection fails */ public abstract testConnectionAsync(): Promise; protected async handleTestConnectionResponseAsync({ queryFunctionAsync, handleResponseAsync, }: { queryFunctionAsync: () => Promise; handleResponseAsync?: (response: Response) => Promise; }): Promise { const response = await queryFunctionAsync().catch((error) => { if (error instanceof Error) { const cause = causeSchema.safeParse(error.cause); if (!cause.success) { logger.error("Failed to test connection", error); throw new IntegrationTestConnectionError("commonError", extractErrorMessage(error)); } if (cause.data.code === "ENOTFOUND") { logger.error("Failed to test connection: Domain not found"); throw new IntegrationTestConnectionError("domainNotFound"); } if (cause.data.code === "ECONNREFUSED") { logger.error("Failed to test connection: Connection refused"); throw new IntegrationTestConnectionError("connectionRefused"); } if (cause.data.code === "ECONNABORTED") { logger.error("Failed to test connection: Connection aborted"); throw new IntegrationTestConnectionError("connectionAborted"); } } logger.error("Failed to test connection", error); throw new IntegrationTestConnectionError("commonError", extractErrorMessage(error)); }); if (response.status >= 400) { logger.error(`Failed to test connection with status code ${response.status}`); throwErrorByStatusCode(response.status); } await handleResponseAsync?.(response); } } export interface TestConnectionError { key: Exclude; message?: string; } export type TestConnectionResult = | { success: false; error: TestConnectionError; } | { success: true; }; const throwErrorByStatusCode = (statusCode: number) => { switch (statusCode) { case 400: throw new IntegrationTestConnectionError("badRequest"); case 401: throw new IntegrationTestConnectionError("unauthorized"); case 403: throw new IntegrationTestConnectionError("forbidden"); case 404: throw new IntegrationTestConnectionError("notFound"); case 500: throw new IntegrationTestConnectionError("internalServerError"); case 503: throw new IntegrationTestConnectionError("serviceUnavailable"); default: throw new IntegrationTestConnectionError("commonError"); } };