@mp-consulting/homebridge-daikin-cloud 1.3.5 → 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 (234) hide show
  1. package/LICENSE +39 -1
  2. package/README.md +5 -3
  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 +6 -6
  10. package/dist/src/accessories/base-accessory.d.ts.map +1 -1
  11. package/dist/src/accessories/base-accessory.js +15 -15
  12. package/dist/src/accessories/base-accessory.js.map +1 -1
  13. package/dist/src/api/daikin-api.d.ts +26 -26
  14. package/dist/src/api/daikin-api.d.ts.map +1 -1
  15. package/dist/src/api/daikin-api.js +68 -42
  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 +22 -14
  19. package/dist/src/api/daikin-cloud.repository.js.map +1 -1
  20. package/dist/src/api/daikin-controller.d.ts +41 -47
  21. package/dist/src/api/daikin-controller.d.ts.map +1 -1
  22. package/dist/src/api/daikin-controller.js +40 -64
  23. package/dist/src/api/daikin-controller.js.map +1 -1
  24. package/dist/src/api/daikin-device.d.ts +36 -31
  25. package/dist/src/api/daikin-device.d.ts.map +1 -1
  26. package/dist/src/api/daikin-device.js +45 -31
  27. package/dist/src/api/daikin-device.js.map +1 -1
  28. package/dist/src/api/daikin-mobile-oauth.d.ts +20 -20
  29. package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
  30. package/dist/src/api/daikin-mobile-oauth.js +49 -44
  31. package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
  32. package/dist/src/api/daikin-oauth.d.ts +32 -32
  33. package/dist/src/api/daikin-oauth.d.ts.map +1 -1
  34. package/dist/src/api/daikin-oauth.js +64 -56
  35. package/dist/src/api/daikin-oauth.js.map +1 -1
  36. package/dist/src/api/daikin-schemas.d.ts +476 -351
  37. package/dist/src/api/daikin-schemas.d.ts.map +1 -1
  38. package/dist/src/api/daikin-schemas.js +11 -42
  39. package/dist/src/api/daikin-schemas.js.map +1 -1
  40. package/dist/src/api/daikin-types.d.ts +5 -1
  41. package/dist/src/api/daikin-types.d.ts.map +1 -1
  42. package/dist/src/api/daikin-types.js.map +1 -1
  43. package/dist/src/api/daikin-websocket.d.ts +31 -32
  44. package/dist/src/api/daikin-websocket.d.ts.map +1 -1
  45. package/dist/src/api/daikin-websocket.js +55 -35
  46. package/dist/src/api/daikin-websocket.js.map +1 -1
  47. package/dist/src/api/index.d.ts +2 -1
  48. package/dist/src/api/index.d.ts.map +1 -1
  49. package/dist/src/api/index.js +3 -1
  50. package/dist/src/api/index.js.map +1 -1
  51. package/dist/src/api/token-storage.d.ts +21 -0
  52. package/dist/src/api/token-storage.d.ts.map +1 -0
  53. package/dist/src/api/token-storage.js +90 -0
  54. package/dist/src/api/token-storage.js.map +1 -0
  55. package/dist/src/config/config-manager.d.ts +33 -33
  56. package/dist/src/config/config-manager.d.ts.map +1 -1
  57. package/dist/src/config/config-manager.js +33 -33
  58. package/dist/src/config/config-manager.js.map +1 -1
  59. package/dist/src/constants/api.constants.d.ts +4 -0
  60. package/dist/src/constants/api.constants.d.ts.map +1 -1
  61. package/dist/src/constants/api.constants.js +5 -1
  62. package/dist/src/constants/api.constants.js.map +1 -1
  63. package/dist/src/constants/device.constants.d.ts +4 -0
  64. package/dist/src/constants/device.constants.d.ts.map +1 -1
  65. package/dist/src/constants/device.constants.js +5 -1
  66. package/dist/src/constants/device.constants.js.map +1 -1
  67. package/dist/src/device/accessory-factory.d.ts +10 -10
  68. package/dist/src/device/accessory-factory.d.ts.map +1 -1
  69. package/dist/src/device/accessory-factory.js +7 -7
  70. package/dist/src/device/accessory-factory.js.map +1 -1
  71. package/dist/src/device/capability-detector.d.ts +8 -8
  72. package/dist/src/device/capability-detector.d.ts.map +1 -1
  73. package/dist/src/device/capability-detector.js +6 -6
  74. package/dist/src/device/capability-detector.js.map +1 -1
  75. package/dist/src/device/capability-docs.d.ts +1 -9
  76. package/dist/src/device/capability-docs.d.ts.map +1 -1
  77. package/dist/src/device/capability-docs.js +19 -73
  78. package/dist/src/device/capability-docs.js.map +1 -1
  79. package/dist/src/device/profiles/device-profile.d.ts +1 -1
  80. package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
  81. package/dist/src/device/profiles/device-profile.js +4 -4
  82. package/dist/src/device/profiles/device-profile.js.map +1 -1
  83. package/dist/src/features/base-feature.d.ts +2 -2
  84. package/dist/src/features/base-feature.d.ts.map +1 -1
  85. package/dist/src/features/base-feature.js +2 -3
  86. package/dist/src/features/base-feature.js.map +1 -1
  87. package/dist/src/features/feature-manager.d.ts +8 -16
  88. package/dist/src/features/feature-manager.d.ts.map +1 -1
  89. package/dist/src/features/feature-manager.js +5 -17
  90. package/dist/src/features/feature-manager.js.map +1 -1
  91. package/dist/src/features/modes/dry-operation-mode.feature.d.ts +1 -1
  92. package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
  93. package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
  94. package/dist/src/features/modes/econo-mode.feature.d.ts +1 -1
  95. package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
  96. package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
  97. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +1 -1
  98. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
  99. package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
  100. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +1 -1
  101. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
  102. package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
  103. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +1 -1
  104. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
  105. package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
  106. package/dist/src/features/modes/powerful-mode.feature.d.ts +1 -1
  107. package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
  108. package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
  109. package/dist/src/features/modes/streamer-mode.feature.d.ts +1 -1
  110. package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
  111. package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
  112. package/dist/src/index.d.ts +1 -1
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js.map +1 -1
  115. package/dist/src/platform.d.ts +11 -8
  116. package/dist/src/platform.d.ts.map +1 -1
  117. package/dist/src/platform.js +64 -15
  118. package/dist/src/platform.js.map +1 -1
  119. package/dist/src/services/climate-control.service.d.ts +8 -2
  120. package/dist/src/services/climate-control.service.d.ts.map +1 -1
  121. package/dist/src/services/climate-control.service.js +59 -58
  122. package/dist/src/services/climate-control.service.js.map +1 -1
  123. package/dist/src/services/hot-water-tank.service.d.ts +6 -2
  124. package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
  125. package/dist/src/services/hot-water-tank.service.js +33 -31
  126. package/dist/src/services/hot-water-tank.service.js.map +1 -1
  127. package/dist/src/types/daikin-enums.js +12 -12
  128. package/dist/src/types/daikin-enums.js.map +1 -1
  129. package/dist/src/types/device-capabilities.d.ts +1 -1
  130. package/dist/src/types/device-capabilities.d.ts.map +1 -1
  131. package/dist/src/utils/log-context.d.ts +23 -23
  132. package/dist/src/utils/log-context.d.ts.map +1 -1
  133. package/dist/src/utils/log-context.js +28 -28
  134. package/dist/src/utils/log-context.js.map +1 -1
  135. package/dist/src/utils/strings.d.ts.map +1 -1
  136. package/dist/src/utils/strings.js.map +1 -1
  137. package/dist/src/utils/update-mapper.d.ts +16 -16
  138. package/dist/src/utils/update-mapper.d.ts.map +1 -1
  139. package/dist/src/utils/update-mapper.js +14 -14
  140. package/dist/src/utils/update-mapper.js.map +1 -1
  141. package/homebridge-ui/public/index.html +2 -2
  142. package/homebridge-ui/public/script.js +957 -898
  143. package/homebridge-ui/server.js +746 -678
  144. package/package.json +29 -27
  145. package/.claude/settings.json +0 -3
  146. package/.claude/settings.local.json +0 -29
  147. package/CHANGELOG.md +0 -103
  148. package/CLAUDE.md +0 -269
  149. package/config.md +0 -2
  150. package/dist/src/api/daikin-device-tracker.d.ts +0 -97
  151. package/dist/src/api/daikin-device-tracker.d.ts.map +0 -1
  152. package/dist/src/api/daikin-device-tracker.js +0 -136
  153. package/dist/src/api/daikin-device-tracker.js.map +0 -1
  154. package/dist/src/api/http-interceptor.d.ts +0 -99
  155. package/dist/src/api/http-interceptor.d.ts.map +0 -1
  156. package/dist/src/api/http-interceptor.js +0 -177
  157. package/dist/src/api/http-interceptor.js.map +0 -1
  158. package/dist/src/di/service-container.d.ts +0 -92
  159. package/dist/src/di/service-container.d.ts.map +0 -1
  160. package/dist/src/di/service-container.js +0 -156
  161. package/dist/src/di/service-container.js.map +0 -1
  162. package/dist/src/features/feature-registry.d.ts +0 -100
  163. package/dist/src/features/feature-registry.d.ts.map +0 -1
  164. package/dist/src/features/feature-registry.js +0 -142
  165. package/dist/src/features/feature-registry.js.map +0 -1
  166. package/dist/src/services/service-factory.d.ts +0 -46
  167. package/dist/src/services/service-factory.d.ts.map +0 -1
  168. package/dist/src/services/service-factory.js +0 -72
  169. package/dist/src/services/service-factory.js.map +0 -1
  170. package/dist/src/utils/error-handler.d.ts +0 -101
  171. package/dist/src/utils/error-handler.d.ts.map +0 -1
  172. package/dist/src/utils/error-handler.js +0 -251
  173. package/dist/src/utils/error-handler.js.map +0 -1
  174. package/dist/src/utils/retry.d.ts +0 -42
  175. package/dist/src/utils/retry.d.ts.map +0 -1
  176. package/dist/src/utils/retry.js +0 -70
  177. package/dist/src/utils/retry.js.map +0 -1
  178. package/docs/ARCHITECTURE.md +0 -645
  179. package/docs/IMPLEMENTATION_GUIDE.md +0 -899
  180. package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
  181. package/docs/NEXT_STEPS.md +0 -368
  182. package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
  183. package/docs/TROUBLESHOOTING.md +0 -475
  184. package/docs/api-response-for-BRP069A8x.json +0 -520
  185. package/docs/api-response-for-BRP069C4x-2.json +0 -881
  186. package/docs/api-response-for-BRP069C4x.json +0 -916
  187. package/docs/api-response-for-altherma.json +0 -759
  188. package/docs/api-response-for-altherma2.json +0 -2735
  189. package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
  190. package/docs/cr-insance-altherma-id-0.json +0 -834
  191. package/docs/mock-air-to-air-dx23.json +0 -759
  192. package/docs/mock-air-to-air-dx4.json +0 -1134
  193. package/docs/mock-airpurifier-with-humidifier.json +0 -732
  194. package/docs/mock-airpurifier.json +0 -450
  195. package/docs/mock-altherma-air-to-water-lan.json +0 -845
  196. package/docs/mock-altherma-air-to-water-wlan.json +0 -845
  197. package/docs/mock-d2cnd-gas-boiler.json +0 -649
  198. package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
  199. package/images/fan-speed.jpeg +0 -0
  200. package/images/homekit-controls.jpeg +0 -0
  201. package/images/homekit-settings.jpeg +0 -0
  202. package/images/swing-mode.png +0 -0
  203. package/jest.config.ts +0 -13
  204. package/test/fixtures/altherma-crSense-2.ts +0 -834
  205. package/test/fixtures/altherma-fraction.ts +0 -718
  206. package/test/fixtures/altherma-heat-pump-2.ts +0 -479
  207. package/test/fixtures/altherma-heat-pump.ts +0 -757
  208. package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
  209. package/test/fixtures/altherma-miladcerkic.ts +0 -524
  210. package/test/fixtures/altherma-v1ckoeln.ts +0 -644
  211. package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
  212. package/test/fixtures/dx23-airco-2.ts +0 -343
  213. package/test/fixtures/dx23-airco.ts +0 -518
  214. package/test/fixtures/dx4-airco.ts +0 -914
  215. package/test/fixtures/unknown-jan.ts +0 -488
  216. package/test/fixtures/unknown-kitchen-guests.ts +0 -488
  217. package/test/helpers/test-isolation.ts +0 -228
  218. package/test/integration/air-conditioning.test.ts +0 -410
  219. package/test/integration/altherma.test.ts +0 -289
  220. package/test/integration/platform.test.ts +0 -118
  221. package/test/mocks/index.ts +0 -27
  222. package/test/test-gigya-auth.js +0 -443
  223. package/test/test-mobile-oauth.js +0 -175
  224. package/test/test-websocket-mobile.js +0 -123
  225. package/test/test-websocket.js +0 -116
  226. package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
  227. package/test/unit/api/daikin-api.test.ts +0 -384
  228. package/test/unit/api/daikin-oauth.test.ts +0 -214
  229. package/test/unit/api/daikinCloud.test.ts +0 -12
  230. package/test/unit/config/config-manager.test.ts +0 -271
  231. package/test/unit/device/daikin-device.test.ts +0 -79
  232. package/test/unit/services/hot-water-tank.service.test.ts +0 -123
  233. package/test/unit/utils/error-handler.test.ts +0 -274
  234. package/test/unit/utils/log-context.test.ts +0 -271
