@smarthome.exchange/integrations 1.0.0 → 2.0.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/changelog.md +49 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/classes.configstore.d.ts +2 -0
- package/dist_ts/core/classes.configstore.js +84 -27
- package/dist_ts/core/classes.nativehomeadmission.d.ts +19 -0
- package/dist_ts/core/classes.nativehomeadmission.js +84 -0
- package/dist_ts/core/classes.nativeintegrationowner.d.ts +79 -0
- package/dist_ts/core/classes.nativeintegrationowner.js +269 -0
- package/dist_ts/core/index.d.ts +2 -0
- package/dist_ts/core/index.js +3 -1
- package/dist_ts/core/types.d.ts +14 -0
- package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
- package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
- package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
- package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
- package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
- package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
- package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
- package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
- package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
- package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
- package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
- package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
- package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
- package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
- package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
- package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
- package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
- package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
- package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
- package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
- package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
- package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
- package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
- package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
- package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
- package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
- package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
- package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
- package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
- package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
- package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
- package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
- package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
- package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
- package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
- package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
- package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
- package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
- package/dist_ts/integrations/daikin/daikin.types.js +1 -1
- package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
- package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
- package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
- package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
- package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
- package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
- package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
- package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
- package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
- package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
- package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
- package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
- package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
- package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
- package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
- package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
- package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
- package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
- package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
- package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
- package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
- package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
- package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
- package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
- package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
- package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
- package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
- package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
- package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
- package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
- package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
- package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
- package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
- package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
- package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
- package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
- package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
- package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
- package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
- package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
- package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
- package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
- package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
- package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
- package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
- package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
- package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
- package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
- package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
- package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
- package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
- package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
- package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
- package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
- package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
- package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
- package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
- package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
- package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
- package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
- package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
- package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
- package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
- package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
- package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
- package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
- package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
- package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
- package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
- package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
- package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
- package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
- package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
- package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
- package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
- package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
- package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
- package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
- package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
- package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
- package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
- package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
- package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
- package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
- package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
- package/dist_ts/integrations/wiz/index.d.ts +1 -0
- package/dist_ts/integrations/wiz/index.js +2 -1
- package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
- package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
- package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
- package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
- package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
- package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
- package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
- package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
- package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
- package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
- package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
- package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
- package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
- package/dist_ts/integrations/yamaha/index.d.ts +1 -0
- package/dist_ts/integrations/yamaha/index.js +2 -1
- package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
- package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
- package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
- package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
- package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
- package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
- package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
- package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
- package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
- package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
- package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
- package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
- package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
- package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
- package/package.json +3 -3
- package/readme.md +514 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/classes.configstore.ts +84 -27
- package/ts/core/classes.nativehomeadmission.ts +96 -0
- package/ts/core/classes.nativeintegrationowner.ts +297 -0
- package/ts/core/index.ts +2 -0
- package/ts/core/types.ts +16 -0
- package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
- package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
- package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
- package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
- package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
- package/ts/integrations/braviatv/braviatv.types.ts +53 -173
- package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
- package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
- package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
- package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
- package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
- package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
- package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
- package/ts/integrations/broadlink/broadlink.types.ts +48 -8
- package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
- package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
- package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
- package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
- package/ts/integrations/daikin/daikin.discovery.ts +94 -346
- package/ts/integrations/daikin/daikin.mapper.ts +83 -166
- package/ts/integrations/daikin/daikin.types.ts +27 -48
- package/ts/integrations/daikin/daikin.validation.ts +51 -0
- package/ts/integrations/daikin/readme.md +83 -0
- package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
- package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
- package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
- package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
- package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
- package/ts/integrations/denonavr/denonavr.types.ts +48 -111
- package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
- package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
- package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
- package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
- package/ts/integrations/kodi/kodi.discovery.ts +49 -28
- package/ts/integrations/kodi/kodi.mapper.ts +52 -10
- package/ts/integrations/kodi/kodi.types.ts +33 -58
- package/ts/integrations/kodi/kodi.validation.ts +60 -0
- package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
- package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
- package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
- package/ts/integrations/modbus/modbus.discovery.ts +38 -106
- package/ts/integrations/modbus/modbus.mapper.ts +64 -467
- package/ts/integrations/modbus/modbus.types.ts +64 -381
- package/ts/integrations/modbus/modbus.validation.ts +119 -0
- package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
- package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
- package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
- package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
- package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
- package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
- package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
- package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
- package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
- package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
- package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
- package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
- package/ts/integrations/onkyo/onkyo.types.ts +55 -110
- package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
- package/ts/integrations/onkyo/readme.md +126 -0
- package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
- package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
- package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
- package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
- package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
- package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
- package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
- package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
- package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
- package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
- package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
- package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
- package/ts/integrations/pioneer/pioneer.types.ts +87 -85
- package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
- package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
- package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
- package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
- package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
- package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
- package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
- package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
- package/ts/integrations/wiz/index.ts +1 -0
- package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
- package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
- package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
- package/ts/integrations/wiz/wiz.discovery.ts +48 -291
- package/ts/integrations/wiz/wiz.mapper.ts +81 -754
- package/ts/integrations/wiz/wiz.types.ts +24 -224
- package/ts/integrations/wiz/wiz.validation.ts +73 -0
- package/ts/integrations/yamaha/index.ts +1 -0
- package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
- package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
- package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
- package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
- package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
- package/ts/integrations/yamaha/yamaha.types.ts +77 -97
- package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
|
@@ -1,280 +1,146 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
INanoleafEffectsCommand,
|
|
7
|
-
INanoleafPanelLayout,
|
|
8
|
-
INanoleafRhythmInfo,
|
|
9
|
-
INanoleafSnapshot,
|
|
10
|
-
INanoleafState,
|
|
11
|
-
INanoleafStateCommand,
|
|
12
|
-
} from './nanoleaf.types.js';
|
|
13
|
-
|
|
14
|
-
const DEFAULT_NANOLEAF_PORT = 16021;
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { NoSqlConfigStore } from '../../core/classes.configstore.js';
|
|
3
|
+
import { createNanoleafCredentialStore, nanoleafCredentialKey, readNanoleafCredential } from './nanoleaf.credentials.js';
|
|
4
|
+
import type { INanoleafConfig, INanoleafIntegrationOptions, INanoleafSnapshot, INanoleafWriteResult } from './nanoleaf.types.js';
|
|
5
|
+
import { normalizeNanoleafConfig } from './nanoleaf.validation.js';
|
|
15
6
|
|
|
16
7
|
export class NanoleafClient {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
return this.requestJson<INanoleafState>(this.authenticatedPath('/state'));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public async getEffects(): Promise<INanoleafEffects> {
|
|
56
|
-
if (this.config.snapshot || this.config.effects || this.config.controllerInfo?.effects || !this.canRequest()) {
|
|
57
|
-
return (await this.getSnapshot()).effects;
|
|
58
|
-
}
|
|
59
|
-
return this.requestJson<INanoleafEffects>(this.authenticatedPath('/effects'));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public async getPanelLayout(): Promise<INanoleafPanelLayout | undefined> {
|
|
63
|
-
return (await this.getSnapshot()).panelLayout;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public async getRhythm(): Promise<INanoleafRhythmInfo | undefined> {
|
|
67
|
-
return (await this.getSnapshot()).rhythm;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public async turnOn(): Promise<void> {
|
|
71
|
-
await this.setState({ on: { value: true } });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public async turnOff(transitionArg?: number): Promise<void> {
|
|
75
|
-
if (transitionArg === undefined) {
|
|
76
|
-
await this.setState({ on: { value: false } });
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
await this.setState({ on: { value: false }, brightness: { value: 0, duration: transitionArg } });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
public async setBrightness(percentArg: number, transitionArg?: number): Promise<void> {
|
|
83
|
-
await this.setState({
|
|
84
|
-
brightness: {
|
|
85
|
-
value: this.clamp(Math.round(percentArg), 0, 100),
|
|
86
|
-
duration: transitionArg,
|
|
8
|
+
readonly #config: Readonly<INanoleafConfig>;
|
|
9
|
+
readonly #protocol: plugins.ecobridgeDeviceManager.NanoleafProtocol;
|
|
10
|
+
readonly #ownsStore: boolean;
|
|
11
|
+
readonly #lifetime = new AbortController();
|
|
12
|
+
readonly #operations = new Set<Promise<unknown>>();
|
|
13
|
+
#store?: NoSqlConfigStore;
|
|
14
|
+
#healthy = false;
|
|
15
|
+
#closed = false;
|
|
16
|
+
#closePromise?: Promise<void>;
|
|
17
|
+
#retryPromise?: Promise<void>;
|
|
18
|
+
#protocolClosed = false;
|
|
19
|
+
#removeParentAbort: () => void = () => undefined;
|
|
20
|
+
|
|
21
|
+
constructor(configArg: INanoleafConfig, ownerArg?: INanoleafIntegrationOptions | NoSqlConfigStore, signalArg?: AbortSignal, homeIdArg?: string) {
|
|
22
|
+
this.#config = normalizeNanoleafConfig(configArg);
|
|
23
|
+
if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) throw new TypeError('Nanoleaf lifetime signal must be an AbortSignal.');
|
|
24
|
+
if (homeIdArg === undefined) throw new TypeError('Nanoleaf client requires the admitted host home.');
|
|
25
|
+
const store = createNanoleafCredentialStore(this.#config, homeIdArg, ownerArg);
|
|
26
|
+
this.#store = store.store;
|
|
27
|
+
this.#ownsStore = store.owned;
|
|
28
|
+
this.#protocol = new plugins.ecobridgeDeviceManager.NanoleafProtocol({
|
|
29
|
+
origin: this.#config.origin, expectedSerialNumber: this.#config.expectedSerialNumber,
|
|
30
|
+
expectedModel: this.#config.expectedModel, allowUnencryptedHttp: this.#config.allowUnencryptedHttp,
|
|
31
|
+
ca: this.#config.certificateAuthority, timeoutMs: this.#config.timeoutMs,
|
|
32
|
+
maxResponseBytes: this.#config.maxResponseBytes, maxPendingOperations: this.#config.maxPendingOperations,
|
|
33
|
+
getAuthToken: async (signal) => {
|
|
34
|
+
signal.throwIfAborted();
|
|
35
|
+
const currentStore = this.#store;
|
|
36
|
+
if (!currentStore) throw new Error('Nanoleaf client is destroyed.');
|
|
37
|
+
await currentStore.initialize({ signal });
|
|
38
|
+
signal.throwIfAborted();
|
|
39
|
+
const credential: unknown = await currentStore.get(nanoleafCredentialKey(this.#config));
|
|
40
|
+
signal.throwIfAborted();
|
|
41
|
+
return readNanoleafCredential(this.#config, credential);
|
|
87
42
|
},
|
|
88
43
|
});
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
public async setSaturation(saturationArg: number): Promise<void> {
|
|
96
|
-
await this.setState({ sat: { value: this.clamp(Math.round(saturationArg), 0, 100) } });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
public async setColorTemperature(kelvinArg: number): Promise<void> {
|
|
100
|
-
await this.setState({ ct: { value: Math.round(kelvinArg) } });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public async setState(stateArg: INanoleafStateCommand): Promise<void> {
|
|
104
|
-
this.applyStatePatch(stateArg);
|
|
105
|
-
if (this.canRequest()) {
|
|
106
|
-
await this.requestJson<void>(this.authenticatedPath('/state'), {
|
|
107
|
-
method: 'PUT',
|
|
108
|
-
body: stateArg,
|
|
109
|
-
});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
this.assertFixtureMode();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
public async setEffect(effectArg: string): Promise<void> {
|
|
116
|
-
this.applyEffectsPatch({ select: effectArg });
|
|
117
|
-
if (this.canRequest()) {
|
|
118
|
-
await this.requestJson<void>(this.authenticatedPath('/effects'), {
|
|
119
|
-
method: 'PUT',
|
|
120
|
-
body: { select: effectArg },
|
|
121
|
-
});
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
this.assertFixtureMode();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
public async writeEffectsCommand(commandArg: INanoleafEffectsCommand): Promise<void> {
|
|
128
|
-
if (this.canRequest()) {
|
|
129
|
-
await this.requestJson<void>(this.authenticatedPath('/effects'), {
|
|
130
|
-
method: 'PUT',
|
|
131
|
-
body: { write: commandArg },
|
|
132
|
-
});
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
this.assertFixtureMode();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public async identify(): Promise<void> {
|
|
139
|
-
if (this.canRequest()) {
|
|
140
|
-
await this.requestJson<void>(this.authenticatedPath('/identify'), { method: 'PUT' });
|
|
141
|
-
return;
|
|
44
|
+
if (signalArg) {
|
|
45
|
+
const aborted = (): void => { void this.destroy(); };
|
|
46
|
+
signalArg.addEventListener('abort', aborted, { once: true });
|
|
47
|
+
this.#removeParentAbort = () => signalArg.removeEventListener('abort', aborted);
|
|
48
|
+
if (signalArg.aborted) aborted();
|
|
142
49
|
}
|
|
143
|
-
this.assertFixtureMode();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
public async createAuthToken(): Promise<INanoleafAuthTokenResult> {
|
|
147
|
-
return {
|
|
148
|
-
success: false,
|
|
149
|
-
error: 'Nanoleaf pairing/token generation is not implemented. Put the controller in pairing mode and provide an existing authToken.',
|
|
150
|
-
};
|
|
151
50
|
}
|
|
152
51
|
|
|
153
|
-
public
|
|
52
|
+
public isHealthy(): boolean { return this.#healthy && !this.#closed; }
|
|
154
53
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const state = snapshotArg.state ?? controllerInfo.state ?? {};
|
|
158
|
-
const effects = snapshotArg.effects ?? controllerInfo.effects ?? {};
|
|
159
|
-
const panelLayout = snapshotArg.panelLayout ?? controllerInfo.panelLayout;
|
|
160
|
-
const rhythm = snapshotArg.rhythm ?? controllerInfo.rhythm;
|
|
161
|
-
controllerInfo.state = state;
|
|
162
|
-
controllerInfo.effects = effects;
|
|
163
|
-
controllerInfo.panelLayout = panelLayout;
|
|
164
|
-
controllerInfo.rhythm = rhythm;
|
|
165
|
-
return { controllerInfo, state, effects, panelLayout, rhythm };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
private applyStatePatch(stateArg: INanoleafStateCommand): void {
|
|
169
|
-
const state = this.ensureMutableState();
|
|
170
|
-
if (stateArg.on) {
|
|
171
|
-
state.on = { ...state.on, ...stateArg.on };
|
|
172
|
-
}
|
|
173
|
-
if (stateArg.brightness) {
|
|
174
|
-
state.brightness = { ...state.brightness, ...stateArg.brightness };
|
|
175
|
-
}
|
|
176
|
-
if (stateArg.hue) {
|
|
177
|
-
state.hue = { ...state.hue, ...stateArg.hue };
|
|
178
|
-
}
|
|
179
|
-
if (stateArg.sat) {
|
|
180
|
-
state.sat = { ...state.sat, ...stateArg.sat };
|
|
181
|
-
}
|
|
182
|
-
if (stateArg.ct) {
|
|
183
|
-
state.ct = { ...state.ct, ...stateArg.ct };
|
|
184
|
-
}
|
|
54
|
+
public getSnapshot(signalArg?: AbortSignal): Promise<INanoleafSnapshot> {
|
|
55
|
+
return this.#run(async (signal) => this.#snapshot(await this.#protocol.readSnapshot({ signal })), signalArg);
|
|
185
56
|
}
|
|
186
57
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
Object.assign(effects, effectsArg);
|
|
58
|
+
public setPower(powerArg: boolean, signalArg?: AbortSignal): Promise<INanoleafWriteResult> {
|
|
59
|
+
return this.#setState({ power: powerArg }, signalArg);
|
|
190
60
|
}
|
|
191
61
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.config.snapshot.state ||= {};
|
|
195
|
-
this.config.snapshot.controllerInfo.state = this.config.snapshot.state;
|
|
196
|
-
return this.config.snapshot.state;
|
|
197
|
-
}
|
|
198
|
-
this.config.state ||= this.config.controllerInfo?.state ?? {};
|
|
199
|
-
if (this.config.controllerInfo) {
|
|
200
|
-
this.config.controllerInfo.state = this.config.state;
|
|
201
|
-
}
|
|
202
|
-
return this.config.state;
|
|
62
|
+
public setBrightness(brightnessArg: number, signalArg?: AbortSignal): Promise<INanoleafWriteResult> {
|
|
63
|
+
return this.#setState({ brightness: brightnessArg }, signalArg);
|
|
203
64
|
}
|
|
204
65
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.config.snapshot.effects ||= {};
|
|
208
|
-
this.config.snapshot.controllerInfo.effects = this.config.snapshot.effects;
|
|
209
|
-
return this.config.snapshot.effects;
|
|
210
|
-
}
|
|
211
|
-
this.config.effects ||= this.config.controllerInfo?.effects ?? {};
|
|
212
|
-
if (this.config.controllerInfo) {
|
|
213
|
-
this.config.controllerInfo.effects = this.config.effects;
|
|
214
|
-
}
|
|
215
|
-
return this.config.effects;
|
|
66
|
+
public setColorTemperature(kelvinArg: number, signalArg?: AbortSignal): Promise<INanoleafWriteResult> {
|
|
67
|
+
return this.#setState({ colorTemperatureKelvin: kelvinArg }, signalArg);
|
|
216
68
|
}
|
|
217
69
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
70
|
+
#setState(commandArg: plugins.ecobridgeDeviceManager.INanoleafStateCommand, signalArg?: AbortSignal): Promise<INanoleafWriteResult> {
|
|
71
|
+
return this.#run(async (signal) => {
|
|
72
|
+
const result = await this.#protocol.setState(commandArg, { signal });
|
|
73
|
+
return Object.freeze({ acceptance: result.acceptance, snapshot: this.#snapshot(result.snapshot) });
|
|
74
|
+
}, signalArg);
|
|
222
75
|
}
|
|
223
76
|
|
|
224
|
-
|
|
225
|
-
return
|
|
77
|
+
public selectEffect(effectArg: string, signalArg?: AbortSignal): Promise<INanoleafWriteResult> {
|
|
78
|
+
return this.#run(async (signal) => {
|
|
79
|
+
const result = await this.#protocol.selectEffect(effectArg, { signal });
|
|
80
|
+
return Object.freeze({ acceptance: result.acceptance, snapshot: this.#snapshot(result.snapshot) });
|
|
81
|
+
}, signalArg);
|
|
226
82
|
}
|
|
227
83
|
|
|
228
|
-
|
|
229
|
-
|
|
84
|
+
public destroy(): Promise<void> {
|
|
85
|
+
if (this.#closePromise) return this.#closePromise;
|
|
86
|
+
this.#closed = true;
|
|
87
|
+
this.#healthy = false;
|
|
88
|
+
this.#removeParentAbort();
|
|
89
|
+
this.#closePromise = Promise.resolve().then(() => this.#drainCleanup());
|
|
90
|
+
this.#lifetime.abort();
|
|
91
|
+
return this.#closePromise;
|
|
230
92
|
}
|
|
231
93
|
|
|
232
|
-
|
|
233
|
-
|
|
94
|
+
/** Explicitly retries retained cleanup; no device operation is replayed. */
|
|
95
|
+
public retryCleanup(): Promise<void> {
|
|
96
|
+
if (!this.#closePromise) return this.destroy();
|
|
97
|
+
if (this.#retryPromise) return this.#retryPromise;
|
|
98
|
+
const retry = this.#closePromise.then(() => undefined, () => this.#drainCleanup(true));
|
|
99
|
+
this.#retryPromise = retry;
|
|
100
|
+
void retry.finally(() => { if (this.#retryPromise === retry) this.#retryPromise = undefined; }).catch(() => undefined);
|
|
101
|
+
return retry;
|
|
234
102
|
}
|
|
235
103
|
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
104
|
+
async #drainCleanup(retryArg = false): Promise<void> {
|
|
105
|
+
const results = await Promise.allSettled([
|
|
106
|
+
Promise.resolve().then(async () => {
|
|
107
|
+
if (!this.#protocolClosed) {
|
|
108
|
+
await this.#protocol.close();
|
|
109
|
+
this.#protocolClosed = true;
|
|
110
|
+
}
|
|
111
|
+
}),
|
|
112
|
+
Promise.allSettled([...this.#operations]),
|
|
113
|
+
]);
|
|
114
|
+
if (this.#ownsStore && this.#store) {
|
|
115
|
+
results.push(...await Promise.allSettled([Promise.resolve().then(async () => {
|
|
116
|
+
if (retryArg) await this.#store?.retryCleanup();
|
|
117
|
+
else await this.#store?.close();
|
|
118
|
+
this.#store = undefined;
|
|
119
|
+
})]));
|
|
120
|
+
} else if (this.#protocolClosed) this.#store = undefined;
|
|
121
|
+
const failures = results.flatMap((resultArg) => resultArg.status === 'rejected' ? [resultArg.reason as unknown] : []);
|
|
122
|
+
if (failures.length) throw new AggregateError(failures, 'Nanoleaf client cleanup failed.');
|
|
242
123
|
}
|
|
243
124
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
method: optionsArg.method ?? (optionsArg.body === undefined ? 'GET' : 'POST'),
|
|
247
|
-
headers: optionsArg.body === undefined ? undefined : { 'content-type': 'application/json' },
|
|
248
|
-
body: optionsArg.body === undefined ? undefined : JSON.stringify(optionsArg.body),
|
|
249
|
-
});
|
|
250
|
-
const text = await response.text();
|
|
251
|
-
if (!response.ok) {
|
|
252
|
-
throw new Error(`Nanoleaf request ${pathArg} failed with HTTP ${response.status}: ${text}`);
|
|
253
|
-
}
|
|
254
|
-
if (!text) {
|
|
255
|
-
return undefined as TResult;
|
|
256
|
-
}
|
|
257
|
-
return JSON.parse(text) as TResult;
|
|
125
|
+
#snapshot(snapshotArg: plugins.ecobridgeDeviceManager.INanoleafSnapshot): INanoleafSnapshot {
|
|
126
|
+
return Object.freeze({ ...snapshotArg, homeId: this.#config.homeId, origin: this.#config.origin });
|
|
258
127
|
}
|
|
259
128
|
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
129
|
+
#run<TResult>(workArg: (signalArg: AbortSignal) => Promise<TResult>, signalArg?: AbortSignal): Promise<TResult> {
|
|
130
|
+
if (this.#closed) return Promise.reject(new plugins.ecobridgeDeviceManager.NanoleafProtocolError('CLOSED', 'Nanoleaf client is destroyed.'));
|
|
131
|
+
if (this.#operations.size >= (this.#config.maxPendingOperations ?? 32)) {
|
|
132
|
+
return Promise.reject(new plugins.ecobridgeDeviceManager.NanoleafProtocolError('BUSY', 'Nanoleaf client operation capacity is exhausted.'));
|
|
263
133
|
}
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private clamp(valueArg: number, minArg: number, maxArg: number): number {
|
|
278
|
-
return Math.max(minArg, Math.min(maxArg, valueArg));
|
|
134
|
+
if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) return Promise.reject(new TypeError('Nanoleaf operation signal must be an AbortSignal.'));
|
|
135
|
+
const signal = signalArg ? AbortSignal.any([this.#lifetime.signal, signalArg]) : this.#lifetime.signal;
|
|
136
|
+
const operation = Promise.resolve().then(async () => {
|
|
137
|
+
if (signal.aborted) throw new plugins.ecobridgeDeviceManager.NanoleafProtocolError('ABORTED', 'Nanoleaf operation was cancelled before admission.');
|
|
138
|
+
return workArg(signal);
|
|
139
|
+
}).then((resultArg) => { this.#healthy = !this.#closed && !signal.aborted; return resultArg; }, (errorArg: unknown) => {
|
|
140
|
+
this.#healthy = false;
|
|
141
|
+
throw errorArg;
|
|
142
|
+
}).finally(() => this.#operations.delete(operation));
|
|
143
|
+
this.#operations.add(operation);
|
|
144
|
+
return operation;
|
|
279
145
|
}
|
|
280
146
|
}
|
|
@@ -1,29 +1,100 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
|
|
3
|
+
import type { INativeAdmissionContext } from '../../core/classes.nativehomeadmission.js';
|
|
1
4
|
import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
import { createNanoleafCredentialStore, nanoleafCredential, nanoleafCredentialKey } from './nanoleaf.credentials.js';
|
|
6
|
+
import type { INanoleafConfig, INanoleafIntegrationOptions } from './nanoleaf.types.js';
|
|
7
|
+
import { nanoleafKeys, nanoleafRecord, normalizeNanoleafConfig } from './nanoleaf.validation.js';
|
|
5
8
|
|
|
6
9
|
export class NanoleafConfigFlow implements IConfigFlow<INanoleafConfig> {
|
|
10
|
+
readonly #scope: NativeIntegrationScope;
|
|
11
|
+
readonly #credentials?: INanoleafIntegrationOptions;
|
|
12
|
+
|
|
13
|
+
constructor(ownerArg?: INanoleafIntegrationOptions, scopeArg?: NativeIntegrationScope) {
|
|
14
|
+
this.#credentials = ownerArg && Object.freeze({ db: ownerArg.db, keyProvider: ownerArg.keyProvider });
|
|
15
|
+
this.#scope = scopeArg ?? new NativeIntegrationOwner('nanoleaf', { homeId: ownerArg?.homeId }).createFlowScope();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public destroy(): Promise<void> { return this.#scope.close(); }
|
|
19
|
+
public retryCleanup(): Promise<void> { return this.#scope.retryCleanup(); }
|
|
20
|
+
|
|
7
21
|
public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<INanoleafConfig>> {
|
|
8
|
-
|
|
22
|
+
if (candidateArg.integrationDomain !== undefined && candidateArg.integrationDomain !== 'nanoleaf') {
|
|
23
|
+
return { kind: 'error', error: 'Nanoleaf config flow requires a Nanoleaf candidate.' };
|
|
24
|
+
}
|
|
25
|
+
let context: INativeAdmissionContext;
|
|
26
|
+
try { context = this.#scope.admit(contextArg); }
|
|
27
|
+
catch { return { kind: 'error', error: 'Nanoleaf config flow requires an open instance and the explicit matching host home.' }; }
|
|
9
28
|
return {
|
|
10
|
-
kind: 'form',
|
|
11
|
-
|
|
12
|
-
description: 'Configure the local Nanoleaf HTTP endpoint with an existing auth token from the controller.',
|
|
29
|
+
kind: 'form', title: 'Connect selected Nanoleaf controller',
|
|
30
|
+
description: 'Provide the selected local IPv4 origin, serial and documented model. An existing token is validated and stored encrypted for the host-selected home; plaintext vendor HTTP requires explicit consent.',
|
|
13
31
|
fields: [
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
16
|
-
{ name: '
|
|
32
|
+
{ name: 'origin', label: 'Selected HTTP(S) IPv4 origin', type: 'text', required: true },
|
|
33
|
+
{ name: 'expectedSerialNumber', label: 'Expected serial number', type: 'text', required: true },
|
|
34
|
+
{ name: 'expectedModel', label: 'Expected documented controller model', type: 'text', required: true },
|
|
35
|
+
{ name: 'credentialKey', label: 'New encrypted credential reference', type: 'text', required: true },
|
|
36
|
+
{ name: 'authToken', label: 'Existing controller token', type: 'password', required: true },
|
|
37
|
+
{ name: 'allowUnencryptedHttp', label: 'Consent to plaintext local HTTP', type: 'boolean' },
|
|
38
|
+
{ name: 'certificateAuthority', label: 'Trusted HTTPS controller PEM certificate', type: 'text' },
|
|
17
39
|
],
|
|
18
|
-
submit:
|
|
19
|
-
kind: 'done',
|
|
20
|
-
title: 'Nanoleaf configured',
|
|
21
|
-
config: {
|
|
22
|
-
host: String(valuesArg.host || candidateArg.host || ''),
|
|
23
|
-
port: typeof valuesArg.port === 'number' ? valuesArg.port : candidateArg.port || DEFAULT_NANOLEAF_PORT,
|
|
24
|
-
authToken: String(valuesArg.authToken || ''),
|
|
25
|
-
},
|
|
26
|
-
}),
|
|
40
|
+
submit: (valuesArg) => this.#submit(valuesArg, context),
|
|
27
41
|
};
|
|
28
42
|
}
|
|
43
|
+
|
|
44
|
+
async #submit(valuesArg: Record<string, unknown>, contextArg: INativeAdmissionContext): Promise<IConfigFlowStep<INanoleafConfig>> {
|
|
45
|
+
let config: Readonly<INanoleafConfig>;
|
|
46
|
+
let credential: ReturnType<typeof nanoleafCredential>;
|
|
47
|
+
try {
|
|
48
|
+
const values = nanoleafRecord(valuesArg, 'config flow values');
|
|
49
|
+
nanoleafKeys(values, ['homeId', 'origin', 'expectedSerialNumber', 'expectedModel', 'credentialKey', 'authToken',
|
|
50
|
+
'allowUnencryptedHttp', 'certificateAuthority', 'timeoutMs', 'maxResponseBytes', 'maxPendingOperations']);
|
|
51
|
+
if (values.homeId !== undefined && values.homeId !== contextArg.homeId) throw new TypeError('Nanoleaf form home conflicts with the host home.');
|
|
52
|
+
const { authToken, ...configuration } = values;
|
|
53
|
+
config = normalizeNanoleafConfig({ ...configuration, homeId: contextArg.homeId });
|
|
54
|
+
credential = nanoleafCredential(config, authToken);
|
|
55
|
+
this.#scope.admit(contextArg, config.homeId);
|
|
56
|
+
} catch { return { kind: 'error', error: 'Nanoleaf config flow values or host home are invalid, cancelled or closed.' }; }
|
|
57
|
+
return this.#scope.run(contextArg, config.homeId, async (leaseArg): Promise<IConfigFlowStep<INanoleafConfig>> => {
|
|
58
|
+
try {
|
|
59
|
+
const handle = createNanoleafCredentialStore(config, leaseArg.context.homeId, leaseArg.context.configStore ?? this.#credentials);
|
|
60
|
+
let protocol: plugins.ecobridgeDeviceManager.NanoleafProtocol | undefined;
|
|
61
|
+
let protocolClosed = false;
|
|
62
|
+
let storeClosed = !handle.owned;
|
|
63
|
+
let firstStoreCleanup = true;
|
|
64
|
+
// One admitted credential-validation operation owns its protocol and optional store.
|
|
65
|
+
leaseArg.own(async () => {
|
|
66
|
+
const results = await Promise.allSettled([
|
|
67
|
+
Promise.resolve().then(async () => {
|
|
68
|
+
if (protocol && !protocolClosed) { await protocol.close(); protocolClosed = true; }
|
|
69
|
+
}),
|
|
70
|
+
Promise.resolve().then(async () => {
|
|
71
|
+
if (!storeClosed) {
|
|
72
|
+
// Failed lease callbacks run again only on explicit owner cleanup retry.
|
|
73
|
+
if (firstStoreCleanup) { firstStoreCleanup = false; await handle.store.close(); }
|
|
74
|
+
else await handle.store.retryCleanup();
|
|
75
|
+
storeClosed = true;
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
78
|
+
]);
|
|
79
|
+
const failures = results.flatMap((resultArg) => resultArg.status === 'rejected' ? [resultArg.reason as unknown] : []);
|
|
80
|
+
if (failures.length) throw new AggregateError(failures, 'Nanoleaf flow resource cleanup failed.');
|
|
81
|
+
});
|
|
82
|
+
await handle.store.initialize({ signal: leaseArg.signal });
|
|
83
|
+
leaseArg.assertOpen();
|
|
84
|
+
protocol = new plugins.ecobridgeDeviceManager.NanoleafProtocol({ origin: config.origin,
|
|
85
|
+
expectedSerialNumber: config.expectedSerialNumber, expectedModel: config.expectedModel,
|
|
86
|
+
getAuthToken: () => credential.authToken, allowUnencryptedHttp: config.allowUnencryptedHttp,
|
|
87
|
+
ca: config.certificateAuthority, timeoutMs: config.timeoutMs, maxResponseBytes: config.maxResponseBytes,
|
|
88
|
+
maxPendingOperations: config.maxPendingOperations });
|
|
89
|
+
await protocol.readSnapshot({ signal: leaseArg.signal });
|
|
90
|
+
leaseArg.assertOpen();
|
|
91
|
+
await handle.store.create(nanoleafCredentialKey(config), credential);
|
|
92
|
+
leaseArg.assertOpen();
|
|
93
|
+
return { kind: 'done', title: 'Nanoleaf identity verified and token stored encrypted', config };
|
|
94
|
+
} catch { return { kind: 'error', error: 'Nanoleaf controller validation or encrypted credential persistence failed.' }; }
|
|
95
|
+
}).catch((errorArg: unknown): IConfigFlowStep<INanoleafConfig> => {
|
|
96
|
+
if (errorArg instanceof AggregateError) throw errorArg;
|
|
97
|
+
return { kind: 'error', error: 'Nanoleaf config flow was cancelled or closed.' };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
29
100
|
}
|