homey-lib 2.45.2 → 2.45.4
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 +507 -0
- package/assets/capability/schema.d.ts +53 -0
- package/helpers/index.js +11 -0
- package/index.js +47 -24
- package/lib/App/index.js +125 -7
- 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/index.js +10 -0
- package/package.json +11 -3
- package/tsconfig.types.json +15 -0
- package/types/assets/app/schema.d.ts +507 -0
- package/types/assets/capability/schema.d.ts +53 -0
- package/types/helpers/index.d.ts +11 -0
- package/types/helpers/index.d.ts.map +1 -0
- package/types/index.d.ts +23 -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/index.d.ts +23 -0
- package/types/lib/Util/index.d.ts.map +1 -0
- package/webpack/index.js +1 -1
package/lib/App/index.js
CHANGED
|
@@ -30,6 +30,8 @@ const {
|
|
|
30
30
|
dirname,
|
|
31
31
|
} = require('../../helpers');
|
|
32
32
|
|
|
33
|
+
/** @typedef {import('../../assets/app/schema').App} AppManifest */
|
|
34
|
+
|
|
33
35
|
const VALIDATION_LEVELS = [
|
|
34
36
|
'debug',
|
|
35
37
|
'publish',
|
|
@@ -76,6 +78,9 @@ class App {
|
|
|
76
78
|
static REQUIRED_PYTHON_PLATFORMS = ['arm64', 'amd64']
|
|
77
79
|
static SUPPORTED_PYTHON_VERSIONS = ['3.13', '3.14']
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} path
|
|
83
|
+
*/
|
|
79
84
|
constructor(path) {
|
|
80
85
|
this._path = path;
|
|
81
86
|
|
|
@@ -84,11 +89,19 @@ class App {
|
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
/**
|
|
93
|
+
* @param {...unknown} args
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
87
96
|
debug(...args) {
|
|
88
97
|
if (!this._debug) return;
|
|
89
98
|
console.log('[dbg]', ...args);
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
/**
|
|
102
|
+
* @param {{ level?: 'debug' | 'publish' | 'verified', debug?: boolean }} [options]
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*/
|
|
92
105
|
async validate({
|
|
93
106
|
level = 'debug',
|
|
94
107
|
debug = false,
|
|
@@ -104,8 +117,9 @@ class App {
|
|
|
104
117
|
const levelPublish = (level === 'publish' || level === 'verified');
|
|
105
118
|
const levelVerified = (level === 'verified');
|
|
106
119
|
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
const appJsonBuffer = await readFileAsync(join(this._path, 'app.json'));
|
|
121
|
+
/** @type {AppManifest} */
|
|
122
|
+
const appJson = JSON.parse(appJsonBuffer);
|
|
109
123
|
|
|
110
124
|
const schema = App.getJSONSchema();
|
|
111
125
|
|
|
@@ -216,9 +230,8 @@ class App {
|
|
|
216
230
|
// validate that Python apps target compatibility of at least >=13.0.0
|
|
217
231
|
if (appJson.runtime === 'python') {
|
|
218
232
|
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`);
|
|
233
|
+
if (!semver.gte(minVersion, '13.0.0')) {
|
|
234
|
+
throw new Error(`Invalid compatibility (${appJson.compatibility}), Python apps must have a compatibility of at least >=13.0.0`);
|
|
222
235
|
}
|
|
223
236
|
}
|
|
224
237
|
|
|
@@ -371,9 +384,32 @@ class App {
|
|
|
371
384
|
// validate `appJson.drivers[].capabilitiesOptions`
|
|
372
385
|
if (driver.capabilitiesOptions) {
|
|
373
386
|
for (const [capabilityId, capabilityOptions] of Object.entries(driver.capabilitiesOptions)) {
|
|
374
|
-
//
|
|
387
|
+
// Merge canonical + extra values for target_power_mode - canonical always present, extras appended
|
|
375
388
|
if (Capability.isInstanceOfId(capabilityId, 'target_power_mode')) {
|
|
376
|
-
|
|
389
|
+
const canonicalValues = Capability.getCapability('target_power_mode').values;
|
|
390
|
+
const providedValues = Array.isArray(capabilityOptions.values) ? capabilityOptions.values : [];
|
|
391
|
+
|
|
392
|
+
const seenIds = new Set(canonicalValues.map(canonical => canonical.id));
|
|
393
|
+
const extraValues = [];
|
|
394
|
+
|
|
395
|
+
for (const value of providedValues) {
|
|
396
|
+
if (!value || typeof value !== 'object' || typeof value.id !== 'string') continue;
|
|
397
|
+
if (seenIds.has(value.id)) continue;
|
|
398
|
+
seenIds.add(value.id);
|
|
399
|
+
extraValues.push(value);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const RESERVED_PREFIXES = ['homey_', 'device_'];
|
|
403
|
+
const reservedValues = extraValues.filter(value =>
|
|
404
|
+
RESERVED_PREFIXES.some(prefix => value.id.startsWith(prefix))
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
if (reservedValues.length) {
|
|
408
|
+
const ids = reservedValues.map(value => value.id).join(', ');
|
|
409
|
+
throw new Error(`drivers.${driver.id}.capabilitiesOptions.${capabilityId} custom values cannot use reserved prefixes "homey_" or "device_": ${ids}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
capabilityOptions.values = [...canonicalValues, ...extraValues];
|
|
377
413
|
}
|
|
378
414
|
|
|
379
415
|
// validate target_power exclude range must include 0
|
|
@@ -764,6 +800,10 @@ class App {
|
|
|
764
800
|
this.debug('Validated successfully');
|
|
765
801
|
}
|
|
766
802
|
|
|
803
|
+
/**
|
|
804
|
+
* @param {AppManifest} appJson
|
|
805
|
+
* @returns {void}
|
|
806
|
+
*/
|
|
767
807
|
static validatePythonVersion(appJson) {
|
|
768
808
|
// validate that Python apps specify a runtime version
|
|
769
809
|
if (appJson.runtime === 'python' && !App.SUPPORTED_PYTHON_VERSIONS.includes(appJson.pythonVersion)) {
|
|
@@ -771,6 +811,11 @@ class App {
|
|
|
771
811
|
}
|
|
772
812
|
}
|
|
773
813
|
|
|
814
|
+
/**
|
|
815
|
+
* @param {Record<string, unknown>} setting
|
|
816
|
+
* @param {{ id: string }} driver
|
|
817
|
+
* @returns {void}
|
|
818
|
+
*/
|
|
774
819
|
_checkPrivateSettingPrefixUse(setting, driver) {
|
|
775
820
|
if (
|
|
776
821
|
setting.type &&
|
|
@@ -795,6 +840,11 @@ class App {
|
|
|
795
840
|
}
|
|
796
841
|
}
|
|
797
842
|
|
|
843
|
+
/**
|
|
844
|
+
* @param {Record<string, unknown> | null | undefined} driver
|
|
845
|
+
* @param {Record<string, unknown> | null | undefined} setting
|
|
846
|
+
* @returns {void}
|
|
847
|
+
*/
|
|
798
848
|
_checkZwaveForSetting(driver, setting) {
|
|
799
849
|
if (!driver || !setting || !setting.zwave) return;
|
|
800
850
|
if (typeof setting.zwave.index !== 'number' || typeof setting.zwave.size !== 'number') throw new Error(`Missing property in "zwave" at ${driver.id}, ${setting.id}`);
|
|
@@ -818,6 +868,10 @@ class App {
|
|
|
818
868
|
}
|
|
819
869
|
}
|
|
820
870
|
|
|
871
|
+
/**
|
|
872
|
+
* @param {string} filepath
|
|
873
|
+
* @returns {Promise<string[]>}
|
|
874
|
+
*/
|
|
821
875
|
async _getDirectoryContents(filepath) {
|
|
822
876
|
await this._fileExistsCaseSensitive(filepath);
|
|
823
877
|
|
|
@@ -830,6 +884,10 @@ class App {
|
|
|
830
884
|
});
|
|
831
885
|
}
|
|
832
886
|
|
|
887
|
+
/**
|
|
888
|
+
* @param {string} filepath
|
|
889
|
+
* @returns {Promise<void>}
|
|
890
|
+
*/
|
|
833
891
|
async _ensureFileExistsCaseSensitive(filepath) {
|
|
834
892
|
const exists = await this._fileExistsCaseSensitive(filepath);
|
|
835
893
|
if (exists !== true) {
|
|
@@ -837,6 +895,10 @@ class App {
|
|
|
837
895
|
}
|
|
838
896
|
}
|
|
839
897
|
|
|
898
|
+
/**
|
|
899
|
+
* @param {string} filepath
|
|
900
|
+
* @returns {Promise<boolean>}
|
|
901
|
+
*/
|
|
840
902
|
async _fileExistsCaseSensitive(filepath) {
|
|
841
903
|
filepath = join(this._path, filepath);
|
|
842
904
|
const dir = dirname(filepath);
|
|
@@ -852,6 +914,12 @@ class App {
|
|
|
852
914
|
}
|
|
853
915
|
}
|
|
854
916
|
|
|
917
|
+
/**
|
|
918
|
+
* @param {{ small: string, large: string }} imagesObj
|
|
919
|
+
* @param {'app' | 'driver'} type
|
|
920
|
+
* @param {string} [errorPath]
|
|
921
|
+
* @returns {Promise<void>}
|
|
922
|
+
*/
|
|
855
923
|
async _validateImages(imagesObj, type, errorPath) {
|
|
856
924
|
const sizes = ['small', 'large'];
|
|
857
925
|
for (let i = 0; i < sizes.length; i++) {
|
|
@@ -881,6 +949,9 @@ class App {
|
|
|
881
949
|
}
|
|
882
950
|
}
|
|
883
951
|
|
|
952
|
+
/**
|
|
953
|
+
* @returns {Promise<void>}
|
|
954
|
+
*/
|
|
884
955
|
async _validateModules() {
|
|
885
956
|
const nodeModulesPath = join(this._path, 'node_modules');
|
|
886
957
|
|
|
@@ -904,6 +975,13 @@ class App {
|
|
|
904
975
|
}
|
|
905
976
|
}
|
|
906
977
|
|
|
978
|
+
/**
|
|
979
|
+
* @param {Record<string, unknown>} card
|
|
980
|
+
* @param {string} errorPath
|
|
981
|
+
* @param {AppManifest} appJson
|
|
982
|
+
* @param {{ levelPublish: boolean, levelVerified: boolean }} options
|
|
983
|
+
* @returns {void}
|
|
984
|
+
*/
|
|
907
985
|
_validateFlowCard(card, errorPath, appJson, { levelPublish, levelVerified }) {
|
|
908
986
|
if (Array.isArray(card.tokens)) {
|
|
909
987
|
for (const token of card.tokens) {
|
|
@@ -983,6 +1061,12 @@ class App {
|
|
|
983
1061
|
}
|
|
984
1062
|
}
|
|
985
1063
|
|
|
1064
|
+
/**
|
|
1065
|
+
* @param {string} titleFormatted
|
|
1066
|
+
* @param {{ name: string }[]} args
|
|
1067
|
+
* @param {string} errorPath
|
|
1068
|
+
* @returns {void}
|
|
1069
|
+
*/
|
|
986
1070
|
static _checkTitleFormatted(titleFormatted, args, errorPath) {
|
|
987
1071
|
const argsPresent = args.reduce((obj, arg) => {
|
|
988
1072
|
obj[arg.name] = false;
|
|
@@ -1017,6 +1101,11 @@ class App {
|
|
|
1017
1101
|
}
|
|
1018
1102
|
}
|
|
1019
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* @param {string} filepath
|
|
1106
|
+
* @param {number} numBytes
|
|
1107
|
+
* @returns {Promise<Buffer>}
|
|
1108
|
+
*/
|
|
1020
1109
|
async _readBytes(filepath, numBytes) {
|
|
1021
1110
|
filepath = join(this._path, filepath);
|
|
1022
1111
|
|
|
@@ -1027,6 +1116,10 @@ class App {
|
|
|
1027
1116
|
return buffer;
|
|
1028
1117
|
}
|
|
1029
1118
|
|
|
1119
|
+
/**
|
|
1120
|
+
* @param {string} appId
|
|
1121
|
+
* @returns {boolean}
|
|
1122
|
+
*/
|
|
1030
1123
|
static isValidId(appId) {
|
|
1031
1124
|
if (typeof appId !== 'string') return false;
|
|
1032
1125
|
if (appId.length < 1) return false;
|
|
@@ -1035,16 +1128,26 @@ class App {
|
|
|
1035
1128
|
return true;
|
|
1036
1129
|
}
|
|
1037
1130
|
|
|
1131
|
+
/**
|
|
1132
|
+
* @param {string} color
|
|
1133
|
+
* @returns {boolean}
|
|
1134
|
+
*/
|
|
1038
1135
|
static isValidBrandColor(color) {
|
|
1039
1136
|
return tinycolor(color).getBrightness() <= 184; // empirically determined by many colorpicker samples
|
|
1040
1137
|
}
|
|
1041
1138
|
|
|
1139
|
+
/**
|
|
1140
|
+
* @returns {Record<string, unknown>}
|
|
1141
|
+
*/
|
|
1042
1142
|
static getJSONSchema() {
|
|
1043
1143
|
// eslint-disable-next-line global-require
|
|
1044
1144
|
const schema = require('../../assets/app/schema.json');
|
|
1045
1145
|
return JSON.parse(JSON.stringify(schema));
|
|
1046
1146
|
}
|
|
1047
1147
|
|
|
1148
|
+
/**
|
|
1149
|
+
* @returns {Record<string, Record<string, unknown>>}
|
|
1150
|
+
*/
|
|
1048
1151
|
static getPermissions() {
|
|
1049
1152
|
// eslint-disable-next-line global-require
|
|
1050
1153
|
const permissions = require('../../assets/app/permissions.json');
|
|
@@ -1059,15 +1162,25 @@ class App {
|
|
|
1059
1162
|
return permissions;
|
|
1060
1163
|
}
|
|
1061
1164
|
|
|
1165
|
+
/**
|
|
1166
|
+
* @returns {string[]}
|
|
1167
|
+
*/
|
|
1062
1168
|
static getCategories() {
|
|
1063
1169
|
return ['lights', 'video', 'music', 'appliances', 'security', 'climate', 'tools', 'internet', 'localization', 'energy'];
|
|
1064
1170
|
}
|
|
1065
1171
|
|
|
1172
|
+
/**
|
|
1173
|
+
* @returns {string[]}
|
|
1174
|
+
*/
|
|
1066
1175
|
static getLocales() {
|
|
1067
1176
|
// eslint-disable-next-line max-len
|
|
1068
1177
|
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'];
|
|
1069
1178
|
}
|
|
1070
1179
|
|
|
1180
|
+
/**
|
|
1181
|
+
* @param {string} appId
|
|
1182
|
+
* @returns {string}
|
|
1183
|
+
*/
|
|
1071
1184
|
static getBrandColor(appId) {
|
|
1072
1185
|
const appIdHex = Buffer.from(appId).toString('hex');
|
|
1073
1186
|
let brandColor;
|
|
@@ -1091,6 +1204,11 @@ class App {
|
|
|
1091
1204
|
return brandColor;
|
|
1092
1205
|
}
|
|
1093
1206
|
|
|
1207
|
+
/**
|
|
1208
|
+
* @param {Array<Record<string, unknown>> | undefined} errors
|
|
1209
|
+
* @param {AppManifest} appJson
|
|
1210
|
+
* @returns {string | null}
|
|
1211
|
+
*/
|
|
1094
1212
|
static errorsText(errors, appJson) {
|
|
1095
1213
|
if (Array.isArray(errors) === false) return null;
|
|
1096
1214
|
|
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',
|
package/lib/Media/index.js
CHANGED
package/lib/Signal/index.js
CHANGED
|
@@ -11,8 +11,16 @@ const {
|
|
|
11
11
|
prontoValidator,
|
|
12
12
|
} = require('./validators');
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {'433' | '868' | 'ir'} SignalFrequency
|
|
16
|
+
*/
|
|
17
|
+
|
|
14
18
|
class Signal {
|
|
15
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {Record<string, unknown>} signal
|
|
22
|
+
* @param {{ frequency?: SignalFrequency }} [options]
|
|
23
|
+
*/
|
|
16
24
|
constructor(signal, { frequency = undefined } = {}) {
|
|
17
25
|
this._signal = signal;
|
|
18
26
|
this._frequency = frequency;
|
|
@@ -20,6 +28,10 @@ class Signal {
|
|
|
20
28
|
this._check = this._check.bind(this);
|
|
21
29
|
}
|
|
22
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @param {...unknown} args
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
23
35
|
debug(...args) {
|
|
24
36
|
if (!this._debug) return;
|
|
25
37
|
|
|
@@ -27,12 +39,21 @@ class Signal {
|
|
|
27
39
|
console.log('[dbg]', ...args);
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} message
|
|
44
|
+
* @param {boolean} result
|
|
45
|
+
* @returns {void}
|
|
46
|
+
*/
|
|
30
47
|
_check(message, result) {
|
|
31
48
|
if (result !== true) {
|
|
32
49
|
throw new Error(message);
|
|
33
50
|
}
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @param {{ debug?: boolean }} [options]
|
|
55
|
+
* @returns {Promise<void>}
|
|
56
|
+
*/
|
|
36
57
|
async validate({
|
|
37
58
|
debug = false,
|
|
38
59
|
} = {}) {
|
|
@@ -65,6 +86,10 @@ class Signal {
|
|
|
65
86
|
this.debug('Validated successfully');
|
|
66
87
|
}
|
|
67
88
|
|
|
89
|
+
/**
|
|
90
|
+
* @param {Record<string, (value: unknown, signal: Record<string, unknown>) => { result: boolean, msg: string }>} validatorEngine
|
|
91
|
+
* @returns {void}
|
|
92
|
+
*/
|
|
68
93
|
_validateWithEngine(validatorEngine) {
|
|
69
94
|
return validate(validatorEngine, this._check, this._signal);
|
|
70
95
|
}
|
package/lib/Signal/validators.js
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Record<string, unknown>} SignalDefinition
|
|
7
|
+
* @typedef {{ result: boolean, msg: string }} ValidationResult
|
|
8
|
+
* @typedef {(value: unknown, signal: SignalDefinition) => ValidationResult} Validator
|
|
9
|
+
* @typedef {Record<string, Validator>} ValidatorEngine
|
|
10
|
+
* @typedef {(message: string, result: boolean) => void} CheckFn
|
|
11
|
+
*/
|
|
12
|
+
|
|
5
13
|
/* static functions */
|
|
14
|
+
/**
|
|
15
|
+
* @param {ValidatorEngine} validator
|
|
16
|
+
* @param {CheckFn} check
|
|
17
|
+
* @param {SignalDefinition} signal
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
6
20
|
function validate(validator, check, signal) {
|
|
7
21
|
for (const propName in signal) {
|
|
8
22
|
const property = signal[propName];
|
|
@@ -70,6 +84,7 @@ function _validateGenericData(data, signal) {
|
|
|
70
84
|
return res;
|
|
71
85
|
}
|
|
72
86
|
|
|
87
|
+
/** @type {ValidatorEngine} */
|
|
73
88
|
const genericValidator = {
|
|
74
89
|
words(words, signal) {
|
|
75
90
|
const res = { result: true, msg: 'invalid_words' };
|
|
@@ -166,6 +181,7 @@ const rfBounds = {
|
|
|
166
181
|
repetitions: { min: 1, max: 255 },
|
|
167
182
|
};
|
|
168
183
|
|
|
184
|
+
/** @type {ValidatorEngine} */
|
|
169
185
|
const rfValidator = {
|
|
170
186
|
|
|
171
187
|
words(words, signal) {
|
|
@@ -222,6 +238,7 @@ const modulationBounds = {
|
|
|
222
238
|
channelDeviation: { min: 5000, max: 50000 },
|
|
223
239
|
};
|
|
224
240
|
|
|
241
|
+
/** @type {ValidatorEngine} */
|
|
225
242
|
const modulationValidator = {
|
|
226
243
|
modulation(modulation, signal) {
|
|
227
244
|
// replace by default modulation props in homey-microcontroller?
|
|
@@ -256,6 +273,7 @@ const modulationValidator = {
|
|
|
256
273
|
},
|
|
257
274
|
};
|
|
258
275
|
|
|
276
|
+
/** @type {ValidatorEngine} */
|
|
259
277
|
const prontoValidator = {
|
|
260
278
|
cmds(cmds, signal) {
|
|
261
279
|
const res = !Object.keys(cmds).some(cmd => {
|
|
@@ -279,6 +297,7 @@ const rf433Bounds = {
|
|
|
279
297
|
carrier: { min: 433000000, max: 433990000 },
|
|
280
298
|
};
|
|
281
299
|
|
|
300
|
+
/** @type {ValidatorEngine} */
|
|
282
301
|
const rf433Validator = {
|
|
283
302
|
carrier(carrier, signal) {
|
|
284
303
|
const res = _valid_bounds(carrier, 433000000, 433990000);
|
|
@@ -290,6 +309,7 @@ const rf868Bounds = {
|
|
|
290
309
|
carrier: { min: 868000000, max: 868900000 },
|
|
291
310
|
};
|
|
292
311
|
|
|
312
|
+
/** @type {ValidatorEngine} */
|
|
293
313
|
const rf868Validator = {
|
|
294
314
|
carrier(carrier, signal) {
|
|
295
315
|
const res = _valid_bounds(carrier, rf868Bounds.carrier.min, rf868Bounds.carrier.max);
|
|
@@ -302,6 +322,7 @@ const irBounds = {
|
|
|
302
322
|
dutyCycle: { min: 30, max: 70 },
|
|
303
323
|
};
|
|
304
324
|
|
|
325
|
+
/** @type {ValidatorEngine} */
|
|
305
326
|
const irValidator = {
|
|
306
327
|
carrier(carrier, signal) {
|
|
307
328
|
const res = _valid_bounds(carrier, irBounds.carrier.min, irBounds.carrier.max);
|
package/lib/Util/index.js
CHANGED
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
class Util {
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} modelId
|
|
7
|
+
* @returns {string[]}
|
|
8
|
+
*/
|
|
5
9
|
static getPlatformLocalFeatures(modelId) {
|
|
6
10
|
return Util._platformLocalFeatures[modelId] || [];
|
|
7
11
|
}
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} modelId
|
|
15
|
+
* @param {string[]} wantedFeatures
|
|
16
|
+
* @returns {string[]}
|
|
17
|
+
*/
|
|
9
18
|
static getMissingPlatformLocalFeatures(modelId, wantedFeatures) {
|
|
10
19
|
const features = Util.getPlatformLocalFeatures(modelId);
|
|
11
20
|
return wantedFeatures.filter(feature => !features.includes(feature));
|
|
@@ -18,6 +27,7 @@ Util.FEATURE_LED_RING = 'ledring';
|
|
|
18
27
|
Util.FEATURE_NFC = 'nfc';
|
|
19
28
|
Util.FEATURE_MATTER = 'matter';
|
|
20
29
|
Util.FEATURE_CAMERA_STREAMING = 'camera-streaming';
|
|
30
|
+
/** @type {Record<string, string[]>} */
|
|
21
31
|
Util._platformLocalFeatures = {
|
|
22
32
|
homey1s: [Util.FEATURE_SPEAKER, Util.FEATURE_LED_RING, Util.FEATURE_NFC],
|
|
23
33
|
homey1d: [Util.FEATURE_SPEAKER, Util.FEATURE_LED_RING, Util.FEATURE_NFC],
|
package/package.json
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homey-lib",
|
|
3
|
-
"version": "2.45.
|
|
3
|
+
"version": "2.45.4",
|
|
4
4
|
"description": "Shared Library for Homey",
|
|
5
|
+
"types": "types/index.d.ts",
|
|
5
6
|
"main": "index.js",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"build": "npm ci; npm run pack",
|
|
8
|
+
"build": "npm ci; npm run types:generate; npm run schema:generate; npm run schema:copy; npm run pack",
|
|
8
9
|
"pack": "webpack",
|
|
9
10
|
"lint": "eslint .",
|
|
10
11
|
"test": "mocha --parallel",
|
|
11
12
|
"locales:apply": "node scripts/apply-locale-files.js",
|
|
12
|
-
"locales:generate": "node scripts/generate-locale-files.js"
|
|
13
|
+
"locales:generate": "node scripts/generate-locale-files.js",
|
|
14
|
+
"types:generate": "tsc --project tsconfig.types.json",
|
|
15
|
+
"schema:generate": "npm run capability-schema:generate && npm run app-schema:generate",
|
|
16
|
+
"schema:copy": "mkdir -p types/assets/app types/assets/capability && cp assets/app/schema.d.ts types/assets/app/schema.d.ts && cp assets/capability/schema.d.ts types/assets/capability/schema.d.ts",
|
|
17
|
+
"capability-schema:generate": "json2ts -i assets/capability/schema.json -o assets/capability/schema.d.ts --unknownAny",
|
|
18
|
+
"app-schema:generate": "json2ts -i assets/app/schema.json -o assets/app/schema.d.ts --unknownAny"
|
|
13
19
|
},
|
|
14
20
|
"engines": {
|
|
15
21
|
"node": ">=12.13.0"
|
|
@@ -34,10 +40,12 @@
|
|
|
34
40
|
"devDependencies": {
|
|
35
41
|
"eslint": "^7.31.0",
|
|
36
42
|
"eslint-config-athom": "^2.1.1",
|
|
43
|
+
"json-schema-to-typescript": "^15.0.4",
|
|
37
44
|
"mocha": "^9.1.3",
|
|
38
45
|
"mock-fs": "^5.1.2",
|
|
39
46
|
"object-path": "^0.11.8",
|
|
40
47
|
"set-value": "^4.1.0",
|
|
48
|
+
"typescript": "^5.9.3",
|
|
41
49
|
"webpack": "^5.98.0",
|
|
42
50
|
"webpack-cli": "^5.1.4"
|
|
43
51
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowJs": true,
|
|
4
|
+
"checkJs": false,
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"emitDeclarationOnly": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"outDir": "./types",
|
|
9
|
+
"module": "commonjs",
|
|
10
|
+
"target": "ES2020",
|
|
11
|
+
"strict": false
|
|
12
|
+
},
|
|
13
|
+
"include": ["index.js", "lib/**/*.js"],
|
|
14
|
+
"exclude": ["test/**", "node_modules/**"]
|
|
15
|
+
}
|