homebridge
Version:
HomeKit support for the impatient
244 lines • 10.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChildBridgeFork = void 0;
/**
* This is a standalone script executed as a child process fork
*/
process.title = "homebridge: child bridge";
// registering node-source-map-support for typescript stack traces
require("source-map-support/register");
const hap_nodejs_1 = require("hap-nodejs");
const api_1 = require("./api");
const bridgeService_1 = require("./bridgeService");
const externalPortService_1 = require("./externalPortService");
const logger_1 = require("./logger");
const pluginManager_1 = require("./pluginManager");
const user_1 = require("./user");
class ChildBridgeFork {
bridgeService;
api;
pluginManager;
externalPortService;
type;
plugin;
identifier;
pluginConfig;
bridgeConfig;
bridgeOptions;
homebridgeConfig;
portRequestCallback = new Map();
constructor() {
// tell the parent process we are ready to accept plugin config
this.sendMessage("ready" /* ChildProcessMessageEventType.READY */);
}
sendMessage(type, data) {
if (process.send) {
process.send({
id: type,
data,
});
}
}
async loadPlugin(data) {
// set data
this.type = data.type;
this.identifier = data.identifier;
this.pluginConfig = data.pluginConfig;
this.bridgeConfig = data.bridgeConfig;
this.bridgeOptions = data.bridgeOptions;
this.homebridgeConfig = data.homebridgeConfig;
// remove the _bridge key (some plugins do not like unknown config)
for (const config of this.pluginConfig) {
delete config._bridge;
}
// set bridge settings (inherited from main bridge)
if (this.bridgeOptions.noLogTimestamps) {
logger_1.Logger.setTimestampEnabled(false);
}
if (this.bridgeOptions.debugModeEnabled) {
logger_1.Logger.setDebugEnabled(true);
}
if (this.bridgeOptions.forceColourLogging) {
logger_1.Logger.forceColor();
}
if (this.bridgeOptions.customStoragePath) {
user_1.User.setStoragePath(this.bridgeOptions.customStoragePath);
}
// Initialize HAP-NodeJS with a custom persist directory
hap_nodejs_1.HAPStorage.setCustomStoragePath(user_1.User.persistPath());
// load api
this.api = new api_1.HomebridgeAPI();
this.pluginManager = new pluginManager_1.PluginManager(this.api);
this.externalPortService = new externalPortService_1.ChildBridgeExternalPortService(this);
// load plugin
this.plugin = this.pluginManager.loadPlugin(data.pluginPath);
await this.plugin.load();
await this.pluginManager.initializePlugin(this.plugin, data.identifier);
// change process title to include plugin name
process.title = `homebridge: ${this.plugin.getPluginIdentifier()}`;
this.sendMessage("loaded" /* ChildProcessMessageEventType.LOADED */, {
version: this.plugin.version,
});
}
async startBridge() {
this.bridgeService = new bridgeService_1.BridgeService(this.api, this.pluginManager, this.externalPortService, this.bridgeOptions, this.bridgeConfig, this.homebridgeConfig);
// watch bridge events to check when server is online
this.bridgeService.bridge.on("advertised" /* AccessoryEventTypes.ADVERTISED */, () => {
this.sendPairedStatusEvent();
});
// watch for the paired event to update the server status
this.bridgeService.bridge.on("paired" /* AccessoryEventTypes.PAIRED */, () => {
this.sendPairedStatusEvent();
});
// watch for the unpaired event to update the server status
this.bridgeService.bridge.on("unpaired" /* AccessoryEventTypes.UNPAIRED */, () => {
this.sendPairedStatusEvent();
});
// load the cached accessories
await this.bridgeService.loadCachedPlatformAccessoriesFromDisk();
for (const config of this.pluginConfig) {
if (this.type === "platform" /* PluginType.PLATFORM */) {
const plugin = this.pluginManager.getPluginForPlatform(this.identifier);
const displayName = config.name || plugin.getPluginIdentifier();
const logger = logger_1.Logger.withPrefix(displayName);
const constructor = plugin.getPlatformConstructor(this.identifier);
const platform = new constructor(logger, config, this.api);
if (api_1.HomebridgeAPI.isDynamicPlatformPlugin(platform)) {
plugin.assignDynamicPlatform(this.identifier, platform);
}
else if (api_1.HomebridgeAPI.isStaticPlatformPlugin(platform)) { // Plugin 1.0, load accessories
await this.bridgeService.loadPlatformAccessories(plugin, platform, this.identifier, logger);
}
else {
// otherwise it's a IndependentPlatformPlugin which doesn't expose any methods at all.
// We just call the constructor and let it be enabled.
}
}
else if (this.type === "accessory" /* PluginType.ACCESSORY */) {
const plugin = this.pluginManager.getPluginForAccessory(this.identifier);
const displayName = config.name;
if (!displayName) {
logger_1.Logger.internal.warn("Could not load accessory %s as it is missing the required 'name' property!", this.identifier);
return;
}
const logger = logger_1.Logger.withPrefix(displayName);
const constructor = plugin.getAccessoryConstructor(this.identifier);
const accessoryInstance = new constructor(logger, config, this.api);
//pass accessoryIdentifier for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
const accessory = this.bridgeService.createHAPAccessory(plugin, accessoryInstance, displayName, this.identifier, config.uuid_base);
if (accessory) {
this.bridgeService.bridge.addBridgedAccessory(accessory);
}
else {
logger("Accessory %s returned empty set of services. Won't adding it to the bridge!", this.identifier);
}
}
}
// restore the cached accessories
this.bridgeService.restoreCachedPlatformAccessories();
this.bridgeService.publishBridge();
this.api.signalFinished();
// tell the parent we are online
this.sendMessage("online" /* ChildProcessMessageEventType.ONLINE */);
}
/**
* Request the next available external port from the parent process
* @param username
*/
async requestExternalPort(username) {
return new Promise((resolve) => {
const requestTimeout = setTimeout(() => {
logger_1.Logger.internal.warn("Parent process did not respond to port allocation request within 5 seconds - assigning random port.");
resolve(undefined);
}, 5000);
// setup callback
const callback = (port) => {
clearTimeout(requestTimeout);
resolve(port);
this.portRequestCallback.delete(username);
};
this.portRequestCallback.set(username, callback);
// send port request
this.sendMessage("portRequest" /* ChildProcessMessageEventType.PORT_REQUEST */, { username });
});
}
/**
* Handles the port allocation response message from the parent process
* @param data
*/
handleExternalResponse(data) {
const callback = this.portRequestCallback.get(data.username);
if (callback) {
callback(data.port);
}
}
/**
* Sends the current pairing status of the child bridge to the parent process
*/
sendPairedStatusEvent() {
this.sendMessage("status" /* ChildProcessMessageEventType.STATUS_UPDATE */, {
paired: this.bridgeService?.bridge?._accessoryInfo?.paired() ?? null,
setupUri: this.bridgeService?.bridge?.setupURI() ?? null,
});
}
shutdown() {
this.bridgeService.teardown();
}
}
exports.ChildBridgeFork = ChildBridgeFork;
/**
* Start Self
*/
const childPluginFork = new ChildBridgeFork();
/**
* Handle incoming IPC messages from the parent Homebridge process
*/
process.on("message", (message) => {
if (typeof message !== "object" || !message.id) {
return;
}
switch (message.id) {
case "load" /* ChildProcessMessageEventType.LOAD */: {
childPluginFork.loadPlugin(message.data);
break;
}
case "start" /* ChildProcessMessageEventType.START */: {
childPluginFork.startBridge();
break;
}
case "portAllocated" /* ChildProcessMessageEventType.PORT_ALLOCATED */: {
childPluginFork.handleExternalResponse(message.data);
break;
}
}
});
/**
* Handle the sigterm shutdown signals
*/
let shuttingDown = false;
const signalHandler = (signal, signalNum) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
logger_1.Logger.internal.info("Got %s, shutting down child bridge process...", signal);
try {
childPluginFork.shutdown();
}
catch (e) {
// do nothing
}
setTimeout(() => process.exit(128 + signalNum), 5000);
};
process.on("SIGINT", signalHandler.bind(undefined, "SIGINT", 2));
process.on("SIGTERM", signalHandler.bind(undefined, "SIGTERM", 15));
/**
* Ensure orphaned processes are cleaned up
*/
setInterval(() => {
if (!process.connected) {
logger_1.Logger.internal.info("Parent process not connected, terminating process...");
process.exit(1);
}
}, 5000);
//# sourceMappingURL=childBridgeFork.js.map