Refactor internal APIs
This commit is contained in:
parent
4fad222b82
commit
479fbe5727
11 changed files with 51 additions and 75 deletions
|
@ -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[]>
|
||||
|
|
|
@ -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,
|
||||
|
|
2
src/adapters/discovery/index.ts
Normal file
2
src/adapters/discovery/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './api'
|
||||
export * from './hap_node_js_client'
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
2
src/adapters/http/index.ts
Normal file
2
src/adapters/http/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './api'
|
||||
export * from './fastify'
|
16
src/http.ts
16
src/http.ts
|
@ -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
|
||||
}
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue