@smarthome.exchange/integrations 1.0.0 → 3.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.
Files changed (529) hide show
  1. package/changelog.md +84 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/classes.configstore.d.ts +2 -0
  4. package/dist_ts/core/classes.configstore.js +84 -27
  5. package/dist_ts/core/classes.nativehomeadmission.d.ts +19 -0
  6. package/dist_ts/core/classes.nativehomeadmission.js +84 -0
  7. package/dist_ts/core/classes.nativeintegrationowner.d.ts +81 -0
  8. package/dist_ts/core/classes.nativeintegrationowner.js +273 -0
  9. package/dist_ts/core/classes.runtimemanager.d.ts +2 -0
  10. package/dist_ts/core/classes.runtimemanager.js +11 -3
  11. package/dist_ts/core/index.d.ts +2 -0
  12. package/dist_ts/core/index.js +3 -1
  13. package/dist_ts/core/types.d.ts +16 -0
  14. package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
  15. package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
  16. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
  17. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
  18. package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
  19. package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
  20. package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
  21. package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
  22. package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
  23. package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
  24. package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
  25. package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
  26. package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
  27. package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
  28. package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
  29. package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
  30. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
  31. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
  32. package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
  33. package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
  34. package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
  35. package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
  36. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
  37. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
  38. package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
  39. package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
  40. package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
  41. package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
  42. package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
  43. package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
  44. package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
  45. package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
  46. package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
  47. package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
  48. package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
  49. package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
  50. package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
  51. package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
  52. package/dist_ts/integrations/daikin/daikin.types.js +1 -1
  53. package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
  54. package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
  55. package/dist_ts/integrations/deconz/deconz.classes.client.d.ts +37 -29
  56. package/dist_ts/integrations/deconz/deconz.classes.client.js +521 -294
  57. package/dist_ts/integrations/deconz/deconz.classes.configflow.d.ts +6 -1
  58. package/dist_ts/integrations/deconz/deconz.classes.configflow.js +63 -33
  59. package/dist_ts/integrations/deconz/deconz.classes.integration.d.ts +4 -3
  60. package/dist_ts/integrations/deconz/deconz.classes.integration.js +85 -40
  61. package/dist_ts/integrations/deconz/deconz.mapper.d.ts +1 -0
  62. package/dist_ts/integrations/deconz/deconz.mapper.js +24 -6
  63. package/dist_ts/integrations/deconz/deconz.types.d.ts +43 -6
  64. package/dist_ts/integrations/deconz/deconz.validation.d.ts +7 -1
  65. package/dist_ts/integrations/deconz/deconz.validation.js +215 -4
  66. package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
  67. package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
  68. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
  69. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
  70. package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
  71. package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
  72. package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
  73. package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
  74. package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
  75. package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
  76. package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
  77. package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
  78. package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
  79. package/dist_ts/integrations/esphome/esphome.classes.client.d.ts +19 -7
  80. package/dist_ts/integrations/esphome/esphome.classes.client.js +183 -89
  81. package/dist_ts/integrations/esphome/esphome.classes.configflow.d.ts +5 -2
  82. package/dist_ts/integrations/esphome/esphome.classes.configflow.js +43 -33
  83. package/dist_ts/integrations/esphome/esphome.classes.integration.d.ts +8 -6
  84. package/dist_ts/integrations/esphome/esphome.classes.integration.js +71 -43
  85. package/dist_ts/integrations/esphome/esphome.discovery.js +4 -12
  86. package/dist_ts/integrations/esphome/esphome.mapper.d.ts +2 -2
  87. package/dist_ts/integrations/esphome/esphome.mapper.js +6 -4
  88. package/dist_ts/integrations/esphome/esphome.types.d.ts +31 -6
  89. package/dist_ts/integrations/esphome/esphome.types.js +1 -1
  90. package/dist_ts/integrations/esphome/esphome.validation.d.ts +7 -0
  91. package/dist_ts/integrations/esphome/esphome.validation.js +66 -0
  92. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.d.ts +32 -4
  93. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.js +345 -116
  94. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.d.ts +8 -5
  95. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.js +34 -8
  96. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.d.ts +7 -4
  97. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.js +44 -56
  98. package/dist_ts/integrations/homeassistant/homeassistant.types.d.ts +12 -1
  99. package/dist_ts/integrations/hue/hue.classes.client.d.ts +6 -1
  100. package/dist_ts/integrations/hue/hue.classes.client.js +120 -45
  101. package/dist_ts/integrations/hue/hue.classes.configflow.js +16 -7
  102. package/dist_ts/integrations/hue/hue.classes.integration.d.ts +2 -0
  103. package/dist_ts/integrations/hue/hue.classes.integration.js +103 -30
  104. package/dist_ts/integrations/hue/hue.validation.d.ts +1 -0
  105. package/dist_ts/integrations/hue/hue.validation.js +14 -4
  106. package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
  107. package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
  108. package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
  109. package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
  110. package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
  111. package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
  112. package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
  113. package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
  114. package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
  115. package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
  116. package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
  117. package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
  118. package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
  119. package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
  120. package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
  121. package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
  122. package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
  123. package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
  124. package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
  125. package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
  126. package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
  127. package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
  128. package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
  129. package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
  130. package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
  131. package/dist_ts/integrations/mqtt/mqtt.classes.client.d.ts +15 -2
  132. package/dist_ts/integrations/mqtt/mqtt.classes.client.js +173 -23
  133. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.d.ts +5 -1
  134. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.js +53 -33
  135. package/dist_ts/integrations/mqtt/mqtt.classes.integration.d.ts +4 -1
  136. package/dist_ts/integrations/mqtt/mqtt.classes.integration.js +127 -56
  137. package/dist_ts/integrations/mqtt/mqtt.classes.session.d.ts +13 -0
  138. package/dist_ts/integrations/mqtt/mqtt.classes.session.js +130 -0
  139. package/dist_ts/integrations/mqtt/mqtt.mapper.d.ts +3 -0
  140. package/dist_ts/integrations/mqtt/mqtt.mapper.js +44 -1
  141. package/dist_ts/integrations/mqtt/mqtt.types.d.ts +46 -0
  142. package/dist_ts/integrations/mqtt/mqtt.validation.d.ts +7 -0
  143. package/dist_ts/integrations/mqtt/mqtt.validation.js +180 -0
  144. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
  145. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
  146. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
  147. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
  148. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
  149. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
  150. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
  151. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
  152. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
  153. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
  154. package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
  155. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
  156. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
  157. package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
  158. package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
  159. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
  160. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
  161. package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
  162. package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
  163. package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
  164. package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
  165. package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
  166. package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
  167. package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
  168. package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
  169. package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
  170. package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
  171. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
  172. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
  173. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
  174. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
  175. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
  176. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
  177. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
  178. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
  179. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
  180. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
  181. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
  182. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
  183. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
  184. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
  185. package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
  186. package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
  187. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
  188. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
  189. package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
  190. package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
  191. package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
  192. package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
  193. package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
  194. package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
  195. package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
  196. package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
  197. package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
  198. package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
  199. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.d.ts +4 -2
  200. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.js +41 -16
  201. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.d.ts +5 -0
  202. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.js +20 -7
  203. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.d.ts +1 -0
  204. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.js +53 -50
  205. package/dist_ts/integrations/samsungtv/samsungtv.types.d.ts +4 -1
  206. package/dist_ts/integrations/shelly/shelly.classes.client.d.ts +26 -30
  207. package/dist_ts/integrations/shelly/shelly.classes.client.js +504 -276
  208. package/dist_ts/integrations/shelly/shelly.classes.configflow.d.ts +8 -3
  209. package/dist_ts/integrations/shelly/shelly.classes.configflow.js +43 -38
  210. package/dist_ts/integrations/shelly/shelly.classes.integration.d.ts +6 -6
  211. package/dist_ts/integrations/shelly/shelly.classes.integration.js +103 -101
  212. package/dist_ts/integrations/shelly/shelly.classes.subscription.d.ts +4 -0
  213. package/dist_ts/integrations/shelly/shelly.classes.subscription.js +24 -2
  214. package/dist_ts/integrations/shelly/shelly.mapper.d.ts +1 -2
  215. package/dist_ts/integrations/shelly/shelly.mapper.js +21 -20
  216. package/dist_ts/integrations/shelly/shelly.rpc.js +4 -1
  217. package/dist_ts/integrations/shelly/shelly.types.d.ts +42 -2
  218. package/dist_ts/integrations/shelly/shelly.types.js +11 -2
  219. package/dist_ts/integrations/shelly/shelly.validation.d.ts +7 -0
  220. package/dist_ts/integrations/shelly/shelly.validation.js +79 -0
  221. package/dist_ts/integrations/sonos/sonos.classes.client.d.ts +79 -22
  222. package/dist_ts/integrations/sonos/sonos.classes.client.js +427 -139
  223. package/dist_ts/integrations/sonos/sonos.classes.configflow.d.ts +9 -2
  224. package/dist_ts/integrations/sonos/sonos.classes.configflow.js +26 -28
  225. package/dist_ts/integrations/sonos/sonos.classes.integration.d.ts +7 -6
  226. package/dist_ts/integrations/sonos/sonos.classes.integration.js +289 -224
  227. package/dist_ts/integrations/sonos/sonos.config.d.ts +6 -1
  228. package/dist_ts/integrations/sonos/sonos.config.js +23 -2
  229. package/dist_ts/integrations/sonos/sonos.mapper.d.ts +1 -1
  230. package/dist_ts/integrations/sonos/sonos.mapper.js +14 -9
  231. package/dist_ts/integrations/sonos/sonos.matchers.js +3 -3
  232. package/dist_ts/integrations/sonos/sonos.types.d.ts +26 -1
  233. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
  234. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
  235. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
  236. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
  237. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
  238. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
  239. package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
  240. package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
  241. package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
  242. package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
  243. package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
  244. package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
  245. package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
  246. package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
  247. package/dist_ts/integrations/tasmota/tasmota.classes.integration.d.ts +5 -0
  248. package/dist_ts/integrations/tasmota/tasmota.classes.integration.js +23 -7
  249. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.d.ts +19 -5
  250. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.js +115 -41
  251. package/dist_ts/integrations/tasmota/tasmota.config.js +23 -1
  252. package/dist_ts/integrations/tasmota/tasmota.credentials.d.ts +11 -0
  253. package/dist_ts/integrations/tasmota/tasmota.credentials.js +97 -0
  254. package/dist_ts/integrations/tasmota/tasmota.discovery.d.ts +7 -1
  255. package/dist_ts/integrations/tasmota/tasmota.discovery.js +108 -30
  256. package/dist_ts/integrations/tasmota/tasmota.mapper.d.ts +2 -2
  257. package/dist_ts/integrations/tasmota/tasmota.mapper.js +7 -7
  258. package/dist_ts/integrations/tasmota/tasmota.types.d.ts +41 -2
  259. package/dist_ts/integrations/tuya/tuya.classes.integration.d.ts +4 -1
  260. package/dist_ts/integrations/tuya/tuya.classes.integration.js +79 -110
  261. package/dist_ts/integrations/tuya/tuya.classes.runtime.d.ts +5 -2
  262. package/dist_ts/integrations/tuya/tuya.classes.runtime.js +73 -26
  263. package/dist_ts/integrations/tuya/tuya.mapper.js +2 -2
  264. package/dist_ts/integrations/tuya/tuya.types.d.ts +3 -0
  265. package/dist_ts/integrations/tuya/tuya.validation.d.ts +2 -2
  266. package/dist_ts/integrations/tuya/tuya.validation.js +6 -11
  267. package/dist_ts/integrations/webostv/webostv.classes.client.d.ts +6 -5
  268. package/dist_ts/integrations/webostv/webostv.classes.client.js +147 -76
  269. package/dist_ts/integrations/webostv/webostv.classes.configflow.d.ts +7 -2
  270. package/dist_ts/integrations/webostv/webostv.classes.configflow.js +36 -20
  271. package/dist_ts/integrations/webostv/webostv.classes.integration.d.ts +4 -4
  272. package/dist_ts/integrations/webostv/webostv.classes.integration.js +46 -54
  273. package/dist_ts/integrations/webostv/webostv.mapper.js +5 -5
  274. package/dist_ts/integrations/webostv/webostv.types.d.ts +16 -3
  275. package/dist_ts/integrations/webostv/webostv.types.js +1 -1
  276. package/dist_ts/integrations/webostv/webostv.validation.d.ts +3 -1
  277. package/dist_ts/integrations/webostv/webostv.validation.js +23 -1
  278. package/dist_ts/integrations/wiz/index.d.ts +1 -0
  279. package/dist_ts/integrations/wiz/index.js +2 -1
  280. package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
  281. package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
  282. package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
  283. package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
  284. package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
  285. package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
  286. package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
  287. package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
  288. package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
  289. package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
  290. package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
  291. package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
  292. package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
  293. package/dist_ts/integrations/yamaha/index.d.ts +1 -0
  294. package/dist_ts/integrations/yamaha/index.js +2 -1
  295. package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
  296. package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
  297. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
  298. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
  299. package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
  300. package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
  301. package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
  302. package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
  303. package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
  304. package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
  305. package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
  306. package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
  307. package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
  308. package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
  309. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.d.ts +6 -1
  310. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.js +18 -8
  311. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.d.ts +4 -1
  312. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.js +54 -6
  313. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.d.ts +12 -6
  314. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.js +73 -59
  315. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.d.ts +3 -2
  316. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.js +17 -12
  317. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.types.d.ts +31 -2
  318. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.d.ts +9 -1
  319. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.js +57 -7
  320. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.d.ts +10 -4
  321. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.js +167 -31
  322. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.d.ts +6 -1
  323. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.js +57 -35
  324. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.d.ts +3 -1
  325. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.js +59 -37
  326. package/dist_ts/integrations/zwave_js/zwave_js.discovery.js +3 -3
  327. package/dist_ts/integrations/zwave_js/zwave_js.mapper.js +41 -24
  328. package/dist_ts/integrations/zwave_js/zwave_js.types.d.ts +25 -3
  329. package/dist_ts/integrations/zwave_js/zwave_js.validation.d.ts +6 -2
  330. package/dist_ts/integrations/zwave_js/zwave_js.validation.js +52 -17
  331. package/dist_ts/plugins.d.ts +2 -1
  332. package/dist_ts/plugins.js +3 -2
  333. package/package.json +9 -9
  334. package/readme.md +539 -8
  335. package/ts/00_commitinfo_data.ts +1 -1
  336. package/ts/core/classes.configstore.ts +84 -27
  337. package/ts/core/classes.nativehomeadmission.ts +96 -0
  338. package/ts/core/classes.nativeintegrationowner.ts +302 -0
  339. package/ts/core/classes.runtimemanager.ts +11 -2
  340. package/ts/core/index.ts +2 -0
  341. package/ts/core/types.ts +18 -0
  342. package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
  343. package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
  344. package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
  345. package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
  346. package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
  347. package/ts/integrations/braviatv/braviatv.types.ts +53 -173
  348. package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
  349. package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
  350. package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
  351. package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
  352. package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
  353. package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
  354. package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
  355. package/ts/integrations/broadlink/broadlink.types.ts +48 -8
  356. package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
  357. package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
  358. package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
  359. package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
  360. package/ts/integrations/daikin/daikin.discovery.ts +94 -346
  361. package/ts/integrations/daikin/daikin.mapper.ts +83 -166
  362. package/ts/integrations/daikin/daikin.types.ts +27 -48
  363. package/ts/integrations/daikin/daikin.validation.ts +51 -0
  364. package/ts/integrations/daikin/readme.md +83 -0
  365. package/ts/integrations/deconz/deconz.classes.client.ts +348 -252
  366. package/ts/integrations/deconz/deconz.classes.configflow.ts +57 -29
  367. package/ts/integrations/deconz/deconz.classes.integration.ts +69 -37
  368. package/ts/integrations/deconz/deconz.mapper.ts +22 -5
  369. package/ts/integrations/deconz/deconz.types.ts +49 -6
  370. package/ts/integrations/deconz/deconz.validation.ts +177 -4
  371. package/ts/integrations/deconz/readme.md +15 -1
  372. package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
  373. package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
  374. package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
  375. package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
  376. package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
  377. package/ts/integrations/denonavr/denonavr.types.ts +48 -111
  378. package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
  379. package/ts/integrations/esphome/esphome.classes.client.ts +179 -94
  380. package/ts/integrations/esphome/esphome.classes.configflow.ts +40 -33
  381. package/ts/integrations/esphome/esphome.classes.integration.ts +63 -36
  382. package/ts/integrations/esphome/esphome.discovery.ts +3 -12
  383. package/ts/integrations/esphome/esphome.mapper.ts +9 -7
  384. package/ts/integrations/esphome/esphome.types.ts +35 -6
  385. package/ts/integrations/esphome/esphome.validation.ts +66 -0
  386. package/ts/integrations/esphome/readme.md +20 -6
  387. package/ts/integrations/homeassistant/homeassistant.classes.client.ts +353 -125
  388. package/ts/integrations/homeassistant/homeassistant.classes.integration.ts +35 -18
  389. package/ts/integrations/homeassistant/homeassistant.classes.runtime.ts +50 -63
  390. package/ts/integrations/homeassistant/homeassistant.types.ts +17 -1
  391. package/ts/integrations/homeassistant/readme.md +22 -6
  392. package/ts/integrations/hue/hue.classes.client.ts +110 -41
  393. package/ts/integrations/hue/hue.classes.configflow.ts +12 -6
  394. package/ts/integrations/hue/hue.classes.integration.ts +92 -26
  395. package/ts/integrations/hue/hue.validation.ts +13 -3
  396. package/ts/integrations/hue/readme.md +3 -3
  397. package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
  398. package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
  399. package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
  400. package/ts/integrations/kodi/kodi.discovery.ts +49 -28
  401. package/ts/integrations/kodi/kodi.mapper.ts +52 -10
  402. package/ts/integrations/kodi/kodi.types.ts +33 -58
  403. package/ts/integrations/kodi/kodi.validation.ts +60 -0
  404. package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
  405. package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
  406. package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
  407. package/ts/integrations/modbus/modbus.discovery.ts +38 -106
  408. package/ts/integrations/modbus/modbus.mapper.ts +64 -467
  409. package/ts/integrations/modbus/modbus.types.ts +64 -381
  410. package/ts/integrations/modbus/modbus.validation.ts +119 -0
  411. package/ts/integrations/mqtt/mqtt.classes.client.ts +136 -19
  412. package/ts/integrations/mqtt/mqtt.classes.configflow.ts +45 -32
  413. package/ts/integrations/mqtt/mqtt.classes.integration.ts +99 -38
  414. package/ts/integrations/mqtt/mqtt.classes.session.ts +118 -0
  415. package/ts/integrations/mqtt/mqtt.mapper.ts +43 -1
  416. package/ts/integrations/mqtt/mqtt.types.ts +52 -0
  417. package/ts/integrations/mqtt/mqtt.validation.ts +157 -0
  418. package/ts/integrations/mqtt/readme.md +12 -3
  419. package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
  420. package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
  421. package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
  422. package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
  423. package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
  424. package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
  425. package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
  426. package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
  427. package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
  428. package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
  429. package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
  430. package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
  431. package/ts/integrations/onkyo/onkyo.types.ts +55 -110
  432. package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
  433. package/ts/integrations/onkyo/readme.md +126 -0
  434. package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
  435. package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
  436. package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
  437. package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
  438. package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
  439. package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
  440. package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
  441. package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
  442. package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
  443. package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
  444. package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
  445. package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
  446. package/ts/integrations/pioneer/pioneer.types.ts +87 -85
  447. package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
  448. package/ts/integrations/samsungtv/readme.md +11 -5
  449. package/ts/integrations/samsungtv/samsungtv.classes.client.ts +40 -15
  450. package/ts/integrations/samsungtv/samsungtv.classes.configflow.ts +21 -6
  451. package/ts/integrations/samsungtv/samsungtv.classes.integration.ts +53 -38
  452. package/ts/integrations/samsungtv/samsungtv.types.ts +4 -1
  453. package/ts/integrations/shelly/readme.md +22 -7
  454. package/ts/integrations/shelly/shelly.classes.client.ts +386 -269
  455. package/ts/integrations/shelly/shelly.classes.configflow.ts +43 -38
  456. package/ts/integrations/shelly/shelly.classes.integration.ts +92 -111
  457. package/ts/integrations/shelly/shelly.classes.subscription.ts +24 -1
  458. package/ts/integrations/shelly/shelly.mapper.ts +19 -20
  459. package/ts/integrations/shelly/shelly.rpc.ts +2 -0
  460. package/ts/integrations/shelly/shelly.types.ts +50 -2
  461. package/ts/integrations/shelly/shelly.validation.ts +76 -0
  462. package/ts/integrations/sonos/readme.md +117 -38
  463. package/ts/integrations/sonos/sonos.classes.client.ts +392 -123
  464. package/ts/integrations/sonos/sonos.classes.configflow.ts +25 -28
  465. package/ts/integrations/sonos/sonos.classes.integration.ts +211 -186
  466. package/ts/integrations/sonos/sonos.config.ts +24 -2
  467. package/ts/integrations/sonos/sonos.mapper.ts +13 -8
  468. package/ts/integrations/sonos/sonos.matchers.ts +2 -2
  469. package/ts/integrations/sonos/sonos.types.ts +30 -1
  470. package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
  471. package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
  472. package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
  473. package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
  474. package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
  475. package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
  476. package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
  477. package/ts/integrations/tasmota/readme.md +16 -5
  478. package/ts/integrations/tasmota/tasmota.classes.integration.ts +24 -6
  479. package/ts/integrations/tasmota/tasmota.classes.runtime.ts +112 -43
  480. package/ts/integrations/tasmota/tasmota.config.ts +17 -0
  481. package/ts/integrations/tasmota/tasmota.credentials.ts +92 -0
  482. package/ts/integrations/tasmota/tasmota.discovery.ts +87 -24
  483. package/ts/integrations/tasmota/tasmota.mapper.ts +6 -6
  484. package/ts/integrations/tasmota/tasmota.types.ts +43 -2
  485. package/ts/integrations/tuya/readme.md +52 -10
  486. package/ts/integrations/tuya/tuya.classes.integration.ts +69 -98
  487. package/ts/integrations/tuya/tuya.classes.runtime.ts +70 -24
  488. package/ts/integrations/tuya/tuya.mapper.ts +1 -1
  489. package/ts/integrations/tuya/tuya.types.ts +4 -0
  490. package/ts/integrations/tuya/tuya.validation.ts +5 -8
  491. package/ts/integrations/webostv/readme.md +115 -22
  492. package/ts/integrations/webostv/webostv.classes.client.ts +123 -50
  493. package/ts/integrations/webostv/webostv.classes.configflow.ts +35 -15
  494. package/ts/integrations/webostv/webostv.classes.integration.ts +44 -37
  495. package/ts/integrations/webostv/webostv.mapper.ts +4 -4
  496. package/ts/integrations/webostv/webostv.types.ts +18 -3
  497. package/ts/integrations/webostv/webostv.validation.ts +22 -1
  498. package/ts/integrations/wiz/index.ts +1 -0
  499. package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
  500. package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
  501. package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
  502. package/ts/integrations/wiz/wiz.discovery.ts +48 -291
  503. package/ts/integrations/wiz/wiz.mapper.ts +81 -754
  504. package/ts/integrations/wiz/wiz.types.ts +24 -224
  505. package/ts/integrations/wiz/wiz.validation.ts +73 -0
  506. package/ts/integrations/yamaha/index.ts +1 -0
  507. package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
  508. package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
  509. package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
  510. package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
  511. package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
  512. package/ts/integrations/yamaha/yamaha.types.ts +77 -97
  513. package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
  514. package/ts/integrations/zigbee2mqtt/readme.md +22 -27
  515. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.ts +16 -7
  516. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.ts +53 -6
  517. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.ts +62 -49
  518. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.ts +17 -11
  519. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.types.ts +35 -2
  520. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.ts +53 -7
  521. package/ts/integrations/zwave_js/readme.md +42 -9
  522. package/ts/integrations/zwave_js/zwave_js.classes.client.ts +155 -31
  523. package/ts/integrations/zwave_js/zwave_js.classes.configflow.ts +55 -31
  524. package/ts/integrations/zwave_js/zwave_js.classes.integration.ts +57 -35
  525. package/ts/integrations/zwave_js/zwave_js.discovery.ts +2 -2
  526. package/ts/integrations/zwave_js/zwave_js.mapper.ts +37 -23
  527. package/ts/integrations/zwave_js/zwave_js.types.ts +26 -3
  528. package/ts/integrations/zwave_js/zwave_js.validation.ts +52 -18
  529. package/ts/plugins.ts +2 -1
