diff --git a/src/adapters/discovery/api.ts b/src/adapters/discovery/api.ts index 0041b76..183f472 100644 --- a/src/adapters/discovery/api.ts +++ b/src/adapters/discovery/api.ts @@ -1,14 +1,9 @@ -import type { Device } from '../../boundaries/hap' +import type { Config, Device } from '../../boundaries' import type { Logger } from 'homebridge' -type Pin = string - -export interface HapConfig { - pin: Pin - refreshInterval: number - discoveryTimeout: number - requestTimeout: number - logger: Logger - debug: boolean +export interface HapDiscoveryConfig { + config: Pick + log: Logger } -export type HapDiscover = (config: HapConfig) => Promise + +export type HapDiscover = (config: HapDiscoveryConfig) => Promise diff --git a/src/adapters/discovery/hap_node_js_client.ts b/src/adapters/discovery/hap_node_js_client.ts index e5e5342..060d321 100644 --- a/src/adapters/discovery/hap_node_js_client.ts +++ b/src/adapters/discovery/hap_node_js_client.ts @@ -1,25 +1,18 @@ import type { HapDiscover } from './api' -import { HAPNodeJSClient } from 'hap-node-client' +import { HAPNodeJSClient, type HAPNodeJSClientConfig } from 'hap-node-client' import { type Device, DeviceBoundary, checkBoundary } from '../../boundaries' import type { Logger } from 'homebridge' import z from 'zod' const MaybeDevices = z.array(z.unknown()) -interface HapConfig { - debug: boolean - refresh: number - timeout: number - reqTimeout: number - pin: string -} type ResolveFunc = (devices: Device[]) => void type RejectFunc = (error: unknown) => void const clientMap: Record = {} const promiseMap: Record = {} -function startDiscovery(logger: Logger, config: HapConfig, resolve: ResolveFunc, reject: RejectFunc) { +function startDiscovery(logger: Logger, config: HAPNodeJSClientConfig, resolve: ResolveFunc, reject: RejectFunc) { const key = JSON.stringify(config) if (!clientMap[key]) { @@ -49,16 +42,16 @@ function startDiscovery(logger: Logger, config: HapConfig, resolve: ResolveFunc, promiseMap[key] = [resolve, reject] } -export const discover: HapDiscover = ({ pin, refreshInterval, discoveryTimeout, requestTimeout, logger, debug }) => { +export const hapNodeJsClientDiscover: HapDiscover = ({ config, log }) => { return new Promise((resolve, reject) => { startDiscovery( - logger, + log, { - debug: debug, - refresh: refreshInterval, - timeout: discoveryTimeout, - reqTimeout: requestTimeout, - pin, + debug: config.debug, + refresh: config.refresh_interval, + timeout: config.discovery_timeout, + reqTimeout: config.request_timeout, + pin: config.pin, }, resolve, reject, diff --git a/src/adapters/discovery/index.ts b/src/adapters/discovery/index.ts new file mode 100644 index 0000000..82b1dbe --- /dev/null +++ b/src/adapters/discovery/index.ts @@ -0,0 +1,2 @@ +export * from './api' +export * from './hap_node_js_client' diff --git a/src/adapters/http/api.ts b/src/adapters/http/api.ts index 007148e..f2b9037 100644 --- a/src/adapters/http/api.ts +++ b/src/adapters/http/api.ts @@ -1,4 +1,7 @@ -import type { HttpServer } from '../../http' +import type { Logger } from 'homebridge' +import type { RequestListener, Server } from 'http' +import type { Config } from '../../boundaries' +import type { Metric } from '../../metrics' export interface HttpResponse { statusCode?: number @@ -11,3 +14,16 @@ export interface HttpServerController { } export type HttpAdapter = (config: HttpServer) => Promise + +export type HttpConfig = Pick + +export interface HttpServer { + log?: Logger + config: HttpConfig + serverFactory?: (requestListener: RequestListener) => Server + onRequest(): HttpResponse | undefined + onMetrics(): HttpResponse + onNotFound(): HttpResponse + onError(error: unknown): HttpResponse + updateMetrics(metrics: Metric[]): void +} diff --git a/src/adapters/http/fastify.ts b/src/adapters/http/fastify.ts index 57c2201..0683833 100644 --- a/src/adapters/http/fastify.ts +++ b/src/adapters/http/fastify.ts @@ -1,6 +1,5 @@ import Fastify, { type FastifyReply, type FastifyRequest, type HookHandlerDoneFunction } from 'fastify' -import type { HttpAdapter, HttpResponse } from './api' -import type { HttpServer } from '../../http' +import type { HttpAdapter, HttpResponse, HttpServer } from './api' function adaptResponseToReply(response: HttpResponse, reply: FastifyReply): void { if (response.statusCode) { @@ -23,7 +22,7 @@ function formatCombinedLog(request: FastifyRequest, reply: FastifyReply): string return `${remoteAddress} - "${request.method} ${request.url} HTTP/${request.raw.httpVersion}" ${reply.statusCode} "${request.protocol}://${request.hostname}" "${userAgent}" "${contentType}"` } -export const serve: HttpAdapter = async (server: HttpServer) => { +export const fastifyServe: HttpAdapter = async (server: HttpServer) => { const fastify = Fastify({ logger: false, serverFactory: server.serverFactory, @@ -32,7 +31,7 @@ export const serve: HttpAdapter = async (server: HttpServer) => { fastify.addHook('onResponse', (request: FastifyRequest, reply: FastifyReply) => { if (reply.statusCode >= 400) { server.log?.warn(formatCombinedLog(request, reply)) - } else if (server.debug) { + } else if (server.config.debug) { server.log?.debug(formatCombinedLog(request, reply)) } }) @@ -59,7 +58,7 @@ export const serve: HttpAdapter = async (server: HttpServer) => { adaptResponseToReply(server.onMetrics(), reply) }) - await fastify.listen({ port: server.port, host: '::' }) + await fastify.listen({ port: server.config.port, host: '::' }) return { shutdown() { diff --git a/src/adapters/http/index.ts b/src/adapters/http/index.ts new file mode 100644 index 0000000..0bd7645 --- /dev/null +++ b/src/adapters/http/index.ts @@ -0,0 +1,2 @@ +export * from './api' +export * from './fastify' diff --git a/src/http.ts b/src/http.ts deleted file mode 100644 index 4a052fc..0000000 --- a/src/http.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { HttpResponse } from './adapters/http/api' -import type { Metric } from './metrics' -import type { Logger } from 'homebridge' -import type { RequestListener, Server } from 'http' - -export interface HttpServer { - port: number - debug: boolean - log?: Logger - serverFactory?: (requestListener: RequestListener) => Server - onRequest(): HttpResponse | undefined - onMetrics(): HttpResponse - onNotFound(): HttpResponse - onError(error: unknown): HttpResponse - updateMetrics(metrics: Metric[]): void -} diff --git a/src/metrics.ts b/src/metrics.ts index 8c17ee7..9981335 100644 --- a/src/metrics.ts +++ b/src/metrics.ts @@ -1,4 +1,4 @@ -import type { Accessory, Device, Service } from './boundaries/hap' +import type { Accessory, Device, Service } from './boundaries' import { Uuids } from './generated/services' import { assertTypeExhausted, isType } from './std' diff --git a/src/platform.ts b/src/platform.ts index 4806304..0087638 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,9 +1,8 @@ import type { API, IndependentPlatformPlugin, Logger, PlatformConfig } from 'homebridge' import { aggregate } from './metrics' -import { discover } from './adapters/discovery/hap_node_js_client' -import { serve } from './adapters/http/fastify' -import type { HttpServerController } from './adapters/http/api' +import { hapNodeJsClientDiscover as discover } from './adapters/discovery' +import { type HttpServerController, fastifyServe as serve } from './adapters/http' import { PrometheusServer } from './prometheus' import { type Config, ConfigBoundary, checkBoundary } from './boundaries' @@ -28,7 +27,7 @@ export class PrometheusExporterPlatform implements IndependentPlatformPlugin { this.log.debug('Starting Prometheus HTTP server on port %d', this.config.port) - this.httpServer = new PrometheusServer(this.config.port, this.log, this.config.debug, this.config.prefix) + this.httpServer = new PrometheusServer(this.config, this.log) serve(this.httpServer) .then((httpServerController) => { this.log.debug('HTTP server started on port %d', this.config.port) @@ -46,14 +45,7 @@ export class PrometheusExporterPlatform implements IndependentPlatformPlugin { private startHapDiscovery(): void { this.log.debug('Starting HAP discovery') - discover({ - logger: this.log, - refreshInterval: this.config.refresh_interval, - discoveryTimeout: this.config.discovery_timeout, - requestTimeout: this.config.request_timeout, - pin: this.config.pin, - debug: this.config.debug, - }) + discover({ log: this.log, config: this.config }) .then((devices) => { const metrics = aggregate(devices, new Date()) this.httpServer.updateMetrics(metrics) diff --git a/src/prometheus.ts b/src/prometheus.ts index 2d3332f..cc66156 100644 --- a/src/prometheus.ts +++ b/src/prometheus.ts @@ -1,7 +1,6 @@ import type { Metric } from './metrics' import type { Logger } from 'homebridge' -import type { HttpResponse } from './adapters/http/api' -import type { HttpServer } from './http' +import type { HttpConfig, HttpResponse, HttpServer } from './adapters/http' export class MetricsRenderer { constructor(private readonly prefix: string) {} @@ -44,12 +43,7 @@ export class PrometheusServer implements HttpServer { private metricsInitialized = false private metrics: Metric[] = [] - constructor( - public readonly port: number, - public readonly log: Logger | undefined, - public readonly debug: boolean, - private readonly prefix: string, - ) {} + constructor(public readonly config: HttpConfig, public readonly log: Logger | undefined = undefined) {} onRequest(): HttpResponse | undefined { if (!this.metricsInitialized) { @@ -62,7 +56,7 @@ export class PrometheusServer implements HttpServer { } onMetrics(): HttpResponse { - const renderer = new MetricsRenderer(this.prefix) + const renderer = new MetricsRenderer(this.config.prefix) const metrics = this.metrics.map((metric) => renderer.render(metric)).join('\n') return { diff --git a/tests/adapters/http/fastify.test.ts b/tests/adapters/http/fastify.test.ts index 49299e2..1c5391f 100644 --- a/tests/adapters/http/fastify.test.ts +++ b/tests/adapters/http/fastify.test.ts @@ -1,9 +1,8 @@ import { describe, test } from '@jest/globals' import request from 'supertest' import { PrometheusServer } from '../../../src/prometheus' -import { serve } from '../../../src/adapters/http/fastify' +import { type HttpServer, fastifyServe } from '../../../src/adapters/http' import { type Server, createServer } from 'http' -import type { HttpServer } from '../../../src/http' import { Metric } from '../../../src/metrics' class TestablePrometheusServer extends PrometheusServer { @@ -12,9 +11,9 @@ class TestablePrometheusServer extends PrometheusServer { function createTestServer(): { http: Server; prometheus: HttpServer } { const http = createServer() - const prometheus = new TestablePrometheusServer(0, undefined, false, 'homebridge') + const prometheus = new TestablePrometheusServer({ port: 0, debug: false, prefix: 'homebridge' }) prometheus.serverFactory = (handler) => http.on('request', handler) - serve(prometheus).catch((err: Error) => { + fastifyServe(prometheus).catch((err: Error) => { if (!('code' in err) || (err as unknown as { code: unknown }).code !== 'ERR_SERVER_ALREADY_LISTEN') { console.debug(err) }