@homebridge-plugins/homebridge-plugin-update-check
Version:
A Homebridge plugin for checking for updates to Homebridge and plugins
352 lines (351 loc) • 14.8 kB
JavaScript
/* eslint-disable antfu/if-newline */
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import { hostname } from 'node:os';
import path from 'node:path';
import process from 'node:process';
// eslint-disable-next-line ts/consistent-type-imports
import { UiApi } from './ui-api.js';
let hap;
let Accessory;
const PLUGIN_NAME = '@homebridge-plugins/homebridge-plugin-update-check';
const PLATFORM_NAME = 'PluginUpdate';
class PluginUpdatePlatform {
log;
api;
config;
uiApi;
useNcu;
isDocker;
sensorInfo;
checkHB;
checkHBUI;
checkPlugins;
checkDocker;
service;
timer;
constructor(log, config, api) {
hap = api.hap;
Accessory = api.platformAccessory;
this.log = log;
this.config = config;
this.api = api;
this.uiApi = new UiApi(this.api.user.storagePath(), this.log);
this.useNcu = this.config.forceNcu || !this.uiApi.isConfigured();
this.isDocker = fs.existsSync('/homebridge/package.json');
this.sensorInfo = this.getSensorInfo(this.config.sensorType);
this.checkHB = this.config.checkHomebridgeUpdates ?? false;
this.checkHBUI = this.config.checkHomebridgeUIUpdates ?? false;
this.checkPlugins = this.config.checkPluginUpdates ?? false;
this.checkDocker = this.config.checkDockerUpdates ?? false;
api.on("didFinishLaunching" /* APIEvent.DID_FINISH_LAUNCHING */, this.addUpdateAccessory.bind(this));
}
async runNcu(args, filter = '/^(@.*\\/)?homebridge(-.*)?$/') {
args = [
path.resolve(__dirname, '../node_modules/npm-check-updates/build/src/bin/cli.js'),
'--jsonUpgraded',
'--filter',
filter,
].concat(args);
const output = await new Promise((resolve, reject) => {
try {
const ncu = spawn(process.argv0, args, {
env: this.isDocker ? { ...process.env, HOME: '/homebridge' } : undefined,
});
let stdout = '';
ncu.stdout.on('data', (chunk) => {
stdout += chunk.toString();
});
let stderr = '';
ncu.stderr.on('data', (chunk) => {
stderr += chunk.toString();
});
ncu.on('close', () => {
if (stderr) {
reject(stderr);
}
else {
resolve(stdout);
}
});
}
catch (ex) {
reject(ex);
}
});
return JSON.parse(output);
}
async checkNcu() {
const homebridgeFilter = 'homebridge';
const homebridgeUIFilter = 'homebridge-config-ui-x';
const pluginsFilter = '(?=(@.*\\/)?homebridge-)(?:(?!homebridge-config-ui-x).)*';
const filters = [];
if (this.checkHB)
filters.push(homebridgeFilter);
if (this.checkHBUI)
filters.push(homebridgeUIFilter);
if (this.checkPlugins)
filters.push(pluginsFilter);
// eslint-disable-next-line prefer-template
const filter = '/^' + filters.join('|') + ')$/';
let results = await this.runNcu(['--global'], filter);
if (this.isDocker) {
const dockerPackageResults = await this.runNcu(['--packageFile', '/homebridge/package.json'], filter);
results = { ...results, ...dockerPackageResults };
const docker = await this.uiApi.getDocker();
if (docker.updateAvailable) {
results.push(docker);
}
}
const updates = Object.keys(results).length;
this.log.debug(`npm-check-updates reports ${updates} available update(s): ${JSON.stringify(results)}`);
return updates;
}
async checkUi() {
const updatesAvailable = [];
if (this.checkHB) {
const homebridge = await this.uiApi.getHomebridge();
if (homebridge.updateAvailable) {
updatesAvailable.push(homebridge);
this.log.info(`Homebridge update available: ${homebridge.latestVersion}`);
}
}
if (this.checkHBUI || this.checkPlugins) {
const plugins = await this.uiApi.getPlugins();
if (this.checkHBUI) {
const filteredPlugins = plugins.filter(plugin => plugin.name === 'homebridge-config-ui-x');
filteredPlugins.forEach((plugin) => {
if (plugin.updateAvailable) {
updatesAvailable.push(plugin);
this.log.info(`Homebridge UI update available: ${plugin.latestVersion}`);
}
});
}
if (this.checkPlugins) {
const filteredPlugins = plugins.filter(plugin => plugin.name !== 'homebridge-config-ui-x');
filteredPlugins.forEach((plugin) => {
if (plugin.updateAvailable) {
updatesAvailable.push(plugin);
this.log.info(`Homebridge plugin update available: ${plugin.name} ${plugin.latestVersion}`);
}
});
}
}
if (this.isDocker && this.checkDocker) {
const docker = await this.uiApi.getDocker();
if (docker.updateAvailable) {
updatesAvailable.push(docker);
this.log.info(`Docker update available: ${docker.latestVersion}`);
}
}
this.log.debug(`Found ${updatesAvailable.length} available update(s)`);
return updatesAvailable.length;
}
doCheck() {
if (this.timer) {
clearTimeout(this.timer);
}
const check = this.useNcu ? this.checkNcu() : this.checkUi();
check
.then((updates) => {
this.service?.setCharacteristic(this.sensorInfo.characteristicType, updates ? this.sensorInfo.trippedValue : this.sensorInfo.untrippedValue);
})
.catch((ex) => {
this.log.error(ex);
})
.finally(() => {
this.timer = setTimeout(this.doCheck.bind(this), 60 * 60 * 1000);
});
}
checkService(accessory, serviceType) {
const service = accessory.getService(serviceType);
if (this.sensorInfo.serviceType === serviceType) {
if (service) {
this.service = service;
}
else {
this.service = accessory.addService(serviceType);
}
return true;
}
else {
if (service) {
accessory.removeService(service);
}
return false;
}
}
configureAccessory(accessory) {
accessory.on("identify" /* PlatformAccessoryEvent.IDENTIFY */, () => {
this.log(`${accessory.displayName} identify requested!`);
});
const accInfo = accessory.getService(hap.Service.AccessoryInformation);
if (accInfo) {
accInfo
.setCharacteristic(hap.Characteristic.Manufacturer, 'Homebridge')
.setCharacteristic(hap.Characteristic.Model, 'Plugin Update Check')
.setCharacteristic(hap.Characteristic.SerialNumber, hostname());
}
this.checkService(accessory, hap.Service.MotionSensor);
this.checkService(accessory, hap.Service.ContactSensor);
this.checkService(accessory, hap.Service.OccupancySensor);
this.checkService(accessory, hap.Service.SmokeSensor);
this.checkService(accessory, hap.Service.LeakSensor);
this.checkService(accessory, hap.Service.LightSensor);
this.checkService(accessory, hap.Service.HumiditySensor);
this.checkService(accessory, hap.Service.CarbonMonoxideSensor);
this.checkService(accessory, hap.Service.CarbonDioxideSensor);
this.checkService(accessory, hap.Service.AirQualitySensor);
/* const motionService = accessory.getService(hap.Service.MotionSensor);
const contactService = accessory.getService(hap.Service.ContactSensor);
const occupancyService = accessory.getService(hap.Service.OccupancySensor);
const smokeService = accessory.getService(hap.Service.SmokeSensor);
const leakService = accessory.getService(hap.Service.LeakSensor);
const lightService = accessory.getService(hap.Service.LightSensor);
const humidityService = accessory.getService(hap.Service.HumiditySensor);
const monoxideService = accessory.getService(hap.Service.CarbonMonoxideSensor);
const dioxideService = accessory.getService(hap.Service.CarbonDioxideSensor);
const airService = accessory.getService(hap.Service.AirQualitySensor);
if (this.sensorInfo.serviceType == hap.Service.MotionSensor) {
this.service = motionService;
} else if (motionService) {
accessory.removeService(motionService);
}
if (this.sensorInfo.serviceType == hap.Service.ContactSensor) {
this.service = contactService;
} else if (contactService) {
accessory.removeService(contactService);
}
if (this.sensorInfo.serviceType == hap.Service.OccupancySensor) {
this.service = occupancyService;
} else if (occupancyService) {
accessory.removeService(occupancyService);
}
if (this.sensorInfo.serviceType == hap.Service.SmokeSensor) {
this.service = smokeService;
} else if (smokeService) {
accessory.removeService(smokeService);
}
if (this.sensorInfo.serviceType == hap.Service.LeakSensor) {
this.service = leakService;
} else if (leakService) {
accessory.removeService(leakService);
}
if (this.sensorInfo.serviceType == hap.Service.LightSensor) {
this.service = lightService;
} else if (lightService) {
accessory.removeService(lightService);
}
if (this.sensorInfo.serviceType == hap.Service.HumiditySensor) {
this.service = humidityService;
} else if (humidityService) {
accessory.removeService(humidityService);
}
if (this.sensorInfo.serviceType == hap.Service.CarbonMonoxideSensor) {
this.service = monoxideService;
} else if (monoxideService) {
accessory.removeService(monoxideService);
}
if (this.sensorInfo.serviceType == hap.Service.CarbonDioxideSensor) {
this.service = dioxideService;
} else if (dioxideService) {
accessory.removeService(dioxideService);
}
if (this.sensorInfo.serviceType == hap.Service.AirQualitySensor) {
this.service = airService;
} else if (airService) {
accessory.removeService(airService);
} */
this.service?.setCharacteristic(this.sensorInfo.characteristicType, this.sensorInfo.untrippedValue);
}
addUpdateAccessory() {
if (!this.service) {
const uuid = hap.uuid.generate(PLATFORM_NAME);
const newAccessory = new Accessory('Plugin Update Check', uuid);
newAccessory.addService(this.sensorInfo.serviceType);
this.configureAccessory(newAccessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [newAccessory]);
}
this.timer = setTimeout(this.doCheck.bind(this), 10 * 1000);
}
getSensorInfo(sensorType) {
switch (sensorType?.toLowerCase()) {
case 'contact':
return {
serviceType: hap.Service.ContactSensor,
characteristicType: hap.Characteristic.ContactSensorState,
untrippedValue: 0,
trippedValue: 1,
};
case 'occupancy':
return {
serviceType: hap.Service.OccupancySensor,
characteristicType: hap.Characteristic.OccupancyDetected,
untrippedValue: 0,
trippedValue: 1,
};
case 'smoke':
return {
serviceType: hap.Service.SmokeSensor,
characteristicType: hap.Characteristic.SmokeDetected,
untrippedValue: 0,
trippedValue: 1,
};
case 'leak':
return {
serviceType: hap.Service.LeakSensor,
characteristicType: hap.Characteristic.LeakDetected,
untrippedValue: 0,
trippedValue: 1,
};
case 'light':
return {
serviceType: hap.Service.LightSensor,
characteristicType: hap.Characteristic.CurrentAmbientLightLevel,
untrippedValue: 0.0001,
trippedValue: 100000,
};
case 'humidity':
return {
serviceType: hap.Service.HumiditySensor,
characteristicType: hap.Characteristic.CurrentRelativeHumidity,
untrippedValue: 0,
trippedValue: 100,
};
case 'monoxide':
return {
serviceType: hap.Service.CarbonMonoxideSensor,
characteristicType: hap.Characteristic.CarbonMonoxideDetected,
untrippedValue: 0,
trippedValue: 1,
};
case 'dioxide':
return {
serviceType: hap.Service.CarbonDioxideSensor,
characteristicType: hap.Characteristic.CarbonDioxideDetected,
untrippedValue: 0,
trippedValue: 1,
};
case 'air':
return {
serviceType: hap.Service.AirQualitySensor,
characteristicType: hap.Characteristic.AirQuality,
untrippedValue: 1,
trippedValue: 5,
};
case 'motion':
default:
return {
serviceType: hap.Service.MotionSensor,
characteristicType: hap.Characteristic.MotionDetected,
untrippedValue: false,
trippedValue: true,
};
}
}
}
// Register our platform with homebridge.
export default (api) => {
api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, PluginUpdatePlatform);
};
//# sourceMappingURL=index.js.map