@smarthome.exchange/integrations 2.0.0 → 4.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 (251) hide show
  1. package/changelog.md +46 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/classes.nativeintegrationowner.d.ts +2 -0
  4. package/dist_ts/core/classes.nativeintegrationowner.js +5 -1
  5. package/dist_ts/core/classes.runtimemanager.d.ts +2 -0
  6. package/dist_ts/core/classes.runtimemanager.js +11 -3
  7. package/dist_ts/core/types.d.ts +2 -0
  8. package/dist_ts/integrations/deconz/deconz.classes.client.d.ts +37 -29
  9. package/dist_ts/integrations/deconz/deconz.classes.client.js +521 -294
  10. package/dist_ts/integrations/deconz/deconz.classes.configflow.d.ts +6 -1
  11. package/dist_ts/integrations/deconz/deconz.classes.configflow.js +63 -33
  12. package/dist_ts/integrations/deconz/deconz.classes.integration.d.ts +4 -3
  13. package/dist_ts/integrations/deconz/deconz.classes.integration.js +85 -40
  14. package/dist_ts/integrations/deconz/deconz.mapper.d.ts +1 -0
  15. package/dist_ts/integrations/deconz/deconz.mapper.js +24 -6
  16. package/dist_ts/integrations/deconz/deconz.types.d.ts +43 -6
  17. package/dist_ts/integrations/deconz/deconz.validation.d.ts +7 -1
  18. package/dist_ts/integrations/deconz/deconz.validation.js +215 -4
  19. package/dist_ts/integrations/esphome/esphome.classes.client.d.ts +19 -7
  20. package/dist_ts/integrations/esphome/esphome.classes.client.js +183 -89
  21. package/dist_ts/integrations/esphome/esphome.classes.configflow.d.ts +5 -2
  22. package/dist_ts/integrations/esphome/esphome.classes.configflow.js +43 -33
  23. package/dist_ts/integrations/esphome/esphome.classes.integration.d.ts +8 -6
  24. package/dist_ts/integrations/esphome/esphome.classes.integration.js +71 -43
  25. package/dist_ts/integrations/esphome/esphome.discovery.js +4 -12
  26. package/dist_ts/integrations/esphome/esphome.mapper.d.ts +2 -2
  27. package/dist_ts/integrations/esphome/esphome.mapper.js +6 -4
  28. package/dist_ts/integrations/esphome/esphome.types.d.ts +31 -6
  29. package/dist_ts/integrations/esphome/esphome.types.js +1 -1
  30. package/dist_ts/integrations/esphome/esphome.validation.d.ts +7 -0
  31. package/dist_ts/integrations/esphome/esphome.validation.js +66 -0
  32. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.d.ts +32 -4
  33. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.js +345 -116
  34. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.d.ts +8 -5
  35. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.js +34 -8
  36. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.d.ts +7 -4
  37. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.js +44 -56
  38. package/dist_ts/integrations/homeassistant/homeassistant.types.d.ts +12 -1
  39. package/dist_ts/integrations/hue/hue.classes.client.d.ts +32 -4
  40. package/dist_ts/integrations/hue/hue.classes.client.js +331 -57
  41. package/dist_ts/integrations/hue/hue.classes.configflow.d.ts +6 -1
  42. package/dist_ts/integrations/hue/hue.classes.configflow.js +79 -27
  43. package/dist_ts/integrations/hue/hue.classes.integration.d.ts +3 -1
  44. package/dist_ts/integrations/hue/hue.classes.integration.js +83 -62
  45. package/dist_ts/integrations/hue/hue.mapper.d.ts +2 -0
  46. package/dist_ts/integrations/hue/hue.mapper.js +8 -6
  47. package/dist_ts/integrations/hue/hue.types.d.ts +32 -1
  48. package/dist_ts/integrations/hue/hue.validation.d.ts +9 -3
  49. package/dist_ts/integrations/hue/hue.validation.js +112 -11
  50. package/dist_ts/integrations/mqtt/mqtt.classes.client.d.ts +15 -2
  51. package/dist_ts/integrations/mqtt/mqtt.classes.client.js +173 -23
  52. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.d.ts +5 -1
  53. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.js +53 -33
  54. package/dist_ts/integrations/mqtt/mqtt.classes.integration.d.ts +4 -1
  55. package/dist_ts/integrations/mqtt/mqtt.classes.integration.js +127 -56
  56. package/dist_ts/integrations/mqtt/mqtt.classes.session.d.ts +13 -0
  57. package/dist_ts/integrations/mqtt/mqtt.classes.session.js +130 -0
  58. package/dist_ts/integrations/mqtt/mqtt.mapper.d.ts +3 -0
  59. package/dist_ts/integrations/mqtt/mqtt.mapper.js +44 -1
  60. package/dist_ts/integrations/mqtt/mqtt.types.d.ts +46 -0
  61. package/dist_ts/integrations/mqtt/mqtt.validation.d.ts +7 -0
  62. package/dist_ts/integrations/mqtt/mqtt.validation.js +180 -0
  63. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.d.ts +4 -2
  64. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.js +41 -16
  65. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.d.ts +5 -0
  66. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.js +20 -7
  67. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.d.ts +1 -0
  68. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.js +53 -50
  69. package/dist_ts/integrations/samsungtv/samsungtv.types.d.ts +4 -1
  70. package/dist_ts/integrations/shelly/shelly.classes.client.d.ts +26 -30
  71. package/dist_ts/integrations/shelly/shelly.classes.client.js +504 -276
  72. package/dist_ts/integrations/shelly/shelly.classes.configflow.d.ts +8 -3
  73. package/dist_ts/integrations/shelly/shelly.classes.configflow.js +43 -38
  74. package/dist_ts/integrations/shelly/shelly.classes.integration.d.ts +6 -6
  75. package/dist_ts/integrations/shelly/shelly.classes.integration.js +103 -101
  76. package/dist_ts/integrations/shelly/shelly.classes.subscription.d.ts +4 -0
  77. package/dist_ts/integrations/shelly/shelly.classes.subscription.js +24 -2
  78. package/dist_ts/integrations/shelly/shelly.mapper.d.ts +1 -2
  79. package/dist_ts/integrations/shelly/shelly.mapper.js +21 -20
  80. package/dist_ts/integrations/shelly/shelly.rpc.js +4 -1
  81. package/dist_ts/integrations/shelly/shelly.types.d.ts +42 -2
  82. package/dist_ts/integrations/shelly/shelly.types.js +11 -2
  83. package/dist_ts/integrations/shelly/shelly.validation.d.ts +7 -0
  84. package/dist_ts/integrations/shelly/shelly.validation.js +79 -0
  85. package/dist_ts/integrations/sonos/sonos.classes.client.d.ts +79 -22
  86. package/dist_ts/integrations/sonos/sonos.classes.client.js +427 -139
  87. package/dist_ts/integrations/sonos/sonos.classes.configflow.d.ts +9 -2
  88. package/dist_ts/integrations/sonos/sonos.classes.configflow.js +26 -28
  89. package/dist_ts/integrations/sonos/sonos.classes.integration.d.ts +7 -6
  90. package/dist_ts/integrations/sonos/sonos.classes.integration.js +289 -224
  91. package/dist_ts/integrations/sonos/sonos.config.d.ts +6 -1
  92. package/dist_ts/integrations/sonos/sonos.config.js +23 -2
  93. package/dist_ts/integrations/sonos/sonos.mapper.d.ts +1 -1
  94. package/dist_ts/integrations/sonos/sonos.mapper.js +14 -9
  95. package/dist_ts/integrations/sonos/sonos.matchers.js +3 -3
  96. package/dist_ts/integrations/sonos/sonos.types.d.ts +26 -1
  97. package/dist_ts/integrations/tasmota/tasmota.classes.integration.d.ts +5 -0
  98. package/dist_ts/integrations/tasmota/tasmota.classes.integration.js +23 -7
  99. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.d.ts +19 -5
  100. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.js +115 -41
  101. package/dist_ts/integrations/tasmota/tasmota.config.js +23 -1
  102. package/dist_ts/integrations/tasmota/tasmota.credentials.d.ts +11 -0
  103. package/dist_ts/integrations/tasmota/tasmota.credentials.js +97 -0
  104. package/dist_ts/integrations/tasmota/tasmota.discovery.d.ts +7 -1
  105. package/dist_ts/integrations/tasmota/tasmota.discovery.js +108 -30
  106. package/dist_ts/integrations/tasmota/tasmota.mapper.d.ts +2 -2
  107. package/dist_ts/integrations/tasmota/tasmota.mapper.js +7 -7
  108. package/dist_ts/integrations/tasmota/tasmota.types.d.ts +41 -2
  109. package/dist_ts/integrations/tuya/tuya.classes.integration.d.ts +4 -1
  110. package/dist_ts/integrations/tuya/tuya.classes.integration.js +79 -110
  111. package/dist_ts/integrations/tuya/tuya.classes.runtime.d.ts +5 -2
  112. package/dist_ts/integrations/tuya/tuya.classes.runtime.js +73 -26
  113. package/dist_ts/integrations/tuya/tuya.mapper.js +2 -2
  114. package/dist_ts/integrations/tuya/tuya.types.d.ts +3 -0
  115. package/dist_ts/integrations/tuya/tuya.validation.d.ts +2 -2
  116. package/dist_ts/integrations/tuya/tuya.validation.js +6 -11
  117. package/dist_ts/integrations/webostv/webostv.classes.client.d.ts +6 -5
  118. package/dist_ts/integrations/webostv/webostv.classes.client.js +147 -76
  119. package/dist_ts/integrations/webostv/webostv.classes.configflow.d.ts +7 -2
  120. package/dist_ts/integrations/webostv/webostv.classes.configflow.js +36 -20
  121. package/dist_ts/integrations/webostv/webostv.classes.integration.d.ts +4 -4
  122. package/dist_ts/integrations/webostv/webostv.classes.integration.js +46 -54
  123. package/dist_ts/integrations/webostv/webostv.mapper.js +5 -5
  124. package/dist_ts/integrations/webostv/webostv.types.d.ts +16 -3
  125. package/dist_ts/integrations/webostv/webostv.types.js +1 -1
  126. package/dist_ts/integrations/webostv/webostv.validation.d.ts +3 -1
  127. package/dist_ts/integrations/webostv/webostv.validation.js +23 -1
  128. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.d.ts +6 -1
  129. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.js +18 -8
  130. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.d.ts +4 -1
  131. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.js +54 -6
  132. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.d.ts +12 -6
  133. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.js +73 -59
  134. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.d.ts +3 -2
  135. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.js +17 -12
  136. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.types.d.ts +31 -2
  137. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.d.ts +9 -1
  138. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.js +57 -7
  139. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.d.ts +10 -4
  140. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.js +167 -31
  141. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.d.ts +6 -1
  142. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.js +57 -35
  143. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.d.ts +3 -1
  144. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.js +59 -37
  145. package/dist_ts/integrations/zwave_js/zwave_js.discovery.js +3 -3
  146. package/dist_ts/integrations/zwave_js/zwave_js.mapper.js +41 -24
  147. package/dist_ts/integrations/zwave_js/zwave_js.types.d.ts +25 -3
  148. package/dist_ts/integrations/zwave_js/zwave_js.validation.d.ts +6 -2
  149. package/dist_ts/integrations/zwave_js/zwave_js.validation.js +52 -17
  150. package/dist_ts/plugins.d.ts +2 -1
  151. package/dist_ts/plugins.js +3 -2
  152. package/package.json +10 -10
  153. package/readme.md +25 -6
  154. package/ts/00_commitinfo_data.ts +1 -1
  155. package/ts/core/classes.nativeintegrationowner.ts +5 -0
  156. package/ts/core/classes.runtimemanager.ts +11 -2
  157. package/ts/core/types.ts +2 -0
  158. package/ts/integrations/deconz/deconz.classes.client.ts +348 -252
  159. package/ts/integrations/deconz/deconz.classes.configflow.ts +57 -29
  160. package/ts/integrations/deconz/deconz.classes.integration.ts +69 -37
  161. package/ts/integrations/deconz/deconz.mapper.ts +22 -5
  162. package/ts/integrations/deconz/deconz.types.ts +49 -6
  163. package/ts/integrations/deconz/deconz.validation.ts +177 -4
  164. package/ts/integrations/deconz/readme.md +15 -1
  165. package/ts/integrations/esphome/esphome.classes.client.ts +179 -94
  166. package/ts/integrations/esphome/esphome.classes.configflow.ts +40 -33
  167. package/ts/integrations/esphome/esphome.classes.integration.ts +63 -36
  168. package/ts/integrations/esphome/esphome.discovery.ts +3 -12
  169. package/ts/integrations/esphome/esphome.mapper.ts +9 -7
  170. package/ts/integrations/esphome/esphome.types.ts +35 -6
  171. package/ts/integrations/esphome/esphome.validation.ts +66 -0
  172. package/ts/integrations/esphome/readme.md +20 -6
  173. package/ts/integrations/homeassistant/homeassistant.classes.client.ts +353 -125
  174. package/ts/integrations/homeassistant/homeassistant.classes.integration.ts +35 -18
  175. package/ts/integrations/homeassistant/homeassistant.classes.runtime.ts +50 -63
  176. package/ts/integrations/homeassistant/homeassistant.types.ts +17 -1
  177. package/ts/integrations/homeassistant/readme.md +22 -6
  178. package/ts/integrations/hue/hue.classes.client.ts +288 -55
  179. package/ts/integrations/hue/hue.classes.configflow.ts +68 -25
  180. package/ts/integrations/hue/hue.classes.integration.ts +75 -54
  181. package/ts/integrations/hue/hue.mapper.ts +8 -5
  182. package/ts/integrations/hue/hue.types.ts +37 -1
  183. package/ts/integrations/hue/hue.validation.ts +102 -13
  184. package/ts/integrations/hue/readme.md +49 -12
  185. package/ts/integrations/mqtt/mqtt.classes.client.ts +136 -19
  186. package/ts/integrations/mqtt/mqtt.classes.configflow.ts +45 -32
  187. package/ts/integrations/mqtt/mqtt.classes.integration.ts +99 -38
  188. package/ts/integrations/mqtt/mqtt.classes.session.ts +118 -0
  189. package/ts/integrations/mqtt/mqtt.mapper.ts +43 -1
  190. package/ts/integrations/mqtt/mqtt.types.ts +52 -0
  191. package/ts/integrations/mqtt/mqtt.validation.ts +157 -0
  192. package/ts/integrations/mqtt/readme.md +12 -3
  193. package/ts/integrations/samsungtv/readme.md +11 -5
  194. package/ts/integrations/samsungtv/samsungtv.classes.client.ts +40 -15
  195. package/ts/integrations/samsungtv/samsungtv.classes.configflow.ts +21 -6
  196. package/ts/integrations/samsungtv/samsungtv.classes.integration.ts +53 -38
  197. package/ts/integrations/samsungtv/samsungtv.types.ts +4 -1
  198. package/ts/integrations/shelly/readme.md +22 -7
  199. package/ts/integrations/shelly/shelly.classes.client.ts +386 -269
  200. package/ts/integrations/shelly/shelly.classes.configflow.ts +43 -38
  201. package/ts/integrations/shelly/shelly.classes.integration.ts +92 -111
  202. package/ts/integrations/shelly/shelly.classes.subscription.ts +24 -1
  203. package/ts/integrations/shelly/shelly.mapper.ts +19 -20
  204. package/ts/integrations/shelly/shelly.rpc.ts +2 -0
  205. package/ts/integrations/shelly/shelly.types.ts +50 -2
  206. package/ts/integrations/shelly/shelly.validation.ts +76 -0
  207. package/ts/integrations/sonos/readme.md +117 -38
  208. package/ts/integrations/sonos/sonos.classes.client.ts +392 -123
  209. package/ts/integrations/sonos/sonos.classes.configflow.ts +25 -28
  210. package/ts/integrations/sonos/sonos.classes.integration.ts +211 -186
  211. package/ts/integrations/sonos/sonos.config.ts +24 -2
  212. package/ts/integrations/sonos/sonos.mapper.ts +13 -8
  213. package/ts/integrations/sonos/sonos.matchers.ts +2 -2
  214. package/ts/integrations/sonos/sonos.types.ts +30 -1
  215. package/ts/integrations/tasmota/readme.md +16 -5
  216. package/ts/integrations/tasmota/tasmota.classes.integration.ts +24 -6
  217. package/ts/integrations/tasmota/tasmota.classes.runtime.ts +112 -43
  218. package/ts/integrations/tasmota/tasmota.config.ts +17 -0
  219. package/ts/integrations/tasmota/tasmota.credentials.ts +92 -0
  220. package/ts/integrations/tasmota/tasmota.discovery.ts +87 -24
  221. package/ts/integrations/tasmota/tasmota.mapper.ts +6 -6
  222. package/ts/integrations/tasmota/tasmota.types.ts +43 -2
  223. package/ts/integrations/tuya/readme.md +52 -10
  224. package/ts/integrations/tuya/tuya.classes.integration.ts +69 -98
  225. package/ts/integrations/tuya/tuya.classes.runtime.ts +70 -24
  226. package/ts/integrations/tuya/tuya.mapper.ts +1 -1
  227. package/ts/integrations/tuya/tuya.types.ts +4 -0
  228. package/ts/integrations/tuya/tuya.validation.ts +5 -8
  229. package/ts/integrations/webostv/readme.md +115 -22
  230. package/ts/integrations/webostv/webostv.classes.client.ts +123 -50
  231. package/ts/integrations/webostv/webostv.classes.configflow.ts +35 -15
  232. package/ts/integrations/webostv/webostv.classes.integration.ts +44 -37
  233. package/ts/integrations/webostv/webostv.mapper.ts +4 -4
  234. package/ts/integrations/webostv/webostv.types.ts +18 -3
  235. package/ts/integrations/webostv/webostv.validation.ts +22 -1
  236. package/ts/integrations/zigbee2mqtt/readme.md +22 -27
  237. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.ts +16 -7
  238. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.ts +53 -6
  239. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.ts +62 -49
  240. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.ts +17 -11
  241. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.types.ts +35 -2
  242. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.ts +53 -7
  243. package/ts/integrations/zwave_js/readme.md +42 -9
  244. package/ts/integrations/zwave_js/zwave_js.classes.client.ts +155 -31
  245. package/ts/integrations/zwave_js/zwave_js.classes.configflow.ts +55 -31
  246. package/ts/integrations/zwave_js/zwave_js.classes.integration.ts +57 -35
  247. package/ts/integrations/zwave_js/zwave_js.discovery.ts +2 -2
  248. package/ts/integrations/zwave_js/zwave_js.mapper.ts +37 -23
  249. package/ts/integrations/zwave_js/zwave_js.types.ts +26 -3
  250. package/ts/integrations/zwave_js/zwave_js.validation.ts +52 -18
  251. package/ts/plugins.ts +2 -1
