From 290ae5a6890e501f0fd5edd819898d2526441f8f Mon Sep 17 00:00:00 2001 From: oznu Date: Tue, 2 Feb 2021 21:47:50 +1100 Subject: [PATCH] use common ipc service for hb-service homebridge control --- nodemon.json | 2 +- package-lock.json | 2 +- package.json | 2 +- src/bin/hb-service.ts | 93 +++--------- .../homebridge-ipc/homebridge-ipc.module.ts | 16 +++ .../homebridge-ipc/homebridge-ipc.service.ts | 134 ++++++++++++++++++ src/main.ts | 7 +- src/modules/backup/backup.module.ts | 2 + src/modules/backup/backup.service.ts | 12 +- src/modules/server/server.module.ts | 2 + src/modules/server/server.service.ts | 82 +++++------ src/modules/status/status.controller.ts | 9 ++ src/modules/status/status.gateway.ts | 9 ++ src/modules/status/status.module.ts | 2 + src/modules/status/status.service.ts | 15 +- test/e2e/server.e2e-spec.ts | 17 --- .../installed-plugins.component.ts | 2 +- .../search-plugins.component.ts | 2 +- 18 files changed, 267 insertions(+), 143 deletions(-) create mode 100644 src/core/homebridge-ipc/homebridge-ipc.module.ts create mode 100644 src/core/homebridge-ipc/homebridge-ipc.service.ts diff --git a/nodemon.json b/nodemon.json index 2d197f725..78ab00927 100644 --- a/nodemon.json +++ b/nodemon.json @@ -6,6 +6,6 @@ "ignore": [ "src/**/*.spec.ts" ], - "exec": "sleep 2 && UIX_INSECURE_MODE=1 UIX_SERVICE_MODE=1 HOMEBRIDGE_CONFIG_UI_TERMINAL=1 ts-node -r tsconfig-paths/register src/main.ts", + "exec": "sleep 2 && UIX_INSECURE_MODE=1 UIX_SERVICE_MODE=1 HOMEBRIDGE_CONFIG_UI_TERMINAL=1 ts-node -r tsconfig-paths/register src/bin/hb-service.ts run --stdout", "signal": "SIGTERM" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cf0058988..fe3af8bc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "homebridge-config-ui-x", - "version": "4.37.1-test.3", + "version": "4.37.1-test.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0e8e45ae5..b216a23ef 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "homebridge-config-ui-x", "displayName": "Homebridge UI", - "version": "4.37.1-test.3", + "version": "4.37.1-test.5", "description": "A web based management, configuration and control platform for Homebridge", "license": "MIT", "author": "oznu ", diff --git a/src/bin/hb-service.ts b/src/bin/hb-service.ts index cf8f4dacb..f8afd9699 100644 --- a/src/bin/hb-service.ts +++ b/src/bin/hb-service.ts @@ -23,6 +23,8 @@ import { Win32Installer } from './platforms/win32'; import { LinuxInstaller } from './platforms/linux'; import { DarwinInstaller } from './platforms/darwin'; +import type { HomebridgeIpcService } from '../core/homebridge-ipc/homebridge-ipc.service'; + export class HomebridgeServiceHelper { public action: 'install' | 'uninstall' | 'start' | 'stop' | 'restart' | 'rebuild' | 'run' | 'logs' | 'update-node' | 'before-start' | 'status'; public selfPath = __filename; @@ -53,6 +55,9 @@ export class HomebridgeServiceHelper { private installer: Win32Installer | LinuxInstaller | DarwinInstaller; + // ui services + private ipcService: HomebridgeIpcService; + get logPath(): string { return path.resolve(this.storagePath, 'homebridge.log'); } @@ -337,6 +342,9 @@ export class HomebridgeServiceHelper { // start homebridge this.startExitHandler(); + // start the ui + await this.runUi(); + // delay the launch of homebridge on Raspberry Pi 1/Zero by 20 seconds if (os.cpus().length === 1 && os.arch() === 'arm') { this.logger('Delaying Homebridge startup by 20 seconds on low powered server'); @@ -346,29 +354,6 @@ export class HomebridgeServiceHelper { } else { this.runHomebridge(); } - - // start the ui - this.runUi(); - - process.addListener('message', (event, callback) => { - switch (event) { - case 'clearCachedAccessories': { - return this.clearHomebridgeCachedAccessories(callback); - } - case 'deleteSingleCachedAccessory': { - return this.clearHomebridgeCachedAccessories(callback); - } - case 'restartHomebridge': { - return this.restartHomebridge(); - } - case 'postBackupRestoreRestart': { - return this.postBackupRestoreRestart(); - } - case 'getHomebridgeChildProcess': { - return this.getHomebridgeChildProcess(callback); - } - } - }); } /** @@ -447,6 +432,9 @@ export class HomebridgeServiceHelper { childProcessOpts, ); + // let the ipc service know of the new process + this.ipcService.setHomebridgeProcess(this.homebridge); + this.logger(`Started Homebridge v${this.homebridgePackage.version} with PID: ${this.homebridge.pid}`); this.homebridge.stdout.on('data', (data) => { @@ -485,7 +473,14 @@ export class HomebridgeServiceHelper { */ private async runUi() { try { - await import('../main'); + // import main module + const main = await import('../main'); + + // load the nest js instance + const ui = await main.app; + + // extract services + this.ipcService = ui.get('HomebridgeIpcService'); } catch (e) { this.logger('ERROR: The user interface threw an unhandled error'); console.error(e); @@ -994,56 +989,6 @@ export class HomebridgeServiceHelper { } } - /** - * Clears the Homebridge Cached Accessories - */ - private clearHomebridgeCachedAccessories(callback) { - if (this.homebridge && !this.homebridgeStopped) { - this.homebridge.once('close', callback); - this.restartHomebridge(); - } else { - callback(); - } - } - - /** - * Standard SIGTERM restart for Homebridge - */ - private restartHomebridge() { - if (this.homebridge) { - this.logger('Sending SIGTERM to Homebridge'); - this.homebridge.kill('SIGTERM'); - - setTimeout(() => { - if (!this.homebridgeStopped) { - try { - this.logger('Sending SIGKILL to Homebridge'); - this.homebridge.kill('SIGKILL'); - } catch (e) { } - } - }, 7000); - } - } - - /** - * Send SIGKILL to Homebridge after a restore is completed to prevent the - * Homebridge cached accessories being regenerated - */ - private postBackupRestoreRestart() { - if (this.homebridge) { - this.logger('Sending SIGKILL to Homebridge'); - this.homebridge.kill('SIGKILL'); - } - - setTimeout(() => { - process.kill(process.pid, 'SIGKILL'); - }, 500); - } - - private getHomebridgeChildProcess(callback) { - callback(this.homebridge); - } - /** * Fix the permission on the docker storage directory * This is only used when running in the oznu/docker-homebridge docker container diff --git a/src/core/homebridge-ipc/homebridge-ipc.module.ts b/src/core/homebridge-ipc/homebridge-ipc.module.ts new file mode 100644 index 000000000..11c383576 --- /dev/null +++ b/src/core/homebridge-ipc/homebridge-ipc.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { LoggerModule } from '../logger/logger.module'; +import { HomebridgeIpcService } from './homebridge-ipc.service'; + +@Module({ + imports: [ + LoggerModule, + ], + providers: [ + HomebridgeIpcService + ], + exports: [ + HomebridgeIpcService + ], +}) +export class HomebridgeIpcModule { } diff --git a/src/core/homebridge-ipc/homebridge-ipc.service.ts b/src/core/homebridge-ipc/homebridge-ipc.service.ts new file mode 100644 index 000000000..223afa316 --- /dev/null +++ b/src/core/homebridge-ipc/homebridge-ipc.service.ts @@ -0,0 +1,134 @@ +import { Injectable, ServiceUnavailableException } from '@nestjs/common'; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; +import { Logger } from '../logger/logger.service'; + +@Injectable() +export class HomebridgeIpcService extends EventEmitter { + private homebridge: ChildProcess; + + private permittedEvents = [ + 'childBridgeMetadataResponse', + 'childBridgeStatusUpdate', + ]; + + constructor( + private logger: Logger + ) { + super(); + } + + /** + * Set the current homebridge process. + * This method is called from hb-service. + */ + public setHomebridgeProcess(process: ChildProcess) { + this.homebridge = process; + + this.homebridge.on('message', (message: { id: string; data: unknown }) => { + if (typeof message !== 'object' || !message.id) { + return; + } + if (this.permittedEvents.includes(message.id)) { + this.emit(message.id, message.data); + } + }); + } + + /** + * Send a message to the homebridge child process + */ + private sendMessage(type: string, data: unknown) { + if (this.homebridge && this.homebridge.connected) { + this.homebridge.send({ id: type, data: data }); + } else { + throw new ServiceUnavailableException('The Homebridge Service Is Unavailable'); + } + } + + /** + * Send a data request to homebridge and wait for the reply + */ + private async requestResponse(requestEvent: string, responseEvent: string) { + return new Promise((resolve, reject) => { + const actionTimeout = setTimeout(() => { + this.removeListener(responseEvent, listener); + reject('The Homebridge service did not respond'); + }, 3000); + + const listener = (data) => { + clearTimeout(actionTimeout); + resolve(data); + }; + + this.once(responseEvent, listener); + this.homebridge.send({ id: requestEvent }); + }); + } + + /** + * Restarts the main bridge process and any child bridges + */ + public restartHomebridge(): void { + if (this.homebridge) { + this.logger.log('Sending SIGTERM to Homebridge'); + + // send SIGTERM command + this.homebridge.kill('SIGTERM'); + + // prepare a timeout to send SIGKILL after 7 seconds if not shutdown before then + const shutdownTimeout = setTimeout(() => { + try { + this.logger.warn('Sending SIGKILL to Homebridge'); + this.homebridge.kill('SIGKILL'); + } catch (e) { } + }, 7000); + + // if homebridge ends before the timeout, clear the timeout + this.homebridge.once('close', () => { + clearTimeout(shutdownTimeout); + }); + } + } + + /** + * Restarts and resolves once homebridge is stopped. + */ + public async restartAndWaitForClose(): Promise { + if (!this.homebridge || !this.homebridge.connected) { + return true; + } else { + return new Promise((resolve) => { + this.homebridge.once('close', () => { + resolve(true); + }); + this.restartHomebridge(); + }); + } + } + + /** + * Send a SIGKILL to the homebridge process + */ + public async killHomebridge() { + if (this.homebridge) { + this.logger.log('Sending SIGKILL to Homebridge'); + this.homebridge.kill('SIGKILL'); + } + } + + /** + * Restart a Homebridge child bridge + */ + public async restartChildBridge(username: string) { + await this.sendMessage('restartChildBridge', username); + } + + /** + * Request a list of child bridges from the Homebridge process + */ + public async getChildBridgeMetadata() { + return await this.requestResponse('childBridgeMetadataRequest', 'childBridgeMetadataResponse'); + } + +} diff --git a/src/main.ts b/src/main.ts index 89ec71755..fae2a1b13 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,7 +18,7 @@ import { getStartupConfig } from './core/config/config.startup'; process.env.UIX_BASE_PATH = path.resolve(__dirname, '../'); -async function bootstrap() { +async function bootstrap(): Promise { const startupConfig = await getStartupConfig(); const server = fastify({ @@ -123,5 +123,8 @@ async function bootstrap() { logger.warn(`Homebridge Config UI X v${configService.package.version} is listening on ${startupConfig.host} port ${configService.ui.port}`); await app.listen(configService.ui.port, startupConfig.host); + + return app; } -bootstrap(); + +export const app = bootstrap(); diff --git a/src/modules/backup/backup.module.ts b/src/modules/backup/backup.module.ts index 6b2f7c515..6101ac3bf 100644 --- a/src/modules/backup/backup.module.ts +++ b/src/modules/backup/backup.module.ts @@ -4,6 +4,7 @@ import { PassportModule } from '@nestjs/passport'; import { ConfigModule } from '../../core/config/config.module'; import { LoggerModule } from '../../core/logger/logger.module'; import { SchedulerModule } from '../../core/scheduler/scheduler.module'; +import { HomebridgeIpcModule } from '../../core/homebridge-ipc/homebridge-ipc.module'; import { BackupService } from './backup.service'; import { BackupGateway } from './backup.gateway'; import { BackupController } from './backup.controller'; @@ -16,6 +17,7 @@ import { PluginsModule } from '../plugins/plugins.module'; PluginsModule, SchedulerModule, LoggerModule, + HomebridgeIpcModule, ], providers: [ BackupService, diff --git a/src/modules/backup/backup.service.ts b/src/modules/backup/backup.service.ts index 74eea3b9e..8f2c4245d 100644 --- a/src/modules/backup/backup.service.ts +++ b/src/modules/backup/backup.service.ts @@ -13,6 +13,7 @@ import { FastifyReply } from 'fastify'; import { PluginsService } from '../plugins/plugins.service'; import { SchedulerService } from '../../core/scheduler/scheduler.service'; import { ConfigService, HomebridgeConfig } from '../../core/config/config.service'; +import { HomebridgeIpcService } from '../..//core/homebridge-ipc/homebridge-ipc.service'; import { Logger } from '../../core/logger/logger.service'; import { HomebridgePlugin } from '../plugins/types'; @@ -24,6 +25,7 @@ export class BackupService { private readonly configService: ConfigService, private readonly pluginsService: PluginsService, private readonly schedulerService: SchedulerService, + private readonly homebridgeIpcService: HomebridgeIpcService, private readonly logger: Logger, ) { this.scheduleInstanceBackups(); @@ -581,7 +583,15 @@ export class BackupService { setTimeout(() => { // if running in service mode if (this.configService.serviceMode) { - return process.emit('message', 'postBackupRestoreRestart', undefined); + // kill homebridge + this.homebridgeIpcService.killHomebridge(); + + // kill self + setTimeout(() => { + process.kill(process.pid, 'SIGKILL'); + }, 500); + + return; } // if running in docker diff --git a/src/modules/server/server.module.ts b/src/modules/server/server.module.ts index 7e8cbb01d..5e1872ac3 100644 --- a/src/modules/server/server.module.ts +++ b/src/modules/server/server.module.ts @@ -6,6 +6,7 @@ import { ServerController } from './server.controller'; import { LoggerModule } from '../../core/logger/logger.module'; import { ConfigEditorModule } from '../config-editor/config-editor.module'; import { AccessoriesModule } from '../accessories/accessories.module'; +import { HomebridgeIpcModule } from '../../core/homebridge-ipc/homebridge-ipc.module'; @Module({ imports: [ @@ -14,6 +15,7 @@ import { AccessoriesModule } from '../accessories/accessories.module'; LoggerModule, ConfigEditorModule, AccessoriesModule, + HomebridgeIpcModule, ], providers: [ ServerService, diff --git a/src/modules/server/server.service.ts b/src/modules/server/server.service.ts index 991c42823..889d005e8 100644 --- a/src/modules/server/server.service.ts +++ b/src/modules/server/server.service.ts @@ -7,11 +7,12 @@ import * as si from 'systeminformation'; import * as NodeCache from 'node-cache'; import * as child_process from 'child_process'; import * as tcpPortUsed from 'tcp-port-used'; -import { Injectable, NotFoundException, BadRequestException, ServiceUnavailableException } from '@nestjs/common'; +import { Injectable, NotFoundException, BadRequestException, ServiceUnavailableException, InternalServerErrorException } from '@nestjs/common'; import { Categories } from '@oznu/hap-client/dist/hap-types'; import { Logger } from '../../core/logger/logger.service'; import { ConfigService, HomebridgeConfig } from '../../core/config/config.service'; +import { HomebridgeIpcService } from '../../core/homebridge-ipc/homebridge-ipc.service'; import { ConfigEditorService } from '../config-editor/config-editor.service'; import { AccessoriesService } from '../accessories/accessories.service'; import { HomebridgeMdnsSettingDto } from './server.dto'; @@ -29,6 +30,7 @@ export class ServerService { private readonly configService: ConfigService, private readonly configEditorService: ConfigEditorService, private readonly accessoriesService: AccessoriesService, + private readonly homebridgeIpcService: HomebridgeIpcService, private readonly logger: Logger, ) { } @@ -40,8 +42,9 @@ export class ServerService { if (this.configService.serviceMode && !(await this.configService.uiRestartRequired() || await this.nodeVersionChanged())) { this.logger.log('UI / Bridge settings have not changed; only restarting Homebridge process'); - // emit restart request to hb-service - process.emit('message', 'restartHomebridge', undefined); + // restart homebridge by killing child process + this.homebridgeIpcService.restartHomebridge(); + // reset the pool of discovered homebridge instances this.accessoriesService.resetInstancePool(); return { ok: true, command: 'SIGTERM', restartingUI: false }; @@ -199,24 +202,24 @@ export class ServerService { const cachedAccessoriesPath = path.resolve(this.configService.storagePath, 'accessories', cacheFile); - this.logger.warn(`Sent request to hb-service to remove cached accessory with UUID: ${uuid}`); + this.logger.warn(`Shutting down Homebridge before removing cached accessory: ${uuid}`); - return await new Promise((resolve, reject) => { - process.emit('message', 'deleteSingleCachedAccessory', async () => { - const cachedAccessories = await fs.readJson(cachedAccessoriesPath) as Array; - const accessoryIndex = cachedAccessories.findIndex(x => x.UUID === uuid); + // wait for homebridge to stop. + await this.homebridgeIpcService.restartAndWaitForClose(); - if (accessoryIndex > -1) { - cachedAccessories.splice(accessoryIndex, 1); - await fs.writeJson(cachedAccessoriesPath, cachedAccessories); - this.logger.warn(`Removed cached accessory with UUID: ${uuid}`); - resolve(true); - } else { - this.logger.error(`Cannot find cached accessory with UUID: ${uuid}`); - reject(new NotFoundException()); - } - }); - }); + const cachedAccessories = await fs.readJson(cachedAccessoriesPath) as Array; + const accessoryIndex = cachedAccessories.findIndex(x => x.UUID === uuid); + + if (accessoryIndex > -1) { + cachedAccessories.splice(accessoryIndex, 1); + await fs.writeJson(cachedAccessoriesPath, cachedAccessories); + this.logger.warn(`Removed cached accessory with UUID: ${uuid}`); + } else { + this.logger.error(`Cannot find cached accessory with UUID: ${uuid}`); + throw new NotFoundException(); + } + + return { ok: true }; } /** @@ -235,24 +238,26 @@ export class ServerService { const cachedAccessoriesPath = path.resolve(this.configService.storagePath, 'accessories', 'cachedAccessories'); - this.logger.warn('Sent request to clear cached accesories to hb-service'); + // wait for homebridge to stop. + await this.homebridgeIpcService.restartAndWaitForClose(); - process.emit('message', 'clearCachedAccessories', async () => { - try { - this.logger.log('Clearing Cached Homebridge Accessories...'); - for (const cachedAccessoriesPath of cachedAccessoryPaths) { - if (await fs.pathExists(cachedAccessoriesPath)) { - await fs.unlink(cachedAccessoriesPath); - this.logger.warn(`Removed ${cachedAccessoriesPath}`); - } + this.logger.warn('Shutting down Homebridge before removing cached accessories'); + + try { + this.logger.log('Clearing Cached Homebridge Accessories...'); + for (const cachedAccessoriesPath of cachedAccessoryPaths) { + if (await fs.pathExists(cachedAccessoriesPath)) { + await fs.unlink(cachedAccessoriesPath); + this.logger.warn(`Removed ${cachedAccessoriesPath}`); } - } catch (e) { - this.logger.error(`Failed to clear Homebridge Accessories Cache at ${cachedAccessoriesPath}`); - console.error(e); } - }); + } catch (e) { + this.logger.error(`Failed to clear Homebridge Accessories Cache at ${cachedAccessoriesPath}`); + console.error(e); + throw new InternalServerErrorException('Failed to clear Homebridge accessory cache - see logs.'); + } - return; + return { ok: true }; } /** @@ -268,16 +273,7 @@ export class ServerService { deviceId = deviceId.match(/.{1,2}/g).join(':'); } - await new Promise((resolve, reject) => { - process.emit('message', 'getHomebridgeChildProcess', (homebridge: child_process.ChildProcess) => { - if (homebridge && homebridge.connected) { - homebridge.send({ id: 'restartChildBridge', data: deviceId.toUpperCase() }); - resolve(true); - } else { - reject(new ServiceUnavailableException('The Homebridge Service Is Unavailable')); - } - }); - }); + await this.homebridgeIpcService.restartChildBridge(deviceId); // reset the pool of discovered homebridge instances this.accessoriesService.resetInstancePool(); diff --git a/src/modules/status/status.controller.ts b/src/modules/status/status.controller.ts index d715ecac0..449e5438b 100644 --- a/src/modules/status/status.controller.ts +++ b/src/modules/status/status.controller.ts @@ -38,6 +38,15 @@ export class StatusController { }; } + @ApiOperation({ + summary: 'Return an array of the active child bridges and their status.', + description: 'This method is only available when running `hb-service`.' + }) + @Get('/homebridge/child-bridges') + async getChildBridges() { + return this.statusService.getChildBridges(); + } + @ApiOperation({ summary: 'Return the current Homebridge version / package information.' }) @Get('/homebridge-version') async getHomebridgeVersion() { diff --git a/src/modules/status/status.gateway.ts b/src/modules/status/status.gateway.ts index 75bbdf0c1..f533b2b77 100644 --- a/src/modules/status/status.gateway.ts +++ b/src/modules/status/status.gateway.ts @@ -121,6 +121,15 @@ export class StatusGateway { } } + @SubscribeMessage('get-homebridge-child-bridge-status') + async getChildBridges(client, payload) { + try { + return await this.statusService.getChildBridges(); + } catch (e) { + return new WsException(e.message); + } + } + @SubscribeMessage('monitor-server-status') async serverStatus(client, payload) { this.statusService.watchStats(client); diff --git a/src/modules/status/status.module.ts b/src/modules/status/status.module.ts index 2aa9bc147..3d7aa606f 100644 --- a/src/modules/status/status.module.ts +++ b/src/modules/status/status.module.ts @@ -5,6 +5,7 @@ import { StatusGateway } from './status.gateway'; import { PluginsModule } from '../plugins/plugins.module'; import { ConfigModule } from '../../core/config/config.module'; import { LoggerModule } from '../../core/logger/logger.module'; +import { HomebridgeIpcModule } from '../../core/homebridge-ipc/homebridge-ipc.module'; import { StatusController } from './status.controller'; @Module({ @@ -14,6 +15,7 @@ import { StatusController } from './status.controller'; LoggerModule, PluginsModule, ConfigModule, + HomebridgeIpcModule, ], providers: [ StatusService, diff --git a/src/modules/status/status.service.ts b/src/modules/status/status.service.ts index dc786806f..f40391252 100644 --- a/src/modules/status/status.service.ts +++ b/src/modules/status/status.service.ts @@ -4,10 +4,11 @@ import * as fs from 'fs-extra'; import * as si from 'systeminformation'; import * as semver from 'semver'; import * as NodeCache from 'node-cache'; -import { Injectable, HttpService } from '@nestjs/common'; +import { Injectable, HttpService, BadRequestException } from '@nestjs/common'; import { Logger } from '../../core/logger/logger.service'; import { ConfigService } from '../../core/config/config.service'; +import { HomebridgeIpcService } from '../../core/homebridge-ipc/homebridge-ipc.service'; import { PluginsService } from '../plugins/plugins.service'; @Injectable() @@ -26,6 +27,7 @@ export class StatusService { private logger: Logger, private configService: ConfigService, private pluginsService: PluginsService, + private homebridgeIpcService: HomebridgeIpcService, ) { // systeminformation cpu data is not supported in FreeBSD Jail Shells @@ -257,6 +259,17 @@ export class StatusService { return this.homebridgeStatus; } + /** + * Return an array of child bridges + */ + public async getChildBridges() { + if (!this.configService.serviceMode) { + throw new BadRequestException('This command is only available in service mode'); + } + + return this.homebridgeIpcService.getChildBridgeMetadata(); + } + /** * Get / Cache the default interface */ diff --git a/test/e2e/server.e2e-spec.ts b/test/e2e/server.e2e-spec.ts index b7fbebbd0..8ae736684 100644 --- a/test/e2e/server.e2e-spec.ts +++ b/test/e2e/server.e2e-spec.ts @@ -210,14 +210,6 @@ describe('ServerController (e2e)', () => { let cachedAccessories = await fs.readJson(path.resolve(accessoriesPath, 'cachedAccessories')); expect(cachedAccessories).toHaveLength(1); - const listener = (event, callback) => { - if (event === 'deleteSingleCachedAccessory') { - callback(); - } - }; - - process.addListener('message', listener); - const res = await app.inject({ method: 'DELETE', path: `/server/cached-accessories/${cachedAccessories[0].UUID}`, @@ -227,7 +219,6 @@ describe('ServerController (e2e)', () => { }); expect(res.statusCode).toEqual(204); - process.removeListener('message', listener); // check the cached accessory was removed cachedAccessories = await fs.readJson(path.resolve(accessoriesPath, 'cachedAccessories')); @@ -242,13 +233,6 @@ describe('ServerController (e2e)', () => { let cachedAccessories = await fs.readJson(path.resolve(accessoriesPath, 'cachedAccessories')); expect(cachedAccessories).toHaveLength(1); - const listener = (event, callback) => { - if (event === 'deleteSingleCachedAccessory') { - callback(); - } - }; - process.addListener('message', listener); - const res = await app.inject({ method: 'DELETE', path: '/server/cached-accessories/xxxxxxxx', @@ -258,7 +242,6 @@ describe('ServerController (e2e)', () => { }); expect(res.statusCode).toEqual(404); - process.removeListener('message', listener); // check the cached accessory was not removed cachedAccessories = await fs.readJson(path.resolve(accessoriesPath, 'cachedAccessories')); diff --git a/ui/src/app/modules/plugins/installed-plugins/installed-plugins.component.ts b/ui/src/app/modules/plugins/installed-plugins/installed-plugins.component.ts index 7664d2d7b..18e7b3c06 100644 --- a/ui/src/app/modules/plugins/installed-plugins/installed-plugins.component.ts +++ b/ui/src/app/modules/plugins/installed-plugins/installed-plugins.component.ts @@ -63,7 +63,7 @@ export class InstalledPluginsComponent implements OnInit, OnDestroy { // check if the homebridge version supports external bridges this.canManageBridgeSettings = this.$auth.env.homebridgeVersion ? - gt(this.$auth.env.homebridgeVersion, '1.3.0-experimental.6', { includePrerelease: true }) : false; + gt(this.$auth.env.homebridgeVersion, '1.3.0-beta.47', { includePrerelease: true }) : false; } loadInstalledPlugins() { diff --git a/ui/src/app/modules/plugins/search-plugins/search-plugins.component.ts b/ui/src/app/modules/plugins/search-plugins/search-plugins.component.ts index d35d4411e..ef17ca7e8 100644 --- a/ui/src/app/modules/plugins/search-plugins/search-plugins.component.ts +++ b/ui/src/app/modules/plugins/search-plugins/search-plugins.component.ts @@ -60,7 +60,7 @@ export class SearchPluginsComponent implements OnInit, OnDestroy { // check if the homebridge version supports external bridges this.canManageBridgeSettings = this.$auth.env.homebridgeVersion ? - gt(this.$auth.env.homebridgeVersion, '1.3.0-experimental.6', { includePrerelease: true }) : false; + gt(this.$auth.env.homebridgeVersion, '1.3.0-beta.47', { includePrerelease: true }) : false; } search() {