Refactor internal APIs

This commit is contained in:
Lars Strojny 2022-11-14 01:20:53 +01:00
parent 4fad222b82
commit 479fbe5727
11 changed files with 51 additions and 75 deletions

View file

@ -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<Config, 'debug' | 'pin' | 'refresh_interval' | 'discovery_timeout' | 'request_timeout'>
log: Logger
}
export type HapDiscover = (config: HapConfig) => Promise<Device[]>
export type HapDiscover = (config: HapDiscoveryConfig) => Promise<Device[]>

View file

@ -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<string, HAPNodeJSClient> = {}
const promiseMap: Record<string, [ResolveFunc, RejectFunc]> = {}
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,

View file

@ -0,0 +1,2 @@
export * from './api'
export * from './hap_node_js_client'

View file

@ -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<HttpServerController>
export type HttpConfig = Pick<Config, 'debug' | 'port' | 'prefix'>
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
}

View file

@ -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() {

View file

@ -0,0 +1,2 @@
export * from './api'
export * from './fastify'

View file

@ -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
}

View file

@ -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'

View file

@ -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)

View file

@ -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 {

View file

@ -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)
}