tsvesync 1.0.96 → 1.0.97
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 +7 -0
- package/dist/lib/helpers.d.ts +64 -1
- package/dist/lib/helpers.js +198 -3
- package/dist/lib/vesync.d.ts +15 -1
- package/dist/lib/vesync.js +70 -37
- package/package.json +1 -1
- package/src/lib/helpers.ts +285 -2
- package/src/lib/vesync.ts +85 -49
- package/dist/__tests__/fans/vesyncAir131.test.d.ts +0 -4
- package/dist/__tests__/fans/vesyncAir131.test.js +0 -365
- package/dist/__tests__/fans/vesyncAirBaseV2.test.d.ts +0 -4
- package/dist/__tests__/fans/vesyncAirBaseV2.test.js +0 -283
- package/dist/__tests__/fans/vesyncAirBypass.test.d.ts +0 -4
- package/dist/__tests__/fans/vesyncAirBypass.test.js +0 -257
- package/dist/__tests__/vesyncFan.test.d.ts +0 -4
- package/dist/__tests__/vesyncFan.test.js +0 -263
package/README.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
A TypeScript library for interacting with VeSync smart home devices. This library provides a modern, type-safe interface for controlling VeSync devices including air purifiers, outlets, and switches.
|
|
4
4
|
|
|
5
|
+
## 🆕 Recent Updates
|
|
6
|
+
|
|
7
|
+
- **Enhanced Authentication**: Now supports the new VeSync authentication flow (pyvesync PR #340) with automatic fallback to legacy authentication
|
|
8
|
+
- **Regional API Support**: Automatic detection and routing for US (`smartapi.vesync.com`) and EU (`smartapi.vesync.eu`) endpoints
|
|
9
|
+
- **Improved Error Handling**: Better recovery from API changes and authentication errors
|
|
10
|
+
- **Cross-Region Support**: Handles cross-region authentication errors automatically
|
|
11
|
+
|
|
5
12
|
## Features
|
|
6
13
|
|
|
7
14
|
- Full TypeScript support with type definitions
|
package/dist/lib/helpers.d.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
import { VeSync } from './vesync';
|
|
5
5
|
export declare function getApiBaseUrl(): string;
|
|
6
6
|
export declare function setApiBaseUrl(url: string): void;
|
|
7
|
+
export declare function getCurrentRegion(): string;
|
|
8
|
+
export declare function setCurrentRegion(region: string): void;
|
|
9
|
+
export declare function getRegionFromCountryCode(countryCode: string): string;
|
|
7
10
|
export declare const API_RATE_LIMIT = 30;
|
|
8
11
|
export declare const API_TIMEOUT = 15000;
|
|
9
|
-
export declare const APP_VERSION = "
|
|
12
|
+
export declare const APP_VERSION = "5.6.60";
|
|
10
13
|
export declare const PHONE_BRAND = "SM N9005";
|
|
11
14
|
export declare const PHONE_OS = "Android";
|
|
12
15
|
export declare const USER_TYPE = "1";
|
|
@@ -14,6 +17,17 @@ export declare const DEFAULT_TZ = "America/New_York";
|
|
|
14
17
|
export declare const DEFAULT_REGION = "US";
|
|
15
18
|
export declare const MOBILE_ID = "1234567890123456";
|
|
16
19
|
export declare const BYPASS_HEADER_UA = "okhttp/3.12.1";
|
|
20
|
+
export declare const CLIENT_VERSION = "VeSync 5.6.60";
|
|
21
|
+
export declare const REGION_ENDPOINTS: {
|
|
22
|
+
US: string;
|
|
23
|
+
CA: string;
|
|
24
|
+
MX: string;
|
|
25
|
+
JP: string;
|
|
26
|
+
EU: string;
|
|
27
|
+
};
|
|
28
|
+
export declare const CROSS_REGION_ERROR_CODE = -11260022;
|
|
29
|
+
export declare const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
30
|
+
export declare function generateAppId(): string;
|
|
17
31
|
export interface RequestBody {
|
|
18
32
|
acceptLanguage?: string;
|
|
19
33
|
accountID?: string;
|
|
@@ -33,8 +47,37 @@ export interface RequestBody {
|
|
|
33
47
|
userType?: string;
|
|
34
48
|
uuid?: string;
|
|
35
49
|
status?: string;
|
|
50
|
+
authorizeCode?: string;
|
|
51
|
+
bizToken?: string;
|
|
52
|
+
regionChange?: string;
|
|
53
|
+
userCountryCode?: string;
|
|
54
|
+
authProtocolType?: string;
|
|
55
|
+
clientType?: string;
|
|
56
|
+
sourceAppID?: string;
|
|
57
|
+
appID?: string;
|
|
58
|
+
clientVersion?: string;
|
|
36
59
|
[key: string]: any;
|
|
37
60
|
}
|
|
61
|
+
export interface AuthResponse {
|
|
62
|
+
code: number;
|
|
63
|
+
msg: string;
|
|
64
|
+
result?: {
|
|
65
|
+
authorizeCode?: string;
|
|
66
|
+
bizToken?: string;
|
|
67
|
+
userCountryCode?: string;
|
|
68
|
+
[key: string]: any;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export interface LoginResponse {
|
|
72
|
+
code: number;
|
|
73
|
+
msg: string;
|
|
74
|
+
result?: {
|
|
75
|
+
token?: string;
|
|
76
|
+
accountID?: string;
|
|
77
|
+
countryCode?: string;
|
|
78
|
+
[key: string]: any;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
38
81
|
export declare class Helpers {
|
|
39
82
|
static shouldRedact: boolean;
|
|
40
83
|
/**
|
|
@@ -49,6 +92,10 @@ export declare class Helpers {
|
|
|
49
92
|
* Build header for api requests on 'bypass' endpoint
|
|
50
93
|
*/
|
|
51
94
|
static reqHeaderBypass(): Record<string, string>;
|
|
95
|
+
/**
|
|
96
|
+
* Build header for new authentication endpoints
|
|
97
|
+
*/
|
|
98
|
+
static reqHeaderAuth(): Record<string, string>;
|
|
52
99
|
/**
|
|
53
100
|
* Return universal keys for body of api requests
|
|
54
101
|
*/
|
|
@@ -61,6 +108,14 @@ export declare class Helpers {
|
|
|
61
108
|
* Detail keys for api requests
|
|
62
109
|
*/
|
|
63
110
|
static reqBodyDetails(): Record<string, string>;
|
|
111
|
+
/**
|
|
112
|
+
* Build request body for initial authentication step
|
|
113
|
+
*/
|
|
114
|
+
static reqBodyAuthStep1(manager: VeSync, appId: string): Record<string, any>;
|
|
115
|
+
/**
|
|
116
|
+
* Build request body for second authentication step (login with authorize code)
|
|
117
|
+
*/
|
|
118
|
+
static reqBodyAuthStep2(authorizeCode: string, bizToken: string, appId: string, userCountryCode?: string): Record<string, any>;
|
|
64
119
|
/**
|
|
65
120
|
* Builder for body of api requests
|
|
66
121
|
*/
|
|
@@ -85,4 +140,12 @@ export declare class Helpers {
|
|
|
85
140
|
* Calculate MD5 hash
|
|
86
141
|
*/
|
|
87
142
|
static md5(text: string): string;
|
|
143
|
+
/**
|
|
144
|
+
* Perform new two-step authentication flow
|
|
145
|
+
*/
|
|
146
|
+
static authNewFlow(manager: VeSync, appId: string, region?: string): Promise<[boolean, string | null, string | null, string | null]>;
|
|
147
|
+
/**
|
|
148
|
+
* Perform legacy authentication (fallback)
|
|
149
|
+
*/
|
|
150
|
+
static authLegacyFlow(manager: VeSync): Promise<[boolean, string | null, string | null, string | null]>;
|
|
88
151
|
}
|
package/dist/lib/helpers.js
CHANGED
|
@@ -6,23 +6,51 @@ 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.BYPASS_HEADER_UA = exports.MOBILE_ID = exports.DEFAULT_REGION = exports.DEFAULT_TZ = exports.USER_TYPE = exports.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = void 0;
|
|
9
|
+
exports.Helpers = exports.APP_VERSION_TOO_LOW_ERROR_CODE = exports.CROSS_REGION_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.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
|
+
exports.getCurrentRegion = getCurrentRegion;
|
|
13
|
+
exports.setCurrentRegion = setCurrentRegion;
|
|
14
|
+
exports.getRegionFromCountryCode = getRegionFromCountryCode;
|
|
15
|
+
exports.generateAppId = generateAppId;
|
|
12
16
|
const axios_1 = __importDefault(require("axios"));
|
|
13
17
|
const crypto_1 = __importDefault(require("crypto"));
|
|
14
18
|
const logger_1 = require("./logger");
|
|
15
|
-
// API configuration -
|
|
19
|
+
// API configuration - Support regional endpoints
|
|
16
20
|
let _apiBaseUrl = 'https://smartapi.vesync.com';
|
|
21
|
+
let _currentRegion = 'US';
|
|
17
22
|
function getApiBaseUrl() {
|
|
18
23
|
return _apiBaseUrl;
|
|
19
24
|
}
|
|
20
25
|
function setApiBaseUrl(url) {
|
|
21
26
|
_apiBaseUrl = url;
|
|
22
27
|
}
|
|
28
|
+
function getCurrentRegion() {
|
|
29
|
+
return _currentRegion;
|
|
30
|
+
}
|
|
31
|
+
function setCurrentRegion(region) {
|
|
32
|
+
_currentRegion = region;
|
|
33
|
+
if (region in exports.REGION_ENDPOINTS) {
|
|
34
|
+
_apiBaseUrl = exports.REGION_ENDPOINTS[region];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Determine region from country code
|
|
38
|
+
function getRegionFromCountryCode(countryCode) {
|
|
39
|
+
const euCountries = [
|
|
40
|
+
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
|
|
41
|
+
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
|
|
42
|
+
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', 'IS', 'LI',
|
|
43
|
+
'NO', 'CH'
|
|
44
|
+
];
|
|
45
|
+
if (euCountries.includes(countryCode)) {
|
|
46
|
+
return 'EU';
|
|
47
|
+
}
|
|
48
|
+
// Default to US for other countries
|
|
49
|
+
return 'US';
|
|
50
|
+
}
|
|
23
51
|
exports.API_RATE_LIMIT = 30;
|
|
24
52
|
exports.API_TIMEOUT = 15000;
|
|
25
|
-
exports.APP_VERSION = '
|
|
53
|
+
exports.APP_VERSION = '5.6.60';
|
|
26
54
|
exports.PHONE_BRAND = 'SM N9005';
|
|
27
55
|
exports.PHONE_OS = 'Android';
|
|
28
56
|
exports.USER_TYPE = '1';
|
|
@@ -30,6 +58,27 @@ exports.DEFAULT_TZ = 'America/New_York';
|
|
|
30
58
|
exports.DEFAULT_REGION = 'US';
|
|
31
59
|
exports.MOBILE_ID = '1234567890123456';
|
|
32
60
|
exports.BYPASS_HEADER_UA = 'okhttp/3.12.1';
|
|
61
|
+
exports.CLIENT_VERSION = 'VeSync 5.6.60';
|
|
62
|
+
// Regional API endpoints
|
|
63
|
+
exports.REGION_ENDPOINTS = {
|
|
64
|
+
'US': 'https://smartapi.vesync.com',
|
|
65
|
+
'CA': 'https://smartapi.vesync.com',
|
|
66
|
+
'MX': 'https://smartapi.vesync.com',
|
|
67
|
+
'JP': 'https://smartapi.vesync.com',
|
|
68
|
+
'EU': 'https://smartapi.vesync.eu'
|
|
69
|
+
};
|
|
70
|
+
// Cross-region error code
|
|
71
|
+
exports.CROSS_REGION_ERROR_CODE = -11260022;
|
|
72
|
+
exports.APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
|
|
73
|
+
// Generate unique APP_ID for each session
|
|
74
|
+
function generateAppId() {
|
|
75
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
76
|
+
let result = '';
|
|
77
|
+
for (let i = 0; i < 8; i++) {
|
|
78
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
33
82
|
class Helpers {
|
|
34
83
|
/**
|
|
35
84
|
* Calculate MD5 hash
|
|
@@ -64,6 +113,16 @@ class Helpers {
|
|
|
64
113
|
'User-Agent': exports.BYPASS_HEADER_UA
|
|
65
114
|
};
|
|
66
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Build header for new authentication endpoints
|
|
118
|
+
*/
|
|
119
|
+
static reqHeaderAuth() {
|
|
120
|
+
return {
|
|
121
|
+
'Content-Type': 'application/json; charset=UTF-8',
|
|
122
|
+
'User-Agent': exports.BYPASS_HEADER_UA,
|
|
123
|
+
'accept-language': 'en'
|
|
124
|
+
};
|
|
125
|
+
}
|
|
67
126
|
/**
|
|
68
127
|
* Return universal keys for body of api requests
|
|
69
128
|
*/
|
|
@@ -96,6 +155,42 @@ class Helpers {
|
|
|
96
155
|
'traceId': Date.now().toString()
|
|
97
156
|
};
|
|
98
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Build request body for initial authentication step
|
|
160
|
+
*/
|
|
161
|
+
static reqBodyAuthStep1(manager, appId) {
|
|
162
|
+
return {
|
|
163
|
+
'email': manager.username,
|
|
164
|
+
'password': this.hashPassword(manager.password),
|
|
165
|
+
'timeZone': manager.timeZone,
|
|
166
|
+
'acceptLanguage': 'en',
|
|
167
|
+
'appVersion': exports.APP_VERSION,
|
|
168
|
+
'phoneBrand': exports.PHONE_BRAND,
|
|
169
|
+
'phoneOS': exports.PHONE_OS,
|
|
170
|
+
'traceId': Date.now().toString(),
|
|
171
|
+
'authProtocolType': 'pwd',
|
|
172
|
+
'clientType': 'android',
|
|
173
|
+
'sourceAppID': 'vesync_android',
|
|
174
|
+
'appID': appId,
|
|
175
|
+
'clientVersion': exports.CLIENT_VERSION
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Build request body for second authentication step (login with authorize code)
|
|
180
|
+
*/
|
|
181
|
+
static reqBodyAuthStep2(authorizeCode, bizToken, appId, userCountryCode) {
|
|
182
|
+
const body = {
|
|
183
|
+
'authorizeCode': authorizeCode,
|
|
184
|
+
'bizToken': bizToken,
|
|
185
|
+
'appID': appId,
|
|
186
|
+
'clientVersion': exports.CLIENT_VERSION,
|
|
187
|
+
'acceptLanguage': 'en'
|
|
188
|
+
};
|
|
189
|
+
if (userCountryCode) {
|
|
190
|
+
body.userCountryCode = userCountryCode;
|
|
191
|
+
}
|
|
192
|
+
return body;
|
|
193
|
+
}
|
|
99
194
|
/**
|
|
100
195
|
* Builder for body of api requests
|
|
101
196
|
*/
|
|
@@ -270,6 +365,106 @@ class Helpers {
|
|
|
270
365
|
static md5(text) {
|
|
271
366
|
return crypto_1.default.createHash('md5').update(text).digest('hex');
|
|
272
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Perform new two-step authentication flow
|
|
370
|
+
*/
|
|
371
|
+
static async authNewFlow(manager, appId, region = 'US') {
|
|
372
|
+
try {
|
|
373
|
+
// Step 1: Get authorization code
|
|
374
|
+
const step1Body = this.reqBodyAuthStep1(manager, appId);
|
|
375
|
+
const step1Headers = this.reqHeaderAuth();
|
|
376
|
+
// Set the correct regional endpoint
|
|
377
|
+
const originalUrl = getApiBaseUrl();
|
|
378
|
+
if (region in exports.REGION_ENDPOINTS) {
|
|
379
|
+
setApiBaseUrl(exports.REGION_ENDPOINTS[region]);
|
|
380
|
+
}
|
|
381
|
+
logger_1.logger.debug('Step 1: Getting authorization code...', {
|
|
382
|
+
region,
|
|
383
|
+
endpoint: getApiBaseUrl(),
|
|
384
|
+
body: { ...step1Body, password: '[REDACTED]' }
|
|
385
|
+
});
|
|
386
|
+
const [authResponse, authStatus] = await this.callApi('/globalPlatform/api/accountAuth/v1/authByPWDOrOTM', 'post', step1Body, step1Headers, manager);
|
|
387
|
+
if (!authResponse || authStatus !== 200) {
|
|
388
|
+
logger_1.logger.error('Step 1 failed:', authResponse);
|
|
389
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
390
|
+
return [false, null, null, null];
|
|
391
|
+
}
|
|
392
|
+
if (authResponse.code !== 0) {
|
|
393
|
+
logger_1.logger.error('Step 1 error code:', authResponse.code, 'message:', authResponse.msg);
|
|
394
|
+
// Handle cross-region error
|
|
395
|
+
if (authResponse.code === exports.CROSS_REGION_ERROR_CODE) {
|
|
396
|
+
logger_1.logger.debug('Cross-region error detected, will try different region');
|
|
397
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
398
|
+
return [false, null, null, 'cross_region'];
|
|
399
|
+
}
|
|
400
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
401
|
+
return [false, null, null, null];
|
|
402
|
+
}
|
|
403
|
+
const { authorizeCode, bizToken, userCountryCode } = authResponse.result || {};
|
|
404
|
+
if (!authorizeCode || !bizToken) {
|
|
405
|
+
logger_1.logger.error('Missing required fields in step 1 response:', authResponse.result);
|
|
406
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
407
|
+
return [false, null, null, null];
|
|
408
|
+
}
|
|
409
|
+
logger_1.logger.debug('Step 1 successful, got authorization code');
|
|
410
|
+
// Step 2: Login with authorization code
|
|
411
|
+
const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, userCountryCode);
|
|
412
|
+
logger_1.logger.debug('Step 2: Logging in with authorization code...');
|
|
413
|
+
const [loginResponse, loginStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', step2Body, step1Headers, manager);
|
|
414
|
+
setApiBaseUrl(originalUrl); // Restore original URL
|
|
415
|
+
if (!loginResponse || loginStatus !== 200) {
|
|
416
|
+
logger_1.logger.error('Step 2 failed:', loginResponse);
|
|
417
|
+
return [false, null, null, null];
|
|
418
|
+
}
|
|
419
|
+
if (loginResponse.code !== 0) {
|
|
420
|
+
logger_1.logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
|
|
421
|
+
return [false, null, null, null];
|
|
422
|
+
}
|
|
423
|
+
const { token, accountID, countryCode } = loginResponse.result || {};
|
|
424
|
+
if (!token || !accountID) {
|
|
425
|
+
logger_1.logger.error('Missing required fields in step 2 response:', loginResponse.result);
|
|
426
|
+
return [false, null, null, null];
|
|
427
|
+
}
|
|
428
|
+
logger_1.logger.debug('Step 2 successful, got token and accountID');
|
|
429
|
+
return [true, token, accountID, countryCode || userCountryCode];
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
logger_1.logger.error('New authentication flow error:', error);
|
|
433
|
+
return [false, null, null, null];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Perform legacy authentication (fallback)
|
|
438
|
+
*/
|
|
439
|
+
static async authLegacyFlow(manager) {
|
|
440
|
+
try {
|
|
441
|
+
const body = this.reqBody(manager, 'login');
|
|
442
|
+
logger_1.logger.debug('Legacy login attempt...', {
|
|
443
|
+
endpoint: getApiBaseUrl(),
|
|
444
|
+
body: { ...body, password: '[REDACTED]' }
|
|
445
|
+
});
|
|
446
|
+
const [response, status] = await this.callApi('/cloud/v1/user/login', 'post', body, {}, manager);
|
|
447
|
+
if (!response || status !== 200) {
|
|
448
|
+
logger_1.logger.error('Legacy login failed:', response);
|
|
449
|
+
return [false, null, null, null];
|
|
450
|
+
}
|
|
451
|
+
if (response.code && response.code !== 0) {
|
|
452
|
+
logger_1.logger.error('Legacy login error code:', response.code, 'message:', response.msg);
|
|
453
|
+
return [false, null, null, null];
|
|
454
|
+
}
|
|
455
|
+
const { token, accountID, countryCode } = response.result || {};
|
|
456
|
+
if (!token || !accountID) {
|
|
457
|
+
logger_1.logger.error('Missing required fields in legacy login response:', response.result);
|
|
458
|
+
return [false, null, null, null];
|
|
459
|
+
}
|
|
460
|
+
logger_1.logger.debug('Legacy login successful');
|
|
461
|
+
return [true, token, accountID, countryCode];
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
logger_1.logger.error('Legacy authentication flow error:', error);
|
|
465
|
+
return [false, null, null, null];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
273
468
|
}
|
|
274
469
|
exports.Helpers = Helpers;
|
|
275
470
|
Helpers.shouldRedact = true;
|
package/dist/lib/vesync.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare class VeSync {
|
|
|
25
25
|
private _lastUpdateTs;
|
|
26
26
|
private _inProcess;
|
|
27
27
|
private _excludeConfig;
|
|
28
|
+
private _appId;
|
|
29
|
+
private _region;
|
|
28
30
|
username: string;
|
|
29
31
|
password: string;
|
|
30
32
|
token: string | null;
|
|
@@ -66,6 +68,18 @@ export declare class VeSync {
|
|
|
66
68
|
*/
|
|
67
69
|
get energyUpdateInterval(): number;
|
|
68
70
|
set energyUpdateInterval(interval: number);
|
|
71
|
+
/**
|
|
72
|
+
* Get current App ID
|
|
73
|
+
*/
|
|
74
|
+
get appId(): string;
|
|
75
|
+
/**
|
|
76
|
+
* Get current region
|
|
77
|
+
*/
|
|
78
|
+
get region(): string;
|
|
79
|
+
/**
|
|
80
|
+
* Set region and update API endpoint
|
|
81
|
+
*/
|
|
82
|
+
set region(region: string);
|
|
69
83
|
/**
|
|
70
84
|
* Test if device should be removed
|
|
71
85
|
*/
|
|
@@ -91,7 +105,7 @@ export declare class VeSync {
|
|
|
91
105
|
*/
|
|
92
106
|
getDevices(): Promise<boolean>;
|
|
93
107
|
/**
|
|
94
|
-
* Login to VeSync server
|
|
108
|
+
* Login to VeSync server with new authentication flow and backwards compatibility
|
|
95
109
|
*/
|
|
96
110
|
login(retryAttempts?: number, initialDelayMs?: number): Promise<boolean>;
|
|
97
111
|
/**
|
package/dist/lib/vesync.js
CHANGED
|
@@ -139,6 +139,8 @@ class VeSync {
|
|
|
139
139
|
this._lastUpdateTs = null;
|
|
140
140
|
this._inProcess = false;
|
|
141
141
|
this._excludeConfig = excludeConfig || null;
|
|
142
|
+
this._appId = (0, helpers_1.generateAppId)();
|
|
143
|
+
this._region = 'US';
|
|
142
144
|
this.username = username;
|
|
143
145
|
this.password = password;
|
|
144
146
|
this.token = null;
|
|
@@ -178,8 +180,9 @@ class VeSync {
|
|
|
178
180
|
(0, helpers_1.setApiBaseUrl)(apiUrl);
|
|
179
181
|
}
|
|
180
182
|
else {
|
|
181
|
-
//
|
|
183
|
+
// Start with US endpoint as default
|
|
182
184
|
(0, helpers_1.setApiBaseUrl)('https://smartapi.vesync.com');
|
|
185
|
+
(0, helpers_1.setCurrentRegion)('US');
|
|
183
186
|
}
|
|
184
187
|
// Set custom logger if provided
|
|
185
188
|
if (customLogger) {
|
|
@@ -222,6 +225,27 @@ class VeSync {
|
|
|
222
225
|
this._energyUpdateInterval = interval;
|
|
223
226
|
}
|
|
224
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Get current App ID
|
|
230
|
+
*/
|
|
231
|
+
get appId() {
|
|
232
|
+
return this._appId;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get current region
|
|
236
|
+
*/
|
|
237
|
+
get region() {
|
|
238
|
+
return this._region;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Set region and update API endpoint
|
|
242
|
+
*/
|
|
243
|
+
set region(region) {
|
|
244
|
+
if (region in helpers_1.REGION_ENDPOINTS) {
|
|
245
|
+
this._region = region;
|
|
246
|
+
(0, helpers_1.setCurrentRegion)(region);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
225
249
|
/**
|
|
226
250
|
* Test if device should be removed
|
|
227
251
|
*/
|
|
@@ -408,62 +432,71 @@ class VeSync {
|
|
|
408
432
|
return success;
|
|
409
433
|
}
|
|
410
434
|
/**
|
|
411
|
-
* Login to VeSync server
|
|
435
|
+
* Login to VeSync server with new authentication flow and backwards compatibility
|
|
412
436
|
*/
|
|
413
437
|
async login(retryAttempts = 3, initialDelayMs = 1000) {
|
|
414
|
-
|
|
415
|
-
|
|
438
|
+
logger_1.logger.debug('Starting VeSync authentication...', {
|
|
439
|
+
username: this.username,
|
|
440
|
+
appId: this._appId,
|
|
441
|
+
region: this._region
|
|
442
|
+
});
|
|
416
443
|
for (let attempt = 0; attempt < retryAttempts; attempt++) {
|
|
417
444
|
try {
|
|
418
|
-
logger_1.logger.debug(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
case -11003:
|
|
434
|
-
logger_1.logger.error('Authentication failed - check credentials');
|
|
435
|
-
break;
|
|
436
|
-
case -11001:
|
|
437
|
-
logger_1.logger.error('Invalid request format');
|
|
438
|
-
break;
|
|
439
|
-
default:
|
|
440
|
-
logger_1.logger.error('API error code:', response.code, 'message:', response.msg);
|
|
445
|
+
logger_1.logger.debug(`Authentication attempt ${attempt + 1} of ${retryAttempts}`);
|
|
446
|
+
// Try new authentication flow first
|
|
447
|
+
let [success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, this._region);
|
|
448
|
+
if (!success) {
|
|
449
|
+
// Check if we need to try a different region
|
|
450
|
+
if (countryCode === 'cross_region') {
|
|
451
|
+
logger_1.logger.debug('Cross-region error detected, trying EU region...');
|
|
452
|
+
this._region = 'EU';
|
|
453
|
+
(0, helpers_1.setCurrentRegion)('EU');
|
|
454
|
+
[success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, 'EU');
|
|
455
|
+
}
|
|
456
|
+
// If new flow still fails, try legacy authentication as fallback
|
|
457
|
+
if (!success) {
|
|
458
|
+
logger_1.logger.debug('New authentication flow failed, trying legacy flow...');
|
|
459
|
+
[success, token, accountId, countryCode] = await helpers_1.Helpers.authLegacyFlow(this);
|
|
441
460
|
}
|
|
442
461
|
}
|
|
443
|
-
if (
|
|
444
|
-
this.token =
|
|
445
|
-
this.accountId =
|
|
446
|
-
this.countryCode =
|
|
462
|
+
if (success && token && accountId) {
|
|
463
|
+
this.token = token;
|
|
464
|
+
this.accountId = accountId;
|
|
465
|
+
this.countryCode = countryCode;
|
|
447
466
|
this.enabled = true;
|
|
448
|
-
|
|
467
|
+
// Set region based on country code if we got one
|
|
468
|
+
if (countryCode) {
|
|
469
|
+
const detectedRegion = (0, helpers_1.getRegionFromCountryCode)(countryCode);
|
|
470
|
+
if (detectedRegion !== this._region) {
|
|
471
|
+
logger_1.logger.debug(`Updating region from ${this._region} to ${detectedRegion} based on country code: ${countryCode}`);
|
|
472
|
+
this._region = detectedRegion;
|
|
473
|
+
(0, helpers_1.setCurrentRegion)(detectedRegion);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
logger_1.logger.debug('Authentication successful!', {
|
|
477
|
+
region: this._region,
|
|
478
|
+
countryCode: countryCode,
|
|
479
|
+
endpoint: (0, helpers_1.getApiBaseUrl)()
|
|
480
|
+
});
|
|
449
481
|
return true;
|
|
450
482
|
}
|
|
451
|
-
// If we reach here,
|
|
483
|
+
// If we reach here, authentication failed
|
|
452
484
|
const delay = initialDelayMs * Math.pow(2, attempt);
|
|
453
|
-
logger_1.logger.debug(`
|
|
485
|
+
logger_1.logger.debug(`Authentication attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
|
|
454
486
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
455
487
|
}
|
|
456
488
|
catch (error) {
|
|
489
|
+
logger_1.logger.error(`Authentication attempt ${attempt + 1} error:`, error);
|
|
457
490
|
if (attempt === retryAttempts - 1) {
|
|
458
|
-
logger_1.logger.error('
|
|
491
|
+
logger_1.logger.error('Authentication failed after all retry attempts');
|
|
459
492
|
return false;
|
|
460
493
|
}
|
|
461
494
|
const delay = initialDelayMs * Math.pow(2, attempt);
|
|
462
|
-
logger_1.logger.debug(`
|
|
495
|
+
logger_1.logger.debug(`Retrying authentication in ${delay}ms...`);
|
|
463
496
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
464
497
|
}
|
|
465
498
|
}
|
|
466
|
-
logger_1.logger.error('Unable to
|
|
499
|
+
logger_1.logger.error('Unable to authenticate with supplied credentials after all retry attempts');
|
|
467
500
|
return false;
|
|
468
501
|
}
|
|
469
502
|
/**
|