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"; import type { TestingResult } from "../base/test-connection/test-connection-service"; import { speedtestTrackerLatestResultEnvelopeSchema, speedtestTrackerResultsCollectionSchema, speedtestTrackerStatsEnvelopeSchema, } from "./speedtest-tracker-types"; import type { SpeedtestTrackerDashboardData, SpeedtestTrackerResult, SpeedtestTrackerResultsCollection, SpeedtestTrackerStats, } from "./speedtest-tracker-types"; export class SpeedtestTrackerIntegration extends Integration { public async getHttpAuthenticationAsync(): Promise { return { headers: this.getAuthHeaders(), redactValues: [] }; } protected async testingAsync(input: IntegrationTestingInput): Promise { const url = this.url("/api/v1/results/latest"); const response = await input.fetchAsync(url, { headers: this.getAuthHeaders(), }); if (!response.ok) { return TestConnectionError.StatusResult(response); } return { success: true }; } public async getLatestResultAsync(): Promise { const response = await this.getAsync("/api/v1/results/latest"); // Speedtest Tracker returns 404 when no tests have been run yet (fresh install) if (response.status === 404) { return null; } if (!response.ok) { throw new ResponseError(response); } return speedtestTrackerLatestResultEnvelopeSchema.parse(await response.json()).data; } public async getStatsAsync(): Promise { const response = await this.getAsync("/api/v1/stats"); if (!response.ok) { throw new ResponseError(response); } return speedtestTrackerStatsEnvelopeSchema.parse(await response.json()).data; } public async getRecentResultsAsync(perPage = 10): Promise { const response = await this.getAsync("/api/v1/results", { result_count: String(perPage), sort: "-created_at", }); if (!response.ok) { throw new ResponseError(response); } return speedtestTrackerResultsCollectionSchema.parse(await response.json()); } public async getDashboardDataAsync(): Promise { const [latestResult, stats, recentCollection] = await Promise.all([ this.getLatestResultAsync(), this.getStatsAsync(), this.getRecentResultsAsync(30), ]); return { latestResult, stats, recentResults: recentCollection.data, }; } private async getAsync(path: `/api/v1/${string}`, queryParams?: Record) { return await fetchWithTrustedCertificatesAsync(this.url(path, queryParams), { headers: this.getAuthHeaders(), }); } private getAuthHeaders(): Record { return { Authorization: `Bearer ${this.getSecretValue("apiKey")}`, Accept: "application/json", }; } }