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 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, defaults to standard API URL
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
@@ -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";
@@ -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:', error.code, error.message);
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
  }
@@ -158,14 +158,7 @@ class VeSync {
158
158
  switches: this.switches,
159
159
  bulbs: this.bulbs
160
160
  };
161
- // Set custom API URL if provided
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.0.87",
3
+ "version": "1.0.89",
4
4
  "description": "A TypeScript library for interacting with VeSync smart home devices",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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:', error.code, error.message);
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 custom API URL if provided
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
- logger.debug('Login request:', {
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