homebridge 2.0.0-alpha.4 → 2.0.0-alpha.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/homebridge.js +22 -0
- package/config-sample.json +12 -1
- package/dist/api.d.ts +226 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +166 -0
- package/dist/api.js.map +1 -0
- package/dist/bridgeService.d.ts +125 -0
- package/dist/bridgeService.d.ts.map +1 -0
- package/dist/bridgeService.js +396 -0
- package/dist/bridgeService.js.map +1 -0
- package/dist/childBridgeFork.d.ts +38 -0
- package/dist/childBridgeFork.d.ts.map +1 -0
- package/dist/childBridgeFork.js +241 -2
- package/dist/childBridgeFork.js.map +1 -7
- package/dist/childBridgeService.d.ts +203 -0
- package/dist/childBridgeService.d.ts.map +1 -0
- package/dist/childBridgeService.js +451 -0
- package/dist/childBridgeService.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +90 -2
- package/dist/cli.js.map +1 -7
- package/dist/externalPortService.d.ts +33 -0
- package/dist/externalPortService.d.ts.map +1 -0
- package/dist/externalPortService.js +59 -0
- package/dist/externalPortService.js.map +1 -0
- package/dist/index.d.ts +85 -1099
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -7
- package/dist/ipcService.d.ts +30 -0
- package/dist/ipcService.d.ts.map +1 -0
- package/dist/ipcService.js +49 -0
- package/dist/ipcService.js.map +1 -0
- package/dist/logger.d.ts +78 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +138 -0
- package/dist/logger.js.map +1 -0
- package/dist/matterConfigValidator.d.ts +34 -0
- package/dist/matterConfigValidator.d.ts.map +1 -0
- package/dist/matterConfigValidator.js +249 -0
- package/dist/matterConfigValidator.js.map +1 -0
- package/dist/matterService.d.ts +168 -0
- package/dist/matterService.d.ts.map +1 -0
- package/dist/matterService.js +677 -0
- package/dist/matterService.js.map +1 -0
- package/dist/matterTypes.d.ts +20 -0
- package/dist/matterTypes.d.ts.map +1 -0
- package/dist/matterTypes.js +278 -0
- package/dist/matterTypes.js.map +1 -0
- package/dist/platformAccessory.d.ts +56 -0
- package/dist/platformAccessory.d.ts.map +1 -0
- package/dist/platformAccessory.js +105 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/plugin.d.ts +30 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +182 -0
- package/dist/plugin.js.map +1 -0
- package/dist/pluginManager.d.ts +77 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +375 -0
- package/dist/pluginManager.js.map +1 -0
- package/dist/server.d.ts +61 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +467 -0
- package/dist/server.js.map +1 -0
- package/dist/storageService.d.ts +13 -0
- package/dist/storageService.d.ts.map +1 -0
- package/dist/storageService.js +41 -0
- package/dist/storageService.js.map +1 -0
- package/dist/user.d.ts +13 -0
- package/dist/user.d.ts.map +1 -0
- package/dist/user.js +29 -0
- package/dist/user.js.map +1 -0
- package/dist/util/mac.d.ts +5 -0
- package/dist/util/mac.d.ts.map +1 -0
- package/dist/util/mac.js +14 -0
- package/dist/util/mac.js.map +1 -0
- package/dist/util/matter-cli.d.ts +3 -0
- package/dist/util/matter-cli.d.ts.map +1 -0
- package/dist/util/matter-cli.js +211 -0
- package/dist/util/matter-cli.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/package.json +31 -34
- package/bin/homebridge +0 -19
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { satisfies } from 'semver';
|
|
6
|
+
import { Logger } from './logger.js';
|
|
7
|
+
import { PluginManager } from './pluginManager.js';
|
|
8
|
+
import getVersion from './version.js';
|
|
9
|
+
const log = Logger.internal;
|
|
10
|
+
/**
|
|
11
|
+
* Represents a loaded Homebridge plugin.
|
|
12
|
+
*/
|
|
13
|
+
export class Plugin {
|
|
14
|
+
pluginName;
|
|
15
|
+
scope; // npm package scope
|
|
16
|
+
pluginPath; // like "/usr/local/lib/node_modules/homebridge-lockitron"
|
|
17
|
+
disabled = false; // mark the plugin as disabled
|
|
18
|
+
// ------------------ package.json content ------------------
|
|
19
|
+
version;
|
|
20
|
+
main;
|
|
21
|
+
loadContext;
|
|
22
|
+
// ----------------------------------------------------------
|
|
23
|
+
pluginInitializer; // default exported function from the plugin that initializes it
|
|
24
|
+
registeredAccessories = new Map();
|
|
25
|
+
registeredPlatforms = new Map();
|
|
26
|
+
activeDynamicPlatforms = new Map();
|
|
27
|
+
constructor(name, path, packageJSON, scope) {
|
|
28
|
+
this.pluginName = name;
|
|
29
|
+
this.scope = scope;
|
|
30
|
+
this.pluginPath = path;
|
|
31
|
+
this.version = packageJSON.version || '0.0.0';
|
|
32
|
+
this.main = '';
|
|
33
|
+
// figure out the main module
|
|
34
|
+
// exports is available - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_package_entry_points
|
|
35
|
+
if (packageJSON.exports) {
|
|
36
|
+
// main entrypoint - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_main_entry_point_export
|
|
37
|
+
if (typeof packageJSON.exports === 'string') {
|
|
38
|
+
this.main = packageJSON.exports;
|
|
39
|
+
}
|
|
40
|
+
else { // subpath export - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_subpath_exports
|
|
41
|
+
// conditional exports - https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_conditional_exports
|
|
42
|
+
const exports = packageJSON.exports.import || packageJSON.exports.require || packageJSON.exports.node || packageJSON.exports.default || packageJSON.exports['.'];
|
|
43
|
+
// check if conditional export is nested
|
|
44
|
+
if (typeof exports !== 'string') {
|
|
45
|
+
if (exports.import) {
|
|
46
|
+
this.main = exports.import;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this.main = exports.require || exports.node || exports.default;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.main = exports;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// exports search was not successful, fallback to package.main, using index.js as fallback
|
|
58
|
+
if (!this.main) {
|
|
59
|
+
this.main = packageJSON.main || './index.js';
|
|
60
|
+
}
|
|
61
|
+
// very temporary fix for first wave of plugins
|
|
62
|
+
if (packageJSON.peerDependencies && (!packageJSON.engines || !packageJSON.engines.homebridge)) {
|
|
63
|
+
packageJSON.engines = packageJSON.engines || {};
|
|
64
|
+
packageJSON.engines.homebridge = packageJSON.peerDependencies.homebridge;
|
|
65
|
+
}
|
|
66
|
+
this.loadContext = {
|
|
67
|
+
engines: packageJSON.engines,
|
|
68
|
+
dependencies: packageJSON.dependencies,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
getPluginIdentifier() {
|
|
72
|
+
return (this.scope ? `${this.scope}/` : '') + this.pluginName;
|
|
73
|
+
}
|
|
74
|
+
getPluginPath() {
|
|
75
|
+
return this.pluginPath;
|
|
76
|
+
}
|
|
77
|
+
registerAccessory(name, constructor) {
|
|
78
|
+
if (this.registeredAccessories.has(name)) {
|
|
79
|
+
throw new Error(`Plugin '${this.getPluginIdentifier()}' tried to register an accessory '${name}' which has already been registered!`);
|
|
80
|
+
}
|
|
81
|
+
if (!this.disabled) {
|
|
82
|
+
log.info('Registering accessory \'%s\'', `${this.getPluginIdentifier()}.${name}`);
|
|
83
|
+
}
|
|
84
|
+
this.registeredAccessories.set(name, constructor);
|
|
85
|
+
}
|
|
86
|
+
registerPlatform(name, constructor) {
|
|
87
|
+
if (this.registeredPlatforms.has(name)) {
|
|
88
|
+
throw new Error(`Plugin '${this.getPluginIdentifier()}' tried to register a platform '${name}' which has already been registered!`);
|
|
89
|
+
}
|
|
90
|
+
if (!this.disabled) {
|
|
91
|
+
log.info('Registering platform \'%s\'', `${this.getPluginIdentifier()}.${name}`);
|
|
92
|
+
}
|
|
93
|
+
this.registeredPlatforms.set(name, constructor);
|
|
94
|
+
}
|
|
95
|
+
getAccessoryConstructor(accessoryIdentifier) {
|
|
96
|
+
const name = PluginManager.getAccessoryName(accessoryIdentifier);
|
|
97
|
+
const constructor = this.registeredAccessories.get(name);
|
|
98
|
+
if (!constructor) {
|
|
99
|
+
throw new Error(`The requested accessory '${name}' was not registered by the plugin '${this.getPluginIdentifier()}'.`);
|
|
100
|
+
}
|
|
101
|
+
return constructor;
|
|
102
|
+
}
|
|
103
|
+
getPlatformConstructor(platformIdentifier) {
|
|
104
|
+
const name = PluginManager.getPlatformName(platformIdentifier);
|
|
105
|
+
const constructor = this.registeredPlatforms.get(name);
|
|
106
|
+
if (!constructor) {
|
|
107
|
+
throw new Error(`The requested platform '${name}' was not registered by the plugin '${this.getPluginIdentifier()}'.`);
|
|
108
|
+
}
|
|
109
|
+
// If it's a dynamic platform plugin, ensure it's not enabled multiple times.
|
|
110
|
+
if (this.activeDynamicPlatforms.has(name)) {
|
|
111
|
+
throw new Error(`The dynamic platform ${name} from the plugin ${this.getPluginIdentifier()} is configured `
|
|
112
|
+
+ 'times in your config.json.');
|
|
113
|
+
}
|
|
114
|
+
return constructor;
|
|
115
|
+
}
|
|
116
|
+
assignDynamicPlatform(platformIdentifier, platformPlugin) {
|
|
117
|
+
const name = PluginManager.getPlatformName(platformIdentifier);
|
|
118
|
+
let platforms = this.activeDynamicPlatforms.get(name);
|
|
119
|
+
if (!platforms) {
|
|
120
|
+
platforms = [];
|
|
121
|
+
this.activeDynamicPlatforms.set(name, platforms);
|
|
122
|
+
}
|
|
123
|
+
// the last platform published should be at the first position for easy access
|
|
124
|
+
// we just try to mimic pre 1.0.0 behavior
|
|
125
|
+
platforms.unshift(platformPlugin);
|
|
126
|
+
}
|
|
127
|
+
getActiveDynamicPlatform(platformName) {
|
|
128
|
+
const platforms = this.activeDynamicPlatforms.get(platformName);
|
|
129
|
+
// we always use the last registered
|
|
130
|
+
return platforms && platforms[0];
|
|
131
|
+
}
|
|
132
|
+
async load() {
|
|
133
|
+
const context = this.loadContext;
|
|
134
|
+
assert(context, 'Reached illegal state. Plugin state is undefined!');
|
|
135
|
+
this.loadContext = undefined; // free up memory
|
|
136
|
+
// pluck out the Homebridge version requirement
|
|
137
|
+
if (!context.engines || !context.engines.homebridge) {
|
|
138
|
+
throw new Error(`Plugin ${this.pluginPath} does not contain the 'homebridge' package in 'engines'.`);
|
|
139
|
+
}
|
|
140
|
+
const versionRequired = context.engines.homebridge;
|
|
141
|
+
const nodeVersionRequired = context.engines.node;
|
|
142
|
+
// make sure the version is satisfied by the currently running version of Homebridge
|
|
143
|
+
if (!satisfies(getVersion(), versionRequired, { includePrerelease: true })) {
|
|
144
|
+
// TODO - change this back to an error
|
|
145
|
+
log.warn(`The plugin "${this.pluginName}" requires a Homebridge version of ${versionRequired} which does \
|
|
146
|
+
not satisfy the current Homebridge version of v${getVersion()}. You may need to update this plugin (or Homebridge) to a newer version. \
|
|
147
|
+
You may face unexpected issues or stability problems running this plugin.`);
|
|
148
|
+
}
|
|
149
|
+
// make sure the version is satisfied by the currently running version of Node
|
|
150
|
+
if (nodeVersionRequired && !satisfies(process.version, nodeVersionRequired)) {
|
|
151
|
+
log.warn(`The plugin "${this.pluginName}" requires a Node.js version of ${nodeVersionRequired} which does \
|
|
152
|
+
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`);
|
|
153
|
+
}
|
|
154
|
+
const dependencies = context.dependencies || {};
|
|
155
|
+
if (dependencies.homebridge || dependencies['hap-nodejs']) {
|
|
156
|
+
log.error(`The plugin "${this.pluginName}" defines 'homebridge' and/or 'hap-nodejs' in their 'dependencies' section, \
|
|
157
|
+
meaning they carry an additional copy of homebridge and hap-nodejs. This not only wastes disk space, but also can cause \
|
|
158
|
+
major incompatibility issues and thus is considered bad practice. Please inform the developer to update their plugin!`);
|
|
159
|
+
}
|
|
160
|
+
const mainPath = join(this.pluginPath, this.main);
|
|
161
|
+
// try to import it and grab the exported initialization hook
|
|
162
|
+
// pathToFileURL(specifier).href to turn a path into a "file url"
|
|
163
|
+
// see https://github.com/nodejs/node/issues/31710
|
|
164
|
+
const pluginModules = (await import(pathToFileURL(mainPath).href)).default;
|
|
165
|
+
if (typeof pluginModules === 'function') {
|
|
166
|
+
this.pluginInitializer = pluginModules;
|
|
167
|
+
}
|
|
168
|
+
else if (pluginModules && typeof pluginModules.default === 'function') {
|
|
169
|
+
this.pluginInitializer = pluginModules.default;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
throw new Error(`Plugin ${this.pluginPath} does not export a initializer function from main.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
initialize(api) {
|
|
176
|
+
if (!this.pluginInitializer) {
|
|
177
|
+
throw new Error('Tried to initialize a plugin which hasn\'t been loaded yet!');
|
|
178
|
+
}
|
|
179
|
+
return this.pluginInitializer(api);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AccessoryIdentifier, AccessoryName, HomebridgeAPI, PlatformIdentifier, PlatformName, PluginIdentifier, PluginName } from './api.js';
|
|
2
|
+
import { Plugin } from './plugin.js';
|
|
3
|
+
export interface PackageJSON {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
keywords?: string[];
|
|
7
|
+
exports?: string | Record<string, string | Record<string, string>>;
|
|
8
|
+
main?: string;
|
|
9
|
+
/**
|
|
10
|
+
* When set as module, it marks .js file to be treated as ESM.
|
|
11
|
+
* See https://nodejs.org/dist/latest-v14.x/docs/api/esm.html#esm_enabling
|
|
12
|
+
*/
|
|
13
|
+
type?: 'module' | 'commonjs';
|
|
14
|
+
engines?: Record<string, string>;
|
|
15
|
+
dependencies?: Record<string, string>;
|
|
16
|
+
devDependencies?: Record<string, string>;
|
|
17
|
+
peerDependencies?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export interface PluginManagerOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Additional path to search for plugins in. Specified relative to the current working directory.
|
|
22
|
+
*/
|
|
23
|
+
customPluginPath?: string;
|
|
24
|
+
/**
|
|
25
|
+
* If set, only load plugins from the customPluginPath, if set, otherwise only from the primary global node_modules.
|
|
26
|
+
*/
|
|
27
|
+
strictPluginResolution?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* When defined, only plugins specified here will be initialized.
|
|
30
|
+
*/
|
|
31
|
+
activePlugins?: PluginIdentifier[];
|
|
32
|
+
/**
|
|
33
|
+
* Plugins that are marked as disabled and whose corresponding config blocks should be ignored
|
|
34
|
+
*/
|
|
35
|
+
disabledPlugins?: PluginIdentifier[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Utility which exposes methods to search for installed Homebridge plugins
|
|
39
|
+
*/
|
|
40
|
+
export declare class PluginManager {
|
|
41
|
+
private static readonly PLUGIN_IDENTIFIER_PATTERN;
|
|
42
|
+
private readonly api;
|
|
43
|
+
private readonly searchPaths;
|
|
44
|
+
private readonly strictPluginResolution;
|
|
45
|
+
private readonly activePlugins?;
|
|
46
|
+
private readonly disabledPlugins?;
|
|
47
|
+
private readonly plugins;
|
|
48
|
+
private readonly pluginIdentifierTranslation;
|
|
49
|
+
private readonly accessoryToPluginMap;
|
|
50
|
+
private readonly platformToPluginMap;
|
|
51
|
+
private currentInitializingPlugin?;
|
|
52
|
+
constructor(api: HomebridgeAPI, options?: PluginManagerOptions);
|
|
53
|
+
static isQualifiedPluginIdentifier(identifier: string): boolean;
|
|
54
|
+
static extractPluginName(name: string): PluginName;
|
|
55
|
+
static extractPluginScope(name: string): string;
|
|
56
|
+
static getAccessoryName(identifier: AccessoryIdentifier): AccessoryName;
|
|
57
|
+
static getPlatformName(identifier: PlatformIdentifier): PlatformIdentifier;
|
|
58
|
+
static getPluginIdentifier(identifier: AccessoryIdentifier | PlatformIdentifier): PluginIdentifier;
|
|
59
|
+
initializeInstalledPlugins(): Promise<void>;
|
|
60
|
+
initializePlugin(plugin: Plugin, identifier: string): Promise<void>;
|
|
61
|
+
private handleRegisterAccessory;
|
|
62
|
+
private handleRegisterPlatform;
|
|
63
|
+
getPluginForAccessory(accessoryIdentifier: AccessoryIdentifier | AccessoryName): Plugin;
|
|
64
|
+
getPluginForPlatform(platformIdentifier: PlatformIdentifier | PlatformName): Plugin;
|
|
65
|
+
hasPluginRegistered(pluginIdentifier: PluginIdentifier): boolean;
|
|
66
|
+
getPlugin(pluginIdentifier: PluginIdentifier): Plugin | undefined;
|
|
67
|
+
getPluginByActiveDynamicPlatform(platformName: PlatformName): Plugin | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Gets all plugins installed on the local system
|
|
70
|
+
*/
|
|
71
|
+
private loadInstalledPlugins;
|
|
72
|
+
loadPlugin(absolutePath: string): Plugin;
|
|
73
|
+
private static loadPackageJSON;
|
|
74
|
+
private loadDefaultPaths;
|
|
75
|
+
private addNpmPrefixToSearchPaths;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=pluginManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pluginManager.d.ts","sourceRoot":"","sources":["../src/pluginManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { delimiter, join, resolve } from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { Logger } from './logger.js';
|
|
7
|
+
import { Plugin } from './plugin.js';
|
|
8
|
+
const log = Logger.internal;
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const paths = require.resolve.paths('');
|
|
11
|
+
/**
|
|
12
|
+
* Utility which exposes methods to search for installed Homebridge plugins
|
|
13
|
+
*/
|
|
14
|
+
export class PluginManager {
|
|
15
|
+
// name must be prefixed with 'homebridge-' or '@scope/homebridge-'
|
|
16
|
+
static PLUGIN_IDENTIFIER_PATTERN = /^((@[\w-]+(\.[\w-]+)*)\/)?(homebridge-[\w-]+)$/;
|
|
17
|
+
api;
|
|
18
|
+
searchPaths = new Set(); // unique set of search paths we will use to discover installed plugins
|
|
19
|
+
strictPluginResolution = false;
|
|
20
|
+
activePlugins;
|
|
21
|
+
disabledPlugins;
|
|
22
|
+
plugins = new Map();
|
|
23
|
+
// we have some plugins which simply pass a wrong or misspelled plugin name to the api calls, this translation tries to mitigate this
|
|
24
|
+
pluginIdentifierTranslation = new Map();
|
|
25
|
+
accessoryToPluginMap = new Map();
|
|
26
|
+
platformToPluginMap = new Map();
|
|
27
|
+
currentInitializingPlugin; // used to match registering plugins, see handleRegisterAccessory and handleRegisterPlatform
|
|
28
|
+
constructor(api, options) {
|
|
29
|
+
this.api = api;
|
|
30
|
+
if (options) {
|
|
31
|
+
if (options.customPluginPath) {
|
|
32
|
+
this.searchPaths.add(resolve(process.cwd(), options.customPluginPath));
|
|
33
|
+
}
|
|
34
|
+
this.strictPluginResolution = options.strictPluginResolution || false;
|
|
35
|
+
this.activePlugins = options.activePlugins;
|
|
36
|
+
this.disabledPlugins = Array.isArray(options.disabledPlugins) ? options.disabledPlugins : undefined;
|
|
37
|
+
}
|
|
38
|
+
this.api.on("registerAccessory" /* InternalAPIEvent.REGISTER_ACCESSORY */, this.handleRegisterAccessory.bind(this));
|
|
39
|
+
this.api.on("registerPlatform" /* InternalAPIEvent.REGISTER_PLATFORM */, this.handleRegisterPlatform.bind(this));
|
|
40
|
+
}
|
|
41
|
+
static isQualifiedPluginIdentifier(identifier) {
|
|
42
|
+
return PluginManager.PLUGIN_IDENTIFIER_PATTERN.test(identifier);
|
|
43
|
+
}
|
|
44
|
+
static extractPluginName(name) {
|
|
45
|
+
return name.match(PluginManager.PLUGIN_IDENTIFIER_PATTERN)[4];
|
|
46
|
+
}
|
|
47
|
+
static extractPluginScope(name) {
|
|
48
|
+
return name.match(PluginManager.PLUGIN_IDENTIFIER_PATTERN)[2];
|
|
49
|
+
}
|
|
50
|
+
static getAccessoryName(identifier) {
|
|
51
|
+
if (!identifier.includes('.')) {
|
|
52
|
+
return identifier;
|
|
53
|
+
}
|
|
54
|
+
return identifier.split('.')[1];
|
|
55
|
+
}
|
|
56
|
+
static getPlatformName(identifier) {
|
|
57
|
+
if (!identifier.includes('.')) {
|
|
58
|
+
return identifier;
|
|
59
|
+
}
|
|
60
|
+
return identifier.split('.')[1];
|
|
61
|
+
}
|
|
62
|
+
static getPluginIdentifier(identifier) {
|
|
63
|
+
return identifier.split('.')[0];
|
|
64
|
+
}
|
|
65
|
+
async initializeInstalledPlugins() {
|
|
66
|
+
log.info('---');
|
|
67
|
+
this.loadInstalledPlugins();
|
|
68
|
+
for (const [identifier, plugin] of this.plugins) {
|
|
69
|
+
try {
|
|
70
|
+
await plugin.load();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
log.error('====================');
|
|
74
|
+
log.error(`ERROR LOADING PLUGIN ${identifier}:`);
|
|
75
|
+
log.error(error.stack);
|
|
76
|
+
log.error('====================');
|
|
77
|
+
this.plugins.delete(identifier);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (this.disabledPlugins && this.disabledPlugins.includes(plugin.getPluginIdentifier())) {
|
|
81
|
+
plugin.disabled = true;
|
|
82
|
+
}
|
|
83
|
+
if (plugin.disabled) {
|
|
84
|
+
log.warn(`Disabled plugin: ${identifier}@${plugin.version}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
log.info(`Loaded plugin: ${identifier}@${plugin.version}`);
|
|
88
|
+
}
|
|
89
|
+
await this.initializePlugin(plugin, identifier);
|
|
90
|
+
log.info('---');
|
|
91
|
+
}
|
|
92
|
+
this.currentInitializingPlugin = undefined;
|
|
93
|
+
}
|
|
94
|
+
async initializePlugin(plugin, identifier) {
|
|
95
|
+
try {
|
|
96
|
+
this.currentInitializingPlugin = plugin;
|
|
97
|
+
await plugin.initialize(this.api); // call the plugin's initializer and pass it our API instance
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
log.error('====================');
|
|
101
|
+
log.error(`ERROR INITIALIZING PLUGIN ${identifier}:`);
|
|
102
|
+
log.error(error.stack);
|
|
103
|
+
log.error('====================');
|
|
104
|
+
this.plugins.delete(identifier);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
handleRegisterAccessory(name, constructor, pluginIdentifier) {
|
|
108
|
+
if (!this.currentInitializingPlugin) {
|
|
109
|
+
throw new Error(`Unexpected accessory registration. Plugin ${pluginIdentifier ? `'${pluginIdentifier}' ` : ''}tried to register outside the initializer function!`);
|
|
110
|
+
}
|
|
111
|
+
if (pluginIdentifier && pluginIdentifier !== this.currentInitializingPlugin.getPluginIdentifier()) {
|
|
112
|
+
log.info(`Plugin '${this.currentInitializingPlugin.getPluginIdentifier()}' tried to register with an incorrect plugin identifier: '${pluginIdentifier}'. Please report this to the developer!`);
|
|
113
|
+
this.pluginIdentifierTranslation.set(pluginIdentifier, this.currentInitializingPlugin.getPluginIdentifier());
|
|
114
|
+
}
|
|
115
|
+
this.currentInitializingPlugin.registerAccessory(name, constructor);
|
|
116
|
+
let plugins = this.accessoryToPluginMap.get(name);
|
|
117
|
+
if (!plugins) {
|
|
118
|
+
plugins = [];
|
|
119
|
+
this.accessoryToPluginMap.set(name, plugins);
|
|
120
|
+
}
|
|
121
|
+
plugins.push(this.currentInitializingPlugin);
|
|
122
|
+
}
|
|
123
|
+
handleRegisterPlatform(name, constructor, pluginIdentifier) {
|
|
124
|
+
if (!this.currentInitializingPlugin) {
|
|
125
|
+
throw new Error(`Unexpected platform registration. Plugin ${pluginIdentifier ? `'${pluginIdentifier}' ` : ''}tried to register outside the initializer function!`);
|
|
126
|
+
}
|
|
127
|
+
if (pluginIdentifier && pluginIdentifier !== this.currentInitializingPlugin.getPluginIdentifier()) {
|
|
128
|
+
log.debug(`Plugin '${this.currentInitializingPlugin.getPluginIdentifier()}' tried to register with an incorrect plugin identifier: '${pluginIdentifier}'. Please report this to the developer!`);
|
|
129
|
+
this.pluginIdentifierTranslation.set(pluginIdentifier, this.currentInitializingPlugin.getPluginIdentifier());
|
|
130
|
+
}
|
|
131
|
+
this.currentInitializingPlugin.registerPlatform(name, constructor);
|
|
132
|
+
let plugins = this.platformToPluginMap.get(name);
|
|
133
|
+
if (!plugins) {
|
|
134
|
+
plugins = [];
|
|
135
|
+
this.platformToPluginMap.set(name, plugins);
|
|
136
|
+
}
|
|
137
|
+
plugins.push(this.currentInitializingPlugin);
|
|
138
|
+
}
|
|
139
|
+
getPluginForAccessory(accessoryIdentifier) {
|
|
140
|
+
let plugin;
|
|
141
|
+
if (!accessoryIdentifier.includes('.')) { // see if it matches exactly one accessory
|
|
142
|
+
let found = this.accessoryToPluginMap.get(accessoryIdentifier);
|
|
143
|
+
if (!found) {
|
|
144
|
+
throw new Error(`No plugin was found for the accessory "${accessoryIdentifier}" in your config.json. Please make sure the corresponding plugin is installed correctly.`);
|
|
145
|
+
}
|
|
146
|
+
if (found.length > 1) {
|
|
147
|
+
const options = found.map(plugin => `${plugin.getPluginIdentifier()}.${accessoryIdentifier}`).join(', ');
|
|
148
|
+
// check if only one of the multiple platforms is not disabled
|
|
149
|
+
found = found.filter(plugin => !plugin.disabled);
|
|
150
|
+
if (found.length !== 1) {
|
|
151
|
+
throw new Error(`The requested accessory '${accessoryIdentifier}' has been registered multiple times. Please be more specific by writing one of: ${options}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
plugin = found[0];
|
|
155
|
+
accessoryIdentifier = `${plugin.getPluginIdentifier()}.${accessoryIdentifier}`;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const pluginIdentifier = PluginManager.getPluginIdentifier(accessoryIdentifier);
|
|
159
|
+
if (!this.hasPluginRegistered(pluginIdentifier)) {
|
|
160
|
+
throw new Error(`The requested plugin '${pluginIdentifier}' was not registered.`);
|
|
161
|
+
}
|
|
162
|
+
plugin = this.getPlugin(pluginIdentifier);
|
|
163
|
+
}
|
|
164
|
+
return plugin;
|
|
165
|
+
}
|
|
166
|
+
getPluginForPlatform(platformIdentifier) {
|
|
167
|
+
let plugin;
|
|
168
|
+
if (!platformIdentifier.includes('.')) { // see if it matches exactly one platform
|
|
169
|
+
let found = this.platformToPluginMap.get(platformIdentifier);
|
|
170
|
+
if (!found) {
|
|
171
|
+
throw new Error(`No plugin was found for the platform "${platformIdentifier}" in your config.json. Please make sure the corresponding plugin is installed correctly.`);
|
|
172
|
+
}
|
|
173
|
+
if (found.length > 1) {
|
|
174
|
+
const options = found.map(plugin => `${plugin.getPluginIdentifier()}.${platformIdentifier}`).join(', ');
|
|
175
|
+
// check if only one of the multiple platforms is not disabled
|
|
176
|
+
found = found.filter(plugin => !plugin.disabled);
|
|
177
|
+
if (found.length !== 1) {
|
|
178
|
+
throw new Error(`The requested platform '${platformIdentifier}' has been registered multiple times. Please be more specific by writing one of: ${options}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
plugin = found[0];
|
|
182
|
+
platformIdentifier = `${plugin.getPluginIdentifier()}.${platformIdentifier}`;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const pluginIdentifier = PluginManager.getPluginIdentifier(platformIdentifier);
|
|
186
|
+
if (!this.hasPluginRegistered(pluginIdentifier)) {
|
|
187
|
+
throw new Error(`The requested plugin '${pluginIdentifier}' was not registered.`);
|
|
188
|
+
}
|
|
189
|
+
plugin = this.getPlugin(pluginIdentifier);
|
|
190
|
+
}
|
|
191
|
+
return plugin;
|
|
192
|
+
}
|
|
193
|
+
hasPluginRegistered(pluginIdentifier) {
|
|
194
|
+
return this.plugins.has(pluginIdentifier) || this.pluginIdentifierTranslation.has(pluginIdentifier);
|
|
195
|
+
}
|
|
196
|
+
getPlugin(pluginIdentifier) {
|
|
197
|
+
const plugin = this.plugins.get(pluginIdentifier);
|
|
198
|
+
if (plugin) {
|
|
199
|
+
return plugin;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const translation = this.pluginIdentifierTranslation.get(pluginIdentifier);
|
|
203
|
+
if (translation) {
|
|
204
|
+
return this.plugins.get(translation);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
getPluginByActiveDynamicPlatform(platformName) {
|
|
210
|
+
const found = (this.platformToPluginMap.get(platformName) || [])
|
|
211
|
+
.filter(plugin => !!plugin.getActiveDynamicPlatform(platformName));
|
|
212
|
+
if (found.length === 0) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
else if (found.length > 1) {
|
|
216
|
+
const plugins = found.map(plugin => plugin.getPluginIdentifier()).join(', ');
|
|
217
|
+
throw new Error(`'${platformName}' is an ambiguous platform name. It was registered by multiple plugins: ${plugins}`);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
return found[0];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Gets all plugins installed on the local system
|
|
225
|
+
*/
|
|
226
|
+
loadInstalledPlugins() {
|
|
227
|
+
this.loadDefaultPaths();
|
|
228
|
+
this.searchPaths.forEach((searchPath) => {
|
|
229
|
+
if (!existsSync(searchPath)) { // just because this path is in require.main.paths doesn't mean it necessarily exists!
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (existsSync(join(searchPath, 'package.json'))) { // does this path point inside a single plugin and not a directory containing plugins?
|
|
233
|
+
try {
|
|
234
|
+
this.loadPlugin(searchPath);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
log.warn(error.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else { // read through each directory in this node_modules folder
|
|
241
|
+
const relativePluginPaths = readdirSync(searchPath) // search for directories only
|
|
242
|
+
.filter((relativePath) => {
|
|
243
|
+
try {
|
|
244
|
+
return statSync(resolve(searchPath, relativePath)).isDirectory();
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
log.debug(`Ignoring path ${resolve(searchPath, relativePath)} - ${error.message}`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
// expand out @scoped plugins
|
|
252
|
+
relativePluginPaths.slice()
|
|
253
|
+
.filter(path => path.charAt(0) === '@') // is it a scope directory?
|
|
254
|
+
.forEach((scopeDirectory) => {
|
|
255
|
+
// remove scopeDirectory from the path list
|
|
256
|
+
const index = relativePluginPaths.indexOf(scopeDirectory);
|
|
257
|
+
relativePluginPaths.splice(index, 1);
|
|
258
|
+
const absolutePath = join(searchPath, scopeDirectory);
|
|
259
|
+
readdirSync(absolutePath)
|
|
260
|
+
.filter(name => PluginManager.isQualifiedPluginIdentifier(name))
|
|
261
|
+
.filter((name) => {
|
|
262
|
+
try {
|
|
263
|
+
return statSync(resolve(absolutePath, name)).isDirectory();
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
log.debug(`Ignoring path ${resolve(absolutePath, name)} - ${error.message}`);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
.forEach(name => relativePluginPaths.push(`${scopeDirectory}/${name}`));
|
|
271
|
+
});
|
|
272
|
+
relativePluginPaths
|
|
273
|
+
.filter((pluginIdentifier) => {
|
|
274
|
+
return PluginManager.isQualifiedPluginIdentifier(pluginIdentifier) // needs to be a valid homebridge plugin name
|
|
275
|
+
&& (!this.activePlugins || this.activePlugins.includes(pluginIdentifier)); // check if activePlugins is restricted and if so is the plugin is contained
|
|
276
|
+
})
|
|
277
|
+
.forEach((pluginIdentifier) => {
|
|
278
|
+
try {
|
|
279
|
+
const absolutePath = resolve(searchPath, pluginIdentifier);
|
|
280
|
+
this.loadPlugin(absolutePath);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
log.warn(error.message);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
if (this.plugins.size === 0) {
|
|
289
|
+
log.warn('No plugins found.');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
loadPlugin(absolutePath) {
|
|
293
|
+
const packageJson = PluginManager.loadPackageJSON(absolutePath);
|
|
294
|
+
const identifier = packageJson.name;
|
|
295
|
+
const name = PluginManager.extractPluginName(identifier);
|
|
296
|
+
const scope = PluginManager.extractPluginScope(identifier); // possibly undefined
|
|
297
|
+
const alreadyInstalled = this.plugins.get(identifier); // check if there is already a plugin with the same Identifier
|
|
298
|
+
if (alreadyInstalled) {
|
|
299
|
+
throw new Error(`Warning: skipping plugin found at '${absolutePath}' since we already loaded the same plugin from '${alreadyInstalled.getPluginPath()}'.`);
|
|
300
|
+
}
|
|
301
|
+
const plugin = new Plugin(name, absolutePath, packageJson, scope);
|
|
302
|
+
this.plugins.set(identifier, plugin);
|
|
303
|
+
return plugin;
|
|
304
|
+
}
|
|
305
|
+
static loadPackageJSON(pluginPath) {
|
|
306
|
+
const packageJsonPath = join(pluginPath, 'package.json');
|
|
307
|
+
let packageJson;
|
|
308
|
+
if (!existsSync(packageJsonPath)) {
|
|
309
|
+
throw new Error(`Plugin ${pluginPath} does not contain a package.json.`);
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
packageJson = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf8' })); // attempt to parse package.json
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
throw new Error(`Plugin ${pluginPath} contains an invalid package.json. Error: ${error}`);
|
|
316
|
+
}
|
|
317
|
+
if (!packageJson.name || !PluginManager.isQualifiedPluginIdentifier(packageJson.name)) {
|
|
318
|
+
throw new Error(`Plugin ${pluginPath} does not have a package name that begins with 'homebridge-' or '@scope/homebridge-.`);
|
|
319
|
+
}
|
|
320
|
+
// verify that it's tagged with the correct keyword
|
|
321
|
+
if (!packageJson.keywords || !packageJson.keywords.includes('homebridge-plugin')) {
|
|
322
|
+
throw new Error(`Plugin ${pluginPath} package.json does not contain the keyword 'homebridge-plugin'.`);
|
|
323
|
+
}
|
|
324
|
+
return packageJson;
|
|
325
|
+
}
|
|
326
|
+
loadDefaultPaths() {
|
|
327
|
+
if (this.strictPluginResolution) {
|
|
328
|
+
// if strict plugin resolution is enabled:
|
|
329
|
+
// * only use custom plugin path, if set;
|
|
330
|
+
// * otherwise add the current npm global prefix (e.g. /usr/local/lib/node_modules)
|
|
331
|
+
if (this.searchPaths.size === 0) {
|
|
332
|
+
this.addNpmPrefixToSearchPaths();
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (paths) {
|
|
337
|
+
// add the paths used by require()
|
|
338
|
+
paths.forEach(path => this.searchPaths.add(path));
|
|
339
|
+
}
|
|
340
|
+
// THIS SECTION FROM: https://github.com/yeoman/environment/blob/master/lib/resolver.js
|
|
341
|
+
// Adding global npm directories
|
|
342
|
+
// We tried using npm to get the global modules path, but it hasn't work out
|
|
343
|
+
// because of bugs in the parsable implementation of `ls` command and mostly
|
|
344
|
+
// performance issues. So, we go with our best bet for now.
|
|
345
|
+
if (process.env.NODE_PATH) {
|
|
346
|
+
process.env
|
|
347
|
+
.NODE_PATH
|
|
348
|
+
.split(delimiter)
|
|
349
|
+
.filter(path => !!path) // trim out empty values
|
|
350
|
+
.forEach(path => this.searchPaths.add(path));
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
// Default paths for non-windows systems
|
|
354
|
+
if (process.platform !== 'win32') {
|
|
355
|
+
this.searchPaths.add('/usr/local/lib/node_modules');
|
|
356
|
+
this.searchPaths.add('/usr/lib/node_modules');
|
|
357
|
+
}
|
|
358
|
+
this.addNpmPrefixToSearchPaths();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
addNpmPrefixToSearchPaths() {
|
|
362
|
+
if (process.platform === 'win32') {
|
|
363
|
+
this.searchPaths.add(join(process.env.APPDATA, 'npm/node_modules'));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
this.searchPaths.add(execSync('/bin/echo -n "$(npm -g prefix)/lib/node_modules"', {
|
|
367
|
+
env: Object.assign({
|
|
368
|
+
npm_config_loglevel: 'silent',
|
|
369
|
+
npm_update_notifier: 'false',
|
|
370
|
+
}, process.env),
|
|
371
|
+
}).toString('utf8'));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
//# sourceMappingURL=pluginManager.js.map
|