homematic-manager 3.0.0-beta.2 → 3.0.0-beta.21

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 (332) hide show
  1. package/README.md +2 -1
  2. package/data/easymode-time-selectors.json +285 -0
  3. package/data/icons/106_hm-lc-sw1-dr.webp +0 -0
  4. package/data/icons/108_hm-rc-dis-h-x-eu.webp +0 -0
  5. package/data/icons/109_hm-lc-sw1-pl-ct.webp +0 -0
  6. package/data/icons/110_hm-es-pmsw1-dr.webp +0 -0
  7. package/data/icons/112_hmip-wrc2.webp +0 -0
  8. package/data/icons/113_hmip-psm-ch.webp +0 -0
  9. package/data/icons/113_hmip-psm-it.webp +0 -0
  10. package/data/icons/113_hmip-psm-pe.webp +0 -0
  11. package/data/icons/113_hmip-psm-uk.webp +0 -0
  12. package/data/icons/113_hmip-psm.webp +0 -0
  13. package/data/icons/114_hm-lc-dim1t-fm-lf_3.webp +0 -0
  14. package/data/icons/116_hm-rc-4-3_brc-h3.webp +0 -0
  15. package/data/icons/118_hmip-swdo.webp +0 -0
  16. package/data/icons/119_hmip-rc8.webp +0 -0
  17. package/data/icons/120_hmip-etrv.webp +0 -0
  18. package/data/icons/121_hmip-wth.webp +0 -0
  19. package/data/icons/123_oligo.smart.ip.hm.webp +0 -0
  20. package/data/icons/125_hmip-smi.webp +0 -0
  21. package/data/icons/130_hmip-srh.webp +0 -0
  22. package/data/icons/131_hmip-wrc6.webp +0 -0
  23. package/data/icons/132_hmip-smo.webp +0 -0
  24. package/data/icons/133_hmip-asir.webp +0 -0
  25. package/data/icons/134_hmip-fsm.webp +0 -0
  26. package/data/icons/135_hmip-fsm16.webp +0 -0
  27. package/data/icons/136_hmip-miob.webp +0 -0
  28. package/data/icons/137_hmip-fal-c6.webp +0 -0
  29. package/data/icons/138_hmip-fal-c10.webp +0 -0
  30. package/data/icons/140_alpha-ip-rgb.webp +0 -0
  31. package/data/icons/141_alpha-ip-rgba.webp +0 -0
  32. package/data/icons/144_hmip-wgc.webp +0 -0
  33. package/data/icons/145_hmip-froll_hmip-fbl.webp +0 -0
  34. package/data/icons/146_hmip-sth.webp +0 -0
  35. package/data/icons/147_hmip-sthd.webp +0 -0
  36. package/data/icons/148_hmip-stho.webp +0 -0
  37. package/data/icons/149_hmip-sam.webp +0 -0
  38. package/data/icons/151_hmip-pcbs-bat.webp +0 -0
  39. package/data/icons/152_hmip-swdo-i.webp +0 -0
  40. package/data/icons/153_hmip-spi.webp +0 -0
  41. package/data/icons/154_hmip-spdr.webp +0 -0
  42. package/data/icons/155_hmip-whs2.webp +0 -0
  43. package/data/icons/156_hmip-mod-oc8.webp +0 -0
  44. package/data/icons/158_hmip-etrv-uk.webp +0 -0
  45. package/data/icons/159_hmip-mod-rc8.webp +0 -0
  46. package/data/icons/160_hmipw-drs4.webp +0 -0
  47. package/data/icons/161_hmipw-drs8.webp +0 -0
  48. package/data/icons/162_hmipw-drap.webp +0 -0
  49. package/data/icons/163_hmipw-drbl4.webp +0 -0
  50. package/data/icons/164_hmipw-dri16.webp +0 -0
  51. package/data/icons/165_hmipw-fio6.webp +0 -0
  52. package/data/icons/166_hmipw-drd3.webp +0 -0
  53. package/data/icons/167_hmipw-dri32.webp +0 -0
  54. package/data/icons/168_hmip-smi55.webp +0 -0
  55. package/data/icons/169_hmip-swo-pr.webp +0 -0
  56. package/data/icons/170_hmip-swo-pl.webp +0 -0
  57. package/data/icons/171_hmip-swo-b.webp +0 -0
  58. package/data/icons/172_hmip-swd.webp +0 -0
  59. package/data/icons/173_hmip-bsl.webp +0 -0
  60. package/data/icons/174_hmip-slo.webp +0 -0
  61. package/data/icons/175_hmip-group-heating.webp +0 -0
  62. package/data/icons/177_hmip-dbb.webp +0 -0
  63. package/data/icons/178_hmip-etrv-b1.webp +0 -0
  64. package/data/icons/179_hmip-swdm-b2.webp +0 -0
  65. package/data/icons/180_hmip-etrv-b.webp +0 -0
  66. package/data/icons/181_hmip-swdm.webp +0 -0
  67. package/data/icons/182_hmip-fci1.webp +0 -0
  68. package/data/icons/183_hmip-mod-tm.webp +0 -0
  69. package/data/icons/184_hmip-pcbs2.webp +0 -0
  70. package/data/icons/185_hmip-FCI6.webp +0 -0
  71. package/data/icons/186_hmip-mp3p.webp +0 -0
  72. package/data/icons/187_hmip-rcb1.webp +0 -0
  73. package/data/icons/188_hmip-etrv-c.webp +0 -0
  74. package/data/icons/190_hmip-sci.webp +0 -0
  75. package/data/icons/191_hmip-mod-ho.webp +0 -0
  76. package/data/icons/192_hmip-asir-o.webp +0 -0
  77. package/data/icons/193_hmip-wt.webp +0 -0
  78. package/data/icons/194_hmip-wrcd.webp +0 -0
  79. package/data/icons/195_hmipw-smi55.webp +0 -0
  80. package/data/icons/196_hmip-ASIR-2.webp +0 -0
  81. package/data/icons/197_hmip-swdo-pl.webp +0 -0
  82. package/data/icons/198_hmip-falmot-c12.webp +0 -0
  83. package/data/icons/199_hmip-mio16-pcb.webp +0 -0
  84. package/data/icons/200_hmip-wth-b.webp +0 -0
  85. package/data/icons/201_hmip-stv.webp +0 -0
  86. package/data/icons/202_hmip-wrcr.webp +0 -0
  87. package/data/icons/203_hmip-fsi16.webp +0 -0
  88. package/data/icons/204_hmip-drdi3.webp +0 -0
  89. package/data/icons/205_hmip-drsi4.webp +0 -0
  90. package/data/icons/206_hmip-drbli4.webp +0 -0
  91. package/data/icons/207_hmip-srdo.webp +0 -0
  92. package/data/icons/208_hmip-dsd-pcb.webp +0 -0
  93. package/data/icons/209_hmip-etrv-b-uk.webp +0 -0
  94. package/data/icons/210_hmip-ste2-pcb.webp +0 -0
  95. package/data/icons/211_hmip-drg-dali.webp +0 -0
  96. package/data/icons/211_hmip-drsi1.webp +0 -0
  97. package/data/icons/212_hmip-sfd.webp +0 -0
  98. package/data/icons/212_hmipw-scthd.webp +0 -0
  99. package/data/icons/213_hmip-smo230.webp +0 -0
  100. package/data/icons/213_hmip-wua.webp +0 -0
  101. package/data/icons/214_hmip-dld.webp +0 -0
  102. package/data/icons/215_hmip-psm-pe-2.webp +0 -0
  103. package/data/icons/215_hmip-scth230.webp +0 -0
  104. package/data/icons/216_hmip-etrv-e.webp +0 -0
  105. package/data/icons/216_hmip-psm-ch-2.webp +0 -0
  106. package/data/icons/217_hmip-usbsm.webp +0 -0
  107. package/data/icons/218_hmip-dls.webp +0 -0
  108. package/data/icons/219_hmip-fwi.webp +0 -0
  109. package/data/icons/220_hmip-wsc.webp +0 -0
  110. package/data/icons/221_hmip-sti.webp +0 -0
  111. package/data/icons/221_hmip-wkp.webp +0 -0
  112. package/data/icons/222_hmipw-wgd.webp +0 -0
  113. package/data/icons/223_hmip-mod-wd-vk.webp +0 -0
  114. package/data/icons/224_hmip-etrv-cl.webp +0 -0
  115. package/data/icons/225_hmip-rgbw.webp +0 -0
  116. package/data/icons/226_hmip-esi.webp +0 -0
  117. package/data/icons/227_hmip-lsc.webp +0 -0
  118. package/data/icons/228_elv-sh-sw1-bat.webp +0 -0
  119. package/data/icons/229_elv-sh-bm-s.webp +0 -0
  120. package/data/icons/230_hmip-etrv-f.webp +0 -0
  121. package/data/icons/231_elv-sh-cth.webp +0 -0
  122. package/data/icons/232_elv-sh-taco.webp +0 -0
  123. package/data/icons/233_hmip-flc.webp +0 -0
  124. package/data/icons/234_hmip-etrv-3.webp +0 -0
  125. package/data/icons/235_hmip-psmco.webp +0 -0
  126. package/data/icons/236_elv-sh-sb8.webp +0 -0
  127. package/data/icons/237_elv-sh-ctv.webp +0 -0
  128. package/data/icons/238_elv-sh-cap.webp +0 -0
  129. package/data/icons/239_hmip-wgs-f.webp +0 -0
  130. package/data/icons/240_hmip-wgtc-f.webp +0 -0
  131. package/data/icons/241_hmip-wgt-f.webp +0 -0
  132. package/data/icons/242_elv-sh-smsi.webp +0 -0
  133. package/data/icons/243_elv-sh-crc.webp +0 -0
  134. package/data/icons/244_elv-sh-krco.webp +0 -0
  135. package/data/icons/245_hmip-falmot-c8.webp +0 -0
  136. package/data/icons/246_elv-sh-wsm.webp +0 -0
  137. package/data/icons/247_hmip-hap-2.webp +0 -0
  138. package/data/icons/248_hmip-dlp.webp +0 -0
  139. package/data/icons/249_elv-sh-psmci.webp +0 -0
  140. package/data/icons/250_elv-sh-sps25.webp +0 -0
  141. package/data/icons/252_elv-sh-pti2.webp +0 -0
  142. package/data/icons/253_hmip-udi-smi55.webp +0 -0
  143. package/data/icons/254_hmip-udi-pb2.webp +0 -0
  144. package/data/icons/256_ELV-SH-SMS2.webp +0 -0
  145. package/data/icons/257_ELV-SH-FSI.webp +0 -0
  146. package/data/icons/258_hmip-m-td15.webp +0 -0
  147. package/data/icons/259_elv-sh-dusi.webp +0 -0
  148. package/data/icons/260_hmip-wss.webp +0 -0
  149. package/data/icons/54a_lc-ddc1-pcb.webp +0 -0
  150. package/data/icons/72_hm-rc-brc-h.webp +0 -0
  151. package/data/icons/73_hm-atent.webp +0 -0
  152. package/data/icons/79_hm-lc-dim1pwm-cv.webp +0 -0
  153. package/data/icons/84_hm-rc-4-3.webp +0 -0
  154. package/data/icons/85_hm-rc-sec4-3.webp +0 -0
  155. package/data/icons/86_hm-rc-key4-3.webp +0 -0
  156. package/data/icons/CCU3-1-50.webp +0 -0
  157. package/data/icons/CCU3.webp +0 -0
  158. package/data/icons/hm-coupling-dim.webp +0 -0
  159. package/data/icons/hm-coupling-group.webp +0 -0
  160. package/data/icons/hm-coupling-onoff.webp +0 -0
  161. package/data/icons/hm-coupling-rgb-dim.webp +0 -0
  162. package/data/icons/hm-coupling-rgb.webp +0 -0
  163. package/data/icons/hm-coupling-rgbw-dim.webp +0 -0
  164. package/data/icons/hm-coupling-rgbw.webp +0 -0
  165. package/data/icons/hm-coupling-white-dim.webp +0 -0
  166. package/data/icons/hm-coupling-white.webp +0 -0
  167. package/data/icons/hm-hue_gateway.webp +0 -0
  168. package/data/icons/hm-lightify_gateway.webp +0 -0
  169. package/data/icons/hm_resc-win-pcb-sc.webp +0 -0
  170. package/data/icons/unknown_device.webp +0 -0
  171. package/data/manifest.json +7 -1
  172. package/data/master-metadata.json +1997 -33
  173. package/data/profiles/ACCESS_RECEIVER.json +26 -13
  174. package/data/profiles/ACOUSTIC_SIGNAL_VIRTUAL_RECEIVER.json +3270 -76
  175. package/data/profiles/ACTOR_SECURITY.json +90 -10
  176. package/data/profiles/ACTOR_WINDOW.json +580 -43
  177. package/data/profiles/ALARMACTUATOR.json +564 -37
  178. package/data/profiles/ALARM_COND_SWITCH_RECEIVER.json +36 -4
  179. package/data/profiles/ALARM_SWITCH_VIRTUAL_RECEIVER.json +735 -33
  180. package/data/profiles/ARMING.json +115 -5
  181. package/data/profiles/BLIND.json +1038 -49
  182. package/data/profiles/BLIND_VIRTUAL_RECEIVER.json +4624 -124
  183. package/data/profiles/CLIMATECONTROL_FLOOR_PUMP_TRANSCEIVER.json +27 -1
  184. package/data/profiles/DIMMER.json +2233 -71
  185. package/data/profiles/DIMMER_VIRTUAL_RECEIVER.json +5773 -102
  186. package/data/profiles/DIMMER_woLongKeyPress.json +117 -4
  187. package/data/profiles/DOOR_RECEIVER.json +138 -10
  188. package/data/profiles/DUAL_WHITE_BRIGHTNESS.json +305 -11
  189. package/data/profiles/DUAL_WHITE_COLOR.json +730 -23
  190. package/data/profiles/HEATING_KEY_RECEIVER.json +127 -10
  191. package/data/profiles/HMW_BLIND.json +555 -20
  192. package/data/profiles/HMW_DIMMER.json +1337 -47
  193. package/data/profiles/HMW_INPUT_OUTPUT.json +375 -20
  194. package/data/profiles/HMW_SWITCH.json +375 -20
  195. package/data/profiles/JALOUSIE.json +1320 -52
  196. package/data/profiles/KEYMATIC.json +120 -12
  197. package/data/profiles/REMOTECONTROL_RECEIVER.json +10 -2
  198. package/data/profiles/RGBW_AUTOMATIC.json +46 -6
  199. package/data/profiles/RGBW_COLOR.json +14 -4
  200. package/data/profiles/SERVO_VIRTUAL_RECEIVER.json +117 -3
  201. package/data/profiles/SHUTTER_VIRTUAL_RECEIVER.json +4263 -128
  202. package/data/profiles/SIGNAL_CHIME.json +195 -14
  203. package/data/profiles/SIGNAL_CHIMEM.json +195 -14
  204. package/data/profiles/SIGNAL_LED.json +195 -14
  205. package/data/profiles/SIGNAL_LEDM.json +195 -14
  206. package/data/profiles/SIMPLE_SWITCH_RECEIVER.json +113 -9
  207. package/data/profiles/STATUS_INDICATOR.json +1073 -45
  208. package/data/profiles/SWITCH.json +1412 -60
  209. package/data/profiles/SWITCH_PANIC.json +315 -13
  210. package/data/profiles/SWITCH_SENSOR.json +243 -11
  211. package/data/profiles/SWITCH_VIRTUAL_RECEIVER.json +3963 -131
  212. package/data/profiles/UNIVERSAL_ACTOR.json +4319 -90
  213. package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_LSC.json +6094 -196
  214. package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_PWM.json +4920 -101
  215. package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_RGB(W).json +2353 -80
  216. package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_RGBW_DALI.json +6116 -176
  217. package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_TW.json +3844 -104
  218. package/data/profiles/VIRTUAL_DIMMER.json +305 -11
  219. package/data/profiles/VIRTUAL_DUAL_WHITE_COLOR.json +730 -23
  220. package/data/profiles/WATER_SWITCH_VIRTUAL_RECEIVER.json +3903 -128
  221. package/data/profiles/WINDOW_DRIVE_RECEIVER.json +220 -8
  222. package/data/profiles/WINMATIC.json +505 -23
  223. package/dist/cli.js +24 -0
  224. package/dist/occulite.d.ts +86 -0
  225. package/dist/occulite.js +140 -0
  226. package/dist/options.d.ts +37 -6
  227. package/dist/options.js +49 -6
  228. package/dist/server.d.ts +47 -4
  229. package/dist/server.js +237 -27
  230. package/dist/sessions.d.ts +9 -1
  231. package/dist/sessions.js +8 -2
  232. package/dist/testSupport.js +9 -2
  233. package/node_modules/@homematic-manager/backend/dist/api/backend.d.ts +33 -0
  234. package/node_modules/@homematic-manager/backend/dist/api/backend.js +558 -46
  235. package/node_modules/@homematic-manager/backend/dist/cache/names.d.ts +9 -0
  236. package/node_modules/@homematic-manager/backend/dist/cache/names.js +21 -0
  237. package/node_modules/@homematic-manager/backend/dist/config/defaults.js +10 -1
  238. package/node_modules/@homematic-manager/backend/dist/config/store.d.ts +28 -1
  239. package/node_modules/@homematic-manager/backend/dist/config/store.js +59 -3
  240. package/node_modules/@homematic-manager/backend/dist/devices/installMode.js +9 -0
  241. package/node_modules/@homematic-manager/backend/dist/errors.d.ts +18 -0
  242. package/node_modules/@homematic-manager/backend/dist/errors.js +51 -2
  243. package/node_modules/@homematic-manager/backend/dist/images/deviceImages.d.ts +6 -0
  244. package/node_modules/@homematic-manager/backend/dist/images/deviceImages.js +38 -2
  245. package/node_modules/@homematic-manager/backend/dist/index.d.ts +3 -1
  246. package/node_modules/@homematic-manager/backend/dist/index.js +4 -1
  247. package/node_modules/@homematic-manager/backend/dist/interfaces/manager.d.ts +70 -8
  248. package/node_modules/@homematic-manager/backend/dist/interfaces/manager.js +333 -44
  249. package/node_modules/@homematic-manager/backend/dist/meta/client.d.ts +120 -0
  250. package/node_modules/@homematic-manager/backend/dist/meta/client.js +307 -0
  251. package/node_modules/@homematic-manager/backend/dist/meta/credentials.d.ts +38 -0
  252. package/node_modules/@homematic-manager/backend/dist/meta/credentials.js +53 -0
  253. package/node_modules/@homematic-manager/backend/dist/meta/index.d.ts +12 -0
  254. package/node_modules/@homematic-manager/backend/dist/meta/index.js +12 -0
  255. package/node_modules/@homematic-manager/backend/dist/meta/localProvider.d.ts +47 -0
  256. package/node_modules/@homematic-manager/backend/dist/meta/localProvider.js +119 -0
  257. package/node_modules/@homematic-manager/backend/dist/meta/occuliteProvider.d.ts +79 -0
  258. package/node_modules/@homematic-manager/backend/dist/meta/occuliteProvider.js +305 -0
  259. package/node_modules/@homematic-manager/backend/dist/meta/provider.d.ts +68 -0
  260. package/node_modules/@homematic-manager/backend/dist/meta/provider.js +19 -0
  261. package/node_modules/@homematic-manager/backend/dist/meta/regaProvider.d.ts +84 -0
  262. package/node_modules/@homematic-manager/backend/dist/meta/regaProvider.js +403 -0
  263. package/node_modules/@homematic-manager/backend/dist/meta/service.d.ts +137 -0
  264. package/node_modules/@homematic-manager/backend/dist/meta/service.js +412 -0
  265. package/node_modules/@homematic-manager/backend/dist/rega/client.d.ts +11 -0
  266. package/node_modules/@homematic-manager/backend/dist/rega/client.js +13 -0
  267. package/node_modules/@homematic-manager/backend/dist/rega/scripts.d.ts +51 -0
  268. package/node_modules/@homematic-manager/backend/dist/rega/scripts.js +171 -0
  269. package/node_modules/@homematic-manager/backend/dist/rpc/client.d.ts +18 -5
  270. package/node_modules/@homematic-manager/backend/dist/rpc/client.js +11 -5
  271. package/node_modules/@homematic-manager/backend/dist/rpc/log.d.ts +86 -0
  272. package/node_modules/@homematic-manager/backend/dist/rpc/log.js +241 -0
  273. package/node_modules/@homematic-manager/backend/dist/rpc/origin.d.ts +30 -0
  274. package/node_modules/@homematic-manager/backend/dist/rpc/origin.js +35 -0
  275. package/node_modules/@homematic-manager/backend/dist/rpc/server.d.ts +12 -0
  276. package/node_modules/@homematic-manager/backend/dist/rpc/server.js +37 -5
  277. package/node_modules/@homematic-manager/backend/dist/util/net.d.ts +86 -1
  278. package/node_modules/@homematic-manager/backend/dist/util/net.js +223 -9
  279. package/node_modules/@homematic-manager/backend/package.json +1 -1
  280. package/node_modules/@homematic-manager/core/dist/api/types.d.ts +397 -10
  281. package/node_modules/@homematic-manager/core/dist/api/types.js +23 -2
  282. package/node_modules/@homematic-manager/core/dist/data/memory.d.ts +3 -1
  283. package/node_modules/@homematic-manager/core/dist/data/memory.js +3 -0
  284. package/node_modules/@homematic-manager/core/dist/data/types.d.ts +60 -0
  285. package/node_modules/@homematic-manager/core/dist/data/types.js +2 -0
  286. package/node_modules/@homematic-manager/core/dist/devices/index.d.ts +19 -0
  287. package/node_modules/@homematic-manager/core/dist/devices/index.js +53 -0
  288. package/node_modules/@homematic-manager/core/dist/easymodes/engine.d.ts +3 -1
  289. package/node_modules/@homematic-manager/core/dist/easymodes/engine.js +1 -0
  290. package/node_modules/@homematic-manager/core/dist/easymodes/form.d.ts +43 -0
  291. package/node_modules/@homematic-manager/core/dist/easymodes/form.js +84 -0
  292. package/node_modules/@homematic-manager/core/dist/i18n/messages.js +61 -0
  293. package/node_modules/@homematic-manager/core/dist/index.d.ts +3 -0
  294. package/node_modules/@homematic-manager/core/dist/index.js +4 -0
  295. package/node_modules/@homematic-manager/core/dist/interfaces/table.d.ts +23 -2
  296. package/node_modules/@homematic-manager/core/dist/interfaces/table.js +18 -1
  297. package/node_modules/@homematic-manager/core/dist/meta/document.d.ts +71 -0
  298. package/node_modules/@homematic-manager/core/dist/meta/document.js +260 -0
  299. package/node_modules/@homematic-manager/core/dist/meta/follow.d.ts +27 -0
  300. package/node_modules/@homematic-manager/core/dist/meta/follow.js +180 -0
  301. package/node_modules/@homematic-manager/core/dist/meta/index.d.ts +16 -0
  302. package/node_modules/@homematic-manager/core/dist/meta/index.js +16 -0
  303. package/node_modules/@homematic-manager/core/dist/meta/paths.d.ts +96 -0
  304. package/node_modules/@homematic-manager/core/dist/meta/paths.js +182 -0
  305. package/node_modules/@homematic-manager/core/dist/meta/slug.d.ts +22 -0
  306. package/node_modules/@homematic-manager/core/dist/meta/slug.js +59 -0
  307. package/node_modules/@homematic-manager/core/dist/meta/store.d.ts +147 -0
  308. package/node_modules/@homematic-manager/core/dist/meta/store.js +537 -0
  309. package/node_modules/@homematic-manager/core/dist/meta/tree.d.ts +19 -0
  310. package/node_modules/@homematic-manager/core/dist/meta/tree.js +53 -0
  311. package/node_modules/@homematic-manager/core/dist/meta/types.d.ts +143 -0
  312. package/node_modules/@homematic-manager/core/dist/meta/types.js +63 -0
  313. package/node_modules/@homematic-manager/core/dist/meta/view.d.ts +50 -0
  314. package/node_modules/@homematic-manager/core/dist/meta/view.js +103 -0
  315. package/node_modules/@homematic-manager/core/dist/rpc/methods.d.ts +10 -0
  316. package/node_modules/@homematic-manager/core/dist/rpc/methods.js +51 -1
  317. package/node_modules/@homematic-manager/core/dist/rpc/text.d.ts +22 -0
  318. package/node_modules/@homematic-manager/core/dist/rpc/text.js +46 -0
  319. package/node_modules/@homematic-manager/core/dist/rssi/index.d.ts +125 -13
  320. package/node_modules/@homematic-manager/core/dist/rssi/index.js +146 -29
  321. package/node_modules/@homematic-manager/core/dist/serviceMessages/index.d.ts +28 -2
  322. package/node_modules/@homematic-manager/core/dist/serviceMessages/index.js +44 -6
  323. package/node_modules/@homematic-manager/core/package.json +1 -1
  324. package/package.json +3 -3
  325. package/ui/assets/index-D-NsbBVS.css +1 -0
  326. package/ui/assets/index-DNUNMBbb.js +16 -0
  327. package/ui/favicon.ico +0 -0
  328. package/ui/index.html +3 -2
  329. package/node_modules/@homematic-manager/backend/dist/write/log.d.ts +0 -51
  330. package/node_modules/@homematic-manager/backend/dist/write/log.js +0 -151
  331. package/ui/assets/index-lB-HxsKR.js +0 -13
  332. package/ui/assets/index-qbhgN2pA.css +0 -1
