homebridge-config-ui-x
Version:
A web based management, configuration and control platform for Homebridge.
607 lines • 29.7 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { Buffer } from 'node:buffer';
import { exec, spawn } from 'node:child_process';
import { createWriteStream } from 'node:fs';
import { readdir, unlink } from 'node:fs/promises';
import { extname, join, resolve } from 'node:path';
import process from 'node:process';
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';
import { Categories } from '@homebridge/hap-client/dist/hap-types.js';
import { BadRequestException, Inject, Injectable, InternalServerErrorException, NotFoundException, ServiceUnavailableException, } from '@nestjs/common';
import { pathExists, readJson, remove, writeJson } from 'fs-extra/esm';
import NodeCache from 'node-cache';
import { networkInterfaces } from 'systeminformation';
import { check as tcpCheck } from 'tcp-port-used';
import { ConfigService } from '../../core/config/config.service.js';
import { HomebridgeIpcService } from '../../core/homebridge-ipc/homebridge-ipc.service.js';
import { Logger } from '../../core/logger/logger.service.js';
import { AccessoriesService } from '../accessories/accessories.service.js';
import { ConfigEditorService } from '../config-editor/config-editor.service.js';
const pump = promisify(pipeline);
let ServerService = class ServerService {
configService;
configEditorService;
accessoriesService;
homebridgeIpcService;
logger;
serverServiceCache = new NodeCache({ stdTTL: 300 });
accessoryId;
accessoryInfoPath;
setupCode = null;
paired = false;
constructor(configService, configEditorService, accessoriesService, homebridgeIpcService, logger) {
this.configService = configService;
this.configEditorService = configEditorService;
this.accessoriesService = accessoriesService;
this.homebridgeIpcService = homebridgeIpcService;
this.logger = logger;
this.accessoryId = this.configService.homebridgeConfig.bridge.username.split(':').join('');
this.accessoryInfoPath = join(this.configService.storagePath, 'persist', `AccessoryInfo.${this.accessoryId}.json`);
}
async deleteSingleDeviceAccessories(id, cachedAccessoriesDir) {
const cachedAccessories = join(cachedAccessoriesDir, `cachedAccessories.${id}`);
const cachedAccessoriesBackup = join(cachedAccessoriesDir, `.cachedAccessories.${id}.bak`);
if (await pathExists(cachedAccessories)) {
await unlink(cachedAccessories);
this.logger.warn(`Bridge ${id} accessory removal: removed ${cachedAccessories}.`);
}
if (await pathExists(cachedAccessoriesBackup)) {
await unlink(cachedAccessoriesBackup);
this.logger.warn(`Bridge ${id} accessory removal: removed ${cachedAccessoriesBackup}.`);
}
}
async deleteSingleDevicePairing(id, resetPairingInfo) {
const persistPath = join(this.configService.storagePath, 'persist');
const accessoryInfo = join(persistPath, `AccessoryInfo.${id}.json`);
const identifierCache = join(persistPath, `IdentifierCache.${id}.json`);
try {
const configFile = await this.configEditorService.getConfigFile();
const username = id.match(/.{1,2}/g).join(':').toUpperCase();
const uiConfig = configFile.platforms.find(x => x.platform === 'config');
let blacklistChanged = false;
let bridgesChanged = false;
if (uiConfig.accessoryControl?.instanceBlacklist?.includes(username)) {
blacklistChanged = true;
uiConfig.accessoryControl.instanceBlacklist = uiConfig.accessoryControl.instanceBlacklist
.filter((x) => x.toUpperCase() !== username);
}
let oldBridgeConfig;
if (uiConfig.bridges && Array.isArray(uiConfig.bridges)) {
const bridgeIndex = uiConfig.bridges.findIndex(x => x.username?.toUpperCase() === username);
if (bridgeIndex > -1) {
bridgesChanged = true;
oldBridgeConfig = uiConfig.bridges[bridgeIndex];
uiConfig.bridges.splice(bridgeIndex, 1);
}
}
if (resetPairingInfo) {
const pluginBlocks = [
...(configFile.accessories || []),
...(configFile.platforms || []),
{ _bridge: configFile.bridge },
]
.filter((block) => block._bridge?.username?.toUpperCase() === username.toUpperCase());
const pluginBlock = pluginBlocks.find((block) => block._bridge?.port);
const otherBlocks = pluginBlocks.filter((block) => !block._bridge?.port);
if (pluginBlock) {
pluginBlock._bridge.username = this.configEditorService.generateUsername();
pluginBlock._bridge.pin = this.configEditorService.generatePin();
otherBlocks.forEach((block) => {
block._bridge.username = pluginBlock._bridge.username;
});
if (blacklistChanged) {
uiConfig.accessoryControl.instanceBlacklist = uiConfig.accessoryControl.instanceBlacklist
.concat(pluginBlock._bridge.username);
}
if (bridgesChanged) {
uiConfig.bridges.push({
...oldBridgeConfig,
username: pluginBlock._bridge.username,
});
}
this.logger.warn(`Bridge ${id} reset: new username: ${pluginBlock._bridge.username} and new pin: ${pluginBlock._bridge.pin}.`);
}
else {
this.logger.error(`Failed to reset username and pin for child bridge ${id} as the plugin block could not be found.`);
}
}
if (blacklistChanged) {
uiConfig.accessoryControl.instanceBlacklist = uiConfig.accessoryControl.instanceBlacklist
.sort((a, b) => a.localeCompare(b));
}
await this.configEditorService.updateConfigFile(configFile);
}
catch (e) {
this.logger.error(`Failed to reset username and pin for child bridge ${id} as ${e.message}.`);
}
if (await pathExists(accessoryInfo)) {
await unlink(accessoryInfo);
this.logger.warn(`Bridge ${id} reset: removed ${accessoryInfo}.`);
}
if (await pathExists(identifierCache)) {
await unlink(identifierCache);
this.logger.warn(`Bridge ${id} reset: removed ${identifierCache}.`);
}
await this.deleteDeviceAccessories(id);
}
async restartServer() {
this.logger.log('Homebridge restart request received.');
if (!await this.configService.uiRestartRequired() && !await this.nodeVersionChanged()) {
this.logger.log('UI/Bridge settings have not changed - only restarting Homebridge process.');
this.homebridgeIpcService.restartHomebridge();
this.accessoriesService.resetInstancePool();
return { ok: true, command: 'SIGTERM', restartingUI: false };
}
setTimeout(() => {
if (this.configService.ui.restart) {
this.logger.log(`Executing restart command ${this.configService.ui.restart}.`);
exec(this.configService.ui.restart, (err) => {
if (err) {
this.logger.log('Restart command exited with an error, failed to restart Homebridge.');
}
});
}
else {
this.logger.log('Sending SIGTERM to process...');
process.kill(process.pid, 'SIGTERM');
}
}, 500);
return { ok: true, command: this.configService.ui.restart, restartingUI: true };
}
async resetHomebridgeAccessory() {
this.configService.hbServiceUiRestartRequired = true;
const configFile = await this.configEditorService.getConfigFile();
const oldUsername = configFile.bridge.username;
configFile.bridge.pin = this.configEditorService.generatePin();
configFile.bridge.username = this.configEditorService.generateUsername();
const uiConfig = configFile.platforms.find(x => x.platform === 'config');
if (uiConfig.accessoryControl?.instanceBlacklist?.includes(oldUsername.toUpperCase())) {
uiConfig.accessoryControl.instanceBlacklist = uiConfig.accessoryControl.instanceBlacklist
.filter((x) => x.toUpperCase() !== oldUsername.toUpperCase())
.concat(configFile.bridge.pin)
.sort((a, b) => a.localeCompare(b));
}
this.logger.warn(`Homebridge bridge reset: new username ${configFile.bridge.username} and new pin ${configFile.bridge.pin}.`);
await this.configEditorService.updateConfigFile(configFile);
await remove(resolve(this.configService.storagePath, 'accessories'));
await remove(resolve(this.configService.storagePath, 'persist'));
this.logger.log('Homebridge bridge reset: accessories and persist directories were removed.');
}
async getDevicePairings() {
const persistPath = join(this.configService.storagePath, 'persist');
const devices = (await readdir(persistPath))
.filter(x => x.match(/AccessoryInfo\.([A-Fa-f0-9]+)\.json$/));
const configFile = await this.configEditorService.getConfigFile();
return Promise.all(devices.map(async (x) => {
return await this.getDevicePairingById(x.split('.')[1], configFile);
}));
}
async getDevicePairingById(deviceId, configFile = null) {
const persistPath = join(this.configService.storagePath, 'persist');
let device;
try {
device = await readJson(join(persistPath, `AccessoryInfo.${deviceId}.json`));
}
catch (e) {
throw new NotFoundException();
}
if (!configFile) {
configFile = await this.configEditorService.getConfigFile();
}
const username = deviceId.match(/.{1,2}/g).join(':');
const isMain = this.configService.homebridgeConfig.bridge.username.toUpperCase() === username.toUpperCase();
const pluginBlock = configFile.accessories
.concat(configFile.platforms)
.concat([{ _bridge: configFile.bridge }])
.find((block) => block._bridge?.username?.toUpperCase() === username.toUpperCase());
try {
device._category = Object.entries(Categories).find(([, value]) => value === device.category)[0].toLowerCase();
}
catch (e) {
device._category = 'Other';
}
device.name = pluginBlock?._bridge.name || pluginBlock?.name || device.displayName;
device._id = deviceId;
device._username = username;
device._main = isMain;
device._isPaired = device.pairedClients && Object.keys(device.pairedClients).length > 0;
device._setupCode = this.generateSetupCode(device);
device._couldBeStale = !device._main && device._category === 'bridge' && !pluginBlock;
delete device.signSk;
delete device.signPk;
delete device.configHash;
delete device.pairedClients;
delete device.pairedClientsPermission;
return device;
}
async deleteDevicePairing(id, resetPairingInfo) {
this.logger.warn(`Shutting down Homebridge before resetting paired bridge ${id}...`);
await this.homebridgeIpcService.restartAndWaitForClose();
await this.deleteSingleDevicePairing(id, resetPairingInfo);
return { ok: true };
}
async deleteDevicesPairing(bridges) {
this.logger.warn(`Shutting down Homebridge before resetting paired bridges ${bridges.map(x => x.id).join(', ')}...`);
await this.homebridgeIpcService.restartAndWaitForClose();
for (const { id, resetPairingInfo } of bridges) {
try {
await this.deleteSingleDevicePairing(id, resetPairingInfo);
}
catch (e) {
this.logger.error(`Failed to reset paired bridge ${id} as ${e.message}.`);
}
}
return { ok: true };
}
async deleteDeviceAccessories(id) {
this.logger.warn(`Shutting down Homebridge before removing accessories for paired bridge ${id}...`);
await this.homebridgeIpcService.restartAndWaitForClose();
const cachedAccessoriesDir = join(this.configService.storagePath, 'accessories');
await this.deleteSingleDeviceAccessories(id, cachedAccessoriesDir);
}
async deleteDevicesAccessories(bridges) {
this.logger.warn(`Shutting down Homebridge before removing accessories for paired bridges ${bridges.map(x => x.id).join(', ')}...`);
await this.homebridgeIpcService.restartAndWaitForClose();
const cachedAccessoriesDir = join(this.configService.storagePath, 'accessories');
for (const { id } of bridges) {
try {
await this.deleteSingleDeviceAccessories(id, cachedAccessoriesDir);
}
catch (e) {
this.logger.error(`Failed to remove accessories for bridge ${id} as ${e.message}.`);
}
}
}
async getCachedAccessories() {
const cachedAccessoriesDir = join(this.configService.storagePath, 'accessories');
const cachedAccessoryFiles = (await readdir(cachedAccessoriesDir))
.filter(x => x.match(/^cachedAccessories\.([A-F,0-9]+)$/) || x === 'cachedAccessories');
const cachedAccessories = [];
await Promise.all(cachedAccessoryFiles.map(async (x) => {
const accessories = await readJson(join(cachedAccessoriesDir, x));
for (const accessory of accessories) {
accessory.$cacheFile = x;
cachedAccessories.push(accessory);
}
}));
return cachedAccessories;
}
async deleteCachedAccessory(uuid, cacheFile) {
cacheFile = cacheFile || 'cachedAccessories';
const cachedAccessoriesPath = resolve(this.configService.storagePath, 'accessories', cacheFile);
this.logger.warn(`Shutting down Homebridge before removing cached accessory ${uuid}...`);
await this.homebridgeIpcService.restartAndWaitForClose();
const cachedAccessories = await readJson(cachedAccessoriesPath);
const accessoryIndex = cachedAccessories.findIndex(x => x.UUID === uuid);
if (accessoryIndex > -1) {
cachedAccessories.splice(accessoryIndex, 1);
await writeJson(cachedAccessoriesPath, cachedAccessories);
this.logger.warn(`Removed cached accessory with UUID ${uuid} from file ${cacheFile}.`);
}
else {
this.logger.error(`Cannot find cached accessory with UUID ${uuid} from file ${cacheFile}.`);
throw new NotFoundException();
}
return { ok: true };
}
async deleteCachedAccessories(accessories) {
this.logger.warn(`Shutting down Homebridge before removing cached accessories ${accessories.map(x => x.uuid).join(', ')}.`);
await this.homebridgeIpcService.restartAndWaitForClose();
const accessoriesByCacheFile = new Map();
for (const { cacheFile, uuid } of accessories) {
const accessoryCacheFile = cacheFile || 'cachedAccessories';
if (!accessoriesByCacheFile.has(accessoryCacheFile)) {
accessoriesByCacheFile.set(accessoryCacheFile, []);
}
accessoriesByCacheFile.get(accessoryCacheFile).push({ uuid });
}
for (const [cacheFile, accessories] of accessoriesByCacheFile.entries()) {
const cachedAccessoriesPath = resolve(this.configService.storagePath, 'accessories', cacheFile);
const cachedAccessories = await readJson(cachedAccessoriesPath);
for (const { uuid } of accessories) {
try {
const accessoryIndex = cachedAccessories.findIndex(x => x.UUID === uuid);
if (accessoryIndex > -1) {
cachedAccessories.splice(accessoryIndex, 1);
this.logger.warn(`Removed cached accessory with UUID ${uuid} from file ${cacheFile}.`);
}
else {
this.logger.error(`Cannot find cached accessory with UUID ${uuid} from file ${cacheFile}.`);
}
}
catch (e) {
this.logger.error(`Failed to remove cached accessory with UUID ${uuid} from file ${cacheFile} as ${e.message}.`);
}
}
await writeJson(cachedAccessoriesPath, cachedAccessories);
}
return { ok: true };
}
async deleteAllCachedAccessories() {
const cachedAccessoriesDir = join(this.configService.storagePath, 'accessories');
const cachedAccessoryPaths = (await readdir(cachedAccessoriesDir))
.filter(x => x.match(/cachedAccessories\.([A-F,0-9]+)/) || x === 'cachedAccessories' || x === '.cachedAccessories.bak')
.map(x => resolve(cachedAccessoriesDir, x));
const cachedAccessoriesPath = resolve(this.configService.storagePath, 'accessories', 'cachedAccessories');
await this.homebridgeIpcService.restartAndWaitForClose();
this.logger.warn('Shutting down Homebridge before removing cached accessories');
try {
this.logger.log('Clearing all cached accessories...');
for (const thisCachedAccessoriesPath of cachedAccessoryPaths) {
if (await pathExists(thisCachedAccessoriesPath)) {
await unlink(thisCachedAccessoriesPath);
this.logger.warn(`Removed ${thisCachedAccessoriesPath}.`);
}
}
}
catch (e) {
this.logger.error(`Failed to clear all cached accessories at ${cachedAccessoriesPath} as ${e.message}.`);
console.error(e);
throw new InternalServerErrorException('Failed to clear Homebridge accessory cache - see logs.');
}
return { ok: true };
}
async getSetupCode() {
if (this.setupCode) {
return this.setupCode;
}
else {
if (!await pathExists(this.accessoryInfoPath)) {
return null;
}
const accessoryInfo = await readJson(this.accessoryInfoPath);
this.setupCode = this.generateSetupCode(accessoryInfo);
return this.setupCode;
}
}
generateSetupCode(accessoryInfo) {
const buffer = Buffer.allocUnsafe(8);
let valueLow = Number.parseInt(accessoryInfo.pincode.replace(/-/g, ''), 10);
const valueHigh = accessoryInfo.category >> 1;
valueLow |= 1 << 28;
buffer.writeUInt32BE(valueLow, 4);
if (accessoryInfo.category & 1) {
buffer[4] = buffer[4] | 1 << 7;
}
buffer.writeUInt32BE(valueHigh, 0);
let encodedPayload = (buffer.readUInt32BE(4) + (buffer.readUInt32BE(0) * 2 ** 32)).toString(36).toUpperCase();
if (encodedPayload.length !== 9) {
for (let i = 0; i <= 9 - encodedPayload.length; i += 1) {
encodedPayload = `0${encodedPayload}`;
}
}
return `X-HM://${encodedPayload}${accessoryInfo.setupID}`;
}
async getBridgePairingInformation() {
if (!await pathExists(this.accessoryInfoPath)) {
return new ServiceUnavailableException('Pairing Information Not Available Yet');
}
const accessoryInfo = await readJson(this.accessoryInfoPath);
return {
displayName: accessoryInfo.displayName,
pincode: accessoryInfo.pincode,
setupCode: await this.getSetupCode(),
isPaired: accessoryInfo.pairedClients && Object.keys(accessoryInfo.pairedClients).length > 0,
};
}
async getSystemNetworkInterfaces() {
const fromCache = this.serverServiceCache.get('network-interfaces');
const interfaces = fromCache || (await networkInterfaces()).filter((adapter) => {
return !adapter.internal
&& (adapter.ip4 || (adapter.ip6));
});
if (!fromCache) {
this.serverServiceCache.set('network-interfaces', interfaces);
}
return interfaces;
}
async getHomebridgeNetworkInterfaces() {
const config = await this.configEditorService.getConfigFile();
if (!config.bridge?.bind) {
return [];
}
if (Array.isArray(config.bridge?.bind)) {
return config.bridge.bind;
}
if (typeof config.bridge?.bind === 'string') {
return [config.bridge.bind];
}
return [];
}
async getHomebridgeMdnsSetting() {
const config = await this.configEditorService.getConfigFile();
if (!config.bridge.advertiser) {
config.bridge.advertiser = 'bonjour-hap';
}
return {
advertiser: config.bridge.advertiser,
};
}
async setHomebridgeMdnsSetting(setting) {
const config = await this.configEditorService.getConfigFile();
config.bridge.advertiser = setting.advertiser;
await this.configEditorService.updateConfigFile(config);
}
async setHomebridgeNetworkInterfaces(adapters) {
const config = await this.configEditorService.getConfigFile();
if (!config.bridge) {
config.bridge = {};
}
if (!adapters.length) {
delete config.bridge.bind;
}
else {
config.bridge.bind = adapters;
}
await this.configEditorService.updateConfigFile(config);
}
async lookupUnusedPort() {
const min = this.configService.homebridgeConfig.ports?.start ?? 30000;
const max = this.configService.homebridgeConfig.ports?.end ?? 60000;
const randomPort = () => Math.floor(Math.random() * (max - min + 1) + min);
let port = randomPort();
while (await tcpCheck(port)) {
port = randomPort();
}
return { port };
}
async getHomebridgePort() {
const config = await this.configEditorService.getConfigFile();
return { port: config.bridge.port };
}
async getUsablePorts() {
const config = await this.configEditorService.getConfigFile();
let start;
let end;
if (config.ports && typeof config.ports === 'object') {
if (config.ports.start) {
start = config.ports.start;
}
if (config.ports.end) {
end = config.ports.end;
}
}
return { start, end };
}
async setHomebridgeName(name) {
if (!name || !(/^[\p{L}\p{N}][\p{L}\p{N} ']*[\p{L}\p{N}]$/u).test(name)) {
throw new BadRequestException('Invalid name');
}
const config = await this.configEditorService.getConfigFile();
config.bridge.name = name;
await this.configEditorService.updateConfigFile(config);
}
async setHomebridgePort(port) {
if (!port || typeof port !== 'number' || !Number.isInteger(port) || port < 1025 || port > 65533) {
throw new BadRequestException('Invalid port number');
}
const config = await this.configEditorService.getConfigFile();
config.bridge.port = port;
await this.configEditorService.updateConfigFile(config);
}
async setUsablePorts(value) {
let config = await this.configEditorService.getConfigFile();
if (value.start === null) {
delete value.start;
}
if (value.end === null) {
delete value.end;
}
if ('start' in value && (typeof value.start !== 'number' || value.start < 1025 || value.start > 65533)) {
throw new BadRequestException('Port start must be a number between 1025 and 65533.');
}
if ('end' in value && (typeof value.end !== 'number' || value.end < 1025 || value.end > 65533)) {
throw new BadRequestException('Port end must be a number between 1025 and 65533.');
}
if ('start' in value && 'end' in value && value.start >= value.end) {
throw new BadRequestException('Ports start must be less than end.');
}
if ('start' in value && !('end' in value) && config.ports?.end && value.start >= config.ports.end) {
throw new BadRequestException('Ports start must be less than end.');
}
if ('end' in value && !('start' in value) && config.ports?.start && config.ports.start >= value.end) {
throw new BadRequestException('Ports start must be less than end.');
}
if (!value.start && !value.end) {
delete config.ports;
}
else {
config.ports = {};
if (value.start) {
config.ports.start = value.start;
}
if (value.end) {
config.ports.end = value.end;
}
}
const { bridge, ports, ...rest } = config;
config = ports ? { bridge, ports, ...rest } : { bridge, ...rest };
await this.configEditorService.updateConfigFile(config);
}
async uploadWallpaper(data) {
const configFile = await this.configEditorService.getConfigFile();
const uiConfigBlock = configFile.platforms.find(x => x.platform === 'config');
if (uiConfigBlock) {
if (uiConfigBlock.wallpaper) {
const oldPath = join(this.configService.storagePath, uiConfigBlock.wallpaper);
if (await pathExists(oldPath)) {
try {
await unlink(oldPath);
this.logger.log(`Old wallpaper file ${oldPath} deleted successfully.`);
}
catch (e) {
this.logger.error(`Failed to delete old wallpaper ${oldPath} as ${e.message}.`);
}
}
}
const fileExtension = extname(data.filename);
const newPath = join(this.configService.storagePath, `ui-wallpaper${fileExtension}`);
await pump(data.file, createWriteStream(newPath));
uiConfigBlock.wallpaper = `ui-wallpaper${fileExtension}`;
await this.configEditorService.updateConfigFile(configFile);
this.logger.log('Wallpaper uploaded and set in the config file.');
}
}
async deleteWallpaper() {
const configFile = await this.configEditorService.getConfigFile();
const uiConfigBlock = configFile.platforms.find(x => x.platform === 'config');
const fullPath = join(this.configService.storagePath, uiConfigBlock.wallpaper);
if (uiConfigBlock && uiConfigBlock.wallpaper) {
if (await pathExists(fullPath)) {
try {
await unlink(fullPath);
this.logger.log(`Wallpaper file ${uiConfigBlock.wallpaper} deleted successfully.`);
}
catch (e) {
this.logger.error(`Failed to delete wallpaper file (${uiConfigBlock.wallpaper}) as ${e.message}.`);
}
}
delete uiConfigBlock.wallpaper;
await this.configEditorService.updateConfigFile(configFile);
this.configService.removeWallpaperCache();
this.logger.log('Wallpaper reference removed from the config file.');
}
}
async nodeVersionChanged() {
return new Promise((res) => {
let result = false;
const child = spawn(process.execPath, ['-v']);
child.stdout.once('data', (data) => {
result = data.toString().trim() !== process.version;
});
child.on('error', () => {
result = true;
});
child.on('close', () => {
return res(result);
});
});
}
};
ServerService = __decorate([
Injectable(),
__param(0, Inject(ConfigService)),
__param(1, Inject(ConfigEditorService)),
__param(2, Inject(AccessoriesService)),
__param(3, Inject(HomebridgeIpcService)),
__param(4, Inject(Logger)),
__metadata("design:paramtypes", [ConfigService,
ConfigEditorService,
AccessoriesService,
HomebridgeIpcService,
Logger])
], ServerService);
export { ServerService };
//# sourceMappingURL=server.service.js.map