tsvesync 1.0.100 → 1.0.102
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/dist/lib/helpers.d.ts +18 -4
- package/dist/lib/helpers.js +97 -24
- package/dist/lib/vesync.d.ts +17 -7
- package/dist/lib/vesync.js +63 -25
- package/package.json +1 -1
- package/src/lib/helpers.ts +96 -24
- package/src/lib/vesync.ts +76 -27
package/dist/lib/helpers.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare function setCurrentRegion(region: string): void;
|
|
|
9
9
|
export declare function getRegionFromCountryCode(countryCode: string): string;
|
|
10
10
|
export declare const API_RATE_LIMIT = 30;
|
|
11
11
|
export declare const API_TIMEOUT = 15000;
|
|
12
|
-
export declare const APP_VERSION = "5.
|
|
12
|
+
export declare const APP_VERSION = "5.7.16";
|
|
13
13
|
export declare const PHONE_BRAND = "SM N9005";
|
|
14
14
|
export declare const PHONE_OS = "Android";
|
|
15
15
|
export declare const CLIENT_INFO = "SM N9005";
|
|
@@ -18,7 +18,7 @@ export declare const DEFAULT_TZ = "America/New_York";
|
|
|
18
18
|
export declare const DEFAULT_REGION = "US";
|
|
19
19
|
export declare const MOBILE_ID = "1234567890123456";
|
|
20
20
|
export declare const BYPASS_HEADER_UA = "okhttp/3.12.1";
|
|
21
|
-
export declare const CLIENT_VERSION = "VeSync 5.
|
|
21
|
+
export declare const CLIENT_VERSION = "VeSync 5.7.16";
|
|
22
22
|
export declare const REGION_ENDPOINTS: {
|
|
23
23
|
US: string;
|
|
24
24
|
CA: string;
|
|
@@ -26,8 +26,22 @@ export declare const REGION_ENDPOINTS: {
|
|
|
26
26
|
JP: string;
|
|
27
27
|
EU: string;
|
|
28
28
|
};
|
|
29
|
-
export declare const
|
|
29
|
+
export declare const CROSS_REGION_ERROR_CODES: number[];
|
|
30
30
|
export declare const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
31
|
+
export declare const AUTH_ERROR_CODES: {
|
|
32
|
+
ACCOUNT_PASSWORD_INCORRECT: number;
|
|
33
|
+
ILLEGAL_ARGUMENT: number;
|
|
34
|
+
CROSS_REGION: number;
|
|
35
|
+
CROSS_REGION_ALT: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Detect user's home region from email domain or country hints
|
|
39
|
+
*/
|
|
40
|
+
export declare function detectUserRegion(email: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Get country code from region
|
|
43
|
+
*/
|
|
44
|
+
export declare function getCountryCodeFromRegion(region: string, email?: string): string;
|
|
31
45
|
export declare function generateAppId(): string;
|
|
32
46
|
export declare function generateTerminalId(): string;
|
|
33
47
|
export interface RequestBody {
|
|
@@ -113,7 +127,7 @@ export declare class Helpers {
|
|
|
113
127
|
/**
|
|
114
128
|
* Build request body for initial authentication step
|
|
115
129
|
*/
|
|
116
|
-
static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string): Record<string, any>;
|
|
130
|
+
static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string, region?: string): Record<string, any>;
|
|
117
131
|
/**
|
|
118
132
|
* Build request body for second authentication step (login with authorize code)
|
|
119
133
|
*/
|
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.APP_VERSION_TOO_LOW_ERROR_CODE = exports.
|
|
9
|
+
exports.Helpers = exports.AUTH_ERROR_CODES = exports.APP_VERSION_TOO_LOW_ERROR_CODE = exports.CROSS_REGION_ERROR_CODES = 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.detectUserRegion = detectUserRegion;
|
|
16
|
+
exports.getCountryCodeFromRegion = getCountryCodeFromRegion;
|
|
15
17
|
exports.generateAppId = generateAppId;
|
|
16
18
|
exports.generateTerminalId = generateTerminalId;
|
|
17
19
|
const axios_1 = __importDefault(require("axios"));
|
|
@@ -51,7 +53,7 @@ function getRegionFromCountryCode(countryCode) {
|
|
|
51
53
|
}
|
|
52
54
|
exports.API_RATE_LIMIT = 30;
|
|
53
55
|
exports.API_TIMEOUT = 15000;
|
|
54
|
-
exports.APP_VERSION = '5.
|
|
56
|
+
exports.APP_VERSION = '5.7.16'; // Updated to newer version
|
|
55
57
|
exports.PHONE_BRAND = 'SM N9005';
|
|
56
58
|
exports.PHONE_OS = 'Android';
|
|
57
59
|
exports.CLIENT_INFO = 'SM N9005';
|
|
@@ -60,7 +62,7 @@ exports.DEFAULT_TZ = 'America/New_York';
|
|
|
60
62
|
exports.DEFAULT_REGION = 'US';
|
|
61
63
|
exports.MOBILE_ID = '1234567890123456';
|
|
62
64
|
exports.BYPASS_HEADER_UA = 'okhttp/3.12.1';
|
|
63
|
-
exports.CLIENT_VERSION = 'VeSync 5.
|
|
65
|
+
exports.CLIENT_VERSION = 'VeSync 5.7.16';
|
|
64
66
|
// Regional API endpoints
|
|
65
67
|
exports.REGION_ENDPOINTS = {
|
|
66
68
|
'US': 'https://smartapi.vesync.com',
|
|
@@ -69,12 +71,57 @@ exports.REGION_ENDPOINTS = {
|
|
|
69
71
|
'JP': 'https://smartapi.vesync.com',
|
|
70
72
|
'EU': 'https://smartapi.vesync.eu'
|
|
71
73
|
};
|
|
72
|
-
// Cross-region error
|
|
73
|
-
exports.
|
|
74
|
+
// Cross-region error codes - multiple versions exist
|
|
75
|
+
exports.CROSS_REGION_ERROR_CODES = [-11260022, -11261022];
|
|
74
76
|
exports.APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
77
|
+
// Authentication error codes
|
|
78
|
+
exports.AUTH_ERROR_CODES = {
|
|
79
|
+
ACCOUNT_PASSWORD_INCORRECT: -11201129,
|
|
80
|
+
ILLEGAL_ARGUMENT: -11000022,
|
|
81
|
+
CROSS_REGION: -11260022,
|
|
82
|
+
CROSS_REGION_ALT: -11261022
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Detect user's home region from email domain or country hints
|
|
86
|
+
*/
|
|
87
|
+
function detectUserRegion(email) {
|
|
88
|
+
// Check for common EU email domains
|
|
89
|
+
const euDomains = ['.de', '.fr', '.it', '.es', '.nl', '.be', '.at', '.dk', '.se', '.no', '.fi', '.eu'];
|
|
90
|
+
const emailLower = email.toLowerCase();
|
|
91
|
+
for (const domain of euDomains) {
|
|
92
|
+
if (emailLower.includes(domain)) {
|
|
93
|
+
return 'EU';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Default to US for unknown domains
|
|
97
|
+
return 'US';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get country code from region
|
|
101
|
+
*/
|
|
102
|
+
function getCountryCodeFromRegion(region, email) {
|
|
103
|
+
if (region === 'EU') {
|
|
104
|
+
// Try to detect specific EU country from email
|
|
105
|
+
if (email) {
|
|
106
|
+
const emailLower = email.toLowerCase();
|
|
107
|
+
if (emailLower.includes('.de') || emailLower.includes('german'))
|
|
108
|
+
return 'DE';
|
|
109
|
+
if (emailLower.includes('.fr'))
|
|
110
|
+
return 'FR';
|
|
111
|
+
if (emailLower.includes('.it'))
|
|
112
|
+
return 'IT';
|
|
113
|
+
if (emailLower.includes('.es'))
|
|
114
|
+
return 'ES';
|
|
115
|
+
if (emailLower.includes('.nl'))
|
|
116
|
+
return 'NL';
|
|
117
|
+
}
|
|
118
|
+
return 'DE'; // Default to Germany for EU
|
|
119
|
+
}
|
|
120
|
+
return 'US'; // Default for non-EU
|
|
121
|
+
}
|
|
75
122
|
// Generate unique APP_ID for each session
|
|
76
123
|
function generateAppId() {
|
|
77
|
-
const chars = '
|
|
124
|
+
const chars = 'ABCDEFGHIJKLMNOPqRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
78
125
|
let result = '';
|
|
79
126
|
for (let i = 0; i < 8; i++) {
|
|
80
127
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
@@ -131,7 +178,9 @@ class Helpers {
|
|
|
131
178
|
return {
|
|
132
179
|
'Content-Type': 'application/json; charset=UTF-8',
|
|
133
180
|
'User-Agent': exports.BYPASS_HEADER_UA,
|
|
134
|
-
'accept-language': 'en'
|
|
181
|
+
'accept-language': 'en',
|
|
182
|
+
'appVersion': exports.APP_VERSION,
|
|
183
|
+
'clientVersion': exports.CLIENT_VERSION
|
|
135
184
|
};
|
|
136
185
|
}
|
|
137
186
|
/**
|
|
@@ -169,7 +218,16 @@ class Helpers {
|
|
|
169
218
|
/**
|
|
170
219
|
* Build request body for initial authentication step
|
|
171
220
|
*/
|
|
172
|
-
static reqBodyAuthStep1(manager, appId, terminalId) {
|
|
221
|
+
static reqBodyAuthStep1(manager, appId, terminalId, region = 'US') {
|
|
222
|
+
// Detect user's home region and country code
|
|
223
|
+
const userRegion = detectUserRegion(manager.username);
|
|
224
|
+
const userCountryCode = getCountryCodeFromRegion(region === 'EU' ? 'EU' : userRegion, manager.username);
|
|
225
|
+
logger_1.logger.debug('Authentication Step 1 - Region detection:', {
|
|
226
|
+
email: manager.username,
|
|
227
|
+
detectedRegion: userRegion,
|
|
228
|
+
requestRegion: region,
|
|
229
|
+
userCountryCode: userCountryCode
|
|
230
|
+
});
|
|
173
231
|
return {
|
|
174
232
|
'email': manager.username,
|
|
175
233
|
'method': 'authByPWDOrOTM',
|
|
@@ -185,7 +243,7 @@ class Helpers {
|
|
|
185
243
|
'terminalId': terminalId,
|
|
186
244
|
'timeZone': manager.timeZone || exports.DEFAULT_TZ,
|
|
187
245
|
'token': '',
|
|
188
|
-
'userCountryCode':
|
|
246
|
+
'userCountryCode': userCountryCode,
|
|
189
247
|
'appID': appId,
|
|
190
248
|
'sourceAppID': appId,
|
|
191
249
|
'traceId': `APP${appId}${Math.floor(Date.now() / 1000)}`
|
|
@@ -402,7 +460,7 @@ class Helpers {
|
|
|
402
460
|
// Generate terminal ID to be used in both steps
|
|
403
461
|
const terminalId = generateTerminalId();
|
|
404
462
|
// Step 1: Get authorization code
|
|
405
|
-
const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId);
|
|
463
|
+
const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId, region);
|
|
406
464
|
const step1Headers = this.reqHeaderAuth();
|
|
407
465
|
// Set the correct regional endpoint
|
|
408
466
|
const originalUrl = getApiBaseUrl();
|
|
@@ -422,16 +480,16 @@ class Helpers {
|
|
|
422
480
|
}
|
|
423
481
|
if (authResponse.code !== 0) {
|
|
424
482
|
logger_1.logger.error('Step 1 error code:', authResponse.code, 'message:', authResponse.msg);
|
|
425
|
-
// Handle cross-region error
|
|
426
|
-
if (authResponse.code
|
|
427
|
-
logger_1.logger.debug('Cross-region error detected, will try different region');
|
|
483
|
+
// Handle cross-region error (multiple error codes possible)
|
|
484
|
+
if (exports.CROSS_REGION_ERROR_CODES.includes(authResponse.code)) {
|
|
485
|
+
logger_1.logger.debug('Cross-region error detected:', authResponse.code, 'will try different region');
|
|
428
486
|
setApiBaseUrl(originalUrl); // Restore original URL
|
|
429
487
|
return [false, null, null, 'cross_region'];
|
|
430
488
|
}
|
|
431
489
|
setApiBaseUrl(originalUrl); // Restore original URL
|
|
432
490
|
return [false, null, null, null];
|
|
433
491
|
}
|
|
434
|
-
const { authorizeCode, bizToken
|
|
492
|
+
const { authorizeCode, bizToken } = authResponse.result || {};
|
|
435
493
|
if (!authorizeCode) {
|
|
436
494
|
logger_1.logger.error('Missing authorization code in step 1 response:', authResponse.result);
|
|
437
495
|
setApiBaseUrl(originalUrl); // Restore original URL
|
|
@@ -439,7 +497,9 @@ class Helpers {
|
|
|
439
497
|
}
|
|
440
498
|
logger_1.logger.debug('Step 1 successful, got authorization code');
|
|
441
499
|
// Step 2: Login with authorization code
|
|
442
|
-
|
|
500
|
+
// Use the appropriate country code based on the region we're authenticating with
|
|
501
|
+
const countryCodeForStep2 = region === 'EU' ? 'DE' : 'US';
|
|
502
|
+
const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, countryCodeForStep2);
|
|
443
503
|
logger_1.logger.debug('Step 2: Logging in with authorization code...');
|
|
444
504
|
const [loginResponse, loginStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', step2Body, step1Headers, manager);
|
|
445
505
|
if (!loginResponse || loginStatus !== 200) {
|
|
@@ -449,31 +509,44 @@ class Helpers {
|
|
|
449
509
|
}
|
|
450
510
|
if (loginResponse.code !== 0) {
|
|
451
511
|
// Handle cross-region error with retry (like pyvesync PR #340)
|
|
452
|
-
if (loginResponse.code
|
|
512
|
+
if (exports.CROSS_REGION_ERROR_CODES.includes(loginResponse.code)) {
|
|
453
513
|
logger_1.logger.debug('Cross-region error detected in Step 2, retrying with region change token...');
|
|
454
514
|
const { bizToken: regionChangeToken, countryCode: newCountryCode } = loginResponse.result || {};
|
|
455
|
-
if (regionChangeToken) {
|
|
456
|
-
// Retry Step 2 with region change token
|
|
515
|
+
if (regionChangeToken && newCountryCode) {
|
|
516
|
+
// Retry Step 2 with region change token on the SAME endpoint
|
|
517
|
+
// The region change token tells the server to handle the cross-region authentication
|
|
457
518
|
const retryBody = {
|
|
458
|
-
|
|
519
|
+
method: 'loginByAuthorizeCode4Vesync',
|
|
520
|
+
authorizeCode: authorizeCode,
|
|
521
|
+
acceptLanguage: 'en',
|
|
522
|
+
clientInfo: exports.CLIENT_INFO,
|
|
523
|
+
clientType: 'vesyncApp',
|
|
524
|
+
clientVersion: exports.CLIENT_VERSION,
|
|
525
|
+
debugMode: false,
|
|
526
|
+
emailSubscriptions: false,
|
|
527
|
+
osInfo: exports.PHONE_OS,
|
|
528
|
+
terminalId: terminalId,
|
|
529
|
+
timeZone: exports.DEFAULT_TZ,
|
|
530
|
+
userCountryCode: newCountryCode,
|
|
531
|
+
traceId: `APP${appId}${Math.floor(Date.now() / 1000)}`,
|
|
459
532
|
bizToken: regionChangeToken,
|
|
460
|
-
regionChange: 'last_region'
|
|
461
|
-
userCountryCode: newCountryCode || userCountryCode
|
|
533
|
+
regionChange: 'last_region'
|
|
462
534
|
};
|
|
463
535
|
logger_1.logger.debug('Retrying Step 2 with region change token...', {
|
|
464
536
|
newCountryCode,
|
|
465
|
-
|
|
537
|
+
currentEndpoint: getApiBaseUrl()
|
|
466
538
|
});
|
|
467
539
|
const [retryResponse, retryStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', retryBody, step1Headers, manager);
|
|
468
|
-
setApiBaseUrl(originalUrl); // Restore original URL after retry
|
|
469
540
|
if (retryResponse && retryResponse.code === 0) {
|
|
470
541
|
const { token, accountID, countryCode } = retryResponse.result || {};
|
|
471
542
|
if (token && accountID) {
|
|
472
543
|
logger_1.logger.debug('Step 2 retry successful with region change');
|
|
544
|
+
setApiBaseUrl(originalUrl); // Restore original URL on success
|
|
473
545
|
return [true, token, accountID, countryCode || newCountryCode];
|
|
474
546
|
}
|
|
475
547
|
}
|
|
476
548
|
logger_1.logger.error('Step 2 retry failed:', retryResponse);
|
|
549
|
+
setApiBaseUrl(originalUrl); // Restore original URL on failure
|
|
477
550
|
return [false, null, null, null];
|
|
478
551
|
}
|
|
479
552
|
else {
|
|
@@ -491,7 +564,7 @@ class Helpers {
|
|
|
491
564
|
return [false, null, null, null];
|
|
492
565
|
}
|
|
493
566
|
logger_1.logger.debug('Step 2 successful, got token and accountID');
|
|
494
|
-
return [true, token, accountID, countryCode ||
|
|
567
|
+
return [true, token, accountID, countryCode || countryCodeForStep2];
|
|
495
568
|
}
|
|
496
569
|
catch (error) {
|
|
497
570
|
logger_1.logger.error('New authentication flow error:', error);
|
package/dist/lib/vesync.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export declare class VeSync {
|
|
|
36
36
|
enabled: boolean;
|
|
37
37
|
updateInterval: number;
|
|
38
38
|
timeZone: string;
|
|
39
|
+
authFlowUsed?: 'legacy' | 'new';
|
|
40
|
+
apiBaseUrl?: string;
|
|
39
41
|
fans: VeSyncBaseDevice[];
|
|
40
42
|
outlets: VeSyncBaseDevice[];
|
|
41
43
|
switches: VeSyncBaseDevice[];
|
|
@@ -46,13 +48,21 @@ export declare class VeSync {
|
|
|
46
48
|
* @param username - VeSync account username
|
|
47
49
|
* @param password - VeSync account password
|
|
48
50
|
* @param timeZone - Optional timezone for device operations (defaults to America/New_York)
|
|
49
|
-
* @param
|
|
50
|
-
* @param redact - Optional redact mode flag
|
|
51
|
-
* @param apiUrl - Optional API base URL override
|
|
52
|
-
* @param customLogger - Optional custom logger implementation
|
|
53
|
-
* @param excludeConfig - Optional device exclusion configuration
|
|
54
|
-
*/
|
|
55
|
-
constructor(username: string, password: string, timeZone?: string,
|
|
51
|
+
* @param optionsOrDebug - Either options object or debug flag for backward compatibility
|
|
52
|
+
* @param redact - Optional redact mode flag (used when optionsOrDebug is boolean)
|
|
53
|
+
* @param apiUrl - Optional API base URL override (used when optionsOrDebug is boolean)
|
|
54
|
+
* @param customLogger - Optional custom logger implementation (used when optionsOrDebug is boolean)
|
|
55
|
+
* @param excludeConfig - Optional device exclusion configuration (used when optionsOrDebug is boolean)
|
|
56
|
+
*/
|
|
57
|
+
constructor(username: string, password: string, timeZone?: string, optionsOrDebug?: boolean | {
|
|
58
|
+
debug?: boolean;
|
|
59
|
+
redact?: boolean;
|
|
60
|
+
apiUrl?: string;
|
|
61
|
+
customLogger?: Logger;
|
|
62
|
+
excludeConfig?: ExcludeConfig;
|
|
63
|
+
region?: string;
|
|
64
|
+
debugMode?: boolean;
|
|
65
|
+
}, redact?: boolean, apiUrl?: string, customLogger?: Logger, excludeConfig?: ExcludeConfig);
|
|
56
66
|
/**
|
|
57
67
|
* Get/Set debug mode
|
|
58
68
|
*/
|
package/dist/lib/vesync.js
CHANGED
|
@@ -125,22 +125,37 @@ class VeSync {
|
|
|
125
125
|
* @param username - VeSync account username
|
|
126
126
|
* @param password - VeSync account password
|
|
127
127
|
* @param timeZone - Optional timezone for device operations (defaults to America/New_York)
|
|
128
|
-
* @param
|
|
129
|
-
* @param redact - Optional redact mode flag
|
|
130
|
-
* @param apiUrl - Optional API base URL override
|
|
131
|
-
* @param customLogger - Optional custom logger implementation
|
|
132
|
-
* @param excludeConfig - Optional device exclusion configuration
|
|
128
|
+
* @param optionsOrDebug - Either options object or debug flag for backward compatibility
|
|
129
|
+
* @param redact - Optional redact mode flag (used when optionsOrDebug is boolean)
|
|
130
|
+
* @param apiUrl - Optional API base URL override (used when optionsOrDebug is boolean)
|
|
131
|
+
* @param customLogger - Optional custom logger implementation (used when optionsOrDebug is boolean)
|
|
132
|
+
* @param excludeConfig - Optional device exclusion configuration (used when optionsOrDebug is boolean)
|
|
133
133
|
*/
|
|
134
|
-
constructor(username, password, timeZone = helpers_1.DEFAULT_TZ,
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
constructor(username, password, timeZone = helpers_1.DEFAULT_TZ, optionsOrDebug = false, redact = true, apiUrl, customLogger, excludeConfig) {
|
|
135
|
+
// Handle options object or backward compatible parameters
|
|
136
|
+
let options = {};
|
|
137
|
+
if (typeof optionsOrDebug === 'object' && optionsOrDebug !== null) {
|
|
138
|
+
// Using new options pattern
|
|
139
|
+
options = optionsOrDebug;
|
|
140
|
+
this._debug = options.debug || options.debugMode || false;
|
|
141
|
+
this._redact = options.redact !== undefined ? options.redact : true;
|
|
142
|
+
this._excludeConfig = options.excludeConfig || null;
|
|
143
|
+
this._region = options.region || 'US';
|
|
144
|
+
customLogger = options.customLogger;
|
|
145
|
+
apiUrl = options.apiUrl;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Using old parameter pattern for backward compatibility
|
|
149
|
+
this._debug = optionsOrDebug;
|
|
150
|
+
this._redact = redact;
|
|
151
|
+
this._excludeConfig = excludeConfig || null;
|
|
152
|
+
this._region = 'US';
|
|
153
|
+
}
|
|
137
154
|
this._energyUpdateInterval = DEFAULT_ENERGY_UPDATE_INTERVAL;
|
|
138
155
|
this._energyCheck = true;
|
|
139
156
|
this._lastUpdateTs = null;
|
|
140
157
|
this._inProcess = false;
|
|
141
|
-
this._excludeConfig = excludeConfig || null;
|
|
142
158
|
this._appId = (0, helpers_1.generateAppId)();
|
|
143
|
-
this._region = 'US';
|
|
144
159
|
this.username = username;
|
|
145
160
|
this.password = password;
|
|
146
161
|
this.token = null;
|
|
@@ -175,25 +190,28 @@ class VeSync {
|
|
|
175
190
|
this.timeZone = helpers_1.DEFAULT_TZ;
|
|
176
191
|
logger_1.logger.debug('Time zone is not a string');
|
|
177
192
|
}
|
|
178
|
-
// Set custom API URL if provided, otherwise use
|
|
193
|
+
// Set custom API URL if provided, otherwise use region-based endpoint
|
|
179
194
|
if (apiUrl) {
|
|
180
195
|
(0, helpers_1.setApiBaseUrl)(apiUrl);
|
|
181
196
|
}
|
|
182
197
|
else {
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
198
|
+
// Set endpoint based on region
|
|
199
|
+
const regionEndpoints = {
|
|
200
|
+
'US': 'https://smartapi.vesync.com',
|
|
201
|
+
'EU': 'https://smartapi.vesync.eu',
|
|
202
|
+
'CA': 'https://smartapi.vesync.com',
|
|
203
|
+
'MX': 'https://smartapi.vesync.com',
|
|
204
|
+
'JP': 'https://smartapi.vesync.com'
|
|
205
|
+
};
|
|
206
|
+
const endpoint = regionEndpoints[this._region] || 'https://smartapi.vesync.com';
|
|
207
|
+
(0, helpers_1.setApiBaseUrl)(endpoint);
|
|
208
|
+
(0, helpers_1.setCurrentRegion)(this._region);
|
|
186
209
|
}
|
|
187
210
|
// Set custom logger if provided
|
|
188
211
|
if (customLogger) {
|
|
189
212
|
(0, logger_1.setLogger)(customLogger);
|
|
190
213
|
}
|
|
191
|
-
|
|
192
|
-
this.debug = true;
|
|
193
|
-
}
|
|
194
|
-
if (redact) {
|
|
195
|
-
this.redact = true;
|
|
196
|
-
}
|
|
214
|
+
// Debug and redact are already set above in the options handling
|
|
197
215
|
}
|
|
198
216
|
/**
|
|
199
217
|
* Get/Set debug mode
|
|
@@ -443,20 +461,38 @@ class VeSync {
|
|
|
443
461
|
for (let attempt = 0; attempt < retryAttempts; attempt++) {
|
|
444
462
|
try {
|
|
445
463
|
logger_1.logger.debug(`Authentication attempt ${attempt + 1} of ${retryAttempts}`);
|
|
446
|
-
//
|
|
464
|
+
// First, try to detect user's region from email
|
|
465
|
+
const detectedRegion = (0, helpers_1.getRegionFromCountryCode)((0, helpers_1.getCountryCodeFromRegion)(this._region, this.username));
|
|
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
|
+
}
|
|
471
|
+
// Try new authentication flow first with detected region
|
|
447
472
|
let [success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, this._region);
|
|
473
|
+
// Track if new flow succeeded
|
|
474
|
+
if (success) {
|
|
475
|
+
this.authFlowUsed = 'new';
|
|
476
|
+
}
|
|
448
477
|
if (!success) {
|
|
449
478
|
// Check if we need to try a different region
|
|
450
479
|
if (countryCode === 'cross_region') {
|
|
451
|
-
logger_1.logger.debug('Cross-region error detected, trying
|
|
452
|
-
this._region
|
|
453
|
-
|
|
454
|
-
|
|
480
|
+
logger_1.logger.debug('Cross-region error detected, trying opposite region...');
|
|
481
|
+
const alternateRegion = this._region === 'US' ? 'EU' : 'US';
|
|
482
|
+
this._region = alternateRegion;
|
|
483
|
+
(0, helpers_1.setCurrentRegion)(alternateRegion);
|
|
484
|
+
[success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, alternateRegion);
|
|
485
|
+
if (success) {
|
|
486
|
+
this.authFlowUsed = 'new';
|
|
487
|
+
}
|
|
455
488
|
}
|
|
456
489
|
// If new flow still fails, try legacy authentication as fallback
|
|
457
490
|
if (!success) {
|
|
458
491
|
logger_1.logger.debug('New authentication flow failed, trying legacy flow...');
|
|
459
492
|
[success, token, accountId, countryCode] = await helpers_1.Helpers.authLegacyFlow(this);
|
|
493
|
+
if (success) {
|
|
494
|
+
this.authFlowUsed = 'legacy';
|
|
495
|
+
}
|
|
460
496
|
}
|
|
461
497
|
}
|
|
462
498
|
if (success && token && accountId) {
|
|
@@ -464,6 +500,7 @@ class VeSync {
|
|
|
464
500
|
this.accountId = accountId;
|
|
465
501
|
this.countryCode = countryCode;
|
|
466
502
|
this.enabled = true;
|
|
503
|
+
this.apiBaseUrl = (0, helpers_1.getApiBaseUrl)(); // Track the API endpoint being used
|
|
467
504
|
// Set region based on country code if we got one
|
|
468
505
|
if (countryCode) {
|
|
469
506
|
const detectedRegion = (0, helpers_1.getRegionFromCountryCode)(countryCode);
|
|
@@ -474,6 +511,7 @@ class VeSync {
|
|
|
474
511
|
}
|
|
475
512
|
}
|
|
476
513
|
logger_1.logger.debug('Authentication successful!', {
|
|
514
|
+
authFlow: this.authFlowUsed,
|
|
477
515
|
region: this._region,
|
|
478
516
|
countryCode: countryCode,
|
|
479
517
|
endpoint: (0, helpers_1.getApiBaseUrl)()
|
package/package.json
CHANGED
package/src/lib/helpers.ts
CHANGED
|
@@ -51,7 +51,7 @@ export function getRegionFromCountryCode(countryCode: string): string {
|
|
|
51
51
|
export const API_RATE_LIMIT = 30;
|
|
52
52
|
export const API_TIMEOUT = 15000;
|
|
53
53
|
|
|
54
|
-
export const APP_VERSION = '5.
|
|
54
|
+
export const APP_VERSION = '5.7.16'; // Updated to newer version
|
|
55
55
|
export const PHONE_BRAND = 'SM N9005';
|
|
56
56
|
export const PHONE_OS = 'Android';
|
|
57
57
|
export const CLIENT_INFO = 'SM N9005';
|
|
@@ -60,7 +60,7 @@ export const DEFAULT_TZ = 'America/New_York';
|
|
|
60
60
|
export const DEFAULT_REGION = 'US';
|
|
61
61
|
export const MOBILE_ID = '1234567890123456';
|
|
62
62
|
export const BYPASS_HEADER_UA = 'okhttp/3.12.1';
|
|
63
|
-
export const CLIENT_VERSION = 'VeSync 5.
|
|
63
|
+
export const CLIENT_VERSION = 'VeSync 5.7.16';
|
|
64
64
|
|
|
65
65
|
// Regional API endpoints
|
|
66
66
|
export const REGION_ENDPOINTS = {
|
|
@@ -71,13 +71,58 @@ export const REGION_ENDPOINTS = {
|
|
|
71
71
|
'EU': 'https://smartapi.vesync.eu'
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
// Cross-region error
|
|
75
|
-
export const
|
|
74
|
+
// Cross-region error codes - multiple versions exist
|
|
75
|
+
export const CROSS_REGION_ERROR_CODES = [-11260022, -11261022];
|
|
76
76
|
export const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
77
77
|
|
|
78
|
+
// Authentication error codes
|
|
79
|
+
export const AUTH_ERROR_CODES = {
|
|
80
|
+
ACCOUNT_PASSWORD_INCORRECT: -11201129,
|
|
81
|
+
ILLEGAL_ARGUMENT: -11000022,
|
|
82
|
+
CROSS_REGION: -11260022,
|
|
83
|
+
CROSS_REGION_ALT: -11261022
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Detect user's home region from email domain or country hints
|
|
88
|
+
*/
|
|
89
|
+
export function detectUserRegion(email: string): string {
|
|
90
|
+
// Check for common EU email domains
|
|
91
|
+
const euDomains = ['.de', '.fr', '.it', '.es', '.nl', '.be', '.at', '.dk', '.se', '.no', '.fi', '.eu'];
|
|
92
|
+
const emailLower = email.toLowerCase();
|
|
93
|
+
|
|
94
|
+
for (const domain of euDomains) {
|
|
95
|
+
if (emailLower.includes(domain)) {
|
|
96
|
+
return 'EU';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Default to US for unknown domains
|
|
101
|
+
return 'US';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get country code from region
|
|
106
|
+
*/
|
|
107
|
+
export function getCountryCodeFromRegion(region: string, email?: string): string {
|
|
108
|
+
if (region === 'EU') {
|
|
109
|
+
// Try to detect specific EU country from email
|
|
110
|
+
if (email) {
|
|
111
|
+
const emailLower = email.toLowerCase();
|
|
112
|
+
if (emailLower.includes('.de') || emailLower.includes('german')) return 'DE';
|
|
113
|
+
if (emailLower.includes('.fr')) return 'FR';
|
|
114
|
+
if (emailLower.includes('.it')) return 'IT';
|
|
115
|
+
if (emailLower.includes('.es')) return 'ES';
|
|
116
|
+
if (emailLower.includes('.nl')) return 'NL';
|
|
117
|
+
}
|
|
118
|
+
return 'DE'; // Default to Germany for EU
|
|
119
|
+
}
|
|
120
|
+
return 'US'; // Default for non-EU
|
|
121
|
+
}
|
|
122
|
+
|
|
78
123
|
// Generate unique APP_ID for each session
|
|
79
124
|
export function generateAppId(): string {
|
|
80
|
-
const chars = '
|
|
125
|
+
const chars = 'ABCDEFGHIJKLMNOPqRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
81
126
|
let result = '';
|
|
82
127
|
for (let i = 0; i < 8; i++) {
|
|
83
128
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
@@ -196,7 +241,9 @@ export class Helpers {
|
|
|
196
241
|
return {
|
|
197
242
|
'Content-Type': 'application/json; charset=UTF-8',
|
|
198
243
|
'User-Agent': BYPASS_HEADER_UA,
|
|
199
|
-
'accept-language': 'en'
|
|
244
|
+
'accept-language': 'en',
|
|
245
|
+
'appVersion': APP_VERSION,
|
|
246
|
+
'clientVersion': CLIENT_VERSION
|
|
200
247
|
};
|
|
201
248
|
}
|
|
202
249
|
|
|
@@ -238,7 +285,18 @@ export class Helpers {
|
|
|
238
285
|
/**
|
|
239
286
|
* Build request body for initial authentication step
|
|
240
287
|
*/
|
|
241
|
-
static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string): Record<string, any> {
|
|
288
|
+
static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string, region: string = 'US'): Record<string, any> {
|
|
289
|
+
// Detect user's home region and country code
|
|
290
|
+
const userRegion = detectUserRegion(manager.username);
|
|
291
|
+
const userCountryCode = getCountryCodeFromRegion(region === 'EU' ? 'EU' : userRegion, manager.username);
|
|
292
|
+
|
|
293
|
+
logger.debug('Authentication Step 1 - Region detection:', {
|
|
294
|
+
email: manager.username,
|
|
295
|
+
detectedRegion: userRegion,
|
|
296
|
+
requestRegion: region,
|
|
297
|
+
userCountryCode: userCountryCode
|
|
298
|
+
});
|
|
299
|
+
|
|
242
300
|
return {
|
|
243
301
|
'email': manager.username,
|
|
244
302
|
'method': 'authByPWDOrOTM',
|
|
@@ -254,7 +312,7 @@ export class Helpers {
|
|
|
254
312
|
'terminalId': terminalId,
|
|
255
313
|
'timeZone': manager.timeZone || DEFAULT_TZ,
|
|
256
314
|
'token': '',
|
|
257
|
-
'userCountryCode':
|
|
315
|
+
'userCountryCode': userCountryCode,
|
|
258
316
|
'appID': appId,
|
|
259
317
|
'sourceAppID': appId,
|
|
260
318
|
'traceId': `APP${appId}${Math.floor(Date.now() / 1000)}`
|
|
@@ -504,7 +562,7 @@ export class Helpers {
|
|
|
504
562
|
const terminalId = generateTerminalId();
|
|
505
563
|
|
|
506
564
|
// Step 1: Get authorization code
|
|
507
|
-
const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId);
|
|
565
|
+
const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId, region);
|
|
508
566
|
const step1Headers = this.reqHeaderAuth();
|
|
509
567
|
|
|
510
568
|
// Set the correct regional endpoint
|
|
@@ -536,9 +594,9 @@ export class Helpers {
|
|
|
536
594
|
if (authResponse.code !== 0) {
|
|
537
595
|
logger.error('Step 1 error code:', authResponse.code, 'message:', authResponse.msg);
|
|
538
596
|
|
|
539
|
-
// Handle cross-region error
|
|
540
|
-
if (authResponse.code
|
|
541
|
-
logger.debug('Cross-region error detected, will try different region');
|
|
597
|
+
// Handle cross-region error (multiple error codes possible)
|
|
598
|
+
if (CROSS_REGION_ERROR_CODES.includes(authResponse.code)) {
|
|
599
|
+
logger.debug('Cross-region error detected:', authResponse.code, 'will try different region');
|
|
542
600
|
setApiBaseUrl(originalUrl); // Restore original URL
|
|
543
601
|
return [false, null, null, 'cross_region'];
|
|
544
602
|
}
|
|
@@ -547,7 +605,7 @@ export class Helpers {
|
|
|
547
605
|
return [false, null, null, null];
|
|
548
606
|
}
|
|
549
607
|
|
|
550
|
-
const { authorizeCode, bizToken
|
|
608
|
+
const { authorizeCode, bizToken } = authResponse.result || {};
|
|
551
609
|
|
|
552
610
|
if (!authorizeCode) {
|
|
553
611
|
logger.error('Missing authorization code in step 1 response:', authResponse.result);
|
|
@@ -558,7 +616,9 @@ export class Helpers {
|
|
|
558
616
|
logger.debug('Step 1 successful, got authorization code');
|
|
559
617
|
|
|
560
618
|
// Step 2: Login with authorization code
|
|
561
|
-
|
|
619
|
+
// Use the appropriate country code based on the region we're authenticating with
|
|
620
|
+
const countryCodeForStep2 = region === 'EU' ? 'DE' : 'US';
|
|
621
|
+
const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, countryCodeForStep2);
|
|
562
622
|
|
|
563
623
|
logger.debug('Step 2: Logging in with authorization code...');
|
|
564
624
|
|
|
@@ -578,23 +638,35 @@ export class Helpers {
|
|
|
578
638
|
|
|
579
639
|
if (loginResponse.code !== 0) {
|
|
580
640
|
// Handle cross-region error with retry (like pyvesync PR #340)
|
|
581
|
-
if (loginResponse.code
|
|
641
|
+
if (CROSS_REGION_ERROR_CODES.includes(loginResponse.code)) {
|
|
582
642
|
logger.debug('Cross-region error detected in Step 2, retrying with region change token...');
|
|
583
643
|
|
|
584
644
|
const { bizToken: regionChangeToken, countryCode: newCountryCode } = loginResponse.result || {};
|
|
585
645
|
|
|
586
|
-
if (regionChangeToken) {
|
|
587
|
-
// Retry Step 2 with region change token
|
|
646
|
+
if (regionChangeToken && newCountryCode) {
|
|
647
|
+
// Retry Step 2 with region change token on the SAME endpoint
|
|
648
|
+
// The region change token tells the server to handle the cross-region authentication
|
|
588
649
|
const retryBody = {
|
|
589
|
-
|
|
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)}`,
|
|
590
663
|
bizToken: regionChangeToken,
|
|
591
|
-
regionChange: 'last_region'
|
|
592
|
-
userCountryCode: newCountryCode || userCountryCode
|
|
664
|
+
regionChange: 'last_region'
|
|
593
665
|
};
|
|
594
666
|
|
|
595
667
|
logger.debug('Retrying Step 2 with region change token...', {
|
|
596
668
|
newCountryCode,
|
|
597
|
-
|
|
669
|
+
currentEndpoint: getApiBaseUrl()
|
|
598
670
|
});
|
|
599
671
|
|
|
600
672
|
const [retryResponse, retryStatus] = await this.callApi(
|
|
@@ -605,17 +677,17 @@ export class Helpers {
|
|
|
605
677
|
manager
|
|
606
678
|
);
|
|
607
679
|
|
|
608
|
-
setApiBaseUrl(originalUrl); // Restore original URL after retry
|
|
609
|
-
|
|
610
680
|
if (retryResponse && retryResponse.code === 0) {
|
|
611
681
|
const { token, accountID, countryCode } = retryResponse.result || {};
|
|
612
682
|
if (token && accountID) {
|
|
613
683
|
logger.debug('Step 2 retry successful with region change');
|
|
684
|
+
setApiBaseUrl(originalUrl); // Restore original URL on success
|
|
614
685
|
return [true, token, accountID, countryCode || newCountryCode];
|
|
615
686
|
}
|
|
616
687
|
}
|
|
617
688
|
|
|
618
689
|
logger.error('Step 2 retry failed:', retryResponse);
|
|
690
|
+
setApiBaseUrl(originalUrl); // Restore original URL on failure
|
|
619
691
|
return [false, null, null, null];
|
|
620
692
|
} else {
|
|
621
693
|
logger.error('No region change token in cross-region error response');
|
|
@@ -637,7 +709,7 @@ export class Helpers {
|
|
|
637
709
|
}
|
|
638
710
|
|
|
639
711
|
logger.debug('Step 2 successful, got token and accountID');
|
|
640
|
-
return [true, token, accountID, countryCode ||
|
|
712
|
+
return [true, token, accountID, countryCode || countryCodeForStep2];
|
|
641
713
|
|
|
642
714
|
} catch (error) {
|
|
643
715
|
logger.error('New authentication flow error:', error);
|
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 } from './helpers';
|
|
5
|
+
import { Helpers, API_RATE_LIMIT, DEFAULT_TZ, setApiBaseUrl, getApiBaseUrl, generateAppId, getRegionFromCountryCode, setCurrentRegion, getCurrentRegion, REGION_ENDPOINTS, getCountryCodeFromRegion } from './helpers';
|
|
6
6
|
import { VeSyncBaseDevice } from './vesyncBaseDevice';
|
|
7
7
|
import { fanModules } from './vesyncFanImpl';
|
|
8
8
|
import { outletModules } from './vesyncOutletImpl';
|
|
@@ -171,6 +171,8 @@ export class VeSync {
|
|
|
171
171
|
enabled: boolean;
|
|
172
172
|
updateInterval: number;
|
|
173
173
|
timeZone: string;
|
|
174
|
+
authFlowUsed?: 'legacy' | 'new'; // Track which auth flow was used
|
|
175
|
+
apiBaseUrl?: string; // Track which API endpoint is being used
|
|
174
176
|
|
|
175
177
|
fans: VeSyncBaseDevice[];
|
|
176
178
|
outlets: VeSyncBaseDevice[];
|
|
@@ -183,31 +185,54 @@ export class VeSync {
|
|
|
183
185
|
* @param username - VeSync account username
|
|
184
186
|
* @param password - VeSync account password
|
|
185
187
|
* @param timeZone - Optional timezone for device operations (defaults to America/New_York)
|
|
186
|
-
* @param
|
|
187
|
-
* @param redact - Optional redact mode flag
|
|
188
|
-
* @param apiUrl - Optional API base URL override
|
|
189
|
-
* @param customLogger - Optional custom logger implementation
|
|
190
|
-
* @param excludeConfig - Optional device exclusion configuration
|
|
188
|
+
* @param optionsOrDebug - Either options object or debug flag for backward compatibility
|
|
189
|
+
* @param redact - Optional redact mode flag (used when optionsOrDebug is boolean)
|
|
190
|
+
* @param apiUrl - Optional API base URL override (used when optionsOrDebug is boolean)
|
|
191
|
+
* @param customLogger - Optional custom logger implementation (used when optionsOrDebug is boolean)
|
|
192
|
+
* @param excludeConfig - Optional device exclusion configuration (used when optionsOrDebug is boolean)
|
|
191
193
|
*/
|
|
192
194
|
constructor(
|
|
193
195
|
username: string,
|
|
194
196
|
password: string,
|
|
195
197
|
timeZone: string = DEFAULT_TZ,
|
|
196
|
-
|
|
198
|
+
optionsOrDebug: boolean | {
|
|
199
|
+
debug?: boolean;
|
|
200
|
+
redact?: boolean;
|
|
201
|
+
apiUrl?: string;
|
|
202
|
+
customLogger?: Logger;
|
|
203
|
+
excludeConfig?: ExcludeConfig;
|
|
204
|
+
region?: string;
|
|
205
|
+
debugMode?: boolean; // Alias for debug
|
|
206
|
+
} = false,
|
|
197
207
|
redact = true,
|
|
198
208
|
apiUrl?: string,
|
|
199
209
|
customLogger?: Logger,
|
|
200
210
|
excludeConfig?: ExcludeConfig
|
|
201
211
|
) {
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
// Handle options object or backward compatible parameters
|
|
213
|
+
let options: any = {};
|
|
214
|
+
if (typeof optionsOrDebug === 'object' && optionsOrDebug !== null) {
|
|
215
|
+
// Using new options pattern
|
|
216
|
+
options = optionsOrDebug;
|
|
217
|
+
this._debug = options.debug || options.debugMode || false;
|
|
218
|
+
this._redact = options.redact !== undefined ? options.redact : true;
|
|
219
|
+
this._excludeConfig = options.excludeConfig || null;
|
|
220
|
+
this._region = options.region || 'US';
|
|
221
|
+
customLogger = options.customLogger;
|
|
222
|
+
apiUrl = options.apiUrl;
|
|
223
|
+
} else {
|
|
224
|
+
// Using old parameter pattern for backward compatibility
|
|
225
|
+
this._debug = optionsOrDebug as boolean;
|
|
226
|
+
this._redact = redact;
|
|
227
|
+
this._excludeConfig = excludeConfig || null;
|
|
228
|
+
this._region = 'US';
|
|
229
|
+
}
|
|
230
|
+
|
|
204
231
|
this._energyUpdateInterval = DEFAULT_ENERGY_UPDATE_INTERVAL;
|
|
205
232
|
this._energyCheck = true;
|
|
206
233
|
this._lastUpdateTs = null;
|
|
207
234
|
this._inProcess = false;
|
|
208
|
-
this._excludeConfig = excludeConfig || null;
|
|
209
235
|
this._appId = generateAppId();
|
|
210
|
-
this._region = 'US';
|
|
211
236
|
|
|
212
237
|
this.username = username;
|
|
213
238
|
this.password = password;
|
|
@@ -245,13 +270,21 @@ export class VeSync {
|
|
|
245
270
|
logger.debug('Time zone is not a string');
|
|
246
271
|
}
|
|
247
272
|
|
|
248
|
-
// Set custom API URL if provided, otherwise use
|
|
273
|
+
// Set custom API URL if provided, otherwise use region-based endpoint
|
|
249
274
|
if (apiUrl) {
|
|
250
275
|
setApiBaseUrl(apiUrl);
|
|
251
276
|
} else {
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
277
|
+
// Set endpoint based on region
|
|
278
|
+
const regionEndpoints: Record<string, string> = {
|
|
279
|
+
'US': 'https://smartapi.vesync.com',
|
|
280
|
+
'EU': 'https://smartapi.vesync.eu',
|
|
281
|
+
'CA': 'https://smartapi.vesync.com',
|
|
282
|
+
'MX': 'https://smartapi.vesync.com',
|
|
283
|
+
'JP': 'https://smartapi.vesync.com'
|
|
284
|
+
};
|
|
285
|
+
const endpoint = regionEndpoints[this._region] || 'https://smartapi.vesync.com';
|
|
286
|
+
setApiBaseUrl(endpoint);
|
|
287
|
+
setCurrentRegion(this._region);
|
|
255
288
|
}
|
|
256
289
|
|
|
257
290
|
// Set custom logger if provided
|
|
@@ -259,13 +292,7 @@ export class VeSync {
|
|
|
259
292
|
setLogger(customLogger);
|
|
260
293
|
}
|
|
261
294
|
|
|
262
|
-
|
|
263
|
-
this.debug = true;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (redact) {
|
|
267
|
-
this.redact = true;
|
|
268
|
-
}
|
|
295
|
+
// Debug and redact are already set above in the options handling
|
|
269
296
|
}
|
|
270
297
|
|
|
271
298
|
/**
|
|
@@ -548,22 +575,42 @@ export class VeSync {
|
|
|
548
575
|
try {
|
|
549
576
|
logger.debug(`Authentication attempt ${attempt + 1} of ${retryAttempts}`);
|
|
550
577
|
|
|
551
|
-
//
|
|
578
|
+
// First, try to detect user's region from email
|
|
579
|
+
const detectedRegion = getRegionFromCountryCode(getCountryCodeFromRegion(this._region, this.username));
|
|
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
|
+
}
|
|
585
|
+
|
|
586
|
+
// Try new authentication flow first with detected region
|
|
552
587
|
let [success, token, accountId, countryCode] = await Helpers.authNewFlow(this, this._appId, this._region);
|
|
588
|
+
|
|
589
|
+
// Track if new flow succeeded
|
|
590
|
+
if (success) {
|
|
591
|
+
this.authFlowUsed = 'new';
|
|
592
|
+
}
|
|
553
593
|
|
|
554
594
|
if (!success) {
|
|
555
595
|
// Check if we need to try a different region
|
|
556
596
|
if (countryCode === 'cross_region') {
|
|
557
|
-
logger.debug('Cross-region error detected, trying
|
|
558
|
-
this._region
|
|
559
|
-
|
|
560
|
-
|
|
597
|
+
logger.debug('Cross-region error detected, trying opposite region...');
|
|
598
|
+
const alternateRegion = this._region === 'US' ? 'EU' : 'US';
|
|
599
|
+
this._region = alternateRegion;
|
|
600
|
+
setCurrentRegion(alternateRegion);
|
|
601
|
+
[success, token, accountId, countryCode] = await Helpers.authNewFlow(this, this._appId, alternateRegion);
|
|
602
|
+
if (success) {
|
|
603
|
+
this.authFlowUsed = 'new';
|
|
604
|
+
}
|
|
561
605
|
}
|
|
562
606
|
|
|
563
607
|
// If new flow still fails, try legacy authentication as fallback
|
|
564
608
|
if (!success) {
|
|
565
609
|
logger.debug('New authentication flow failed, trying legacy flow...');
|
|
566
610
|
[success, token, accountId, countryCode] = await Helpers.authLegacyFlow(this);
|
|
611
|
+
if (success) {
|
|
612
|
+
this.authFlowUsed = 'legacy';
|
|
613
|
+
}
|
|
567
614
|
}
|
|
568
615
|
}
|
|
569
616
|
|
|
@@ -572,6 +619,7 @@ export class VeSync {
|
|
|
572
619
|
this.accountId = accountId;
|
|
573
620
|
this.countryCode = countryCode;
|
|
574
621
|
this.enabled = true;
|
|
622
|
+
this.apiBaseUrl = getApiBaseUrl(); // Track the API endpoint being used
|
|
575
623
|
|
|
576
624
|
// Set region based on country code if we got one
|
|
577
625
|
if (countryCode) {
|
|
@@ -584,6 +632,7 @@ export class VeSync {
|
|
|
584
632
|
}
|
|
585
633
|
|
|
586
634
|
logger.debug('Authentication successful!', {
|
|
635
|
+
authFlow: this.authFlowUsed,
|
|
587
636
|
region: this._region,
|
|
588
637
|
countryCode: countryCode,
|
|
589
638
|
endpoint: getApiBaseUrl()
|