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.
- package/.eslintignore +2 -1
- package/assets/app/schema.d.ts +546 -0
- package/assets/app/schema.json +136 -0
- package/assets/capability/schema.d.ts +53 -0
- package/helpers/index.js +21 -0
- package/index.js +48 -24
- package/lib/App/index.js +184 -5
- package/lib/Capability/index.js +42 -0
- package/lib/Device/index.js +10 -0
- package/lib/Energy/index.js +6 -0
- package/lib/Media/index.js +3 -0
- package/lib/Signal/index.js +25 -0
- package/lib/Signal/validators.js +21 -0
- package/lib/Util/file.js +84 -0
- package/lib/Util/index.js +72 -0
- package/lib/Util/zigbee.js +225 -0
- package/package.json +11 -3
- package/tsconfig.types.json +15 -0
- package/types/assets/app/schema.d.ts +546 -0
- package/types/assets/capability/schema.d.ts +53 -0
- package/types/helpers/index.d.ts +12 -0
- package/types/helpers/index.d.ts.map +1 -0
- package/types/index.d.ts +24 -0
- package/types/index.d.ts.map +1 -0
- package/types/lib/App/index.d.ts +140 -0
- package/types/lib/App/index.d.ts.map +1 -0
- package/types/lib/Capability/index.d.ts +61 -0
- package/types/lib/Capability/index.d.ts.map +1 -0
- package/types/lib/Device/index.d.ts +18 -0
- package/types/lib/Device/index.d.ts.map +1 -0
- package/types/lib/Energy/index.d.ts +12 -0
- package/types/lib/Energy/index.d.ts.map +1 -0
- package/types/lib/Media/index.d.ts +8 -0
- package/types/lib/Media/index.d.ts.map +1 -0
- package/types/lib/Signal/index.d.ts +52 -0
- package/types/lib/Signal/index.d.ts.map +1 -0
- package/types/lib/Signal/validators.d.ts +37 -0
- package/types/lib/Signal/validators.d.ts.map +1 -0
- package/types/lib/Util/file.d.ts +27 -0
- package/types/lib/Util/file.d.ts.map +1 -0
- package/types/lib/Util/index.d.ts +76 -0
- package/types/lib/Util/index.d.ts.map +1 -0
- package/types/lib/Util/zigbee.d.ts +88 -0
- package/types/lib/Util/zigbee.d.ts.map +1 -0
- package/webpack/index.js +1 -1
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
module.exports.
|
|
12
|
-
module.exports.
|
|
13
|
-
|
|
14
|
-
module.exports.
|
|
15
|
-
module.exports.
|
|
16
|
-
module.exports.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
module.exports.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
module.exports.
|
|
26
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
|
package/lib/Capability/index.js
CHANGED
|
@@ -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];
|
package/lib/Device/index.js
CHANGED
|
@@ -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
|
}
|
package/lib/Energy/index.js
CHANGED
|
@@ -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',
|