UNPKG

homebridge-config-ui-x

Version:

A web based management, configuration and control platform for Homebridge.

157 lines 6.44 kB
import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import process from 'node:process'; import helmet from '@fastify/helmet'; import fastifyMultipart from '@fastify/multipart'; import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { FastifyAdapter } from '@nestjs/platform-fastify'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { Bonjour } from 'bonjour-service'; import { AppModule } from './app.module.js'; import { ConfigService } from './core/config/config.service.js'; import { getStartupConfig } from './core/config/config.startup.js'; import { devServerCorsConfig } from './core/cors.config.js'; import { Logger } from './core/logger/logger.service.js'; import { SpaFilter } from './core/spa/spa.filter.js'; import './env-setup.js'; import 'reflect-metadata'; import './self-check.js'; import './global-defaults.js'; export { HomebridgeIpcService } from './core/homebridge-ipc/homebridge-ipc.service.js'; async function bootstrap() { const startupConfig = await getStartupConfig(); const fAdapter = new FastifyAdapter({ https: startupConfig.httpsOptions, logger: startupConfig.debug || false, }); fAdapter.register(fastifyMultipart, { limits: { files: 1, fileSize: globalThis.backup.maxBackupSize, }, }); fAdapter.register(helmet, { hsts: false, frameguard: false, referrerPolicy: { policy: 'no-referrer', }, crossOriginEmbedderPolicy: false, crossOriginOpenerPolicy: false, crossOriginResourcePolicy: false, contentSecurityPolicy: { directives: { defaultSrc: ['\'self\''], scriptSrc: ['\'self\'', '\'unsafe-inline\'', '\'unsafe-eval\''], styleSrc: ['\'self\'', '\'unsafe-inline\''], imgSrc: ['\'self\'', 'data:', 'https://raw.githubusercontent.com', 'https://user-images.githubusercontent.com'], connectSrc: ['\'self\'', 'https://openweathermap.org', 'https://api.openweathermap.org', (req) => { return `wss://${req.headers.host} ws://${req.headers.host} ${startupConfig.cspWsOverride || ''}`; }], frameSrc: ['\'self\'', 'data:', 'https://developers.homebridge.io'], workerSrc: ['\'self\'', 'blob:'], fontSrc: ['\'self\'', 'data:'], scriptSrcAttr: null, objectSrc: null, frameAncestors: null, formAction: null, baseUri: null, upgradeInsecureRequests: null, blockAllMixedContent: null, }, }, }); const app = await NestFactory.create(AppModule, fAdapter, { logger: (startupConfig.debug || process.env.UIX_DEVELOPMENT === '1') ? new Logger() : false, httpsOptions: startupConfig.httpsOptions, }); const configService = app.get(ConfigService); const logger = app.get(Logger); app.getHttpAdapter().get('/', async (req, res) => { res.type('text/html'); res.header('Cache-Control', 'no-cache, no-store, must-revalidate'); res.header('Pragma', 'no-cache'); res.header('Expires', '0'); res.send(await readFile(resolve(process.env.UIX_BASE_PATH, 'public/index.html'))); }); app.useStaticAssets({ root: resolve(process.env.UIX_BASE_PATH, 'public'), setHeaders(res) { res.setHeader('Cache-Control', 'public,max-age=31536000,immutable'); }, }); app.setGlobalPrefix('/api'); app.enableCors({ ...devServerCorsConfig, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], }); app.useGlobalPipes(new ValidationPipe({ whitelist: true, skipMissingProperties: true, })); const options = new DocumentBuilder() .setTitle('Homebridge UI API Reference') .setVersion(configService.package.version) .addBearerAuth({ type: 'oauth2', flows: { password: { tokenUrl: '/api/auth/login', scopes: null, }, }, }) .build(); const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup('swagger', app, document); app.useGlobalFilters(new SpaFilter()); logger.warn(`Homebridge UI v${configService.package.version} is listening on ${startupConfig.host} port ${configService.ui.port}.`); await app.listen(configService.ui.port, startupConfig.host); let bonjour = null; if (configService.ui.enableMdnsAdvertise) { try { bonjour = new Bonjour(); const serviceName = configService.homebridgeConfig?.bridge?.name ? configService.homebridgeConfig.bridge.name : 'Homebridge UI'; const service = bonjour.publish({ name: serviceName, type: 'http', port: configService.ui.port, host: startupConfig.host === '0.0.0.0' || startupConfig.host === '::' ? undefined : startupConfig.host, txt: { path: '/', version: configService.package.version, https: startupConfig.httpsOptions ? 'true' : 'false', }, }); logger.log(`Homebridge UI HTTP service advertised via mDNS as "${service.name}" on port ${configService.ui.port}`); } catch (error) { logger.error('Failed to advertise mDNS service:', error); } } const handleShutdown = (signal) => { logger.log(`Received ${signal}, starting graceful shutdown...`); if (bonjour) { try { logger.log('Shutting down mDNS service advertising...'); bonjour.unpublishAll(); bonjour.destroy(); bonjour = null; } catch (error) { logger.error('Error during mDNS cleanup:', error); } } app.close().finally(() => { process.exit(0); }); }; process.once('SIGINT', () => handleShutdown('SIGINT')); process.once('SIGTERM', () => handleShutdown('SIGTERM')); return app; } export const app = bootstrap(); //# sourceMappingURL=main.js.map