@mp-consulting/homebridge-daikin-cloud 1.3.6 → 1.3.8

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 (291) hide show
  1. package/LICENSE +39 -1
  2. package/README.md +3 -1
  3. package/dist/src/accessories/air-conditioning-accessory.d.ts +2 -2
  4. package/dist/src/accessories/air-conditioning-accessory.d.ts.map +1 -1
  5. package/dist/src/accessories/air-conditioning-accessory.js.map +1 -1
  6. package/dist/src/accessories/altherma-accessory.d.ts +2 -2
  7. package/dist/src/accessories/altherma-accessory.d.ts.map +1 -1
  8. package/dist/src/accessories/altherma-accessory.js.map +1 -1
  9. package/dist/src/accessories/base-accessory.d.ts +5 -5
  10. package/dist/src/accessories/base-accessory.d.ts.map +1 -1
  11. package/dist/src/accessories/base-accessory.js +7 -4
  12. package/dist/src/accessories/base-accessory.js.map +1 -1
  13. package/dist/src/api/daikin-api.d.ts +25 -25
  14. package/dist/src/api/daikin-api.d.ts.map +1 -1
  15. package/dist/src/api/daikin-api.js +41 -31
  16. package/dist/src/api/daikin-api.js.map +1 -1
  17. package/dist/src/api/daikin-cloud.repository.d.ts.map +1 -1
  18. package/dist/src/api/daikin-cloud.repository.js.map +1 -1
  19. package/dist/src/api/daikin-controller.d.ts +41 -42
  20. package/dist/src/api/daikin-controller.d.ts.map +1 -1
  21. package/dist/src/api/daikin-controller.js +39 -39
  22. package/dist/src/api/daikin-controller.js.map +1 -1
  23. package/dist/src/api/daikin-device.d.ts +33 -31
  24. package/dist/src/api/daikin-device.d.ts.map +1 -1
  25. package/dist/src/api/daikin-device.js +40 -29
  26. package/dist/src/api/daikin-device.js.map +1 -1
  27. package/dist/src/api/daikin-mobile-oauth.d.ts +16 -16
  28. package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
  29. package/dist/src/api/daikin-mobile-oauth.js +32 -22
  30. package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
  31. package/dist/src/api/daikin-oauth.d.ts +29 -29
  32. package/dist/src/api/daikin-oauth.d.ts.map +1 -1
  33. package/dist/src/api/daikin-oauth.js +45 -35
  34. package/dist/src/api/daikin-oauth.js.map +1 -1
  35. package/dist/src/api/daikin-schemas.d.ts +4 -4
  36. package/dist/src/api/daikin-schemas.js +3 -3
  37. package/dist/src/api/daikin-schemas.js.map +1 -1
  38. package/dist/src/api/daikin-types.js.map +1 -1
  39. package/dist/src/api/daikin-websocket.d.ts +31 -32
  40. package/dist/src/api/daikin-websocket.d.ts.map +1 -1
  41. package/dist/src/api/daikin-websocket.js +30 -30
  42. package/dist/src/api/daikin-websocket.js.map +1 -1
  43. package/dist/src/api/index.d.ts +1 -1
  44. package/dist/src/api/index.d.ts.map +1 -1
  45. package/dist/src/api/index.js +2 -1
  46. package/dist/src/api/index.js.map +1 -1
  47. package/dist/src/api/token-storage.d.ts +1 -1
  48. package/dist/src/api/token-storage.d.ts.map +1 -1
  49. package/dist/src/api/token-storage.js +20 -11
  50. package/dist/src/api/token-storage.js.map +1 -1
  51. package/dist/src/config/config-manager.d.ts +33 -33
  52. package/dist/src/config/config-manager.d.ts.map +1 -1
  53. package/dist/src/config/config-manager.js +33 -33
  54. package/dist/src/config/config-manager.js.map +1 -1
  55. package/dist/src/constants/api.constants.js.map +1 -1
  56. package/dist/src/device/accessory-factory.d.ts +10 -10
  57. package/dist/src/device/accessory-factory.d.ts.map +1 -1
  58. package/dist/src/device/accessory-factory.js +6 -6
  59. package/dist/src/device/accessory-factory.js.map +1 -1
  60. package/dist/src/device/capability-detector.d.ts +8 -8
  61. package/dist/src/device/capability-detector.d.ts.map +1 -1
  62. package/dist/src/device/capability-detector.js +6 -6
  63. package/dist/src/device/capability-detector.js.map +1 -1
  64. package/dist/src/device/capability-docs.d.ts +1 -1
  65. package/dist/src/device/capability-docs.d.ts.map +1 -1
  66. package/dist/src/device/capability-docs.js +1 -2
  67. package/dist/src/device/capability-docs.js.map +1 -1
  68. package/dist/src/device/profiles/device-profile.d.ts +1 -1
  69. package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
  70. package/dist/src/device/profiles/device-profile.js +4 -4
  71. package/dist/src/device/profiles/device-profile.js.map +1 -1
  72. package/dist/src/features/base-feature.d.ts +2 -2
  73. package/dist/src/features/base-feature.d.ts.map +1 -1
  74. package/dist/src/features/base-feature.js +2 -3
  75. package/dist/src/features/base-feature.js.map +1 -1
  76. package/dist/src/features/feature-manager.d.ts +8 -8
  77. package/dist/src/features/feature-manager.d.ts.map +1 -1
  78. package/dist/src/features/feature-manager.js +5 -5
  79. package/dist/src/features/feature-manager.js.map +1 -1
  80. package/dist/src/features/modes/dry-operation-mode.feature.d.ts +1 -1
  81. package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
  82. package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
  83. package/dist/src/features/modes/econo-mode.feature.d.ts +1 -1
  84. package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
  85. package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
  86. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +1 -1
  87. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
  88. package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
  89. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +1 -1
  90. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
  91. package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
  92. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +1 -1
  93. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
  94. package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
  95. package/dist/src/features/modes/powerful-mode.feature.d.ts +1 -1
  96. package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
  97. package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
  98. package/dist/src/features/modes/streamer-mode.feature.d.ts +1 -1
  99. package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
  100. package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
  101. package/dist/src/index.d.ts +1 -1
  102. package/dist/src/index.d.ts.map +1 -1
  103. package/dist/src/index.js.map +1 -1
  104. package/dist/src/platform.d.ts +6 -5
  105. package/dist/src/platform.d.ts.map +1 -1
  106. package/dist/src/platform.js +2 -2
  107. package/dist/src/platform.js.map +1 -1
  108. package/dist/src/services/climate-control.service.d.ts +8 -2
  109. package/dist/src/services/climate-control.service.d.ts.map +1 -1
  110. package/dist/src/services/climate-control.service.js +53 -59
  111. package/dist/src/services/climate-control.service.js.map +1 -1
  112. package/dist/src/services/hot-water-tank.service.d.ts +6 -2
  113. package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
  114. package/dist/src/services/hot-water-tank.service.js +31 -34
  115. package/dist/src/services/hot-water-tank.service.js.map +1 -1
  116. package/dist/src/types/daikin-enums.js +12 -12
  117. package/dist/src/types/daikin-enums.js.map +1 -1
  118. package/dist/src/types/device-capabilities.d.ts +1 -1
  119. package/dist/src/types/device-capabilities.d.ts.map +1 -1
  120. package/dist/src/utils/log-context.d.ts +23 -23
  121. package/dist/src/utils/log-context.d.ts.map +1 -1
  122. package/dist/src/utils/log-context.js +28 -28
  123. package/dist/src/utils/log-context.js.map +1 -1
  124. package/dist/src/utils/strings.d.ts.map +1 -1
  125. package/dist/src/utils/strings.js.map +1 -1
  126. package/dist/src/utils/update-mapper.d.ts +16 -16
  127. package/dist/src/utils/update-mapper.d.ts.map +1 -1
  128. package/dist/src/utils/update-mapper.js +14 -14
  129. package/dist/src/utils/update-mapper.js.map +1 -1
  130. package/homebridge-ui/public/index.html +24 -24
  131. package/homebridge-ui/public/lib/kit.css +253 -0
  132. package/homebridge-ui/public/lib/kit.js +133 -0
  133. package/homebridge-ui/public/script.js +957 -898
  134. package/homebridge-ui/public/styles.css +0 -1
  135. package/homebridge-ui/server.js +739 -695
  136. package/package.json +30 -25
  137. package/.claude/settings.json +0 -3
  138. package/.claude/settings.local.json +0 -24
  139. package/CHANGELOG.md +0 -114
  140. package/CLAUDE.md +0 -269
  141. package/config.md +0 -2
  142. package/docs/ARCHITECTURE.md +0 -645
  143. package/docs/IMPLEMENTATION_GUIDE.md +0 -899
  144. package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
  145. package/docs/NEXT_STEPS.md +0 -368
  146. package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
  147. package/docs/TROUBLESHOOTING.md +0 -475
  148. package/docs/api-response-for-BRP069A8x.json +0 -520
  149. package/docs/api-response-for-BRP069C4x-2.json +0 -881
  150. package/docs/api-response-for-BRP069C4x.json +0 -916
  151. package/docs/api-response-for-altherma.json +0 -759
  152. package/docs/api-response-for-altherma2.json +0 -2735
  153. package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
  154. package/docs/cr-insance-altherma-id-0.json +0 -834
  155. package/docs/mock-air-to-air-dx23.json +0 -759
  156. package/docs/mock-air-to-air-dx4.json +0 -1134
  157. package/docs/mock-airpurifier-with-humidifier.json +0 -732
  158. package/docs/mock-airpurifier.json +0 -450
  159. package/docs/mock-altherma-air-to-water-lan.json +0 -845
  160. package/docs/mock-altherma-air-to-water-wlan.json +0 -845
  161. package/docs/mock-d2cnd-gas-boiler.json +0 -649
  162. package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
  163. package/images/fan-speed.jpeg +0 -0
  164. package/images/homekit-controls.jpeg +0 -0
  165. package/images/homekit-settings.jpeg +0 -0
  166. package/images/swing-mode.png +0 -0
  167. package/jest.config.ts +0 -21
  168. package/test/fixtures/altherma-crSense-2.ts +0 -834
  169. package/test/fixtures/altherma-fraction.ts +0 -718
  170. package/test/fixtures/altherma-heat-pump-2.ts +0 -479
  171. package/test/fixtures/altherma-heat-pump.ts +0 -757
  172. package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
  173. package/test/fixtures/altherma-miladcerkic.ts +0 -524
  174. package/test/fixtures/altherma-v1ckoeln.ts +0 -644
  175. package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
  176. package/test/fixtures/dx23-airco-2.ts +0 -343
  177. package/test/fixtures/dx23-airco.ts +0 -518
  178. package/test/fixtures/dx4-airco.ts +0 -914
  179. package/test/fixtures/unknown-jan.ts +0 -488
  180. package/test/fixtures/unknown-kitchen-guests.ts +0 -488
  181. package/test/hbConfig/.daikin-mobile-tokenset +0 -8
  182. package/test/hbConfig/.uix-dashboard.json +0 -1
  183. package/test/hbConfig/.uix-secrets +0 -1
  184. package/test/hbConfig/accessories/.cachedAccessories.bak +0 -1
  185. package/test/hbConfig/accessories/cachedAccessories +0 -1
  186. package/test/hbConfig/accessories/uiAccessoriesLayout.json +0 -1
  187. package/test/hbConfig/auth.json +0 -10
  188. package/test/hbConfig/backups/config-backups/config.json.1767953686461 +0 -25
  189. package/test/hbConfig/backups/config-backups/config.json.1767953695236 +0 -29
  190. package/test/hbConfig/backups/config-backups/config.json.1767953814763 +0 -29
  191. package/test/hbConfig/backups/config-backups/config.json.1767953823101 +0 -29
  192. package/test/hbConfig/backups/config-backups/config.json.1767954822835 +0 -29
  193. package/test/hbConfig/backups/config-backups/config.json.1767954859218 +0 -29
  194. package/test/hbConfig/backups/config-backups/config.json.1767960145503 +0 -33
  195. package/test/hbConfig/backups/config-backups/config.json.1767960168068 +0 -44
  196. package/test/hbConfig/backups/config-backups/config.json.1767960170333 +0 -46
  197. package/test/hbConfig/backups/config-backups/config.json.1767960172731 +0 -44
  198. package/test/hbConfig/backups/config-backups/config.json.1767960179323 +0 -44
  199. package/test/hbConfig/backups/config-backups/config.json.1767960182114 +0 -44
  200. package/test/hbConfig/backups/config-backups/config.json.1767960189302 +0 -44
  201. package/test/hbConfig/backups/config-backups/config.json.1767960195194 +0 -44
  202. package/test/hbConfig/backups/config-backups/config.json.1767960197301 +0 -44
  203. package/test/hbConfig/backups/config-backups/config.json.1767960199151 +0 -44
  204. package/test/hbConfig/backups/config-backups/config.json.1767960199667 +0 -44
  205. package/test/hbConfig/backups/config-backups/config.json.1767960329839 +0 -44
  206. package/test/hbConfig/backups/config-backups/config.json.1767960334503 +0 -44
  207. package/test/hbConfig/backups/config-backups/config.json.1767960336208 +0 -44
  208. package/test/hbConfig/backups/config-backups/config.json.1767960338537 +0 -44
  209. package/test/hbConfig/backups/config-backups/config.json.1767963223953 +0 -44
  210. package/test/hbConfig/backups/config-backups/config.json.1767963241753 +0 -44
  211. package/test/hbConfig/backups/config-backups/config.json.1767963252785 +0 -44
  212. package/test/hbConfig/backups/config-backups/config.json.1767963463944 +0 -44
  213. package/test/hbConfig/backups/config-backups/config.json.1767963834475 +0 -44
  214. package/test/hbConfig/backups/config-backups/config.json.1767963838474 +0 -44
  215. package/test/hbConfig/backups/config-backups/config.json.1767963843066 +0 -44
  216. package/test/hbConfig/backups/config-backups/config.json.1767965217715 +0 -44
  217. package/test/hbConfig/backups/config-backups/config.json.1767965419624 +0 -25
  218. package/test/hbConfig/backups/config-backups/config.json.1767965870934 +0 -32
  219. package/test/hbConfig/backups/config-backups/config.json.1767977675045 +0 -32
  220. package/test/hbConfig/backups/config-backups/config.json.1767977677222 +0 -33
  221. package/test/hbConfig/backups/config-backups/config.json.1767977710226 +0 -33
  222. package/test/hbConfig/backups/config-backups/config.json.1767977741397 +0 -33
  223. package/test/hbConfig/backups/config-backups/config.json.1767977977093 +0 -35
  224. package/test/hbConfig/backups/config-backups/config.json.1767977981773 +0 -35
  225. package/test/hbConfig/backups/config-backups/config.json.1767977986514 +0 -35
  226. package/test/hbConfig/backups/config-backups/config.json.1767977991174 +0 -35
  227. package/test/hbConfig/backups/config-backups/config.json.1767979424487 +0 -35
  228. package/test/hbConfig/backups/config-backups/config.json.1767979424987 +0 -35
  229. package/test/hbConfig/backups/config-backups/config.json.1767979432646 +0 -47
  230. package/test/hbConfig/backups/config-backups/config.json.1767979433150 +0 -47
  231. package/test/hbConfig/backups/config-backups/config.json.1767979436933 +0 -47
  232. package/test/hbConfig/backups/config-backups/config.json.1767979437438 +0 -47
  233. package/test/hbConfig/backups/config-backups/config.json.1767979441676 +0 -47
  234. package/test/hbConfig/backups/config-backups/config.json.1767979442180 +0 -47
  235. package/test/hbConfig/backups/config-backups/config.json.1767979466735 +0 -47
  236. package/test/hbConfig/backups/config-backups/config.json.1767979903636 +0 -47
  237. package/test/hbConfig/backups/config-backups/config.json.1767979904135 +0 -47
  238. package/test/hbConfig/backups/config-backups/config.json.1767979906606 +0 -47
  239. package/test/hbConfig/backups/config-backups/config.json.1767979907108 +0 -47
  240. package/test/hbConfig/backups/config-backups/config.json.1767988702341 +0 -47
  241. package/test/hbConfig/backups/config-backups/config.json.1767988702837 +0 -47
  242. package/test/hbConfig/backups/config-backups/config.json.1767988713159 +0 -47
  243. package/test/hbConfig/backups/config-backups/config.json.1767988713664 +0 -47
  244. package/test/hbConfig/backups/config-backups/config.json.1767988918139 +0 -47
  245. package/test/hbConfig/backups/config-backups/config.json.1767988918639 +0 -47
  246. package/test/hbConfig/backups/config-backups/config.json.1767988921120 +0 -47
  247. package/test/hbConfig/backups/config-backups/config.json.1767988921624 +0 -47
  248. package/test/hbConfig/backups/config-backups/config.json.1767988930307 +0 -47
  249. package/test/hbConfig/backups/config-backups/config.json.1767988935070 +0 -47
  250. package/test/hbConfig/backups/config-backups/config.json.1767988935574 +0 -47
  251. package/test/hbConfig/backups/config-backups/config.json.1767989710262 +0 -47
  252. package/test/hbConfig/backups/config-backups/config.json.1767989710760 +0 -47
  253. package/test/hbConfig/backups/config-backups/config.json.1767989729668 +0 -47
  254. package/test/hbConfig/backups/config-backups/config.json.1767990295225 +0 -47
  255. package/test/hbConfig/backups/config-backups/config.json.1767990479921 +0 -47
  256. package/test/hbConfig/backups/config-backups/config.json.1767990481702 +0 -49
  257. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768010187391.tar.gz +0 -0
  258. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768096587387.tar.gz +0 -0
  259. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768182987404.tar.gz +0 -0
  260. package/test/hbConfig/config.json +0 -47
  261. package/test/hbConfig/daikin-cloud-certs/server.crt +0 -22
  262. package/test/hbConfig/daikin-cloud-certs/server.key +0 -28
  263. package/test/hbConfig/persist/AccessoryInfo.1E4A432551BA.json +0 -1
  264. package/test/hbConfig/persist/IdentifierCache.1E4A432551BA.json +0 -1
  265. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14758 +0 -1
  266. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14759 +0 -1
  267. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14760 +0 -1
  268. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14761 +0 -1
  269. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14762 +0 -1
  270. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14764 +0 -1
  271. package/test/helpers/test-isolation.ts +0 -228
  272. package/test/integration/air-conditioning.test.ts +0 -396
  273. package/test/integration/altherma.test.ts +0 -279
  274. package/test/integration/platform.test.ts +0 -118
  275. package/test/mobile-tokens.json +0 -8
  276. package/test/mocks/index.ts +0 -27
  277. package/test/test-gigya-auth.js +0 -443
  278. package/test/test-mobile-oauth.js +0 -175
  279. package/test/test-websocket-mobile.js +0 -123
  280. package/test/test-websocket.js +0 -116
  281. package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
  282. package/test/unit/api/daikin-api.test.ts +0 -442
  283. package/test/unit/api/daikin-cloud-repository.test.ts +0 -107
  284. package/test/unit/api/daikin-oauth.test.ts +0 -214
  285. package/test/unit/api/daikinCloud.test.ts +0 -12
  286. package/test/unit/api/token-storage.test.ts +0 -90
  287. package/test/unit/config/config-manager.test.ts +0 -271
  288. package/test/unit/device/daikin-device.test.ts +0 -73
  289. package/test/unit/services/hot-water-tank.service.test.ts +0 -123
  290. package/test/unit/utils/log-context.test.ts +0 -271
  291. package/test/unit/utils/update-mapper.test.ts +0 -404
