|
| 1 | +import { createDashboardRouter } from './api/dashboard-router' |
| 2 | +import { createLogger } from '../factories/logger-factory' |
| 3 | +import { DashboardServiceConfig } from './config' |
| 4 | +import { DashboardWebSocketHub } from './ws/dashboard-ws-hub' |
| 5 | +import express from 'express' |
| 6 | +import { getHealthRequestHandler } from './handlers/request-handlers/get-health-request-handler' |
| 7 | +import http from 'http' |
| 8 | +import { PollingScheduler } from './polling/polling-scheduler' |
| 9 | +import { SnapshotService } from './services/snapshot-service' |
| 10 | +import { WebSocketServer } from 'ws' |
| 11 | + |
| 12 | +const debug = createLogger('dashboard-service:app') |
| 13 | + |
| 14 | +export interface DashboardService { |
| 15 | + readonly config: DashboardServiceConfig |
| 16 | + readonly snapshotService: SnapshotService |
| 17 | + readonly pollingScheduler: PollingScheduler |
| 18 | + start(): Promise<void> |
| 19 | + stop(): Promise<void> |
| 20 | + getHttpPort(): number |
| 21 | +} |
| 22 | + |
| 23 | +export const createDashboardService = (config: DashboardServiceConfig): DashboardService => { |
| 24 | + console.info( |
| 25 | + 'dashboard-service: creating service (host=%s, port=%d, wsPath=%s, pollIntervalMs=%d)', |
| 26 | + config.host, |
| 27 | + config.port, |
| 28 | + config.wsPath, |
| 29 | + config.pollIntervalMs, |
| 30 | + ) |
| 31 | + |
| 32 | + const snapshotService = new SnapshotService() |
| 33 | + |
| 34 | + const app = express() |
| 35 | + .disable('x-powered-by') |
| 36 | + .get('/healthz', getHealthRequestHandler) |
| 37 | + .use('/api/v1/kpis', createDashboardRouter(snapshotService)) |
| 38 | + |
| 39 | + const webServer = http.createServer(app) |
| 40 | + const webSocketServer = new WebSocketServer({ |
| 41 | + server: webServer, |
| 42 | + path: config.wsPath, |
| 43 | + }) |
| 44 | + |
| 45 | + const webSocketHub = new DashboardWebSocketHub(webSocketServer, () => snapshotService.getSnapshot()) |
| 46 | + |
| 47 | + const pollingScheduler = new PollingScheduler(config.pollIntervalMs, () => { |
| 48 | + const nextSnapshot = snapshotService.refreshPlaceholder() |
| 49 | + debug('poll tick produced snapshot sequence=%d', nextSnapshot.sequence) |
| 50 | + webSocketHub.broadcastTick(nextSnapshot.sequence) |
| 51 | + webSocketHub.broadcastSnapshot(nextSnapshot) |
| 52 | + }) |
| 53 | + |
| 54 | + const start = async () => { |
| 55 | + if (webServer.listening) { |
| 56 | + debug('start requested but service is already listening') |
| 57 | + return |
| 58 | + } |
| 59 | + |
| 60 | + console.info('dashboard-service: starting http and websocket servers') |
| 61 | + |
| 62 | + await new Promise<void>((resolve, reject) => { |
| 63 | + webServer.listen(config.port, config.host, () => { |
| 64 | + const address = webServer.address() |
| 65 | + debug('listening on %o', address) |
| 66 | + console.info('dashboard-service: listening on %o', address) |
| 67 | + resolve() |
| 68 | + }) |
| 69 | + webServer.once('error', (error) => { |
| 70 | + console.error('dashboard-service: failed to start server', error) |
| 71 | + reject(error) |
| 72 | + }) |
| 73 | + }) |
| 74 | + |
| 75 | + pollingScheduler.start() |
| 76 | + console.info('dashboard-service: polling scheduler started') |
| 77 | + } |
| 78 | + |
| 79 | + const stop = async () => { |
| 80 | + console.info('dashboard-service: stopping service') |
| 81 | + pollingScheduler.stop() |
| 82 | + webSocketHub.close() |
| 83 | + await new Promise<void>((resolve, reject) => { |
| 84 | + if (!webServer.listening) { |
| 85 | + debug('stop requested while server was already stopped') |
| 86 | + resolve() |
| 87 | + return |
| 88 | + } |
| 89 | + |
| 90 | + webServer.close((error) => { |
| 91 | + if (error) { |
| 92 | + console.error('dashboard-service: failed to stop cleanly', error) |
| 93 | + reject(error) |
| 94 | + return |
| 95 | + } |
| 96 | + |
| 97 | + console.info('dashboard-service: http server closed') |
| 98 | + resolve() |
| 99 | + }) |
| 100 | + }) |
| 101 | + } |
| 102 | + |
| 103 | + const getHttpPort = (): number => { |
| 104 | + const address = webServer.address() |
| 105 | + return typeof address === 'object' && address !== null ? address.port : config.port |
| 106 | + } |
| 107 | + |
| 108 | + return { |
| 109 | + config, |
| 110 | + snapshotService, |
| 111 | + pollingScheduler, |
| 112 | + start, |
| 113 | + stop, |
| 114 | + getHttpPort, |
| 115 | + } |
| 116 | +} |
0 commit comments