tsvesync 1.0.103 → 1.0.104

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