package/dist/server.js CHANGED
@@ -24,11 +24,12 @@
24
24
  import fs from 'node:fs/promises';
25
25
  import http from 'node:http';
26
26
  import path from 'node:path';
27
- import { ApiWebSocketServer, Backend, RegaAuthenticator } from '@homematic-manager/backend';
28
- import { applyCookieToken, applySessionToken, clearedSessionCookie, createToken, isLoopbackHost, readCookie, SESSION_COOKIE, sessionCookie, TOKEN_COOKIE, tokenCookie, } from './auth.js';
27
+ import { ApiWebSocketServer, Backend, RegaAuthenticator, } from '@homematic-manager/backend';
28
+ import { applyCookieToken, applySessionToken, clearedSessionCookie, createToken, hasTokenQuery, isLoopbackHost, readCookie, SESSION_COOKIE, sessionCookie, TOKEN_COOKIE, tokenCookie, withTokenQuery, } from './auth.js';
29
29
  import { DeviceImageService, readIconMapFile } from './images.js';
30
30
  import { clientAddress, parseLoginForm, pickLanguage, readBody, renderLoginPage, } from './login.js';
31
31
  import { createLogger, silentLogger } from './log.js';
32
+ import { OCCULITE_SESSION_HEADER, OcculiteAuthenticator, parseSid, sidCanBeSession, } from './occulite.js';
32
33
  import { defaultDataDir, defaultMetadataDir, defaultUiDir, packageVersion } from './paths.js';
