tsvesync 1.0.87 → 1.0.89
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 +36 -1
- package/dist/lib/helpers.d.ts +13 -0
- package/dist/lib/helpers.js +173 -2
- package/dist/lib/vesync.js +37 -12
- package/package.json +1 -1
- package/src/lib/helpers.ts +189 -1
- package/src/lib/vesync.ts +42 -16
package/README.md
CHANGED
|
@@ -201,7 +201,42 @@ The library supports configuration through environment variables:
|
|
|
201
201
|
```env
|
|
202
202
|
VESYNC_USERNAME=your_email@example.com
|
|
203
203
|
VESYNC_PASSWORD=your_password
|
|
204
|
-
VESYNC_API_URL=https://smartapi.vesync.com # Optional,
|
|
204
|
+
VESYNC_API_URL=https://smartapi.vesync.com # Optional, auto-detects regional endpoint
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Regional API Support
|
|
208
|
+
|
|
209
|
+
The library automatically detects the correct regional API endpoint based on your timezone:
|
|
210
|
+
|
|
211
|
+
- **Europe**: `https://smartapi.vesync.eu` (for European timezones like `Europe/Berlin`, `Europe/London`)
|
|
212
|
+
- **Asia**: `https://smartapi.vesync.com.cn` (for Asian timezones like `Asia/Shanghai`, `Asia/Tokyo`)
|
|
213
|
+
- **US/Others**: `https://smartapi.vesync.com` (default for all other timezones)
|
|
214
|
+
|
|
215
|
+
**For German users experiencing authentication issues:**
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// This will automatically use the EU API endpoint
|
|
219
|
+
const manager = new VeSync(
|
|
220
|
+
'your_email@example.com',
|
|
221
|
+
'your_password',
|
|
222
|
+
'Europe/Berlin' // This triggers EU endpoint selection
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
await manager.login();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Manual API endpoint override:**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Force a specific regional endpoint
|
|
232
|
+
const manager = new VeSync(
|
|
233
|
+
'your_email@example.com',
|
|
234
|
+
'your_password',
|
|
235
|
+
'Europe/Berlin',
|
|
236
|
+
false, // debug
|
|
237
|
+
true, // redact
|
|
238
|
+
'https://smartapi.vesync.eu' // Force EU endpoint
|
|
239
|
+
);
|
|
205
240
|
```
|
|
206
241
|
|
|
207
242
|
## Testing
|
package/dist/lib/helpers.d.ts
CHANGED
|
@@ -2,8 +2,21 @@
|
|
|
2
2
|
* Helper functions for VeSync API
|
|
3
3
|
*/
|
|
4
4
|
import { VeSync } from './vesync';
|
|
5
|
+
export declare const REGIONAL_ENDPOINTS: {
|
|
6
|
+
readonly US: "https://smartapi.vesync.com";
|
|
7
|
+
readonly EU: "https://smartapi.vesync.eu";
|
|
8
|
+
readonly ASIA: "https://smartapi.vesync.com.cn";
|
|
9
|
+
};
|
|
5
10
|
export declare function getApiBaseUrl(): string;
|
|
6
11
|
export declare function setApiBaseUrl(url: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get regional API endpoint based on timezone
|
|
14
|
+
*/
|
|
15
|
+
export declare function getRegionalEndpoint(timeZone: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Set API endpoint based on timezone if not explicitly set
|
|
18
|
+
*/
|
|
19
|
+
export declare function setRegionalEndpoint(timeZone: string): void;
|
|
7
20
|
export declare const API_RATE_LIMIT = 30;
|
|
8
21
|
export declare const API_TIMEOUT = 15000;
|
|
9
22
|
export declare const APP_VERSION = "2.8.6";
|
package/dist/lib/helpers.js
CHANGED
|
@@ -6,20 +6,174 @@ 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.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 = exports.REGIONAL_ENDPOINTS = void 0;
|
|
10
10
|
exports.getApiBaseUrl = getApiBaseUrl;
|
|
11
11
|
exports.setApiBaseUrl = setApiBaseUrl;
|
|
12
|
+
exports.getRegionalEndpoint = getRegionalEndpoint;
|
|
13
|
+
exports.setRegionalEndpoint = setRegionalEndpoint;
|
|
12
14
|
const axios_1 = __importDefault(require("axios"));
|
|
13
15
|
const crypto_1 = __importDefault(require("crypto"));
|
|
14
16
|
const logger_1 = require("./logger");
|
|
15
17
|
// API configuration
|
|
16
18
|
let _apiBaseUrl = 'https://smartapi.vesync.com';
|
|
19
|
+
// Regional API endpoints
|
|
20
|
+
exports.REGIONAL_ENDPOINTS = {
|
|
21
|
+
US: 'https://smartapi.vesync.com',
|
|
22
|
+
EU: 'https://smartapi.vesync.eu',
|
|
23
|
+
ASIA: 'https://smartapi.vesync.com.cn'
|
|
24
|
+
};
|
|
25
|
+
// Timezone to region mapping for API endpoint detection
|
|
26
|
+
const TIMEZONE_REGION_MAP = {
|
|
27
|
+
// European timezones - Western Europe
|
|
28
|
+
'Europe/Berlin': 'EU',
|
|
29
|
+
'Europe/London': 'EU',
|
|
30
|
+
'Europe/Paris': 'EU',
|
|
31
|
+
'Europe/Rome': 'EU',
|
|
32
|
+
'Europe/Madrid': 'EU',
|
|
33
|
+
'Europe/Amsterdam': 'EU',
|
|
34
|
+
'Europe/Vienna': 'EU',
|
|
35
|
+
'Europe/Brussels': 'EU',
|
|
36
|
+
'Europe/Copenhagen': 'EU',
|
|
37
|
+
'Europe/Dublin': 'EU',
|
|
38
|
+
'Europe/Helsinki': 'EU',
|
|
39
|
+
'Europe/Lisbon': 'EU',
|
|
40
|
+
'Europe/Luxembourg': 'EU',
|
|
41
|
+
'Europe/Oslo': 'EU',
|
|
42
|
+
'Europe/Prague': 'EU',
|
|
43
|
+
'Europe/Stockholm': 'EU',
|
|
44
|
+
'Europe/Warsaw': 'EU',
|
|
45
|
+
'Europe/Zurich': 'EU',
|
|
46
|
+
// European timezones - Eastern Europe
|
|
47
|
+
'Europe/Athens': 'EU',
|
|
48
|
+
'Europe/Budapest': 'EU',
|
|
49
|
+
'Europe/Bucharest': 'EU',
|
|
50
|
+
'Europe/Sofia': 'EU',
|
|
51
|
+
'Europe/Kiev': 'EU',
|
|
52
|
+
'Europe/Riga': 'EU',
|
|
53
|
+
'Europe/Vilnius': 'EU',
|
|
54
|
+
'Europe/Tallinn': 'EU',
|
|
55
|
+
'Europe/Zagreb': 'EU',
|
|
56
|
+
'Europe/Ljubljana': 'EU',
|
|
57
|
+
'Europe/Bratislava': 'EU',
|
|
58
|
+
'Europe/Belgrade': 'EU',
|
|
59
|
+
'Europe/Sarajevo': 'EU',
|
|
60
|
+
'Europe/Podgorica': 'EU',
|
|
61
|
+
'Europe/Skopje': 'EU',
|
|
62
|
+
'Europe/Tirane': 'EU',
|
|
63
|
+
// European timezones - Nordic/Baltic
|
|
64
|
+
'Europe/Reykjavik': 'EU',
|
|
65
|
+
'Europe/Moscow': 'EU',
|
|
66
|
+
'Europe/Kaliningrad': 'EU',
|
|
67
|
+
'Europe/Minsk': 'EU',
|
|
68
|
+
// European timezones - Other
|
|
69
|
+
'Europe/Malta': 'EU',
|
|
70
|
+
'Europe/Monaco': 'EU',
|
|
71
|
+
'Europe/Vatican': 'EU',
|
|
72
|
+
'Europe/San_Marino': 'EU',
|
|
73
|
+
'Europe/Andorra': 'EU',
|
|
74
|
+
'Europe/Gibraltar': 'EU',
|
|
75
|
+
'Europe/Isle_of_Man': 'EU',
|
|
76
|
+
'Europe/Jersey': 'EU',
|
|
77
|
+
'Europe/Guernsey': 'EU',
|
|
78
|
+
// Asian timezones - East Asia
|
|
79
|
+
'Asia/Shanghai': 'ASIA',
|
|
80
|
+
'Asia/Beijing': 'ASIA',
|
|
81
|
+
'Asia/Hong_Kong': 'ASIA',
|
|
82
|
+
'Asia/Tokyo': 'ASIA',
|
|
83
|
+
'Asia/Seoul': 'ASIA',
|
|
84
|
+
'Asia/Singapore': 'ASIA',
|
|
85
|
+
'Asia/Taipei': 'ASIA',
|
|
86
|
+
'Asia/Macau': 'ASIA',
|
|
87
|
+
// Asian timezones - Southeast Asia
|
|
88
|
+
'Asia/Bangkok': 'ASIA',
|
|
89
|
+
'Asia/Jakarta': 'ASIA',
|
|
90
|
+
'Asia/Manila': 'ASIA',
|
|
91
|
+
'Asia/Ho_Chi_Minh': 'ASIA',
|
|
92
|
+
'Asia/Kuala_Lumpur': 'ASIA',
|
|
93
|
+
'Asia/Phnom_Penh': 'ASIA',
|
|
94
|
+
'Asia/Vientiane': 'ASIA',
|
|
95
|
+
'Asia/Yangon': 'ASIA',
|
|
96
|
+
'Asia/Brunei': 'ASIA',
|
|
97
|
+
// Asian timezones - South Asia
|
|
98
|
+
'Asia/Mumbai': 'ASIA',
|
|
99
|
+
'Asia/Delhi': 'ASIA',
|
|
100
|
+
'Asia/Kolkata': 'ASIA',
|
|
101
|
+
'Asia/Dhaka': 'ASIA',
|
|
102
|
+
'Asia/Karachi': 'ASIA',
|
|
103
|
+
'Asia/Colombo': 'ASIA',
|
|
104
|
+
'Asia/Kathmandu': 'ASIA',
|
|
105
|
+
'Asia/Thimphu': 'ASIA',
|
|
106
|
+
// Asian timezones - Central Asia
|
|
107
|
+
'Asia/Almaty': 'ASIA',
|
|
108
|
+
'Asia/Bishkek': 'ASIA',
|
|
109
|
+
'Asia/Dushanbe': 'ASIA',
|
|
110
|
+
'Asia/Tashkent': 'ASIA',
|
|
111
|
+
'Asia/Ashgabat': 'ASIA',
|
|
112
|
+
// Asian timezones - West Asia / Middle East
|
|
113
|
+
'Asia/Dubai': 'ASIA',
|
|
114
|
+
'Asia/Riyadh': 'ASIA',
|
|
115
|
+
'Asia/Tehran': 'ASIA',
|
|
116
|
+
'Asia/Baghdad': 'ASIA',
|
|
117
|
+
'Asia/Kuwait': 'ASIA',
|
|
118
|
+
'Asia/Qatar': 'ASIA',
|
|
119
|
+
'Asia/Bahrain': 'ASIA',
|
|
120
|
+
'Asia/Muscat': 'ASIA',
|
|
121
|
+
'Asia/Baku': 'ASIA',
|
|
122
|
+
'Asia/Yerevan': 'ASIA',
|
|
123
|
+
'Asia/Tbilisi': 'ASIA',
|
|
124
|
+
// Oceania (using ASIA endpoint for now)
|
|
125
|
+
'Australia/Sydney': 'ASIA',
|
|
126
|
+
'Australia/Melbourne': 'ASIA',
|
|
127
|
+
'Australia/Brisbane': 'ASIA',
|
|
128
|
+
'Australia/Perth': 'ASIA',
|
|
129
|
+
'Australia/Adelaide': 'ASIA',
|
|
130
|
+
'Australia/Hobart': 'ASIA',
|
|
131
|
+
'Australia/Darwin': 'ASIA',
|
|
132
|
+
'Australia/Canberra': 'ASIA',
|
|
133
|
+
'Pacific/Auckland': 'ASIA',
|
|
134
|
+
'Pacific/Wellington': 'ASIA',
|
|
135
|
+
'Pacific/Fiji': 'ASIA',
|
|
136
|
+
'Pacific/Honolulu': 'US',
|
|
137
|
+
// North America (Canada)
|
|
138
|
+
'America/Toronto': 'US',
|
|
139
|
+
'America/Montreal': 'US',
|
|
140
|
+
'America/Vancouver': 'US',
|
|
141
|
+
'America/Calgary': 'US',
|
|
142
|
+
'America/Edmonton': 'US',
|
|
143
|
+
'America/Winnipeg': 'US',
|
|
144
|
+
'America/Halifax': 'US',
|
|
145
|
+
'America/St_Johns': 'US',
|
|
146
|
+
// North America (Mexico)
|
|
147
|
+
'America/Mexico_City': 'US',
|
|
148
|
+
'America/Tijuana': 'US',
|
|
149
|
+
'America/Cancun': 'US',
|
|
150
|
+
'America/Merida': 'US',
|
|
151
|
+
'America/Monterrey': 'US'
|
|
152
|
+
// US and other timezones default to US endpoint
|
|
153
|
+
};
|
|
17
154
|
function getApiBaseUrl() {
|
|
18
155
|
return _apiBaseUrl;
|
|
19
156
|
}
|
|
20
157
|
function setApiBaseUrl(url) {
|
|
21
158
|
_apiBaseUrl = url;
|
|
22
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Get regional API endpoint based on timezone
|
|
162
|
+
*/
|
|
163
|
+
function getRegionalEndpoint(timeZone) {
|
|
164
|
+
const region = TIMEZONE_REGION_MAP[timeZone] || 'US';
|
|
165
|
+
return exports.REGIONAL_ENDPOINTS[region];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Set API endpoint based on timezone if not explicitly set
|
|
169
|
+
*/
|
|
170
|
+
function setRegionalEndpoint(timeZone) {
|
|
171
|
+
const regionalUrl = getRegionalEndpoint(timeZone);
|
|
172
|
+
if (_apiBaseUrl === exports.REGIONAL_ENDPOINTS.US && regionalUrl !== exports.REGIONAL_ENDPOINTS.US) {
|
|
173
|
+
logger_1.logger.debug(`Setting regional API endpoint for ${timeZone}: ${regionalUrl}`);
|
|
174
|
+
setApiBaseUrl(regionalUrl);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
23
177
|
exports.API_RATE_LIMIT = 30;
|
|
24
178
|
exports.API_TIMEOUT = 15000;
|
|
25
179
|
exports.APP_VERSION = '2.8.6';
|
|
@@ -164,7 +318,13 @@ class Helpers {
|
|
|
164
318
|
*/
|
|
165
319
|
static async callApi(endpoint, method, data = null, headers = {}, manager) {
|
|
166
320
|
try {
|
|
321
|
+
// Ensure API base URL is properly set
|
|
322
|
+
if (!_apiBaseUrl || _apiBaseUrl === 'undefined') {
|
|
323
|
+
logger_1.logger.error('API base URL is not properly configured. Setting regional endpoint...');
|
|
324
|
+
setRegionalEndpoint(manager.timeZone);
|
|
325
|
+
}
|
|
167
326
|
const url = _apiBaseUrl + endpoint;
|
|
327
|
+
logger_1.logger.debug(`Making API call to: ${url}`);
|
|
168
328
|
const response = await (0, axios_1.default)({
|
|
169
329
|
method,
|
|
170
330
|
url,
|
|
@@ -187,9 +347,20 @@ class Helpers {
|
|
|
187
347
|
return await this.callApi(endpoint, method, data, headers, manager);
|
|
188
348
|
}
|
|
189
349
|
}
|
|
350
|
+
// Log specific error details for debugging
|
|
351
|
+
logger_1.logger.error('API call failed with response:', {
|
|
352
|
+
status: error.response.status,
|
|
353
|
+
code: responseData === null || responseData === void 0 ? void 0 : responseData.code,
|
|
354
|
+
message: responseData === null || responseData === void 0 ? void 0 : responseData.msg,
|
|
355
|
+
url: _apiBaseUrl + endpoint
|
|
356
|
+
});
|
|
190
357
|
return [responseData, error.response.status];
|
|
191
358
|
}
|
|
192
|
-
logger_1.logger.error('API call failed:',
|
|
359
|
+
logger_1.logger.error('API call failed:', {
|
|
360
|
+
code: error.code,
|
|
361
|
+
message: error.message,
|
|
362
|
+
url: _apiBaseUrl + endpoint
|
|
363
|
+
});
|
|
193
364
|
return [null, 0];
|
|
194
365
|
}
|
|
195
366
|
}
|
package/dist/lib/vesync.js
CHANGED
|
@@ -158,14 +158,7 @@ class VeSync {
|
|
|
158
158
|
switches: this.switches,
|
|
159
159
|
bulbs: this.bulbs
|
|
160
160
|
};
|
|
161
|
-
// Set
|
|
162
|
-
if (apiUrl) {
|
|
163
|
-
(0, helpers_1.setApiBaseUrl)(apiUrl);
|
|
164
|
-
}
|
|
165
|
-
// Set custom logger if provided
|
|
166
|
-
if (customLogger) {
|
|
167
|
-
(0, logger_1.setLogger)(customLogger);
|
|
168
|
-
}
|
|
161
|
+
// Set timezone first
|
|
169
162
|
if (typeof timeZone === 'string' && timeZone) {
|
|
170
163
|
const regTest = /[^a-zA-Z/_]/;
|
|
171
164
|
if (regTest.test(timeZone)) {
|
|
@@ -180,6 +173,18 @@ class VeSync {
|
|
|
180
173
|
this.timeZone = helpers_1.DEFAULT_TZ;
|
|
181
174
|
logger_1.logger.debug('Time zone is not a string');
|
|
182
175
|
}
|
|
176
|
+
// Set custom API URL if provided, otherwise use regional endpoint
|
|
177
|
+
if (apiUrl) {
|
|
178
|
+
(0, helpers_1.setApiBaseUrl)(apiUrl);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// Automatically set regional endpoint based on timezone
|
|
182
|
+
(0, helpers_1.setRegionalEndpoint)(this.timeZone);
|
|
183
|
+
}
|
|
184
|
+
// Set custom logger if provided
|
|
185
|
+
if (customLogger) {
|
|
186
|
+
(0, logger_1.setLogger)(customLogger);
|
|
187
|
+
}
|
|
183
188
|
if (debug) {
|
|
184
189
|
this.debug = true;
|
|
185
190
|
}
|
|
@@ -408,19 +413,39 @@ class VeSync {
|
|
|
408
413
|
async login(retryAttempts = 3, initialDelayMs = 1000) {
|
|
409
414
|
var _a;
|
|
410
415
|
const body = helpers_1.Helpers.reqBody(this, 'login');
|
|
411
|
-
logger_1.logger.debug('Login request:', {
|
|
412
|
-
url: `${process.env.VESYNC_API_URL}/cloud/v1/user/login`,
|
|
413
|
-
body
|
|
414
|
-
});
|
|
415
416
|
for (let attempt = 0; attempt < retryAttempts; attempt++) {
|
|
416
417
|
try {
|
|
418
|
+
logger_1.logger.debug('Login attempt', {
|
|
419
|
+
attempt: attempt + 1,
|
|
420
|
+
apiUrl: (0, helpers_1.getApiBaseUrl)(),
|
|
421
|
+
timeZone: this.timeZone,
|
|
422
|
+
appVersion: body.appVersion
|
|
423
|
+
});
|
|
417
424
|
const [response, status] = await helpers_1.Helpers.callApi('/cloud/v1/user/login', 'post', body, {}, this);
|
|
418
425
|
logger_1.logger.debug('Login response:', { status, response });
|
|
426
|
+
// Handle specific error codes
|
|
427
|
+
if (response && response.code) {
|
|
428
|
+
switch (response.code) {
|
|
429
|
+
case -11012022:
|
|
430
|
+
logger_1.logger.error('App version too low error. Current version:', body.appVersion);
|
|
431
|
+
logger_1.logger.error('This typically indicates a regional API compatibility issue.');
|
|
432
|
+
break;
|
|
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);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
419
443
|
if ((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.token) {
|
|
420
444
|
this.token = response.result.token;
|
|
421
445
|
this.accountId = response.result.accountID;
|
|
422
446
|
this.countryCode = response.result.countryCode;
|
|
423
447
|
this.enabled = true;
|
|
448
|
+
logger_1.logger.debug('Login successful for region:', this.countryCode);
|
|
424
449
|
return true;
|
|
425
450
|
}
|
|
426
451
|
// If we reach here, login failed but didn't throw an error
|
package/package.json
CHANGED
package/src/lib/helpers.ts
CHANGED
|
@@ -10,6 +10,154 @@ import { logger } from './logger';
|
|
|
10
10
|
// API configuration
|
|
11
11
|
let _apiBaseUrl = 'https://smartapi.vesync.com';
|
|
12
12
|
|
|
13
|
+
// Regional API endpoints
|
|
14
|
+
export const REGIONAL_ENDPOINTS = {
|
|
15
|
+
US: 'https://smartapi.vesync.com',
|
|
16
|
+
EU: 'https://smartapi.vesync.eu',
|
|
17
|
+
ASIA: 'https://smartapi.vesync.com.cn'
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
// Timezone to region mapping for API endpoint detection
|
|
21
|
+
const TIMEZONE_REGION_MAP: Record<string, keyof typeof REGIONAL_ENDPOINTS> = {
|
|
22
|
+
// European timezones - Western Europe
|
|
23
|
+
'Europe/Berlin': 'EU',
|
|
24
|
+
'Europe/London': 'EU',
|
|
25
|
+
'Europe/Paris': 'EU',
|
|
26
|
+
'Europe/Rome': 'EU',
|
|
27
|
+
'Europe/Madrid': 'EU',
|
|
28
|
+
'Europe/Amsterdam': 'EU',
|
|
29
|
+
'Europe/Vienna': 'EU',
|
|
30
|
+
'Europe/Brussels': 'EU',
|
|
31
|
+
'Europe/Copenhagen': 'EU',
|
|
32
|
+
'Europe/Dublin': 'EU',
|
|
33
|
+
'Europe/Helsinki': 'EU',
|
|
34
|
+
'Europe/Lisbon': 'EU',
|
|
35
|
+
'Europe/Luxembourg': 'EU',
|
|
36
|
+
'Europe/Oslo': 'EU',
|
|
37
|
+
'Europe/Prague': 'EU',
|
|
38
|
+
'Europe/Stockholm': 'EU',
|
|
39
|
+
'Europe/Warsaw': 'EU',
|
|
40
|
+
'Europe/Zurich': 'EU',
|
|
41
|
+
|
|
42
|
+
// European timezones - Eastern Europe
|
|
43
|
+
'Europe/Athens': 'EU',
|
|
44
|
+
'Europe/Budapest': 'EU',
|
|
45
|
+
'Europe/Bucharest': 'EU',
|
|
46
|
+
'Europe/Sofia': 'EU',
|
|
47
|
+
'Europe/Kiev': 'EU',
|
|
48
|
+
'Europe/Riga': 'EU',
|
|
49
|
+
'Europe/Vilnius': 'EU',
|
|
50
|
+
'Europe/Tallinn': 'EU',
|
|
51
|
+
'Europe/Zagreb': 'EU',
|
|
52
|
+
'Europe/Ljubljana': 'EU',
|
|
53
|
+
'Europe/Bratislava': 'EU',
|
|
54
|
+
'Europe/Belgrade': 'EU',
|
|
55
|
+
'Europe/Sarajevo': 'EU',
|
|
56
|
+
'Europe/Podgorica': 'EU',
|
|
57
|
+
'Europe/Skopje': 'EU',
|
|
58
|
+
'Europe/Tirane': 'EU',
|
|
59
|
+
|
|
60
|
+
// European timezones - Nordic/Baltic
|
|
61
|
+
'Europe/Reykjavik': 'EU',
|
|
62
|
+
'Europe/Moscow': 'EU',
|
|
63
|
+
'Europe/Kaliningrad': 'EU',
|
|
64
|
+
'Europe/Minsk': 'EU',
|
|
65
|
+
|
|
66
|
+
// European timezones - Other
|
|
67
|
+
'Europe/Malta': 'EU',
|
|
68
|
+
'Europe/Monaco': 'EU',
|
|
69
|
+
'Europe/Vatican': 'EU',
|
|
70
|
+
'Europe/San_Marino': 'EU',
|
|
71
|
+
'Europe/Andorra': 'EU',
|
|
72
|
+
'Europe/Gibraltar': 'EU',
|
|
73
|
+
'Europe/Isle_of_Man': 'EU',
|
|
74
|
+
'Europe/Jersey': 'EU',
|
|
75
|
+
'Europe/Guernsey': 'EU',
|
|
76
|
+
|
|
77
|
+
// Asian timezones - East Asia
|
|
78
|
+
'Asia/Shanghai': 'ASIA',
|
|
79
|
+
'Asia/Beijing': 'ASIA',
|
|
80
|
+
'Asia/Hong_Kong': 'ASIA',
|
|
81
|
+
'Asia/Tokyo': 'ASIA',
|
|
82
|
+
'Asia/Seoul': 'ASIA',
|
|
83
|
+
'Asia/Singapore': 'ASIA',
|
|
84
|
+
'Asia/Taipei': 'ASIA',
|
|
85
|
+
'Asia/Macau': 'ASIA',
|
|
86
|
+
|
|
87
|
+
// Asian timezones - Southeast Asia
|
|
88
|
+
'Asia/Bangkok': 'ASIA',
|
|
89
|
+
'Asia/Jakarta': 'ASIA',
|
|
90
|
+
'Asia/Manila': 'ASIA',
|
|
91
|
+
'Asia/Ho_Chi_Minh': 'ASIA',
|
|
92
|
+
'Asia/Kuala_Lumpur': 'ASIA',
|
|
93
|
+
'Asia/Phnom_Penh': 'ASIA',
|
|
94
|
+
'Asia/Vientiane': 'ASIA',
|
|
95
|
+
'Asia/Yangon': 'ASIA',
|
|
96
|
+
'Asia/Brunei': 'ASIA',
|
|
97
|
+
|
|
98
|
+
// Asian timezones - South Asia
|
|
99
|
+
'Asia/Mumbai': 'ASIA',
|
|
100
|
+
'Asia/Delhi': 'ASIA',
|
|
101
|
+
'Asia/Kolkata': 'ASIA',
|
|
102
|
+
'Asia/Dhaka': 'ASIA',
|
|
103
|
+
'Asia/Karachi': 'ASIA',
|
|
104
|
+
'Asia/Colombo': 'ASIA',
|
|
105
|
+
'Asia/Kathmandu': 'ASIA',
|
|
106
|
+
'Asia/Thimphu': 'ASIA',
|
|
107
|
+
|
|
108
|
+
// Asian timezones - Central Asia
|
|
109
|
+
'Asia/Almaty': 'ASIA',
|
|
110
|
+
'Asia/Bishkek': 'ASIA',
|
|
111
|
+
'Asia/Dushanbe': 'ASIA',
|
|
112
|
+
'Asia/Tashkent': 'ASIA',
|
|
113
|
+
'Asia/Ashgabat': 'ASIA',
|
|
114
|
+
|
|
115
|
+
// Asian timezones - West Asia / Middle East
|
|
116
|
+
'Asia/Dubai': 'ASIA',
|
|
117
|
+
'Asia/Riyadh': 'ASIA',
|
|
118
|
+
'Asia/Tehran': 'ASIA',
|
|
119
|
+
'Asia/Baghdad': 'ASIA',
|
|
120
|
+
'Asia/Kuwait': 'ASIA',
|
|
121
|
+
'Asia/Qatar': 'ASIA',
|
|
122
|
+
'Asia/Bahrain': 'ASIA',
|
|
123
|
+
'Asia/Muscat': 'ASIA',
|
|
124
|
+
'Asia/Baku': 'ASIA',
|
|
125
|
+
'Asia/Yerevan': 'ASIA',
|
|
126
|
+
'Asia/Tbilisi': 'ASIA',
|
|
127
|
+
|
|
128
|
+
// Oceania (using ASIA endpoint for now)
|
|
129
|
+
'Australia/Sydney': 'ASIA',
|
|
130
|
+
'Australia/Melbourne': 'ASIA',
|
|
131
|
+
'Australia/Brisbane': 'ASIA',
|
|
132
|
+
'Australia/Perth': 'ASIA',
|
|
133
|
+
'Australia/Adelaide': 'ASIA',
|
|
134
|
+
'Australia/Hobart': 'ASIA',
|
|
135
|
+
'Australia/Darwin': 'ASIA',
|
|
136
|
+
'Australia/Canberra': 'ASIA',
|
|
137
|
+
'Pacific/Auckland': 'ASIA',
|
|
138
|
+
'Pacific/Wellington': 'ASIA',
|
|
139
|
+
'Pacific/Fiji': 'ASIA',
|
|
140
|
+
'Pacific/Honolulu': 'US',
|
|
141
|
+
|
|
142
|
+
// North America (Canada)
|
|
143
|
+
'America/Toronto': 'US',
|
|
144
|
+
'America/Montreal': 'US',
|
|
145
|
+
'America/Vancouver': 'US',
|
|
146
|
+
'America/Calgary': 'US',
|
|
147
|
+
'America/Edmonton': 'US',
|
|
148
|
+
'America/Winnipeg': 'US',
|
|
149
|
+
'America/Halifax': 'US',
|
|
150
|
+
'America/St_Johns': 'US',
|
|
151
|
+
|
|
152
|
+
// North America (Mexico)
|
|
153
|
+
'America/Mexico_City': 'US',
|
|
154
|
+
'America/Tijuana': 'US',
|
|
155
|
+
'America/Cancun': 'US',
|
|
156
|
+
'America/Merida': 'US',
|
|
157
|
+
'America/Monterrey': 'US'
|
|
158
|
+
// US and other timezones default to US endpoint
|
|
159
|
+
};
|
|
160
|
+
|
|
13
161
|
export function getApiBaseUrl(): string {
|
|
14
162
|
return _apiBaseUrl;
|
|
15
163
|
}
|
|
@@ -18,6 +166,25 @@ export function setApiBaseUrl(url: string): void {
|
|
|
18
166
|
_apiBaseUrl = url;
|
|
19
167
|
}
|
|
20
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Get regional API endpoint based on timezone
|
|
171
|
+
*/
|
|
172
|
+
export function getRegionalEndpoint(timeZone: string): string {
|
|
173
|
+
const region = TIMEZONE_REGION_MAP[timeZone] || 'US';
|
|
174
|
+
return REGIONAL_ENDPOINTS[region];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Set API endpoint based on timezone if not explicitly set
|
|
179
|
+
*/
|
|
180
|
+
export function setRegionalEndpoint(timeZone: string): void {
|
|
181
|
+
const regionalUrl = getRegionalEndpoint(timeZone);
|
|
182
|
+
if (_apiBaseUrl === REGIONAL_ENDPOINTS.US && regionalUrl !== REGIONAL_ENDPOINTS.US) {
|
|
183
|
+
logger.debug(`Setting regional API endpoint for ${timeZone}: ${regionalUrl}`);
|
|
184
|
+
setApiBaseUrl(regionalUrl);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
21
188
|
export const API_RATE_LIMIT = 30;
|
|
22
189
|
export const API_TIMEOUT = 15000;
|
|
23
190
|
|
|
@@ -206,7 +373,15 @@ export class Helpers {
|
|
|
206
373
|
manager: VeSync
|
|
207
374
|
): Promise<[any, number]> {
|
|
208
375
|
try {
|
|
376
|
+
// Ensure API base URL is properly set
|
|
377
|
+
if (!_apiBaseUrl || _apiBaseUrl === 'undefined') {
|
|
378
|
+
logger.error('API base URL is not properly configured. Setting regional endpoint...');
|
|
379
|
+
setRegionalEndpoint(manager.timeZone);
|
|
380
|
+
}
|
|
381
|
+
|
|
209
382
|
const url = _apiBaseUrl + endpoint;
|
|
383
|
+
logger.debug(`Making API call to: ${url}`);
|
|
384
|
+
|
|
210
385
|
const response = await axios({
|
|
211
386
|
method,
|
|
212
387
|
url,
|
|
@@ -231,10 +406,23 @@ export class Helpers {
|
|
|
231
406
|
return await this.callApi(endpoint, method, data, headers, manager);
|
|
232
407
|
}
|
|
233
408
|
}
|
|
409
|
+
|
|
410
|
+
// Log specific error details for debugging
|
|
411
|
+
logger.error('API call failed with response:', {
|
|
412
|
+
status: error.response.status,
|
|
413
|
+
code: responseData?.code,
|
|
414
|
+
message: responseData?.msg,
|
|
415
|
+
url: _apiBaseUrl + endpoint
|
|
416
|
+
});
|
|
417
|
+
|
|
234
418
|
return [responseData, error.response.status];
|
|
235
419
|
}
|
|
236
420
|
|
|
237
|
-
logger.error('API call failed:',
|
|
421
|
+
logger.error('API call failed:', {
|
|
422
|
+
code: error.code,
|
|
423
|
+
message: error.message,
|
|
424
|
+
url: _apiBaseUrl + endpoint
|
|
425
|
+
});
|
|
238
426
|
return [null, 0];
|
|
239
427
|
}
|
|
240
428
|
}
|
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 } from './helpers';
|
|
5
|
+
import { Helpers, API_RATE_LIMIT, DEFAULT_TZ, setApiBaseUrl, setRegionalEndpoint, getApiBaseUrl } from './helpers';
|
|
6
6
|
import { VeSyncBaseDevice } from './vesyncBaseDevice';
|
|
7
7
|
import { fanModules } from './vesyncFanImpl';
|
|
8
8
|
import { outletModules } from './vesyncOutletImpl';
|
|
@@ -227,16 +227,7 @@ export class VeSync {
|
|
|
227
227
|
bulbs: this.bulbs
|
|
228
228
|
};
|
|
229
229
|
|
|
230
|
-
// Set
|
|
231
|
-
if (apiUrl) {
|
|
232
|
-
setApiBaseUrl(apiUrl);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Set custom logger if provided
|
|
236
|
-
if (customLogger) {
|
|
237
|
-
setLogger(customLogger);
|
|
238
|
-
}
|
|
239
|
-
|
|
230
|
+
// Set timezone first
|
|
240
231
|
if (typeof timeZone === 'string' && timeZone) {
|
|
241
232
|
const regTest = /[^a-zA-Z/_]/;
|
|
242
233
|
if (regTest.test(timeZone)) {
|
|
@@ -250,6 +241,19 @@ export class VeSync {
|
|
|
250
241
|
logger.debug('Time zone is not a string');
|
|
251
242
|
}
|
|
252
243
|
|
|
244
|
+
// Set custom API URL if provided, otherwise use regional endpoint
|
|
245
|
+
if (apiUrl) {
|
|
246
|
+
setApiBaseUrl(apiUrl);
|
|
247
|
+
} else {
|
|
248
|
+
// Automatically set regional endpoint based on timezone
|
|
249
|
+
setRegionalEndpoint(this.timeZone);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Set custom logger if provided
|
|
253
|
+
if (customLogger) {
|
|
254
|
+
setLogger(customLogger);
|
|
255
|
+
}
|
|
256
|
+
|
|
253
257
|
if (debug) {
|
|
254
258
|
this.debug = true;
|
|
255
259
|
}
|
|
@@ -506,13 +510,16 @@ export class VeSync {
|
|
|
506
510
|
*/
|
|
507
511
|
async login(retryAttempts: number = 3, initialDelayMs: number = 1000): Promise<boolean> {
|
|
508
512
|
const body = Helpers.reqBody(this, 'login');
|
|
509
|
-
|
|
510
|
-
url: `${process.env.VESYNC_API_URL}/cloud/v1/user/login`,
|
|
511
|
-
body
|
|
512
|
-
});
|
|
513
|
-
|
|
513
|
+
|
|
514
514
|
for (let attempt = 0; attempt < retryAttempts; attempt++) {
|
|
515
515
|
try {
|
|
516
|
+
logger.debug('Login attempt', {
|
|
517
|
+
attempt: attempt + 1,
|
|
518
|
+
apiUrl: getApiBaseUrl(),
|
|
519
|
+
timeZone: this.timeZone,
|
|
520
|
+
appVersion: body.appVersion
|
|
521
|
+
});
|
|
522
|
+
|
|
516
523
|
const [response, status] = await Helpers.callApi(
|
|
517
524
|
'/cloud/v1/user/login',
|
|
518
525
|
'post',
|
|
@@ -523,11 +530,30 @@ export class VeSync {
|
|
|
523
530
|
|
|
524
531
|
logger.debug('Login response:', { status, response });
|
|
525
532
|
|
|
533
|
+
// Handle specific error codes
|
|
534
|
+
if (response && response.code) {
|
|
535
|
+
switch (response.code) {
|
|
536
|
+
case -11012022:
|
|
537
|
+
logger.error('App version too low error. Current version:', body.appVersion);
|
|
538
|
+
logger.error('This typically indicates a regional API compatibility issue.');
|
|
539
|
+
break;
|
|
540
|
+
case -11003:
|
|
541
|
+
logger.error('Authentication failed - check credentials');
|
|
542
|
+
break;
|
|
543
|
+
case -11001:
|
|
544
|
+
logger.error('Invalid request format');
|
|
545
|
+
break;
|
|
546
|
+
default:
|
|
547
|
+
logger.error('API error code:', response.code, 'message:', response.msg);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
526
551
|
if (response?.result?.token) {
|
|
527
552
|
this.token = response.result.token;
|
|
528
553
|
this.accountId = response.result.accountID;
|
|
529
554
|
this.countryCode = response.result.countryCode;
|
|
530
555
|
this.enabled = true;
|
|
556
|
+
logger.debug('Login successful for region:', this.countryCode);
|
|
531
557
|
return true;
|
|
532
558
|
}
|
|
533
559
|
|