@@ -1,20 +1,28 @@
1
1
  import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
2
+ import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
2
3
  import type { ISamsungtvConfig } from './samsungtv.types.js';
3
4
  import { normalizeSamsungtvConfig, samsungtvKeys, samsungtvObject } from './samsungtv.validation.js';
4
5
 
5
6
  export class SamsungtvConfigFlow implements IConfigFlow<ISamsungtvConfig> {
7
+ readonly #scope: NativeIntegrationScope;
8
+
9
+ constructor(ownerArg: NativeIntegrationOwner = new NativeIntegrationOwner('samsungtv')) {
10
+ this.#scope = ownerArg.createFlowScope();
11
+ }
12
+
6
13
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<ISamsungtvConfig>> {
7
- void contextArg;
8
- const metadata = candidateArg.metadata === undefined ? {} : samsungtvObject(candidateArg.metadata, 'Samsung TV candidate metadata');
14
+ const context = this.#scope.admit(contextArg);
15
+ const candidate = samsungtvObject(candidateArg, 'Samsung TV discovery candidate');
16
+ const metadata = candidate.metadata === undefined ? {} : samsungtvObject(candidate.metadata, 'Samsung TV candidate metadata');
9
17
  const hints: Record<string, unknown> = {};
10
18
  for (const field of ['httpOrigin', 'websocketOrigin', 'expectedDeviceId']) {
11
19
  if (typeof metadata[field] === 'string') hints[field] = metadata[field];
12
20
  }
21
+ Object.freeze(hints);
13
22
  return {
14
23
  kind: 'form', title: 'Pair Samsung Smart TV',
15
- description: 'Confirm this home, the exact TV UUID, explicit REST/WSS origins and trusted TV CA. Confirm the TV pairing prompt when using prompt mode. Pairing credentials are stored only in encrypted home-scoped persistence.',
24
+ description: 'Confirm the exact TV UUID, explicit REST/WSS origins and trusted TV CA for the selected home. Confirm the TV pairing prompt when using prompt mode. Pairing credentials are stored only in encrypted home-scoped persistence.',
16
25
  fields: [
17
- { name: 'homeId', label: 'Home ID', type: 'text', required: true },
18
26
  { name: 'expectedDeviceId', label: 'Exact TV UUID', type: 'text', required: true },
19
27
  { name: 'httpOrigin', label: 'REST origin', type: 'text', required: true },
20
28
  { name: 'websocketOrigin', label: 'Verified WSS origin', type: 'text', required: true },
@@ -27,17 +35,24 @@ export class SamsungtvConfigFlow implements IConfigFlow<ISamsungtvConfig> {
27
35
  try {
28
36
  const values = samsungtvObject(valuesArg, 'Samsung TV config form');
29
37
  samsungtvKeys(values, ['homeId', 'expectedDeviceId', 'httpOrigin', 'websocketOrigin', 'pairingMode', 'certificateAuthority', 'allowUnencryptedLocalDiscovery']);
38
+ if (values.homeId !== undefined && values.homeId !== context.homeId) throw new Error('Samsung TV form home does not match the selected host home.');
30
39
  const config = normalizeSamsungtvConfig({
31
- homeId: values.homeId, expectedDeviceId: values.expectedDeviceId ?? hints.expectedDeviceId,
40
+ homeId: context.homeId, expectedDeviceId: values.expectedDeviceId ?? hints.expectedDeviceId,
32
41
  httpOrigin: values.httpOrigin ?? hints.httpOrigin, websocketOrigin: values.websocketOrigin ?? hints.websocketOrigin,
33
42
  pairingMode: values.pairingMode, certificateAuthority: values.certificateAuthority,
34
43
  allowUnencryptedLocalDiscovery: values.allowUnencryptedLocalDiscovery,
35
44
  });
36
- return { kind: 'done', title: 'Samsung Smart TV configuration accepted', config };
45
+ return await this.#scope.run(context, config.homeId, async (leaseArg) => {
46
+ leaseArg.assertOpen();
47
+ return { kind: 'done', title: 'Samsung Smart TV configuration accepted', config };
48
+ });
37
49
  } catch (errorArg) {
38
50
  return { kind: 'error', error: errorArg instanceof Error ? errorArg.message : 'Samsung TV configuration is invalid.' };
39
51
  }
40
52
  },
41
53
  };
42
54
  }
