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

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 (287) 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/script.js +956 -897
  131. package/homebridge-ui/server.js +739 -695
  132. package/package.json +27 -24
  133. package/.claude/settings.json +0 -3
  134. package/.claude/settings.local.json +0 -24
  135. package/CHANGELOG.md +0 -114
  136. package/CLAUDE.md +0 -269
  137. package/config.md +0 -2
  138. package/docs/ARCHITECTURE.md +0 -645
  139. package/docs/IMPLEMENTATION_GUIDE.md +0 -899
  140. package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
  141. package/docs/NEXT_STEPS.md +0 -368
  142. package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
  143. package/docs/TROUBLESHOOTING.md +0 -475
  144. package/docs/api-response-for-BRP069A8x.json +0 -520
  145. package/docs/api-response-for-BRP069C4x-2.json +0 -881
  146. package/docs/api-response-for-BRP069C4x.json +0 -916
  147. package/docs/api-response-for-altherma.json +0 -759
  148. package/docs/api-response-for-altherma2.json +0 -2735
  149. package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
  150. package/docs/cr-insance-altherma-id-0.json +0 -834
  151. package/docs/mock-air-to-air-dx23.json +0 -759
  152. package/docs/mock-air-to-air-dx4.json +0 -1134
  153. package/docs/mock-airpurifier-with-humidifier.json +0 -732
  154. package/docs/mock-airpurifier.json +0 -450
  155. package/docs/mock-altherma-air-to-water-lan.json +0 -845
  156. package/docs/mock-altherma-air-to-water-wlan.json +0 -845
  157. package/docs/mock-d2cnd-gas-boiler.json +0 -649
  158. package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
  159. package/images/fan-speed.jpeg +0 -0
  160. package/images/homekit-controls.jpeg +0 -0
  161. package/images/homekit-settings.jpeg +0 -0
  162. package/images/swing-mode.png +0 -0
  163. package/jest.config.ts +0 -21
  164. package/test/fixtures/altherma-crSense-2.ts +0 -834
  165. package/test/fixtures/altherma-fraction.ts +0 -718
  166. package/test/fixtures/altherma-heat-pump-2.ts +0 -479
  167. package/test/fixtures/altherma-heat-pump.ts +0 -757
  168. package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
  169. package/test/fixtures/altherma-miladcerkic.ts +0 -524
  170. package/test/fixtures/altherma-v1ckoeln.ts +0 -644
  171. package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
  172. package/test/fixtures/dx23-airco-2.ts +0 -343
  173. package/test/fixtures/dx23-airco.ts +0 -518
  174. package/test/fixtures/dx4-airco.ts +0 -914
  175. package/test/fixtures/unknown-jan.ts +0 -488
  176. package/test/fixtures/unknown-kitchen-guests.ts +0 -488
  177. package/test/hbConfig/.daikin-mobile-tokenset +0 -8
  178. package/test/hbConfig/.uix-dashboard.json +0 -1
  179. package/test/hbConfig/.uix-secrets +0 -1
  180. package/test/hbConfig/accessories/.cachedAccessories.bak +0 -1
  181. package/test/hbConfig/accessories/cachedAccessories +0 -1
  182. package/test/hbConfig/accessories/uiAccessoriesLayout.json +0 -1
  183. package/test/hbConfig/auth.json +0 -10
  184. package/test/hbConfig/backups/config-backups/config.json.1767953686461 +0 -25
  185. package/test/hbConfig/backups/config-backups/config.json.1767953695236 +0 -29
  186. package/test/hbConfig/backups/config-backups/config.json.1767953814763 +0 -29
  187. package/test/hbConfig/backups/config-backups/config.json.1767953823101 +0 -29
  188. package/test/hbConfig/backups/config-backups/config.json.1767954822835 +0 -29
  189. package/test/hbConfig/backups/config-backups/config.json.1767954859218 +0 -29
  190. package/test/hbConfig/backups/config-backups/config.json.1767960145503 +0 -33
  191. package/test/hbConfig/backups/config-backups/config.json.1767960168068 +0 -44
  192. package/test/hbConfig/backups/config-backups/config.json.1767960170333 +0 -46
  193. package/test/hbConfig/backups/config-backups/config.json.1767960172731 +0 -44
  194. package/test/hbConfig/backups/config-backups/config.json.1767960179323 +0 -44
  195. package/test/hbConfig/backups/config-backups/config.json.1767960182114 +0 -44
  196. package/test/hbConfig/backups/config-backups/config.json.1767960189302 +0 -44
  197. package/test/hbConfig/backups/config-backups/config.json.1767960195194 +0 -44
  198. package/test/hbConfig/backups/config-backups/config.json.1767960197301 +0 -44
  199. package/test/hbConfig/backups/config-backups/config.json.1767960199151 +0 -44
  200. package/test/hbConfig/backups/config-backups/config.json.1767960199667 +0 -44
  201. package/test/hbConfig/backups/config-backups/config.json.1767960329839 +0 -44
  202. package/test/hbConfig/backups/config-backups/config.json.1767960334503 +0 -44
  203. package/test/hbConfig/backups/config-backups/config.json.1767960336208 +0 -44
  204. package/test/hbConfig/backups/config-backups/config.json.1767960338537 +0 -44
  205. package/test/hbConfig/backups/config-backups/config.json.1767963223953 +0 -44
  206. package/test/hbConfig/backups/config-backups/config.json.1767963241753 +0 -44
  207. package/test/hbConfig/backups/config-backups/config.json.1767963252785 +0 -44
  208. package/test/hbConfig/backups/config-backups/config.json.1767963463944 +0 -44
  209. package/test/hbConfig/backups/config-backups/config.json.1767963834475 +0 -44
  210. package/test/hbConfig/backups/config-backups/config.json.1767963838474 +0 -44
  211. package/test/hbConfig/backups/config-backups/config.json.1767963843066 +0 -44
  212. package/test/hbConfig/backups/config-backups/config.json.1767965217715 +0 -44
  213. package/test/hbConfig/backups/config-backups/config.json.1767965419624 +0 -25
  214. package/test/hbConfig/backups/config-backups/config.json.1767965870934 +0 -32
  215. package/test/hbConfig/backups/config-backups/config.json.1767977675045 +0 -32
  216. package/test/hbConfig/backups/config-backups/config.json.1767977677222 +0 -33
  217. package/test/hbConfig/backups/config-backups/config.json.1767977710226 +0 -33
  218. package/test/hbConfig/backups/config-backups/config.json.1767977741397 +0 -33
  219. package/test/hbConfig/backups/config-backups/config.json.1767977977093 +0 -35
  220. package/test/hbConfig/backups/config-backups/config.json.1767977981773 +0 -35
  221. package/test/hbConfig/backups/config-backups/config.json.1767977986514 +0 -35
  222. package/test/hbConfig/backups/config-backups/config.json.1767977991174 +0 -35
  223. package/test/hbConfig/backups/config-backups/config.json.1767979424487 +0 -35
  224. package/test/hbConfig/backups/config-backups/config.json.1767979424987 +0 -35
  225. package/test/hbConfig/backups/config-backups/config.json.1767979432646 +0 -47
  226. package/test/hbConfig/backups/config-backups/config.json.1767979433150 +0 -47
  227. package/test/hbConfig/backups/config-backups/config.json.1767979436933 +0 -47
  228. package/test/hbConfig/backups/config-backups/config.json.1767979437438 +0 -47
  229. package/test/hbConfig/backups/config-backups/config.json.1767979441676 +0 -47
  230. package/test/hbConfig/backups/config-backups/config.json.1767979442180 +0 -47
  231. package/test/hbConfig/backups/config-backups/config.json.1767979466735 +0 -47
  232. package/test/hbConfig/backups/config-backups/config.json.1767979903636 +0 -47
  233. package/test/hbConfig/backups/config-backups/config.json.1767979904135 +0 -47
  234. package/test/hbConfig/backups/config-backups/config.json.1767979906606 +0 -47
  235. package/test/hbConfig/backups/config-backups/config.json.1767979907108 +0 -47
  236. package/test/hbConfig/backups/config-backups/config.json.1767988702341 +0 -47
  237. package/test/hbConfig/backups/config-backups/config.json.1767988702837 +0 -47
  238. package/test/hbConfig/backups/config-backups/config.json.1767988713159 +0 -47
  239. package/test/hbConfig/backups/config-backups/config.json.1767988713664 +0 -47
  240. package/test/hbConfig/backups/config-backups/config.json.1767988918139 +0 -47
  241. package/test/hbConfig/backups/config-backups/config.json.1767988918639 +0 -47
  242. package/test/hbConfig/backups/config-backups/config.json.1767988921120 +0 -47
  243. package/test/hbConfig/backups/config-backups/config.json.1767988921624 +0 -47
  244. package/test/hbConfig/backups/config-backups/config.json.1767988930307 +0 -47
  245. package/test/hbConfig/backups/config-backups/config.json.1767988935070 +0 -47
  246. package/test/hbConfig/backups/config-backups/config.json.1767988935574 +0 -47
  247. package/test/hbConfig/backups/config-backups/config.json.1767989710262 +0 -47
  248. package/test/hbConfig/backups/config-backups/config.json.1767989710760 +0 -47
  249. package/test/hbConfig/backups/config-backups/config.json.1767989729668 +0 -47
  250. package/test/hbConfig/backups/config-backups/config.json.1767990295225 +0 -47
  251. package/test/hbConfig/backups/config-backups/config.json.1767990479921 +0 -47
  252. package/test/hbConfig/backups/config-backups/config.json.1767990481702 +0 -49
  253. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768010187391.tar.gz +0 -0
  254. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768096587387.tar.gz +0 -0
  255. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768182987404.tar.gz +0 -0
  256. package/test/hbConfig/config.json +0 -47
  257. package/test/hbConfig/daikin-cloud-certs/server.crt +0 -22
  258. package/test/hbConfig/daikin-cloud-certs/server.key +0 -28
  259. package/test/hbConfig/persist/AccessoryInfo.1E4A432551BA.json +0 -1
  260. package/test/hbConfig/persist/IdentifierCache.1E4A432551BA.json +0 -1
  261. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14758 +0 -1
  262. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14759 +0 -1
  263. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14760 +0 -1
  264. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14761 +0 -1
  265. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14762 +0 -1
  266. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14764 +0 -1
  267. package/test/helpers/test-isolation.ts +0 -228
  268. package/test/integration/air-conditioning.test.ts +0 -396
  269. package/test/integration/altherma.test.ts +0 -279
  270. package/test/integration/platform.test.ts +0 -118
  271. package/test/mobile-tokens.json +0 -8
  272. package/test/mocks/index.ts +0 -27
  273. package/test/test-gigya-auth.js +0 -443
  274. package/test/test-mobile-oauth.js +0 -175
  275. package/test/test-websocket-mobile.js +0 -123
  276. package/test/test-websocket.js +0 -116
  277. package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
  278. package/test/unit/api/daikin-api.test.ts +0 -442
  279. package/test/unit/api/daikin-cloud-repository.test.ts +0 -107
  280. package/test/unit/api/daikin-oauth.test.ts +0 -214
  281. package/test/unit/api/daikinCloud.test.ts +0 -12
  282. package/test/unit/api/token-storage.test.ts +0 -90
  283. package/test/unit/config/config-manager.test.ts +0 -271
  284. package/test/unit/device/daikin-device.test.ts +0 -73
  285. package/test/unit/services/hot-water-tank.service.test.ts +0 -123
  286. package/test/unit/utils/log-context.test.ts +0 -271
  287. 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);