refactor: let integrations own HTTP authentication
This commit is contained in:
1 parent
8157a43b9b
commit
db4b6bf4b5
46 files changed
+425
-447
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { ParseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -10,6 +11,10 @@ import type { DnsHoleSummary } from "../interfaces/dns-hole-summary/dns-hole-sum
|
||||
import { filteringStatusSchema, statsResponseSchema, statusResponseSchema } from "./adguard-home-types";
|
||||
|
||||
export class AdGuardHomeIntegration extends Integration implements DnsHoleSummaryIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: { Authorization: `Basic ${this.getAuthorizationHeaderValue()}` } };
|
||||
}
|
||||
|
||||
public async getSummaryAsync(): Promise<DnsHoleSummary> {
|
||||
const headers = {
|
||||
Authorization: `Basic ${this.getAuthorizationHeaderValue()}`,
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { bearerAuth } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -14,11 +16,13 @@ import type { AnchorNote, AnchorNotesListInput, AnchorNoteSummary, AnchorNoteUpd
|
||||
const logger = createLogger({ module: "anchorIntegration" });
|
||||
|
||||
export class AnchorIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return bearerAuth(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/notes", { limit: 1 }), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.getSecretValue("apiKey")}`,
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -36,6 +37,10 @@ interface WarriorSnapshotState {
|
||||
}
|
||||
|
||||
export class ArchiveTeamWarriorIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.createAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/index.html"), {
|
||||
headers: this.createAuthHeaders(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -26,6 +27,10 @@ const mediaTypeCountField = {
|
||||
>;
|
||||
|
||||
export class AudiobookshelfIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders(), redactValues: [] };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const healthResponse = await input.fetchAsync(this.url("/healthcheck"));
|
||||
|
||||
|
||||
@@ -1,296 +1,120 @@
|
||||
import { isHttpIntegrationKind } from "@homarr/definitions";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
import {
|
||||
withHttpAuthentication,
|
||||
apiKeyAuth,
|
||||
bearerAuth,
|
||||
basicAuth,
|
||||
headerAuth,
|
||||
queryAuth,
|
||||
noAuth,
|
||||
optionalAuth,
|
||||
alternativeAuth,
|
||||
rpcAuth,
|
||||
feedAuth,
|
||||
secret,
|
||||
clientAuthentication,
|
||||
} from "../http-auth";
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { clientAuthentication } from "../http-auth";
|
||||
import type { Integration, IntegrationInput } from "./integration";
|
||||
|
||||
const logger = createLogger({ module: "integrationFactory" });
|
||||
|
||||
// Keep each import path explicit so Node and Turbopack can discover every lazy integration chunk.
|
||||
const integrationCreators = {
|
||||
anchor: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../anchor/anchor-integration")).AnchorIntegration(input),
|
||||
bearerAuth,
|
||||
),
|
||||
piHole: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
(await import("../pi-hole/pi-hole-integration-factory")).createPiHoleIntegrationAsync(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
adGuardHome: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../adguard-home/adguard-home-integration")).AdGuardHomeIntegration(input),
|
||||
basicAuth(),
|
||||
),
|
||||
technitiumDns: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
(await import("../technitium/technitium-integration-factory")).createTechnitiumDnsIntegrationAsync(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
homeAssistant: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../homeassistant/homeassistant-integration")).HomeAssistantIntegration(input),
|
||||
bearerAuth,
|
||||
),
|
||||
jellyfin: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../jellyfin/jellyfin-integration")).JellyfinIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
plex: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../plex/plex-integration")).PlexIntegration(input),
|
||||
headerAuth("X-Plex-Token"),
|
||||
),
|
||||
sonarr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/sonarr/sonarr-integration")).SonarrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
radarr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/radarr/radarr-integration")).RadarrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
sabNzbd: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/sabnzbd/sabnzbd-integration")).SabnzbdIntegration(input),
|
||||
queryAuth("apikey"),
|
||||
),
|
||||
nzbGet: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/nzbget/nzbget-integration")).NzbGetIntegration(input),
|
||||
basicAuth(),
|
||||
),
|
||||
qBittorrent: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/qbittorrent/qbittorrent-integration")).QBitTorrentIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
deluge: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/deluge/deluge-integration")).DelugeIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
transmission: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/transmission/transmission-integration")).TransmissionIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
slskd: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/slskd/slskd-integration")).SlskdIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
aria2: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/aria2/aria2-integration")).Aria2Integration(input),
|
||||
optionalAuth(rpcAuth),
|
||||
),
|
||||
jellyseerr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../jellyseerr/jellyseerr-integration")).JellyseerrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
seerr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../seerr/seerr-integration")).SeerrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
overseerr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../overseerr/overseerr-integration")).OverseerrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
prowlarr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../prowlarr/prowlarr-integration")).ProwlarrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
openmediavault: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../openmediavault/openmediavault-integration")).OpenMediaVaultIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
lidarr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/lidarr/lidarr-integration")).LidarrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
readarr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/readarr/readarr-integration")).ReadarrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
dashDot: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../dashdot/dashdot-integration")).DashDotIntegration(input),
|
||||
noAuth,
|
||||
),
|
||||
tdarr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../media-transcoding/tdarr-integration")).TdarrIntegration(input),
|
||||
optionalAuth(apiKeyAuth),
|
||||
),
|
||||
proxmox: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../proxmox/proxmox-integration")).ProxmoxIntegration(input),
|
||||
(input) => ({
|
||||
headers: {
|
||||
Authorization: `PVEAPIToken=${secret(input, "username")}@${secret(input, "realm")}!${secret(input, "tokenId")}=${secret(input, "apiKey")}`,
|
||||
},
|
||||
}),
|
||||
),
|
||||
emby: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../emby/emby-integration")).EmbyIntegration(input),
|
||||
headerAuth("X-Emby-Token"),
|
||||
),
|
||||
nextcloud: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../nextcloud/nextcloud.integration")).NextcloudIntegration(input),
|
||||
basicAuth("username", "password", { "OCS-APIRequest": "true" }),
|
||||
),
|
||||
unifiController: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../unifi-controller/unifi-controller-integration")).UnifiControllerIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
opnsense: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../opnsense/opnsense-integration")).OPNsenseIntegration(input),
|
||||
basicAuth("opnsenseApiKey", "opnsenseApiSecret"),
|
||||
),
|
||||
ical: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../ical/ical-integration")).ICalIntegration(input),
|
||||
feedAuth,
|
||||
),
|
||||
ntfy: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../ntfy/ntfy-integration")).NTFYIntegration(input),
|
||||
optionalAuth(bearerAuth),
|
||||
),
|
||||
gotify: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../gotify/gotify-integration")).GotifyIntegration(input),
|
||||
basicAuth(),
|
||||
),
|
||||
anchor: async (input: IntegrationInput) =>
|
||||
new (await import("../anchor/anchor-integration")).AnchorIntegration(input),
|
||||
piHole: async (input: IntegrationInput) =>
|
||||
(await import("../pi-hole/pi-hole-integration-factory")).createPiHoleIntegrationAsync(input),
|
||||
adGuardHome: async (input: IntegrationInput) =>
|
||||
new (await import("../adguard-home/adguard-home-integration")).AdGuardHomeIntegration(input),
|
||||
technitiumDns: async (input: IntegrationInput) =>
|
||||
(await import("../technitium/technitium-integration-factory")).createTechnitiumDnsIntegrationAsync(input),
|
||||
homeAssistant: async (input: IntegrationInput) =>
|
||||
new (await import("../homeassistant/homeassistant-integration")).HomeAssistantIntegration(input),
|
||||
jellyfin: async (input: IntegrationInput) =>
|
||||
new (await import("../jellyfin/jellyfin-integration")).JellyfinIntegration(input),
|
||||
plex: async (input: IntegrationInput) => new (await import("../plex/plex-integration")).PlexIntegration(input),
|
||||
sonarr: async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/sonarr/sonarr-integration")).SonarrIntegration(input),
|
||||
radarr: async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/radarr/radarr-integration")).RadarrIntegration(input),
|
||||
sabNzbd: async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/sabnzbd/sabnzbd-integration")).SabnzbdIntegration(input),
|
||||
nzbGet: async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/nzbget/nzbget-integration")).NzbGetIntegration(input),
|
||||
qBittorrent: async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/qbittorrent/qbittorrent-integration")).QBitTorrentIntegration(input),
|
||||
deluge: async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/deluge/deluge-integration")).DelugeIntegration(input),
|
||||
transmission: async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/transmission/transmission-integration")).TransmissionIntegration(input),
|
||||
slskd: async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/slskd/slskd-integration")).SlskdIntegration(input),
|
||||
aria2: async (input: IntegrationInput) =>
|
||||
new (await import("../download-client/aria2/aria2-integration")).Aria2Integration(input),
|
||||
jellyseerr: async (input: IntegrationInput) =>
|
||||
new (await import("../jellyseerr/jellyseerr-integration")).JellyseerrIntegration(input),
|
||||
seerr: async (input: IntegrationInput) => new (await import("../seerr/seerr-integration")).SeerrIntegration(input),
|
||||
overseerr: async (input: IntegrationInput) =>
|
||||
new (await import("../overseerr/overseerr-integration")).OverseerrIntegration(input),
|
||||
prowlarr: async (input: IntegrationInput) =>
|
||||
new (await import("../prowlarr/prowlarr-integration")).ProwlarrIntegration(input),
|
||||
openmediavault: async (input: IntegrationInput) =>
|
||||
new (await import("../openmediavault/openmediavault-integration")).OpenMediaVaultIntegration(input),
|
||||
lidarr: async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/lidarr/lidarr-integration")).LidarrIntegration(input),
|
||||
readarr: async (input: IntegrationInput) =>
|
||||
new (await import("../media-organizer/readarr/readarr-integration")).ReadarrIntegration(input),
|
||||
dashDot: async (input: IntegrationInput) =>
|
||||
new (await import("../dashdot/dashdot-integration")).DashDotIntegration(input),
|
||||
tdarr: async (input: IntegrationInput) =>
|
||||
new (await import("../media-transcoding/tdarr-integration")).TdarrIntegration(input),
|
||||
proxmox: async (input: IntegrationInput) =>
|
||||
new (await import("../proxmox/proxmox-integration")).ProxmoxIntegration(input),
|
||||
emby: async (input: IntegrationInput) => new (await import("../emby/emby-integration")).EmbyIntegration(input),
|
||||
nextcloud: async (input: IntegrationInput) =>
|
||||
new (await import("../nextcloud/nextcloud.integration")).NextcloudIntegration(input),
|
||||
unifiController: async (input: IntegrationInput) =>
|
||||
new (await import("../unifi-controller/unifi-controller-integration")).UnifiControllerIntegration(input),
|
||||
opnsense: async (input: IntegrationInput) =>
|
||||
new (await import("../opnsense/opnsense-integration")).OPNsenseIntegration(input),
|
||||
ical: async (input: IntegrationInput) => new (await import("../ical/ical-integration")).ICalIntegration(input),
|
||||
ntfy: async (input: IntegrationInput) => new (await import("../ntfy/ntfy-integration")).NTFYIntegration(input),
|
||||
gotify: async (input: IntegrationInput) =>
|
||||
new (await import("../gotify/gotify-integration")).GotifyIntegration(input),
|
||||
mock: async (input: IntegrationInput) => new (await import("../mock/mock-integration")).MockIntegration(input),
|
||||
truenas: async (input: IntegrationInput) =>
|
||||
new (await import("../truenas/truenas-integration")).TrueNasIntegration(input),
|
||||
synology: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../synology/synology-integration")).SynologyIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
unraid: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../unraid/unraid-integration")).UnraidIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
coolify: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../coolify/coolify-integration")).CoolifyIntegration(input),
|
||||
bearerAuth,
|
||||
),
|
||||
tracearr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../tracearr/tracearr-integration")).TracearrIntegration(input),
|
||||
bearerAuth,
|
||||
),
|
||||
glances: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../glances/glances-integration")).GlancesIntegration(input),
|
||||
noAuth,
|
||||
),
|
||||
immich: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../immich/immich-integration")).ImmichIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
paperlessNgx: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../paperless-ngx/paperless-ngx-integration")).PaperlessNgxIntegration(input),
|
||||
headerAuth("Authorization", "Token "),
|
||||
),
|
||||
patchmon: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../patchmon/patchmon-integration")).PatchMonIntegration(input),
|
||||
basicAuth("patchmonApiKey", "patchmonApiSecret"),
|
||||
),
|
||||
speedtestTracker: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../speedtest-tracker/speedtest-tracker-integration")).SpeedtestTrackerIntegration(input),
|
||||
bearerAuth,
|
||||
),
|
||||
audiobookshelf: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../audiobookshelf/audiobookshelf-integration")).AudiobookshelfIntegration(input),
|
||||
bearerAuth,
|
||||
),
|
||||
navidrome: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../navidrome/navidrome-integration")).NavidromeIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
umami: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../umami/umami-integration")).UmamiIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
gluetun: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../gluetun/gluetun-integration")).GluetunIntegration(input),
|
||||
alternativeAuth(apiKeyAuth),
|
||||
),
|
||||
archiveTeamWarrior: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../archive-team-warrior/archive-team-warrior-integration")).ArchiveTeamWarriorIntegration(
|
||||
input,
|
||||
),
|
||||
optionalAuth(basicAuth(), ["username", "password"]),
|
||||
),
|
||||
uptimeKuma: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../uptime-kuma/uptime-kuma-integration")).UptimeKumaIntegration(input),
|
||||
noAuth,
|
||||
),
|
||||
peaNut: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../peanut/peanut-integration")).PeaNutIntegration(input),
|
||||
optionalAuth(basicAuth(), ["username", "password"]),
|
||||
),
|
||||
beszel: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../beszel/beszel-integration")).BeszelIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
bazarr: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../bazarr/bazarr-integration")).BazarrIntegration(input),
|
||||
apiKeyAuth,
|
||||
),
|
||||
traefik: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../traefik/traefik-integration")).TraefikIntegration(input),
|
||||
clientAuthentication,
|
||||
),
|
||||
wud: withHttpAuthentication(
|
||||
async (input: IntegrationInput) => new (await import("../wud/wud-integration")).WudIntegration(input),
|
||||
optionalAuth(basicAuth(), ["username", "password"]),
|
||||
),
|
||||
llamacpp: withHttpAuthentication(
|
||||
async (input: IntegrationInput) =>
|
||||
new (await import("../llama-cpp/llamacpp-integration")).LlamacppIntegration(input),
|
||||
noAuth,
|
||||
),
|
||||
} satisfies {
|
||||
[Kind in IntegrationKind]: ((input: IntegrationInput) => Promise<Integration>) &
|
||||
(Kind extends "truenas" | "mock"
|
||||
? unknown
|
||||
: {
|
||||
authenticate(input: IntegrationInput): IntegrationHttpAuthentication | Promise<IntegrationHttpAuthentication>;
|
||||
});
|
||||
};
|
||||
synology: async (input: IntegrationInput) =>
|
||||
new (await import("../synology/synology-integration")).SynologyIntegration(input),
|
||||
unraid: async (input: IntegrationInput) =>
|
||||
new (await import("../unraid/unraid-integration")).UnraidIntegration(input),
|
||||
coolify: async (input: IntegrationInput) =>
|
||||
new (await import("../coolify/coolify-integration")).CoolifyIntegration(input),
|
||||
tracearr: async (input: IntegrationInput) =>
|
||||
new (await import("../tracearr/tracearr-integration")).TracearrIntegration(input),
|
||||
glances: async (input: IntegrationInput) =>
|
||||
new (await import("../glances/glances-integration")).GlancesIntegration(input),
|
||||
immich: async (input: IntegrationInput) =>
|
||||
new (await import("../immich/immich-integration")).ImmichIntegration(input),
|
||||
paperlessNgx: async (input: IntegrationInput) =>
|
||||
new (await import("../paperless-ngx/paperless-ngx-integration")).PaperlessNgxIntegration(input),
|
||||
patchmon: async (input: IntegrationInput) =>
|
||||
new (await import("../patchmon/patchmon-integration")).PatchMonIntegration(input),
|
||||
speedtestTracker: async (input: IntegrationInput) =>
|
||||
new (await import("../speedtest-tracker/speedtest-tracker-integration")).SpeedtestTrackerIntegration(input),
|
||||
audiobookshelf: async (input: IntegrationInput) =>
|
||||
new (await import("../audiobookshelf/audiobookshelf-integration")).AudiobookshelfIntegration(input),
|
||||
navidrome: async (input: IntegrationInput) =>
|
||||
new (await import("../navidrome/navidrome-integration")).NavidromeIntegration(input),
|
||||
umami: async (input: IntegrationInput) => new (await import("../umami/umami-integration")).UmamiIntegration(input),
|
||||
gluetun: async (input: IntegrationInput) =>
|
||||
new (await import("../gluetun/gluetun-integration")).GluetunIntegration(input),
|
||||
archiveTeamWarrior: async (input: IntegrationInput) =>
|
||||
new (await import("../archive-team-warrior/archive-team-warrior-integration")).ArchiveTeamWarriorIntegration(input),
|
||||
uptimeKuma: async (input: IntegrationInput) =>
|
||||
new (await import("../uptime-kuma/uptime-kuma-integration")).UptimeKumaIntegration(input),
|
||||
peaNut: async (input: IntegrationInput) =>
|
||||
new (await import("../peanut/peanut-integration")).PeaNutIntegration(input),
|
||||
beszel: async (input: IntegrationInput) =>
|
||||
new (await import("../beszel/beszel-integration")).BeszelIntegration(input),
|
||||
bazarr: async (input: IntegrationInput) =>
|
||||
new (await import("../bazarr/bazarr-integration")).BazarrIntegration(input),
|
||||
traefik: async (input: IntegrationInput) =>
|
||||
new (await import("../traefik/traefik-integration")).TraefikIntegration(input),
|
||||
wud: async (input: IntegrationInput) => new (await import("../wud/wud-integration")).WudIntegration(input),
|
||||
llamacpp: async (input: IntegrationInput) =>
|
||||
new (await import("../llama-cpp/llamacpp-integration")).LlamacppIntegration(input),
|
||||
} satisfies Record<IntegrationKind, (input: IntegrationInput) => Promise<Integration>>;
|
||||
|
||||
type IntegrationCreators = typeof integrationCreators;
|
||||
type IntegrationInstanceOfKind<TKind extends keyof IntegrationCreators> = Awaited<
|
||||
@@ -341,10 +165,7 @@ export async function createIntegrationAsync(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getIntegrationHttpAuthenticationAsync(
|
||||
integration: IntegrationInput & { kind: IntegrationKind },
|
||||
): Promise<IntegrationHttpAuthentication> {
|
||||
const creator = integrationCreators[integration.kind];
|
||||
if (!("authenticate" in creator)) throw new Error("This integration does not use HTTP");
|
||||
return creator.authenticate(integration);
|
||||
export async function getIntegrationHttpAuthenticationAsync(integration: IntegrationInput & { kind: IntegrationKind }) {
|
||||
if (!isHttpIntegrationKind(integration.kind)) throw new Error("This integration does not use HTTP");
|
||||
return clientAuthentication(() => createIntegrationAsync(integration));
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { removeTrailingSlash } from "@homarr/common";
|
||||
import { createAxiosCertificateInstanceAsync, createCertificateAgentAsync } from "@homarr/core/infrastructure/http";
|
||||
import type { IntegrationKind, IntegrationSecretKind } from "@homarr/definitions";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { HandleIntegrationErrors } from "./errors/decorator";
|
||||
import { TestConnectionError } from "./test-connection/test-connection-error";
|
||||
import type { TestingResult } from "./test-connection/test-connection-service";
|
||||
@@ -36,6 +37,8 @@ export interface IntegrationTestingInput {
|
||||
export abstract class Integration {
|
||||
constructor(protected integration: IntegrationInput) {}
|
||||
|
||||
public abstract getHttpAuthenticationAsync(refresh?: boolean): Promise<IntegrationHttpAuthentication>;
|
||||
|
||||
public get publicIntegration() {
|
||||
return {
|
||||
id: this.integration.id,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -8,6 +9,10 @@ import type { BazarrBadges } from "./bazarr-types";
|
||||
import { bazarrBadgesSchema, bazarrSystemStatusSchema } from "./bazarr-types";
|
||||
|
||||
export class BazarrIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders(), redactValues: [] };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/system/status"), {
|
||||
headers: this.getAuthHeaders(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
@@ -32,6 +33,10 @@ import {
|
||||
} from "./coolify-types";
|
||||
|
||||
export class CoolifyIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const healthUrl = this.url("/api/health");
|
||||
const healthResponse = await input.fetchAsync(healthUrl, {});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { createChannelEventHistoryOld } from "../../../redis/src/lib/channel";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
@@ -14,6 +15,10 @@ import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-mo
|
||||
import type { SystemHealthMonitoring } from "../interfaces/health-monitoring/health-monitoring-types";
|
||||
|
||||
export class DashDotIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/info"));
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { fetch as undiciFetch } from "undici";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
||||
@@ -13,6 +14,27 @@ import type { DownloadClientItem } from "../../interfaces/downloads/download-cli
|
||||
import type { Aria2Download, Aria2GetClient } from "./aria2-types";
|
||||
|
||||
export class Aria2Integration extends Integration implements IDownloadClientIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
if (!this.hasSecretValue("apiKey") || !this.getSecretValue("apiKey")) return {};
|
||||
return {
|
||||
transformBody: (body) => {
|
||||
const authenticate = (value: unknown): unknown => {
|
||||
if (Array.isArray(value)) return value.map(authenticate);
|
||||
if (!value || typeof value !== "object") throw new Error("Expected a JSON-RPC request body");
|
||||
const request = value as { method?: string; methodName?: string; params?: unknown[] };
|
||||
const method = request.method ?? request.methodName;
|
||||
const params = request.params ?? [];
|
||||
if (!Array.isArray(params)) throw new Error("JSON-RPC params must be an array");
|
||||
if (method === "system.multicall") return { ...request, params: [authenticate(params[0])] };
|
||||
if (method === "system.listMethods" || method === "system.listNotifications") return request;
|
||||
if (typeof params[0] === "string" && params[0].startsWith("token:")) params.shift();
|
||||
return { ...request, params: [`token:${this.getSecretValue("apiKey")}`, ...params] };
|
||||
};
|
||||
return JSON.stringify(authenticate(JSON.parse(body ?? "null")));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async getClientJobsAndStatusAsync(input: { limit: number }): Promise<DownloadClientJobsAndStatus> {
|
||||
const client = this.getClient();
|
||||
const keys: (keyof Aria2Download)[] = [
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { fetch as undiciFetch } from "undici";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { basicAuth } from "../../http-auth";
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
||||
@@ -14,6 +16,10 @@ import type { DownloadClientStatus } from "../../interfaces/downloads/download-c
|
||||
import type { NzbGetClient } from "./nzbget-types";
|
||||
|
||||
export class NzbGetIntegration extends Integration implements IDownloadClientIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return basicAuth()(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
await this.nzbGetApiCallWithCustomFetchAsync(input.fetchAsync, "version");
|
||||
return {
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { fetch as undiciFetch } from "undici";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { queryAuth } from "../../http-auth";
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import type { TestingResult } from "../../base/test-connection/test-connection-service";
|
||||
@@ -17,6 +19,10 @@ import { historySchema, queueSchema } from "./sabnzbd-schema";
|
||||
dayjs.extend(duration);
|
||||
|
||||
export class SabnzbdIntegration extends Integration implements IDownloadClientIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return queryAuth("apikey")(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
//This is the one call that uses the least amount of data while requiring the api key
|
||||
await this.sabNzbApiCallWithCustomFetchAsync(input.fetchAsync, "translate", { value: "ping" });
|
||||
|
||||
@@ -6,6 +6,8 @@ import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/h
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { apiKeyAuth } from "../../http-auth";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import { Integration } from "../../base/integration";
|
||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||
@@ -49,6 +51,10 @@ const logger = createLogger({ module: "slskd-integration" });
|
||||
* Slskd integration cannot pause or stop downloads, it can only list downloads.
|
||||
*/
|
||||
export class SlskdIntegration extends Integration implements IDownloadClientIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
public pauseQueueAsync(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
@@ -76,9 +82,7 @@ export class SlskdIntegration extends Integration implements IDownloadClientInte
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/v0/server"), {
|
||||
headers: {
|
||||
"X-API-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -148,9 +152,7 @@ export class SlskdIntegration extends Integration implements IDownloadClientInte
|
||||
): Promise<unknown> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url(path), {
|
||||
...options,
|
||||
headers: {
|
||||
"X-API-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { z } from "zod/v4";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { headerAuth } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -90,6 +92,10 @@ const userSchema = z.object({
|
||||
});
|
||||
|
||||
export class EmbyIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return headerAuth("X-Emby-Token")(this.integration);
|
||||
}
|
||||
|
||||
private static readonly apiKeyHeader = "X-Emby-Token";
|
||||
private static readonly deviceId = "homarr-emby-integration";
|
||||
private static readonly authorizationHeaderValue = `Emby Client="Dashboard", Device="Homarr", DeviceId="${EmbyIntegration.deviceId}", Version="0.0.1"`;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod/v4";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { SessionStore } from "../base/session-store";
|
||||
@@ -14,6 +15,10 @@ import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-mo
|
||||
import type { SystemHealthMonitoring } from "../types";
|
||||
|
||||
export class GlancesIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return {};
|
||||
}
|
||||
|
||||
private readonly sessionStore: SessionStore<{ version: string }>;
|
||||
|
||||
constructor(integration: IntegrationInput) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -16,6 +17,10 @@ import {
|
||||
import type { GluetunDnsStatus, GluetunPublicIp, GluetunVpnSettings, GluetunVpnStatus } from "./gluetun-types";
|
||||
|
||||
export class GluetunIntegration extends Integration implements VpnSummaryIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const authHeaders = this.getAuthHeaders();
|
||||
const url = this.url("/v1/vpn/status");
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { ImageProxy } from "@homarr/image-proxy";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
@@ -10,6 +11,10 @@ import type { INotificationsIntegration } from "../interfaces/notifications/noti
|
||||
import { gotifyApplicationsResponseSchema, gotifyMessagesResponseSchema } from "./gotify-schema";
|
||||
|
||||
export class GotifyIntegration extends Integration implements INotificationsIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getHeaders() };
|
||||
}
|
||||
|
||||
public async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
await input.fetchAsync(this.url("/health"), { headers: this.getHeaders() });
|
||||
return { success: true };
|
||||
|
||||
@@ -5,6 +5,7 @@ import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/h
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -17,6 +18,10 @@ import { calendarEventSchema, calendarsSchema, entityStateSchema } from "./homea
|
||||
const logger = createLogger({ module: "homeAssistantIntegration" });
|
||||
|
||||
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration, ICalendarIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
public async getEntityStateAsync(entityId: string) {
|
||||
try {
|
||||
const response = await this.getAsync(`/api/states/${entityId}`);
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface IntegrationHttpAuthentication {
|
||||
refreshAsync?(): Promise<IntegrationHttpAuthentication>;
|
||||
}
|
||||
|
||||
export function secret(input: IntegrationInput, kind: IntegrationSecretKind) {
|
||||
function secret(input: IntegrationInput, kind: IntegrationSecretKind) {
|
||||
const value = input.decryptedSecrets.find((entry) => entry.kind === kind)?.value;
|
||||
if (!value) throw new Error(`Integration credential ${kind} is missing`);
|
||||
return value;
|
||||
@@ -41,83 +41,28 @@ export const basicAuth =
|
||||
export const queryAuth =
|
||||
(name: string, kind: IntegrationSecretKind = "apiKey") =>
|
||||
(input: IntegrationInput): IntegrationHttpAuthentication => ({ query: { [name]: secret(input, kind) } });
|
||||
export const noAuth = (): IntegrationHttpAuthentication => ({});
|
||||
export const optionalAuth =
|
||||
(
|
||||
resolve: (input: IntegrationInput) => IntegrationHttpAuthentication,
|
||||
required: IntegrationSecretKind[] = ["apiKey"],
|
||||
) =>
|
||||
(input: IntegrationInput) => {
|
||||
if (
|
||||
!required.every((requiredKind) =>
|
||||
input.decryptedSecrets.some(({ kind, value }) => kind === requiredKind && value),
|
||||
)
|
||||
)
|
||||
return noAuth();
|
||||
return resolve(input);
|
||||
};
|
||||
export const alternativeAuth =
|
||||
(
|
||||
apiKey: (input: IntegrationInput) => IntegrationHttpAuthentication,
|
||||
fallback = optionalAuth(basicAuth(), ["username", "password"]),
|
||||
) =>
|
||||
(input: IntegrationInput) => {
|
||||
if (input.decryptedSecrets.some(({ kind, value }) => kind === "apiKey" && value)) return apiKey(input);
|
||||
return fallback(input);
|
||||
};
|
||||
export const rpcAuth = (input: IntegrationInput): IntegrationHttpAuthentication => ({
|
||||
transformBody(body) {
|
||||
const authenticate = (value: unknown): unknown => {
|
||||
if (Array.isArray(value)) return value.map(authenticate);
|
||||
if (!value || typeof value !== "object") throw new Error("Expected a JSON-RPC request body");
|
||||
const request = value as { method?: string; methodName?: string; params?: unknown[] };
|
||||
const method = request.method ?? request.methodName;
|
||||
const params = request.params ?? [];
|
||||
if (!Array.isArray(params)) throw new Error("JSON-RPC params must be an array");
|
||||
if (method === "system.multicall") return { ...request, params: [authenticate(params[0])] };
|
||||
if (method === "system.listMethods" || method === "system.listNotifications") return request;
|
||||
if (typeof params[0] === "string" && params[0].startsWith("token:")) params.shift();
|
||||
return { ...request, params: [`token:${secret(input, "apiKey")}`, ...params] };
|
||||
};
|
||||
return JSON.stringify(authenticate(JSON.parse(body ?? "null")));
|
||||
},
|
||||
});
|
||||
|
||||
export function withHttpAuthentication<T>(
|
||||
create: (input: IntegrationInput) => Promise<T>,
|
||||
authenticate: (
|
||||
input: IntegrationInput,
|
||||
create: () => Promise<T>,
|
||||
) => IntegrationHttpAuthentication | Promise<IntegrationHttpAuthentication>,
|
||||
) {
|
||||
return Object.assign(create, {
|
||||
authenticate: (input: IntegrationInput) => withAuthTimeout(() => authenticate(input, () => create(input))),
|
||||
});
|
||||
}
|
||||
export const clientAuthentication = async <
|
||||
T extends { getHttpAuthenticationAsync(refresh?: boolean): Promise<IntegrationHttpAuthentication> },
|
||||
>(
|
||||
_input: IntegrationInput,
|
||||
create: () => Promise<T>,
|
||||
) => {
|
||||
const client = await create();
|
||||
const resolve = async (refresh = false): Promise<IntegrationHttpAuthentication> => {
|
||||
const auth = await client.getHttpAuthenticationAsync(refresh);
|
||||
const values = [...Object.values(auth.headers ?? {}), ...Object.values(auth.query ?? {})];
|
||||
for (const [name, value] of Object.entries(auth.headers ?? {})) {
|
||||
if (name.toLowerCase() === "cookie") {
|
||||
for (const cookie of value.split(";")) values.push(cookie.slice(cookie.indexOf("=") + 1).trim());
|
||||
export const clientAuthentication = async (
|
||||
create: () => Promise<{ getHttpAuthenticationAsync(refresh?: boolean): Promise<IntegrationHttpAuthentication> }>,
|
||||
) =>
|
||||
withAuthTimeout(async () => {
|
||||
const client = await create();
|
||||
const resolve = async (refresh = false): Promise<IntegrationHttpAuthentication> => {
|
||||
const auth = await client.getHttpAuthenticationAsync(refresh);
|
||||
const values = [...Object.values(auth.headers ?? {}), ...Object.values(auth.query ?? {})];
|
||||
for (const [name, value] of Object.entries(auth.headers ?? {})) {
|
||||
if (name.toLowerCase() === "cookie") {
|
||||
for (const cookie of value.split(";")) values.push(cookie.slice(cookie.indexOf("=") + 1).trim());
|
||||
}
|
||||
if (name.toLowerCase() === "authorization") values.push(value.slice(value.indexOf(" ") + 1));
|
||||
}
|
||||
if (name.toLowerCase() === "authorization") values.push(value.slice(value.indexOf(" ") + 1));
|
||||
}
|
||||
return {
|
||||
...auth,
|
||||
redactValues: auth.redactValues ?? values,
|
||||
refreshAsync: () => withAuthTimeout(() => resolve(true)),
|
||||
return {
|
||||
...auth,
|
||||
redactValues: auth.redactValues ?? values,
|
||||
refreshAsync: () => withAuthTimeout(() => resolve(true)),
|
||||
};
|
||||
};
|
||||
};
|
||||
return resolve();
|
||||
};
|
||||
return resolve();
|
||||
});
|
||||
|
||||
async function withAuthTimeout<T>(resolve: () => T | Promise<T>): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
@@ -132,17 +77,3 @@ async function withAuthTimeout<T>(resolve: () => T | Promise<T>): Promise<T> {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
export const feedAuth = (input: IntegrationInput): IntegrationHttpAuthentication => {
|
||||
const baseUrl = secret(input, "url");
|
||||
const base = new URL(baseUrl);
|
||||
return {
|
||||
baseUrl,
|
||||
redactValues: [...base.searchParams.values()],
|
||||
transformUrl(url) {
|
||||
// The stored feed URL is a complete endpoint, so the root request uses it exactly.
|
||||
if (url.pathname === `${base.pathname}/`) url.pathname = base.pathname;
|
||||
for (const [name, value] of base.searchParams) url.searchParams.set(name, value);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import ICAL from "ical.js";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -10,6 +11,20 @@ import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integ
|
||||
import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
|
||||
|
||||
export class ICalIntegration extends Integration implements ICalendarIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
const baseUrl = this.getSecretValue("url");
|
||||
const base = new URL(baseUrl);
|
||||
return {
|
||||
baseUrl,
|
||||
redactValues: [...base.searchParams.values()],
|
||||
transformUrl(url) {
|
||||
// The stored feed URL is a complete endpoint, so the root request uses it exactly.
|
||||
if (url.pathname === `${base.pathname}/`) url.pathname = base.pathname;
|
||||
for (const [name, value] of base.searchParams) url.searchParams.set(name, value);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(super.getSecretValue("url"));
|
||||
const result = await response.text();
|
||||
|
||||
@@ -17,6 +17,8 @@ import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/h
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ImageProxy } from "@homarr/image-proxy";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { apiKeyAuth } from "../http-auth";
|
||||
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
@@ -48,6 +50,10 @@ const logger = createLogger({ module: "immich-integration" });
|
||||
const carouselPhotoCount = 50;
|
||||
|
||||
export class ImmichIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
constructor(integration: IntegrationInput) {
|
||||
super(integration);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { apiKeyAuth } from "../http-auth";
|
||||
import type { MediaAvailability } from "../interfaces/media-requests/media-request-types";
|
||||
import { UpstreamMediaAvailability } from "../interfaces/media-requests/media-request-types";
|
||||
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
|
||||
|
||||
export class JellyseerrIntegration extends OverseerrIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
protected override mapAvailability(availability: UpstreamMediaAvailability, inProgress: boolean): MediaAvailability {
|
||||
// Availability statuses are not exactly the same between Jellyseerr and Overseerr (Jellyseerr has "blacklisted" additionally (deleted is the same value in overseerr))
|
||||
if (availability === UpstreamMediaAvailability.JellyseerrBlacklistedOrOverseerrDeleted) return "blacklisted";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ParseError, ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -22,6 +23,10 @@ import {
|
||||
const LLMACPP_REQUEST_TIMEOUT_MS = 10_000;
|
||||
|
||||
export class LlamacppIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/health"), {
|
||||
signal: AbortSignal.timeout(LLMACPP_REQUEST_TIMEOUT_MS),
|
||||
|
||||
@@ -3,6 +3,8 @@ import { z } from "zod/v4";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { apiKeyAuth } from "../../http-auth";
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||
@@ -14,9 +16,13 @@ import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
const logger = createLogger({ module: "lidarrIntegration" });
|
||||
|
||||
export class LidarrIntegration extends Integration implements ICalendarIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api"), {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
@@ -39,9 +45,7 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: {
|
||||
"X-Api-Key": super.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const lidarrCalendarEvents = await z.array(lidarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { z } from "zod/v4";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { apiKeyAuth } from "../../http-auth";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import { Integration } from "../../base/integration";
|
||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||
@@ -17,6 +19,10 @@ import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
const logger = createLogger({ module: "radarrIntegration" });
|
||||
|
||||
export class RadarrIntegration extends Integration implements ICalendarIntegration, IMediaOrganizerIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the events in the Radarr calendar between two dates.
|
||||
* @param start The start date
|
||||
@@ -31,9 +37,7 @@ export class RadarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: {
|
||||
"X-Api-Key": super.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const radarrCalendarEvents = await z.array(radarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
@@ -122,7 +126,7 @@ export class RadarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const data = await z
|
||||
.object({
|
||||
@@ -154,7 +158,7 @@ export class RadarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const data = await z
|
||||
.object({
|
||||
@@ -188,7 +192,7 @@ export class RadarrIntegration extends Integration implements ICalendarIntegrati
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api"), {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
|
||||
@@ -3,6 +3,8 @@ import { z } from "zod/v4";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { apiKeyAuth } from "../../http-auth";
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||
@@ -14,9 +16,13 @@ import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
const logger = createLogger({ module: "readarrIntegration" });
|
||||
|
||||
export class ReadarrIntegration extends Integration implements ICalendarIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api"), {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
@@ -45,9 +51,7 @@ export class ReadarrIntegration extends Integration implements ICalendarIntegrat
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: {
|
||||
"X-Api-Key": super.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const readarrCalendarEvents = await z.array(readarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { z } from "zod/v4";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../../http-auth";
|
||||
import { apiKeyAuth } from "../../http-auth";
|
||||
import { Integration } from "../../base/integration";
|
||||
import type { IntegrationTestingInput } from "../../base/integration";
|
||||
import { TestConnectionError } from "../../base/test-connection/test-connection-error";
|
||||
@@ -16,6 +18,10 @@ import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
const logger = createLogger({ module: "sonarrIntegration" });
|
||||
|
||||
export class SonarrIntegration extends Integration implements ICalendarIntegration, IMediaOrganizerIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the events in the Sonarr calendar between two dates.
|
||||
* @param start The start date
|
||||
@@ -33,9 +39,7 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: {
|
||||
"X-Api-Key": super.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const sonarrCalendarEvents = await z.array(sonarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
@@ -119,7 +123,7 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const data = await z
|
||||
.object({
|
||||
@@ -157,7 +161,7 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const data = await z
|
||||
.object({
|
||||
@@ -193,7 +197,7 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api"), {
|
||||
headers: { "X-Api-Key": super.getSecretValue("apiKey") },
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { apiKeyAuth } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -9,12 +11,17 @@ import type { TdarrQueue, TdarrStatistics, TdarrWorker } from "../interfaces/med
|
||||
import { getNodesResponseSchema, getStatisticsSchema, getStatusTableSchema } from "./tdarr-validation-schemas";
|
||||
|
||||
export class TdarrIntegration extends Integration implements IMediaTranscodingIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
if (!this.hasSecretValue("apiKey") || !this.getSecretValue("apiKey")) return {};
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/v2/is-server-alive"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"X-Api-Key": super.hasSecretValue("apiKey") ? super.getSecretValue("apiKey") : "",
|
||||
...(await this.getHttpAuthenticationAsync()).headers,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -30,7 +37,7 @@ export class TdarrIntegration extends Integration implements IMediaTranscodingIn
|
||||
const headerParams = {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
|
||||
...(await this.getHttpAuthenticationAsync()).headers,
|
||||
};
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
@@ -76,7 +83,7 @@ export class TdarrIntegration extends Integration implements IMediaTranscodingIn
|
||||
const url = this.url("/api/v2/get-nodes");
|
||||
const headerParams = {
|
||||
"Content-Type": "application/json",
|
||||
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
|
||||
...(await this.getHttpAuthenticationAsync()).headers,
|
||||
};
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
method: "GET",
|
||||
@@ -120,7 +127,7 @@ export class TdarrIntegration extends Integration implements IMediaTranscodingIn
|
||||
const url = this.url("/api/v2/client/status-tables");
|
||||
const headerParams = {
|
||||
"Content-Type": "application/json",
|
||||
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
|
||||
...(await this.getHttpAuthenticationAsync()).headers,
|
||||
};
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
method: "POST",
|
||||
@@ -159,7 +166,7 @@ export class TdarrIntegration extends Integration implements IMediaTranscodingIn
|
||||
const url = this.url("/api/v2/client/status-tables");
|
||||
const headerParams = {
|
||||
"Content-Type": "application/json",
|
||||
...(super.hasSecretValue("apiKey") ? { "X-Api-Key": super.getSecretValue("apiKey") } : {}),
|
||||
...(await this.getHttpAuthenticationAsync()).headers,
|
||||
};
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
method: "POST",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
@@ -52,6 +53,10 @@ export class MockIntegration
|
||||
ISmartHomeIntegration,
|
||||
IUpsSummaryIntegration
|
||||
{
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
throw new Error("This integration does not use HTTP");
|
||||
}
|
||||
|
||||
private static readonly dnsHole = new DnsHoleMockService();
|
||||
private static readonly calendar = new CalendarMockService();
|
||||
private static readonly downloadClient = new DownloadClientMockService();
|
||||
|
||||
@@ -12,6 +12,8 @@ import { ResponseError } from "@homarr/common/server";
|
||||
import { createHttpsAgentAsync, fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { basicAuth } from "../http-auth";
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
import { integrationTsdavHttpErrorHandler } from "../base/errors/http";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
@@ -57,6 +59,10 @@ dayjs.extend(timezone);
|
||||
|
||||
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
|
||||
export class NextcloudIntegration extends Integration implements ICalendarIntegration, INotificationsIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return basicAuth("username", "password", { "OCS-APIRequest": "true" })(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const client = await this.createCalendarClientAsync(await createHttpsAgentAsync(input.options));
|
||||
await client.login();
|
||||
@@ -168,12 +174,7 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
||||
public async getNotificationsAsync(): Promise<Notification[]> {
|
||||
const url = this.url("/ocs/v2.php/apps/notifications/api/v2/notifications", { format: "json" });
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
`${this.getSecretValue("username")}:${this.getSecretValue("password")}`,
|
||||
).toString("base64")}`,
|
||||
"OCS-APIRequest": "true",
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
@@ -9,6 +10,10 @@ import type { INotificationsIntegration } from "../interfaces/notifications/noti
|
||||
import { ntfyNotificationSchema } from "./ntfy-schema";
|
||||
|
||||
export class NTFYIntegration extends Integration implements INotificationsIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getHeaders() };
|
||||
}
|
||||
|
||||
public async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
await input.fetchAsync(this.url("/v1/account"), { headers: this.getHeaders() });
|
||||
return { success: true };
|
||||
@@ -17,7 +22,7 @@ export class NTFYIntegration extends Integration implements INotificationsIntegr
|
||||
private getTopicURL() {
|
||||
return this.url(`/${encodeURIComponent(super.getSecretValue("topic"))}/json`, { poll: 1 });
|
||||
}
|
||||
private getHeaders() {
|
||||
private getHeaders(): Record<string, string> {
|
||||
return this.hasSecretValue("apiKey") ? { Authorization: `Bearer ${super.getSecretValue("apiKey")}` } : {};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ParseError, ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { createChannelEventHistoryOld } from "../../../redis/src/lib/channel";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
@@ -22,6 +23,10 @@ import {
|
||||
} from "./opnsense-types";
|
||||
|
||||
export class OPNsenseIntegration extends Integration implements FirewallSummaryIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: { Authorization: this.getAuthHeaders() } };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/diagnostics/system/system_information"), {
|
||||
headers: {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { apiKeyAuth } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { ISearchableIntegration } from "../base/searchable-integration";
|
||||
@@ -40,11 +42,13 @@ export class OverseerrIntegration
|
||||
extends Integration
|
||||
implements IMediaRequestIntegration, ISearchableIntegration<OverseerrSearchResult>
|
||||
{
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
public async searchAsync(query: string) {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/search", { query }), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const schemaData = await searchSchema.parseAsync(await response.json());
|
||||
|
||||
@@ -69,9 +73,7 @@ export class OverseerrIntegration
|
||||
public async getSeriesInformationAsync(mediaType: "movie" | "tv", id: number) {
|
||||
const url = mediaType === "tv" ? this.url(`/api/v1/tv/${id}`) : this.url(`/api/v1/movie/${id}`);
|
||||
const response = await fetchWithTrustedCertificatesAsync(url, {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const data = await mediaInformationSchema.parseAsync(await response.json());
|
||||
const requestedSeasons = [
|
||||
@@ -110,9 +112,7 @@ export class OverseerrIntegration
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/v1/auth/me"), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
@@ -126,18 +126,14 @@ export class OverseerrIntegration
|
||||
const pendingRequests = await fetchWithTrustedCertificatesAsync(
|
||||
this.url("/api/v1/request", { take: 20, filter: "pending" }),
|
||||
{
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
},
|
||||
);
|
||||
|
||||
const allRequests = await fetchWithTrustedCertificatesAsync(
|
||||
this.url("/api/v1/request", { take: 20, sort: "modified" }),
|
||||
{
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -234,18 +230,14 @@ export class OverseerrIntegration
|
||||
|
||||
public async getStatsAsync(): Promise<RequestStats> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/request/count"), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
return await getStatsSchema.parseAsync(await response.json());
|
||||
}
|
||||
|
||||
public async getUsersAsync(): Promise<RequestUser[]> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/user", { take: 10, sort: "requests" }), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
const users = (await getUsersSchema.parseAsync(await response.json())).results;
|
||||
return users.map((user): RequestUser => {
|
||||
@@ -261,9 +253,7 @@ export class OverseerrIntegration
|
||||
logger.info("Approving media request", { requestId, integration: this.integration.name });
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/request/${requestId}/approve`), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
logger.info("Successfully approved media request", { requestId, integration: this.integration.name });
|
||||
@@ -274,9 +264,7 @@ export class OverseerrIntegration
|
||||
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/request/${requestId}/decline`), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
logger.info("Successfully declined media request", { requestId, integration: this.integration.name });
|
||||
@@ -284,9 +272,7 @@ export class OverseerrIntegration
|
||||
|
||||
private async getItemInformationAsync(id: number, type: MediaRequest["type"]): Promise<MediaInformation> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/${type}/${id}`), {
|
||||
headers: {
|
||||
"X-Api-Key": this.getSecretValue("apiKey"),
|
||||
},
|
||||
headers: (await this.getHttpAuthenticationAsync()).headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -9,6 +10,10 @@ import type { PaperlessNgxStats } from "./paperless-ngx-types";
|
||||
import { paperlessNgxPaginatedCountSchema, paperlessNgxStatisticsSchema } from "./paperless-ngx-types";
|
||||
|
||||
export class PaperlessNgxIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const statisticsUrl = this.url("/api/statistics/");
|
||||
const statisticsResponse = await input.fetchAsync(statisticsUrl, {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ParseError, ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -11,6 +12,10 @@ import { mapPatchMonStats, parsePatchMonStatsResponseAsync } from "./patchmon-ty
|
||||
const STATS_REQUEST_TIMEOUT_MS = 10_000;
|
||||
|
||||
export class PatchMonIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/v1/gethomepage/stats"), {
|
||||
headers: this.getAuthHeaders(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -11,6 +12,10 @@ import type { PeaNutDevice } from "./peanut-types";
|
||||
import { peaNutDevicesSchema } from "./peanut-types";
|
||||
|
||||
export class PeaNutIntegration extends Integration implements IUpsSummaryIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/v1/devices"), {
|
||||
headers: this.getAuthHeaders(),
|
||||
|
||||
@@ -6,6 +6,8 @@ import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/h
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ImageProxy } from "@homarr/image-proxy";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { headerAuth } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -50,6 +52,10 @@ function isStreamDirect(decision: string | undefined): boolean {
|
||||
}
|
||||
|
||||
export class PlexIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return headerAuth("X-Plex-Token")(this.integration);
|
||||
}
|
||||
|
||||
public async getCurrentSessionsAsync(_options: CurrentSessionsInput): Promise<StreamSession[]> {
|
||||
const token = super.getSecretValue("apiKey");
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { apiKeyAuth } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -11,6 +13,10 @@ import type { Indexer } from "../interfaces/indexer-manager/indexer-manager-type
|
||||
import { indexerResponseSchema, statusResponseSchema } from "./prowlarr-types";
|
||||
|
||||
export class ProwlarrIntegration extends Integration implements IIndexerManagerIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
public async getIndexersAsync(): Promise<Indexer[]> {
|
||||
const apiKey = super.getSecretValue("apiKey");
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import proxmoxApi from "proxmox-api";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
@@ -23,6 +24,14 @@ const logger = createLogger({ module: "proxmoxIntegration" });
|
||||
|
||||
@HandleIntegrationErrors([new ProxmoxApiErrorHandler()])
|
||||
export class ProxmoxIntegration extends Integration implements IClusterHealthMonitoringIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return {
|
||||
headers: {
|
||||
Authorization: `PVEAPIToken=${this.getSecretValue("username")}@${this.getSecretValue("realm")}!${this.getSecretValue("tokenId")}=${this.getSecretValue("apiKey")}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const proxmox = this.getPromoxApi(input.fetchAsync);
|
||||
await proxmox.nodes.$get();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -18,6 +19,10 @@ import type {
|
||||
} from "./speedtest-tracker-types";
|
||||
|
||||
export class SpeedtestTrackerIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders(), redactValues: [] };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const url = this.url("/api/v1/results/latest");
|
||||
const response = await input.fetchAsync(url, {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { ImageProxy } from "@homarr/image-proxy";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
@@ -17,6 +18,10 @@ import type {
|
||||
} from "./tracearr-types";
|
||||
|
||||
export class TracearrIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const healthUrl = this.url("/api/v1/public/health");
|
||||
const healthResponse = await input.fetchAsync(healthUrl, {
|
||||
|
||||
@@ -3,6 +3,7 @@ import z from "zod";
|
||||
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
@@ -32,6 +33,10 @@ const describeRequestError = (error: unknown) => {
|
||||
};
|
||||
|
||||
export class TrueNasIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
throw new Error("This integration does not use HTTP");
|
||||
}
|
||||
|
||||
private client?: TrueNasClient;
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import { apiKeyAuth } from "../http-auth";
|
||||
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
@@ -18,6 +20,10 @@ const logger = createLogger({ module: "UnraidIntegration" });
|
||||
|
||||
@HandleIntegrationErrors([])
|
||||
export class UnraidIntegration extends Integration implements ISystemHealthMonitoringIntegration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return apiKeyAuth(this.integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
await this.queryGraphQLAsync<{ info: UnraidSystemInfo }>(
|
||||
`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ParseError, ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -13,6 +14,10 @@ import {
|
||||
import type { UptimeKumaDashboardData, UptimeKumaMonitor, UptimeKumaMonitorCategory } from "./uptime-kuma-types";
|
||||
|
||||
export class UptimeKumaIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const statusPageResponse = await input.fetchAsync(this.statusPageUrl());
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Buffer } from "node:buffer";
|
||||
import { ParseError, ResponseError } from "@homarr/common/server";
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationHttpAuthentication } from "../http-auth";
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
@@ -13,6 +14,10 @@ import { mapWudStats, parseWudContainersResponseAsync } from "./wud-types";
|
||||
const CONTAINERS_REQUEST_TIMEOUT_MS = 10_000;
|
||||
|
||||
export class WudIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync(): Promise<IntegrationHttpAuthentication> {
|
||||
return { headers: this.getAuthHeaders() };
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/containers"), {
|
||||
headers: this.getAuthHeaders(),
|
||||
|
||||
@@ -45,6 +45,9 @@ describe("Base integration", () => {
|
||||
});
|
||||
|
||||
class FakeIntegration extends Integration {
|
||||
public async getHttpAuthenticationAsync() {
|
||||
return {};
|
||||
}
|
||||
constructor(
|
||||
private testingResult?: TestingResult,
|
||||
private error?: Error,
|
||||
|
||||
Reference in new issue
Block a user