matterbridge 2.2.6-dev.5 → 2.2.7

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.
Files changed (152) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/dist/cli.d.ts +29 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +37 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -2
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +114 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +94 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/frontend.d.ts +221 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +329 -19
  21. package/dist/frontend.js.map +1 -0
  22. package/dist/index.d.ts +35 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +28 -1
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/behaviors.d.ts +2 -0
  31. package/dist/matter/behaviors.d.ts.map +1 -0
  32. package/dist/matter/behaviors.js +2 -0
  33. package/dist/matter/behaviors.js.map +1 -0
  34. package/dist/matter/clusters.d.ts +2 -0
  35. package/dist/matter/clusters.d.ts.map +1 -0
  36. package/dist/matter/clusters.js +2 -0
  37. package/dist/matter/clusters.js.map +1 -0
  38. package/dist/matter/devices.d.ts +2 -0
  39. package/dist/matter/devices.d.ts.map +1 -0
  40. package/dist/matter/devices.js +2 -0
  41. package/dist/matter/devices.js.map +1 -0
  42. package/dist/matter/endpoints.d.ts +2 -0
  43. package/dist/matter/endpoints.d.ts.map +1 -0
  44. package/dist/matter/endpoints.js +2 -0
  45. package/dist/matter/endpoints.js.map +1 -0
  46. package/dist/matter/export.d.ts +5 -0
  47. package/dist/matter/export.d.ts.map +1 -0
  48. package/dist/matter/export.js +2 -0
  49. package/dist/matter/export.js.map +1 -0
  50. package/dist/matter/types.d.ts +3 -0
  51. package/dist/matter/types.d.ts.map +1 -0
  52. package/dist/matter/types.js +2 -0
  53. package/dist/matter/types.js.map +1 -0
  54. package/dist/matterbridge.d.ts +425 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +753 -47
  57. package/dist/matterbridge.js.map +1 -0
  58. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  60. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  61. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  62. package/dist/matterbridgeBehaviors.d.ts +1056 -0
  63. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  64. package/dist/matterbridgeBehaviors.js +32 -1
  65. package/dist/matterbridgeBehaviors.js.map +1 -0
  66. package/dist/matterbridgeDeviceTypes.d.ts +177 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  68. package/dist/matterbridgeDeviceTypes.js +112 -11
  69. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  70. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  72. package/dist/matterbridgeDynamicPlatform.js +33 -0
  73. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  74. package/dist/matterbridgeEndpoint.d.ts +852 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +737 -10
  77. package/dist/matterbridgeEndpoint.js.map +1 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpointHelpers.js +118 -9
  81. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  82. package/dist/matterbridgePlatform.d.ts +285 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +221 -12
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +183 -0
  87. package/dist/matterbridgeTypes.d.ts.map +1 -0
  88. package/dist/matterbridgeTypes.js +24 -0
  89. package/dist/matterbridgeTypes.js.map +1 -0
  90. package/dist/pluginManager.d.ts +271 -0
  91. package/dist/pluginManager.d.ts.map +1 -0
  92. package/dist/pluginManager.js +349 -7
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/shelly.d.ts +92 -0
  95. package/dist/shelly.d.ts.map +1 -0
  96. package/dist/shelly.js +146 -6
  97. package/dist/shelly.js.map +1 -0
  98. package/dist/storage/export.d.ts +2 -0
  99. package/dist/storage/export.d.ts.map +1 -0
  100. package/dist/storage/export.js +1 -0
  101. package/dist/storage/export.js.map +1 -0
  102. package/dist/update.d.ts +32 -0
  103. package/dist/update.d.ts.map +1 -0
  104. package/dist/update.js +45 -0
  105. package/dist/update.js.map +1 -0
  106. package/dist/utils/colorUtils.d.ts +61 -0
  107. package/dist/utils/colorUtils.d.ts.map +1 -0
  108. package/dist/utils/colorUtils.js +205 -2
  109. package/dist/utils/colorUtils.js.map +1 -0
  110. package/dist/utils/copyDirectory.d.ts +32 -0
  111. package/dist/utils/copyDirectory.d.ts.map +1 -0
  112. package/dist/utils/copyDirectory.js +37 -1
  113. package/dist/utils/copyDirectory.js.map +1 -0
  114. package/dist/utils/createZip.d.ts +38 -0
  115. package/dist/utils/createZip.d.ts.map +1 -0
  116. package/dist/utils/createZip.js +42 -2
  117. package/dist/utils/createZip.js.map +1 -0
  118. package/dist/utils/deepCopy.d.ts +31 -0
  119. package/dist/utils/deepCopy.d.ts.map +1 -0
  120. package/dist/utils/deepCopy.js +40 -0
  121. package/dist/utils/deepCopy.js.map +1 -0
  122. package/dist/utils/deepEqual.d.ts +53 -0
  123. package/dist/utils/deepEqual.d.ts.map +1 -0
  124. package/dist/utils/deepEqual.js +65 -1
  125. package/dist/utils/deepEqual.js.map +1 -0
  126. package/dist/utils/export.d.ts +10 -0
  127. package/dist/utils/export.d.ts.map +1 -0
  128. package/dist/utils/export.js +1 -0
  129. package/dist/utils/export.js.map +1 -0
  130. package/dist/utils/isvalid.d.ts +87 -0
  131. package/dist/utils/isvalid.d.ts.map +1 -0
  132. package/dist/utils/isvalid.js +86 -0
  133. package/dist/utils/isvalid.js.map +1 -0
  134. package/dist/utils/network.d.ts +69 -0
  135. package/dist/utils/network.d.ts.map +1 -0
  136. package/dist/utils/network.js +77 -8
  137. package/dist/utils/network.js.map +1 -0
  138. package/dist/utils/parameter.d.ts +44 -0
  139. package/dist/utils/parameter.d.ts.map +1 -0
  140. package/dist/utils/parameter.js +41 -0
  141. package/dist/utils/parameter.js.map +1 -0
  142. package/dist/utils/wait.d.ts +43 -0
  143. package/dist/utils/wait.d.ts.map +1 -0
  144. package/dist/utils/wait.js +48 -5
  145. package/dist/utils/wait.js.map +1 -0
  146. package/frontend/build/asset-manifest.json +3 -3
  147. package/frontend/build/index.html +1 -1
  148. package/frontend/build/static/js/{main.f00179ca.js → main.1fa50342.js} +3 -3
  149. package/frontend/build/static/js/{main.f00179ca.js.map → main.1fa50342.js.map} +1 -1
  150. package/npm-shrinkwrap.json +2 -2
  151. package/package.json +2 -1
  152. /package/frontend/build/static/js/{main.f00179ca.js.LICENSE.txt → main.1fa50342.js.LICENSE.txt} +0 -0
