homey-lib 2.45.3 → 2.46.0

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 (46) hide show
  1. package/.eslintignore +2 -1
  2. package/assets/app/schema.d.ts +546 -0
  3. package/assets/app/schema.json +136 -0
  4. package/assets/capability/schema.d.ts +53 -0
  5. package/helpers/index.js +21 -0
  6. package/index.js +48 -24
  7. package/lib/App/index.js +184 -5
  8. package/lib/Capability/index.js +42 -0
  9. package/lib/Device/index.js +10 -0
  10. package/lib/Energy/index.js +6 -0
  11. package/lib/Media/index.js +3 -0
  12. package/lib/Signal/index.js +25 -0
  13. package/lib/Signal/validators.js +21 -0
  14. package/lib/Util/file.js +84 -0
  15. package/lib/Util/index.js +72 -0
  16. package/lib/Util/zigbee.js +225 -0
  17. package/package.json +11 -3
  18. package/tsconfig.types.json +15 -0
  19. package/types/assets/app/schema.d.ts +546 -0
  20. package/types/assets/capability/schema.d.ts +53 -0
  21. package/types/helpers/index.d.ts +12 -0
  22. package/types/helpers/index.d.ts.map +1 -0
  23. package/types/index.d.ts +24 -0
  24. package/types/index.d.ts.map +1 -0
  25. package/types/lib/App/index.d.ts +140 -0
  26. package/types/lib/App/index.d.ts.map +1 -0
  27. package/types/lib/Capability/index.d.ts +61 -0
  28. package/types/lib/Capability/index.d.ts.map +1 -0
  29. package/types/lib/Device/index.d.ts +18 -0
  30. package/types/lib/Device/index.d.ts.map +1 -0
  31. package/types/lib/Energy/index.d.ts +12 -0
  32. package/types/lib/Energy/index.d.ts.map +1 -0
  33. package/types/lib/Media/index.d.ts +8 -0
  34. package/types/lib/Media/index.d.ts.map +1 -0
  35. package/types/lib/Signal/index.d.ts +52 -0
  36. package/types/lib/Signal/index.d.ts.map +1 -0
  37. package/types/lib/Signal/validators.d.ts +37 -0
  38. package/types/lib/Signal/validators.d.ts.map +1 -0
  39. package/types/lib/Util/file.d.ts +27 -0
  40. package/types/lib/Util/file.d.ts.map +1 -0
  41. package/types/lib/Util/index.d.ts +76 -0
  42. package/types/lib/Util/index.d.ts.map +1 -0
  43. package/types/lib/Util/zigbee.d.ts +88 -0
  44. package/types/lib/Util/zigbee.d.ts.map +1 -0
  45. package/webpack/index.js +1 -1
  46. package/webpack.config.js +1 -0
