@smarthome.exchange/integrations 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/changelog.md +49 -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 +79 -0
  8. package/dist_ts/core/classes.nativeintegrationowner.js +269 -0
  9. package/dist_ts/core/index.d.ts +2 -0
  10. package/dist_ts/core/index.js +3 -1
  11. package/dist_ts/core/types.d.ts +14 -0
  12. package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
  13. package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
  14. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
  15. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
  16. package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
  17. package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
  18. package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
  19. package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
  20. package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
  21. package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
  22. package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
  23. package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
  24. package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
  25. package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
  26. package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
  27. package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
  28. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
  29. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
  30. package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
  31. package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
  32. package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
  33. package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
  34. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
  35. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
  36. package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
  37. package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
  38. package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
  39. package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
  40. package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
  41. package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
  42. package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
  43. package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
  44. package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
  45. package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
  46. package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
  47. package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
  48. package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
  49. package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
  50. package/dist_ts/integrations/daikin/daikin.types.js +1 -1
  51. package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
  52. package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
  53. package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
  54. package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
  55. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
  56. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
  57. package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
  58. package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
  59. package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
  60. package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
  61. package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
  62. package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
  63. package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
  64. package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
  65. package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
  66. package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
  67. package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
  68. package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
  69. package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
  70. package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
  71. package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
  72. package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
  73. package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
  74. package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
  75. package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
  76. package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
  77. package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
  78. package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
  79. package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
  80. package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
  81. package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
  82. package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
  83. package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
  84. package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
  85. package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
  86. package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
  87. package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
  88. package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
  89. package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
  90. package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
  91. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
  92. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
  93. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
  94. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
  95. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
  96. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
  97. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
  98. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
  99. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
  100. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
  101. package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
  102. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
  103. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
  104. package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
  105. package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
  106. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
  107. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
  108. package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
  109. package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
  110. package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
  111. package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
  112. package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
  113. package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
  114. package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
  115. package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
  116. package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
  117. package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
  118. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
  119. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
  120. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
  121. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
  122. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
  123. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
  124. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
  125. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
  126. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
  127. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
  128. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
  129. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
  130. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
  131. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
  132. package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
  133. package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
  134. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
  135. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
  136. package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
  137. package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
  138. package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
  139. package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
  140. package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
  141. package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
  142. package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
  143. package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
  144. package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
  145. package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
  146. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
  147. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
  148. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
  149. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
  150. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
  151. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
  152. package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
  153. package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
  154. package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
  155. package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
  156. package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
  157. package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
  158. package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
  159. package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
  160. package/dist_ts/integrations/wiz/index.d.ts +1 -0
  161. package/dist_ts/integrations/wiz/index.js +2 -1
  162. package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
  163. package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
  164. package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
  165. package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
  166. package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
  167. package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
  168. package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
  169. package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
  170. package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
  171. package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
  172. package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
  173. package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
  174. package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
  175. package/dist_ts/integrations/yamaha/index.d.ts +1 -0
  176. package/dist_ts/integrations/yamaha/index.js +2 -1
  177. package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
  178. package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
  179. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
  180. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
  181. package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
  182. package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
  183. package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
  184. package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
  185. package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
  186. package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
  187. package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
  188. package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
  189. package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
  190. package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
  191. package/package.json +3 -3
  192. package/readme.md +514 -2
  193. package/ts/00_commitinfo_data.ts +1 -1
  194. package/ts/core/classes.configstore.ts +84 -27
  195. package/ts/core/classes.nativehomeadmission.ts +96 -0
  196. package/ts/core/classes.nativeintegrationowner.ts +297 -0
  197. package/ts/core/index.ts +2 -0
  198. package/ts/core/types.ts +16 -0
  199. package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
  200. package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
  201. package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
  202. package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
  203. package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
  204. package/ts/integrations/braviatv/braviatv.types.ts +53 -173
  205. package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
  206. package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
  207. package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
  208. package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
  209. package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
  210. package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
  211. package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
  212. package/ts/integrations/broadlink/broadlink.types.ts +48 -8
  213. package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
  214. package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
  215. package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
  216. package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
  217. package/ts/integrations/daikin/daikin.discovery.ts +94 -346
  218. package/ts/integrations/daikin/daikin.mapper.ts +83 -166
  219. package/ts/integrations/daikin/daikin.types.ts +27 -48
  220. package/ts/integrations/daikin/daikin.validation.ts +51 -0
  221. package/ts/integrations/daikin/readme.md +83 -0
  222. package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
  223. package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
  224. package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
  225. package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
  226. package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
  227. package/ts/integrations/denonavr/denonavr.types.ts +48 -111
  228. package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
  229. package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
  230. package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
  231. package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
  232. package/ts/integrations/kodi/kodi.discovery.ts +49 -28
  233. package/ts/integrations/kodi/kodi.mapper.ts +52 -10
  234. package/ts/integrations/kodi/kodi.types.ts +33 -58
  235. package/ts/integrations/kodi/kodi.validation.ts +60 -0
  236. package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
  237. package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
  238. package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
  239. package/ts/integrations/modbus/modbus.discovery.ts +38 -106
  240. package/ts/integrations/modbus/modbus.mapper.ts +64 -467
  241. package/ts/integrations/modbus/modbus.types.ts +64 -381
  242. package/ts/integrations/modbus/modbus.validation.ts +119 -0
  243. package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
  244. package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
  245. package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
  246. package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
  247. package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
  248. package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
  249. package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
  250. package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
  251. package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
  252. package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
  253. package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
  254. package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
  255. package/ts/integrations/onkyo/onkyo.types.ts +55 -110
  256. package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
  257. package/ts/integrations/onkyo/readme.md +126 -0
  258. package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
  259. package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
  260. package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
  261. package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
  262. package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
  263. package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
  264. package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
  265. package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
  266. package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
  267. package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
  268. package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
  269. package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
  270. package/ts/integrations/pioneer/pioneer.types.ts +87 -85
  271. package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
  272. package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
  273. package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
  274. package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
  275. package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
  276. package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
  277. package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
  278. package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
  279. package/ts/integrations/wiz/index.ts +1 -0
  280. package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
  281. package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
  282. package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
  283. package/ts/integrations/wiz/wiz.discovery.ts +48 -291
  284. package/ts/integrations/wiz/wiz.mapper.ts +81 -754
  285. package/ts/integrations/wiz/wiz.types.ts +24 -224
  286. package/ts/integrations/wiz/wiz.validation.ts +73 -0
  287. package/ts/integrations/yamaha/index.ts +1 -0
  288. package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
  289. package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
  290. package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
  291. package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
  292. package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
  293. package/ts/integrations/yamaha/yamaha.types.ts +77 -97
  294. package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