@@ -1,14 +1,17 @@
1
1
  import * as plugins from '../../plugins.js';
2
+ import { NativeIntegrationOwner } from '../../core/classes.nativeintegrationowner.js';
3
+ import { normalizeHomeAssistantConfig } from './homeassistant.classes.client.js';
4
+ import { homeAssistantRecord } from './homeassistant.validation.js';
2
5
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
6
  import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
4
7
  import type {
5
- IIntegrationRuntime,
6
8
  IIntegrationSetupContext,
7
9
  } from '../../core/types.js';
8
10
  import { HomeAssistantClient } from './homeassistant.classes.client.js';
9
11
  import { HomeAssistantRuntime } from './homeassistant.classes.runtime.js';
10
12
  import type {
11
13
  IHomeAssistantHomeassistantConfig,
14
+ IHomeAssistantIntegrationOptions,
12
15
  THomeAssistantWebSocketFactory,
13
16
  } from './homeassistant.types.js';
14
17
 
@@ -29,26 +32,40 @@ export class HomeAssistantHomeassistantIntegration extends BaseIntegration<IHome
29
32
  transport: 'websocket',
30
33
  };
31
34
 
32
- constructor(
33
- private readonly webSocketFactory: THomeAssistantWebSocketFactory = (
34
- urlArg
35
- ) => new globalThis.WebSocket(urlArg)
36
- ) {
35
+ private readonly owner: NativeIntegrationOwner;
36
+ private readonly webSocketFactory: THomeAssistantWebSocketFactory;
37
+
38
+ constructor(optionsOrFactoryArg: IHomeAssistantIntegrationOptions | THomeAssistantWebSocketFactory = {}) {
37
39
  super();
40
+ const options = homeAssistantRecord(typeof optionsOrFactoryArg === 'function'
41
+ ? { webSocketFactory: optionsOrFactoryArg } : optionsOrFactoryArg);
42
+ if (Object.keys(options).some((key) => !['homeId', 'capacity', 'webSocketFactory'].includes(key))) throw new TypeError('Home Assistant integration options contain unsupported fields.');
43
+ if (options.webSocketFactory !== undefined && typeof options.webSocketFactory !== 'function') throw new TypeError('Home Assistant webSocketFactory must be a function.');
44
+ this.webSocketFactory = options.webSocketFactory as THomeAssistantWebSocketFactory | undefined ?? ((urlArg) => new globalThis.WebSocket(urlArg));
45
+ this.owner = new NativeIntegrationOwner(this.domain, { homeId: options.homeId as string | undefined, capacity: options.capacity as number | undefined });
38
46
  }
39
47
 
40
- public async setup(
41
- configArg: IHomeAssistantHomeassistantConfig,
42
- contextArg: IIntegrationSetupContext
43
- ): Promise<IIntegrationRuntime> {
44
- const client = new HomeAssistantClient(
45
- configArg,
46
- plugins.homeAssistantWebsocket,
47
- this.webSocketFactory
48
- );
49
- await client.init(contextArg.abortSignal);
50
- return new HomeAssistantRuntime(client);
48
+ public async setup(configArg: IHomeAssistantHomeassistantConfig, contextArg: IIntegrationSetupContext): Promise<HomeAssistantRuntime> {
49
+ // Validate/capture before deferred setup so later caller mutation cannot change scope.
50
+ normalizeHomeAssistantConfig(configArg);
51
+ const config = structuredClone(configArg);
52
+ return this.owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
53
+ const client = new HomeAssistantClient(config, leaseArg.context, plugins.homeAssistantWebsocket,
54
+ this.webSocketFactory, { owner: this.owner, lease: leaseArg });
55
+ let cleanupStarted = false;
56
+ leaseArg.own(() => {
57
+ const retry = cleanupStarted;
58
+ cleanupStarted = true;
59
+ return retry ? client.retryCleanup() : client.destroy();
60
+ });
61
+ await client.init(leaseArg.signal);
62
+ leaseArg.assertOpen();
63
+ const runtime = new HomeAssistantRuntime(client, leaseArg, this.owner);
64
+ leaseArg.promoteToRuntime();
65
+ return runtime;
66
+ });
51
67
  }