@@ -0,0 +1,53 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated by json-schema-to-typescript.
4
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5
+ * and run json-schema-to-typescript to regenerate this file.
6
+ */
7
+
8
+ export type Capability = {
9
+ [k: string]: unknown;
10
+ } & {
11
+ title: I18NObject;
12
+ desc?: I18NObject;
13
+ type: "boolean" | "number" | "string" | "enum";
14
+ getable?: boolean;
15
+ setable?: boolean;
16
+ icon?: string;
17
+ insights?: boolean;
18
+ insightsTitleTrue?: I18NObject;
19
+ insightsTitleFalse?: I18NObject;
20
+ chartType?: "line" | "area" | "stepLine" | "column" | "spline" | "splineArea" | "scatter";
21
+ decimals?: number;
22
+ min?: number;
23
+ max?: number;
24
+ step?: number;
25
+ units?: I18NObject;
26
+ values?: {
27
+ id: string;
28
+ title: I18NObject;
29
+ [k: string]: unknown;
30
+ }[];
31
+ uiComponent?:
32
+ | ("thermostat" | "media" | "toggle" | "slider" | "ternary" | "button" | "color" | "picker" | "sensor" | "battery")
33
+ | null;
34
+ [k: string]: unknown;
35
+ } & (
36
+ | {
37
+ type?: "enum";
38
+ [k: string]: unknown;
39
+ }
40
+ | {
41
+ type?: "boolean" | "number" | "string";
42
+ [k: string]: unknown;
43
+ }
44
+ );
45
+ export type I18NObject =
46
+ | string
47
+ | {
48
+ /**
49
+ * This interface was referenced by `undefined`'s JSON-Schema definition
50
+ * via the `patternProperty` "^.*$".
51
+ */
52
+ [k: string]: string;
53
+ };
package/helpers/index.js CHANGED
@@ -9,21 +9,42 @@ try {
9
9
  const imageSize = require('image-size');
10
10
 
11
11
  if (fs && util && util.promisify) {
12
+ /** @type {typeof fs.open.__promisify__} */
12
13
  module.exports.openAsync = util.promisify(fs.open);
14
+ /** @type {typeof fs.close.__promisify__} */
13
15
  module.exports.closeAsync = util.promisify(fs.close);
16
+ /** @type {typeof fs.read.__promisify__} */
14
17
  module.exports.readAsync = util.promisify(fs.read);
18
+ /** @type {typeof fs.stat.__promisify__} */
15
19
  module.exports.statAsync = util.promisify(fs.stat);
20
+ /** @type {typeof fs.readFile.__promisify__} */
16
21
  module.exports.readFileAsync = util.promisify(fs.readFile);
22
+ /** @type {typeof fs.readdir.__promisify__} */
17
23
  module.exports.readDirAsync = util.promisify(fs.readdir);
24
+ /** @type {typeof fs.lstat.__promisify__} */
18
25
  module.exports.lstatAsync = util.promisify(fs.lstat);
19
26
  module.exports.imageSizeAsync = util.promisify(imageSize);
20
27
  }
21
28
 
22
29
  if (path) {
30
+ /** @type {typeof path.join} */
23
31
  module.exports.join = path.join;
32
+ /** @type {typeof path.extname} */
24
33
  module.exports.extname = path.extname;
34
+ /** @type {typeof path.basename} */
25
35
  module.exports.basename = path.basename;
36
+ /** @type {typeof path.dirname} */
26
37
  module.exports.dirname = path.dirname;
27
38
  }
28
39
  } catch (err) {
29
40
  }
41
+
42
+ function toArray(value) {
43
+ if (value) {
44
+ return Array.isArray(value) ? value : [value];
45
+ }
46
+
47
+ return [];
48
+ }
49
+
50
+ module.exports.toArray = toArray;
package/index.js CHANGED
@@ -1,26 +1,50 @@
1
1
  'use strict';
2
2
 