@@ -1,3 +1,26 @@
1
+ /**
2
+ * This file contains the Plugins class.
3
+ *
4
+ * @file plugins.ts
5
+ * @author Luca Liguori
6
+ * @date 2024-07-14
7
+ * @version 1.1.1
8
+ *
9
+ * Copyright 2024, 2025, 2026 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // AnsiLogger module
1
24
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr } from './logger/export.js';
2
25
  import { plg, typ } from './matterbridgeTypes.js';
3
26
  export class PluginManager {
@@ -7,8 +30,9 @@ export class PluginManager {
7
30
  log;
8
31
  constructor(matterbridge) {
9
32
  this.matterbridge = matterbridge;
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
34
  this.nodeContext = matterbridge.nodeContext;
11
- this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4, logLevel: matterbridge.log.logLevel });
35
+ this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: matterbridge.log.logLevel });
12
36
  this.log.debug('Matterbridge plugin manager starting...');
13
37
  }
14
38
  get length() {
@@ -45,6 +69,7 @@ export class PluginManager {
45
69
  }
46
70
  catch (error) {
47
71
  this.log.error(`Error processing forEach plugin ${plg}${plugin.name}${er}:`, error);
72
+ // throw error;
48
73
  }
49
74
  });
50
75
  await Promise.all(tasks);
@@ -52,13 +77,31 @@ export class PluginManager {
52
77
  set logLevel(logLevel) {
53
78
  this.log.logLevel = logLevel;
54
79
  }
80
+ /**
81
+ * Loads registered plugins from storage.
82
+ *
83
+ * This method retrieves an array of registered plugins from the storage and converts it
84
+ * into a map where the plugin names are the keys and the plugin objects are the values.
85
+ *
86
+ * @returns {Promise<RegisteredPlugin[]>} A promise that resolves to an array of registered plugins.
87
+ */
55
88
  async loadFromStorage() {
89
+ // Load the array from storage and convert it to a map
56
90
  const pluginsArray = await this.nodeContext.get('plugins', []);
57
91
  for (const plugin of pluginsArray)
58
92
  this._plugins.set(plugin.name, plugin);
59
93
  return pluginsArray;
60
94
  }
