@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.
- package/LICENSE +39 -1
- package/README.md +5 -3
- package/dist/src/accessories/air-conditioning-accessory.d.ts +2 -2
- package/dist/src/accessories/air-conditioning-accessory.d.ts.map +1 -1
- package/dist/src/accessories/air-conditioning-accessory.js.map +1 -1
- package/dist/src/accessories/altherma-accessory.d.ts +2 -2
- package/dist/src/accessories/altherma-accessory.d.ts.map +1 -1
- package/dist/src/accessories/altherma-accessory.js.map +1 -1
- package/dist/src/accessories/base-accessory.d.ts +6 -6
- package/dist/src/accessories/base-accessory.d.ts.map +1 -1
- package/dist/src/accessories/base-accessory.js +15 -15
- package/dist/src/accessories/base-accessory.js.map +1 -1
- package/dist/src/api/daikin-api.d.ts +26 -26
- package/dist/src/api/daikin-api.d.ts.map +1 -1
- package/dist/src/api/daikin-api.js +68 -42
- package/dist/src/api/daikin-api.js.map +1 -1
- package/dist/src/api/daikin-cloud.repository.d.ts.map +1 -1
- package/dist/src/api/daikin-cloud.repository.js +22 -14
- package/dist/src/api/daikin-cloud.repository.js.map +1 -1
- package/dist/src/api/daikin-controller.d.ts +41 -47
- package/dist/src/api/daikin-controller.d.ts.map +1 -1
- package/dist/src/api/daikin-controller.js +40 -64
- package/dist/src/api/daikin-controller.js.map +1 -1
- package/dist/src/api/daikin-device.d.ts +36 -31
- package/dist/src/api/daikin-device.d.ts.map +1 -1
- package/dist/src/api/daikin-device.js +45 -31
- package/dist/src/api/daikin-device.js.map +1 -1
- package/dist/src/api/daikin-mobile-oauth.d.ts +20 -20
- package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
- package/dist/src/api/daikin-mobile-oauth.js +49 -44
- package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
- package/dist/src/api/daikin-oauth.d.ts +32 -32
- package/dist/src/api/daikin-oauth.d.ts.map +1 -1
- package/dist/src/api/daikin-oauth.js +64 -56
- package/dist/src/api/daikin-oauth.js.map +1 -1
- package/dist/src/api/daikin-schemas.d.ts +476 -351
- package/dist/src/api/daikin-schemas.d.ts.map +1 -1
- package/dist/src/api/daikin-schemas.js +11 -42
- package/dist/src/api/daikin-schemas.js.map +1 -1
- package/dist/src/api/daikin-types.d.ts +5 -1
- package/dist/src/api/daikin-types.d.ts.map +1 -1
- package/dist/src/api/daikin-types.js.map +1 -1
- package/dist/src/api/daikin-websocket.d.ts +31 -32
- package/dist/src/api/daikin-websocket.d.ts.map +1 -1
- package/dist/src/api/daikin-websocket.js +55 -35
- package/dist/src/api/daikin-websocket.js.map +1 -1
- package/dist/src/api/index.d.ts +2 -1
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +3 -1
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/token-storage.d.ts +21 -0
- package/dist/src/api/token-storage.d.ts.map +1 -0
- package/dist/src/api/token-storage.js +90 -0
- package/dist/src/api/token-storage.js.map +1 -0
- package/dist/src/config/config-manager.d.ts +33 -33
- package/dist/src/config/config-manager.d.ts.map +1 -1
- package/dist/src/config/config-manager.js +33 -33
- package/dist/src/config/config-manager.js.map +1 -1
- package/dist/src/constants/api.constants.d.ts +4 -0
- package/dist/src/constants/api.constants.d.ts.map +1 -1
- package/dist/src/constants/api.constants.js +5 -1
- package/dist/src/constants/api.constants.js.map +1 -1
- package/dist/src/constants/device.constants.d.ts +4 -0
- package/dist/src/constants/device.constants.d.ts.map +1 -1
- package/dist/src/constants/device.constants.js +5 -1
- package/dist/src/constants/device.constants.js.map +1 -1
- package/dist/src/device/accessory-factory.d.ts +10 -10
- package/dist/src/device/accessory-factory.d.ts.map +1 -1
- package/dist/src/device/accessory-factory.js +7 -7
- package/dist/src/device/accessory-factory.js.map +1 -1
- package/dist/src/device/capability-detector.d.ts +8 -8
- package/dist/src/device/capability-detector.d.ts.map +1 -1
- package/dist/src/device/capability-detector.js +6 -6
- package/dist/src/device/capability-detector.js.map +1 -1
- package/dist/src/device/capability-docs.d.ts +1 -9
- package/dist/src/device/capability-docs.d.ts.map +1 -1
- package/dist/src/device/capability-docs.js +19 -73
- package/dist/src/device/capability-docs.js.map +1 -1
- package/dist/src/device/profiles/device-profile.d.ts +1 -1
- package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
- package/dist/src/device/profiles/device-profile.js +4 -4
- package/dist/src/device/profiles/device-profile.js.map +1 -1
- package/dist/src/features/base-feature.d.ts +2 -2
- package/dist/src/features/base-feature.d.ts.map +1 -1
- package/dist/src/features/base-feature.js +2 -3
- package/dist/src/features/base-feature.js.map +1 -1
- package/dist/src/features/feature-manager.d.ts +8 -16
- package/dist/src/features/feature-manager.d.ts.map +1 -1
- package/dist/src/features/feature-manager.js +5 -17
- package/dist/src/features/feature-manager.js.map +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
- package/dist/src/features/modes/econo-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
- package/dist/src/features/modes/powerful-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
- package/dist/src/features/modes/streamer-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/platform.d.ts +11 -8
- package/dist/src/platform.d.ts.map +1 -1
- package/dist/src/platform.js +64 -15
- package/dist/src/platform.js.map +1 -1
- package/dist/src/services/climate-control.service.d.ts +8 -2
- package/dist/src/services/climate-control.service.d.ts.map +1 -1
- package/dist/src/services/climate-control.service.js +59 -58
- package/dist/src/services/climate-control.service.js.map +1 -1
- package/dist/src/services/hot-water-tank.service.d.ts +6 -2
- package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
- package/dist/src/services/hot-water-tank.service.js +33 -31
- package/dist/src/services/hot-water-tank.service.js.map +1 -1
- package/dist/src/types/daikin-enums.js +12 -12
- package/dist/src/types/daikin-enums.js.map +1 -1
- package/dist/src/types/device-capabilities.d.ts +1 -1
- package/dist/src/types/device-capabilities.d.ts.map +1 -1
- package/dist/src/utils/log-context.d.ts +23 -23
- package/dist/src/utils/log-context.d.ts.map +1 -1
- package/dist/src/utils/log-context.js +28 -28
- package/dist/src/utils/log-context.js.map +1 -1
- package/dist/src/utils/strings.d.ts.map +1 -1
- package/dist/src/utils/strings.js.map +1 -1
- package/dist/src/utils/update-mapper.d.ts +16 -16
- package/dist/src/utils/update-mapper.d.ts.map +1 -1
- package/dist/src/utils/update-mapper.js +14 -14
- package/dist/src/utils/update-mapper.js.map +1 -1
- package/homebridge-ui/public/index.html +2 -2
- package/homebridge-ui/public/script.js +957 -898
- package/homebridge-ui/server.js +746 -678
- package/package.json +29 -27
- package/.claude/settings.json +0 -3
- package/.claude/settings.local.json +0 -29
- package/CHANGELOG.md +0 -103
- package/CLAUDE.md +0 -269
- package/config.md +0 -2
- package/dist/src/api/daikin-device-tracker.d.ts +0 -97
- package/dist/src/api/daikin-device-tracker.d.ts.map +0 -1
- package/dist/src/api/daikin-device-tracker.js +0 -136
- package/dist/src/api/daikin-device-tracker.js.map +0 -1
- package/dist/src/api/http-interceptor.d.ts +0 -99
- package/dist/src/api/http-interceptor.d.ts.map +0 -1
- package/dist/src/api/http-interceptor.js +0 -177
- package/dist/src/api/http-interceptor.js.map +0 -1
- package/dist/src/di/service-container.d.ts +0 -92
- package/dist/src/di/service-container.d.ts.map +0 -1
- package/dist/src/di/service-container.js +0 -156
- package/dist/src/di/service-container.js.map +0 -1
- package/dist/src/features/feature-registry.d.ts +0 -100
- package/dist/src/features/feature-registry.d.ts.map +0 -1
- package/dist/src/features/feature-registry.js +0 -142
- package/dist/src/features/feature-registry.js.map +0 -1
- package/dist/src/services/service-factory.d.ts +0 -46
- package/dist/src/services/service-factory.d.ts.map +0 -1
- package/dist/src/services/service-factory.js +0 -72
- package/dist/src/services/service-factory.js.map +0 -1
- package/dist/src/utils/error-handler.d.ts +0 -101
- package/dist/src/utils/error-handler.d.ts.map +0 -1
- package/dist/src/utils/error-handler.js +0 -251
- package/dist/src/utils/error-handler.js.map +0 -1
- package/dist/src/utils/retry.d.ts +0 -42
- package/dist/src/utils/retry.d.ts.map +0 -1
- package/dist/src/utils/retry.js +0 -70
- package/dist/src/utils/retry.js.map +0 -1
- package/docs/ARCHITECTURE.md +0 -645
- package/docs/IMPLEMENTATION_GUIDE.md +0 -899
- package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
- package/docs/NEXT_STEPS.md +0 -368
- package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
- package/docs/TROUBLESHOOTING.md +0 -475
- package/docs/api-response-for-BRP069A8x.json +0 -520
- package/docs/api-response-for-BRP069C4x-2.json +0 -881
- package/docs/api-response-for-BRP069C4x.json +0 -916
- package/docs/api-response-for-altherma.json +0 -759
- package/docs/api-response-for-altherma2.json +0 -2735
- package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
- package/docs/cr-insance-altherma-id-0.json +0 -834
- package/docs/mock-air-to-air-dx23.json +0 -759
- package/docs/mock-air-to-air-dx4.json +0 -1134
- package/docs/mock-airpurifier-with-humidifier.json +0 -732
- package/docs/mock-airpurifier.json +0 -450
- package/docs/mock-altherma-air-to-water-lan.json +0 -845
- package/docs/mock-altherma-air-to-water-wlan.json +0 -845
- package/docs/mock-d2cnd-gas-boiler.json +0 -649
- package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
- package/images/fan-speed.jpeg +0 -0
- package/images/homekit-controls.jpeg +0 -0
- package/images/homekit-settings.jpeg +0 -0
- package/images/swing-mode.png +0 -0
- package/jest.config.ts +0 -13
- package/test/fixtures/altherma-crSense-2.ts +0 -834
- package/test/fixtures/altherma-fraction.ts +0 -718
- package/test/fixtures/altherma-heat-pump-2.ts +0 -479
- package/test/fixtures/altherma-heat-pump.ts +0 -757
- package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
- package/test/fixtures/altherma-miladcerkic.ts +0 -524
- package/test/fixtures/altherma-v1ckoeln.ts +0 -644
- package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
- package/test/fixtures/dx23-airco-2.ts +0 -343
- package/test/fixtures/dx23-airco.ts +0 -518
- package/test/fixtures/dx4-airco.ts +0 -914
- package/test/fixtures/unknown-jan.ts +0 -488
- package/test/fixtures/unknown-kitchen-guests.ts +0 -488
- package/test/helpers/test-isolation.ts +0 -228
- package/test/integration/air-conditioning.test.ts +0 -410
- package/test/integration/altherma.test.ts +0 -289
- package/test/integration/platform.test.ts +0 -118
- package/test/mocks/index.ts +0 -27
- package/test/test-gigya-auth.js +0 -443
- package/test/test-mobile-oauth.js +0 -175
- package/test/test-websocket-mobile.js +0 -123
- package/test/test-websocket.js +0 -116
- package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
- package/test/unit/api/daikin-api.test.ts +0 -384
- package/test/unit/api/daikin-oauth.test.ts +0 -214
- package/test/unit/api/daikinCloud.test.ts +0 -12
- package/test/unit/config/config-manager.test.ts +0 -271
- package/test/unit/device/daikin-device.test.ts +0 -79
- package/test/unit/services/hot-water-tank.service.test.ts +0 -123
- package/test/unit/utils/error-handler.test.ts +0 -274
- package/test/unit/utils/log-context.test.ts +0 -271
package/test/test-gigya-auth.js
DELETED
|
@@ -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);
|