52
68
 
53
- public async destroy(): Promise<void> {}
69
+ public destroy(): Promise<void> { return this.owner.close(); }
70
+ public retryCleanup(): Promise<void> { return this.owner.retryCleanup(); }
54
71
  }
@@ -1,3 +1,4 @@
1
+ import type { INativeSetupLease, NativeIntegrationOwner } from '../../core/classes.nativeintegrationowner.js';
1
2
  import type * as shxInterfaces from '@smarthome.exchange/interfaces';
2
3
  import type {
3
4
  IDeviceFeatureWriteRequest,
@@ -10,6 +11,7 @@ import { HomeAssistantMapper } from './homeassistant.mapper.js';
10
11
  import { homeAssistantRecord } from './homeassistant.validation.js';
11
12
  import type {
12
13
  IHomeAssistantClient,
14
+ IHomeAssistantOperationSession,
13
15
  IHomeAssistantTargetScope,
14
16
  } from './homeassistant.types.js';
15
17
 
@@ -21,43 +23,44 @@ const abortError = (abortSignalArg: AbortSignal): Error => {
21
23
 
22
24
  export class HomeAssistantRuntime implements IIntegrationRuntime {
23
25
  public readonly domain = 'homeassistant';
24
- private readonly subscriptions = new Set<() => void>();
25
26
  private destroyed = false;
26
- private cleanupComplete = false;
27
27
  private destroyPromise?: Promise<void>;
28
28
  private readonly targetScope: IHomeAssistantTargetScope;
29
29
 
30
- constructor(private readonly client: IHomeAssistantClient) {
30
+ constructor(private readonly client: IHomeAssistantClient, private readonly lease?: INativeSetupLease, private readonly owner?: NativeIntegrationOwner) {
31
31
  this.targetScope = Object.freeze({ ...client.getTargetScope() });
32
32
  }
33
33
 
34
34
  public isHealthy(): boolean {
35
- return !this.destroyed && this.client.connected;
35
+ return !this.destroyed && !this.lease?.signal.aborted && this.client.connected;
36
36
  }
37
37
 
38
38
  public async devices(
39
39
  abortSignalArg?: AbortSignal
40
40
  ): Promise<shxInterfaces.data.IDeviceDefinition[]> {
41
41
  this.assertActive();
42
- if (abortSignalArg?.aborted) {
43
- throw abortError(abortSignalArg);
44
- }
45
- return HomeAssistantMapper.toDevices(
46
- this.client.getSnapshot(),
47
- this.client.connected,
48
- this.client.getEntityPlatforms(),
49
- this.client.getServices(),
50
- this.targetScope
51
- );
42
+ return this.client.withOperation(async () => {
43
+ this.assertActive();
44
+ return HomeAssistantMapper.toDevices(
45
+ this.client.getSnapshot(),
46
+ this.client.connected,
47
+ this.client.getEntityPlatforms(),
48
+ this.client.getServices(),
49
+ this.targetScope
50
+ );
51
+ }, abortSignalArg);
52
52
  }
53
53
 
54
54
  public async entities(): Promise<IIntegrationEntity[]> {
55
55
  this.assertActive();
56
- return HomeAssistantMapper.toEntities(this.client.getSnapshot(), this.client.connected, this.targetScope);
56
+ return this.client.withOperation(async () => {
57
+ this.assertActive();
58
+ return HomeAssistantMapper.toEntities(this.client.getSnapshot(), this.client.connected, this.targetScope);
59
+ });
57
60
  }
58
61
 
59
62
  public async subscribe(
60
- handlerArg: (eventArg: IIntegrationEvent) => void,
63
+ handlerArg: (eventArg: IIntegrationEvent) => void | Promise<void>,
61
64
  abortSignalArg?: AbortSignal
62
65
  ): Promise<() => Promise<void>> {
63
66
  this.assertActive();
@@ -65,32 +68,14 @@ export class HomeAssistantRuntime implements IIntegrationRuntime {
65
68
  throw abortError(abortSignalArg);
66
69
  }
67
70
 
68
- let subscribed = true;
69
- const unsubscribeClient = this.client.subscribe(() => {
70
- handlerArg({
71
+ if (typeof handlerArg !== 'function') throw new TypeError('Home Assistant subscriber must be a function.');
72
+ return this.client.subscribe(() => {
73
+ return handlerArg({
71
74
  type: 'state_changed',
72
75
  integrationDomain: this.domain,
73
76
  timestamp: Date.now(),
74
77
  });
75
- });
76
- const unsubscribe = (): void => {
77
- if (!subscribed) {
78
- return;
79
- }
80
- subscribed = false;
81
- this.subscriptions.delete(unsubscribe);
82
- abortSignalArg?.removeEventListener('abort', unsubscribe);
83
- unsubscribeClient();
84
- };
85
- this.subscriptions.add(unsubscribe);
86
- abortSignalArg?.addEventListener('abort', unsubscribe, { once: true });
87
- if (abortSignalArg?.aborted) {
88
- unsubscribe();
89
- }
90
-
91
- return async () => {
92
- unsubscribe();
93
- };
78
+ }, abortSignalArg);
94
79
  }
95
80
 
96
81
  public async writeFeature(
@@ -101,6 +86,19 @@ export class HomeAssistantRuntime implements IIntegrationRuntime {
101
86
  if (abortSignalArg?.aborted) {
102
87
  throw abortError(abortSignalArg);
103
88
  }
89
+ homeAssistantRecord(requestArg);
90
+ homeAssistantRecord(requestArg.device);
91
+ homeAssistantRecord(requestArg.feature);
92
+ if (requestArg.device.metadata !== undefined) homeAssistantRecord(requestArg.device.metadata);
93
+ const request = structuredClone(requestArg);
94
+ return this.client.withOperation((sessionArg) => this.writeAdmitted(request, sessionArg), abortSignalArg);
95
+ }
96
+
97
+ private async writeAdmitted(
98
+ requestArg: IDeviceFeatureWriteRequest,
99
+ sessionArg: IHomeAssistantOperationSession
100
+ ): Promise<IServiceCallResult> {
101
+ this.assertActive();
104
102
  if (Object.keys(homeAssistantRecord(requestArg)).some((key) => !['device', 'feature', 'value'].includes(key))) {
105
103
  return { success: false, error: 'Home Assistant feature requests contain unsupported fields.' };
106
104
  }
@@ -145,10 +143,8 @@ export class HomeAssistantRuntime implements IIntegrationRuntime {
145
143
  return { success: false, error: mappedCall.error || 'Unsupported Home Assistant write.' };
146
144
  }
147
145
 
148
- const data = await this.client.callService(mappedCall.request, abortSignalArg);
149
- if (abortSignalArg?.aborted) {
150
- throw abortError(abortSignalArg);
151
- }
146
+ const data = await sessionArg.callService(mappedCall.request);
147
+ sessionArg.signal.throwIfAborted();
152
148
  return {
153
149
  success: true,
154
150
  data: {
@@ -159,34 +155,25 @@ export class HomeAssistantRuntime implements IIntegrationRuntime {
159
155
  }
160
156
 
161
157
  private assertActive(): void {
158
+ this.lease?.assertOpen();
162
159
  if (this.destroyed) throw new Error('Home Assistant runtime is destroyed.');
163
160
  }
164
161
 
165
162
  public destroy(): Promise<void> {
166
- if (this.destroyPromise) {
167
- return this.destroyPromise;
168
- }
169
- if (this.cleanupComplete) {
170
- return Promise.resolve();
163
+ if (!this.destroyPromise) {
164
+ this.destroyed = true;
165
+ this.destroyPromise = Promise.resolve().then(() => this.performDestroy());
171
166
  }
172
- this.destroyed = true;
173
- const destroyPromise = this.performDestroy();
174
- this.destroyPromise = destroyPromise;
175
- void destroyPromise.then(() => {
176
- this.cleanupComplete = true;
177
- }).finally(() => {
178
- if (this.destroyPromise === destroyPromise) {
179
- this.destroyPromise = undefined;
180
- }
181
- }).catch(() => undefined);
182
- return destroyPromise;
167
+ return this.destroyPromise;
168
+ }
169
+
170
+ public retryCleanup(): Promise<void> {
171
+ if (!this.destroyPromise) return Promise.reject(new Error('Home Assistant cleanup retry requires destruction.'));
172
+ return this.owner ? this.owner.retryCleanup() : this.client.retryCleanup();
183
173
  }
184
174
 
185
175
  private async performDestroy(): Promise<void> {
186
- for (const unsubscribe of [...this.subscriptions]) {
187
- unsubscribe();
188
- }
189
- this.subscriptions.clear();
190
- await this.client.destroy();
176
+ // The client closes transport before joining its owned subscriber continuations.
177
+ await (this.lease ? this.lease.close() : this.client.destroy());
191
178
  }
192
179
  }
@@ -1,3 +1,4 @@
1
+ import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
1
2
  import type * as homeAssistantWebsocket from 'home-assistant-js-websocket';
2
3
 
3
4
  export type THomeAssistantVacuumCommand =
@@ -8,6 +9,10 @@ export type THomeAssistantVacuumCommand =
8
9
  | 'locate'
9
10
  | 'clean_spot';
10
11
 
12
+ export interface IHomeAssistantIntegrationOptions extends INativeIntegrationOwnerOptions {
13
+ readonly webSocketFactory?: THomeAssistantWebSocketFactory;
14
+ }
15
+
11
16
  export interface IHomeAssistantHomeassistantConfig {
12
17
  homeId: string;
13
18
  baseUrl: string;
@@ -72,12 +77,23 @@ export interface IHomeAssistantClient {
72
77
  getEntityPlatforms(): Record<string, string>;
73
78
  getServices(): ReadonlySet<string>;
74
79
  getTargetScope(): IHomeAssistantTargetScope;
75
- subscribe(handlerArg: () => void): () => void;
80
+ subscribe(handlerArg: () => void | Promise<void>, abortSignalArg?: AbortSignal): () => Promise<void>;
81
+ withOperation<TResult>(
82
+ workArg: (sessionArg: IHomeAssistantOperationSession) => Promise<TResult>,
83
+ abortSignalArg?: AbortSignal
84
+ ): Promise<TResult>;
76
85
  callService(
77
86
  requestArg: IHomeAssistantServiceCall,
78
87
  abortSignalArg?: AbortSignal
79
88
  ): Promise<unknown>;
80
89
  destroy(): Promise<void>;
90
+ retryCleanup(): Promise<void>;
91
+ }
92
+
93
+ /** A service session remains usable only inside its admitted continuation. */
94
+ export interface IHomeAssistantOperationSession {
95
+ readonly signal: AbortSignal;
96
+ callService(requestArg: IHomeAssistantServiceCall): Promise<unknown>;
81
97
  }
82
98
 
83
99
  export interface IHomeAssistantTargetScope {
@@ -1,9 +1,9 @@
1
1
  # Home Assistant runtime
2
2
 
3
- `HomeAssistantHomeassistantIntegration` connects directly to Home Assistant's authenticated `/api/websocket` endpoint through the installed official `home-assistant-js-websocket` client and Node's native WebSocket. Its existing optional WebSocket-factory constructor remains available. Production contains no fixture entities or credentials.
3
+ `HomeAssistantHomeassistantIntegration` connects directly to Home Assistant's authenticated `/api/websocket` endpoint through the installed official `home-assistant-js-websocket` client and Node's native WebSocket. Its existing WebSocket-factory constructor remains available; an options object additionally accepts `homeId`, `capacity` and `webSocketFactory`. Production contains no fixture entities or credentials.
4
4
 
5
5
  ```typescript
6
- const integration = new HomeAssistantHomeassistantIntegration();
6
+ const integration = new HomeAssistantHomeassistantIntegration({ capacity: 16 });
7
7
  const runtime = await integration.setup({
8
8
  homeId: 'house-a',
9
9
  baseUrl: 'https://ha.example.test:8123',
@@ -14,15 +14,19 @@ const runtime = await integration.setup({
14
14
  setupRetry: 0,
15
15
  initialSnapshotTimeoutMs: 10_000,
16
16
  serviceCallTimeoutMs: 15_000,
17
- }, { abortSignal });
17
+ }, { homeId: 'house-a', abortSignal });
18
18
  try {
19
19
  const devices = await runtime.devices();
20
20
  // Send selected advertised features through the hub's scoped tool boundary.
21
21
  } finally {
22
- await runtime.destroy();
22
+ await integration.destroy();
23
23
  }
24
24
  ```
25
25
 
26
+ Setup requires an independent host-selected `context.homeId`, even if the constructor constrains a home. Configuration home is an exact consistency check. The first admitted home remains fixed after failed readiness; context is frozen before allocation. An optional borrowed `NoSqlConfigStore` must be a real instance with exactly that home and the `homeassistant` domain; HA never closes it, its database, logger or caller-owned signal.
27
+
28
+ For a host-owned encrypted config store, pass `{ homeId: selectedHome, configStore: homeAssistantStore, abortSignal }` as context. The store's scope must be `{ homeId: selectedHome, integrationDomain: 'homeassistant' }`. The host closes that store after its integrations finish. Supplying a store does not replace the required inline runtime token or create a credential-reference API.
29
+
26
30
  `homeId`, an HTTP(S) origin without user information, path, query or fragment, and a nonempty configured access token are required. Home IDs contain 1–128 alphanumeric, underscore, period, colon or hyphen characters and begin with an alphanumeric character. The exact configured ID is preserved, including case and colons in IDs such as `home:studio` and `home:unconfigured`; it is never trimmed or renamed. Optional entity IDs and domains form intersecting allowlists. Credentials stay in runtime configuration; a caller that persists configuration must use the core encrypted NoSql config store and its external key provider. This integration writes no application data to the filesystem.
27
31
 
28
32
  Setup uses one deadline for authentication, subscription acknowledgements, an authoritative `get_states` snapshot, `get_services`, and `config/entity_registry/list_for_display`. Registry provenance is fetched for selected entities even when strict registry enforcement is disabled. Strict enforcement additionally requires every explicitly configured entity to exist in the state and registry snapshots. Home Assistant must support these documented commands; an unsupported or malformed response rejects setup.
@@ -33,8 +37,20 @@ Supported writes are exact light/switch/input-boolean `turn_on` and `turn_off`,
33
37
 
34
38
  `state_changed` updates are authoritative. Service acknowledgements never synthesize state. `service_registered`, `service_removed` and `entity_registry_updated` events revoke snapshot readiness until fresh state, service and registry snapshots complete. Reconnects do the same before health or writes recover. Missing required entities, offline entities, malformed states or inconsistent event targets block controls. A malformed state event closes its connection and leaves retained devices offline.
35
39
 
36
- Snapshots and buffered events are bounded at 10,000 entities, services at 10,000 actions and 1,000 domains, and entity attributes at depth 16 and 4,096 values. Caller aborts and write deadlines cancel the pending command through the owning connection; commands are never retried or replayed by this adapter. Cancellation after dispatch leaves the physical effect uncertain. Destroy aborts pending callers, releases all subscriptions, closes sockets, waits for physical closure up to one second, clears retained data and makes the runtime terminal. Failed cleanup remains retryable through `destroy()`.
40
+ Snapshots and buffered events are bounded at 10,000 entities, services at 10,000 actions and 1,000 domains, and entity attributes at depth 16 and 4,096 values. Caller aborts and write deadlines cancel the pending command through the owning connection; commands are never retried or replayed by this adapter. Cancellation after dispatch leaves the physical effect uncertain. Destroy aborts pending callers, releases all subscriptions, closes sockets, waits for physical closure up to one second, clears retained data and makes the runtime terminal. Repeated `destroy()` calls return the same permanent result, including a failure. `retryCleanup()` retries only failed cleanup handles and never reopens, authenticates or replays a command. Integration `destroy()` drains setup and all promoted runtimes, returns one permanent result and rejects reopening; integration `retryCleanup()` performs cleanup only. Runtime destruction closes its setup lease. Cancellation of the setup context remains attached after promotion. A shared capacity (default 16) counts pending work, live runtimes and retained cleanup; rejection happens before allocation. The exported controlling client also requires independent context: `new HomeAssistantClient(config, { homeId: selectedHome }, api?, webSocketFactory?)`.
41
+
42
+ Close joins actual connection, subscription, refresh and service settlement even when caller cancellation has already rejected. A late handle remains owned until its cleanup completes. Sibling cleanup proceeds even when one unsubscribe fails.
43
+
44
+ Runtime reads, feature writes, direct service calls and subscriber delivery use the same owner capacity as live runtimes. `client.withOperation(async (session) => ...)` owns the entire continuation and provides `session.signal` and a scoped `session.callService(request)`; a session cannot dispatch after that continuation finishes. Caller cancellation rejects promptly while the actual continuation and any dispatched service promise still occupy capacity until settlement. Requests are captured before deferred admission and validated against current authoritative state at dispatch.
45
+
46
+ Subscribers may return `Promise<void>`. The returned unsubscribe function fences new and queued delivery immediately, returns one stable promise and joins callbacks already running. Runtime and integration close shut down transport before joining callbacks. Synchronous and asynchronous callback failures, and rejected callback admission, remain observable through unsubscribe and close; a failed subscriber stops receiving events while siblings continue. Explicit cleanup retry clears reported callback failures and retries retained cleanup without replaying callbacks or commands. A callback must finish its own work before awaiting destruction of its owning runtime.
47
+
48
+ The local test fixtures use real HTTP upgrades and native WebSockets with test-only tokens. Their delayed replies preserve the result captured when a command was received. They verify exact request bytes, acknowledgement gates, catalogue changes, registry refresh, reconnect readiness, authoritative state, malformed inputs, cross-home and mutated-target rejection, shared capacity, callback drainage, cancellation and physical closure. The five suites contain 62 tests: injected behavior 20, native protocol 15, lifecycle 6, native ownership 9, finite operations/subscribers 12. The domain has seven production TypeScript roots and seven test/support roots for strict type and emit checks.
49
+
50
+ Qualification commands are `pnpm exec tstest test/homeassistant/ --verbose --logfile --timeout 60`, `pnpm run build`, and `pnpm exec tsbuild check 'test/**/*'` without `skipLibCheck`. For the domain's 14 roots, use the installed `@git.zone/tsbuild` `TsCompiler.checkTypes(roots, { strict: true })` and `checkEmit(roots, { strict: true })`; the package build also covers the shared owner and generated domains.
51
+
52
+ One upstream contract remains blocked. In installed `home-assistant-js-websocket` 9.7.0, terminal `Connection.close()` leaves an unacknowledged `subscribeEvents()` promise pending forever. `Connection._handleClose` preserves subscription commands for reconnect before checking `closeRequested`, then stops reconnecting. A fixture probe sent one subscription, closed its socket and observed no subscription settlement. The published 9.7.1 artifact was inspected on 2026-10-11 and retains the same implementation. HA's actual-work ledger deliberately continues owning that promise; shutdown during an unacknowledged subscription cannot be qualified as complete. The proper path is a fix and published release in the official client that rejects unresolved subscriptions on terminal close, followed by a published consumer update and native terminal-subscription regressions. No local shim, command replay or fabricated settlement is used.
37
53
 
38
- The local test fixtures use real HTTP upgrades and native WebSockets with test-only tokens. They verify exact request bytes, catalogue changes, registry refresh, reconnect readiness, authoritative state, malformed inputs, cross-home and mutated-target rejection, cancellation and physical closure. Hardware acceptance against the owner's Home Assistant remains outside this lane.
54
+ Real Home Assistant authentication, registry compatibility, credential revocation, commissioning/control and physical shutdown acceptance remain owner-gated. Credential references and config flow, explicit control/plaintext consent, public offline-mapper defaults and `reconnect-error` reporting remain separate public-contract decisions.
39
55
 
40
56
  API references: [Home Assistant WebSocket API](https://developers.home-assistant.io/docs/api/websocket/), [REST API](https://developers.home-assistant.io/docs/api/rest/), and [official JavaScript client](https://github.com/home-assistant/home-assistant-js-websocket).
@@ -1,6 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { IHueConfig, IHueEvent, IHueLightStatePatch, IHueResourceResponse } from './hue.types.js';
3
- import { hueContentType, hueUuid, normalizeHueConfig, parseHueEnvelope, parseHueEvents, parseHueResources, validateHuePatch } from './hue.validation.js';
3
+ import { hueContentType, hueRecord, hueUuid, normalizeHueConfig, parseHueEnvelope, parseHueEvents, parseHueResources, validateHuePatch } from './hue.validation.js';
4
4
 
5
5
  interface IHueConnection {
6
6
  cancel(): void;
@@ -49,7 +49,11 @@ export class HueClient {
49
49
  readonly #agent: plugins.https.Agent;
50
50
  readonly #lifetime = new AbortController();
51
51
  readonly #connections = new Set<IHueConnection>();
52
+ readonly #operations = new Set<Promise<unknown>>();
53
+ readonly #callbackFailures = new Set<Error>();
52
54
  #destroyPromise?: Promise<void>;
55
+ #retryPromise?: Promise<void>;
56
+ #agentClosed = false;
53
57
  #streamCount = 0;
54
58
  #healthy = false;
55
59
  #eventFailure = false;
@@ -62,10 +66,14 @@ export class HueClient {
62
66
  }
63
67
 
64
68
  public isHealthy(): boolean {
65
- return !this.#lifetime.signal.aborted && this.#healthy && !this.#eventFailure;
69
+ return !this.#lifetime.signal.aborted && this.#healthy && !this.#eventFailure && this.#callbackFailures.size === 0;
66
70
  }
67
71
 
68
- public async getResources(abortSignalArg?: AbortSignal): Promise<IHueResourceResponse> {
72
+ public getResources(abortSignalArg?: AbortSignal): Promise<IHueResourceResponse> {
73
+ return this.run(() => this.readResources(abortSignalArg), abortSignalArg);
74
+ }
75
+
76
+ private async readResources(abortSignalArg?: AbortSignal): Promise<IHueResourceResponse> {
69
77
  try {
70
78
  const data = parseHueEnvelope(await this.request('GET', '/clip/v2/resource', undefined, abortSignalArg));
71
79
  const resources = parseHueResources(data, this.#config.bridgeId);
@@ -76,27 +84,36 @@ export class HueClient {
76
84
 
77
85
  public async setLightState(lightIdArg: string, stateArg: IHueLightStatePatch, abortSignalArg?: AbortSignal): Promise<void> {
78
86
  if (!hueUuid(lightIdArg)) throw new Error('Hue light target must be a resource UUID.');
79
- validateHuePatch(stateArg);
87
+ const captured = hueRecord(stateArg);
88
+ const state = { on: captured.on, brightness: captured.brightness } as IHueLightStatePatch;
89
+ validateHuePatch(captured as IHueLightStatePatch);
80
90
  const lightId = lightIdArg.toLowerCase();
81
- const state = { ...stateArg };
82
- const snapshot = await this.getResources(abortSignalArg);
83
- const light = snapshot.lights.find((lightArg) => lightArg.id === lightId);
84
- if (!light || !light.available) throw new Error('Hue light target is unknown or unavailable.');
85
- if (state.brightness !== undefined && !light.dimming) throw new Error('Hue light does not support brightness.');
86
- const body = { ...(state.on === undefined ? {} : { on: { on: state.on } }),
87
- ...(state.brightness === undefined ? {} : { dimming: { brightness: state.brightness } }) };
88
- const data = parseHueEnvelope(await this.request('PUT', `/clip/v2/resource/light/${lightId}`, body, abortSignalArg));
89
- if (data.length !== 1) throw new Error('Hue write acknowledgement does not identify the target light.');
90
- const acknowledgement = data[0];
91
- if (!acknowledgement || typeof acknowledgement !== 'object' || Array.isArray(acknowledgement)
92
- || !('rid' in acknowledgement) || typeof acknowledgement.rid !== 'string' || acknowledgement.rid.toLowerCase() !== lightId
93
- || !('rtype' in acknowledgement) || acknowledgement.rtype !== 'light') {
94
- throw new Error('Hue write acknowledgement does not identify the target light.');
95
- }
91
+ return this.run(async () => {
92
+ const snapshot = await this.readResources(abortSignalArg);
93
+ const light = snapshot.lights.find((lightArg) => lightArg.id === lightId);
94
+ if (!light || !light.available) throw new Error('Hue light target is unknown or unavailable.');
95
+ if (state.brightness !== undefined && !light.dimming) throw new Error('Hue light does not support brightness.');
96
+ const body = { ...(state.on === undefined ? {} : { on: { on: state.on } }),
97
+ ...(state.brightness === undefined ? {} : { dimming: { brightness: state.brightness } }) };
98
+ const data = parseHueEnvelope(await this.request('PUT', `/clip/v2/resource/light/${lightId}`, body, abortSignalArg));
99
+ if (data.length !== 1) throw new Error('Hue write acknowledgement does not identify the target light.');
100
+ const acknowledgement = data[0];
101
+ if (!acknowledgement || typeof acknowledgement !== 'object' || Array.isArray(acknowledgement)
102
+ || !('rid' in acknowledgement) || typeof acknowledgement.rid !== 'string' || acknowledgement.rid.toLowerCase() !== lightId
103
+ || !('rtype' in acknowledgement) || acknowledgement.rtype !== 'light') {
104
+ throw new Error('Hue write acknowledgement does not identify the target light.');
105
+ }
106
+ }, abortSignalArg);
96
107
  }
97
108
 
98
- public async subscribeToEvents(handlerArg: (eventArg: IHueEvent) => void,
99
- onErrorArg: (errorArg: Error) => void, abortSignalArg?: AbortSignal): Promise<() => Promise<void>> {
109
+ public subscribeToEvents(handlerArg: (eventArg: IHueEvent) => unknown,
110
+ onErrorArg: (errorArg: Error) => unknown, abortSignalArg?: AbortSignal): Promise<() => Promise<void>> {
111
+ if (typeof handlerArg !== 'function' || typeof onErrorArg !== 'function') throw new TypeError('Hue subscribers must be functions.');
112
+ return this.run(() => this.subscribe(handlerArg, onErrorArg, abortSignalArg), abortSignalArg);
113
+ }
114
+
115
+ private async subscribe(handlerArg: (eventArg: IHueEvent) => unknown,
116
+ onErrorArg: (errorArg: Error) => unknown, abortSignalArg?: AbortSignal): Promise<() => Promise<void>> {
100
117
  this.assertOpen(abortSignalArg);
101
118
  if (this.#streamCount >= 2) throw new Error('Hue event subscription limit reached.');
102
119
  this.#streamCount++;
@@ -106,6 +123,17 @@ export class HueClient {
106
123
  const signal = AbortSignal.any([this.#lifetime.signal, abort.signal, ...(abortSignalArg ? [abortSignalArg] : [])]);
107
124
  let active = true;
108
125
  signal.addEventListener('abort', () => { active = false; }, { once: true });
126
+ const callbacks = new Set<Promise<void>>();
127
+ const invoke = (workArg: () => unknown, reportArg: boolean): void => {
128
+ const callback = Promise.resolve().then(workArg).then(() => undefined, () => {
129
+ const error = new Error('Hue subscriber callback failed.');
130
+ if (!this.#callbackFailures.size) this.#callbackFailures.add(error);
131
+ this.#eventFailure = true;
132
+ if (reportArg) fail(error);
133
+ });
134
+ callbacks.add(callback);
135
+ void callback.then(() => callbacks.delete(callback));
136
+ };
109
137
  let established = false;
110
138
  let request: ReturnType<typeof plugins.https.request> | undefined;
111
139
  const connection: IHueConnection = { closed, cancel: () => { active = false; abort.abort(); request?.destroy(); } };
@@ -119,7 +147,7 @@ export class HueClient {
119
147
  active = false;
120
148
  abort.abort();
121
149
  request?.destroy();
122
- if (established) onErrorArg(errorArg);
150
+ if (established) invoke(() => onErrorArg(errorArg), false);
123
151
  };
124
152
  const ready = new Promise<void>((resolveArg, rejectArg) => {
125
153
  const deadline = setTimeout(() => {
@@ -145,12 +173,16 @@ export class HueClient {
145
173
  resolveArg();
146
174
  responseArg.on('data', (chunkArg: Buffer) => {
147
175
  if (!active) return;
148
- try {
149
- for (const event of decoder.feed(chunkArg)) {
150
- if (active) handlerArg(event);
176
+ let events: IHueEvent[];
177
+ try { events = decoder.feed(chunkArg); }
178
+ catch { settleFailure(new Error('Hue event stream contains invalid or oversized data.')); return; }
179
+ for (const event of events) {
180
+ if (!active) break;
181
+ if (callbacks.size >= 32) {
182
+ settleFailure(new Error('Hue subscriber callback capacity reached.'));
183
+ break;
151
184
  }
152
- } catch {
153
- settleFailure(new Error('Hue event stream contains invalid or oversized data.'));
185
+ invoke(() => handlerArg(event), true);
154
186
  }
155
187
  });
156
188
  responseArg.once('error', () => settleFailure(new Error('Hue event stream transport failed.')));
@@ -160,9 +192,12 @@ export class HueClient {
160
192
  request.once('error', () => settleFailure(new Error('Hue event stream transport failed.')));
161
193
  request.once('close', () => {
162
194
  clearTimeout(deadline);
163
- this.#connections.delete(connection);
164
- this.#streamCount--;
165
- resolveClosed();
195
+ active = false;
196
+ void Promise.allSettled([...callbacks]).then(() => {
197
+ this.#connections.delete(connection);
198
+ this.#streamCount--;
199
+ resolveClosed();
200
+ });
166
201
  if (!established) rejectArg(new Error('Hue event subscription was cancelled.'));
167
202
  });
168
203
  request.end();
@@ -175,28 +210,62 @@ export class HueClient {
175
210
  await closed;
176
211
  throw error;
177
212
  }
178
- return async () => { connection.cancel(); await closed; };
213
+ let closePromise: Promise<void> | undefined;
214
+ return () => {
215
+ closePromise ??= Promise.resolve().then(async () => { connection.cancel(); await closed; });
216
+ return closePromise;
217
+ };
179
218
  }
180
219
 
181
220
  public destroy(): Promise<void> {
182
- if (this.#destroyPromise) return this.#destroyPromise;
183
- this.setupSignal?.removeEventListener('abort', this.abortSetup);
184
- this.#lifetime.abort();
185
- this.#destroyPromise = this.drain();
221
+ if (!this.#destroyPromise) {
222
+ this.#destroyPromise = Promise.resolve().then(() => this.drain());
223
+ this.setupSignal?.removeEventListener('abort', this.abortSetup);
224
+ this.#lifetime.abort();
225
+ }
186
226
  return this.#destroyPromise;
187
227
  }
188
228
 
189
- private readonly abortSetup = (): void => { void this.destroy(); };
229
+ /** Retries only failed cleanup and acknowledges settled callback failures; never replays callbacks or HTTPS work. */
230
+ public retryCleanup(): Promise<void> {
231
+ if (!this.#destroyPromise) throw new Error('Hue cleanup retry requires a terminal client.');
232
+ if (!this.#retryPromise) {
233
+ this.#retryPromise = Promise.resolve().then(async () => {
234
+ await this.#destroyPromise!.catch(() => undefined);
235
+ this.#callbackFailures.clear();
236
+ await this.drain();
237
+ });
238
+ const settled = (): void => { this.#retryPromise = undefined; };
239
+ void this.#retryPromise.then(settled, settled);
240
+ }
241
+ return this.#retryPromise;
242
+ }
243
+
244
+ private readonly abortSetup = (): void => { void this.destroy().catch(() => undefined); };
190
245
 
191
246
  private async drain(): Promise<void> {
192
- const connections = [...this.#connections];
193
- for (const connection of connections) connection.cancel();
194
- this.#agent.destroy();
195
- await Promise.all(connections.map((connectionArg) => connectionArg.closed));
247
+ const results = await Promise.allSettled([
248
+ ...[...this.#connections].map(async (connectionArg) => { connectionArg.cancel(); await connectionArg.closed; }),
249
+ Promise.resolve().then(() => { if (!this.#agentClosed) { this.#agent.destroy(); this.#agentClosed = true; } }),
250
+ ]);
251
+ await Promise.allSettled([...this.#operations]);
252
+ const failures = results.filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected')
253
+ .map(() => new Error('Hue transport cleanup failed.'));
254
+ failures.push(...this.#callbackFailures);
255
+ if (failures.length) throw new AggregateError(failures, 'Hue cleanup failed; explicit cleanup retry is required.');
256
+ }
257
+
258
+ private run<TResult>(workArg: () => Promise<TResult>, signalArg?: AbortSignal): Promise<TResult> {
259
+ this.assertOpen(signalArg);
260
+ if (this.#operations.size >= 32) throw new Error('Hue concurrent operation limit reached.');
261
+ const operation = Promise.resolve().then(() => { this.assertOpen(signalArg); return workArg(); });
262
+ this.#operations.add(operation);
263
+ void operation.then(() => this.#operations.delete(operation), () => this.#operations.delete(operation));
264
+ return operation;
196
265
  }
197
266
 
198
267
  private assertOpen(abortSignalArg?: AbortSignal): void {
199
- if (this.#lifetime.signal.aborted || abortSignalArg?.aborted) throw new Error('Hue client operation was cancelled or the client is closed.');
268
+ if (this.#destroyPromise || this.#lifetime.signal.aborted || abortSignalArg?.aborted) throw new Error('Hue client operation was cancelled or the client is closed.');
200
269
  if (this.#connections.size >= 32) throw new Error('Hue concurrent request limit reached.');
201
270
  }
202
271
 
@@ -1,14 +1,18 @@
1
1
  import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
2
2
  import { HueClient } from './hue.classes.client.js';
3
- import { hueBridgeId, normalizeHueConfig, normalizeHueHost } from './hue.validation.js';
3
+ import { hueBridgeId, hueRecord, normalizeHueConfig, normalizeHueHost } from './hue.validation.js';
4
4
  import type { IHueConfig } from './hue.types.js';
5
5
 
6
6
  export class HueConfigFlow implements IConfigFlow<IHueConfig> {
7
7
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IHueConfig>> {
8
8
  void contextArg;
9
- const bridgeId = hueBridgeId(candidateArg.id) ? candidateArg.id : undefined;
9
+ const candidate = hueRecord(candidateArg);
10
+ if (candidate.integrationDomain !== 'hue') throw new Error('Hue flow requires a Hue candidate.');
11
+ const bridgeId = hueBridgeId(candidate.id) ? candidate.id : undefined;
12
+ const port = candidate.port;
13
+ if (port !== undefined && typeof port !== 'number') throw new Error('Hue discovered port is invalid.');
10
14
  let host: string | undefined;
11
- try { host = normalizeHueHost(candidateArg.host); } catch { host = undefined; }
15
+ try { host = normalizeHueHost(candidate.host); } catch { host = undefined; }
12
16
  return {
13
17
  kind: 'form', title: 'Connect Philips Hue Bridge',
14
18
  description: 'Provide a bridge application key created with the physical link button. For Hue private-CA certificates, provide the trusted Hue CA from the developer portal. Setup verifies the TLS identity and live bridge resources.',
@@ -21,9 +25,11 @@ export class HueConfigFlow implements IConfigFlow<IHueConfig> {
21
25
  submit: async (valuesArg) => {
22
26
  let client: HueClient | undefined;
23
27
  try {
24
- const config = normalizeHueConfig({ bridgeId: bridgeId ?? valuesArg.bridgeId as string,
25
- host: host ?? valuesArg.host as string, port: candidateArg.port ?? 443,
26
- appKey: valuesArg.appKey as string, trustedCa: valuesArg.trustedCa === '' ? undefined : valuesArg.trustedCa as string | undefined });
28
+ const values = hueRecord(valuesArg);
29
+ if (Reflect.ownKeys(values).some((keyArg) => !['bridgeId', 'host', 'appKey', 'trustedCa'].includes(String(keyArg)))) throw new Error('Hue flow values contain unsupported fields.');
30
+ const config = normalizeHueConfig({ bridgeId: bridgeId ?? values.bridgeId as string,
31
+ host: host ?? values.host as string, port: port ?? 443,
32
+ appKey: values.appKey as string, trustedCa: values.trustedCa === '' ? undefined : values.trustedCa as string | undefined });
27
33
  client = new HueClient(config);
28
34
  await client.getResources();
29
35
  return { kind: 'done', title: 'Hue bridge verified', config };