33
34
  import { proxyRequest, proxyUpgrade } from './proxy.js';
34
35
  import { RateLimiter, SessionStore } from './sessions.js';
@@ -39,6 +40,12 @@ export const DEFAULT_PORT = 8090;
39
40
  export const DEFAULT_HOST = '127.0.0.1';
40
41
  /** How long `close()` waits for `backend.stop()` before it gives up and exits anyway. */
41
42
  export const SHUTDOWN_TIMEOUT_MS = 5000;
43
+ /**
44
+ * B-94, D-65: how long the box's answer about an `X-Occulite-Session` is believed without asking
45
+ * again. The addon contract says to cache it briefly, if at all; a minute covers the burst of
46
+ * assets and the socket of one page load.
47
+ */
48
+ export const GATE_RECHECK_MS = 60_000;
42
49
  /**
43
50
  * How often an idle API socket is pinged. Below lighttpd's `server.max-read-idle` /
44
51
  * `max-write-idle` default of 60 s, which is what would otherwise cut the proxied socket of the
@@ -77,8 +84,8 @@ export async function createWebHost(options = {}) {
77
84
  const apiPath = `${base}api`;
78
85
  const authMode = options.authMode ?? 'token';
79
86
  requireLocalForRega(authMode, options, auth);
80
- // D-32: everything the login needs, and nothing at all in `token` mode
81
- const sessions = authMode === 'rega' ? new SessionStore(sessionStoreOptions(options)) : undefined;
87
+ // D-32/D-40: everything a login needs, and nothing at all in `token` mode
88
+ const sessions = authMode === 'token' ? undefined : new SessionStore(sessionStoreOptions(options));
82
89
  const loginLimit = authMode === 'rega' ? new RateLimiter() : undefined;
83
90
  const credentials = authMode === 'rega'
84
91
  ? (options.authenticator ??
@@ -86,10 +93,33 @@ export async function createWebHost(options = {}) {
86
93
  onNotice: (level, message) => log[level](`login: ${message}`),
87
94
  }))
88
95
  : undefined;
96
+ // D-40: the box that issued the session the shell hands over
97
+ const occuliteUrl = (options.occuliteUrl ?? 'http://127.0.0.1').replace(/\/$/, '');
98
+ const boxSessions = authMode === 'occulite'
99
+ ? (options.sessionChecker ??
100
+ new OcculiteAuthenticator({
101
+ baseUrl: occuliteUrl,
102
+ onNotice: (level, message) => log[level](`login: ${message}`),
103
+ }))
104
+ : undefined;
89
105
  let connection;
90
106
  let backend;
91
107
  if (!demo) {
92
108
  await ensureDataDir(dataDir);
109
+ const defaultCallbackPorts = callbackDefaults(options);
110
+ if (defaultCallbackPorts !== undefined) {
111
+ log.info(`callback: default ports xmlrpc=${String(defaultCallbackPorts.xmlrpc)} binrpc=${String(defaultCallbackPorts.binrpc)} while the configuration says 0`);
112
+ }
113
+ const pinnedCallback = callbackPins(options);
114
+ if (pinnedCallback !== undefined) {
115
+ const { ip, xmlrpcPort, binrpcPort } = pinnedCallback;
116
+ const parts = [
117
+ ...(ip === undefined ? [] : [`ip=${ip}`]),
118
+ ...(xmlrpcPort === undefined ? [] : [`xmlrpc=${String(xmlrpcPort)}`]),
119
+ ...(binrpcPort === undefined ? [] : [`binrpc=${String(binrpcPort)}`]),
120
+ ];
121
+ log.info(`callback: ${parts.join(' ')} set at start, read-only in the settings dialog`);
122
+ }
93
123
  backend = await Backend.open({
94
124
  dataDir,
95
125
  version: options.version ?? packageVersion(),
@@ -98,6 +128,9 @@ export async function createWebHost(options = {}) {
98
128
  // reports them, and Electron's in-process transport reports none - so an Electron
99
129
  // window can never be idled out however the backend is configured.
100
130
  ...(options.idleUnsubscribeMs === undefined ? {} : { idleUnsubscribeMs: options.idleUnsubscribeMs }),
131
+ ...(defaultCallbackPorts === undefined ? {} : { defaultCallbackPorts }),
132
+ ...(pinnedCallback === undefined ? {} : { pinnedCallback }),
133
+ ...(options.inContainer === true ? { inContainer: true } : {}),
101
134
  ...options.backendOptions,
102
135
  });
103
136
  backend.on('notice', (notice) => {
@@ -146,6 +179,36 @@ export async function createWebHost(options = {}) {
146
179
  return;
147
180
  }
148
181
  const rest = pathname.slice(base.length);
182
+ // D-40: the hand-over from openccu-lite's shell. It opens an addon page with the user's
183
+ // session on the URL (`?sid=@xxxxxxxxxx@`), the same convention the CCU's WebUI uses; the
184
+ // session is checked against the box, turned into one of ours, and taken off the URL again
185
+ // so that it does not sit in the address bar, in a bookmark and in every referrer.
186
+ if (boxSessions && sessions && (method === 'GET' || method === 'HEAD')) {
187
+ const offered = parseSid(url.searchParams.get('sid'));
188
+ if (offered !== undefined) {
189
+ // B-36: the gate header first. Since openccu-lite task 125 the `?sid=` a shell puts on
190
+ // the URL is the session's legacy alias, which the box's API refuses, while the same
191
+ // request carries the session the gate validated: a confirmed header signs the request
192
+ // in, whatever `?sid=` says, and the `?sid=` only comes off the URL.
193
+ const gate = gateSid(request);
194
+ if (gate !== undefined) {
195
+ const session = await sessionFromGate(request, sessions.get(readCookie(request.headers.cookie, SESSION_COOKIE)));
196
+ if (session?.credential === gate) {
197
+ redirectWithoutSid(request, response, session, url, rest);
198
+ return;
199
+ }
200
+ }
201
+ if (!sidCanBeSession(offered, gate)) {
202
+ // what the box would answer, without asking it: see sidCanBeSession
203
+ log.warn(`login: the session offered from ${clientAddress(request)} cannot be one of this openccu-lite's (not asked)`);
204
+ response.writeHead(302, { Location: '/', 'Cache-Control': 'no-store' });
205
+ response.end();
206
+ return;
207
+ }
208
+ await handleHandover(request, response, offered, url, rest);
209
+ return;
210
+ }
211
+ }
149
212
  // D-32: the login is the one route that takes a POST, and the one route that is reachable
150
213
  // without a session - it is what hands one out
151
214
  if (sessions && rest === 'login') {
@@ -168,7 +231,9 @@ export async function createWebHost(options = {}) {
168
231
  if (rest === 'logout') {
169
232
  sessions.remove(readCookie(request.headers.cookie, SESSION_COOKIE));
170
233
  response.writeHead(302, {
171
- Location: `${base}login`,
234
+ // D-40: on openccu-lite the login is the box's, not ours, so this is where a
235
+ // logout goes; the box's own session stays valid until it is ended there
236
+ Location: authMode === 'occulite' ? '/' : `${base}login`,
172
237
  'Set-Cookie': clearedSessionCookie(base, isHttps(request)),
173
238
  'Cache-Control': 'no-store',
174
239
  });
@@ -176,13 +241,30 @@ export async function createWebHost(options = {}) {
176
241
  return;
177
242
  }
178
243
  session = sessions.get(readCookie(request.headers.cookie, SESSION_COOKIE));
244
+ // B-94, D-65: on openccu-lite the gate names the session it let through. It is checked
245
+ // with the box like the `?sid=` hand-over, and ignored when the box does not confirm it.
246
+ session = await sessionFromGate(request, session);
247
+ if (session?.credential !== undefined) {
248
+ // D-40: writes to the box go out as the session of whoever is looking at the page
249
+ backend?.noteMetaSession(session.credential);
250
+ }
179
251
  if (!session && !hasValidToken(request)) {
180
252
  // the page itself becomes the login form; anything else - assets, metadata, images -
181
253
  // is answered with a plain 401, because a login page in place of a stylesheet only
182
254
  // confuses a browser. The `settings.cgi` hand-over of task 13 never lands here: its
183
255
  // token cookie is accepted above, and the WebUI session it checked is why.
184
256
  if (rest === '' || rest === 'index.html') {
185
- sendLoginPage(request, response, 200);
257
+ if (authMode === 'occulite') {
258
+ // D-40: there is no form to show - openccu-lite's users are the box's, and
259
+ // lighttpd's own gate sends an unauthenticated request to the box's login
260
+ // long before it reaches this process. Landing there is the honest answer
261
+ // to a bookmark that was opened without a session.
262
+ response.writeHead(302, { Location: '/', 'Cache-Control': 'no-store' });
263
+ response.end();
264
+ }
265
+ else {
266
+ sendLoginPage(request, response, 200);
267
+ }
186
268
  }
187
269
  else {
188
270
  unauthorised(response);
@@ -277,6 +359,107 @@ export async function createWebHost(options = {}) {
277
359
  });
278
360
  response.end();
279
361
  }
362
+ /**
363
+ * D-40: `?sid=@…@` from openccu-lite's shell into a session of this host.
364
+ *
365
+ * One check against the box and one redirect. A session that the box does not know goes back to
366
+ * the box's login rather than to a form here, and the failure is not counted or rate-limited:
367
+ * the id was not guessed at, it was handed over, and the only way to get a wrong one is for it
368
+ * to have expired.
369
+ */
370
+ async function handleHandover(request, response, offered, url, rest) {
371
+ const checked = await boxSessions?.check(offered);
372
+ if (!checked) {
373
+ log.warn(`login: openccu-lite refused the session offered from ${clientAddress(request)}`);
374
+ response.writeHead(302, { Location: '/', 'Cache-Control': 'no-store' });
375
+ response.end();
376
+ return;
377
+ }
378
+ const session = sessions.create(checked.name, checked.level, checked.sid);
379
+ backend?.noteMetaSession(checked.sid);
380
+ log.info(`login: ${checked.name} (level ${String(checked.level)}) through the openccu-lite shell`);
381
+ redirectWithoutSid(request, response, session, url, rest);
382
+ }
383
+ /** The same page without the session in the URL, with the cookie of the session it was let in by. */
384
+ function redirectWithoutSid(request, response, session, url, rest) {
385
+ const query = new URLSearchParams(url.searchParams);
386
+ query.delete('sid');
387
+ const search = query.size === 0 ? '' : `?${query.toString()}`;
388
+ response.writeHead(302, {
389
+ Location: `${base}${rest}${search}`,
390
+ 'Set-Cookie': sessionCookie(session.id, base, sessions.ttlMs / 1000, isHttps(request)),
391
+ 'Cache-Control': 'no-store',
392
+ });
393
+ response.end();
394
+ }
395
+ /**
396
+ * B-94, D-65: the sessions made from the gate's `X-Occulite-Session`, by the box's session id,
397
+ * with the time the box confirmed it. A request of the same page load that has no cookie of ours
398
+ * yet - an asset, the socket - finds its session here instead of asking the box again.
399
+ */
400
+ const gateSessions = new Map();
401
+ let lastGateWarning = 0;
402
+ /** The session id the gate put on a request; `undefined` outside `occulite` mode and for a malformed one. */
403
+ function gateSid(request) {
404
+ if (!boxSessions) {
405
+ return undefined;
406
+ }
407
+ const value = request.headers[OCCULITE_SESSION_HEADER];
408
+ // two copies are joined with a comma by Node, which no session id contains: refused
409
+ return parseSid(Array.isArray(value) ? value.join(', ') : value);
410
+ }
411
+ /** A session made from this box session and confirmed less than `GATE_RECHECK_MS` ago. */
412
+ function cachedGateSession(sid) {
413
+ const entry = gateSessions.get(sid);
414
+ if (!entry) {
415
+ return undefined;
416
+ }
417
+ const session = sessions?.get(entry.id);
418
+ if (!session || Date.now() - entry.checkedAt > GATE_RECHECK_MS) {
419
+ gateSessions.delete(sid);
420
+ return undefined;
421
+ }
422
+ return session;
423
+ }
424
+ /**
425
+ * B-94, D-65: the session of a request that came through openccu-lite's gate.
426
+ *
427
+ * The cookie stays as it is; this adds a third way in, for a request that has none - a bookmark,
428
+ * a reload after our session expired - or whose cookie belongs to another box session than the
429
+ * one the gate found. A request with `?sid=` asks this first too (B-36). The header is a claim until the box
430
+ * confirms it (`GET /api/auth/v1/state`): a CCU and an older openccu-lite image pass a client's
431
+ * header through, and the backend's port is open to every process on the box.
432
+ */
433
+ async function sessionFromGate(request, current) {
434
+ const offered = gateSid(request);
435
+ if (!boxSessions || !sessions || offered === undefined || current?.credential === offered) {
436
+ return current;
437
+ }
438
+ const cached = cachedGateSession(offered);
439
+ if (cached) {
440
+ return cached;
441
+ }
442
+ const checked = await boxSessions.check(offered, { requireState: true });
443
+ if (!checked) {
444
+ // a header the box does not confirm counts as no header: the cookie, the token and the
445
+ // redirect to the box decide, as they did before there was a header
446
+ if (Date.now() - lastGateWarning > GATE_RECHECK_MS) {
447
+ lastGateWarning = Date.now();
448
+ log.warn(`login: openccu-lite did not confirm the X-Occulite-Session from ${clientAddress(request)}`);
449
+ }
450
+ return current;
451
+ }
452
+ const now = Date.now();
453
+ for (const [sid, entry] of gateSessions) {
454
+ if (now - entry.checkedAt > GATE_RECHECK_MS) {
455
+ gateSessions.delete(sid);
456
+ }
457
+ }
458
+ const session = sessions.create(checked.name, checked.level, checked.sid);
459
+ gateSessions.set(checked.sid, { id: session.id, checkedAt: now });
460
+ log.info(`login: ${checked.name} (level ${String(checked.level)}) through the openccu-lite gate`);
461
+ return session;
462
+ }
280
463
  /** The token cookie of task 13 - `settings.cgi`'s hand-over still opens every door. */