@@ -1,443 +0,0 @@
1
- /**
2
- * Daikin Mobile App Authentication Flow
3
- *
4
- * Replicates the Gigya (SAP CDC) PKCE authentication flow used by the Daikin Onecta mobile app.
5
- *
6
- * Flow:
7
- * 1. Call /authorize with PKCE to get context JWT
8
- * 2. Call accounts.login with email/password to get login_token
9
- * 3. Call /authorize/continue with context + login_token to get authorization code
10
- * 4. Exchange code for tokens at /token endpoint with PKCE verifier
11
- */
12
-
13
- const https = require('https');
14
- const crypto = require('crypto');
15
- const readline = require('readline');
16
-
17
- // Configuration from mobile app
18
- const CONFIG = {
19
- apiKey: '3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm',
20
- clientId: 'FjS6T5oZHvzpZENIDybFRdtK',
21
- clientSecret: '_yWGLBGUnQFrN-u7uIOAZhSBsJOfcnBs0IS87wTgUvUmnLnEOs4NQmaKagqZBpQpG0XYl07KeCx8XHHKxAn24w',
22
- redirectUri: 'daikinunified://cdc/',
23
- baseUrl: 'https://cdc.daikin.eu',
24
- idpTokenEndpoint: 'https://idp.onecta.daikineurope.com/v1/oidc/token',
25
- scope: 'openid onecta:onecta.application offline_access',
26
- };
27
-
28
- const OIDC_BASE = `${CONFIG.baseUrl}/oidc/op/v1.0/${CONFIG.apiKey}`;
29
-
30
- // Generate PKCE challenge pair
31
- function generatePKCE() {
32
- const verifier = crypto.randomBytes(32).toString('base64url');
33
- const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
34
- return { verifier, challenge };
35
- }
36
-
37
- // Helper to make HTTPS requests
38
- function httpsRequest(url, options = {}, postData = null) {
39
- return new Promise((resolve, reject) => {
40
- const urlObj = new URL(url);
41
- const reqOptions = {
42
- hostname: urlObj.hostname,
43
- port: 443,
44
- path: urlObj.pathname + urlObj.search,
45
- method: options.method || 'GET',
46
- headers: options.headers || {},
47
- };
48
-
49
- const req = https.request(reqOptions, (res) => {
50
- let data = '';
51
- res.on('data', chunk => data += chunk);
52
- res.on('end', () => {
53
- resolve({
54
- statusCode: res.statusCode,
55
- headers: res.headers,
56
- body: data,
57
- });
58
- });
59
- });
60
-
61
- req.on('error', reject);
62
-
63
- if (postData) {
64
- req.write(postData);
65
- }
66
- req.end();
67
- });
68
- }
69
-
70
- // Step 1: Get OIDC context via authorize endpoint with PKCE
71
- async function getOidcContext(pkce) {
72
- console.log('\n[1/4] Getting OIDC context with PKCE...');
73
-
74
- const params = new URLSearchParams({
75
- client_id: CONFIG.clientId,
76
- redirect_uri: CONFIG.redirectUri,
77
- response_type: 'code',
78
- scope: CONFIG.scope,
79
- code_challenge: pkce.challenge,
80
- code_challenge_method: 'S256',
81
- state: crypto.randomBytes(16).toString('hex'),
82
- });
83
-
84
- const url = `${OIDC_BASE}/authorize?${params}`;
85
- const response = await httpsRequest(url, { method: 'GET' });
86
-
87
- if (response.statusCode === 302) {
88
- const location = response.headers.location;
89
-
90
- // Extract context from redirect URL
91
- const contextMatch = location.match(/context=([^&]+)/);
92
- if (contextMatch) {
93
- const context = decodeURIComponent(contextMatch[1]);
94
- console.log(' ✓ Context obtained');
95
- return context;
96
- }
97
- }
98
-
99
- throw new Error('Failed to get OIDC context');
100
- }
101
-
102
- // Generate riskContext fingerprint (simplified)
103
- function generateRiskContext() {
104
- const now = new Date();
105
- const timeStr = `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
106
- return JSON.stringify({
107
- b0: 14063,
108
- b1: [0, 2, 2, 0],
109
- b2: 4,
110
- b3: [],
111
- b4: 2,
112
- b5: 1,
113
- b6: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)',
114
- b7: [],
115
- b8: timeStr,
116
- b9: 0,
117
- b10: { state: 'denied' },
118
- b11: false,
119
- b12: null,
120
- b13: [5, '402|874|24', false, true],
121
- });
122
- }
123
-
124
- // Step 1.5: Initialize Gigya SDK and get required cookies
125
- async function initGigyaSdk(context) {
126
- console.log('\n[1.5/4] Initializing Gigya SDK...');
127
-
128
- // Build the pageURL like the mobile app does (with context from authorize)
129
- const proxyUrl = `https://id.daikin.eu/cdc/onecta/oidc/proxy.html?context=${encodeURIComponent(context)}&client_id=${CONFIG.clientId}&mode=login&scope=${encodeURIComponent(CONFIG.scope)}&gig_skipConsent=true`;
130
-
131
- const params = new URLSearchParams({
132
- apiKey: CONFIG.apiKey,
133
- pageURL: proxyUrl,
134
- sdk: 'js_latest',
135
- sdkBuild: '18305',
136
- format: 'json',
137
- });
138
-
139
- const response = await httpsRequest(
140
- `${CONFIG.baseUrl}/accounts.webSdkBootstrap?${params}`,
141
- {
142
- method: 'GET',
143
- headers: {
144
- 'Accept': '*/*',
145
- 'Origin': 'https://id.daikin.eu',
146
- 'Referer': 'https://id.daikin.eu/',
147
- },
148
- }
149
- );
150
-
151
- // Extract cookies from response headers (gmid, ucid, hasGmid)
152
- const cookies = [];
153
- const setCookies = response.headers['set-cookie'];
154
- if (setCookies) {
155
- const cookieArray = Array.isArray(setCookies) ? setCookies : [setCookies];
156
- for (const cookie of cookieArray) {
157
- const match = cookie.match(/^([^=]+=[^;]+)/);
158
- if (match) {
159
- cookies.push(match[1]);
160
- }
161
- }
162
- }
163
-
164
- // Add bootstrap cookie
165
- cookies.push(`gig_bootstrap_${CONFIG.apiKey}=cdc_ver4`);
166
-
167
- const cookieStr = cookies.join('; ');
168
- console.log(' ✓ SDK initialized (got', cookies.length, 'cookies)');
169
- return cookieStr;
170
- }
171
-
172
- // Step 2: Login with Gigya
173
- async function gigyaLogin(email, password, cookies) {
174
- console.log('\n[2/4] Authenticating with Gigya...');
175
-
176
- const params = new URLSearchParams({
177
- loginID: email,
178
- password: password,
179
- sessionExpiration: '31536000',
180
- targetEnv: 'jssdk',
181
- include: 'profile,data,emails,subscriptions,preferences,',
182
- includeUserInfo: 'true',
183
- loginMode: 'standard',
184
- lang: 'en',
185
- riskContext: generateRiskContext(),
186
- APIKey: CONFIG.apiKey,
187
- source: 'showScreenSet',
188
- sdk: 'js_latest',
189
- authMode: 'cookie',
190
- pageURL: `https://id.daikin.eu/cdc/onecta/oidc/registration-login.html?gig_client_id=${CONFIG.clientId}`,
191
- sdkBuild: '18305',
192
- format: 'json',
193
- });
194
-
195
- const response = await httpsRequest(
196
- `${CONFIG.baseUrl}/accounts.login`,
197
- {
198
- method: 'POST',
199
- headers: {
200
- 'Content-Type': 'application/x-www-form-urlencoded',
201
- 'Content-Length': Buffer.byteLength(params.toString()),
202
- 'Origin': 'https://id.daikin.eu',
203
- 'Referer': 'https://id.daikin.eu/',
204
- 'Cookie': cookies,
205
- },
206
- },
207
- params.toString()
208
- );
209
-
210
- const result = JSON.parse(response.body);
211
-
212
- if (result.errorCode !== 0) {
213
- console.log(' Debug - Full error response:', JSON.stringify(result, null, 2));
214
- throw new Error(`Login failed (code ${result.errorCode}): ${result.errorMessage || result.errorDetails}`);
215
- }
216
-
217
- console.log(' ✓ Login successful');
218
-
219
- // Get login_token from sessionInfo
220
- if (result.sessionInfo && result.sessionInfo.login_token) {
221
- return result.sessionInfo.login_token;
222
- }
223
-
224
- throw new Error('No login_token in response');
225
- }
226
-
227
- // Step 3: Continue authorization with login token
228
- async function authorizeWithToken(context, loginToken, cookies) {
229
- console.log('\n[3/4] Exchanging login token for authorization code...');
230
-
231
- const params = new URLSearchParams({
232
- context: context,
233
- login_token: loginToken,
234
- });
235
-
236
- // Add login token cookie (glt_{apiKey}) - required for authorize/continue
237
- const cookieStr = cookies + `; glt_${CONFIG.apiKey}=${loginToken}`;
238
-
239
- const url = `${OIDC_BASE}/authorize/continue?${params}`;
240
- const response = await httpsRequest(url, {
241
- method: 'GET',
242
- headers: {
243
- 'Cookie': cookieStr,
244
- 'Referer': 'https://id.daikin.eu/',
245
- },
246
- });
247
-
248
- // Debug output
249
- console.log(' Debug - Status:', response.statusCode);
250
- if (response.headers.location) {
251
- console.log(' Debug - Location:', response.headers.location.substring(0, 150) + '...');
252
- } else {
253
- console.log(' Debug - Body:', response.body.substring(0, 300));
254
- }
255
-
256
- if (response.statusCode === 302) {
257
- const location = response.headers.location;
258
-
259
- // Extract code from redirect
260
- const codeMatch = location.match(/code=([^&]+)/);
261
- if (codeMatch) {
262
- console.log(' ✓ Authorization code obtained');
263
- return codeMatch[1];
264
- }
265
-
266
- // Check for error
267
- const errorMatch = location.match(/error=([^&]+)/);
268
- if (errorMatch) {
269
- const errorDesc = location.match(/error_description=([^&]+)/);
270
- throw new Error(`Authorization error: ${decodeURIComponent(errorDesc ? errorDesc[1] : errorMatch[1])}`);
271
- }
272
- }
273
-
274
- throw new Error('Failed to get authorization code');
275
- }
276
-
277
- // Step 4: Exchange authorization code for tokens at IDP endpoint
278
- async function exchangeCodeForTokens(code, pkce) {
279
- console.log('\n[4/4] Exchanging authorization code for tokens at IDP...');
280
-
281
- // Build Basic Auth header with client_id:client_secret
282
- const basicAuth = Buffer.from(`${CONFIG.clientId}:${CONFIG.clientSecret}`).toString('base64');
283
-
284
- const params = new URLSearchParams({
285
- grant_type: 'authorization_code',
286
- code: code,
287
- redirect_uri: CONFIG.redirectUri,
288
- code_verifier: pkce.verifier,
289
- });
290
-
291
- const response = await httpsRequest(
292
- CONFIG.idpTokenEndpoint,
293
- {
294
- method: 'POST',
295
- headers: {
296
- 'Content-Type': 'application/x-www-form-urlencoded',
297
- 'Authorization': `Basic ${basicAuth}`,
298
- 'Content-Length': Buffer.byteLength(params.toString()),
299
- },
300
- },
301
- params.toString()
302
- );
303
-
304
- console.log(' Debug - Status:', response.statusCode);
305
-
306
- const result = JSON.parse(response.body);
307
-
308
- if (result.error) {
309
- console.log(' Debug - Error response:', JSON.stringify(result, null, 2));
310
- throw new Error(`Token exchange failed: ${result.error_description || result.error}`);
311
- }
312
-
313
- console.log(' ✓ Tokens obtained from IDP');
314
- return result;
315
- }
316
-
317
- // Test API access
318
- async function testApiAccess(accessToken) {
319
- console.log('\n[Test] Testing API access...');
320
-
321
- const response = await httpsRequest(
322
- 'https://api.onecta.daikineurope.com/v1/gateway-devices',
323
- {
324
- method: 'GET',
325
- headers: {
326
- 'Authorization': `Bearer ${accessToken}`,
327
- 'Accept': 'application/json',
328
- },
329
- }
330
- );
331
-
332
- if (response.statusCode === 200) {
333
- const devices = JSON.parse(response.body);
334
- console.log(' ✓ API access successful! Found', devices.length, 'device(s)');
335
-
336
- // Show rate limit
337
- const limitDay = response.headers['x-ratelimit-limit-day'];
338
- const remainingDay = response.headers['x-ratelimit-remaining-day'];
339
- console.log(' Rate limit:', remainingDay + '/' + limitDay, 'requests/day');
340
-
341
- return devices;
342
- } else {
343
- console.log(' ✗ API access failed:', response.statusCode);
344
- return null;
345
- }
346
- }
347
-
348
- // Test WebSocket access
349
- async function testWebSocket(accessToken) {
350
- console.log('\n[Test] Testing WebSocket access...');
351
-
352
- // We just check if the token has WebSocket in audience
353
- const payload = JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString());
354
-
355
- if (payload.aud && payload.aud.includes('wss://wsapi.onecta.daikineurope.com')) {
356
- console.log(' ✓ WebSocket access: ENABLED');
357
- return true;
358
- } else {
359
- console.log(' ✗ WebSocket access: NOT in token audience');
360
- return false;
361
- }
362
- }
363
-
364
- // Main flow
365
- async function main() {
366
- console.log('╔════════════════════════════════════════════════════════════╗');
367
- console.log('║ Daikin Mobile App Authentication (Gigya + PKCE) ║');
368
- console.log('╚════════════════════════════════════════════════════════════╝\n');
369
-
370
- console.log('This uses the official Daikin Onecta mobile app OAuth flow.');
371
- console.log('Benefits: WebSocket access, 5000 API calls/day (vs 200 for Developer Portal)\n');
372
-
373
- // Get credentials from env vars or prompt
374
- let email = process.env.DAIKIN_EMAIL;
375
- let password = process.env.DAIKIN_PASSWORD;
376
- let rl;
377
-
378
- if (!email || !password) {
379
- rl = readline.createInterface({
380
- input: process.stdin,
381
- output: process.stdout,
382
- });
383
-
384
- const question = (q) => new Promise(resolve => rl.question(q, resolve));
385
-
386
- if (!email) email = await question('Daikin account email: ');
387
- if (!password) password = await question('Daikin account password: ');
388
- rl.close();
389
- } else {
390
- console.log('Using credentials from environment variables\n');
391
- }
392
-
393
- try {
394
-
395
- // Generate PKCE
396
- const pkce = generatePKCE();
397
- console.log('\nPKCE generated');
398
-
399
- // Execute the authentication flow
400
- const context = await getOidcContext(pkce);
401
- const cookies = await initGigyaSdk(context);
402
- const loginToken = await gigyaLogin(email, password, cookies);
403
- const code = await authorizeWithToken(context, loginToken, cookies);
404
- const tokens = await exchangeCodeForTokens(code, pkce);
405
-
406
- // Success!
407
- console.log('\n╔════════════════════════════════════════════════════════════╗');
408
- console.log('║ AUTHENTICATION SUCCESS ║');
409
- console.log('╚════════════════════════════════════════════════════════════╝\n');
410
-
411
- console.log('Token type:', tokens.token_type);
412
- console.log('Expires in:', tokens.expires_in, 'seconds');
413
- console.log('Access token:', tokens.access_token.substring(0, 50) + '...');
414
-
415
- if (tokens.refresh_token) {
416
- console.log('Refresh token:', tokens.refresh_token.substring(0, 30) + '...');
417
- }
418
-
419
- // Decode and show scope
420
- const payload = JSON.parse(Buffer.from(tokens.access_token.split('.')[1], 'base64').toString());
421
- console.log('\nToken scope:', payload.scope);
422
-
423
- // Test API and WebSocket
424
- await testApiAccess(tokens.access_token);
425
- await testWebSocket(tokens.access_token);
426
-
427
- // Save tokens
428
- const fs = require('fs');
429
- const tokenData = {
430
- ...tokens,
431
- expires_at: Math.floor(Date.now() / 1000) + tokens.expires_in,
432
- };
433
- fs.writeFileSync('mobile-tokens.json', JSON.stringify(tokenData, null, 2));
434
- console.log('\n✓ Tokens saved to mobile-tokens.json');
435
-
436
- } catch (error) {
437
- console.error('\n✗ Error:', error.message);
438
- rl.close();
439
- process.exit(1);
440
- }
441
- }
442
-
443
- main();
@@ -1,175 +0,0 @@
1
- /**
2
- * Mobile App OAuth Flow Test
3
- *
4
- * Tests if we can authenticate using the mobile app's OAuth flow.
5
- */
6
-
7
- const crypto = require('crypto');
8
- const https = require('https');
9
- const http = require('http');
10
- const { URL } = require('url');
11
-
12
- // Mobile app OAuth config (extracted from JWT)
13
- const GIGYA_API_KEY = '3_xRB3jaQ62bVjqXU1omaEsPDVYC0Twi1zfq1zHPu_5HFT0zWkDvZJS97Yw1loJnTm';
14
- const MOBILE_CLIENT_ID = 'FjS6T5oZHvzpZENIDybFRdtK';
15
- const BASE_URL = `https://cdc.daikin.eu/oidc/op/v1.0/${GIGYA_API_KEY}`;
16
-
17
- // Scopes needed for WebSocket access
18
- const SCOPES = 'openid onecta:onecta.application offline_access';
19
-
20
- // Generate PKCE challenge
21
- function generatePKCE() {
22
- const verifier = crypto.randomBytes(32).toString('base64url');
23
- const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
24
- return { verifier, challenge };
25
- }
26
-
27
- // Generate state
28
- function generateState() {
29
- return crypto.randomBytes(16).toString('hex');
30
- }
31
-
32
- console.log('=== Daikin Mobile App OAuth Flow Test ===\n');
33
- console.log('Gigya API Key:', GIGYA_API_KEY.substring(0, 20) + '...');
34
- console.log('Mobile Client ID:', MOBILE_CLIENT_ID);
35
- console.log('Requested Scopes:', SCOPES);
36
- console.log('');
37
-
38
- // Generate PKCE values
39
- const pkce = generatePKCE();
40
- const state = generateState();
41
-
42
- console.log('PKCE Verifier:', pkce.verifier.substring(0, 20) + '...');
43
- console.log('PKCE Challenge:', pkce.challenge.substring(0, 20) + '...');
44
- console.log('State:', state);
45
- console.log('');
46
-
47
- // We need a redirect URI - let's try a few options
48
- const redirectUris = [
49
- 'daikin://auth', // Mobile app custom scheme
50
- 'https://my.daikin.eu/oauth/callback', // Possible web callback
51
- 'http://localhost:8888/callback', // Local callback for testing
52
- ];
53
-
54
- // Build authorization URL
55
- const authUrl = new URL(`${BASE_URL}/authorize`);
56
- authUrl.searchParams.set('client_id', MOBILE_CLIENT_ID);
57
- authUrl.searchParams.set('response_type', 'code');
58
- authUrl.searchParams.set('scope', SCOPES);
59
- authUrl.searchParams.set('redirect_uri', redirectUris[2]); // localhost for testing
60
- authUrl.searchParams.set('state', state);
61
- authUrl.searchParams.set('code_challenge', pkce.challenge);
62
- authUrl.searchParams.set('code_challenge_method', 'S256');
63
-
64
- console.log('Authorization URL:');
65
- console.log(authUrl.toString());
66
- console.log('');
67
-
68
- // Start local callback server
69
- const server = http.createServer(async (req, res) => {
70
- const reqUrl = new URL(req.url, 'http://localhost:8888');
71
-
72
- if (reqUrl.pathname === '/callback') {
73
- const code = reqUrl.searchParams.get('code');
74
- const returnedState = reqUrl.searchParams.get('state');
75
- const error = reqUrl.searchParams.get('error');
76
-
77
- console.log('\n--- Callback received ---');
78
-
79
- if (error) {
80
- console.log('Error:', error);
81
- console.log('Description:', reqUrl.searchParams.get('error_description'));
82
- res.writeHead(200);
83
- res.end('Error: ' + error);
84
- server.close();
85
- return;
86
- }
87
-
88
- if (returnedState !== state) {
89
- console.log('State mismatch!');
90
- res.writeHead(400);
91
- res.end('State mismatch');
92
- server.close();
93
- return;
94
- }
95
-
96
- console.log('Authorization code received:', code.substring(0, 20) + '...');
97
- console.log('');
98
-
99
- // Exchange code for tokens
100
- console.log('Exchanging code for tokens...');
101
-
102
- const tokenUrl = `${BASE_URL}/token`;
103
- const tokenParams = new URLSearchParams({
104
- grant_type: 'authorization_code',
105
- client_id: MOBILE_CLIENT_ID,
106
- code: code,
107
- redirect_uri: redirectUris[2],
108
- code_verifier: pkce.verifier,
109
- });
110
-
111
- const tokenReq = https.request(tokenUrl, {
112
- method: 'POST',
113
- headers: {
114
- 'Content-Type': 'application/x-www-form-urlencoded',
115
- 'Content-Length': Buffer.byteLength(tokenParams.toString()),
116
- },
117
- }, (tokenRes) => {
118
- let data = '';
119
- tokenRes.on('data', chunk => data += chunk);
120
- tokenRes.on('end', () => {
121
- console.log('Token response status:', tokenRes.statusCode);
122
- console.log('Token response:');
123
- try {
124
- const tokens = JSON.parse(data);
125
- console.log(JSON.stringify(tokens, null, 2));
126
-
127
- if (tokens.access_token) {
128
- console.log('\n✅ SUCCESS! Got access token');
129
- console.log('Token expires in:', tokens.expires_in, 'seconds');
130
-
131
- // Decode token to verify scope
132
- const payload = JSON.parse(Buffer.from(tokens.access_token.split('.')[1], 'base64').toString());
133
- console.log('Token scope:', payload.scope);
134
- console.log('WebSocket access:', payload.aud?.includes('wss://wsapi.onecta.daikineurope.com') ? 'YES' : 'NO');
135
- }
136
- } catch (e) {
137
- console.log('Raw response:', data);
138
- }
139
-
140
- res.writeHead(200);
141
- res.end('Authentication complete! Check terminal for results.');
142
- server.close();
143
- });
144
- });
145
-
146
- tokenReq.on('error', (e) => {
147
- console.log('Token request error:', e.message);
148
- res.writeHead(500);
149
- res.end('Token request failed');
150
- server.close();
151
- });
152
-
153
- tokenReq.write(tokenParams.toString());
154
- tokenReq.end();
155
- } else {
156
- res.writeHead(404);
157
- res.end('Not found');
158
- }
159
- });
160
-
161
- server.listen(8888, () => {
162
- console.log('Callback server listening on http://localhost:8888');
163
- console.log('');
164
- console.log('Open this URL in your browser to authenticate:');
165
- console.log(authUrl.toString());
166
- console.log('');
167
- console.log('Waiting for callback... (Ctrl+C to cancel)');
168
- });
169
-
170
- // Timeout after 5 minutes
171
- setTimeout(() => {
172
- console.log('\nTimeout - no callback received');
173
- server.close();
174
- process.exit(1);
175
- }, 300000);