3
- module.exports.App = require('./lib/App');
4
- module.exports.Capability = require('./lib/Capability');
5
- module.exports.Device = require('./lib/Device');
6
- module.exports.Energy = require('./lib/Energy');
7
- module.exports.Media = require('./lib/Media');
8
- module.exports.Signal = require('./lib/Signal');
9
- module.exports.Util = require('./lib/Util');
10
-
11
- module.exports.getDeviceClasses = module.exports.Device.getClasses.bind(module.exports.Device);
12
- module.exports.getDeviceClass = module.exports.Device.getClass.bind(module.exports.Device);
13
-
14
- module.exports.getCapabilities = module.exports.Capability.getCapabilities.bind(module.exports.Capability);
15
- module.exports.getCapability = module.exports.Capability.getCapability.bind(module.exports.Capability);
16
- module.exports.hasCapability = module.exports.Capability.hasCapability.bind(module.exports.Capability);
17
-
18
- module.exports.getAppLocales = module.exports.App.getLocales.bind(module.exports.App);
19
- module.exports.getAppCategories = module.exports.App.getCategories.bind(module.exports.App);
20
- module.exports.getAppPermissions = module.exports.App.getPermissions.bind(module.exports.App);
21
- module.exports.getAppBrandColor = module.exports.App.getBrandColor.bind(module.exports.App);
22
-
23
- module.exports.getMediaCodecs = module.exports.Media.getCodecs.bind(module.exports.Media);
24
-
25
- module.exports.getCurrencies = module.exports.Energy.getCurrencies.bind(module.exports.Energy);
26
- module.exports.getBatteries = module.exports.Energy.getBatteries.bind(module.exports.Energy);
3
+ const App = require('./lib/App');
4
+ const Capability = require('./lib/Capability');
5
+ const Device = require('./lib/Device');
6
+ const Energy = require('./lib/Energy');
7
+ const Media = require('./lib/Media');
8
+ const Signal = require('./lib/Signal');
9
+ const Util = require('./lib/Util');
10
+
11
+ module.exports.App = App;
12
+ module.exports.Capability = Capability;
13
+ module.exports.Device = Device;
14
+ module.exports.Energy = Energy;
15
+ module.exports.Media = Media;
16
+ module.exports.Signal = Signal;
17
+ module.exports.Util = Util;
18
+
19
+ /** @type {typeof Device.getClasses} */
20
+ module.exports.getDeviceClasses = Device.getClasses.bind(Device);
21
+ /** @type {typeof Device.getClass} */
22
+ module.exports.getDeviceClass = Device.getClass.bind(Device);
23
+
24
+ /** @type {typeof Capability.getCapabilities} */
25
+ module.exports.getCapabilities = Capability.getCapabilities.bind(Capability);
26
+ /** @type {typeof Capability.getCapability} */
27
+ module.exports.getCapability = Capability.getCapability.bind(Capability);
28
+ /** @type {typeof Capability.hasCapability} */
29
+ module.exports.hasCapability = Capability.hasCapability.bind(Capability);
30
+
31
+ /** @type {typeof App.getLocales} */
32
+ module.exports.getAppLocales = App.getLocales.bind(App);
33
+ /** @type {typeof App.getCategories} */
34
+ module.exports.getAppCategories = App.getCategories.bind(App);
35
+ /** @type {typeof App.getPermissions} */
36
+ module.exports.getAppPermissions = App.getPermissions.bind(App);
37
+ /** @type {typeof App.getBrandColor} */
38
+ module.exports.getAppBrandColor = App.getBrandColor.bind(App);
39
+
40
+ /** @type {typeof Media.getCodecs} */
41
+ module.exports.getMediaCodecs = Media.getCodecs.bind(Media);
42
+
43
+ /** @type {typeof Energy.getCurrencies} */
44
+ module.exports.getCurrencies = Energy.getCurrencies.bind(Energy);
45
+ /** @type {typeof Energy.getBatteries} */
46
+ module.exports.getBatteries = Energy.getBatteries.bind(Energy);
47
+
48
+ /** @typedef {import('./assets/app/schema').App} AppManifest */
49
+ /** @typedef {import('./assets/capability/schema').Capability} CapabilityDefinition */
50
+ /** @typedef {import('./assets/app/schema').ZigbeeFirmwareUpdates} ZigbeeFirmwareUpdates */
package/lib/App/index.js CHANGED
@@ -14,6 +14,7 @@ const Device = require('../Device');
14
14
  const Capability = require('../Capability');
15
15
  const Signal = require('../Signal');
16
16
  const Energy = require('../Energy');
17
+ const Util = require('../Util');
17
18
 