281
464
  function hasValidToken(request) {
282
465
  return token !== undefined && readCookie(request.headers.cookie, TOKEN_COOKIE) === token;
@@ -348,6 +531,14 @@ export async function createWebHost(options = {}) {
348
531
  // D-32: and a login session opens the same socket, in the same way
349
532
  if (sessions) {
350
533
  applySessionToken(request, token, (id) => sessions.get(id) !== undefined);
534
+ // B-94: the socket of a page that came in through the gate, before our cookie is there
535
+ const gate = gateSid(request);
536
+ if (token !== undefined &&
537
+ gate !== undefined &&
538
+ !hasTokenQuery(request.url) &&
539
+ cachedGateSession(gate)) {
540
+ request.url = withTokenQuery(request.url, token);
541
+ }
351
542
  }
352
543
  api.handleUpgrade(request, socket, head);
353
544
  return;
@@ -397,6 +588,7 @@ export async function createWebHost(options = {}) {
397
588
  port,
398
589
  base,
399
590
  token,
591
+ issueCookie,
400
592
  authMode,
401
593
  sessions,
402
594
  close,
@@ -416,6 +608,16 @@ export async function ensureDataDir(dataDir) {
416
608
  * says why is the only useful answer.
417
609
  */
418
610
  export function requireLocalForRega(authMode, options, auth) {
611
+ if (authMode === 'occulite') {
612
+ // D-40: no `--local` requirement - the box is reached over HTTP and may be named with
613
+ // `--occulite-url` - but the same rule about the token holds: without one the socket lets
614
+ // everybody in and checking the session would guard nothing.
615
+ if (!auth) {
616
+ throw new Error('--auth-mode occulite cannot be combined with --no-auth: without a token the api socket lets ' +
617
+ 'everyone in anyway, and the session check would guard nothing.');
618
+ }
619
+ return;
620
+ }
419
621
  if (authMode !== 'rega') {
420
622
  return;
421
623
  }
@@ -466,17 +668,36 @@ function upstreamOf(connection) {
466
668
  ...(connection.auth ? { auth: connection.auth } : {}),
467
669
  };
468
670
  }
469
- /** `--ccu`, `--local` and the callback options win over what `config.json` holds. */
671
+ /** Task 35: the default pair, or nothing when neither port is set to anything but `0`. */
672
+ function callbackDefaults(options) {
673
+ const xmlrpc = options.callbackXmlrpcDefaultPort ?? 0;
674
+ const binrpc = options.callbackBinrpcDefaultPort ?? 0;
675
+ return xmlrpc === 0 && binrpc === 0 ? undefined : { xmlrpc, binrpc };
676
+ }
677
+ /**
678
+ * Task 38: the callback fields given at start, or nothing. An empty address and a port of `0` are
679
+ * "not configured" in the connection model and pin nothing.
680
+ */
681
+ function callbackPins(options) {
682
+ const pins = {
683
+ ...(options.callbackIp === undefined || options.callbackIp === '' ? {} : { ip: options.callbackIp }),
684
+ ...(options.callbackXmlrpcPort === undefined || options.callbackXmlrpcPort === 0
685
+ ? {}
686
+ : { xmlrpcPort: options.callbackXmlrpcPort }),
687
+ ...(options.callbackBinrpcPort === undefined || options.callbackBinrpcPort === 0
688
+ ? {}
689
+ : { binrpcPort: options.callbackBinrpcPort }),
690
+ };
691
+ return Object.keys(pins).length === 0 ? undefined : pins;
692
+ }
693
+ /**
694
+ * `--ccu` and `--local` win over what `config.json` holds, and are written to it. The callback
695
+ * options are not: since task 38 the backend lays them over the profile (`pinnedCallback`).
696
+ */
470
697
  async function applyConnectionOptions(backend, options, log) {
471
698
  const wantsHost = options.ccu !== undefined && options.ccu !== '';
472
699
  const wantsLocal = options.local !== undefined;
473
- const callback = {
474
- ...(options.callbackIp === undefined ? {} : { ip: options.callbackIp }),
475
- ...(options.callbackXmlrpcPort === undefined ? {} : { xmlrpcPort: options.callbackXmlrpcPort }),
476
- ...(options.callbackBinrpcPort === undefined ? {} : { binrpcPort: options.callbackBinrpcPort }),
477
- };
478
- const wantsCallback = Object.keys(callback).length > 0;
479
- if (!wantsHost && !wantsLocal && !wantsCallback) {
700
+ if (!wantsHost && !wantsLocal) {
480
701
  return;
481
702
  }
482
703
  const config = await backend.request('config.get');
@@ -484,22 +705,11 @@ async function applyConnectionOptions(backend, options, log) {
484
705
  ...config.connection,
485
706
  ...(wantsHost ? { host: options.ccu } : {}),
486
707
  ...(wantsLocal ? { local: options.local } : {}),
487
- callback: { ...config.connection.callback, ...callback },
488
708
  };
489
- if (connection.host === config.connection.host &&
490
- connection.local === config.connection.local &&
491
- connection.callback.ip === config.connection.callback.ip &&
492
- connection.callback.xmlrpcPort === config.connection.callback.xmlrpcPort &&
493
- connection.callback.binrpcPort === config.connection.callback.binrpcPort) {
709
+ if (connection.host === config.connection.host && connection.local === config.connection.local) {
494
710
  return;
495
711
  }
496
- if (wantsHost || wantsLocal) {
497
- log.info(`connection: host=${connection.host}${connection.local === true ? ' (local)' : ''}`);
498
- }
499
- if (wantsCallback) {
500
- const { ip, xmlrpcPort, binrpcPort } = connection.callback;
501
- log.info(`callback: ${ip === '' ? 'auto' : ip} xmlrpc=${String(xmlrpcPort)} binrpc=${String(binrpcPort)}`);
502
- }
712
+ log.info(`connection: host=${connection.host}${connection.local === true ? ' (local)' : ''}`);
503
713
  await backend.request('config.set', connection);
504
714
  }
505
715
  async function withTimeout(work, ms, onTimeout) {
@@ -22,6 +22,14 @@ export interface Session {
22
22
  readonly user: string;
23
23
  /** ReGa `UserLevel()`: 8 admin, 2 user, 1 guest. */
24
24
  readonly level: number;
25
+ /**
26
+ * D-40: the credential the box gave this user - their `?sid=@…@`.
27
+ *
28
+ * Kept because on openccu-lite it is not only proof of a login: it is what a write to the box's
29
+ * metadata store goes out with, so that a rename is attributed to the person who asked for it
30
+ * rather than to the addon. Absent in every other mode, where nothing downstream needs one.
31
+ */
32
+ readonly credential?: string;
25
33
  /** Epoch milliseconds; pushed out by every request that uses the session. */
26
34
  expiresAt: number;
27
35
  }
@@ -41,7 +49,7 @@ export declare class SessionStore {
41
49
  constructor(options?: SessionStoreOptions);
42
50
  get size(): number;
43
51
  /** A new session for a user who has just proved who they are. */
44
- create(user: string, level: number): Session;
52
+ create(user: string, level: number, credential?: string): Session;
45
53
  /**
46
54
  * The session behind an id, expiry slid forward - or `undefined` when there is none, which is
47
55
  * the answer for an unknown, a forged and an expired id alike.
package/dist/sessions.js CHANGED
@@ -37,9 +37,15 @@ export class SessionStore {
37
37
  return this.#sessions.size;
38
38
  }
39
39
  /** A new session for a user who has just proved who they are. */
40
- create(user, level) {
40
+ create(user, level, credential) {
41
41
  this.sweep();
42
- const session = { id: this.#createId(), user, level, expiresAt: this.#now() + this.ttlMs };
42
+ const session = {
43
+ id: this.#createId(),
44
+ user,
45
+ level,
46
+ ...(credential === undefined ? {} : { credential }),
47
+ expiresAt: this.#now() + this.ttlMs,
48
+ };
43
49
  this.#sessions.set(session.id, session);
44
50
  return session;
45
51
  }
@@ -183,7 +183,14 @@ export async function startForTest(options = {}) {
183
183
  }
184
184
  }
185
185
  const ports = simulator
186
- ? { 'BidCos-RF': simulator.ports.rfd, 'HmIP-RF': simulator.ports.hmip }
186
+ ? {
187
+ 'BidCos-RF': simulator.ports.rfd,
188
+ 'HmIP-RF': simulator.ports.hmip,
189
+ // only when the simulator was given a `virtualListenPort` (the CCU's group process)
190
+ ...(typeof simulator.ports.virtual === 'number'
191
+ ? { VirtualDevices: simulator.ports.virtual }
192
+ : {}),
193
+ }
187
194
  : {};
188
195
  const host = await createWebHost({
189
196
  port: 0,
@@ -206,7 +213,7 @@ export async function startForTest(options = {}) {
206
213
  if (simulator && host.backend) {
207
214
  const connection = {
208
215
  host: '127.0.0.1',
209
- interfaces: ['BidCos-RF', 'HmIP-RF'],
216
+ interfaces: ['BidCos-RF', 'HmIP-RF', ...(ports['VirtualDevices'] === undefined ? [] : ['VirtualDevices'])],
210
217
  autoDetect: false,
211
218
  extraInterfaces: [],
212
219
  tls: false,
@@ -17,8 +17,10 @@ import { maintenanceAddress, type AppConfig, type ApiEventName, type ApiEvents,
17
17
  import { type ConfigStoreOptions } from '../config/store.js';
18
18
  import { type DiscoverOptions } from '../discovery/discover.js';
19
19
  import { InterfaceManager, type InterfaceManagerOptions } from '../interfaces/manager.js';
20
+ import { type MetaServiceOptions } from '../meta/service.js';
20
21
  import { RegaService, type RegaServiceOptions } from '../rega/client.js';
21
22
  import { ApiEventEmitter } from '../util/emitter.js';
23
+ import { type CallbackNetwork } from '../util/net.js';
22
24
  /** How often the BidCos service messages are polled while the connection is up. */
23
25
  export declare const SERVICE_MESSAGE_POLL_MS = 300000;
24
26
  /** How long the HmIP `getParamset(:0, VALUES)` sweep waits for the device list to settle. */
@@ -29,6 +31,26 @@ export interface BackendOptions extends Omit<ConfigStoreOptions, 'version'> {
29
31
  /** Roots `data.file` may read from, keyed by the prefix the UI uses. */
30
32
  readonly fileRoots?: Readonly<Record<string, string>>;
31
33
  readonly callbackHost?: string;
34
+ /**
35
+ * Task 35 (D-43): the callback ports taken while the connection's are `0` - the CCU addon's fixed
36
+ * pair. A property of the host, not of the profile: it is never written to `config.json`, and
37
+ * `AppConfig.callbackDefaultPorts` reports it so the settings dialog can say what `0` means.
38
+ */
39
+ readonly defaultCallbackPorts?: {
40
+ readonly xmlrpc: number;
41
+ readonly binrpc: number;
42
+ };
43
+ /**
44
+ * Task 38: the host runs in a container (the image sets `HMM_IN_CONTAINER`). Where its callback
45
+ * servers listen beyond the loopback, `AppConfig.publishCallbackPorts` tells the interface
46
+ * popup to say that the callback ports must be published unchanged.
47
+ */
48
+ readonly inContainer?: boolean;
49
+ /**
50
+ * B-53: the interfaces, name lookup and route probe the automatic callback address comes from.
51
+ * Absent, the machine's own - or, where `localAddresses` is injected, that list alone.
52
+ */
53
+ readonly network?: CallbackNetwork;
32
54
  readonly rpcTimeoutMs?: number;
33
55
  readonly watchdogIntervalMs?: number;
34
56
  readonly serviceMessagePollMs?: number;
@@ -48,6 +70,8 @@ export interface BackendOptions extends Omit<ConfigStoreOptions, 'version'> {
48
70
  /** Injected by the tests in place of the real world. */
49
71
  readonly createInterfaceManager?: (options: InterfaceManagerOptions) => InterfaceManager;
50
72
  readonly createRega?: (options: RegaServiceOptions) => RegaService;
73
+ /** D-40: injected by the tests, and by the integration test that runs a real occulited. */
74
+ readonly metaOptions?: Partial<MetaServiceOptions>;
51
75
  readonly discover?: (options: DiscoverOptions) => Promise<AppConfig['discovered']>;
52
76
  readonly interfaceManagerOptions?: Partial<InterfaceManagerOptions>;
53
77
  readonly regaOptions?: Partial<RegaServiceOptions>;
@@ -82,6 +106,15 @@ export declare class Backend {
82
106
  * on the wire unchanged.
83
107
  */
84
108
  request<M extends ApiMethodName>(method: M, ...params: ApiParams<M>): Promise<ApiResult<M>>;
109
+ /**
110
+ * D-40: the session of the person looking at the page, for the writes to an openccu-lite box.
111
+ *
112
+ * The shell of the box hands an addon page the user's session as `?sid=@xxxxxxxxxx@`, and that
113
+ * session is a valid credential for the metadata API. The addon reads with the box's local
114
+ * token, which is read-only by design, and writes as the user - so nothing renames a device
115
+ * unless a person asked for it. Hosts without a login never call this.
116
+ */
117
+ noteMetaSession(sid: string | undefined): void;
85
118
  /** One polling round; public so a test does not have to wait five minutes. */
86
119
  pollServiceMessages(): Promise<void>;
87
120
  /** One sweep; public so a test can run it without the timer. */