tsvesync 1.0.102 → 1.0.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -1
- package/dist/lib/constants.d.ts +14 -0
- package/dist/lib/constants.js +35 -0
- package/dist/lib/helpers.d.ts +10 -2
- package/dist/lib/helpers.js +81 -50
- package/dist/lib/vesync.d.ts +2 -0
- package/dist/lib/vesync.js +73 -10
- package/package.json +1 -1
- package/src/index.ts +9 -1
- package/src/lib/constants.ts +35 -0
- package/src/lib/helpers.ts +85 -58
- package/src/lib/vesync.ts +83 -11
package/README.md
CHANGED
|
@@ -6,8 +6,10 @@ A TypeScript library for interacting with VeSync smart home devices. This librar
|
|
|
6
6
|
|
|
7
7
|
- **Enhanced Authentication**: Now supports the new VeSync authentication flow (pyvesync PR #340) with automatic fallback to legacy authentication
|
|
8
8
|
- **Regional API Support**: Automatic detection and routing for US (`smartapi.vesync.com`) and EU (`smartapi.vesync.eu`) endpoints
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
9
|
+
- **International Account Support**: Full support for accounts worldwide including Australia, New Zealand, Japan, and all European countries
|
|
10
|
+
- **Country Code Configuration**: Specify your country code for proper authentication with international accounts
|
|
11
|
+
- **Improved Error Handling**: Better recovery from API changes and authentication errors with helpful guidance
|
|
12
|
+
- **Cross-Region Support**: Automatic handling of cross-region authentication errors
|
|
11
13
|
|
|
12
14
|
## Features
|
|
13
15
|
|
|
@@ -176,6 +178,68 @@ await manager.login();
|
|
|
176
178
|
await manager.getDevices();
|
|
177
179
|
```
|
|
178
180
|
|
|
181
|
+
### International Account Support
|
|
182
|
+
|
|
183
|
+
For accounts created outside the United States, you may need to specify your country code:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { VeSync } from 'tsvesync';
|
|
187
|
+
|
|
188
|
+
// For Australian users
|
|
189
|
+
const manager = new VeSync(username, password, 'America/Sydney', {
|
|
190
|
+
countryCode: 'AU'
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// For German users
|
|
194
|
+
const manager = new VeSync(username, password, 'Europe/Berlin', {
|
|
195
|
+
countryCode: 'DE'
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// For UK users
|
|
199
|
+
const manager = new VeSync(username, password, 'Europe/London', {
|
|
200
|
+
countryCode: 'GB'
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await manager.login();
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Supported Country Codes
|
|
207
|
+
|
|
208
|
+
The library supports all country codes. Common ones include:
|
|
209
|
+
|
|
210
|
+
**Americas:**
|
|
211
|
+
- `US` - United States (default)
|
|
212
|
+
- `CA` - Canada
|
|
213
|
+
- `MX` - Mexico
|
|
214
|
+
|
|
215
|
+
**Europe:** (uses EU endpoint)
|
|
216
|
+
- `GB` - United Kingdom
|
|
217
|
+
- `DE` - Germany
|
|
218
|
+
- `FR` - France
|
|
219
|
+
- `IT` - Italy
|
|
220
|
+
- `ES` - Spain
|
|
221
|
+
- `NL` - Netherlands
|
|
222
|
+
- `SE` - Sweden
|
|
223
|
+
- `NO` - Norway
|
|
224
|
+
- `DK` - Denmark
|
|
225
|
+
- `FI` - Finland
|
|
226
|
+
- `PL` - Poland
|
|
227
|
+
- And all other European countries
|
|
228
|
+
|
|
229
|
+
**Asia-Pacific:** (uses US endpoint)
|
|
230
|
+
- `AU` - Australia
|
|
231
|
+
- `NZ` - New Zealand
|
|
232
|
+
- `JP` - Japan
|
|
233
|
+
- `SG` - Singapore
|
|
234
|
+
- `CN` - China
|
|
235
|
+
- `KR` - South Korea
|
|
236
|
+
|
|
237
|
+
**Important Notes:**
|
|
238
|
+
- If you get an authentication error about "country code mismatch" or "cross-region error", you need to set your country code
|
|
239
|
+
- The country code should match the country where your VeSync account was created
|
|
240
|
+
- European countries automatically use the EU endpoint (`smartapi.vesync.eu`)
|
|
241
|
+
- All other countries use the US endpoint (`smartapi.vesync.com`)
|
|
242
|
+
|
|
179
243
|
### Working with Devices
|
|
180
244
|
|
|
181
245
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export { VeSyncBulb } from './lib/vesyncBulb';
|
|
|
5
5
|
export { VeSyncWallSwitch } from './lib/vesyncSwitchImpl';
|
|
6
6
|
export { VeSyncBaseDevice } from './lib/vesyncBaseDevice';
|
|
7
7
|
export { Helpers, setApiBaseUrl } from './lib/helpers';
|
|
8
|
+
export { CREDENTIAL_ERROR_CODES, CROSS_REGION_ERROR_CODES, isCredentialError, isCrossRegionError } from './lib/constants';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setApiBaseUrl = exports.Helpers = exports.VeSyncBaseDevice = exports.VeSyncWallSwitch = exports.VeSyncBulb = exports.VeSyncOutdoorPlug = exports.VeSyncOutlet15A = exports.VeSyncOutlet10A = exports.VeSyncOutlet7A = exports.VeSyncAirBypass = exports.VeSync = void 0;
|
|
3
|
+
exports.isCrossRegionError = exports.isCredentialError = exports.CROSS_REGION_ERROR_CODES = exports.CREDENTIAL_ERROR_CODES = exports.setApiBaseUrl = exports.Helpers = exports.VeSyncBaseDevice = exports.VeSyncWallSwitch = exports.VeSyncBulb = exports.VeSyncOutdoorPlug = exports.VeSyncOutlet15A = exports.VeSyncOutlet10A = exports.VeSyncOutlet7A = exports.VeSyncAirBypass = exports.VeSync = void 0;
|
|
4
4
|
// Export main VeSync manager class
|
|
5
5
|
var vesync_1 = require("./lib/vesync");
|
|
6
6
|
Object.defineProperty(exports, "VeSync", { enumerable: true, get: function () { return vesync_1.VeSync; } });
|
|
@@ -23,3 +23,9 @@ Object.defineProperty(exports, "VeSyncBaseDevice", { enumerable: true, get: func
|
|
|
23
23
|
var helpers_1 = require("./lib/helpers");
|
|
24
24
|
Object.defineProperty(exports, "Helpers", { enumerable: true, get: function () { return helpers_1.Helpers; } });
|
|
25
25
|
Object.defineProperty(exports, "setApiBaseUrl", { enumerable: true, get: function () { return helpers_1.setApiBaseUrl; } });
|
|
26
|
+
// Export constants
|
|
27
|
+
var constants_1 = require("./lib/constants");
|
|
28
|
+
Object.defineProperty(exports, "CREDENTIAL_ERROR_CODES", { enumerable: true, get: function () { return constants_1.CREDENTIAL_ERROR_CODES; } });
|
|
29
|
+
Object.defineProperty(exports, "CROSS_REGION_ERROR_CODES", { enumerable: true, get: function () { return constants_1.CROSS_REGION_ERROR_CODES; } });
|
|
30
|
+
Object.defineProperty(exports, "isCredentialError", { enumerable: true, get: function () { return constants_1.isCredentialError; } });
|
|
31
|
+
Object.defineProperty(exports, "isCrossRegionError", { enumerable: true, get: function () { return constants_1.isCrossRegionError; } });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VeSync API Error Codes
|
|
3
|
+
*/
|
|
4
|
+
export declare const CREDENTIAL_ERROR_CODES: number[];
|
|
5
|
+
export declare const CROSS_REGION_ERROR_CODES: number[];
|
|
6
|
+
export declare const APP_VERSION_ERROR_CODES: number[];
|
|
7
|
+
/**
|
|
8
|
+
* Check if an error code indicates bad credentials
|
|
9
|
+
*/
|
|
10
|
+
export declare function isCredentialError(code: number): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Check if an error code indicates cross-region issue
|
|
13
|
+
*/
|
|
14
|
+
export declare function isCrossRegionError(code: number): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VeSync API Error Codes
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.APP_VERSION_ERROR_CODES = exports.CROSS_REGION_ERROR_CODES = exports.CREDENTIAL_ERROR_CODES = void 0;
|
|
7
|
+
exports.isCredentialError = isCredentialError;
|
|
8
|
+
exports.isCrossRegionError = isCrossRegionError;
|
|
9
|
+
// Authentication error codes that indicate bad credentials (no point retrying)
|
|
10
|
+
exports.CREDENTIAL_ERROR_CODES = [
|
|
11
|
+
-11201129, // account or password incorrect
|
|
12
|
+
-11202129, // the account does not exist
|
|
13
|
+
-11000129, // illegal argument (empty credentials)
|
|
14
|
+
];
|
|
15
|
+
// Cross-region error codes that require region switching
|
|
16
|
+
exports.CROSS_REGION_ERROR_CODES = [
|
|
17
|
+
-11260022, // cross region error
|
|
18
|
+
-11261022, // access region conflict error
|
|
19
|
+
];
|
|
20
|
+
// Other error codes
|
|
21
|
+
exports.APP_VERSION_ERROR_CODES = [
|
|
22
|
+
-11012022, // app version is too low
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Check if an error code indicates bad credentials
|
|
26
|
+
*/
|
|
27
|
+
function isCredentialError(code) {
|
|
28
|
+
return exports.CREDENTIAL_ERROR_CODES.includes(code);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if an error code indicates cross-region issue
|
|
32
|
+
*/
|
|
33
|
+
function isCrossRegionError(code) {
|
|
34
|
+
return exports.CROSS_REGION_ERROR_CODES.includes(code);
|
|
35
|
+
}
|
package/dist/lib/helpers.d.ts
CHANGED
|
@@ -26,7 +26,6 @@ export declare const REGION_ENDPOINTS: {
|
|
|
26
26
|
JP: string;
|
|
27
27
|
EU: string;
|
|
28
28
|
};
|
|
29
|
-
export declare const CROSS_REGION_ERROR_CODES: number[];
|
|
30
29
|
export declare const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
31
30
|
export declare const AUTH_ERROR_CODES: {
|
|
32
31
|
ACCOUNT_PASSWORD_INCORRECT: number;
|
|
@@ -34,6 +33,15 @@ export declare const AUTH_ERROR_CODES: {
|
|
|
34
33
|
CROSS_REGION: number;
|
|
35
34
|
CROSS_REGION_ALT: number;
|
|
36
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Get list of country codes that use the US endpoint
|
|
38
|
+
* These are tried when we get a cross-region error
|
|
39
|
+
*/
|
|
40
|
+
export declare function getUSEndpointCountryCodes(): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Get the appropriate API endpoint based on country code
|
|
43
|
+
*/
|
|
44
|
+
export declare function getEndpointForCountryCode(countryCode: string): 'US' | 'EU';
|
|
37
45
|
/**
|
|
38
46
|
* Detect user's home region from email domain or country hints
|
|
39
47
|
*/
|
|
@@ -159,7 +167,7 @@ export declare class Helpers {
|
|
|
159
167
|
/**
|
|
160
168
|
* Perform new two-step authentication flow
|
|
161
169
|
*/
|
|
162
|
-
static authNewFlow(manager: VeSync, appId: string, region?: string): Promise<[boolean, string | null, string | null, string | null]>;
|
|
170
|
+
static authNewFlow(manager: VeSync, appId: string, region?: string, countryCodeOverride?: string): Promise<[boolean, string | null, string | null, string | null]>;
|
|
163
171
|
/**
|
|
164
172
|
* Perform legacy authentication (fallback)
|
|
165
173
|
*/
|
package/dist/lib/helpers.js
CHANGED
|
@@ -6,12 +6,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
7
|
};
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.Helpers = exports.AUTH_ERROR_CODES = exports.APP_VERSION_TOO_LOW_ERROR_CODE = exports.
|
|
9
|
+
exports.Helpers = exports.AUTH_ERROR_CODES = exports.APP_VERSION_TOO_LOW_ERROR_CODE = exports.REGION_ENDPOINTS = exports.CLIENT_VERSION = exports.BYPASS_HEADER_UA = exports.MOBILE_ID = exports.DEFAULT_REGION = exports.DEFAULT_TZ = exports.USER_TYPE = exports.CLIENT_INFO = exports.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = void 0;
|
|
10
10
|
exports.getApiBaseUrl = getApiBaseUrl;
|
|
11
11
|
exports.setApiBaseUrl = setApiBaseUrl;
|
|
12
12
|
exports.getCurrentRegion = getCurrentRegion;
|
|
13
13
|
exports.setCurrentRegion = setCurrentRegion;
|
|
14
14
|
exports.getRegionFromCountryCode = getRegionFromCountryCode;
|
|
15
|
+
exports.getUSEndpointCountryCodes = getUSEndpointCountryCodes;
|
|
16
|
+
exports.getEndpointForCountryCode = getEndpointForCountryCode;
|
|
15
17
|
exports.detectUserRegion = detectUserRegion;
|
|
16
18
|
exports.getCountryCodeFromRegion = getCountryCodeFromRegion;
|
|
17
19
|
exports.generateAppId = generateAppId;
|
|
@@ -19,6 +21,7 @@ exports.generateTerminalId = generateTerminalId;
|
|
|
19
21
|
const axios_1 = __importDefault(require("axios"));
|
|
20
22
|
const crypto_1 = __importDefault(require("crypto"));
|
|
21
23
|
const logger_1 = require("./logger");
|
|
24
|
+
const constants_1 = require("./constants");
|
|
22
25
|
// API configuration - Support regional endpoints
|
|
23
26
|
let _apiBaseUrl = 'https://smartapi.vesync.com';
|
|
24
27
|
let _currentRegion = 'US';
|
|
@@ -72,7 +75,7 @@ exports.REGION_ENDPOINTS = {
|
|
|
72
75
|
'EU': 'https://smartapi.vesync.eu'
|
|
73
76
|
};
|
|
74
77
|
// Cross-region error codes - multiple versions exist
|
|
75
|
-
|
|
78
|
+
// CROSS_REGION_ERROR_CODES moved to constants.ts
|
|
76
79
|
exports.APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
77
80
|
// Authentication error codes
|
|
78
81
|
exports.AUTH_ERROR_CODES = {
|
|
@@ -81,6 +84,38 @@ exports.AUTH_ERROR_CODES = {
|
|
|
81
84
|
CROSS_REGION: -11260022,
|
|
82
85
|
CROSS_REGION_ALT: -11261022
|
|
83
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Get list of country codes that use the US endpoint
|
|
89
|
+
* These are tried when we get a cross-region error
|
|
90
|
+
*/
|
|
91
|
+
function getUSEndpointCountryCodes() {
|
|
92
|
+
// Countries that use the US endpoint but might need their own country code
|
|
93
|
+
return ['US', 'AU', 'NZ', 'JP', 'CA', 'MX', 'SG'];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the appropriate API endpoint based on country code
|
|
97
|
+
*/
|
|
98
|
+
function getEndpointForCountryCode(countryCode) {
|
|
99
|
+
// European country codes that should use the EU endpoint
|
|
100
|
+
const euCountryCodes = [
|
|
101
|
+
// Western Europe
|
|
102
|
+
'GB', 'DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'AT', 'CH', 'IE', 'PT', 'LU',
|
|
103
|
+
// Nordic countries
|
|
104
|
+
'SE', 'NO', 'DK', 'FI', 'IS',
|
|
105
|
+
// Eastern Europe
|
|
106
|
+
'PL', 'CZ', 'HU', 'RO', 'BG', 'HR', 'SI', 'SK', 'LT', 'LV', 'EE',
|
|
107
|
+
// Southern Europe
|
|
108
|
+
'GR', 'CY', 'MT',
|
|
109
|
+
// Other European
|
|
110
|
+
'AL', 'BA', 'RS', 'ME', 'MK', 'MD', 'UA', 'BY'
|
|
111
|
+
];
|
|
112
|
+
// Check if country code is in EU list
|
|
113
|
+
if (euCountryCodes.includes(countryCode)) {
|
|
114
|
+
return 'EU';
|
|
115
|
+
}
|
|
116
|
+
// Everything else uses US endpoint (including AU, NZ, JP, CA, MX, etc.)
|
|
117
|
+
return 'US';
|
|
118
|
+
}
|
|
84
119
|
/**
|
|
85
120
|
* Detect user's home region from email domain or country hints
|
|
86
121
|
*/
|
|
@@ -455,7 +490,7 @@ class Helpers {
|
|
|
455
490
|
/**
|
|
456
491
|
* Perform new two-step authentication flow
|
|
457
492
|
*/
|
|
458
|
-
static async authNewFlow(manager, appId, region = 'US') {
|
|
493
|
+
static async authNewFlow(manager, appId, region = 'US', countryCodeOverride) {
|
|
459
494
|
try {
|
|
460
495
|
// Generate terminal ID to be used in both steps
|
|
461
496
|
const terminalId = generateTerminalId();
|
|
@@ -480,8 +515,14 @@ class Helpers {
|
|
|
480
515
|
}
|
|
481
516
|
if (authResponse.code !== 0) {
|
|
482
517
|
logger_1.logger.error('Step 1 error code:', authResponse.code, 'message:', authResponse.msg);
|
|
518
|
+
// Check if it's a credential error (no point retrying)
|
|
519
|
+
if ((0, constants_1.isCredentialError)(authResponse.code)) {
|
|
520
|
+
logger_1.logger.error('Credential error detected - invalid username or password');
|
|
521
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
522
|
+
return [false, null, null, 'credential_error'];
|
|
523
|
+
}
|
|
483
524
|
// Handle cross-region error (multiple error codes possible)
|
|
484
|
-
if (
|
|
525
|
+
if ((0, constants_1.isCrossRegionError)(authResponse.code)) {
|
|
485
526
|
logger_1.logger.debug('Cross-region error detected:', authResponse.code, 'will try different region');
|
|
486
527
|
setApiBaseUrl(originalUrl); // Restore original URL
|
|
487
528
|
return [false, null, null, 'cross_region'];
|
|
@@ -497,61 +538,46 @@ class Helpers {
|
|
|
497
538
|
}
|
|
498
539
|
logger_1.logger.debug('Step 1 successful, got authorization code');
|
|
499
540
|
// Step 2: Login with authorization code
|
|
500
|
-
// Use the
|
|
501
|
-
|
|
541
|
+
// Use the override if provided, otherwise default to 'US'
|
|
542
|
+
// Users should specify their actual country code in the configuration
|
|
543
|
+
const countryCodeForStep2 = countryCodeOverride || 'US';
|
|
502
544
|
const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, countryCodeForStep2);
|
|
503
545
|
logger_1.logger.debug('Step 2: Logging in with authorization code...');
|
|
504
546
|
const [loginResponse, loginStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', step2Body, step1Headers, manager);
|
|
547
|
+
logger_1.logger.debug('Step 2 response:', {
|
|
548
|
+
status: loginStatus,
|
|
549
|
+
code: loginResponse === null || loginResponse === void 0 ? void 0 : loginResponse.code,
|
|
550
|
+
hasResult: !!(loginResponse === null || loginResponse === void 0 ? void 0 : loginResponse.result),
|
|
551
|
+
msg: loginResponse === null || loginResponse === void 0 ? void 0 : loginResponse.msg
|
|
552
|
+
});
|
|
505
553
|
if (!loginResponse || loginStatus !== 200) {
|
|
506
554
|
logger_1.logger.error('Step 2 failed:', loginResponse);
|
|
507
555
|
setApiBaseUrl(originalUrl); // Restore original URL on failure
|
|
508
556
|
return [false, null, null, null];
|
|
509
557
|
}
|
|
510
558
|
if (loginResponse.code !== 0) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
regionChange: 'last_region'
|
|
534
|
-
};
|
|
535
|
-
logger_1.logger.debug('Retrying Step 2 with region change token...', {
|
|
536
|
-
newCountryCode,
|
|
537
|
-
currentEndpoint: getApiBaseUrl()
|
|
538
|
-
});
|
|
539
|
-
const [retryResponse, retryStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', retryBody, step1Headers, manager);
|
|
540
|
-
if (retryResponse && retryResponse.code === 0) {
|
|
541
|
-
const { token, accountID, countryCode } = retryResponse.result || {};
|
|
542
|
-
if (token && accountID) {
|
|
543
|
-
logger_1.logger.debug('Step 2 retry successful with region change');
|
|
544
|
-
setApiBaseUrl(originalUrl); // Restore original URL on success
|
|
545
|
-
return [true, token, accountID, countryCode || newCountryCode];
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
logger_1.logger.error('Step 2 retry failed:', retryResponse);
|
|
549
|
-
setApiBaseUrl(originalUrl); // Restore original URL on failure
|
|
550
|
-
return [false, null, null, null];
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
logger_1.logger.error('No region change token in cross-region error response');
|
|
554
|
-
}
|
|
559
|
+
logger_1.logger.debug('Step 2 error, code:', loginResponse.code, 'checking if cross-region...');
|
|
560
|
+
// Handle cross-region error - for EU accounts, we need to switch regions entirely
|
|
561
|
+
if (constants_1.CROSS_REGION_ERROR_CODES.includes(loginResponse.code)) {
|
|
562
|
+
// Log helpful message about country code mismatch
|
|
563
|
+
logger_1.logger.warn('═══════════════════════════════════════════════════════════════');
|
|
564
|
+
logger_1.logger.warn('COUNTRY CODE MISMATCH DETECTED');
|
|
565
|
+
logger_1.logger.warn(`Your account expects a different country code than '${countryCodeForStep2}'`);
|
|
566
|
+
logger_1.logger.warn('This usually means:');
|
|
567
|
+
logger_1.logger.warn(' 1. You need to set the correct country code in your config');
|
|
568
|
+
logger_1.logger.warn(' 2. Your account was created in a different country/region');
|
|
569
|
+
logger_1.logger.warn('');
|
|
570
|
+
logger_1.logger.warn('To fix this:');
|
|
571
|
+
logger_1.logger.warn(' - In Homebridge UI: Select your correct country from the dropdown');
|
|
572
|
+
logger_1.logger.warn(' - In config.json: Add "countryCode": "YOUR_COUNTRY_CODE"');
|
|
573
|
+
logger_1.logger.warn('');
|
|
574
|
+
logger_1.logger.warn('Common country codes: US, CA, GB, DE, FR, AU, NZ, JP');
|
|
575
|
+
logger_1.logger.warn('═══════════════════════════════════════════════════════════════');
|
|
576
|
+
logger_1.logger.debug('Cross-region error detected in Step 2 - will try different endpoint');
|
|
577
|
+
// For cross-region errors, we need to retry the entire flow with a different region
|
|
578
|
+
// The authorization code from Step 1 is tied to the endpoint it was obtained from
|
|
579
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
580
|
+
return [false, null, null, 'cross_region_retry'];
|
|
555
581
|
}
|
|
556
582
|
logger_1.logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
|
|
557
583
|
setApiBaseUrl(originalUrl); // Restore original URL on error
|
|
@@ -588,6 +614,11 @@ class Helpers {
|
|
|
588
614
|
}
|
|
589
615
|
if (response.code && response.code !== 0) {
|
|
590
616
|
logger_1.logger.error('Legacy login error code:', response.code, 'message:', response.msg);
|
|
617
|
+
// Check if it's a credential error
|
|
618
|
+
if ((0, constants_1.isCredentialError)(response.code)) {
|
|
619
|
+
logger_1.logger.error('Legacy auth: Credential error detected');
|
|
620
|
+
return [false, null, null, 'credential_error'];
|
|
621
|
+
}
|
|
591
622
|
return [false, null, null, null];
|
|
592
623
|
}
|
|
593
624
|
const { token, accountID, countryCode } = response.result || {};
|
package/dist/lib/vesync.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare class VeSync {
|
|
|
27
27
|
private _excludeConfig;
|
|
28
28
|
private _appId;
|
|
29
29
|
private _region;
|
|
30
|
+
private _countryCodeOverride;
|
|
30
31
|
username: string;
|
|
31
32
|
password: string;
|
|
32
33
|
token: string | null;
|
|
@@ -61,6 +62,7 @@ export declare class VeSync {
|
|
|
61
62
|
customLogger?: Logger;
|
|
62
63
|
excludeConfig?: ExcludeConfig;
|
|
63
64
|
region?: string;
|
|
65
|
+
countryCode?: string;
|
|
64
66
|
debugMode?: boolean;
|
|
65
67
|
}, redact?: boolean, apiUrl?: string, customLogger?: Logger, excludeConfig?: ExcludeConfig);
|
|
66
68
|
/**
|
package/dist/lib/vesync.js
CHANGED
|
@@ -141,6 +141,7 @@ class VeSync {
|
|
|
141
141
|
this._redact = options.redact !== undefined ? options.redact : true;
|
|
142
142
|
this._excludeConfig = options.excludeConfig || null;
|
|
143
143
|
this._region = options.region || 'US';
|
|
144
|
+
this._countryCodeOverride = options.countryCode || null;
|
|
144
145
|
customLogger = options.customLogger;
|
|
145
146
|
apiUrl = options.apiUrl;
|
|
146
147
|
}
|
|
@@ -150,6 +151,7 @@ class VeSync {
|
|
|
150
151
|
this._redact = redact;
|
|
151
152
|
this._excludeConfig = excludeConfig || null;
|
|
152
153
|
this._region = 'US';
|
|
154
|
+
this._countryCodeOverride = null;
|
|
153
155
|
}
|
|
154
156
|
this._energyUpdateInterval = DEFAULT_ENERGY_UPDATE_INTERVAL;
|
|
155
157
|
this._energyCheck = true;
|
|
@@ -195,6 +197,12 @@ class VeSync {
|
|
|
195
197
|
(0, helpers_1.setApiBaseUrl)(apiUrl);
|
|
196
198
|
}
|
|
197
199
|
else {
|
|
200
|
+
// If country code is provided, use it to determine the endpoint
|
|
201
|
+
if (this._countryCodeOverride) {
|
|
202
|
+
const endpointRegion = (0, helpers_1.getEndpointForCountryCode)(this._countryCodeOverride);
|
|
203
|
+
this._region = endpointRegion;
|
|
204
|
+
logger_1.logger.debug(`Country code ${this._countryCodeOverride} maps to ${endpointRegion} endpoint`);
|
|
205
|
+
}
|
|
198
206
|
// Set endpoint based on region
|
|
199
207
|
const regionEndpoints = {
|
|
200
208
|
'US': 'https://smartapi.vesync.com',
|
|
@@ -458,32 +466,82 @@ class VeSync {
|
|
|
458
466
|
appId: this._appId,
|
|
459
467
|
region: this._region
|
|
460
468
|
});
|
|
469
|
+
// Track which regions have been attempted to prevent infinite loops
|
|
470
|
+
const triedRegions = new Set();
|
|
461
471
|
for (let attempt = 0; attempt < retryAttempts; attempt++) {
|
|
462
472
|
try {
|
|
463
473
|
logger_1.logger.debug(`Authentication attempt ${attempt + 1} of ${retryAttempts}`);
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
if (detectedRegion !== this._region) {
|
|
467
|
-
logger_1.logger.debug(`Adjusting region from ${this._region} to ${detectedRegion} based on email domain`);
|
|
468
|
-
this._region = detectedRegion;
|
|
469
|
-
(0, helpers_1.setCurrentRegion)(detectedRegion);
|
|
470
|
-
}
|
|
474
|
+
// Skip email-based region detection for now since we'll handle cross-region errors
|
|
475
|
+
// The cross-region error handling below will automatically switch to the correct region
|
|
471
476
|
// Try new authentication flow first with detected region
|
|
472
|
-
let [success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, this._region);
|
|
477
|
+
let [success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, this._region, this._countryCodeOverride || undefined);
|
|
473
478
|
// Track if new flow succeeded
|
|
474
479
|
if (success) {
|
|
475
480
|
this.authFlowUsed = 'new';
|
|
476
481
|
}
|
|
477
482
|
if (!success) {
|
|
483
|
+
// Check if it's a credential error (no point retrying)
|
|
484
|
+
if (countryCode === 'credential_error') {
|
|
485
|
+
logger_1.logger.error('Authentication failed due to invalid credentials');
|
|
486
|
+
return false; // Exit immediately, don't retry
|
|
487
|
+
}
|
|
478
488
|
// Check if we need to try a different region
|
|
479
|
-
if (countryCode === 'cross_region') {
|
|
489
|
+
if (countryCode === 'cross_region' || countryCode === 'cross_region_retry') {
|
|
490
|
+
// Mark current region as tried
|
|
491
|
+
triedRegions.add(this._region);
|
|
480
492
|
logger_1.logger.debug('Cross-region error detected, trying opposite region...');
|
|
481
493
|
const alternateRegion = this._region === 'US' ? 'EU' : 'US';
|
|
494
|
+
// Check if we've already tried this region to prevent infinite loops
|
|
495
|
+
if (triedRegions.has(alternateRegion)) {
|
|
496
|
+
logger_1.logger.error('═══════════════════════════════════════════════════════════════');
|
|
497
|
+
logger_1.logger.error('AUTHENTICATION FAILED: COUNTRY CODE REQUIRED');
|
|
498
|
+
logger_1.logger.error('');
|
|
499
|
+
logger_1.logger.error('Both US and EU endpoints rejected your account.');
|
|
500
|
+
logger_1.logger.error('This means you MUST specify your country code.');
|
|
501
|
+
logger_1.logger.error('');
|
|
502
|
+
logger_1.logger.error('SOLUTION:');
|
|
503
|
+
logger_1.logger.error('1. In Homebridge UI:');
|
|
504
|
+
logger_1.logger.error(' - Go to plugin settings');
|
|
505
|
+
logger_1.logger.error(' - Select your country from the "Country Code" dropdown');
|
|
506
|
+
logger_1.logger.error('');
|
|
507
|
+
logger_1.logger.error('2. In config.json:');
|
|
508
|
+
logger_1.logger.error(' Add: "countryCode": "YOUR_COUNTRY_CODE"');
|
|
509
|
+
logger_1.logger.error('');
|
|
510
|
+
logger_1.logger.error('Common non-US/EU country codes:');
|
|
511
|
+
logger_1.logger.error(' 🇦🇺 Australia: "AU"');
|
|
512
|
+
logger_1.logger.error(' 🇳🇿 New Zealand: "NZ"');
|
|
513
|
+
logger_1.logger.error(' 🇯🇵 Japan: "JP"');
|
|
514
|
+
logger_1.logger.error(' 🇨🇦 Canada: "CA"');
|
|
515
|
+
logger_1.logger.error(' 🇸🇬 Singapore: "SG"');
|
|
516
|
+
logger_1.logger.error(' 🇲🇽 Mexico: "MX"');
|
|
517
|
+
logger_1.logger.error('═══════════════════════════════════════════════════════════════');
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
logger_1.logger.debug(`Switching from ${this._region} to ${alternateRegion} region`);
|
|
482
521
|
this._region = alternateRegion;
|
|
483
522
|
(0, helpers_1.setCurrentRegion)(alternateRegion);
|
|
484
|
-
|
|
523
|
+
// Update the API endpoint for the new region
|
|
524
|
+
const regionEndpoints = {
|
|
525
|
+
'US': 'https://smartapi.vesync.com',
|
|
526
|
+
'EU': 'https://smartapi.vesync.eu'
|
|
527
|
+
};
|
|
528
|
+
if (alternateRegion in regionEndpoints) {
|
|
529
|
+
(0, helpers_1.setApiBaseUrl)(regionEndpoints[alternateRegion]);
|
|
530
|
+
logger_1.logger.debug(`API endpoint set to ${regionEndpoints[alternateRegion]}`);
|
|
531
|
+
}
|
|
532
|
+
// Retry with the alternate region
|
|
533
|
+
[success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, alternateRegion, this._countryCodeOverride || undefined);
|
|
485
534
|
if (success) {
|
|
486
535
|
this.authFlowUsed = 'new';
|
|
536
|
+
logger_1.logger.debug(`Authentication successful with ${alternateRegion} region`);
|
|
537
|
+
}
|
|
538
|
+
else if (countryCode === 'cross_region' || countryCode === 'cross_region_retry') {
|
|
539
|
+
// Both regions failed with cross-region error
|
|
540
|
+
logger_1.logger.error('Authentication failed: Account rejected by both US and EU regions');
|
|
541
|
+
logger_1.logger.error('This may indicate your account was created in a region not yet supported');
|
|
542
|
+
logger_1.logger.error('Please contact VeSync support to determine your account region');
|
|
543
|
+
// Don't retry further attempts as we've exhausted both regions
|
|
544
|
+
return false;
|
|
487
545
|
}
|
|
488
546
|
}
|
|
489
547
|
// If new flow still fails, try legacy authentication as fallback
|
|
@@ -493,6 +551,11 @@ class VeSync {
|
|
|
493
551
|
if (success) {
|
|
494
552
|
this.authFlowUsed = 'legacy';
|
|
495
553
|
}
|
|
554
|
+
else if (countryCode === 'credential_error') {
|
|
555
|
+
// Legacy auth also detected bad credentials
|
|
556
|
+
logger_1.logger.error('Both authentication methods report invalid credentials');
|
|
557
|
+
return false; // Exit immediately
|
|
558
|
+
}
|
|
496
559
|
}
|
|
497
560
|
}
|
|
498
561
|
if (success && token && accountId) {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -11,4 +11,12 @@ export { VeSyncWallSwitch } from './lib/vesyncSwitchImpl';
|
|
|
11
11
|
export { VeSyncBaseDevice } from './lib/vesyncBaseDevice';
|
|
12
12
|
|
|
13
13
|
// Export helper functions
|
|
14
|
-
export { Helpers, setApiBaseUrl } from './lib/helpers';
|
|
14
|
+
export { Helpers, setApiBaseUrl } from './lib/helpers';
|
|
15
|
+
|
|
16
|
+
// Export constants
|
|
17
|
+
export {
|
|
18
|
+
CREDENTIAL_ERROR_CODES,
|
|
19
|
+
CROSS_REGION_ERROR_CODES,
|
|
20
|
+
isCredentialError,
|
|
21
|
+
isCrossRegionError
|
|
22
|
+
} from './lib/constants';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VeSync API Error Codes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Authentication error codes that indicate bad credentials (no point retrying)
|
|
6
|
+
export const CREDENTIAL_ERROR_CODES = [
|
|
7
|
+
-11201129, // account or password incorrect
|
|
8
|
+
-11202129, // the account does not exist
|
|
9
|
+
-11000129, // illegal argument (empty credentials)
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
// Cross-region error codes that require region switching
|
|
13
|
+
export const CROSS_REGION_ERROR_CODES = [
|
|
14
|
+
-11260022, // cross region error
|
|
15
|
+
-11261022, // access region conflict error
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
// Other error codes
|
|
19
|
+
export const APP_VERSION_ERROR_CODES = [
|
|
20
|
+
-11012022, // app version is too low
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if an error code indicates bad credentials
|
|
25
|
+
*/
|
|
26
|
+
export function isCredentialError(code: number): boolean {
|
|
27
|
+
return CREDENTIAL_ERROR_CODES.includes(code);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if an error code indicates cross-region issue
|
|
32
|
+
*/
|
|
33
|
+
export function isCrossRegionError(code: number): boolean {
|
|
34
|
+
return CROSS_REGION_ERROR_CODES.includes(code);
|
|
35
|
+
}
|
package/src/lib/helpers.ts
CHANGED
|
@@ -6,6 +6,7 @@ import axios from 'axios';
|
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import { VeSync } from './vesync';
|
|
8
8
|
import { logger } from './logger';
|
|
9
|
+
import { isCredentialError, isCrossRegionError, CROSS_REGION_ERROR_CODES } from './constants';
|
|
9
10
|
|
|
10
11
|
// API configuration - Support regional endpoints
|
|
11
12
|
let _apiBaseUrl = 'https://smartapi.vesync.com';
|
|
@@ -72,7 +73,7 @@ export const REGION_ENDPOINTS = {
|
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
// Cross-region error codes - multiple versions exist
|
|
75
|
-
|
|
76
|
+
// CROSS_REGION_ERROR_CODES moved to constants.ts
|
|
76
77
|
export const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
77
78
|
|
|
78
79
|
// Authentication error codes
|
|
@@ -83,6 +84,42 @@ export const AUTH_ERROR_CODES = {
|
|
|
83
84
|
CROSS_REGION_ALT: -11261022
|
|
84
85
|
};
|
|
85
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Get list of country codes that use the US endpoint
|
|
89
|
+
* These are tried when we get a cross-region error
|
|
90
|
+
*/
|
|
91
|
+
export function getUSEndpointCountryCodes(): string[] {
|
|
92
|
+
// Countries that use the US endpoint but might need their own country code
|
|
93
|
+
return ['US', 'AU', 'NZ', 'JP', 'CA', 'MX', 'SG'];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the appropriate API endpoint based on country code
|
|
98
|
+
*/
|
|
99
|
+
export function getEndpointForCountryCode(countryCode: string): 'US' | 'EU' {
|
|
100
|
+
// European country codes that should use the EU endpoint
|
|
101
|
+
const euCountryCodes = [
|
|
102
|
+
// Western Europe
|
|
103
|
+
'GB', 'DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'AT', 'CH', 'IE', 'PT', 'LU',
|
|
104
|
+
// Nordic countries
|
|
105
|
+
'SE', 'NO', 'DK', 'FI', 'IS',
|
|
106
|
+
// Eastern Europe
|
|
107
|
+
'PL', 'CZ', 'HU', 'RO', 'BG', 'HR', 'SI', 'SK', 'LT', 'LV', 'EE',
|
|
108
|
+
// Southern Europe
|
|
109
|
+
'GR', 'CY', 'MT',
|
|
110
|
+
// Other European
|
|
111
|
+
'AL', 'BA', 'RS', 'ME', 'MK', 'MD', 'UA', 'BY'
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
// Check if country code is in EU list
|
|
115
|
+
if (euCountryCodes.includes(countryCode)) {
|
|
116
|
+
return 'EU';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Everything else uses US endpoint (including AU, NZ, JP, CA, MX, etc.)
|
|
120
|
+
return 'US';
|
|
121
|
+
}
|
|
122
|
+
|
|
86
123
|
/**
|
|
87
124
|
* Detect user's home region from email domain or country hints
|
|
88
125
|
*/
|
|
@@ -556,7 +593,7 @@ export class Helpers {
|
|
|
556
593
|
/**
|
|
557
594
|
* Perform new two-step authentication flow
|
|
558
595
|
*/
|
|
559
|
-
static async authNewFlow(manager: VeSync, appId: string, region: string = 'US'): Promise<[boolean, string | null, string | null, string | null]> {
|
|
596
|
+
static async authNewFlow(manager: VeSync, appId: string, region: string = 'US', countryCodeOverride?: string): Promise<[boolean, string | null, string | null, string | null]> {
|
|
560
597
|
try {
|
|
561
598
|
// Generate terminal ID to be used in both steps
|
|
562
599
|
const terminalId = generateTerminalId();
|
|
@@ -594,8 +631,15 @@ export class Helpers {
|
|
|
594
631
|
if (authResponse.code !== 0) {
|
|
595
632
|
logger.error('Step 1 error code:', authResponse.code, 'message:', authResponse.msg);
|
|
596
633
|
|
|
634
|
+
// Check if it's a credential error (no point retrying)
|
|
635
|
+
if (isCredentialError(authResponse.code)) {
|
|
636
|
+
logger.error('Credential error detected - invalid username or password');
|
|
637
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
638
|
+
return [false, null, null, 'credential_error'];
|
|
639
|
+
}
|
|
640
|
+
|
|
597
641
|
// Handle cross-region error (multiple error codes possible)
|
|
598
|
-
if (
|
|
642
|
+
if (isCrossRegionError(authResponse.code)) {
|
|
599
643
|
logger.debug('Cross-region error detected:', authResponse.code, 'will try different region');
|
|
600
644
|
setApiBaseUrl(originalUrl); // Restore original URL
|
|
601
645
|
return [false, null, null, 'cross_region'];
|
|
@@ -616,8 +660,9 @@ export class Helpers {
|
|
|
616
660
|
logger.debug('Step 1 successful, got authorization code');
|
|
617
661
|
|
|
618
662
|
// Step 2: Login with authorization code
|
|
619
|
-
// Use the
|
|
620
|
-
|
|
663
|
+
// Use the override if provided, otherwise default to 'US'
|
|
664
|
+
// Users should specify their actual country code in the configuration
|
|
665
|
+
const countryCodeForStep2 = countryCodeOverride || 'US';
|
|
621
666
|
const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, countryCodeForStep2);
|
|
622
667
|
|
|
623
668
|
logger.debug('Step 2: Logging in with authorization code...');
|
|
@@ -630,6 +675,13 @@ export class Helpers {
|
|
|
630
675
|
manager
|
|
631
676
|
);
|
|
632
677
|
|
|
678
|
+
logger.debug('Step 2 response:', {
|
|
679
|
+
status: loginStatus,
|
|
680
|
+
code: loginResponse?.code,
|
|
681
|
+
hasResult: !!loginResponse?.result,
|
|
682
|
+
msg: loginResponse?.msg
|
|
683
|
+
});
|
|
684
|
+
|
|
633
685
|
if (!loginResponse || loginStatus !== 200) {
|
|
634
686
|
logger.error('Step 2 failed:', loginResponse);
|
|
635
687
|
setApiBaseUrl(originalUrl); // Restore original URL on failure
|
|
@@ -637,61 +689,29 @@ export class Helpers {
|
|
|
637
689
|
}
|
|
638
690
|
|
|
639
691
|
if (loginResponse.code !== 0) {
|
|
640
|
-
|
|
692
|
+
logger.debug('Step 2 error, code:', loginResponse.code, 'checking if cross-region...');
|
|
693
|
+
// Handle cross-region error - for EU accounts, we need to switch regions entirely
|
|
641
694
|
if (CROSS_REGION_ERROR_CODES.includes(loginResponse.code)) {
|
|
642
|
-
|
|
695
|
+
// Log helpful message about country code mismatch
|
|
696
|
+
logger.warn('═══════════════════════════════════════════════════════════════');
|
|
697
|
+
logger.warn('COUNTRY CODE MISMATCH DETECTED');
|
|
698
|
+
logger.warn(`Your account expects a different country code than '${countryCodeForStep2}'`);
|
|
699
|
+
logger.warn('This usually means:');
|
|
700
|
+
logger.warn(' 1. You need to set the correct country code in your config');
|
|
701
|
+
logger.warn(' 2. Your account was created in a different country/region');
|
|
702
|
+
logger.warn('');
|
|
703
|
+
logger.warn('To fix this:');
|
|
704
|
+
logger.warn(' - In Homebridge UI: Select your correct country from the dropdown');
|
|
705
|
+
logger.warn(' - In config.json: Add "countryCode": "YOUR_COUNTRY_CODE"');
|
|
706
|
+
logger.warn('');
|
|
707
|
+
logger.warn('Common country codes: US, CA, GB, DE, FR, AU, NZ, JP');
|
|
708
|
+
logger.warn('═══════════════════════════════════════════════════════════════');
|
|
643
709
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const retryBody = {
|
|
650
|
-
method: 'loginByAuthorizeCode4Vesync',
|
|
651
|
-
authorizeCode: authorizeCode,
|
|
652
|
-
acceptLanguage: 'en',
|
|
653
|
-
clientInfo: CLIENT_INFO,
|
|
654
|
-
clientType: 'vesyncApp',
|
|
655
|
-
clientVersion: CLIENT_VERSION,
|
|
656
|
-
debugMode: false,
|
|
657
|
-
emailSubscriptions: false,
|
|
658
|
-
osInfo: PHONE_OS,
|
|
659
|
-
terminalId: terminalId,
|
|
660
|
-
timeZone: DEFAULT_TZ,
|
|
661
|
-
userCountryCode: newCountryCode,
|
|
662
|
-
traceId: `APP${appId}${Math.floor(Date.now() / 1000)}`,
|
|
663
|
-
bizToken: regionChangeToken,
|
|
664
|
-
regionChange: 'last_region'
|
|
665
|
-
};
|
|
666
|
-
|
|
667
|
-
logger.debug('Retrying Step 2 with region change token...', {
|
|
668
|
-
newCountryCode,
|
|
669
|
-
currentEndpoint: getApiBaseUrl()
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
const [retryResponse, retryStatus] = await this.callApi(
|
|
673
|
-
'/user/api/accountManage/v1/loginByAuthorizeCode4Vesync',
|
|
674
|
-
'post',
|
|
675
|
-
retryBody,
|
|
676
|
-
step1Headers,
|
|
677
|
-
manager
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
if (retryResponse && retryResponse.code === 0) {
|
|
681
|
-
const { token, accountID, countryCode } = retryResponse.result || {};
|
|
682
|
-
if (token && accountID) {
|
|
683
|
-
logger.debug('Step 2 retry successful with region change');
|
|
684
|
-
setApiBaseUrl(originalUrl); // Restore original URL on success
|
|
685
|
-
return [true, token, accountID, countryCode || newCountryCode];
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
logger.error('Step 2 retry failed:', retryResponse);
|
|
690
|
-
setApiBaseUrl(originalUrl); // Restore original URL on failure
|
|
691
|
-
return [false, null, null, null];
|
|
692
|
-
} else {
|
|
693
|
-
logger.error('No region change token in cross-region error response');
|
|
694
|
-
}
|
|
710
|
+
logger.debug('Cross-region error detected in Step 2 - will try different endpoint');
|
|
711
|
+
// For cross-region errors, we need to retry the entire flow with a different region
|
|
712
|
+
// The authorization code from Step 1 is tied to the endpoint it was obtained from
|
|
713
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
714
|
+
return [false, null, null, 'cross_region_retry'];
|
|
695
715
|
}
|
|
696
716
|
|
|
697
717
|
logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
|
|
@@ -744,6 +764,13 @@ export class Helpers {
|
|
|
744
764
|
|
|
745
765
|
if (response.code && response.code !== 0) {
|
|
746
766
|
logger.error('Legacy login error code:', response.code, 'message:', response.msg);
|
|
767
|
+
|
|
768
|
+
// Check if it's a credential error
|
|
769
|
+
if (isCredentialError(response.code)) {
|
|
770
|
+
logger.error('Legacy auth: Credential error detected');
|
|
771
|
+
return [false, null, null, 'credential_error'];
|
|
772
|
+
}
|
|
773
|
+
|
|
747
774
|
return [false, null, null, null];
|
|
748
775
|
}
|
|
749
776
|
|
package/src/lib/vesync.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* VeSync API Device Library
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Helpers, API_RATE_LIMIT, DEFAULT_TZ, setApiBaseUrl, getApiBaseUrl, generateAppId, getRegionFromCountryCode, setCurrentRegion, getCurrentRegion, REGION_ENDPOINTS, getCountryCodeFromRegion } from './helpers';
|
|
5
|
+
import { Helpers, API_RATE_LIMIT, DEFAULT_TZ, setApiBaseUrl, getApiBaseUrl, generateAppId, getRegionFromCountryCode, setCurrentRegion, getCurrentRegion, REGION_ENDPOINTS, getCountryCodeFromRegion, getEndpointForCountryCode } from './helpers';
|
|
6
6
|
import { VeSyncBaseDevice } from './vesyncBaseDevice';
|
|
7
7
|
import { fanModules } from './vesyncFanImpl';
|
|
8
8
|
import { outletModules } from './vesyncOutletImpl';
|
|
@@ -161,6 +161,7 @@ export class VeSync {
|
|
|
161
161
|
private _excludeConfig: ExcludeConfig | null;
|
|
162
162
|
private _appId: string;
|
|
163
163
|
private _region: string;
|
|
164
|
+
private _countryCodeOverride: string | null;
|
|
164
165
|
|
|
165
166
|
username: string;
|
|
166
167
|
password: string;
|
|
@@ -202,6 +203,7 @@ export class VeSync {
|
|
|
202
203
|
customLogger?: Logger;
|
|
203
204
|
excludeConfig?: ExcludeConfig;
|
|
204
205
|
region?: string;
|
|
206
|
+
countryCode?: string; // Override country code for Step 2 authentication
|
|
205
207
|
debugMode?: boolean; // Alias for debug
|
|
206
208
|
} = false,
|
|
207
209
|
redact = true,
|
|
@@ -218,6 +220,7 @@ export class VeSync {
|
|
|
218
220
|
this._redact = options.redact !== undefined ? options.redact : true;
|
|
219
221
|
this._excludeConfig = options.excludeConfig || null;
|
|
220
222
|
this._region = options.region || 'US';
|
|
223
|
+
this._countryCodeOverride = options.countryCode || null;
|
|
221
224
|
customLogger = options.customLogger;
|
|
222
225
|
apiUrl = options.apiUrl;
|
|
223
226
|
} else {
|
|
@@ -226,6 +229,7 @@ export class VeSync {
|
|
|
226
229
|
this._redact = redact;
|
|
227
230
|
this._excludeConfig = excludeConfig || null;
|
|
228
231
|
this._region = 'US';
|
|
232
|
+
this._countryCodeOverride = null;
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
this._energyUpdateInterval = DEFAULT_ENERGY_UPDATE_INTERVAL;
|
|
@@ -274,6 +278,13 @@ export class VeSync {
|
|
|
274
278
|
if (apiUrl) {
|
|
275
279
|
setApiBaseUrl(apiUrl);
|
|
276
280
|
} else {
|
|
281
|
+
// If country code is provided, use it to determine the endpoint
|
|
282
|
+
if (this._countryCodeOverride) {
|
|
283
|
+
const endpointRegion = getEndpointForCountryCode(this._countryCodeOverride);
|
|
284
|
+
this._region = endpointRegion;
|
|
285
|
+
logger.debug(`Country code ${this._countryCodeOverride} maps to ${endpointRegion} endpoint`);
|
|
286
|
+
}
|
|
287
|
+
|
|
277
288
|
// Set endpoint based on region
|
|
278
289
|
const regionEndpoints: Record<string, string> = {
|
|
279
290
|
'US': 'https://smartapi.vesync.com',
|
|
@@ -571,20 +582,18 @@ export class VeSync {
|
|
|
571
582
|
region: this._region
|
|
572
583
|
});
|
|
573
584
|
|
|
585
|
+
// Track which regions have been attempted to prevent infinite loops
|
|
586
|
+
const triedRegions = new Set<string>();
|
|
587
|
+
|
|
574
588
|
for (let attempt = 0; attempt < retryAttempts; attempt++) {
|
|
575
589
|
try {
|
|
576
590
|
logger.debug(`Authentication attempt ${attempt + 1} of ${retryAttempts}`);
|
|
577
591
|
|
|
578
|
-
//
|
|
579
|
-
|
|
580
|
-
if (detectedRegion !== this._region) {
|
|
581
|
-
logger.debug(`Adjusting region from ${this._region} to ${detectedRegion} based on email domain`);
|
|
582
|
-
this._region = detectedRegion;
|
|
583
|
-
setCurrentRegion(detectedRegion);
|
|
584
|
-
}
|
|
592
|
+
// Skip email-based region detection for now since we'll handle cross-region errors
|
|
593
|
+
// The cross-region error handling below will automatically switch to the correct region
|
|
585
594
|
|
|
586
595
|
// Try new authentication flow first with detected region
|
|
587
|
-
let [success, token, accountId, countryCode] = await Helpers.authNewFlow(this, this._appId, this._region);
|
|
596
|
+
let [success, token, accountId, countryCode] = await Helpers.authNewFlow(this, this._appId, this._region, this._countryCodeOverride || undefined);
|
|
588
597
|
|
|
589
598
|
// Track if new flow succeeded
|
|
590
599
|
if (success) {
|
|
@@ -592,15 +601,74 @@ export class VeSync {
|
|
|
592
601
|
}
|
|
593
602
|
|
|
594
603
|
if (!success) {
|
|
604
|
+
// Check if it's a credential error (no point retrying)
|
|
605
|
+
if (countryCode === 'credential_error') {
|
|
606
|
+
logger.error('Authentication failed due to invalid credentials');
|
|
607
|
+
return false; // Exit immediately, don't retry
|
|
608
|
+
}
|
|
609
|
+
|
|
595
610
|
// Check if we need to try a different region
|
|
596
|
-
if (countryCode === 'cross_region') {
|
|
611
|
+
if (countryCode === 'cross_region' || countryCode === 'cross_region_retry') {
|
|
612
|
+
// Mark current region as tried
|
|
613
|
+
triedRegions.add(this._region);
|
|
614
|
+
|
|
597
615
|
logger.debug('Cross-region error detected, trying opposite region...');
|
|
598
616
|
const alternateRegion = this._region === 'US' ? 'EU' : 'US';
|
|
617
|
+
|
|
618
|
+
// Check if we've already tried this region to prevent infinite loops
|
|
619
|
+
if (triedRegions.has(alternateRegion)) {
|
|
620
|
+
logger.error('═══════════════════════════════════════════════════════════════');
|
|
621
|
+
logger.error('AUTHENTICATION FAILED: COUNTRY CODE REQUIRED');
|
|
622
|
+
logger.error('');
|
|
623
|
+
logger.error('Both US and EU endpoints rejected your account.');
|
|
624
|
+
logger.error('This means you MUST specify your country code.');
|
|
625
|
+
logger.error('');
|
|
626
|
+
logger.error('SOLUTION:');
|
|
627
|
+
logger.error('1. In Homebridge UI:');
|
|
628
|
+
logger.error(' - Go to plugin settings');
|
|
629
|
+
logger.error(' - Select your country from the "Country Code" dropdown');
|
|
630
|
+
logger.error('');
|
|
631
|
+
logger.error('2. In config.json:');
|
|
632
|
+
logger.error(' Add: "countryCode": "YOUR_COUNTRY_CODE"');
|
|
633
|
+
logger.error('');
|
|
634
|
+
logger.error('Common non-US/EU country codes:');
|
|
635
|
+
logger.error(' 🇦🇺 Australia: "AU"');
|
|
636
|
+
logger.error(' 🇳🇿 New Zealand: "NZ"');
|
|
637
|
+
logger.error(' 🇯🇵 Japan: "JP"');
|
|
638
|
+
logger.error(' 🇨🇦 Canada: "CA"');
|
|
639
|
+
logger.error(' 🇸🇬 Singapore: "SG"');
|
|
640
|
+
logger.error(' 🇲🇽 Mexico: "MX"');
|
|
641
|
+
logger.error('═══════════════════════════════════════════════════════════════');
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
logger.debug(`Switching from ${this._region} to ${alternateRegion} region`);
|
|
646
|
+
|
|
599
647
|
this._region = alternateRegion;
|
|
600
648
|
setCurrentRegion(alternateRegion);
|
|
601
|
-
|
|
649
|
+
|
|
650
|
+
// Update the API endpoint for the new region
|
|
651
|
+
const regionEndpoints: Record<string, string> = {
|
|
652
|
+
'US': 'https://smartapi.vesync.com',
|
|
653
|
+
'EU': 'https://smartapi.vesync.eu'
|
|
654
|
+
};
|
|
655
|
+
if (alternateRegion in regionEndpoints) {
|
|
656
|
+
setApiBaseUrl(regionEndpoints[alternateRegion]);
|
|
657
|
+
logger.debug(`API endpoint set to ${regionEndpoints[alternateRegion]}`);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Retry with the alternate region
|
|
661
|
+
[success, token, accountId, countryCode] = await Helpers.authNewFlow(this, this._appId, alternateRegion, this._countryCodeOverride || undefined);
|
|
602
662
|
if (success) {
|
|
603
663
|
this.authFlowUsed = 'new';
|
|
664
|
+
logger.debug(`Authentication successful with ${alternateRegion} region`);
|
|
665
|
+
} else if (countryCode === 'cross_region' || countryCode === 'cross_region_retry') {
|
|
666
|
+
// Both regions failed with cross-region error
|
|
667
|
+
logger.error('Authentication failed: Account rejected by both US and EU regions');
|
|
668
|
+
logger.error('This may indicate your account was created in a region not yet supported');
|
|
669
|
+
logger.error('Please contact VeSync support to determine your account region');
|
|
670
|
+
// Don't retry further attempts as we've exhausted both regions
|
|
671
|
+
return false;
|
|
604
672
|
}
|
|
605
673
|
}
|
|
606
674
|
|
|
@@ -610,6 +678,10 @@ export class VeSync {
|
|
|
610
678
|
[success, token, accountId, countryCode] = await Helpers.authLegacyFlow(this);
|
|
611
679
|
if (success) {
|
|
612
680
|
this.authFlowUsed = 'legacy';
|
|
681
|
+
} else if (countryCode === 'credential_error') {
|
|
682
|
+
// Legacy auth also detected bad credentials
|
|
683
|
+
logger.error('Both authentication methods report invalid credentials');
|
|
684
|
+
return false; // Exit immediately
|
|
613
685
|
}
|
|
614
686
|
}
|
|
615
687
|
}
|