18
19
  const {
19
20
  openAsync,
@@ -28,8 +29,11 @@ const {
28
29
  extname,
29
30
  basename,
30
31
  dirname,
32
+ toArray,
31
33
  } = require('../../helpers');
32
34
 
35
+ /** @typedef {import('../../assets/app/schema').App} AppManifest */
36
+
33
37
  const VALIDATION_LEVELS = [
34
38
  'debug',
35
39
  'publish',
@@ -76,6 +80,9 @@ class App {
76
80
  static REQUIRED_PYTHON_PLATFORMS = ['arm64', 'amd64']
77
81
  static SUPPORTED_PYTHON_VERSIONS = ['3.13', '3.14']
78
82
 
83
+ /**
84
+ * @param {string} path
85
+ */
79
86
  constructor(path) {
80
87
  this._path = path;
81
88
 
@@ -84,11 +91,19 @@ class App {
84
91
  }
85
92
  }
86
93
 
94
+ /**
95
+ * @param {...unknown} args
96
+ * @returns {void}
97
+ */
87
98
  debug(...args) {
88
99
  if (!this._debug) return;
89
100
  console.log('[dbg]', ...args);
90
101
  }
91
102
 
103
+ /**
104
+ * @param {{ level?: 'debug' | 'publish' | 'verified', debug?: boolean }} [options]
105
+ * @returns {Promise<void>}
106
+ */
92
107
  async validate({
93
108
  level = 'debug',
94
109
  debug = false,
@@ -104,8 +119,9 @@ class App {
104
119
  const levelPublish = (level === 'publish' || level === 'verified');
105
120
  const levelVerified = (level === 'verified');
106
121
 
107
- let appJson = await readFileAsync(join(this._path, 'app.json'));
108
- appJson = JSON.parse(appJson);
122
+ const appJsonBuffer = await readFileAsync(join(this._path, 'app.json'));
123
+ /** @type {AppManifest} */
124
+ const appJson = JSON.parse(appJsonBuffer);
109
125
 
110
126
  const schema = App.getJSONSchema();
111
127
 
@@ -216,9 +232,8 @@ class App {
216
232
  // validate that Python apps target compatibility of at least >=13.0.0
217
233
  if (appJson.runtime === 'python') {
218
234
  const minVersion = semver.minVersion(appJson.compatibility);
219
- // tmp set to 12.11.1
220
- if (!semver.gte(minVersion, '12.11.1')) {
221
- throw new Error(`Invalid compatibility (${appJson.compatibility}), Python apps must have a compatibility of at least >=12.11.1`);
235
+ if (!semver.gte(minVersion, '13.0.0')) {
236
+ throw new Error(`Invalid compatibility (${appJson.compatibility}), Python apps must have a compatibility of at least >=13.0.0`);
222
237
  }
223
238
  }
224
239
 
@@ -631,6 +646,88 @@ class App {
631
646
  if (driver.matter && !(driver.connectivity && driver.connectivity.includes('matter'))) {
632
647
  throw new Error(`drivers.${driver.id} Matter drivers require 'connectivity' to include 'matter'.`);
633
648
  }
649
+
650
+ // Validate driver.firmwareUpdates
651
+ if (driver.firmwareUpdates && Array.isArray(driver.firmwareUpdates.updates)) {
652
+ // TODO: Remove when we want to roll out firmware updates.
653
+ if (level !== 'debug') {
654
+ throw new Error(`drivers.${driver.id} firmwareUpdates can only be included in debug mode validation.`);
655
+ }
656
+
657
+ const isZigbeeDriver = typeof driver.zigbee === 'object' && driver.zigbee !== null;
658
+
659
+ if (!isZigbeeDriver) {
660
+ throw new Error(`drivers.${driver.id} firmwareUpdates are only supported for Zigbee drivers (missing driver.zigbee)`);
661
+ }
662
+
663
+ for (const [updateIndex, update] of Object.entries(driver.firmwareUpdates.updates)) {
664
+ const hasChangelogString = typeof update.changelog === 'string';
665
+ const hasChangelogEnString = update.changelog && typeof update.changelog.en === 'string';
666
+
667
+ if (!hasChangelogString && !hasChangelogEnString) {
668
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] is missing a changelog string`);
669
+ }
670
+
671
+ if (!Array.isArray(update.files) || update.files.length === 0) {
672
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}].files must include at least one file`);
673
+ }
674
+
675
+ if (isZigbeeDriver) {
676
+ const driverManufacturerNames = toArray(driver.zigbee.manufacturerName);
677
+ const driverProductIds = toArray(driver.zigbee.productId);
678
+
679
+ const updateManufacturerNames = toArray(update.device.manufacturerName);
680
+ const updateProductIds = toArray(update.device.productId);
681
+
682
+ if (updateManufacturerNames.length === 0 || updateProductIds.length === 0) {
683
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] must include at least one manufacturerName and productId`);
684
+ }
685
+
686
+ const hasMismatchedManufacturerName = updateManufacturerNames.some(name => !driverManufacturerNames.includes(name));
687
+ const hasMismatchedProductId = updateProductIds.some(id => !driverProductIds.includes(id));
688
+
689
+ if (hasMismatchedManufacturerName) {
690
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] has a manufacturerName that does not match the driver zigbee.manufacturerName`);
691
+ }
692
+
693
+ if (hasMismatchedProductId) {
694
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] has a productId that does not match the driver zigbee.productId`);
695
+ }
696
+ }
697
+
698
+ const seenFiles = new Set();
699
+
700
+ for (const [fileIndex, file] of Object.entries(update.files)) {
701
+ const relativeFilePath = Util.getOTAFilePath({ appPath: '', driverId: driver.id, fileName: file.name });
702
+ await this._ensureFileExistsCaseSensitive(relativeFilePath);
703
+ const filePath = join(this._path, relativeFilePath);
704
+ const isIntegrityValid = await Util.validateIntegrity(filePath, file.integrity);
705
+
706
+ if (!isIntegrityValid) {
707
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}].files[${fileIndex}] integrity mismatch`);
708
+ }
709
+
710
+ if (isZigbeeDriver) {
711
+ try {
712
+ await Util.validateZigbeeOTAHeader({
713
+ filePath,
714
+ manufacturerCode: file.manufacturerCode,
715
+ fileVersion: file.fileVersion,
716
+ imageType: file.imageType,
717
+ });
718
+ } catch (err) {
719
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}].files[${fileIndex}] invalid Zigbee OTA header: ${err.message}`);
720
+ }
721
+ }
722
+
723
+ const fileId = `${file.manufacturerCode}-${file.imageType}-${file.fileVersion}`;
724
+ if (seenFiles.has(fileId)) {
725
+ throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] has multiple files with the same manufacturerCode, imageType and fileVersion`);
726
+ }
727
+ seenFiles.add(fileId);
728
+ }
729
+ }
730
+ }
634
731
  }