@@ -1,899 +0,0 @@
1
- # Implementation Guide for Remaining Improvements
2
-
3
- This document provides detailed implementation guidance for the remaining improvements identified in the code analysis. Use this as a roadmap to complete the enhancement phases.
4
-
5
- ## ✅ Completed Improvements
6
-
7
- ### Phase 1: Foundation & Type Safety
8
-
9
- #### 1.1 Enable Strict TypeScript Checking ✓
10
- **Status**: Completed
11
- **Changes Made:**
12
- - Enabled `noImplicitAny: true` in [tsconfig.json](tsconfig.json)
13
- - Fixed 2 implicit `any` type errors in [daikin-cloud.repository.ts](src/api/daikin-cloud.repository.ts)
14
- - Build passes with strict type checking
15
-
16
- #### 1.2 Add Data Validation Layer with Zod ✓
17
- **Status**: Completed
18
- **Changes Made:**
19
- - Installed Zod v3.23.8 (compatible with TypeScript 4.4.4)
20
- - Created [daikin-schemas.ts](src/api/daikin-schemas.ts) with validation schemas for:
21
- - TokenSet, RateLimitStatus
22
- - ManagementPoint, GatewayDevice
23
- - WebSocketDeviceUpdate
24
- - Configuration validation (DaikinClientConfig, MobileClientConfig, DaikinControllerConfig)
25
- - Added `validateWithZod()` method to ConfigManager
26
- - Helper functions: `validateData()` and `safeValidateData()`
27
-
28
- **Next Steps:**
29
- - Use Zod schemas in API response parsing
30
- - Add validation to WebSocket message handling
31
- - Validate device data before processing
32
-
33
- #### 1.3 Consolidate WebSocket Update Logic ✓
34
- **Status**: Completed
35
- **Changes Made:**
36
- - Refactored [platform.ts](src/platform.ts) to use [UpdateMapper](src/utils/update-mapper.ts)
37
- - Removed 180+ lines of duplicated code
38
- - Single source of truth for WebSocket update handling
39
- - Improved maintainability and testability
40
-
41
- #### 1.4 Security Hardening ✓
42
- **Status**: Completed
43
- **Changes Made:**
44
- - Verified token files already use mode 0o600 (secure permissions)
45
- - Updated GitHub Actions to v4:
46
- - [.github/workflows/build.yml](.github/workflows/build.yml): actions/checkout@v4, actions/setup-node@v4
47
- - [.github/workflows/npm-publish.yml](.github/workflows/npm-publish.yml): actions/checkout@v4, actions/setup-node@v4
48
-
49
- #### 1.5 Create ARCHITECTURE.md ✓
50
- **Status**: Completed
51
- **Changes Made:**
52
- - Created comprehensive [ARCHITECTURE.md](docs/ARCHITECTURE.md) with:
53
- - System architecture diagram
54
- - Component documentation
55
- - Data flow diagrams
56
- - Configuration schema
57
- - Security considerations
58
- - Extension guides
59
-
60
- ---
61
-
62
- ## 📋 Remaining Improvements
63
-
64
- ### Phase 1 Remaining Tasks
65
-
66
- #### 1.6 Add Integration Tests for OAuth Flows
67
-
68
- **Priority**: High
69
- **Estimated Effort**: 4-6 hours
70
- **Files to Create/Modify:**
71
- - `test/integration/oauth-developer-portal.test.ts` (new)
72
- - `test/integration/oauth-mobile-app.test.ts` (new)
73
- - `test/mocks/oauth-mock-server.ts` (new)
74
-
75
- **Implementation Steps:**
76
-
77
- 1. **Create OAuth Mock Server**
78
- ```typescript
79
- // test/mocks/oauth-mock-server.ts
80
- import express from 'express';
81
-
82
- export class OAuthMockServer {
83
- private app: express.Application;
84
- private server: any;
85
-
86
- constructor() {
87
- this.app = express();
88
- this.setupRoutes();
89
- }
90
-
91
- private setupRoutes() {
92
- // Mock authorization endpoint
93
- this.app.get('/v1/oidc/authorize', (req, res) => {
94
- const {redirect_uri, state} = req.query;
95
- res.redirect(`${redirect_uri}?code=mock_code&state=${state}`);
96
- });
97
-
98
- // Mock token endpoint
99
- this.app.post('/v1/oidc/token', (req, res) => {
100
- res.json({
101
- access_token: 'mock_access_token',
102
- refresh_token: 'mock_refresh_token',
103
- token_type: 'Bearer',
104
- expires_in: 3600,
105
- });
106
- });
107
-
108
- // Mock Gigya login
109
- this.app.post('/accounts.login', (req, res) => {
110
- res.json({
111
- id_token: 'mock_gigya_token',
112
- sessionInfo: {sessionToken: 'mock_session'},
113
- });
114
- });
115
- }
116
-
117
- start(port: number): Promise<void> {
118
- return new Promise((resolve) => {
119
- this.server = this.app.listen(port, () => resolve());
120
- });
121
- }
122
-
123
- stop(): Promise<void> {
124
- return new Promise((resolve) => {
125
- this.server.close(() => resolve());
126
- });
127
- }
128
- }
129
- ```
130
-
131
- 2. **Create Developer Portal OAuth Tests**
132
- ```typescript
133
- // test/integration/oauth-developer-portal.test.ts
134
- import {DaikinOAuth} from '../../src/api/daikin-oauth';
135
- import {OAuthMockServer} from '../mocks/oauth-mock-server';
136
- import fs from 'fs';
137
- import path from 'path';
138
-
139
- describe('Developer Portal OAuth Flow', () => {
140
- let mockServer: OAuthMockServer;
141
- let tokenFilePath: string;
142
-
143
- beforeAll(async () => {
144
- mockServer = new OAuthMockServer();
145
- await mockServer.start(8585);
146
- tokenFilePath = path.join(__dirname, '.test-tokenset');
147
- });
148
-
149
- afterAll(async () => {
150
- await mockServer.stop();
151
- if (fs.existsSync(tokenFilePath)) {
152
- fs.unlinkSync(tokenFilePath);
153
- }
154
- });
155
-
156
- test('should complete authorization code flow', async () => {
157
- const oauth = new DaikinOAuth({
158
- clientId: 'test_client',
159
- clientSecret: 'test_secret',
160
- callbackServerExternalAddress: 'localhost',
161
- callbackServerPort: 8586,
162
- tokenFilePath,
163
- });
164
-
165
- // Test authorization URL generation
166
- const authUrl = oauth.buildAuthUrl();
167
- expect(authUrl).toContain('response_type=code');
168
- expect(authUrl).toContain('client_id=test_client');
169
-
170
- // Test token exchange
171
- const tokenSet = await oauth.exchangeCode('mock_code');
172
- expect(tokenSet.access_token).toBe('mock_access_token');
173
- expect(tokenSet.refresh_token).toBe('mock_refresh_token');
174
-
175
- // Verify token was saved
176
- expect(fs.existsSync(tokenFilePath)).toBe(true);
177
- const savedToken = JSON.parse(fs.readFileSync(tokenFilePath, 'utf8'));
178
- expect(savedToken.access_token).toBe('mock_access_token');
179
- });
180
-
181
- test('should refresh expired token', async () => {
182
- // Implementation
183
- });
184
-
185
- test('should handle authentication errors', async () => {
186
- // Implementation
187
- });
188
- });
189
- ```
190
-
191
- 3. **Create Mobile App OAuth Tests**
192
- ```typescript
193
- // test/integration/oauth-mobile-app.test.ts
194
- import {DaikinMobileOAuth} from '../../src/api/daikin-mobile-oauth';
195
-
196
- describe('Mobile App OAuth Flow', () => {
197
- test('should login with email/password', async () => {
198
- // Implementation
199
- });
200
-
201
- test('should exchange Gigya token for OIDC token', async () => {
202
- // Implementation
203
- });
204
-
205
- test('should handle invalid credentials', async () => {
206
- // Implementation
207
- });
208
- });
209
- ```
210
-
211
- 4. **Add to package.json**
212
- ```json
213
- {
214
- "devDependencies": {
215
- "express": "^4.18.2",
216
- "@types/express": "^4.17.17"
217
- }
218
- }
219
- ```
220
-
221
- **Testing:**
222
- ```bash
223
- npm install
224
- npm run test -- test/integration/oauth-developer-portal.test.ts
225
- npm run test -- test/integration/oauth-mobile-app.test.ts
226
- ```
227
-
228
- ---
229
-
230
- ### Phase 2: Error Handling & Resilience
231
-
232
- #### 2.1 Implement Error Recovery in Services
233
-
234
- **Priority**: High
235
- **Estimated Effort**: 3-4 hours
236
- **Files to Modify:**
237
- - [src/services/climate-control.service.ts](src/services/climate-control.service.ts)
238
- - [src/services/hot-water-tank.service.ts](src/services/hot-water-tank.service.ts)
239
-
240
- **Implementation Steps:**
241
-
242
- 1. **Create Retry Helper**
243
- ```typescript
244
- // src/utils/retry.ts
245
- export async function retryWithBackoff<T>(
246
- fn: () => Promise<T>,
247
- options: {
248
- maxRetries?: number;
249
- initialDelay?: number;
250
- maxDelay?: number;
251
- onRetry?: (attempt: number, error: Error) => void;
252
- } = {},
253
- ): Promise<T> {
254
- const {
255
- maxRetries = 3,
256
- initialDelay = 1000,
257
- maxDelay = 10000,
258
- onRetry,
259
- } = options;
260
-
261
- let lastError: Error;
262
-
263
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
264
- try {
265
- return await fn();
266
- } catch (error) {
267
- lastError = error as Error;
268
-
269
- if (attempt < maxRetries) {
270
- const delay = Math.min(
271
- initialDelay * Math.pow(2, attempt),
272
- maxDelay,
273
- );
274
-
275
- onRetry?.(attempt + 1, lastError);
276
- await new Promise(resolve => setTimeout(resolve, delay));
277
- }
278
- }
279
- }
280
-
281
- throw lastError!;
282
- }
283
- ```
284
-
285
- 2. **Update Climate Control Service**
286
- ```typescript
287
- // In climate-control.service.ts
288
- import {retryWithBackoff} from '../utils/retry';
289
- import {ErrorHandler, ErrorSeverity} from '../utils/error-handler';
290
-
291
- async handleTargetStateSet(value: CharacteristicValue) {
292
- try {
293
- const targetState = value as number;
294
-
295
- await retryWithBackoff(
296
- async () => {
297
- const operationMode = this.mapTargetStateToOperationMode(targetState);
298
- await this.controller.setDeviceData(/* ... */);
299
- },
300
- {
301
- maxRetries: 3,
302
- onRetry: (attempt, error) => {
303
- this.platform.log.warn(
304
- `[Service] Retry attempt ${attempt} for target state: ${error.message}`,
305
- );
306
- },
307
- },
308
- );
309
-
310
- // Success - schedule force update
311
- this.scheduleForceUpdate();
312
-
313
- } catch (error) {
314
- const errorInfo = ErrorHandler.categorizeError(error);
315
- ErrorHandler.logError(
316
- this.platform.log,
317
- 'Failed to set target state after retries',
318
- error,
319
- errorInfo.severity,
320
- );
321
-
322
- // Update characteristic to reflect failure
323
- this.service.updateCharacteristic(
324
- this.platform.Characteristic.TargetHeaterCoolerState,
325
- this.getCurrentTargetState(), // Revert to last known good state
326
- );
327
-
328
- throw new this.platform.api.hap.HapStatusError(
329
- this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE,
330
- );
331
- }
332
- }
333
- ```
334
-
335
- 3. **Add State Tracking**
336
- ```typescript
337
- private lastKnownGoodState: {
338
- active?: number;
339
- targetState?: number;
340
- coolingThreshold?: number;
341
- heatingThreshold?: number;
342
- } = {};
343
-
344
- private updateLastKnownGoodState(characteristic: string, value: number) {
345
- this.lastKnownGoodState[characteristic] = value;
346
- }
347
- ```
348
-
349
- #### 2.2 Add WebSocket Resilience Tests
350
-
351
- **Priority**: Medium
352
- **Estimated Effort**: 3-4 hours
353
- **Files to Create:**
354
- - `test/unit/api/daikin-websocket.test.ts` (new)
355
-
356
- **Implementation:**
357
-
358
- ```typescript
359
- // test/unit/api/daikin-websocket.test.ts
360
- import {DaikinWebSocket} from '../../../src/api/daikin-websocket';
361
- import {EventEmitter} from 'events';
362
- import WS from 'ws';
363
-
364
- describe('DaikinWebSocket', () => {
365
- let mockServer: WS.Server;
366
-
367
- beforeEach(() => {
368
- mockServer = new WS.Server({port: 8587});
369
- });
370
-
371
- afterEach(() => {
372
- mockServer.close();
373
- });
374
-
375
- test('should connect and handle messages', async () => {
376
- const websocket = new DaikinWebSocket(/* ... */);
377
-
378
- await websocket.connect();
379
-
380
- // Simulate server message
381
- mockServer.clients.forEach(client => {
382
- client.send(JSON.stringify({
383
- deviceId: 'test-device',
384
- embeddedId: '0',
385
- characteristicName: 'onOffMode',
386
- data: {value: 'on'},
387
- }));
388
- });
389
-
390
- // Assert event was emitted
391
- });
392
-
393
- test('should reconnect after disconnect', async () => {
394
- const websocket = new DaikinWebSocket(/* ... */);
395
- await websocket.connect();
396
-
397
- // Simulate disconnect
398
- mockServer.close();
399
-
400
- // Wait for reconnection attempt
401
- await new Promise(resolve => setTimeout(resolve, 2000));
402
-
403
- // Verify reconnection logic was triggered
404
- });
405
-
406
- test('should handle malformed messages gracefully', async () => {
407
- const websocket = new DaikinWebSocket(/* ... */);
408
- await websocket.connect();
409
-
410
- mockServer.clients.forEach(client => {
411
- client.send('invalid json');
412
- });
413
-
414
- // Should not crash
415
- });
416
-
417
- test('should respect exponential backoff on reconnect', async () => {
418
- // Test backoff delays: 1s, 2s, 4s, 8s, ...
419
- });
420
- });
421
- ```
422
-
423
- #### 2.3 Create Status UI for Monitoring
424
-
425
- **Priority**: Medium
426
- **Estimated Effort**: 6-8 hours
427
- **Files to Create/Modify:**
428
- - `homebridge-ui/public/status.html` (new)
429
- - `homebridge-ui/server.js` (modify)
430
-
431
- **Implementation:**
432
-
433
- 1. **Create Status Endpoint**
434
- ```javascript
435
- // homebridge-ui/server.js
436
- app.get('/status', async (req, res) => {
437
- try {
438
- const status = {
439
- authMode: config.authMode || 'developer_portal',
440
- authenticated: false,
441
- apiUsage: {
442
- limitDay: 0,
443
- remainingDay: 0,
444
- limitMinute: 0,
445
- remainingMinute: 0,
446
- },
447
- websocket: {
448
- connected: false,
449
- lastConnected: null,
450
- },
451
- devices: [],
452
- lastSync: null,
453
- };
454
-
455
- // Read status from plugin
456
- // (Plugin would need to expose status via file or API)
457
-
458
- res.json(status);
459
- } catch (error) {
460
- res.status(500).json({error: error.message});
461
- }
462
- });
463
- ```
464
-
465
- 2. **Create Status UI**
466
- ```html
467
- <!-- homebridge-ui/public/status.html -->
468
- <div class="card">
469
- <h3>Authentication Status</h3>
470
- <div id="auth-status">
471
- <span class="badge badge-success">Connected</span>
472
- <span>Mode: <strong id="auth-mode">Mobile App</strong></span>
473
- </div>
474
- </div>
475
-
476
- <div class="card">
477
- <h3>API Rate Limit</h3>
478
- <div class="progress">
479
- <div id="rate-limit-bar" class="progress-bar" style="width: 75%">
480
- <span id="rate-limit-text">75% remaining (3750/5000)</span>
481
- </div>
482
- </div>
483
- <small>Resets: <span id="rate-limit-reset">23:59 UTC</span></small>
484
- </div>
485
-
486
- <div class="card">
487
- <h3>WebSocket Connection</h3>
488
- <div id="websocket-status">
489
- <span class="badge badge-success">Connected</span>
490
- <span>Last message: <span id="ws-last-message">2 minutes ago</span></span>
491
- </div>
492
- </div>
493
-
494
- <div class="card">
495
- <h3>Devices</h3>
496
- <table class="table">
497
- <thead>
498
- <tr>
499
- <th>Device</th>
500
- <th>Status</th>
501
- <th>Last Sync</th>
502
- <th>Errors</th>
503
- </tr>
504
- </thead>
505
- <tbody id="device-list">
506
- <!-- Populated by JavaScript -->
507
- </tbody>
508
- </table>
509
- </div>
510
-
511
- <script>
512
- async function loadStatus() {
513
- const response = await fetch('/status');
514
- const status = await response.json();
515
-
516
- // Update UI with status
517
- document.getElementById('auth-mode').textContent = status.authMode;
518
- // ... etc
519
- }
520
-
521
- setInterval(loadStatus, 5000); // Refresh every 5 seconds
522
- loadStatus();
523
- </script>
524
- ```
525
-
526
- ---
527
-
528
- ### Phase 3: Documentation & Performance
529
-
530
- #### 3.1 Add Comprehensive JSDoc Documentation
531
-
532
- **Priority**: Medium
533
- **Estimated Effort**: 4-6 hours
534
- **Files to Modify**: All core classes
535
-
536
- **Implementation Template:**
537
-
538
- ```typescript
539
- /**
540
- * Daikin Cloud Controller
541
- *
542
- * Manages authentication, API communication, and device data for Daikin Cloud integration.
543
- * Supports both Developer Portal (OAuth 2.0) and Mobile App (Gigya) authentication modes.
544
- *
545
- * @example
546
- * ```typescript
547
- * const controller = new DaikinCloudController({
548
- * authMode: 'mobile_app',
549
- * email: 'user@example.com',
550
- * password: 'password',
551
- * tokenFilePath: '/path/to/tokens',
552
- * });
553
- *
554
- * await controller.authenticate();
555
- * await controller.updateAllDeviceData();
556
- * const devices = controller.getDevices();
557
- * ```
558
- *
559
- * @see {@link DaikinControllerConfig} for configuration options
560
- * @see {@link DaikinCloudDevice} for device data structure
561
- */
562
- export class DaikinCloudController extends EventEmitter {
563
- /**
564
- * Creates a new Daikin Cloud Controller
565
- *
566
- * @param config - Controller configuration
567
- * @param logger - Optional logger instance for debugging
568
- * @throws {Error} If configuration is invalid
569
- */
570
- constructor(config: DaikinControllerConfig, logger?: Logger) {
571
- // ...
572
- }
573
-
574
- /**
575
- * Authenticate with Daikin Cloud
576
- *
577
- * Initiates authentication flow based on configured auth mode:
578
- * - Developer Portal: Starts OAuth callback server and returns authorization URL
579
- * - Mobile App: Automatically completes login and token exchange
580
- *
581
- * @returns Promise that resolves when authentication is complete
582
- * @throws {Error} If authentication fails or credentials are invalid
583
- * @emits token_update When tokens are refreshed
584
- * @emits error On authentication failure
585
- *
586
- * @example
587
- * ```typescript
588
- * try {
589
- * await controller.authenticate();
590
- * console.log('Authentication successful');
591
- * } catch (error) {
592
- * console.error('Authentication failed:', error);
593
- * }
594
- * ```
595
- */
596
- async authenticate(): Promise<void> {
597
- // ...
598
- }
599
- }
600
- ```
601
-
602
- **Priority Files for JSDoc:**
603
- 1. [src/api/daikin-controller.ts](src/api/daikin-controller.ts)
604
- 2. [src/api/daikin-api.ts](src/api/daikin-api.ts)
605
- 3. [src/api/daikin-websocket.ts](src/api/daikin-websocket.ts)
606
- 4. [src/platform.ts](src/platform.ts)
607
- 5. [src/accessories/base-accessory.ts](src/accessories/base-accessory.ts)
608
-
609
- #### 3.2 Implement Differential Device Updates
610
-
611
- **Priority**: Low-Medium
612
- **Estimated Effort**: 4-5 hours
613
- **Files to Modify:**
614
- - [src/api/daikin-device.ts](src/api/daikin-device.ts)
615
- - [src/platform.ts](src/platform.ts)
616
-
617
- **Implementation:**
618
-
619
- ```typescript
620
- // In daikin-device.ts
621
- export class DaikinCloudDevice {
622
- private lastUpdate: Date = new Date();
623
- private dataHash: string = '';
624
-
625
- /**
626
- * Check if device data has changed since last update
627
- */
628
- hasChanges(newData: GatewayDevice): boolean {
629
- const newHash = this.computeHash(newData);
630
- return this.dataHash !== newHash;
631
- }
632
-
633
- /**
634
- * Compute hash of device data for change detection
635
- */
636
- private computeHash(data: GatewayDevice): string {
637
- // Use fast-json-stable-stringify or similar
638
- return crypto
639
- .createHash('md5')
640
- .update(JSON.stringify(data))
641
- .digest('hex');
642
- }
643
-
644
- /**
645
- * Update device data and track last update time
646
- */
647
- updateData(updates: Partial<GatewayDevice>): void {
648
- this.deviceData = {...this.deviceData, ...updates};
649
- this.lastUpdate = new Date();
650
- this.dataHash = this.computeHash(this.deviceData);
651
- }
652
-
653
- /**
654
- * Get time since last update in milliseconds
655
- */
656
- getTimeSinceLastUpdate(): number {
657
- return Date.now() - this.lastUpdate.getTime();
658
- }
659
- }
660
- ```
661
-
662
- ```typescript
663
- // In platform.ts
664
- private async updateDevices() {
665
- if (!this.controller) return;
666
-
667
- try {
668
- await this.controller.updateAllDeviceData();
669
-
670
- const devices = this.controller.getDevices();
671
- let changedCount = 0;
672
-
673
- devices.forEach(device => {
674
- if (device.hasChanges(/* new data */)) {
675
- changedCount++;
676
- // Only update accessories for changed devices
677
- this.updateAccessoriesForDevice(device);
678
- }
679
- });
680
-
681
- this.log.debug(
682
- `[API Syncing] Updated ${changedCount}/${devices.length} devices with changes`,
683
- );
684
- } catch (error) {
685
- this.log.error(`[API Syncing] Failed to update devices: ${error}`);
686
- }
687
- }
688
- ```
689
-
690
- #### 3.3 Add Device-Level Error Tracking
691
-
692
- **Priority**: Low
693
- **Estimated Effort**: 3-4 hours
694
- **Files to Modify:**
695
- - [src/api/daikin-device.ts](src/api/daikin-device.ts)
696
- - [src/accessories/base-accessory.ts](src/accessories/base-accessory.ts)
697
-
698
- **Implementation:**
699
-
700
- ```typescript
701
- // In daikin-device.ts
702
- export interface DeviceError {
703
- timestamp: Date;
704
- severity: 'error' | 'warning';
705
- message: string;
706
- operation: string;
707
- retryCount: number;
708
- }
709
-
710
- export class DaikinCloudDevice {
711
- private errors: DeviceError[] = [];
712
- private maxErrors = 10; // Keep last 10 errors
713
-
714
- addError(error: DeviceError): void {
715
- this.errors.unshift(error);
716
- if (this.errors.length > this.maxErrors) {
717
- this.errors.pop();
718
- }
719
- }
720
-
721
- getErrors(): DeviceError[] {
722
- return [...this.errors];
723
- }
724
-
725
- getRecentErrors(since: Date): DeviceError[] {
726
- return this.errors.filter(e => e.timestamp >= since);
727
- }
728
-
729
- clearErrors(): void {
730
- this.errors = [];
731
- }
732
-
733
- hasRecentErrors(): boolean {
734
- const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
735
- return this.getRecentErrors(fiveMinutesAgo).length > 0;
736
- }
737
- }
738
- ```
739
-
740
- ```typescript
741
- // In base-accessory.ts
742
- protected handleError(
743
- operation: string,
744
- error: Error,
745
- retryCount: number = 0,
746
- ): void {
747
- this.accessory.context.device.addError({
748
- timestamp: new Date(),
749
- severity: retryCount >= 3 ? 'error' : 'warning',
750
- message: error.message,
751
- operation,
752
- retryCount,
753
- });
754
-
755
- // Log error with device context
756
- this.platform.log.error(
757
- `[${this.accessory.displayName}] ${operation} failed: ${error.message}`,
758
- );
759
-
760
- // Optionally update a status characteristic
761
- // this.updateErrorStatusCharacteristic();
762
- }
763
- ```
764
-
765
- ---
766
-
767
- ## Testing & Verification
768
-
769
- ### Running Tests
770
-
771
- ```bash
772
- # Run all tests
773
- npm test
774
-
775
- # Run with coverage
776
- npm test -- --coverage
777
-
778
- # Run specific test file
779
- npm test -- test/unit/api/daikin-api.test.ts
780
-
781
- # Watch mode for development
782
- npm test -- --watch
783
- ```
784
-
785
- ### Build & Lint
786
-
787
- ```bash
788
- # Build TypeScript
789
- npm run build
790
-
791
- # Lint code
792
- npm run lint
793
-
794
- # Fix linting issues
795
- npm run lint -- --fix
796
- ```
797
-
798
- ### Pre-Commit Checklist
799
-
800
- - [ ] All tests pass
801
- - [ ] Coverage > 70% for modified files
802
- - [ ] No linting errors
803
- - [ ] Build completes successfully
804
- - [ ] Documentation updated (JSDoc, README)
805
- - [ ] CHANGELOG.md updated
806
-
807
- ---
808
-
809
- ## Performance Benchmarks
810
-
811
- ### Before Improvements
812
- - Test coverage: 62%
813
- - Build time: ~5s
814
- - Unchecked type assertions: 27
815
- - Duplicated code: ~200 lines
816
- - GitHub Actions: Using deprecated v2
817
-
818
- ### After Phase 1
819
- - Test coverage: 62% (unchanged, tests pending)
820
- - Build time: ~5s
821
- - Unchecked type assertions: 2 (explicit `any`)
822
- - Duplicated code: 0 (consolidated)
823
- - GitHub Actions: Using v4
824
- - Validation: Zod schemas added
825
-
826
- ### Target After All Phases
827
- - Test coverage: > 80%
828
- - Build time: < 6s
829
- - Unchecked type assertions: 0
830
- - Duplicated code: 0
831
- - Documentation: 100% public APIs
832
- - Performance: Differential updates reduce CPU usage
833
-
834
- ---
835
-
836
- ## Migration Notes
837
-
838
- ### Breaking Changes
839
- None of the completed improvements introduce breaking changes.
840
-
841
- ### Configuration Changes
842
- No configuration changes required. Zod validation is backward-compatible.
843
-
844
- ### Token File Migration
845
- No migration needed. Token file format unchanged, permissions already secure.
846
-
847
- ---
848
-
849
- ## Troubleshooting Implementation Issues
850
-
851
- ### TypeScript Strict Mode Errors
852
- If strict mode reveals new errors:
853
- 1. Add explicit types instead of using `any`
854
- 2. Use type guards for runtime validation
855
- 3. Use Zod schemas for external data
856
-
857
- ### Zod Version Compatibility
858
- Zod v3.23.8 is compatible with TypeScript 4.4.4. Do not upgrade to Zod v4 without upgrading TypeScript to 5.0+.
859
-
860
- ### Test Failures After Refactoring
861
- If tests fail after consolidating WebSocket logic:
862
- 1. Update test mocks to use UpdateMapper
863
- 2. Verify characteristic mappings match original logic
864
- 3. Check event emission in UpdateMapper tests
865
-
866
- ---
867
-
868
- ## Resources
869
-
870
- - [Zod Documentation](https://zod.dev/)
871
- - [Jest Testing Guide](https://jestjs.io/docs/getting-started)
872
- - [Homebridge Plugin Development](https://developers.homebridge.io/)
873
- - [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)
874
- - [GitHub Actions](https://docs.github.com/en/actions)
875
-
876
- ---
877
-
878
- ## Contributing
879
-
880
- When implementing improvements:
881
- 1. Create feature branch: `feature/improvement-name`
882
- 2. Implement changes with tests
883
- 3. Update documentation
884
- 4. Submit PR with description linking to this guide
885
- 5. Ensure CI passes
886
-
887
- ---
888
-
889
- ## Next Steps
890
-
891
- 1. **Immediate**: Run test suite to verify completed changes
892
- 2. **Short-term**: Implement OAuth integration tests (Phase 1.6)
893
- 3. **Medium-term**: Add error recovery to services (Phase 2.1)
894
- 4. **Long-term**: Complete JSDoc documentation (Phase 3.1)
895
-
896
- ---
897
-
898
- *Last Updated: 2026-01-10*
899
- *Maintained by: Claude Code*