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.
- package/README.md +2 -1
- package/data/easymode-time-selectors.json +285 -0
- package/data/icons/106_hm-lc-sw1-dr.webp +0 -0
- package/data/icons/108_hm-rc-dis-h-x-eu.webp +0 -0
- package/data/icons/109_hm-lc-sw1-pl-ct.webp +0 -0
- package/data/icons/110_hm-es-pmsw1-dr.webp +0 -0
- package/data/icons/112_hmip-wrc2.webp +0 -0
- package/data/icons/113_hmip-psm-ch.webp +0 -0
- package/data/icons/113_hmip-psm-it.webp +0 -0
- package/data/icons/113_hmip-psm-pe.webp +0 -0
- package/data/icons/113_hmip-psm-uk.webp +0 -0
- package/data/icons/113_hmip-psm.webp +0 -0
- package/data/icons/114_hm-lc-dim1t-fm-lf_3.webp +0 -0
- package/data/icons/116_hm-rc-4-3_brc-h3.webp +0 -0
- package/data/icons/118_hmip-swdo.webp +0 -0
- package/data/icons/119_hmip-rc8.webp +0 -0
- package/data/icons/120_hmip-etrv.webp +0 -0
- package/data/icons/121_hmip-wth.webp +0 -0
- package/data/icons/123_oligo.smart.ip.hm.webp +0 -0
- package/data/icons/125_hmip-smi.webp +0 -0
- package/data/icons/130_hmip-srh.webp +0 -0
- package/data/icons/131_hmip-wrc6.webp +0 -0
- package/data/icons/132_hmip-smo.webp +0 -0
- package/data/icons/133_hmip-asir.webp +0 -0
- package/data/icons/134_hmip-fsm.webp +0 -0
- package/data/icons/135_hmip-fsm16.webp +0 -0
- package/data/icons/136_hmip-miob.webp +0 -0
- package/data/icons/137_hmip-fal-c6.webp +0 -0
- package/data/icons/138_hmip-fal-c10.webp +0 -0
- package/data/icons/140_alpha-ip-rgb.webp +0 -0
- package/data/icons/141_alpha-ip-rgba.webp +0 -0
- package/data/icons/144_hmip-wgc.webp +0 -0
- package/data/icons/145_hmip-froll_hmip-fbl.webp +0 -0
- package/data/icons/146_hmip-sth.webp +0 -0
- package/data/icons/147_hmip-sthd.webp +0 -0
- package/data/icons/148_hmip-stho.webp +0 -0
- package/data/icons/149_hmip-sam.webp +0 -0
- package/data/icons/151_hmip-pcbs-bat.webp +0 -0
- package/data/icons/152_hmip-swdo-i.webp +0 -0
- package/data/icons/153_hmip-spi.webp +0 -0
- package/data/icons/154_hmip-spdr.webp +0 -0
- package/data/icons/155_hmip-whs2.webp +0 -0
- package/data/icons/156_hmip-mod-oc8.webp +0 -0
- package/data/icons/158_hmip-etrv-uk.webp +0 -0
- package/data/icons/159_hmip-mod-rc8.webp +0 -0
- package/data/icons/160_hmipw-drs4.webp +0 -0
- package/data/icons/161_hmipw-drs8.webp +0 -0
- package/data/icons/162_hmipw-drap.webp +0 -0
- package/data/icons/163_hmipw-drbl4.webp +0 -0
- package/data/icons/164_hmipw-dri16.webp +0 -0
- package/data/icons/165_hmipw-fio6.webp +0 -0
- package/data/icons/166_hmipw-drd3.webp +0 -0
- package/data/icons/167_hmipw-dri32.webp +0 -0
- package/data/icons/168_hmip-smi55.webp +0 -0
- package/data/icons/169_hmip-swo-pr.webp +0 -0
- package/data/icons/170_hmip-swo-pl.webp +0 -0
- package/data/icons/171_hmip-swo-b.webp +0 -0
- package/data/icons/172_hmip-swd.webp +0 -0
- package/data/icons/173_hmip-bsl.webp +0 -0
- package/data/icons/174_hmip-slo.webp +0 -0
- package/data/icons/175_hmip-group-heating.webp +0 -0
- package/data/icons/177_hmip-dbb.webp +0 -0
- package/data/icons/178_hmip-etrv-b1.webp +0 -0
- package/data/icons/179_hmip-swdm-b2.webp +0 -0
- package/data/icons/180_hmip-etrv-b.webp +0 -0
- package/data/icons/181_hmip-swdm.webp +0 -0
- package/data/icons/182_hmip-fci1.webp +0 -0
- package/data/icons/183_hmip-mod-tm.webp +0 -0
- package/data/icons/184_hmip-pcbs2.webp +0 -0
- package/data/icons/185_hmip-FCI6.webp +0 -0
- package/data/icons/186_hmip-mp3p.webp +0 -0
- package/data/icons/187_hmip-rcb1.webp +0 -0
- package/data/icons/188_hmip-etrv-c.webp +0 -0
- package/data/icons/190_hmip-sci.webp +0 -0
- package/data/icons/191_hmip-mod-ho.webp +0 -0
- package/data/icons/192_hmip-asir-o.webp +0 -0
- package/data/icons/193_hmip-wt.webp +0 -0
- package/data/icons/194_hmip-wrcd.webp +0 -0
- package/data/icons/195_hmipw-smi55.webp +0 -0
- package/data/icons/196_hmip-ASIR-2.webp +0 -0
- package/data/icons/197_hmip-swdo-pl.webp +0 -0
- package/data/icons/198_hmip-falmot-c12.webp +0 -0
- package/data/icons/199_hmip-mio16-pcb.webp +0 -0
- package/data/icons/200_hmip-wth-b.webp +0 -0
- package/data/icons/201_hmip-stv.webp +0 -0
- package/data/icons/202_hmip-wrcr.webp +0 -0
- package/data/icons/203_hmip-fsi16.webp +0 -0
- package/data/icons/204_hmip-drdi3.webp +0 -0
- package/data/icons/205_hmip-drsi4.webp +0 -0
- package/data/icons/206_hmip-drbli4.webp +0 -0
- package/data/icons/207_hmip-srdo.webp +0 -0
- package/data/icons/208_hmip-dsd-pcb.webp +0 -0
- package/data/icons/209_hmip-etrv-b-uk.webp +0 -0
- package/data/icons/210_hmip-ste2-pcb.webp +0 -0
- package/data/icons/211_hmip-drg-dali.webp +0 -0
- package/data/icons/211_hmip-drsi1.webp +0 -0
- package/data/icons/212_hmip-sfd.webp +0 -0
- package/data/icons/212_hmipw-scthd.webp +0 -0
- package/data/icons/213_hmip-smo230.webp +0 -0
- package/data/icons/213_hmip-wua.webp +0 -0
- package/data/icons/214_hmip-dld.webp +0 -0
- package/data/icons/215_hmip-psm-pe-2.webp +0 -0
- package/data/icons/215_hmip-scth230.webp +0 -0
- package/data/icons/216_hmip-etrv-e.webp +0 -0
- package/data/icons/216_hmip-psm-ch-2.webp +0 -0
- package/data/icons/217_hmip-usbsm.webp +0 -0
- package/data/icons/218_hmip-dls.webp +0 -0
- package/data/icons/219_hmip-fwi.webp +0 -0
- package/data/icons/220_hmip-wsc.webp +0 -0
- package/data/icons/221_hmip-sti.webp +0 -0
- package/data/icons/221_hmip-wkp.webp +0 -0
- package/data/icons/222_hmipw-wgd.webp +0 -0
- package/data/icons/223_hmip-mod-wd-vk.webp +0 -0
- package/data/icons/224_hmip-etrv-cl.webp +0 -0
- package/data/icons/225_hmip-rgbw.webp +0 -0
- package/data/icons/226_hmip-esi.webp +0 -0
- package/data/icons/227_hmip-lsc.webp +0 -0
- package/data/icons/228_elv-sh-sw1-bat.webp +0 -0
- package/data/icons/229_elv-sh-bm-s.webp +0 -0
- package/data/icons/230_hmip-etrv-f.webp +0 -0
- package/data/icons/231_elv-sh-cth.webp +0 -0
- package/data/icons/232_elv-sh-taco.webp +0 -0
- package/data/icons/233_hmip-flc.webp +0 -0
- package/data/icons/234_hmip-etrv-3.webp +0 -0
- package/data/icons/235_hmip-psmco.webp +0 -0
- package/data/icons/236_elv-sh-sb8.webp +0 -0
- package/data/icons/237_elv-sh-ctv.webp +0 -0
- package/data/icons/238_elv-sh-cap.webp +0 -0
- package/data/icons/239_hmip-wgs-f.webp +0 -0
- package/data/icons/240_hmip-wgtc-f.webp +0 -0
- package/data/icons/241_hmip-wgt-f.webp +0 -0
- package/data/icons/242_elv-sh-smsi.webp +0 -0
- package/data/icons/243_elv-sh-crc.webp +0 -0
- package/data/icons/244_elv-sh-krco.webp +0 -0
- package/data/icons/245_hmip-falmot-c8.webp +0 -0
- package/data/icons/246_elv-sh-wsm.webp +0 -0
- package/data/icons/247_hmip-hap-2.webp +0 -0
- package/data/icons/248_hmip-dlp.webp +0 -0
- package/data/icons/249_elv-sh-psmci.webp +0 -0
- package/data/icons/250_elv-sh-sps25.webp +0 -0
- package/data/icons/252_elv-sh-pti2.webp +0 -0
- package/data/icons/253_hmip-udi-smi55.webp +0 -0
- package/data/icons/254_hmip-udi-pb2.webp +0 -0
- package/data/icons/256_ELV-SH-SMS2.webp +0 -0
- package/data/icons/257_ELV-SH-FSI.webp +0 -0
- package/data/icons/258_hmip-m-td15.webp +0 -0
- package/data/icons/259_elv-sh-dusi.webp +0 -0
- package/data/icons/260_hmip-wss.webp +0 -0
- package/data/icons/54a_lc-ddc1-pcb.webp +0 -0
- package/data/icons/72_hm-rc-brc-h.webp +0 -0
- package/data/icons/73_hm-atent.webp +0 -0
- package/data/icons/79_hm-lc-dim1pwm-cv.webp +0 -0
- package/data/icons/84_hm-rc-4-3.webp +0 -0
- package/data/icons/85_hm-rc-sec4-3.webp +0 -0
- package/data/icons/86_hm-rc-key4-3.webp +0 -0
- package/data/icons/CCU3-1-50.webp +0 -0
- package/data/icons/CCU3.webp +0 -0
- package/data/icons/hm-coupling-dim.webp +0 -0
- package/data/icons/hm-coupling-group.webp +0 -0
- package/data/icons/hm-coupling-onoff.webp +0 -0
- package/data/icons/hm-coupling-rgb-dim.webp +0 -0
- package/data/icons/hm-coupling-rgb.webp +0 -0
- package/data/icons/hm-coupling-rgbw-dim.webp +0 -0
- package/data/icons/hm-coupling-rgbw.webp +0 -0
- package/data/icons/hm-coupling-white-dim.webp +0 -0
- package/data/icons/hm-coupling-white.webp +0 -0
- package/data/icons/hm-hue_gateway.webp +0 -0
- package/data/icons/hm-lightify_gateway.webp +0 -0
- package/data/icons/hm_resc-win-pcb-sc.webp +0 -0
- package/data/icons/unknown_device.webp +0 -0
- package/data/manifest.json +7 -1
- package/data/master-metadata.json +1997 -33
- package/data/profiles/ACCESS_RECEIVER.json +26 -13
- package/data/profiles/ACOUSTIC_SIGNAL_VIRTUAL_RECEIVER.json +3270 -76
- package/data/profiles/ACTOR_SECURITY.json +90 -10
- package/data/profiles/ACTOR_WINDOW.json +580 -43
- package/data/profiles/ALARMACTUATOR.json +564 -37
- package/data/profiles/ALARM_COND_SWITCH_RECEIVER.json +36 -4
- package/data/profiles/ALARM_SWITCH_VIRTUAL_RECEIVER.json +735 -33
- package/data/profiles/ARMING.json +115 -5
- package/data/profiles/BLIND.json +1038 -49
- package/data/profiles/BLIND_VIRTUAL_RECEIVER.json +4624 -124
- package/data/profiles/CLIMATECONTROL_FLOOR_PUMP_TRANSCEIVER.json +27 -1
- package/data/profiles/DIMMER.json +2233 -71
- package/data/profiles/DIMMER_VIRTUAL_RECEIVER.json +5773 -102
- package/data/profiles/DIMMER_woLongKeyPress.json +117 -4
- package/data/profiles/DOOR_RECEIVER.json +138 -10
- package/data/profiles/DUAL_WHITE_BRIGHTNESS.json +305 -11
- package/data/profiles/DUAL_WHITE_COLOR.json +730 -23
- package/data/profiles/HEATING_KEY_RECEIVER.json +127 -10
- package/data/profiles/HMW_BLIND.json +555 -20
- package/data/profiles/HMW_DIMMER.json +1337 -47
- package/data/profiles/HMW_INPUT_OUTPUT.json +375 -20
- package/data/profiles/HMW_SWITCH.json +375 -20
- package/data/profiles/JALOUSIE.json +1320 -52
- package/data/profiles/KEYMATIC.json +120 -12
- package/data/profiles/REMOTECONTROL_RECEIVER.json +10 -2
- package/data/profiles/RGBW_AUTOMATIC.json +46 -6
- package/data/profiles/RGBW_COLOR.json +14 -4
- package/data/profiles/SERVO_VIRTUAL_RECEIVER.json +117 -3
- package/data/profiles/SHUTTER_VIRTUAL_RECEIVER.json +4263 -128
- package/data/profiles/SIGNAL_CHIME.json +195 -14
- package/data/profiles/SIGNAL_CHIMEM.json +195 -14
- package/data/profiles/SIGNAL_LED.json +195 -14
- package/data/profiles/SIGNAL_LEDM.json +195 -14
- package/data/profiles/SIMPLE_SWITCH_RECEIVER.json +113 -9
- package/data/profiles/STATUS_INDICATOR.json +1073 -45
- package/data/profiles/SWITCH.json +1412 -60
- package/data/profiles/SWITCH_PANIC.json +315 -13
- package/data/profiles/SWITCH_SENSOR.json +243 -11
- package/data/profiles/SWITCH_VIRTUAL_RECEIVER.json +3963 -131
- package/data/profiles/UNIVERSAL_ACTOR.json +4319 -90
- package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_LSC.json +6094 -196
- package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_PWM.json +4920 -101
- package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_RGB(W).json +2353 -80
- package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_RGBW_DALI.json +6116 -176
- package/data/profiles/UNIVERSAL_LIGHT_RECEIVER_TW.json +3844 -104
- package/data/profiles/VIRTUAL_DIMMER.json +305 -11
- package/data/profiles/VIRTUAL_DUAL_WHITE_COLOR.json +730 -23
- package/data/profiles/WATER_SWITCH_VIRTUAL_RECEIVER.json +3903 -128
- package/data/profiles/WINDOW_DRIVE_RECEIVER.json +220 -8
- package/data/profiles/WINMATIC.json +505 -23
- package/dist/cli.js +24 -0
- package/dist/occulite.d.ts +86 -0
- package/dist/occulite.js +140 -0
- package/dist/options.d.ts +37 -6
- package/dist/options.js +49 -6
- package/dist/server.d.ts +47 -4
- package/dist/server.js +237 -27
- package/dist/sessions.d.ts +9 -1
- package/dist/sessions.js +8 -2
- package/dist/testSupport.js +9 -2
- package/node_modules/@homematic-manager/backend/dist/api/backend.d.ts +33 -0
- package/node_modules/@homematic-manager/backend/dist/api/backend.js +558 -46
- package/node_modules/@homematic-manager/backend/dist/cache/names.d.ts +9 -0
- package/node_modules/@homematic-manager/backend/dist/cache/names.js +21 -0
- package/node_modules/@homematic-manager/backend/dist/config/defaults.js +10 -1
- package/node_modules/@homematic-manager/backend/dist/config/store.d.ts +28 -1
- package/node_modules/@homematic-manager/backend/dist/config/store.js +59 -3
- package/node_modules/@homematic-manager/backend/dist/devices/installMode.js +9 -0
- package/node_modules/@homematic-manager/backend/dist/errors.d.ts +18 -0
- package/node_modules/@homematic-manager/backend/dist/errors.js +51 -2
- package/node_modules/@homematic-manager/backend/dist/images/deviceImages.d.ts +6 -0
- package/node_modules/@homematic-manager/backend/dist/images/deviceImages.js +38 -2
- package/node_modules/@homematic-manager/backend/dist/index.d.ts +3 -1
- package/node_modules/@homematic-manager/backend/dist/index.js +4 -1
- package/node_modules/@homematic-manager/backend/dist/interfaces/manager.d.ts +70 -8
- package/node_modules/@homematic-manager/backend/dist/interfaces/manager.js +333 -44
- package/node_modules/@homematic-manager/backend/dist/meta/client.d.ts +120 -0
- package/node_modules/@homematic-manager/backend/dist/meta/client.js +307 -0
- package/node_modules/@homematic-manager/backend/dist/meta/credentials.d.ts +38 -0
- package/node_modules/@homematic-manager/backend/dist/meta/credentials.js +53 -0
- package/node_modules/@homematic-manager/backend/dist/meta/index.d.ts +12 -0
- package/node_modules/@homematic-manager/backend/dist/meta/index.js +12 -0
- package/node_modules/@homematic-manager/backend/dist/meta/localProvider.d.ts +47 -0
- package/node_modules/@homematic-manager/backend/dist/meta/localProvider.js +119 -0
- package/node_modules/@homematic-manager/backend/dist/meta/occuliteProvider.d.ts +79 -0
- package/node_modules/@homematic-manager/backend/dist/meta/occuliteProvider.js +305 -0
- package/node_modules/@homematic-manager/backend/dist/meta/provider.d.ts +68 -0
- package/node_modules/@homematic-manager/backend/dist/meta/provider.js +19 -0
- package/node_modules/@homematic-manager/backend/dist/meta/regaProvider.d.ts +84 -0
- package/node_modules/@homematic-manager/backend/dist/meta/regaProvider.js +403 -0
- package/node_modules/@homematic-manager/backend/dist/meta/service.d.ts +137 -0
- package/node_modules/@homematic-manager/backend/dist/meta/service.js +412 -0
- package/node_modules/@homematic-manager/backend/dist/rega/client.d.ts +11 -0
- package/node_modules/@homematic-manager/backend/dist/rega/client.js +13 -0
- package/node_modules/@homematic-manager/backend/dist/rega/scripts.d.ts +51 -0
- package/node_modules/@homematic-manager/backend/dist/rega/scripts.js +171 -0
- package/node_modules/@homematic-manager/backend/dist/rpc/client.d.ts +18 -5
- package/node_modules/@homematic-manager/backend/dist/rpc/client.js +11 -5
- package/node_modules/@homematic-manager/backend/dist/rpc/log.d.ts +86 -0
- package/node_modules/@homematic-manager/backend/dist/rpc/log.js +241 -0
- package/node_modules/@homematic-manager/backend/dist/rpc/origin.d.ts +30 -0
- package/node_modules/@homematic-manager/backend/dist/rpc/origin.js +35 -0
- package/node_modules/@homematic-manager/backend/dist/rpc/server.d.ts +12 -0
- package/node_modules/@homematic-manager/backend/dist/rpc/server.js +37 -5
- package/node_modules/@homematic-manager/backend/dist/util/net.d.ts +86 -1
- package/node_modules/@homematic-manager/backend/dist/util/net.js +223 -9
- package/node_modules/@homematic-manager/backend/package.json +1 -1
- package/node_modules/@homematic-manager/core/dist/api/types.d.ts +397 -10
- package/node_modules/@homematic-manager/core/dist/api/types.js +23 -2
- package/node_modules/@homematic-manager/core/dist/data/memory.d.ts +3 -1
- package/node_modules/@homematic-manager/core/dist/data/memory.js +3 -0
- package/node_modules/@homematic-manager/core/dist/data/types.d.ts +60 -0
- package/node_modules/@homematic-manager/core/dist/data/types.js +2 -0
- package/node_modules/@homematic-manager/core/dist/devices/index.d.ts +19 -0
- package/node_modules/@homematic-manager/core/dist/devices/index.js +53 -0
- package/node_modules/@homematic-manager/core/dist/easymodes/engine.d.ts +3 -1
- package/node_modules/@homematic-manager/core/dist/easymodes/engine.js +1 -0
- package/node_modules/@homematic-manager/core/dist/easymodes/form.d.ts +43 -0
- package/node_modules/@homematic-manager/core/dist/easymodes/form.js +84 -0
- package/node_modules/@homematic-manager/core/dist/i18n/messages.js +61 -0
- package/node_modules/@homematic-manager/core/dist/index.d.ts +3 -0
- package/node_modules/@homematic-manager/core/dist/index.js +4 -0
- package/node_modules/@homematic-manager/core/dist/interfaces/table.d.ts +23 -2
- package/node_modules/@homematic-manager/core/dist/interfaces/table.js +18 -1
- package/node_modules/@homematic-manager/core/dist/meta/document.d.ts +71 -0
- package/node_modules/@homematic-manager/core/dist/meta/document.js +260 -0
- package/node_modules/@homematic-manager/core/dist/meta/follow.d.ts +27 -0
- package/node_modules/@homematic-manager/core/dist/meta/follow.js +180 -0
- package/node_modules/@homematic-manager/core/dist/meta/index.d.ts +16 -0
- package/node_modules/@homematic-manager/core/dist/meta/index.js +16 -0
- package/node_modules/@homematic-manager/core/dist/meta/paths.d.ts +96 -0
- package/node_modules/@homematic-manager/core/dist/meta/paths.js +182 -0
- package/node_modules/@homematic-manager/core/dist/meta/slug.d.ts +22 -0
- package/node_modules/@homematic-manager/core/dist/meta/slug.js +59 -0
- package/node_modules/@homematic-manager/core/dist/meta/store.d.ts +147 -0
- package/node_modules/@homematic-manager/core/dist/meta/store.js +537 -0
- package/node_modules/@homematic-manager/core/dist/meta/tree.d.ts +19 -0
- package/node_modules/@homematic-manager/core/dist/meta/tree.js +53 -0
- package/node_modules/@homematic-manager/core/dist/meta/types.d.ts +143 -0
- package/node_modules/@homematic-manager/core/dist/meta/types.js +63 -0
- package/node_modules/@homematic-manager/core/dist/meta/view.d.ts +50 -0
- package/node_modules/@homematic-manager/core/dist/meta/view.js +103 -0
- package/node_modules/@homematic-manager/core/dist/rpc/methods.d.ts +10 -0
- package/node_modules/@homematic-manager/core/dist/rpc/methods.js +51 -1
- package/node_modules/@homematic-manager/core/dist/rpc/text.d.ts +22 -0
- package/node_modules/@homematic-manager/core/dist/rpc/text.js +46 -0
- package/node_modules/@homematic-manager/core/dist/rssi/index.d.ts +125 -13
- package/node_modules/@homematic-manager/core/dist/rssi/index.js +146 -29
- package/node_modules/@homematic-manager/core/dist/serviceMessages/index.d.ts +28 -2
- package/node_modules/@homematic-manager/core/dist/serviceMessages/index.js +44 -6
- package/node_modules/@homematic-manager/core/package.json +1 -1
- package/package.json +3 -3
- package/ui/assets/index-D-NsbBVS.css +1 -0
- package/ui/assets/index-DNUNMBbb.js +16 -0
- package/ui/favicon.ico +0 -0
- package/ui/index.html +3 -2
- package/node_modules/@homematic-manager/backend/dist/write/log.d.ts +0 -51
- package/node_modules/@homematic-manager/backend/dist/write/log.js +0 -151
- package/ui/assets/index-lB-HxsKR.js +0 -13
- 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
|
|
81
|
-
const sessions = authMode === '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/dist/sessions.d.ts
CHANGED
|
@@ -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 = {
|
|
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
|
}
|
package/dist/testSupport.js
CHANGED
|
@@ -183,7 +183,14 @@ export async function startForTest(options = {}) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
const ports = simulator
|
|
186
|
-
? {
|
|
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. */
|