UNPKG

homebridge

Version:
194 lines 10.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Plugin = void 0; const assert_1 = __importDefault(require("assert")); const path_1 = __importDefault(require("path")); const url_1 = require("url"); const semver_1 = require("semver"); const logger_1 = require("./logger"); const pluginManager_1 = require("./pluginManager"); const version_1 = __importDefault(require("./version")); const log = logger_1.Logger.internal; // Workaround for https://github.com/microsoft/TypeScript/issues/43329 const _importDynamic = new Function("modulePath", "return import(modulePath)"); /** * Represents a loaded Homebridge plugin. */ class Plugin { pluginName; scope; // npm package scope pluginPath; // like "/usr/local/lib/node_modules/homebridge-lockitron" isESM; disabled = false; // mark the plugin as disabled // ------------------ package.json content ------------------ version; main; loadContext; // ---------------------------------------------------------- pluginInitializer; // default exported function from the plugin that initializes it registeredAccessories = new Map(); registeredPlatforms = new Map(); activeDynamicPlatforms = new Map(); constructor(name, path, packageJSON, scope) { this.pluginName = name; this.scope = scope; this.pluginPath = path; this.version = packageJSON.version || "0.0.0"; this.main = ""; // figure out the main module // exports is available - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_package_entry_points if (packageJSON.exports) { // main entrypoint - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_main_entry_point_export if (typeof packageJSON.exports === "string") { this.main = packageJSON.exports; } else { // subpath export - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_subpath_exports // conditional exports - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_conditional_exports const exports = packageJSON.exports.import || packageJSON.exports.require || packageJSON.exports.node || packageJSON.exports.default || packageJSON.exports["."]; // check if conditional export is nested if (typeof exports !== "string") { if (exports.import) { this.main = exports.import; } else { this.main = exports.require || exports.node || exports.default; } } else { this.main = exports; } } } // exports search was not successful, fallback to package.main, using index.js as fallback if (!this.main) { this.main = packageJSON.main || "./index.js"; } // check if it is an ESM module this.isESM = this.main.endsWith(".mjs") || (this.main.endsWith(".js") && packageJSON.type === "module"); // very temporary fix for first wave of plugins if (packageJSON.peerDependencies && (!packageJSON.engines || !packageJSON.engines.homebridge)) { packageJSON.engines = packageJSON.engines || {}; packageJSON.engines.homebridge = packageJSON.peerDependencies.homebridge; } this.loadContext = { engines: packageJSON.engines, dependencies: packageJSON.dependencies, }; } getPluginIdentifier() { return (this.scope ? this.scope + "/" : "") + this.pluginName; } getPluginPath() { return this.pluginPath; } registerAccessory(name, constructor) { if (this.registeredAccessories.has(name)) { throw new Error(`Plugin '${this.getPluginIdentifier()}' tried to register an accessory '${name}' which has already been registered!`); } if (!this.disabled) { log.info("Registering accessory '%s'", this.getPluginIdentifier() + "." + name); } this.registeredAccessories.set(name, constructor); } registerPlatform(name, constructor) { if (this.registeredPlatforms.has(name)) { throw new Error(`Plugin '${this.getPluginIdentifier()}' tried to register a platform '${name}' which has already been registered!`); } if (!this.disabled) { log.info("Registering platform '%s'", this.getPluginIdentifier() + "." + name); } this.registeredPlatforms.set(name, constructor); } getAccessoryConstructor(accessoryIdentifier) { const name = pluginManager_1.PluginManager.getAccessoryName(accessoryIdentifier); const constructor = this.registeredAccessories.get(name); if (!constructor) { throw new Error(`The requested accessory '${name}' was not registered by the plugin '${this.getPluginIdentifier()}'.`); } return constructor; } getPlatformConstructor(platformIdentifier) { const name = pluginManager_1.PluginManager.getPlatformName(platformIdentifier); const constructor = this.registeredPlatforms.get(name); if (!constructor) { throw new Error(`The requested platform '${name}' was not registered by the plugin '${this.getPluginIdentifier()}'.`); } if (this.activeDynamicPlatforms.has(name)) { // if it's a dynamic platform check that it is not enabled multiple times log.error("The dynamic platform " + name + " from the plugin " + this.getPluginIdentifier() + " seems to be configured " + "multiple times in your config.json. This behaviour was deprecated in homebridge v1.0.0 and will be removed in v2.0.0!"); } return constructor; } assignDynamicPlatform(platformIdentifier, platformPlugin) { const name = pluginManager_1.PluginManager.getPlatformName(platformIdentifier); let platforms = this.activeDynamicPlatforms.get(name); if (!platforms) { platforms = []; this.activeDynamicPlatforms.set(name, platforms); } // the last platform published should be at the first position for easy access // we just try to mimic pre 1.0.0 behavior platforms.unshift(platformPlugin); } getActiveDynamicPlatform(platformName) { const platforms = this.activeDynamicPlatforms.get(platformName); // we always use the last registered return platforms && platforms[0]; } async load() { const context = this.loadContext; (0, assert_1.default)(context, "Reached illegal state. Plugin state is undefined!"); this.loadContext = undefined; // free up memory // pluck out the HomeBridge version requirement if (!context.engines || !context.engines.homebridge) { throw new Error(`Plugin ${this.pluginPath} does not contain the 'homebridge' package in 'engines'.`); } const versionRequired = context.engines.homebridge; const nodeVersionRequired = context.engines.node; // make sure the version is satisfied by the currently running version of HomeBridge if (!(0, semver_1.satisfies)((0, version_1.default)(), versionRequired, { includePrerelease: true })) { // TODO - change this back to an error log.error(`The plugin "${this.pluginName}" requires a Homebridge version of ${versionRequired} which does \ not satisfy the current Homebridge version of ${(0, version_1.default)()}. You may need to update this plugin (or Homebridge) to a newer version. \ You may face unexpected issues or stability problems running this plugin.`); } // make sure the version is satisfied by the currently running version of Node if (nodeVersionRequired && !(0, semver_1.satisfies)(process.version, nodeVersionRequired)) { log.warn(`The plugin "${this.pluginName}" requires Node.js version of ${nodeVersionRequired} which does \ not satisfy the current Node.js version of ${process.version}. You may need to upgrade your installation of Node.js - see https://homebridge.io/w/JTKEF`); } const dependencies = context.dependencies || {}; if (dependencies.homebridge || dependencies["hap-nodejs"]) { log.error(`The plugin "${this.pluginName}" defines 'homebridge' and/or 'hap-nodejs' in their 'dependencies' section, \ meaning they carry an additional copy of homebridge and hap-nodejs. This not only wastes disk space, but also can cause \ major incompatibility issues and thus is considered bad practice. Please inform the developer to update their plugin!`); } const mainPath = path_1.default.join(this.pluginPath, this.main); // try to require() it and grab the exported initialization hook // eslint-disable-next-line @typescript-eslint/no-var-requires // pathToFileURL(specifier).href to turn a path into a "file url" // see https://github.com/nodejs/node/issues/31710 // eslint-disable-next-line @typescript-eslint/no-require-imports const pluginModules = this.isESM ? await _importDynamic((0, url_1.pathToFileURL)(mainPath).href) : require(mainPath); if (typeof pluginModules === "function") { this.pluginInitializer = pluginModules; } else if (pluginModules && typeof pluginModules.default === "function") { this.pluginInitializer = pluginModules.default; } else { throw new Error(`Plugin ${this.pluginPath} does not export a initializer function from main.`); } } initialize(api) { if (!this.pluginInitializer) { throw new Error("Tried to initialize a plugin which hasn't been loaded yet!"); } return this.pluginInitializer(api); } } exports.Plugin = Plugin; //# sourceMappingURL=plugin.js.map