55
+
56
+ public destroy(): Promise<void> { return this.#scope.close(); }
57
+ public retryCleanup(): Promise<void> { return this.#scope.retryCleanup(); }
43
58
  }
@@ -1,13 +1,13 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
- import { NoSqlConfigStore } from '../../core/classes.configstore.js';
3
+ import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
4
4
  import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
5
5
  import { SamsungtvClient } from './samsungtv.classes.client.js';
6
6
  import { SamsungtvConfigFlow } from './samsungtv.classes.configflow.js';
7
7
  import { createSamsungtvDiscoveryDescriptor } from './samsungtv.discovery.js';
8
8
  import { SamsungtvMapper } from './samsungtv.mapper.js';
9
- import type { ISamsungtvConfig, ISamsungtvIntegrationOptions, ISamsungtvSnapshot, TSamsungtvRemoteKey } from './samsungtv.types.js';
10
- import { samsungtvKeys, samsungtvObject } from './samsungtv.validation.js';
9
+ import type { ISamsungtvConfig, ISamsungtvClientOptions, ISamsungtvIntegrationOptions, ISamsungtvSnapshot, TSamsungtvRemoteKey } from './samsungtv.types.js';
10
+ import { normalizeSamsungtvConfig, samsungtvKeys, samsungtvObject } from './samsungtv.validation.js';
11
11
 
12
12
  const serviceKeys = {
13
13
  turn_off: 'KEY_POWEROFF', media_play: 'KEY_PLAY', media_pause: 'KEY_PAUSE', media_stop: 'KEY_STOP',
@@ -32,65 +32,77 @@ export class SamsungtvIntegration extends BaseIntegration<ISamsungtvConfig> {
32
32
  public readonly displayName = 'Samsung Smart TV';
33
33
  public readonly status = 'control-runtime' as const;
34
34
  public readonly discoveryDescriptor = createSamsungtvDiscoveryDescriptor();
35
- public readonly configFlow = new SamsungtvConfigFlow();
35
+ public readonly configFlow: SamsungtvConfigFlow;
36
36
  public readonly metadata = {
37
37
  source: 'home-assistant/core', upstreamDomain: 'samsungtv', integrationType: 'device', iotClass: 'local_poll',
38
38
  localApi: { implemented: ['verified UUID and WSS pairing', 'encrypted home-scoped pairing', 'fresh REST observations', 'single supported remote Click'],
39
39
  explicitUnsupported: ['optimistic state', 'automatic retries or replay', 'legacy TCP/PIN', 'play_pause', 'app launch', 'absolute volume/mute assignment', 'wake-on-LAN', 'hold/release/repeated keys'] },
40
40
  };
41
- readonly #runtimes = new Set<SamsungtvRuntime>();
42
- readonly #pending = new Set<SamsungtvClient>();
43
- #options?: ISamsungtvIntegrationOptions;
44
- #destroyed = false;
45
- #destroy?: Promise<void>;
41
+ readonly #owner: NativeIntegrationOwner;
42
+ #options?: ISamsungtvClientOptions;
46
43
 
47
- constructor(optionsArg?: ISamsungtvIntegrationOptions) {
44
+ constructor(optionsArg: ISamsungtvIntegrationOptions = {}) {
48
45
  super();
49
- this.#options = optionsArg ? { db: optionsArg.db, keyProvider: optionsArg.keyProvider } : undefined;
46
+ const options = samsungtvObject(optionsArg, 'Samsung TV integration options');
47
+ samsungtvKeys(options, ['homeId', 'capacity', 'db', 'keyProvider']);
48
+ if ((options.db === undefined) !== (options.keyProvider === undefined)) {
49
+ throw new TypeError('Samsung TV database and key provider must be supplied together.');
50
+ }
51
+ this.#options = options.db === undefined ? undefined : {
52
+ db: options.db as ISamsungtvClientOptions['db'], keyProvider: options.keyProvider as ISamsungtvClientOptions['keyProvider'],
53
+ };
54
+ this.#owner = new NativeIntegrationOwner(this.domain, { homeId: options.homeId as string | undefined, capacity: options.capacity as number | undefined });
55
+ this.configFlow = new SamsungtvConfigFlow(this.#owner);
50
56
  }
51
57
 
52
58
  public async setup(configArg: ISamsungtvConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
53
- if (this.#destroyed) throw new Error('Samsung TV integration is destroyed.');
54
- if (contextArg.configStore !== undefined && !(contextArg.configStore instanceof NoSqlConfigStore)) {
55
- throw new TypeError('Samsung TV runtime requires an encrypted NoSqlConfigStore.');
56
- }
57
- const client = new SamsungtvClient(configArg, contextArg.configStore ?? this.#options, contextArg.abortSignal);
58
- this.#pending.add(client);
59
- try {
60
- const snapshot = await client.getSnapshot(contextArg.abortSignal);
61
- if (this.#destroyed) throw new Error('Samsung TV integration was destroyed during setup.');
62
- const runtime = new SamsungtvRuntime(client, snapshot, () => this.#runtimes.delete(runtime));
63
- this.#runtimes.add(runtime);
59
+ const config = normalizeSamsungtvConfig(configArg);
60
+ return this.#owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
61
+ const client = new SamsungtvClient(config, leaseArg.context.configStore ?? this.#options, leaseArg.signal);
62
+ let cleanupAttempted = false;
63
+ leaseArg.own(() => {
64
+ if (cleanupAttempted) return client.retryCleanup();
65
+ cleanupAttempted = true;
66
+ return client.destroy();
67
+ });
68
+ const snapshot = await client.getSnapshot(leaseArg.signal);
69
+ leaseArg.assertOpen();
70
+ const runtime = new SamsungtvRuntime(client, snapshot, leaseArg);
71
+ leaseArg.promoteToRuntime();
64
72
  return runtime;
65
- } catch (errorArg) { await client.destroy(); throw errorArg; }
66
- finally { this.#pending.delete(client); }
73
+ });
67
74
  }
68
75
 
69
76
  public destroy(): Promise<void> {
70
- if (!this.#destroy) {
71
- this.#destroyed = true;
72
- this.#destroy = Promise.all([
73
- ...[...this.#runtimes].map((runtimeArg) => runtimeArg.destroy()),
74
- ...[...this.#pending].map((clientArg) => clientArg.destroy()),
75
- ]).then(() => { this.#options = undefined; });
76
- }
77
- return this.#destroy;
77
+ const closed = this.#owner.close();
78
+ this.#options = undefined;
79
+ return closed;
78
80
  }
81
+
82
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
79
83
  }
80
84
 
81
85
  export class HomeAssistantSamsungtvIntegration extends SamsungtvIntegration {}
82
86
 
83
87
  class SamsungtvRuntime implements IIntegrationRuntime {
84
88
  public readonly domain = 'samsungtv';
85
- constructor(private readonly client: SamsungtvClient, private readonly identity: ISamsungtvSnapshot, private readonly onDestroyed: () => void) {}
89
+ readonly #deviceId: string;
90
+ constructor(private readonly client: SamsungtvClient, identityArg: ISamsungtvSnapshot, private readonly lease: INativeSetupLease) {
91
+ this.#deviceId = SamsungtvMapper.deviceId(identityArg);
92
+ }
86
93
  public isHealthy(): boolean { return this.client.isHealthy(); }
87
94
  public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
95
+ this.lease.assertOpen();
88
96
  return SamsungtvMapper.toDevices(await this.client.getSnapshot(signalArg));
89
97
  }
90
- public async entities(): Promise<IIntegrationEntity[]> { return SamsungtvMapper.toEntities(await this.client.getSnapshot()); }
98
+ public async entities(): Promise<IIntegrationEntity[]> {
99
+ this.lease.assertOpen();
100
+ return SamsungtvMapper.toEntities(await this.client.getSnapshot());
101
+ }
91
102
 
92
103
  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
93
104
  try {
105
+ this.lease.assertOpen();
94
106
  const request = samsungtvObject(requestArg, 'Samsung TV service request');
95
107
  samsungtvKeys(request, ['domain', 'service', 'target', 'data']);
96
108
  this.#assertTarget(request.target);
@@ -117,24 +129,27 @@ class SamsungtvRuntime implements IIntegrationRuntime {
117
129
 
118
130
  public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
119
131
  try {
132
+ this.lease.assertOpen();
120
133
  const request = samsungtvObject(requestArg, 'Samsung TV feature write');
121
134
  samsungtvKeys(request, ['device', 'feature', 'value']);
122
135
  const device = samsungtvObject(request.device, 'Samsung TV feature device');
123
136
  const feature = samsungtvObject(request.feature, 'Samsung TV feature');
124
- if (device.id !== SamsungtvMapper.deviceId(this.identity) || device.integrationDomain !== this.domain) {
137
+ if (device.id !== this.#deviceId || device.integrationDomain !== this.domain) {
125
138
  throw new TypeError('Samsung TV feature device is not owned by this runtime.');
126
139
  }
127
- if (feature.id !== 'remote_key') throw new TypeError('Samsung TV feature is not writable.');
140
+ if (feature.id !== 'remote_key' || feature.capability !== 'media' || feature.readable !== false || feature.writable !== true) {
141
+ throw new TypeError('Samsung TV feature is not the owned writable remote key.');
142
+ }
128
143
  return { success: true, data: await this.client.sendKey(remoteKey(request.value), signalArg) };
129
144
  } catch (errorArg) { return failure(errorArg); }
130
145
  }
131
146
 
132
- public async destroy(): Promise<void> { await this.client.destroy(); this.onDestroyed(); }
147
+ public destroy(): Promise<void> { return this.lease.close(); }
133
148
 
134
149
  #assertTarget(valueArg: unknown): void {
135
150
  const target = samsungtvObject(valueArg, 'Samsung TV service target');
136
151
  samsungtvKeys(target, ['deviceId', 'entityId']);
137
- const deviceId = SamsungtvMapper.deviceId(this.identity);
152
+ const deviceId = this.#deviceId;
138
153
  if ((target.deviceId === undefined && target.entityId === undefined) ||
139
154
  (target.deviceId !== undefined && target.deviceId !== deviceId) ||
140
155
  (target.entityId !== undefined && target.entityId !== `${deviceId}:media_player`)) {
@@ -1,5 +1,6 @@
1
1
  import type * as plugins from '../../plugins.js';
2
2
  import type { IConfigEncryptionKeyProvider } from '../../core/classes.configstore.js';
3
+ import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
3
4
 
4
5
  export interface ISamsungtvConfig {
5
6
  homeId: string;
@@ -16,11 +17,13 @@ export interface ISamsungtvConfig {
16
17
 
17
18
  export interface IHomeAssistantSamsungtvConfig extends ISamsungtvConfig {}
18
19
 
19
- export interface ISamsungtvIntegrationOptions {
20
+ export interface ISamsungtvClientOptions {
20
21
  db: plugins.nosqldb.SmartdataDb;
21
22
  keyProvider: IConfigEncryptionKeyProvider;
22
23
  }
23
24
 
25
+ export interface ISamsungtvIntegrationOptions extends INativeIntegrationOwnerOptions, Partial<ISamsungtvClientOptions> {}
26
+
24
27
  export interface ISamsungtvSnapshot {
25
28
  readonly homeId: string;
26
29
  readonly device: plugins.ecobridgeDeviceManager.ISamsungTvDeviceInfo;
@@ -1,14 +1,23 @@
1
1
  # Shelly Gen2/3
2
2
 
3
- This native port connects to a local Shelly Gen2 or Gen3 HTTP/RPC endpoint. Setup reads and validates the physical device identity, status and configuration before returning a runtime. It maps switch outputs and their power, voltage, current, energy and temperature readings to canonical devices and entities.
3
+ This native port connects to a local Shelly Gen2 or Gen3 HTTP/RPC endpoint. Setup reads and validates the selected endpoint's reported device ID, MAC, model, generation, methods, status and configuration before returning a runtime. These observations do not attest physical hardware identity. It maps switch outputs and their power, voltage, current, energy and temperature readings to canonical devices and entities.
4
4
 
5
5
  ```typescript
6
- const integration = new ShellyIntegration();
6
+ const integration = new ShellyIntegration({
7
+ resolvePassword, // Caller-owned callback; receives homeId, credentialRef, signal and monotonic deadline.
8
+ });
7
9
  const runtime = await integration.setup({
8
10
  host: '192.168.1.20',
9
- password: devicePassword,
11
+ homeId: selectedHomeId,
12
+ expectedDeviceId: selectedDeviceId,
13
+ expectedMacAddress: selectedMacAddress,
14
+ expectedModel: selectedModel,
15
+ expectedGeneration: selectedGeneration,
16
+ credentialRef: selectedCredentialRef,
17
+ allowUnencryptedLan: true,
18
+ allowControl: true, // Omit for read-only outputs.
10
19
  requestTimeoutMs: 10000,
11
- }, {});
20
+ }, { homeId: hostSelectedHomeId, abortSignal: callerSignal });
12
21
 
13
22
  const [device] = await runtime.devices();
14
23
  const output = device.features.find((feature) => feature.id === 'switch_0_output');
@@ -21,14 +30,20 @@ await unsubscribe?.();
21
30
  await integration.destroy();
22
31
  ```
23
32
 
24
- `host` is a hostname or IP address, with the optional port supplied separately. `protocol` defaults to `http`; `https` validates the endpoint certificate through Node's standard transport. Authentication uses the Shelly `admin` username and SHA-256 digest challenges. Credentials and response bodies are excluded from error messages. Request deadlines default to 10 seconds and accept values between 10 ms and 60 seconds.
33
+ `hostSelectedHomeId` comes independently from the embedding host and must match configuration and any optional constructor home binding. Config flow captures that selection before returning a form. An optional actual `NoSqlConfigStore` must match the admitted home/domain and remains borrowed. The integration's construction is inert; setup, flow work, live runtimes and retained cleanup share a default capacity of 16.
34
+
35
+ `resolvePassword` is an application-owned asynchronous callback. Its immutable request contains admitted `homeId`, captured `credentialRef`, `signal` and an absolute `performance.now()` deadline including queue, authentication and readback. Config accepts references rather than plaintext passwords. The adapter registers actual provider work before callback entry, fences late credentials after cancellation and joins the actual callback during shutdown. Provider errors are sanitized; a provider that never settles keeps cleanup pending.
36
+
37
+ `host` is a hostname or IP address, with the optional port supplied separately. `protocol` defaults to `http`; `https` validates the endpoint certificate through Node's standard transport. Plain HTTP requires explicit `allowUnencryptedLan: true`. Authentication uses the Shelly `admin` username and SHA-256 digest challenges. Credentials and response bodies are excluded from error messages. Request deadlines default to 10 seconds and accept values between 10 ms and 60 seconds.
25
38
 
26
39
  `switch.turn_on` and `switch.turn_off` accept an exact mapped switch entity id. A mapped device id is sufficient when the device exposes one switch. Feature writes require the connected device id, a mapped writable `switch_N_output` feature, and a boolean value. Foreign targets, nonexistent components, ambiguous device targets and fabricated writable sensor features are rejected before a write.
27
40
 
41
+ Controls are denied unless `allowControl: true` is captured in configuration. Each absolute output write freshly validates identity, current methods and component state before sending one `Switch.Set`; it then revalidates identity and independently reads `Switch.GetStatus`. The vendor's `was_on` acknowledges prior state. Results carry `commandAcknowledged`, `causalityConfirmed: false`, requested/prior values and an observed-target or indeterminate outcome. Lost acknowledgement, failed post-dispatch readback or changed identity leaves an indeterminate result. Setter challenges, cancellation and cleanup never replay the effect. Independent readback does not prove relay position, exclusive causality or an atomic preflight.
42
+
28
43
  Subscriptions share one native WebSocket connection to `/rpc`. The initial `Shelly.GetStatus` request supplies a unique source and authenticates both current and older Gen2 digest challenges. `NotifyStatus` patches merge into the validated baseline, including nested fields and null removals. `NotifyFullStatus` replaces the baseline. `NotifyEvent` carries vendor events, and configuration changes refresh mapped names. Disconnects and malformed or foreign frames emit safe errors and unavailable events; `isHealthy()` reflects the failed stream. A later subscription can establish a new connection.
29
44
 
30
- The last unsubscribe closes the socket; an aborted listener removes only that listener. The setup context's abort signal cancels live requests and the push transport. Runtime and integration destruction cancel outstanding requests, remove subscribers, and await owned socket closure. If the endpoint does not complete the native WebSocket close handshake before the deadline, cleanup reports failure and retains ownership for another cleanup attempt.
45
+ The last unsubscribe closes the socket; an aborted listener removes only that listener. The setup context's abort signal cancels live requests and the push transport. Runtime and integration destruction cancel outstanding requests, remove subscribers, and await owned socket closure. If the endpoint does not complete the native WebSocket close handshake before the deadline, cleanup reports failure and retains ownership. Repeated `destroy()` keeps its original promise and failure; explicit integration, config-flow or client `retryCleanup()` retries cleanup only, without credential-provider or device-effect replay. Async subscriber continuations and all sibling cleanup settle before completion. Terminal objects cannot reopen.
31
46
 
32
- Production configuration requires a real endpoint. The former `deviceInfo`, `status` and `deviceConfig` fixture fields have been removed; use a local fixture server in tests or call `ShellyMapper` directly with a snapshot. Tests under `test/shelly/` exercise local HTTP and WebSocket servers without real devices or credentials.
47
+ Production configuration requires a real endpoint. The former `deviceInfo`, `status` and `deviceConfig` fixture fields have been removed; use a local fixture server in tests or call `ShellyMapper` directly with a snapshot. Tests under `test/shelly/` exercise local HTTP and WebSocket servers without real devices or credentials. Qualified synthetic profiles cover Plus 1PM Gen2 `SNSW-001P16EU`, 1PM Gen3 `S3SW-001P16EU` and Plus 2PM `SNSW-002P16EU` with two switch components; this is not universal model certification.
33
48
 
34
49
  Protocol references: [RPC channels](https://shelly-api-docs.shelly.cloud/gen2/General/RPCChannels/), [authentication](https://shelly-api-docs.shelly.cloud/gen2/General/Authentication/), [notifications](https://shelly-api-docs.shelly.cloud/gen2/General/Notifications/), [mDNS](https://shelly-api-docs.shelly.cloud/gen2/General/mDNS/), and [switch RPC](https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/Switch/).