635
732
 
636
733
  const allDriversMatter = appJson.drivers.every(driver => driver.connectivity && driver.connectivity.includes('matter'));
@@ -787,6 +884,10 @@ class App {
787
884
  this.debug('Validated successfully');
788
885
  }
789
886
 
887
+ /**
888
+ * @param {AppManifest} appJson
889
+ * @returns {void}
890
+ */
790
891
  static validatePythonVersion(appJson) {
791
892
  // validate that Python apps specify a runtime version
792
893
  if (appJson.runtime === 'python' && !App.SUPPORTED_PYTHON_VERSIONS.includes(appJson.pythonVersion)) {
@@ -794,6 +895,11 @@ class App {
794
895
  }
795
896
  }
796
897
 
898
+ /**
899
+ * @param {Record<string, unknown>} setting
900
+ * @param {{ id: string }} driver
901
+ * @returns {void}
902
+ */
797
903
  _checkPrivateSettingPrefixUse(setting, driver) {
798
904
  if (
799
905
  setting.type &&
@@ -818,6 +924,11 @@ class App {
818
924
  }
819
925
  }
820
926
 
927
+ /**
928
+ * @param {Record<string, unknown> | null | undefined} driver
929
+ * @param {Record<string, unknown> | null | undefined} setting
930
+ * @returns {void}
931
+ */
821
932
  _checkZwaveForSetting(driver, setting) {
822
933
  if (!driver || !setting || !setting.zwave) return;
823
934
  if (typeof setting.zwave.index !== 'number' || typeof setting.zwave.size !== 'number') throw new Error(`Missing property in "zwave" at ${driver.id}, ${setting.id}`);
@@ -841,6 +952,10 @@ class App {
841
952
  }
842
953
  }
843
954
 
955
+ /**
956
+ * @param {string} filepath
957
+ * @returns {Promise<string[]>}
958
+ */
844
959
  async _getDirectoryContents(filepath) {
845
960
  await this._fileExistsCaseSensitive(filepath);
846
961
 
@@ -853,6 +968,10 @@ class App {
853
968
  });
854
969
  }
855
970
 
971
+ /**
972
+ * @param {string} filepath
973
+ * @returns {Promise<void>}
974
+ */
856
975
  async _ensureFileExistsCaseSensitive(filepath) {
857
976
  const exists = await this._fileExistsCaseSensitive(filepath);
858
977
  if (exists !== true) {
@@ -860,6 +979,10 @@ class App {
860
979
  }
861
980
  }
862
981
 
982
+ /**
983
+ * @param {string} filepath
984
+ * @returns {Promise<boolean>}
985
+ */
863
986
  async _fileExistsCaseSensitive(filepath) {
864
987
  filepath = join(this._path, filepath);
865
988
  const dir = dirname(filepath);
@@ -875,6 +998,12 @@ class App {
875
998
  }
876
999
  }
877
1000
 
1001
+ /**
1002
+ * @param {{ small: string, large: string }} imagesObj
1003
+ * @param {'app' | 'driver'} type
1004
+ * @param {string} [errorPath]
1005
+ * @returns {Promise<void>}
1006
+ */
878
1007
  async _validateImages(imagesObj, type, errorPath) {
879
1008
  const sizes = ['small', 'large'];
880
1009
  for (let i = 0; i < sizes.length; i++) {
@@ -904,6 +1033,9 @@ class App {
904
1033
  }
905
1034
  }
906
1035
 
1036
+ /**
1037
+ * @returns {Promise<void>}
1038
+ */
907
1039
  async _validateModules() {
908
1040
  const nodeModulesPath = join(this._path, 'node_modules');
909
1041
 
@@ -927,6 +1059,13 @@ class App {
927
1059
  }
928
1060
  }
929
1061
 
1062
+ /**
1063
+ * @param {Record<string, unknown>} card
1064
+ * @param {string} errorPath
1065
+ * @param {AppManifest} appJson
1066
+ * @param {{ levelPublish: boolean, levelVerified: boolean }} options
1067
+ * @returns {void}
1068
+ */
930
1069
  _validateFlowCard(card, errorPath, appJson, { levelPublish, levelVerified }) {
931
1070
  if (Array.isArray(card.tokens)) {
932
1071
  for (const token of card.tokens) {
@@ -1006,6 +1145,12 @@ class App {
1006
1145
  }
1007
1146
  }
1008
1147
 
1148
+ /**
1149
+ * @param {string} titleFormatted
1150
+ * @param {{ name: string }[]} args
1151
+ * @param {string} errorPath
1152
+ * @returns {void}
1153
+ */
1009
1154
  static _checkTitleFormatted(titleFormatted, args, errorPath) {
1010
1155
  const argsPresent = args.reduce((obj, arg) => {
1011
1156
  obj[arg.name] = false;
@@ -1040,6 +1185,11 @@ class App {
1040
1185
  }
1041
1186
  }
1042
1187
 
1188
+ /**
1189
+ * @param {string} filepath
1190
+ * @param {number} numBytes
1191
+ * @returns {Promise<Buffer>}
1192
+ */
1043
1193
  async _readBytes(filepath, numBytes) {
1044
1194
  filepath = join(this._path, filepath);
1045
1195
 
@@ -1050,6 +1200,10 @@ class App {
1050
1200
  return buffer;
1051
1201
  }
1052
1202
 
1203
+ /**
1204
+ * @param {string} appId
1205
+ * @returns {boolean}
1206
+ */
1053
1207
  static isValidId(appId) {
1054
1208
  if (typeof appId !== 'string') return false;
1055
1209
  if (appId.length < 1) return false;
@@ -1058,16 +1212,26 @@ class App {
1058
1212
  return true;
1059
1213
  }
1060
1214
 
1215
+ /**
1216
+ * @param {string} color
1217
+ * @returns {boolean}
1218
+ */
1061
1219
  static isValidBrandColor(color) {
1062
1220
  return tinycolor(color).getBrightness() <= 184; // empirically determined by many colorpicker samples
1063
1221
  }
1064
1222
 
1223
+ /**
1224
+ * @returns {Record<string, unknown>}
1225
+ */
1065
1226
  static getJSONSchema() {
1066
1227
  // eslint-disable-next-line global-require
1067
1228
  const schema = require('../../assets/app/schema.json');
1068
1229
  return JSON.parse(JSON.stringify(schema));
1069
1230
  }
1070
1231
 
1232
+ /**
1233
+ * @returns {Record<string, Record<string, unknown>>}
1234
+ */
1071
1235
  static getPermissions() {
1072
1236
  // eslint-disable-next-line global-require
1073
1237
  const permissions = require('../../assets/app/permissions.json');
@@ -1082,15 +1246,25 @@ class App {
1082
1246
  return permissions;
1083
1247
  }
1084
1248
 
1249
+ /**
1250
+ * @returns {string[]}
1251
+ */
1085
1252
  static getCategories() {
1086
1253
  return ['lights', 'video', 'music', 'appliances', 'security', 'climate', 'tools', 'internet', 'localization', 'energy'];
1087
1254
  }
1088
1255
 
1256
+ /**
1257
+ * @returns {string[]}
1258
+ */
1089
1259
  static getLocales() {
1090
1260
  // eslint-disable-next-line max-len
1091
1261
  return ['ab', 'aa', 'af', 'ak', 'sq', 'am', 'ar', 'an', 'hy', 'as', 'av', 'ae', 'ay', 'az', 'bm', 'ba', 'eu', 'be', 'bn', 'bh', 'bi', 'bs', 'br', 'bg', 'my', 'ca', 'ch', 'ce', 'ny', 'zh', 'cv', 'kw', 'co', 'cr', 'hr', 'cs', 'da', 'dv', 'nl', 'dz', 'en', 'eo', 'et', 'ee', 'fo', 'fj', 'fi', 'fr', 'ff', 'gl', 'ka', 'de', 'el', 'gn', 'gu', 'ht', 'ha', 'he', 'hz', 'hi', 'ho', 'hu', 'ia', 'id', 'ie', 'ga', 'ig', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'kl', 'kn', 'kr', 'ks', 'kk', 'km', 'ki', 'rw', 'ky', 'kv', 'kg', 'ko', 'ku', 'kj', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'gv', 'mk', 'mg', 'ms', 'ml', 'mt', 'mi', 'mr', 'mh', 'mn', 'na', 'nv', 'nd', 'ne', 'ng', 'nb', 'nn', 'no', 'ii', 'nr', 'oc', 'oj', 'cu', 'om', 'or', 'os', 'pa', 'pi', 'fa', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'sa', 'sc', 'sd', 'se', 'sm', 'sg', 'sr', 'gd', 'sn', 'si', 'sk', 'sl', 'so', 'st', 'es', 'su', 'sw', 'ss', 'sv', 'ta', 'te', 'tg', 'th', 'ti', 'bo', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'cy', 'wo', 'fy', 'xh', 'yi', 'yo', 'za', 'zu'];
1092
1262
  }
1093
1263
 
1264
+ /**
1265
+ * @param {string} appId
1266
+ * @returns {string}
1267
+ */
1094
1268
  static getBrandColor(appId) {
1095
1269
  const appIdHex = Buffer.from(appId).toString('hex');
1096
1270
  let brandColor;
@@ -1114,6 +1288,11 @@ class App {
1114
1288
  return brandColor;
1115
1289
  }
1116
1290
 
1291
+ /**
1292
+ * @param {Array<Record<string, unknown>> | undefined} errors
1293
+ * @param {AppManifest} appJson
1294
+ * @returns {string | null}
1295
+ */
1117
1296
  static errorsText(errors, appJson) {
1118
1297
  if (Array.isArray(errors) === false) return null;
1119
1298
 
@@ -4,19 +4,33 @@
4
4
 
5
5
  const Ajv = require('ajv');
6
6
 
7
+ /** @typedef {import('../../assets/capability/schema').Capability} CapabilityDefinition */
8
+
9
+ /** @type {Record<string, CapabilityDefinition> | undefined} */
7
10
  let capabilitiesCache;
8
11
 
9
12
  class Capability {
10
13
 
14
+ /**
15
+ * @param {CapabilityDefinition} capability
16
+ */
11
17
  constructor(capability) {
12
18
  this._capability = capability;
13
19
  }
14
20
 
21
+ /**
22
+ * @param {...unknown} args
23
+ * @returns {void}
24
+ */
15
25
  debug(...args) {
16
26
  if (!this._debug) return;
17
27
  console.log('[dbg]', ...args);
18
28
  }
19
29
 
30
+ /**
31
+ * @param {{ debug?: boolean }} [options]
32
+ * @returns {Promise<void>}
33
+ */
20
34
  async validate({
21
35
  debug = false,
22
36
  } = {}) {
@@ -33,11 +47,17 @@ class Capability {
33
47
  this.debug('Validated successfully');
34
48
  }
35
49
 
50
+ /**
51
+ * @returns {Record<string, unknown>}
52
+ */
36
53
  static getJSONSchema() {
37
54
  // eslint-disable-next-line global-require
38
55
  return require('../../assets/capability/schema.json');
39
56
  }
40
57
 
58
+ /**
59
+ * @returns {Record<string, CapabilityDefinition>}
60
+ */
41
61
  static getCapabilities() {
42
62
  if (capabilitiesCache) return capabilitiesCache;
43
63
 
@@ -52,6 +72,10 @@ class Capability {
52
72
  return capabilitiesCache;
53
73
  }
54
74
 
75
+ /**
76
+ * @param {string} id
77
+ * @returns {CapabilityDefinition}
78
+ */
55
79
  static getCapability(id) {
56
80
  const capabilities = Capability.getCapabilities();
57
81
  const capability = capabilities[id];
@@ -61,11 +85,20 @@ class Capability {
61
85
  return capability;
62
86
  }
63
87
 
88
+ /**
89
+ * @param {string} id
90
+ * @returns {boolean}
91
+ */
64
92
  static hasCapability(id) {
65
93
  const capabilities = this.getCapabilities();
66
94
  return !!capabilities[id];
67
95
  }
68
96
 
97
+ /**
98
+ * @param {string} capabilityId
99
+ * @param {CapabilityDefinition} capability
100
+ * @returns {CapabilityDefinition}
101
+ */
69
102
  static _composeCapability(capabilityId, capability) {
70
103
  if (capability.flow) console.warn(`Warning: using \`capability.flow\` (${capabilityId}), expected a \`capability.$flow\``);
71
104
  if (capability.$flow) {
@@ -109,12 +142,21 @@ class Capability {
109
142
  return capability;
110
143
  }
111
144
 
145
+ /**
146
+ * @param {string} capabilityIdToCheck
147
+ * @param {string} capabilityId
148
+ * @returns {boolean}
149
+ */
112
150
  static isInstanceOfId(capabilityIdToCheck, capabilityId) {
113
151
  return (
114
152
  capabilityId === capabilityIdToCheck || capabilityIdToCheck.startsWith(`${capabilityId}.`)
115
153
  );
116
154
  }
117
155
 
156
+ /**
157
+ * @param {string} capabilityId
158
+ * @returns {string}
159
+ */
118
160
  static getBaseId(capabilityId) {
119
161
  const parts = capabilityId.split('.');
120
162
  return parts[0];
@@ -6,6 +6,9 @@ let classesCache;
6
6
 
7
7
  class Device {
8
8
 
9
+ /**
10
+ * @returns {Record<string, Record<string, unknown>>}
11
+ */
9
12
  static getClasses() {
10
13
  if (classesCache) return classesCache;
11
14
 
@@ -19,6 +22,10 @@ class Device {
19
22
  return classesCache;
20
23
  }
21
24
 
25
+ /**
26
+ * @param {string} id
27
+ * @returns {Record<string, unknown>}
28
+ */
22
29
  static getClass(id) {
23
30
  const deviceClasses = Device.getClasses();
24
31
  const deviceClass = deviceClasses[id];
@@ -29,6 +36,9 @@ class Device {
29
36
  }
30
37
 
31
38
  // legacy
39
+ /**
40
+ * @returns {ReturnType<typeof Capability.getCapabilities>}
41
+ */
32
42
  static getCapabilities() {
33
43
  return Capability.getCapabilities();
34
44
  }
@@ -2,11 +2,17 @@
2
2
 
3
3
  class Energy {
4
4
 
5
+ /**
6
+ * @returns {Record<string, Record<string, unknown>>}
7
+ */
5
8
  static getCurrencies() {
6
9
  // eslint-disable-next-line global-require
7
10
  return require('../../assets/energy/currencies.json');
8
11
  }
9
12
 
13
+ /**
14
+ * @returns {string[]}
15
+ */
10
16
  static getBatteries() {
11
17
  return [
12
18
  'LS14250',