95
+ /**
96
+ * Loads registered plugins from storage.
97
+ *
98
+ * This method retrieves an array of registered plugins from the storage and converts it
99
+ * into a map where the plugin names are the keys and the plugin objects are the values.
100
+ *
101
+ * @returns {Promise<RegisteredPlugin[]>} A promise that resolves to an array of registered plugins.
102
+ */
61
103
  async saveToStorage() {
104
+ // Convert the map to an array
62
105
  const plugins = [];
63
106
  const pluginArrayFromMap = Array.from(this._plugins.values());
64
107
  for (const plugin of pluginArrayFromMap) {
@@ -78,13 +121,20 @@ export class PluginManager {
78
121
  this.log.debug(`Saved ${BLUE}${plugins.length}${db} plugins to storage`);
79
122
  return plugins.length;
80
123
  }
124
+ /**
125
+ * Resolves the name of a plugin by loading and parsing its package.json file.
126
+ * @param pluginPath - The path to the plugin or the path to the plugin's package.json file.
127
+ * @returns The path to the resolved package.json file, or null if the package.json file is not found or does not contain a name.
128
+ */
81
129
  async resolve(pluginPath) {
82
130
  const { default: path } = await import('node:path');
83
131
  const { promises } = await import('node:fs');
84
132
  if (!pluginPath.endsWith('package.json'))
85
133
  pluginPath = path.join(pluginPath, 'package.json');
134
+ // Resolve the package.json of the plugin
86
135
  let packageJsonPath = path.resolve(pluginPath);
87
136
  this.log.debug(`Resolving plugin path ${plg}${packageJsonPath}${db}`);
137
+ // Check if the package.json file exists
88
138
  try {
89
139
  await promises.access(packageJsonPath);
90
140
  }
@@ -94,7 +144,9 @@ export class PluginManager {
94
144
  this.log.debug(`Trying at ${plg}${packageJsonPath}${db}`);
95
145
  }
96
146
  try {
147
+ // Load the package.json of the plugin
97
148
  const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
149
+ // Check for main issues
98
150
  if (!packageJson.name) {
99
151
  this.log.error(`Package.json name not found at ${packageJsonPath}`);
100
152
  return null;
@@ -107,6 +159,7 @@ export class PluginManager {
107
159
  this.log.error(`Plugin at ${packageJsonPath} has no main entrypoint in package.json`);
108
160
  return null;
109
161
  }
162
+ // Check for @project-chip and @matter packages in dependencies and devDependencies
110
163
  const checkForProjectChipPackages = (dependencies) => {
111
164
  return Object.keys(dependencies).filter((pkg) => pkg.startsWith('@project-chip') || pkg.startsWith('@matter'));
112
165
  };
@@ -128,6 +181,7 @@ export class PluginManager {
128
181
  this.log.error(`Please open an issue on the plugin repository to remove them.`);
129
182
  return null;
130
183
  }
184
+ // Check for matterbridge package in dependencies and devDependencies
131
185
  const checkForMatterbridgePackage = (dependencies) => {
132
186
  return Object.keys(dependencies).filter((pkg) => pkg === 'matterbridge');
133
187
  };
@@ -157,6 +211,115 @@ export class PluginManager {
157
211
  return null;
158
212
  }
159
213
  }
214
+ /**
215
+ * Get the author of a plugin from its package.json.
216
+ *
217
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
218
+ * @returns {string} The author of the plugin, or 'Unknown author' if not found.
219
+ */
220
+ getAuthor(packageJson) {
221
+ if (packageJson.author && typeof packageJson.author === 'string')
222
+ return packageJson.author;
223
+ if (packageJson.author && typeof packageJson.author === 'object' && packageJson.author.name && typeof packageJson.author.name === 'string')
224
+ return packageJson.author.name;
225
+ return 'Unknown author';
226
+ }
227
+ /**
228
+ * Get the homepage of a plugin from its package.json.
229
+ *
230
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
231
+ * @returns {string | undefined} The homepage of the plugin, or undefined if not found.
232
+ */
233
+ getHomepage(packageJson) {
234
+ if (packageJson.homepage && typeof packageJson.homepage === 'string') {
235
+ return packageJson.homepage.replace('git+', '').replace('.git', '');
236
+ }
237
+ if (packageJson.repository && typeof packageJson.repository === 'object' && packageJson.repository.url && typeof packageJson.repository.url === 'string') {
238
+ return packageJson.repository.url.replace('git+', '').replace('.git', '');
239
+ }
240
+ }
241
+ /**
242
+ * Get the help URL of a plugin from its package.json.
243
+ *
244
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
245
+ * @returns {string | undefined} The URL to the help page or to the README file, or undefined if not found.
246
+ */
247
+ getHelp(packageJson) {
248
+ // If there's a help field that looks like a URL, return it.
249
+ if (packageJson.help && typeof packageJson.help === 'string' && packageJson.help.startsWith('http')) {
250
+ return packageJson.help;
251
+ }
252
+ // Derive a base URL from homepage or repository.
253
+ let baseUrl;
254
+ if (packageJson.homepage && typeof packageJson.homepage === 'string') {
255
+ // Remove a trailing "/README.md" if present.
256
+ baseUrl = packageJson.homepage
257
+ .replace(/\/README\.md$/i, '')
258
+ .replace('git+', '')
259
+ .replace('.git', '');
260
+ }
261
+ else if (packageJson.repository && typeof packageJson.repository === 'object' && packageJson.repository.url && typeof packageJson.repository.url === 'string') {
262
+ baseUrl = packageJson.repository.url.replace('git+', '').replace('.git', '');
263
+ }
264
+ return baseUrl ? `${baseUrl}/blob/main/README.md` : undefined;
265
+ }
266
+ /**
267
+ * Get the changelog URL of a plugin from its package.json.
268
+ *
269
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
270
+ * @returns {string | undefined} The URL to the CHANGELOG file, or undefined if not found.
271
+ */
272
+ getChangelog(packageJson) {
273
+ // If there's a changelog field that looks like a URL, return it.
274
+ if (packageJson.changelog && typeof packageJson.changelog === 'string' && packageJson.changelog.startsWith('http')) {
275
+ return packageJson.changelog;
276
+ }
277
+ // Derive a base URL from homepage or repository.
278
+ let baseUrl;
279
+ if (packageJson.homepage && typeof packageJson.homepage === 'string') {
280
+ baseUrl = packageJson.homepage
281
+ .replace(/\/README\.md$/i, '')
282
+ .replace('git+', '')
283
+ .replace('.git', '');
284
+ }
285
+ else if (packageJson.repository && typeof packageJson.repository === 'object' && packageJson.repository.url && typeof packageJson.repository.url === 'string') {
286
+ baseUrl = packageJson.repository.url.replace('git+', '').replace('.git', '');
287
+ }
288
+ return baseUrl ? `${baseUrl}/blob/main/CHANGELOG.md` : undefined;
289
+ }
290
+ /**
291
+ * Get the first funding URL(s) of a plugin from its package.json.
292
+ *
293
+ * @param {Record<string, any>} packageJson - The package.json object of the plugin.
294
+ * @returns {string | undefined} The first funding URLs, or undefined if not found.
295
+ */
296
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
+ getFunding(packageJson) {
298
+ const funding = packageJson.funding;
299
+ if (!funding)
300
+ return undefined;
301
+ if (typeof funding === 'string' && !funding.startsWith('http'))
302
+ return;
303
+ if (typeof funding === 'string' && funding.startsWith('http'))
304
+ return funding;
305
+ // Normalize funding into an array.
306
+ const fundingEntries = Array.isArray(funding) ? funding : [funding];
307
+ for (const entry of fundingEntries) {
308
+ if (entry && typeof entry === 'string' && entry.startsWith('http')) {
309
+ // If the funding entry is a string, assume it is a URL.
310
+ return entry;
311
+ }
312
+ else if (entry && typeof entry === 'object' && typeof entry.url === 'string' && entry.url.startsWith('http')) {
313
+ // If it's an object with a 'url' property, use that.
314
+ return entry.url;
315
+ }
316
+ }
317
+ }
318
+ /**
319
+ * Loads and parse the plugin package.json and returns it.
320
+ * @param plugin - The plugin to load the package from.
321
+ * @returns A Promise that resolves to the package.json object or undefined if the package.json could not be loaded.
322
+ */
160
323
  async parse(plugin) {
161
324
  const { promises } = await import('node:fs');
162
325
  try {
@@ -170,6 +333,8 @@ export class PluginManager {
170
333
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no description in package.json`);
171
334
  if (!packageJson.author)
172
335
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no author in package.json`);
336
+ if (!packageJson.homepage)
337
+ this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no homepage in package.json`);
173
338
  if (!packageJson.type || packageJson.type !== 'module')
174
339
  this.log.error(`Plugin ${plg}${plugin.name}${er} is not a module`);
175
340
  if (!packageJson.main)
@@ -177,11 +342,16 @@ export class PluginManager {
177
342
  plugin.name = packageJson.name || 'Unknown name';
178
343
  plugin.version = packageJson.version || '1.0.0';
179
344
  plugin.description = packageJson.description || 'Unknown description';
180
- plugin.author = packageJson.author || 'Unknown author';
345
+ plugin.author = this.getAuthor(packageJson);
346
+ plugin.homepage = this.getHomepage(packageJson);
347
+ plugin.help = this.getHelp(packageJson);
348
+ plugin.changelog = this.getChangelog(packageJson);
349
+ plugin.funding = this.getFunding(packageJson);
181
350
  if (!plugin.path)
182
351
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no path`);
183
352
  if (!plugin.type)
184
353
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no type`);
354
+ // Check for @project-chip and @matter packages in dependencies and devDependencies
185
355
  const checkForProjectChipPackages = (dependencies) => {
186
356
  return Object.keys(dependencies).filter((pkg) => pkg.startsWith('@project-chip') || pkg.startsWith('@matter'));
187
357
  };
@@ -203,6 +373,7 @@ export class PluginManager {
203
373
  this.log.error(`Please open an issue on the plugin repository to remove them.`);
204
374
  return null;
205
375
  }
376
+ // Check for matterbridge package in dependencies and devDependencies
206
377
  const checkForMatterbridgePackage = (dependencies) => {
207
378
  return Object.keys(dependencies).filter((pkg) => pkg === 'matterbridge');
208
379
  };
@@ -224,6 +395,7 @@ export class PluginManager {
224
395
  this.log.error(`Please open an issue on the plugin repository to remove them.`);
225
396
  return null;
226
397
  }
398
+ // await this.saveToStorage(); // No need to save the plugin to storage
227
399
  return packageJson;
228
400
  }
229
401
  catch (err) {
@@ -232,6 +404,16 @@ export class PluginManager {
232
404
  return null;
233
405
  }
234
406
  }
407
+ /**
408
+ * Enables a plugin by its name or path.
409
+ *
410
+ * This method enables a plugin by setting its `enabled` property to `true` and saving the updated
411
+ * plugin information to storage. It first checks if the plugin is already registered in the `_plugins` map.
412
+ * If not, it attempts to resolve the plugin's `package.json` file to retrieve its name and enable it.
413
+ *
414
+ * @param {string} nameOrPath - The name or path of the plugin to enable.
415
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the enabled plugin object, or null if the plugin could not be enabled.
416
+ */
235
417
  async enable(nameOrPath) {
236
418
  const { promises } = await import('node:fs');
237
419
  if (!nameOrPath || nameOrPath === '')
@@ -265,6 +447,16 @@ export class PluginManager {
265
447
  return null;
266
448
  }
267
449
  }
450
+ /**
451
+ * Enables a plugin by its name or path.
452
+ *
453
+ * This method enables a plugin by setting its `enabled` property to `true` and saving the updated
454
+ * plugin information to storage. It first checks if the plugin is already registered in the `_plugins` map.
455
+ * If not, it attempts to resolve the plugin's `package.json` file to retrieve its name and enable it.
456
+ *
457
+ * @param {string} nameOrPath - The name or path of the plugin to enable.
458
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the enabled plugin object, or null if the plugin could not be enabled.
459
+ */
268
460
  async disable(nameOrPath) {
269
461
  const { promises } = await import('node:fs');
270
462
  if (!nameOrPath || nameOrPath === '')
@@ -298,6 +490,16 @@ export class PluginManager {
298
490
  return null;
299
491
  }
300
492
  }
493
+ /**
494
+ * Removes a plugin by its name or path.
495
+ *
496
+ * This method removes a plugin from the `_plugins` map and saves the updated plugin information to storage.
497
+ * It first checks if the plugin is already registered in the `_plugins` map. If not, it attempts to resolve
498
+ * the plugin's `package.json` file to retrieve its name and remove it.
499
+ *
500
+ * @param {string} nameOrPath - The name or path of the plugin to remove.
501
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the removed plugin object, or null if the plugin could not be removed.
502
+ */
301
503
  async remove(nameOrPath) {
302
504
  const { promises } = await import('node:fs');
303
505
  if (!nameOrPath || nameOrPath === '')
@@ -331,6 +533,17 @@ export class PluginManager {
331
533
  return null;
332
534
  }
333
535
  }
536
+ /**
537
+ * Adds a plugin by its name or path.
538
+ *
539
+ * This method adds a plugin to the `_plugins` map and saves the updated plugin information to storage.
540
+ * It first resolves the plugin's `package.json` file to retrieve its details. If the plugin is already
541
+ * registered, it logs an info message and returns null. Otherwise, it registers the plugin, enables it,
542
+ * and saves the updated plugin information to storage.
543
+ *
544
+ * @param {string} nameOrPath - The name or path of the plugin to add.
545
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the added plugin object, or null if the plugin could not be added.
546
+ */
334
547
  async add(nameOrPath) {
335
548
  const { promises } = await import('node:fs');
336
549
  if (!nameOrPath || nameOrPath === '')
@@ -346,7 +559,15 @@ export class PluginManager {
346
559
  this.log.info(`Plugin ${plg}${nameOrPath}${nf} already registered`);
347
560
  return null;
348
561
  }
349
- this._plugins.set(packageJson.name, { name: packageJson.name, enabled: true, path: packageJsonPath, type: 'AnyPlatform', version: packageJson.version, description: packageJson.description, author: packageJson.author });
562
+ this._plugins.set(packageJson.name, {
563
+ name: packageJson.name,
564
+ enabled: true,
565
+ path: packageJsonPath,
566
+ type: 'AnyPlatform',
567
+ version: packageJson.version,
568
+ description: packageJson.description,
569
+ author: this.getAuthor(packageJson),
570
+ });
350
571
  this.log.info(`Added plugin ${plg}${packageJson.name}${nf}`);
351
572
  await this.saveToStorage();
352
573
  const plugin = this._plugins.get(packageJson.name);
@@ -357,6 +578,15 @@ export class PluginManager {
357
578
  return null;
358
579
  }
359
580
  }
581
+ /**
582
+ * Installs a plugin by its name.
583
+ *
584
+ * This method first uninstalls any existing version of the plugin, then installs the plugin globally using npm.
585
+ * It logs the installation process and retrieves the installed version of the plugin.
586
+ *
587
+ * @param {string} name - The name of the plugin to install.
588
+ * @returns {Promise<string | undefined>} A promise that resolves to the installed version of the plugin, or undefined if the installation failed.
589
+ */
360
590
  async install(name) {
361
591
  const { exec } = await import('node:child_process');
362
592
  await this.uninstall(name);
@@ -371,11 +601,14 @@ export class PluginManager {
371
601
  else {
372
602
  this.log.info(`Installed plugin ${plg}${name}${nf}`);
373
603
  this.log.debug(`Installed plugin ${plg}${name}${db}: ${stdout}`);
604
+ // Get the installed version
605
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
374
606
  exec(`npm list -g ${name} --depth=0`, (listError, listStdout, listStderr) => {
375
607
  if (listError) {
376
608
  this.log.error(`List error: ${listError}`);
377
609
  resolve(undefined);
378
610
  }
611
+ // Clean the output to get only the package name and version
379
612
  const lines = listStdout.split('\n');
380
613
  const versionLine = lines.find((line) => line.includes(`${name}@`));
381
614
  if (versionLine) {
@@ -391,6 +624,15 @@ export class PluginManager {
391
624
  });
392
625
  });
393
626
  }
627
+ /**
628
+ * Uninstalls a plugin by its name.
629
+ *
630
+ * This method uninstalls a globally installed plugin using npm. It logs the uninstallation process
631
+ * and returns the name of the uninstalled plugin if successful, or undefined if the uninstallation failed.
632
+ *
633
+ * @param {string} name - The name of the plugin to uninstall.
634
+ * @returns {Promise<string | undefined>} A promise that resolves to the name of the uninstalled plugin, or undefined if the uninstallation failed.
635
+ */
394
636
  async uninstall(name) {
395
637
  const { exec } = await import('node:child_process');
396
638
  this.log.info(`Uninstalling plugin ${plg}${name}${nf}`);
@@ -409,6 +651,14 @@ export class PluginManager {
409
651
  });
410
652
  });
411
653
  }
654
+ /**
655
+ * Loads a plugin and returns the corresponding MatterbridgePlatform instance.
656
+ * @param plugin - The plugin to load.
657
+ * @param start - Optional flag indicating whether to start the plugin after loading. Default is false.
658
+ * @param message - Optional message to pass to the plugin when starting.
659
+ * @returns A Promise that resolves to the loaded MatterbridgePlatform instance.
660
+ * @throws An error if the plugin is not enabled, already loaded, or fails to load.
661
+ */
412
662
  async load(plugin, start = false, message = '', configure = false) {
413
663
  const { promises } = await import('node:fs');
414
664
  const { default: path } = await import('node:path');
@@ -422,24 +672,29 @@ export class PluginManager {
422
672
  }
423
673
  this.log.info(`Loading plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
424
674
  try {
675
+ // Load the package.json of the plugin
425
676
  const packageJson = JSON.parse(await promises.readFile(plugin.path, 'utf8'));
677
+ // Resolve the main module path relative to package.json
426
678
  const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main);
679
+ // Dynamically import the plugin
427
680
  const { pathToFileURL } = await import('node:url');
428
681
  const pluginUrl = pathToFileURL(pluginEntry);
429
682
  this.log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
430
683
  const pluginInstance = await import(pluginUrl.href);
431
684
  this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
685
+ // Call the default export function of the plugin, passing this MatterBridge instance, the log and the config
432
686
  if (pluginInstance.default) {
433
687
  const config = await this.loadConfig(plugin);
688
+ // Preset the plugin properties here in case the plugin throws an error during loading. In this case the user can change the config and restart the plugin.
434
689
  plugin.name = packageJson.name;
435
690
  plugin.description = packageJson.description ?? 'No description';
436
691
  plugin.version = packageJson.version;
437
- plugin.author = packageJson.author ?? 'Unknown';
692
+ plugin.author = this.getAuthor(packageJson);
438
693
  plugin.configJson = config;
439
694
  plugin.schemaJson = await this.loadSchema(plugin);
440
695
  config.name = plugin.name;
441
696
  config.version = packageJson.version;
442
- const log = new AnsiLogger({ logName: plugin.description ?? 'No description', logTimestampFormat: 4, logLevel: config.debug ? "debug" : this.matterbridge.log.logLevel });
697
+ const log = new AnsiLogger({ logName: plugin.description ?? 'No description', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: config.debug ? "debug" /* LogLevel.DEBUG */ : this.matterbridge.log.logLevel });
443
698
  const platform = pluginInstance.default(this.matterbridge, log, config);
444
699
  config.type = platform.type;
445
700
  platform.name = packageJson.name;
@@ -448,13 +703,13 @@ export class PluginManager {
448
703
  plugin.name = packageJson.name;
449
704
  plugin.description = packageJson.description ?? 'No description';
450
705
  plugin.version = packageJson.version;
451
- plugin.author = packageJson.author ?? 'Unknown';
706
+ plugin.author = this.getAuthor(packageJson);
452
707
  plugin.type = platform.type;
453
708
  plugin.platform = platform;
454
709
  plugin.loaded = true;
455
710
  plugin.registeredDevices = 0;
456
711
  plugin.addedDevices = 0;
457
- await this.saveToStorage();
712
+ await this.saveToStorage(); // Save the plugin to storage
458
713
  this.log.notice(`Loaded plugin ${plg}${plugin.name}${nt} type ${typ}${platform.type}${nt} (entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
459
714
  if (start)
460
715
  await this.start(plugin, message, false);
@@ -473,6 +728,14 @@ export class PluginManager {
473
728
  }
474
729
  return undefined;
475
730
  }
731
+ /**
732
+ * Starts a plugin.
733
+ *
734
+ * @param {RegisteredPlugin} plugin - The plugin to start.
735
+ * @param {string} [message] - Optional message to pass to the plugin's onStart method.
736
+ * @param {boolean} [configure] - Indicates whether to configure the plugin after starting (default false).
737
+ * @returns {Promise<RegisteredPlugin | undefined>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
738
+ */
476
739
  async start(plugin, message, configure = false) {
477
740
  if (!plugin.loaded) {
478
741
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded`);
@@ -502,6 +765,12 @@ export class PluginManager {
502
765
  }
503
766
  return undefined;
504
767
  }
768
+ /**
769
+ * Configures a plugin.
770
+ *
771
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
772
+ * @returns {Promise<void>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
773
+ */
505
774
  async configure(plugin) {
506
775
  if (!plugin.loaded) {
507
776
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded`);
@@ -532,6 +801,18 @@ export class PluginManager {
532
801
  }
533
802
  return undefined;
534
803
  }
804
+ /**
805
+ * Shuts down a plugin.
806
+ *
807
+ * This method shuts down a plugin by calling its `onShutdown` method and resetting its state.
808
+ * It logs the shutdown process and optionally removes all devices associated with the plugin.
809
+ *
810
+ * @param {RegisteredPlugin} plugin - The plugin to shut down.
811
+ * @param {string} [reason] - The reason for shutting down the plugin.
812
+ * @param {boolean} [removeAllDevices=false] - Whether to remove all devices associated with the plugin.
813
+ * @param {boolean} [force=false] - Whether to force the shutdown even if the plugin is not loaded or started.
814
+ * @returns {Promise<RegisteredPlugin | undefined>} A promise that resolves to the shut down plugin object, or undefined if the shutdown failed.
815
+ */
535
816
  async shutdown(plugin, reason, removeAllDevices = false, force = false) {
536
817
  this.log.debug(`Shutting down plugin ${plg}${plugin.name}${db}`);
537
818
  if (!plugin.loaded) {
@@ -574,6 +855,15 @@ export class PluginManager {
574
855
  }
575
856
  return undefined;
576
857
  }
858
+ /**
859
+ * Loads the configuration for a plugin.
860
+ * If the configuration file exists, it reads the file and returns the parsed JSON data.
861
+ * If the configuration file does not exist, it creates a new file with default configuration and returns it.
862
+ * If any error occurs during file access or creation, it logs an error and return un empty config.
863
+ *
864
+ * @param plugin - The plugin for which to load the configuration.
865
+ * @returns A promise that resolves to the loaded or created configuration.
866
+ */
577
867
  async loadConfig(plugin) {
578
868
  const { default: path } = await import('node:path');
579
869
  const { promises } = await import('node:fs');
@@ -584,6 +874,8 @@ export class PluginManager {
584
874
  const data = await promises.readFile(configFile, 'utf8');
585
875
  const config = JSON.parse(data);
586
876
  this.log.debug(`Loaded config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
877
+ // this.log.debug(`Loaded config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, config);
878
+ // The first time a plugin is added to the system, the config file is created with the plugin name and type "AnyPlatform".
587
879
  config.name = plugin.name;
588
880
  config.type = plugin.type;
589
881
  if (config.debug === undefined)
@@ -607,6 +899,7 @@ export class PluginManager {
607
899
  try {
608
900
  await promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
609
901
  this.log.debug(`Created config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
902
+ // this.log.debug(`Created config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, config);
610
903
  return config;
611
904
  }
612
905
  catch (err) {
@@ -620,6 +913,18 @@ export class PluginManager {
620
913
  }
621
914
  }
622
915
  }
916
+ /**
917
+ * Saves the configuration of a plugin to a file.
918
+ *
919
+ * This method saves the configuration of the specified plugin to a JSON file in the matterbridge directory.
920
+ * If the plugin's configuration is not found, it logs an error and rejects the promise. If the configuration
921
+ * is successfully saved, it logs a debug message. If an error occurs during the file write operation, it logs
922
+ * the error and rejects the promise.
923
+ *
924
+ * @param {RegisteredPlugin} plugin - The plugin whose configuration is to be saved.
925
+ * @returns {Promise<void>} A promise that resolves when the configuration is successfully saved, or rejects if an error occurs.
926
+ * @throws {Error} If the plugin's configuration is not found.
927
+ */
623
928
  async saveConfigFromPlugin(plugin) {
624
929
  const { default: path } = await import('node:path');
625
930
  const { promises } = await import('node:fs');
@@ -632,6 +937,7 @@ export class PluginManager {
632
937
  await promises.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2), 'utf8');
633
938
  plugin.configJson = plugin.platform.config;
634
939
  this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
940
+ // this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, plugin.platform.config);
635
941
  return Promise.resolve();
636
942
  }
637
943
  catch (err) {
@@ -639,6 +945,19 @@ export class PluginManager {
639
945
  return Promise.reject(err);
640
946
  }
641
947
  }
948
+ /**
949
+ * Saves the configuration of a plugin from a JSON object to a file.
950
+ *
951
+ * This method saves the provided configuration of the specified plugin to a JSON file in the matterbridge directory.
952
+ * It first checks if the configuration data is valid by ensuring it contains the correct name and type, and matches
953
+ * the plugin's name. If the configuration data is invalid, it logs an error and returns. If the configuration is
954
+ * successfully saved, it updates the plugin's `configJson` property and logs a debug message. If an error occurs
955
+ * during the file write operation, it logs the error and returns.
956
+ *
957
+ * @param {RegisteredPlugin} plugin - The plugin whose configuration is to be saved.
958
+ * @param {PlatformConfig} config - The configuration data to be saved.
959
+ * @returns {Promise<void>} A promise that resolves when the configuration is successfully saved, or returns if an error occurs.
960
+ */
642
961
  async saveConfigFromJson(plugin, config) {
643
962
  const { default: path } = await import('node:path');
644
963
  const { promises } = await import('node:fs');
@@ -656,12 +975,23 @@ export class PluginManager {
656
975
  plugin.platform.onConfigChanged(config).catch((err) => this.log.error(`Error calling onConfigChanged for plugin ${plg}${plugin.name}${er}: ${err}`));
657
976
  }
658
977
  this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
978
+ // this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, config);
659
979
  }
660
980
  catch (err) {
661
981
  this.log.error(`Error saving config file ${configFile} for plugin ${plg}${plugin.name}${er}: ${err}`);
662
982
  return;
663
983
  }
664
984
  }
985
+ /**
986
+ * Loads the schema for a plugin.
987
+ *
988
+ * This method attempts to load the schema file for the specified plugin. If the schema file is found,
989
+ * it reads and parses the file, updates the schema's title and description, and logs the process.
990
+ * If the schema file is not found, it logs the event and loads a default schema for the plugin.
991
+ *
992
+ * @param {RegisteredPlugin} plugin - The plugin whose schema is to be loaded.
993
+ * @returns {Promise<PlatformSchema>} A promise that resolves to the loaded schema object, or the default schema if the schema file is not found.
994
+ */
665
995
  async loadSchema(plugin) {
666
996
  const { promises } = await import('node:fs');
667
997
  const schemaFile = plugin.path.replace('package.json', `${plugin.name}.schema.json`);
@@ -672,6 +1002,7 @@ export class PluginManager {
672
1002
  schema.title = plugin.description;
673
1003
  schema.description = plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author;
674
1004
  this.log.debug(`Loaded schema file ${schemaFile} for plugin ${plg}${plugin.name}${db}.`);
1005
+ // this.log.debug(`Loaded schema file ${schemaFile} for plugin ${plg}${plugin.name}${db}.\nSchema:${rs}\n`, schema);
675
1006
  return schema;
676
1007
  }
677
1008
  catch (error) {
@@ -679,6 +1010,16 @@ export class PluginManager {
679
1010
  return this.getDefaultSchema(plugin);
680
1011
  }
681
1012
  }
1013
+ /**
1014
+ * Returns the default schema for a plugin.
1015
+ *
1016
+ * This method generates a default schema object for the specified plugin. The schema includes
1017
+ * metadata such as the plugin's title, description, version, and author. It also defines the
1018
+ * properties of the schema, including the plugin's name, type, debug flag, and unregisterOnShutdown flag.
1019
+ *
1020
+ * @param {RegisteredPlugin} plugin - The plugin for which the default schema is to be generated.
1021
+ * @returns {PlatformSchema} The default schema object for the plugin.
1022
+ */
682
1023
  getDefaultSchema(plugin) {
683
1024
  return {
684
1025
  title: plugin.description,
@@ -709,3 +1050,4 @@ export class PluginManager {
709
1050
  };
710
1051
  }
711
1052
  }
1053
+ //# sourceMappingURL=pluginManager.js.map