@@ -1,739 +1,275 @@
1
- import type {
2
- IBraviatvApp,
3
- IBraviatvChannel,
4
- IBraviatvConfig,
5
- IBraviatvPlayingInfo,
6
- IBraviatvSnapshot,
7
- IBraviatvSource,
8
- IBraviatvState,
9
- IBraviatvSystemInfo,
10
- IBraviatvVolumeInfo,
11
- TBraviatvRestService,
12
- TBraviatvSourceType,
13
- } from './braviatv.types.js';
14
-
15
- interface IBraviatvRestResponse {
16
- result?: unknown[];
17
- error?: [number, string] | unknown[];
18
- id?: number;
19
- }
20
-
21
- const defaultTimeoutMs = 10000;
22
- const defaultAudioTarget = 'speaker';
23
- const defaultRestVersion = '1.0';
24
- const powerOnCode = 'AAAAAQAAAAEAAAAuAw==';
25
-
26
- const fallbackCommands: Record<string, string> = {
27
- Power: 'AAAAAQAAAAEAAAAVAw==',
28
- PowerOn: powerOnCode,
29
- Input: 'AAAAAQAAAAEAAAAlAw==',
30
- SyncMenu: 'AAAAAgAAABoAAABYAw==',
31
- Hdmi1: 'AAAAAgAAABoAAABaAw==',
32
- Hdmi2: 'AAAAAgAAABoAAABbAw==',
33
- Hdmi3: 'AAAAAgAAABoAAABcAw==',
34
- Hdmi4: 'AAAAAgAAABoAAABdAw==',
35
- Num1: 'AAAAAQAAAAEAAAAAAw==',
36
- Num2: 'AAAAAQAAAAEAAAABAw==',
37
- Num3: 'AAAAAQAAAAEAAAACAw==',
38
- Num4: 'AAAAAQAAAAEAAAADAw==',
39
- Num5: 'AAAAAQAAAAEAAAAEAw==',
40
- Num6: 'AAAAAQAAAAEAAAAFAw==',
41
- Num7: 'AAAAAQAAAAEAAAAGAw==',
42
- Num8: 'AAAAAQAAAAEAAAAHAw==',
43
- Num9: 'AAAAAQAAAAEAAAAIAw==',
44
- Num0: 'AAAAAQAAAAEAAAAJAw==',
45
- Dot: 'AAAAAgAAAJcAAAAdAw==',
46
- CC: 'AAAAAgAAAJcAAAAoAw==',
47
- Red: 'AAAAAgAAAJcAAAAlAw==',
48
- Green: 'AAAAAgAAAJcAAAAmAw==',
49
- Yellow: 'AAAAAgAAAJcAAAAnAw==',
50
- Blue: 'AAAAAgAAAJcAAAAkAw==',
51
- Up: 'AAAAAQAAAAEAAAB0Aw==',
52
- Down: 'AAAAAQAAAAEAAAB1Aw==',
53
- Right: 'AAAAAQAAAAEAAAAzAw==',
54
- Left: 'AAAAAQAAAAEAAAA0Aw==',
55
- Confirm: 'AAAAAQAAAAEAAABlAw==',
56
- Help: 'AAAAAgAAAMQAAABNAw==',
57
- Display: 'AAAAAQAAAAEAAAA6Aw==',
58
- Options: 'AAAAAgAAAJcAAAA2Aw==',
59
- Back: 'AAAAAgAAAJcAAAAjAw==',
60
- Home: 'AAAAAQAAAAEAAABgAw==',
61
- VolumeUp: 'AAAAAQAAAAEAAAASAw==',
62
- VolumeDown: 'AAAAAQAAAAEAAAATAw==',
63
- Mute: 'AAAAAQAAAAEAAAAUAw==',
64
- Audio: 'AAAAAQAAAAEAAAAXAw==',
65
- ChannelUp: 'AAAAAQAAAAEAAAAQAw==',
66
- ChannelDown: 'AAAAAQAAAAEAAAARAw==',
67
- Play: 'AAAAAgAAAJcAAAAaAw==',
68
- Pause: 'AAAAAgAAAJcAAAAZAw==',
69
- Stop: 'AAAAAgAAAJcAAAAYAw==',
70
- FlashPlus: 'AAAAAgAAAJcAAAB4Aw==',
71
- FlashMinus: 'AAAAAgAAAJcAAAB5Aw==',
72
- Prev: 'AAAAAgAAAJcAAAA8Aw==',
73
- Next: 'AAAAAgAAAJcAAAA9Aw==',
74
- };
75
-
76
- export class BraviatvAuthError extends Error {
77
- constructor(messageArg: string) {
78
- super(messageArg);
79
- this.name = 'BraviatvAuthError';
80
- }
81
- }
82
-
83
- export class BraviatvLiveControlError extends Error {
84
- constructor(messageArg: string) {
85
- super(messageArg);
86
- this.name = 'BraviatvLiveControlError';
87
- }
1
+ import * as plugins from '../../plugins.js';
2
+ import { NativeHomeAdmission, type INativeAdmissionContext } from '../../core/classes.nativehomeadmission.js';
3
+ import type { IBraviatvConfig, IBraviatvEffectResult, IBraviatvIntegrationOptions, IBraviatvSetupContext,
4
+ IBraviatvSnapshot, TBraviatvControl } from './braviatv.types.js';
5
+ import { normalizeBraviatvConfig, normalizeBraviatvOptions,
6
+ validateBraviatvControl } from './braviatv.validation.js';
7
+
8
+ interface IBraviatvOwnedProtocol {
9
+ readonly protocol: plugins.ecobridgeDeviceManager.SonyBraviaProtocol;
10
+ active: boolean;
11
+ failed: boolean;
12
+ cleanup?: Promise<void>;
88
13
  }
89
14
 
15
+ /** Home admission and caller work only; SonyBraviaProtocol owns every wire operation. */
90
16
  export class BraviatvClient {
91
- private commandCache?: Record<string, string>;
92
- private irccEndpoint: string;
93
-
94
- constructor(private readonly config: IBraviatvConfig) {
95
- this.irccEndpoint = config.irccEndpoint || 'ircc';
96
- this.commandCache = config.commands ? { ...fallbackCommands, ...config.commands } : undefined;
97
- }
98
-
99
- public async getSnapshot(): Promise<IBraviatvSnapshot> {
100
- if (this.config.snapshot) {
101
- return this.normalizeSnapshot(this.cloneSnapshot(this.config.snapshot));
102
- }
103
-
104
- if (this.hasLivePskConfig()) {
105
- try {
106
- return await this.getLiveSnapshot();
107
- } catch (errorArg) {
108
- if (errorArg instanceof BraviatvAuthError) {
109
- throw errorArg;
17
+ public readonly config: ReturnType<typeof normalizeBraviatvConfig>;
18
+ readonly #options: Readonly<IBraviatvIntegrationOptions>;
19
+ readonly #context: INativeAdmissionContext;
20
+ readonly #admission: NativeHomeAdmission;
21
+ readonly #lifetime = new AbortController();
22
+ readonly #protocols = new Map<plugins.ecobridgeDeviceManager.SonyBraviaProtocol, IBraviatvOwnedProtocol>();
23
+ readonly #operations = new Set<Promise<unknown>>();
24
+ readonly #credentialOperations = new Map<Promise<string>, Promise<unknown>>();
25
+ readonly #credentialFailures = new Map<plugins.ecobridgeDeviceManager.SonyBraviaProtocolError, Promise<unknown>>();
26
+ readonly #parentAbort: () => void;
27
+ #tail: Promise<void> = Promise.resolve();
28
+ #startPromise?: Promise<IBraviatvSnapshot>;
29
+ #snapshot?: IBraviatvSnapshot;
30
+ #started = false;
31
+ #healthy = false;
32
+ #closePromise?: Promise<void>;
33
+ #cleanupPromise?: Promise<void>;
34
+ #retryPromise?: Promise<void>;
35
+ #cleanupFailed = false;
36
+
37
+ public constructor(config: IBraviatvConfig, context: IBraviatvSetupContext, options: IBraviatvIntegrationOptions) {
38
+ this.config = normalizeBraviatvConfig(config);
39
+ this.#options = normalizeBraviatvOptions(options);
40
+ this.#admission = new NativeHomeAdmission('braviatv', { homeId: this.#options.homeId });
41
+ this.#context = this.#admission.admit(context, this.config.homeId);
42
+ this.#parentAbort = () => { void this.destroy().catch(() => {}); };
43
+ this.#context.abortSignal?.addEventListener('abort', this.#parentAbort, { once: true });
44
+ if (this.#context.abortSignal?.aborted) this.#parentAbort();
45
+ }
46
+
47
+ public isHealthy(): boolean { return !this.#lifetime.signal.aborted && this.#started && this.#healthy; }
48
+
49
+ public start(signal?: AbortSignal): Promise<IBraviatvSnapshot> {
50
+ if (!this.#startPromise) {
51
+ this.#startPromise = this.run((protocol, operation) => protocol.readState(operation), signal, false)
52
+ .then((state) => { const snapshot = this.observe(state); this.#started = true; return snapshot; })
53
+ .catch((error: unknown) => {
54
+ this.#healthy = false;
55
+ this.#lifetime.abort(this.failure('closed'));
56
+ throw error;
57
+ });
58
+ }
59
+ return this.#startPromise;
60
+ }
61
+
62
+ public getObservedSnapshot(): IBraviatvSnapshot {
63
+ this.assertOpen();
64
+ if (!this.#snapshot) throw new Error('BRAVIA has no admitted observation.');
65
+ return this.#snapshot;
66
+ }
67
+
68
+ public getSnapshot(signal?: AbortSignal): Promise<IBraviatvSnapshot> {
69
+ return this.run((protocol, operation) => protocol.readState(operation), signal).then((state) => this.observe(state));
70
+ }
71
+
72
+ public setControl(value: TBraviatvControl, signal?: AbortSignal): Promise<IBraviatvEffectResult> {
73
+ const control = validateBraviatvControl(value);
74
+ if (control.feature === 'speaker_volume_raw' && control.value > this.config.volumeLimitRaw) throw new TypeError('BRAVIA speaker volume exceeds the owner ceiling.');
75
+ return this.run((protocol, operation) => control.feature === 'power' ? protocol.setPower(control.value, operation)
76
+ : control.feature === 'device_mute' ? protocol.setMute(control.value, operation)
77
+ : protocol.setVolumeRaw(control.value, operation), signal).then((result) => {
78
+ try {
79
+ return Object.freeze({ control: result.control, requested: result.requested,
80
+ commandAcknowledged: result.commandAcknowledged, causalityConfirmed: result.causalityConfirmed,
81
+ observation: result.observation, snapshot: this.observe(result.observed) });
82
+ } catch {
83
+ throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('identity-mismatch', 'BRAVIA reported state could not be admitted after acknowledgment.', 'indeterminate', true);
110
84
  }
111
- return this.normalizeSnapshot(this.snapshotFromManualConfig(this.errorMessage(errorArg)));
112
- }
113
- }
114
-
115
- return this.normalizeSnapshot(this.snapshotFromManualConfig());
85
+ });
116
86
  }
117
87
 
118
- public async turnOn(): Promise<void> {
119
- this.assertLivePskSupported('turn_on');
120
- try {
121
- await this.sendRestQuick('system', 'setPowerStatus', { status: true });
122
- } catch {
123
- await this.sendIrcc(powerOnCode);
88
+ public destroy(): Promise<void> {
89
+ if (!this.#closePromise) {
90
+ this.#healthy = false;
91
+ this.#closePromise = Promise.resolve().then(() => this.drainCleanup(false));
92
+ this.#cleanupPromise = this.#closePromise;
93
+ this.#context.abortSignal?.removeEventListener('abort', this.#parentAbort);
94
+ this.#lifetime.abort(this.failure('closed'));
95
+ }
96
+ return this.#closePromise;
97
+ }
98
+
99
+ /** Explicit cleanup retry acknowledges settled credential failures; it cannot resolve a PSK, construct a facade or replay an effect. */
100
+ public retryCleanup(): Promise<void> {
101
+ if (!this.#closePromise) return this.destroy();
102
+ if (!this.#cleanupFailed) return this.#cleanupPromise ?? this.#closePromise;
103
+ if (!this.#retryPromise) {
104
+ this.#retryPromise = Promise.resolve().then(() => this.drainCleanup(true));
105
+ this.#cleanupPromise = this.#retryPromise;
106
+ const settled = (): void => { this.#retryPromise = undefined; };
107
+ void this.#retryPromise.then(settled, settled);
108
+ }
109
+ return this.#retryPromise;
110
+ }
111
+
112
+ private async drainCleanup(retry: boolean): Promise<void> {
113
+ const protocolCleanup = [...this.#protocols.values()].map((owned) => this.closeProtocol(owned, retry));
114
+ const results = await Promise.allSettled([...protocolCleanup, ...this.#credentialOperations.keys(), ...this.#operations, this.#startPromise]);
115
+ const finalResults = await Promise.allSettled([...this.#protocols.values()].map((owned) => this.closeProtocol(owned)));
116
+ this.#snapshot = undefined;
117
+ const failures = [...results.slice(0, protocolCleanup.length), ...finalResults].filter((entry): entry is PromiseRejectedResult => entry.status === 'rejected');
118
+ const reasons = [...new Set(failures.map((entry) => entry.reason)), ...(!retry ? this.#credentialFailures.keys() : [])];
119
+ if (retry) this.#credentialFailures.clear();
120
+ this.#cleanupFailed = reasons.length > 0;
121
+ if (reasons.length) throw new AggregateError(reasons, 'BRAVIA owned operation cleanup failed.');
122
+ }
123
+
124
+ private closeProtocol(owned: IBraviatvOwnedProtocol, retry = false): Promise<void> {
125
+ if (retry && owned.failed) { owned.cleanup = undefined; owned.failed = false; }
126
+ if (!owned.cleanup) {
127
+ owned.cleanup = Promise.resolve().then(() => owned.protocol.close()).then(() => {
128
+ this.#protocols.delete(owned.protocol);
129
+ }, () => {
130
+ owned.failed = true;
131
+ throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('transport', 'BRAVIA owned protocol cleanup failed.');
132
+ });
124
133
  }
125
- this.updateLocalState({ powerStatus: 'active', power: 'on', available: true });
126
- }
127
-
128
- public async turnOff(): Promise<void> {
129
- await this.sendRestQuick('system', 'setPowerStatus', { status: false });
130
- this.updateLocalState({ powerStatus: 'standby', power: 'off', playback: 'off' });
134
+ return owned.cleanup;
131
135
  }
132
136
 
133
- public async setVolumeLevel(volumeLevelArg: number): Promise<void> {
134
- const volume = Math.max(0, Math.min(100, Math.round(volumeLevelArg * 100)));
135
- await this.sendRestQuick('audio', 'setAudioVolume', { target: defaultAudioTarget, volume: String(volume) });
136
- this.updateLocalState({ volumeLevel: volume / 100, volumePercent: volume });
137
+ private observe(state: plugins.ecobridgeDeviceManager.ISonyBraviaState): IBraviatvSnapshot {
138
+ this.assertOpen();
139
+ // Relevant readback replaces the prior observation; unrelated old state is never restamped fresh.
140
+ this.#snapshot = Object.freeze({ ...state, homeId: this.config.homeId, host: this.config.host,
141
+ port: this.config.port, name: this.config.name ?? `Sony BRAVIA ${state.identity.model}` });
142
+ this.#healthy = true;
143
+ return this.#snapshot;
137
144
  }
138
145
 
139
- public async volumeUp(): Promise<void> {
140
- await this.stepVolume(1);
146
+ private failure(code: 'cancelled' | 'closed' | 'timeout' | 'capacity'): plugins.ecobridgeDeviceManager.SonyBraviaProtocolError {
147
+ return new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError(code, code === 'closed' ? 'BRAVIA client is closed.'
148
+ : code === 'timeout' ? 'BRAVIA operation deadline expired.' : code === 'capacity' ? 'BRAVIA operation capacity reached.' : 'BRAVIA operation was cancelled.');
141
149
  }
142
150
 
143
- public async volumeDown(): Promise<void> {
144
- await this.stepVolume(-1);
151
+ private assertOpen(): void {
152
+ if (this.#lifetime.signal.aborted) throw this.failure('closed');
153
+ this.#admission.admit(this.#context, this.config.homeId);
145
154
  }
146
155
 
147
- public async muteVolume(mutedArg: boolean): Promise<void> {
148
- await this.sendRestQuick('audio', 'setAudioMute', { status: mutedArg });
149
- this.updateLocalState({ muted: mutedArg });
150
- }
151
-
152
- public async mediaPlay(): Promise<void> {
153
- await this.sendCommand('Play');
154
- this.updateLocalState({ playback: 'playing' });
155
- }
156
-
157
- public async mediaPause(): Promise<void> {
158
- await this.sendCommand('Pause');
159
- this.updateLocalState({ playback: 'paused' });
160
- }
161
-
162
- public async mediaStop(): Promise<void> {
163
- await this.sendCommand('Stop');
164
- this.updateLocalState({ playback: 'idle' });
165
- }
166
-
167
- public async nextTrack(): Promise<void> {
168
- await this.sendCommand('Next');
169
- }
170
-
171
- public async previousTrack(): Promise<void> {
172
- await this.sendCommand('Prev');
173
- }
174
-
175
- public async selectSource(sourceArg: string): Promise<void> {
176
- const source = sourceArg.trim();
177
- if (!source) {
178
- throw new Error('Sony Bravia TV select_source requires a source name or URI.');
179
- }
180
-
181
- const item = await this.findSource(source);
182
- await this.startSource(item.uri, item.type);
183
- this.updateLocalState({ powerStatus: 'active', power: 'on', source: item.title, sourceUri: item.uri });
184
- }
185
-
186
- public async playMedia(mediaTypeArg: string, mediaIdArg: string): Promise<void> {
187
- const normalizedType = mediaTypeArg.toLowerCase();
188
- if (normalizedType !== 'app' && normalizedType !== 'channel') {
189
- throw new Error(`Sony Bravia TV play_media supports app or channel media only, not ${mediaTypeArg}.`);
190
- }
191
- const source = await this.findSource(mediaIdArg, normalizedType as TBraviatvSourceType);
192
- await this.startSource(source.uri, source.type);
193
- }
194
-
195
- public async sendCommand(commandArg: string): Promise<void> {
196
- const code = await this.commandCode(commandArg);
197
- if (!code) {
198
- throw new Error(`Unsupported Sony Bravia TV IRCC command: ${commandArg}`);
199
- }
200
- await this.sendIrcc(code);
201
- }
202
-
203
- public async sendCommands(commandsArg: string[], repeatsArg = 1): Promise<void> {
204
- const repeats = Math.max(1, Math.floor(repeatsArg));
205
- for (let repeat = 0; repeat < repeats; repeat += 1) {
206
- for (const command of commandsArg) {
207
- await this.sendCommand(command);
208
- }
156
+ private active(signal: AbortSignal, deadline: number): number {
157
+ if (signal.aborted) {
158
+ const reason: unknown = signal.reason;
159
+ throw reason instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError ? reason : this.failure('cancelled');
209
160
  }
161
+ this.assertOpen();
162
+ const remaining = Math.floor(deadline - performance.now());
163
+ if (remaining < 1) throw this.failure('timeout');
164
+ return remaining;
210
165
  }
211
166
 
212
- public async reboot(): Promise<void> {
213
- await this.sendRestQuick('system', 'requestReboot');
167
+ private credentialFailure(): plugins.ecobridgeDeviceManager.SonyBraviaProtocolError {
168
+ return new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('invalid-options', 'BRAVIA caller credential resolution failed.');
214
169
  }
215
170
 
216
- public async terminateApps(): Promise<void> {
217
- await this.sendRestQuick('appControl', 'terminateApps');
218
- }
219
-
220
- public async destroy(): Promise<void> {}
221
-
222
- private async getLiveSnapshot(): Promise<IBraviatvSnapshot> {
223
- const powerStatus = await this.getPowerStatus();
224
- const systemInfo = await this.getSystemInfo().catch(() => this.systemInfoFromConfig());
225
- const state: IBraviatvState = {
226
- ...this.config.state,
227
- powerStatus,
228
- power: this.powerFromStatus(powerStatus),
229
- available: true,
230
- };
231
-
232
- let sources = this.config.sources || [];
233
- let apps = this.config.apps || [];
234
- let channels = this.config.channels || [];
235
- let commands = this.config.commands;
236
-
237
- if (state.power === 'on') {
238
- const [volumeInfo, playingInfo, liveSources, liveApps, liveChannels, liveCommands] = await Promise.all([
239
- this.getVolumeInfo().catch(() => undefined),
240
- this.getPlayingInfo().catch(() => undefined),
241
- this.getExternalInputsStatus().catch(() => sources),
242
- this.getAppList().catch(() => apps),
243
- this.config.fetchChannels ? this.getContentListAll('tv').catch(() => channels) : Promise.resolve(channels),
244
- this.getCommandList().catch(() => commands),
245
- ]);
246
- sources = liveSources;
247
- apps = liveApps;
248
- channels = liveChannels;
249
- commands = liveCommands;
250
- this.applyVolumeInfo(state, volumeInfo);
251
- this.applyPlayingInfo(state, playingInfo);
252
- }
253
-
254
- return this.normalizeSnapshot({
255
- systemInfo,
256
- state,
257
- sources,
258
- apps,
259
- channels,
260
- commands,
261
- updatedAt: new Date().toISOString(),
171
+ private async resolvePsk(signal: AbortSignal, deadline: number, operation: Promise<unknown>): Promise<string> {
172
+ this.active(signal, deadline);
173
+ let cancel: () => void = () => {};
174
+ const cancellation = new Promise<never>((_resolve, reject) => {
175
+ cancel = () => { try { this.active(signal, deadline); } catch (error) { reject(error); } };
176
+ signal.addEventListener('abort', cancel, { once: true });
262
177
  });
263
- }
264
-
265
- private async getPowerStatus(): Promise<string> {
266
- const response = await this.sendRest('system', 'getPowerStatus', undefined, defaultRestVersion, 5000);
267
- return this.firstResult<{ status?: string }>(response)?.status || 'unknown';
268
- }
269
-
270
- private async getSystemInfo(): Promise<IBraviatvSystemInfo> {
271
- const response = await this.sendRest('system', 'getSystemInformation');
272
- const info = this.firstResult<IBraviatvSystemInfo>(response) || {};
273
- this.config.macAddress = info.macAddr || this.config.macAddress;
274
- this.config.cid = info.cid || this.config.cid;
275
- return info;
276
- }
277
-
278
- private async getVolumeInfo(targetArg = defaultAudioTarget): Promise<IBraviatvVolumeInfo | undefined> {
279
- const response = await this.sendRest('audio', 'getVolumeInformation');
280
- const outputs = this.firstResult<IBraviatvVolumeInfo[]>(response) || [];
281
- return outputs.find((outputArg) => outputArg.target === targetArg) || outputs[0];
282
- }
283
-
284
- private async getPlayingInfo(): Promise<IBraviatvPlayingInfo | undefined> {
285
- const response = await this.sendRest('avContent', 'getPlayingContentInfo');
286
- return this.firstResult<IBraviatvPlayingInfo>(response);
287
- }
288
-
289
- private async getExternalInputsStatus(): Promise<IBraviatvSource[]> {
290
- const response = await this.sendRest('avContent', 'getCurrentExternalInputsStatus');
291
- return (this.firstResult<IBraviatvSource[]>(response) || []).map((sourceArg) => ({ ...sourceArg, type: 'input' }));
292
- }
293
-
294
- private async getAppList(): Promise<IBraviatvApp[]> {
295
- const response = await this.sendRest('appControl', 'getApplicationList');
296
- return (this.firstResult<IBraviatvApp[]>(response) || []).map((appArg) => ({ ...appArg, type: 'app' }));
297
- }
298
-
299
- private async getSourceList(schemeArg: string): Promise<string[]> {
300
- const response = await this.sendRest('avContent', 'getSourceList', { scheme: schemeArg });
301
- return (this.firstResult<Array<{ source?: string }>>(response) || []).map((itemArg) => itemArg.source).filter((itemArg): itemArg is string => Boolean(itemArg));
302
- }
303
-
304
- private async getContentCount(sourceArg: string): Promise<number> {
305
- const response = await this.sendRest('avContent', 'getContentCount', { source: sourceArg }, defaultRestVersion, 20000);
306
- return this.firstResult<{ count?: number }>(response)?.count || 0;
307
- }
308
-
309
- private async getContentList(sourceArg: string, indexArg: number, countArg: number): Promise<IBraviatvChannel[]> {
310
- const response = await this.sendRest('avContent', 'getContentList', { source: sourceArg, stIdx: indexArg, cnt: countArg }, defaultRestVersion, 20000);
311
- return (this.firstResult<IBraviatvChannel[]>(response) || []).map((channelArg) => ({ ...channelArg, type: 'channel' }));
312
- }
313
-
314
- private async getContentListAll(schemeArg: string): Promise<IBraviatvChannel[]> {
315
- const channels: IBraviatvChannel[] = [];
316
- for (const source of await this.getSourceList(schemeArg)) {
317
- const total = await this.getContentCount(source);
318
- for (let index = 0; index < total; index += 50) {
319
- channels.push(...await this.getContentList(source, index, Math.min(50, total - index)));
320
- }
321
- }
322
- return channels;
323
- }
324
-
325
- private async getCommandList(): Promise<Record<string, string>> {
326
- if (this.commandCache) {
327
- return this.commandCache;
328
- }
329
- const response = await this.sendRest('system', 'getRemoteControllerInfo');
330
- const remoteCommands = Array.isArray(response.result?.[1]) ? response.result[1] as Array<{ name?: string; value?: string }> : [];
331
- this.commandCache = {
332
- ...fallbackCommands,
333
- ...Object.fromEntries(remoteCommands.filter((itemArg) => itemArg.name && itemArg.value).map((itemArg) => [itemArg.name as string, itemArg.value as string])),
334
- };
335
- return this.commandCache;
336
- }
337
-
338
- private async stepVolume(stepArg: number): Promise<void> {
339
- const prefix = stepArg > 0 ? '+' : '';
340
- await this.sendRestQuick('audio', 'setAudioVolume', { target: defaultAudioTarget, volume: `${prefix}${stepArg}` });
341
- }
342
-
343
- private async findSource(queryArg: string, sourceTypeArg?: TBraviatvSourceType): Promise<Required<Pick<IBraviatvSource, 'title' | 'uri' | 'type'>>> {
344
- const direct = this.directSource(queryArg, sourceTypeArg);
345
- if (direct) {
346
- return direct;
347
- }
348
-
349
- const snapshot = await this.getSnapshot();
350
- const items = [...snapshot.sources, ...snapshot.apps, ...(snapshot.channels || [])].map((itemArg) => ({
351
- ...itemArg,
352
- type: itemArg.type || this.inferSourceType(itemArg.uri),
353
- }));
354
- const exact = items.find((itemArg) => (!sourceTypeArg || itemArg.type === sourceTypeArg) && (itemArg.uri === queryArg || itemArg.title.toLowerCase() === queryArg.toLowerCase()));
355
- if (exact) {
356
- return { title: exact.title, uri: exact.uri, type: exact.type };
357
- }
358
-
359
- const coarse = items.find((itemArg) => (!sourceTypeArg || itemArg.type === sourceTypeArg) && itemArg.title.toLowerCase().includes(queryArg.toLowerCase()));
360
- if (coarse) {
361
- return { title: coarse.title, uri: coarse.uri, type: coarse.type };
362
- }
363
-
364
- throw new Error(`Sony Bravia TV source is not known: ${queryArg}`);
365
- }
366
-
367
- private directSource(sourceArg: string, sourceTypeArg?: TBraviatvSourceType): Required<Pick<IBraviatvSource, 'title' | 'uri' | 'type'>> | undefined {
368
- if (sourceArg.startsWith('extInput:') || sourceArg.startsWith('tv:')) {
369
- return { title: sourceArg, uri: sourceArg, type: sourceArg.startsWith('tv:') ? 'channel' : 'input' };
370
- }
371
- if (sourceArg.startsWith('com.sony.dtv.') || sourceArg.startsWith('localapp:')) {
372
- return { title: sourceArg, uri: sourceArg, type: sourceTypeArg || 'app' };
373
- }
374
- return undefined;
375
- }
376
-
377
- private async startSource(uriArg: string, sourceTypeArg: TBraviatvSourceType): Promise<void> {
378
- if (sourceTypeArg === 'app') {
379
- await this.sendRestQuick('appControl', 'setActiveApp', { uri: uriArg });
380
- return;
381
- }
382
- await this.sendRestQuick('avContent', 'setPlayContent', { uri: uriArg });
383
- }
384
-
385
- private async sendRestQuick(serviceArg: TBraviatvRestService, methodArg: string, paramsArg?: unknown, versionArg = defaultRestVersion): Promise<boolean> {
386
- const response = await this.sendRest(serviceArg, methodArg, paramsArg, versionArg);
387
- return Boolean(response.result);
388
- }
389
-
390
- private async sendRest(serviceArg: TBraviatvRestService, methodArg: string, paramsArg?: unknown, versionArg = defaultRestVersion, timeoutMsArg?: number): Promise<IBraviatvRestResponse> {
391
- this.assertLivePskSupported(`${serviceArg}.${methodArg}`);
392
- const params = paramsArg === undefined ? [] : Array.isArray(paramsArg) ? paramsArg : [paramsArg];
393
- const response = await this.postJson(this.serviceUrl(serviceArg), {
394
- method: methodArg,
395
- params,
396
- id: 1,
397
- version: versionArg,
398
- }, timeoutMsArg);
399
-
400
- if (response.error) {
401
- const [code, message] = response.error;
402
- const text = String(message || code || 'Unknown REST API error');
403
- if (Number(code) === 401) {
404
- throw new BraviatvAuthError(`Sony Bravia TV authentication failed for ${methodArg}: ${text}`);
405
- }
406
- if (text.includes('not power-on')) {
407
- throw new BraviatvLiveControlError(`Sony Bravia TV is turned off and rejected ${methodArg}.`);
408
- }
409
- throw new BraviatvLiveControlError(`Sony Bravia TV REST ${methodArg} failed: ${text}`);
410
- }
411
- return response;
412
- }
413
-
414
- private async sendIrcc(codeArg: string): Promise<boolean> {
415
- this.assertLivePskSupported('IRCC command');
416
178
  try {
417
- return await this.postIrcc(this.irccEndpoint, codeArg);
418
- } catch (errorArg) {
419
- if (this.errorMessage(errorArg).includes('HTTP 404')) {
420
- this.irccEndpoint = this.irccEndpoint === 'ircc' ? 'IRCC' : 'ircc';
421
- return this.postIrcc(this.irccEndpoint, codeArg);
422
- }
423
- throw errorArg;
424
- }
425
- }
426
-
427
- private async postIrcc(endpointArg: string, codeArg: string): Promise<boolean> {
428
- const response = await this.postText(this.serviceUrl(endpointArg), this.irccBody(codeArg), {
429
- SOAPACTION: '"urn:schemas-sony-com:service:IRCC:1#X_SendIRCC"',
430
- 'Content-Type': 'text/xml; charset=UTF-8',
431
- });
432
- return response;
179
+ const credential = Promise.resolve().then(() => {
180
+ this.active(signal, deadline);
181
+ return this.#options.resolvePsk(Object.freeze({ homeId: this.config.homeId,
182
+ credentialRef: this.config.credentialRef, signal, deadline }));
183
+ });
184
+ // Own the actual callback before entering it; cancelling its request does not settle this work.
185
+ this.#credentialOperations.set(credential, operation);
186
+ void credential.then(() => { this.#credentialOperations.delete(credential); }, (error: unknown) => {
187
+ this.#credentialOperations.delete(credential);
188
+ if (signal.aborted && error !== signal.reason) this.#credentialFailures.set(this.credentialFailure(), operation);
189
+ });
190
+ const psk = await Promise.race([credential, cancellation]);
191
+ this.active(signal, deadline);
192
+ if (typeof psk !== 'string') throw new Error('Invalid credential.');
193
+ return psk;
194
+ } catch {
195
+ this.active(signal, deadline);
196
+ throw this.credentialFailure();
197
+ } finally { signal.removeEventListener('abort', cancel); }
433
198
  }
434
199
 
435
- private async postJson(urlArg: string, bodyArg: unknown, timeoutMsArg?: number): Promise<IBraviatvRestResponse> {
436
- const text = await this.request(urlArg, {
437
- 'Content-Type': 'application/json; charset=UTF-8',
438
- }, JSON.stringify(bodyArg), timeoutMsArg);
200
+ /** Admission deadline includes queue, caller credential resolution and owned transport drain. */
201
+ private run<TResult>(execute: (protocol: plugins.ecobridgeDeviceManager.SonyBraviaProtocol,
202
+ operation: plugins.ecobridgeDeviceManager.ISonyBraviaOperationOptions) => Promise<TResult>, signal?: AbortSignal,
203
+ requireStarted = true): Promise<TResult> {
439
204
  try {
440
- return (text ? JSON.parse(text) : {}) as IBraviatvRestResponse;
441
- } catch (errorArg) {
442
- throw new BraviatvLiveControlError(`Sony Bravia TV returned invalid JSON: ${this.errorMessage(errorArg)}`);
443
- }
444
- }
445
-
446
- private async postText(urlArg: string, bodyArg: string, headersArg: Record<string, string>): Promise<boolean> {
447
- await this.request(urlArg, headersArg, bodyArg);
448
- return true;
449
- }
450
-
451
- private async request(urlArg: string, headersArg: Record<string, string>, bodyArg: string, timeoutMsArg?: number): Promise<string> {
205
+ this.assertOpen();
206
+ if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('BRAVIA cancellation requires an AbortSignal.');
207
+ if (signal?.aborted) throw this.failure('cancelled');
208
+ if (requireStarted && !this.#started) throw new Error('BRAVIA client has not started.');
209
+ const retained = [...this.#protocols.values()].filter((owned) => owned.failed && !owned.active).length;
210
+ const retainedCredentials = [...this.#credentialOperations.values(), ...this.#credentialFailures.values()]
211
+ .filter((operation) => !this.#operations.has(operation)).length;
212
+ if (this.#operations.size + retained + retainedCredentials >= this.config.maxPendingOperations) throw this.failure('capacity');
213
+ } catch (error) { return Promise.reject(error); }
214
+ const deadline = performance.now() + this.config.timeoutMs;
452
215
  const controller = new AbortController();
453
- const timeout = setTimeout(() => controller.abort(), timeoutMsArg || this.config.timeoutMs || defaultTimeoutMs);
454
- try {
455
- const response = await globalThis.fetch(urlArg, {
456
- method: 'POST',
457
- headers: {
458
- ...headersArg,
459
- 'Cache-Control': 'no-cache',
460
- Connection: 'keep-alive',
461
- 'X-Auth-PSK': this.config.psk as string,
462
- },
463
- body: bodyArg,
464
- signal: controller.signal,
465
- });
466
- const text = await response.text();
467
- if (response.status === 401 || response.status === 403) {
468
- throw new BraviatvAuthError(`Sony Bravia TV authentication failed with HTTP ${response.status}.`);
469
- }
470
- if (!response.ok) {
471
- throw new BraviatvLiveControlError(`Sony Bravia TV request failed with HTTP ${response.status}: ${text}`);
472
- }
473
- return text;
474
- } catch (errorArg) {
475
- if (errorArg instanceof BraviatvAuthError || errorArg instanceof BraviatvLiveControlError) {
476
- throw errorArg;
477
- }
478
- if (errorArg instanceof Error && errorArg.name === 'AbortError') {
479
- throw new BraviatvLiveControlError(`Sony Bravia TV request timed out after ${timeoutMsArg || this.config.timeoutMs || defaultTimeoutMs}ms.`);
480
- }
481
- throw new BraviatvLiveControlError(`Sony Bravia TV request failed: ${this.errorMessage(errorArg)}`);
482
- } finally {
483
- clearTimeout(timeout);
484
- }
485
- }
486
-
487
- private assertLivePskSupported(commandArg: string): void {
488
- if (!this.config.host) {
489
- throw new BraviatvLiveControlError(`Sony Bravia TV host is required for live ${commandArg}.`);
490
- }
491
- if (!this.config.psk) {
492
- if (this.config.pin || this.config.usePsk === false) {
493
- throw new BraviatvLiveControlError(`Sony Bravia TV PIN pairing/cookie authentication is not implemented in this native TypeScript port. Configure a PSK to use live ${commandArg}.`);
494
- }
495
- throw new BraviatvLiveControlError(`Sony Bravia TV PSK is required for live ${commandArg}.`);
496
- }
497
- }
498
-
499
- private hasLivePskConfig(): boolean {
500
- return Boolean(this.config.host && this.config.psk);
501
- }
502
-
503
- private serviceUrl(serviceArg: string): string {
504
- const protocol = this.config.useSsl ? 'https' : 'http';
505
- const port = this.config.port ? `:${this.config.port}` : '';
506
- return `${protocol}://${this.config.host}${port}/sony/${serviceArg}`;
507
- }
508
-
509
- private irccBody(codeArg: string): string {
510
- return `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendIRCC xmlns:u="urn:schemas-sony-com:service:IRCC:1"><IRCCCode>${codeArg}</IRCCCode></u:X_SendIRCC></s:Body></s:Envelope>`;
511
- }
512
-
513
- private firstResult<TResult>(responseArg: IBraviatvRestResponse): TResult | undefined {
514
- return responseArg.result?.[0] as TResult | undefined;
515
- }
516
-
517
- private async commandCode(commandArg: string): Promise<string | undefined> {
518
- const command = commandArg.trim();
519
- if (this.looksLikeIrccCode(command)) {
520
- return command;
521
- }
522
-
523
- const commands = await this.getCommandList().catch(() => ({ ...fallbackCommands, ...(this.config.commands || {}) }));
524
- const candidates = [command, this.normalizeCommand(command)].filter(Boolean);
525
- for (const candidate of candidates) {
526
- if (commands[candidate]) {
527
- return commands[candidate];
216
+ const cancel = () => controller.abort(this.failure('cancelled'));
217
+ signal?.addEventListener('abort', cancel, { once: true });
218
+ const combined = AbortSignal.any([controller.signal, this.#lifetime.signal]);
219
+ const timer = setTimeout(() => controller.abort(this.failure('timeout')), this.config.timeoutMs);
220
+ let cancelWaiting: () => void = () => {};
221
+ const waiting = new Promise<never>((_resolve, reject) => {
222
+ cancelWaiting = () => { try { this.active(combined, deadline); } catch (error) { reject(error); } };
223
+ combined.addEventListener('abort', cancelWaiting, { once: true });
224
+ });
225
+ let ownedProtocol: IBraviatvOwnedProtocol | undefined;
226
+ const operation = this.#tail.then(async () => {
227
+ combined.removeEventListener('abort', cancelWaiting);
228
+ this.active(combined, deadline);
229
+ const psk = await this.resolvePsk(combined, deadline, operation);
230
+ const timeoutMs = this.active(combined, deadline);
231
+ const protocol = new plugins.ecobridgeDeviceManager.SonyBraviaProtocol({ address: this.config.host,
232
+ expectedModel: this.config.expectedModel, expectedMacAddress: this.config.expectedMacAddress,
233
+ ...(this.config.expectedSerial === undefined ? {} : { expectedSerial: this.config.expectedSerial }), psk,
234
+ allowUnencryptedLan: true, volumeLimitRaw: this.config.volumeLimitRaw, port: this.config.port,
235
+ timeoutMs, maxPendingOperations: 1, maxResponseBytes: this.config.maxResponseBytes });
236
+ const owned: IBraviatvOwnedProtocol = { protocol, failed: false, active: true };
237
+ ownedProtocol = owned;
238
+ this.#protocols.set(protocol, owned);
239
+ let result: TResult | undefined;
240
+ let failure: unknown;
241
+ try {
242
+ this.active(combined, deadline);
243
+ result = await execute(protocol, { signal: combined, timeoutMs: this.active(combined, deadline) });
244
+ } catch (error) { failure = error; }
245
+ try { await this.closeProtocol(owned); }
246
+ catch {
247
+ const acknowledged = (result as Partial<plugins.ecobridgeDeviceManager.ISonyBraviaControlObservation>)?.commandAcknowledged === true
248
+ || failure instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError && failure.commandAcknowledged;
249
+ throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('transport', 'BRAVIA owned protocol cleanup failed.',
250
+ acknowledged ? 'indeterminate' : failure instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError ? failure.effectOutcome : 'not-sent', acknowledged);
528
251
  }
529
- const lowerCandidate = candidate.toLowerCase();
530
- const entry = Object.entries(commands).find(([name]) => name.toLowerCase() === lowerCandidate);
531
- if (entry) {
532
- return entry[1];
252
+ if (failure !== undefined) throw failure;
253
+ try { this.active(combined, deadline); }
254
+ catch (error) {
255
+ const acknowledged = (result as Partial<plugins.ecobridgeDeviceManager.ISonyBraviaControlObservation>)?.commandAcknowledged === true;
256
+ if (acknowledged) throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError(
257
+ error instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError ? error.code : 'cancelled',
258
+ 'BRAVIA operation ended after acknowledgment.', 'indeterminate', true);
259
+ throw error;
533
260
  }
534
- }
535
- return undefined;
536
- }
537
-
538
- private normalizeCommand(commandArg: string): string {
539
- const normalized = commandArg.replace(/[_\s-]+/g, '').toLowerCase();
540
- const aliases: Record<string, string> = {
541
- power: 'Power',
542
- poweron: 'PowerOn',
543
- input: 'Input',
544
- source: 'Input',
545
- hdmi1: 'Hdmi1',
546
- hdmi2: 'Hdmi2',
547
- hdmi3: 'Hdmi3',
548
- hdmi4: 'Hdmi4',
549
- up: 'Up',
550
- down: 'Down',
551
- left: 'Left',
552
- right: 'Right',
553
- ok: 'Confirm',
554
- enter: 'Confirm',
555
- select: 'Confirm',
556
- confirm: 'Confirm',
557
- back: 'Back',
558
- return: 'Back',
559
- home: 'Home',
560
- menu: 'Home',
561
- options: 'Options',
562
- info: 'Display',
563
- display: 'Display',
564
- volumeup: 'VolumeUp',
565
- volup: 'VolumeUp',
566
- volumedown: 'VolumeDown',
567
- voldown: 'VolumeDown',
568
- mute: 'Mute',
569
- channelup: 'ChannelUp',
570
- chup: 'ChannelUp',
571
- channeldown: 'ChannelDown',
572
- chdown: 'ChannelDown',
573
- play: 'Play',
574
- pause: 'Pause',
575
- stop: 'Stop',
576
- next: 'Next',
577
- previous: 'Prev',
578
- prev: 'Prev',
579
- };
580
- return aliases[normalized] || commandArg;
581
- }
582
-
583
- private looksLikeIrccCode(valueArg: string): boolean {
584
- return /^[A-Za-z0-9+/]+={0,2}$/.test(valueArg) && valueArg.length >= 16 && valueArg.includes('Aw');
585
- }
586
-
587
- private snapshotFromManualConfig(errorMessageArg?: string): IBraviatvSnapshot {
588
- const systemInfo = this.systemInfoFromConfig();
589
- const state: IBraviatvState = {
590
- powerStatus: this.config.state?.powerStatus || (this.config.state?.power === 'on' ? 'active' : this.config.state?.power === 'off' ? 'standby' : 'unknown'),
591
- available: Boolean(this.config.snapshot || this.config.state || this.config.systemInfo || this.config.host),
592
- ...this.config.state,
593
- lastError: errorMessageArg || this.config.state?.lastError,
594
- };
595
- return {
596
- systemInfo,
597
- state,
598
- sources: [...(this.config.sources || [])],
599
- apps: [...(this.config.apps || [])],
600
- channels: [...(this.config.channels || [])],
601
- commands: this.config.commands,
602
- updatedAt: new Date().toISOString(),
603
- };
604
- }
605
-
606
- private systemInfoFromConfig(): IBraviatvSystemInfo {
607
- return {
608
- ...this.config.systemInfo,
609
- name: this.config.systemInfo?.name || this.config.name || this.config.host || 'Sony Bravia TV',
610
- model: this.config.systemInfo?.model || this.config.model,
611
- serial: this.config.systemInfo?.serial || this.config.serialNumber,
612
- macAddr: this.config.systemInfo?.macAddr || this.config.macAddress,
613
- cid: this.config.systemInfo?.cid || this.config.cid || this.config.macAddress || this.config.host,
614
- product: this.config.systemInfo?.product || 'TV',
615
- };
616
- }
617
-
618
- private normalizeSnapshot(snapshotArg: IBraviatvSnapshot): IBraviatvSnapshot {
619
- const systemInfo = {
620
- ...this.systemInfoFromConfig(),
621
- ...snapshotArg.systemInfo,
622
- };
623
- const state = this.normalizeState(snapshotArg.state);
624
- return {
625
- ...snapshotArg,
626
- systemInfo,
627
- state,
628
- sources: this.normalizeSources(snapshotArg.sources, 'input'),
629
- apps: this.normalizeSources(snapshotArg.apps, 'app') as IBraviatvApp[],
630
- channels: this.normalizeSources(snapshotArg.channels || [], 'channel') as IBraviatvChannel[],
631
- updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
632
- };
633
- }
634
-
635
- private normalizeState(stateArg: IBraviatvState): IBraviatvState {
636
- const powerStatus = stateArg.powerStatus || (stateArg.power === 'on' ? 'active' : stateArg.power === 'off' ? 'standby' : 'unknown');
637
- const power = stateArg.power || this.powerFromStatus(powerStatus);
638
- let volumeLevel = stateArg.volumeLevel;
639
- if (typeof volumeLevel === 'number' && volumeLevel > 1) {
640
- volumeLevel /= 100;
641
- }
642
- if (typeof volumeLevel !== 'number' && typeof stateArg.volumePercent === 'number') {
643
- volumeLevel = stateArg.volumePercent / 100;
644
- }
645
- return {
646
- ...stateArg,
647
- powerStatus,
648
- power,
649
- playback: stateArg.playback || (power === 'off' ? 'off' : 'idle'),
650
- volumeLevel,
651
- volumePercent: typeof stateArg.volumePercent === 'number' ? stateArg.volumePercent : typeof volumeLevel === 'number' ? Math.round(volumeLevel * 100) : undefined,
652
- };
653
- }
654
-
655
- private normalizeSources(sourcesArg: IBraviatvSource[], typeArg: TBraviatvSourceType): IBraviatvSource[] {
656
- return sourcesArg.filter((sourceArg) => sourceArg.title && sourceArg.uri).map((sourceArg) => ({
657
- ...sourceArg,
658
- type: sourceArg.type || typeArg,
659
- }));
660
- }
661
-
662
- private applyVolumeInfo(stateArg: IBraviatvState, volumeInfoArg: IBraviatvVolumeInfo | undefined): void {
663
- if (!volumeInfoArg) {
664
- return;
665
- }
666
- const volume = typeof volumeInfoArg.volume === 'number' ? volumeInfoArg.volume : Number(volumeInfoArg.volume);
667
- if (Number.isFinite(volume)) {
668
- stateArg.volumePercent = volume;
669
- stateArg.volumeLevel = volume / 100;
670
- }
671
- stateArg.muted = volumeInfoArg.mute;
672
- }
673
-
674
- private applyPlayingInfo(stateArg: IBraviatvState, playingInfoArg: IBraviatvPlayingInfo | undefined): void {
675
- if (!playingInfoArg) {
676
- stateArg.mediaTitle = stateArg.mediaTitle || 'Smart TV';
677
- return;
678
- }
679
- stateArg.mediaTitle = playingInfoArg.programTitle || playingInfoArg.title || stateArg.mediaTitle;
680
- stateArg.mediaContentId = playingInfoArg.uri || playingInfoArg.dispNum || stateArg.mediaContentId;
681
- stateArg.sourceUri = playingInfoArg.uri || stateArg.sourceUri;
682
- stateArg.mediaDuration = playingInfoArg.durationSec;
683
- if (playingInfoArg.uri?.startsWith('extInput:')) {
684
- stateArg.source = playingInfoArg.title || stateArg.source;
685
- }
686
- if (playingInfoArg.uri?.startsWith('tv:')) {
687
- stateArg.mediaChannel = playingInfoArg.title || playingInfoArg.dispNum;
688
- stateArg.mediaContentType = 'channel';
689
- } else if (playingInfoArg.uri) {
690
- stateArg.mediaContentType = this.inferSourceType(playingInfoArg.uri);
691
- }
692
- if (playingInfoArg.startDateTime) {
693
- const startTime = Date.parse(playingInfoArg.startDateTime);
694
- if (Number.isFinite(startTime)) {
695
- stateArg.mediaPosition = Math.max(0, Math.floor((Date.now() - startTime) / 1000));
696
- stateArg.mediaPositionUpdatedAt = new Date().toISOString();
697
- }
698
- }
699
- }
700
-
701
- private inferSourceType(uriArg: string): TBraviatvSourceType {
702
- if (uriArg.startsWith('extInput:')) {
703
- return 'input';
704
- }
705
- if (uriArg.startsWith('tv:')) {
706
- return 'channel';
707
- }
708
- return 'app';
709
- }
710
-
711
- private powerFromStatus(statusArg: string): 'on' | 'off' | 'unknown' {
712
- const status = statusArg.toLowerCase();
713
- if (status === 'active') {
714
- return 'on';
715
- }
716
- if (status === 'standby' || status === 'off') {
717
- return 'off';
718
- }
719
- return 'unknown';
720
- }
721
-
722
- private updateLocalState(stateArg: Partial<IBraviatvState>): void {
723
- const state = this.config.snapshot?.state || this.config.state || {};
724
- Object.assign(state, stateArg);
725
- if (this.config.snapshot) {
726
- this.config.snapshot.state = state;
727
- return;
728
- }
729
- this.config.state = state;
730
- }
731
-
732
- private cloneSnapshot(snapshotArg: IBraviatvSnapshot): IBraviatvSnapshot {
733
- return JSON.parse(JSON.stringify(snapshotArg)) as IBraviatvSnapshot;
734
- }
735
-
736
- private errorMessage(errorArg: unknown): string {
737
- return errorArg instanceof Error ? errorArg.message : String(errorArg);
261
+ return result as TResult;
262
+ }).catch((error: unknown) => { this.#healthy = false; throw error; }).finally(() => {
263
+ clearTimeout(timer);
264
+ signal?.removeEventListener('abort', cancel);
265
+ combined.removeEventListener('abort', cancelWaiting);
266
+ if (ownedProtocol) ownedProtocol.active = false;
267
+ this.#operations.delete(operation);
268
+ });
269
+ this.#operations.add(operation);
270
+ this.#tail = operation.then(() => {}, () => {});
271
+ if (signal?.aborted) cancel();
272
+ if (combined.aborted) cancelWaiting();
273
+ return Promise.race([operation, waiting]);
738
274
  }
739
275
  }