tsvesync 1.0.102 → 1.0.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export { VeSyncBulb } from './lib/vesyncBulb';
5
5
  export { VeSyncWallSwitch } from './lib/vesyncSwitchImpl';
6
6
  export { VeSyncBaseDevice } from './lib/vesyncBaseDevice';
7
7
  export { Helpers, setApiBaseUrl } from './lib/helpers';
8
+ export { CREDENTIAL_ERROR_CODES, CROSS_REGION_ERROR_CODES, isCredentialError, isCrossRegionError } from './lib/constants';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setApiBaseUrl = exports.Helpers = exports.VeSyncBaseDevice = exports.VeSyncWallSwitch = exports.VeSyncBulb = exports.VeSyncOutdoorPlug = exports.VeSyncOutlet15A = exports.VeSyncOutlet10A = exports.VeSyncOutlet7A = exports.VeSyncAirBypass = exports.VeSync = void 0;
3
+ exports.isCrossRegionError = exports.isCredentialError = exports.CROSS_REGION_ERROR_CODES = exports.CREDENTIAL_ERROR_CODES = exports.setApiBaseUrl = exports.Helpers = exports.VeSyncBaseDevice = exports.VeSyncWallSwitch = exports.VeSyncBulb = exports.VeSyncOutdoorPlug = exports.VeSyncOutlet15A = exports.VeSyncOutlet10A = exports.VeSyncOutlet7A = exports.VeSyncAirBypass = exports.VeSync = void 0;
4
4
  // Export main VeSync manager class
5
5
  var vesync_1 = require("./lib/vesync");
6
6
  Object.defineProperty(exports, "VeSync", { enumerable: true, get: function () { return vesync_1.VeSync; } });
@@ -23,3 +23,9 @@ Object.defineProperty(exports, "VeSyncBaseDevice", { enumerable: true, get: func
23
23
  var helpers_1 = require("./lib/helpers");
24
24
  Object.defineProperty(exports, "Helpers", { enumerable: true, get: function () { return helpers_1.Helpers; } });
25
25
  Object.defineProperty(exports, "setApiBaseUrl", { enumerable: true, get: function () { return helpers_1.setApiBaseUrl; } });
26
+ // Export constants
27
+ var constants_1 = require("./lib/constants");
28
+ Object.defineProperty(exports, "CREDENTIAL_ERROR_CODES", { enumerable: true, get: function () { return constants_1.CREDENTIAL_ERROR_CODES; } });
29
+ Object.defineProperty(exports, "CROSS_REGION_ERROR_CODES", { enumerable: true, get: function () { return constants_1.CROSS_REGION_ERROR_CODES; } });
30
+ Object.defineProperty(exports, "isCredentialError", { enumerable: true, get: function () { return constants_1.isCredentialError; } });
31
+ Object.defineProperty(exports, "isCrossRegionError", { enumerable: true, get: function () { return constants_1.isCrossRegionError; } });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * VeSync API Error Codes
3
+ */
4
+ export declare const CREDENTIAL_ERROR_CODES: number[];
5
+ export declare const CROSS_REGION_ERROR_CODES: number[];
6
+ export declare const APP_VERSION_ERROR_CODES: number[];
7
+ /**
8
+ * Check if an error code indicates bad credentials
9
+ */
10
+ export declare function isCredentialError(code: number): boolean;
11
+ /**
12
+ * Check if an error code indicates cross-region issue
13
+ */
14
+ export declare function isCrossRegionError(code: number): boolean;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * VeSync API Error Codes
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.APP_VERSION_ERROR_CODES = exports.CROSS_REGION_ERROR_CODES = exports.CREDENTIAL_ERROR_CODES = void 0;
7
+ exports.isCredentialError = isCredentialError;
8
+ exports.isCrossRegionError = isCrossRegionError;
9
+ // Authentication error codes that indicate bad credentials (no point retrying)
10
+ exports.CREDENTIAL_ERROR_CODES = [
11
+ -11201129, // account or password incorrect
12
+ -11202129, // the account does not exist
13
+ -11000129, // illegal argument (empty credentials)
14
+ ];
15
+ // Cross-region error codes that require region switching
16
+ exports.CROSS_REGION_ERROR_CODES = [
17
+ -11260022, // cross region error
18
+ -11261022, // access region conflict error
19
+ ];
20
+ // Other error codes
21
+ exports.APP_VERSION_ERROR_CODES = [
22
+ -11012022, // app version is too low
23
+ ];
24
+ /**
25
+ * Check if an error code indicates bad credentials
26
+ */
27
+ function isCredentialError(code) {
28
+ return exports.CREDENTIAL_ERROR_CODES.includes(code);
29
+ }
30
+ /**
31
+ * Check if an error code indicates cross-region issue
32
+ */
33
+ function isCrossRegionError(code) {
34
+ return exports.CROSS_REGION_ERROR_CODES.includes(code);
35
+ }
@@ -26,7 +26,6 @@ export declare const REGION_ENDPOINTS: {
26
26
  JP: string;
27
27
  EU: string;
28
28
  };
29
- export declare const CROSS_REGION_ERROR_CODES: number[];
30
29
  export declare const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
31
30
  export declare const AUTH_ERROR_CODES: {
32
31
  ACCOUNT_PASSWORD_INCORRECT: number;
@@ -6,7 +6,7 @@ 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.AUTH_ERROR_CODES = exports.APP_VERSION_TOO_LOW_ERROR_CODE = exports.CROSS_REGION_ERROR_CODES = exports.REGION_ENDPOINTS = exports.CLIENT_VERSION = exports.BYPASS_HEADER_UA = exports.MOBILE_ID = exports.DEFAULT_REGION = exports.DEFAULT_TZ = exports.USER_TYPE = exports.CLIENT_INFO = exports.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = void 0;
9
+ exports.Helpers = exports.AUTH_ERROR_CODES = exports.APP_VERSION_TOO_LOW_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.CLIENT_INFO = exports.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = void 0;
10
10
  exports.getApiBaseUrl = getApiBaseUrl;
11
11
  exports.setApiBaseUrl = setApiBaseUrl;
12
12
  exports.getCurrentRegion = getCurrentRegion;
@@ -19,6 +19,7 @@ exports.generateTerminalId = generateTerminalId;
19
19
  const axios_1 = __importDefault(require("axios"));
20
20
  const crypto_1 = __importDefault(require("crypto"));
21
21
  const logger_1 = require("./logger");
22
+ const constants_1 = require("./constants");
22
23
  // API configuration - Support regional endpoints
23
24
  let _apiBaseUrl = 'https://smartapi.vesync.com';
24
25
  let _currentRegion = 'US';
@@ -72,7 +73,7 @@ exports.REGION_ENDPOINTS = {
72
73
  'EU': 'https://smartapi.vesync.eu'
73
74
  };
74
75
  // Cross-region error codes - multiple versions exist
75
- exports.CROSS_REGION_ERROR_CODES = [-11260022, -11261022];
76
+ // CROSS_REGION_ERROR_CODES moved to constants.ts
76
77
  exports.APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
77
78
  // Authentication error codes
78
79
  exports.AUTH_ERROR_CODES = {
@@ -480,8 +481,14 @@ class Helpers {
480
481
  }
481
482
  if (authResponse.code !== 0) {
482
483
  logger_1.logger.error('Step 1 error code:', authResponse.code, 'message:', authResponse.msg);
484
+ // Check if it's a credential error (no point retrying)
485
+ if ((0, constants_1.isCredentialError)(authResponse.code)) {
486
+ logger_1.logger.error('Credential error detected - invalid username or password');
487
+ setApiBaseUrl(originalUrl); // Restore original URL
488
+ return [false, null, null, 'credential_error'];
489
+ }
483
490
  // Handle cross-region error (multiple error codes possible)
484
- if (exports.CROSS_REGION_ERROR_CODES.includes(authResponse.code)) {
491
+ if ((0, constants_1.isCrossRegionError)(authResponse.code)) {
485
492
  logger_1.logger.debug('Cross-region error detected:', authResponse.code, 'will try different region');
486
493
  setApiBaseUrl(originalUrl); // Restore original URL
487
494
  return [false, null, null, 'cross_region'];
@@ -502,56 +509,26 @@ class Helpers {
502
509
  const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, countryCodeForStep2);
503
510
  logger_1.logger.debug('Step 2: Logging in with authorization code...');
504
511
  const [loginResponse, loginStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', step2Body, step1Headers, manager);
512
+ logger_1.logger.debug('Step 2 response:', {
513
+ status: loginStatus,
514
+ code: loginResponse === null || loginResponse === void 0 ? void 0 : loginResponse.code,
515
+ hasResult: !!(loginResponse === null || loginResponse === void 0 ? void 0 : loginResponse.result),
516
+ msg: loginResponse === null || loginResponse === void 0 ? void 0 : loginResponse.msg
517
+ });
505
518
  if (!loginResponse || loginStatus !== 200) {
506
519
  logger_1.logger.error('Step 2 failed:', loginResponse);
507
520
  setApiBaseUrl(originalUrl); // Restore original URL on failure
508
521
  return [false, null, null, null];
509
522
  }
510
523
  if (loginResponse.code !== 0) {
511
- // Handle cross-region error with retry (like pyvesync PR #340)
512
- if (exports.CROSS_REGION_ERROR_CODES.includes(loginResponse.code)) {
513
- logger_1.logger.debug('Cross-region error detected in Step 2, retrying with region change token...');
514
- const { bizToken: regionChangeToken, countryCode: newCountryCode } = loginResponse.result || {};
515
- if (regionChangeToken && newCountryCode) {
516
- // Retry Step 2 with region change token on the SAME endpoint
517
- // The region change token tells the server to handle the cross-region authentication
518
- const retryBody = {
519
- method: 'loginByAuthorizeCode4Vesync',
520
- authorizeCode: authorizeCode,
521
- acceptLanguage: 'en',
522
- clientInfo: exports.CLIENT_INFO,
523
- clientType: 'vesyncApp',
524
- clientVersion: exports.CLIENT_VERSION,
525
- debugMode: false,
526
- emailSubscriptions: false,
527
- osInfo: exports.PHONE_OS,
528
- terminalId: terminalId,
529
- timeZone: exports.DEFAULT_TZ,
530
- userCountryCode: newCountryCode,
531
- traceId: `APP${appId}${Math.floor(Date.now() / 1000)}`,
532
- bizToken: regionChangeToken,
533
- regionChange: 'last_region'
534
- };
535
- logger_1.logger.debug('Retrying Step 2 with region change token...', {
536
- newCountryCode,
537
- currentEndpoint: getApiBaseUrl()
538
- });
539
- const [retryResponse, retryStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', retryBody, step1Headers, manager);
540
- if (retryResponse && retryResponse.code === 0) {
541
- const { token, accountID, countryCode } = retryResponse.result || {};
542
- if (token && accountID) {
543
- logger_1.logger.debug('Step 2 retry successful with region change');
544
- setApiBaseUrl(originalUrl); // Restore original URL on success
545
- return [true, token, accountID, countryCode || newCountryCode];
546
- }
547
- }
548
- logger_1.logger.error('Step 2 retry failed:', retryResponse);
549
- setApiBaseUrl(originalUrl); // Restore original URL on failure
550
- return [false, null, null, null];
551
- }
552
- else {
553
- logger_1.logger.error('No region change token in cross-region error response');
554
- }
524
+ logger_1.logger.debug('Step 2 error, code:', loginResponse.code, 'checking if cross-region...');
525
+ // Handle cross-region error - for EU accounts, we need to switch regions entirely
526
+ 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');
528
+ // For cross-region errors, we need to retry the entire flow with a different region
529
+ // The authorization code from Step 1 is tied to the endpoint it was obtained from
530
+ setApiBaseUrl(originalUrl); // Restore original URL
531
+ return [false, null, null, 'cross_region_retry'];
555
532
  }
556
533
  logger_1.logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
557
534
  setApiBaseUrl(originalUrl); // Restore original URL on error
@@ -588,6 +565,11 @@ class Helpers {
588
565
  }
589
566
  if (response.code && response.code !== 0) {
590
567
  logger_1.logger.error('Legacy login error code:', response.code, 'message:', response.msg);
568
+ // Check if it's a credential error
569
+ if ((0, constants_1.isCredentialError)(response.code)) {
570
+ logger_1.logger.error('Legacy auth: Credential error detected');
571
+ return [false, null, null, 'credential_error'];
572
+ }
591
573
  return [false, null, null, null];
592
574
  }
593
575
  const { token, accountID, countryCode } = response.result || {};
@@ -461,13 +461,8 @@ class VeSync {
461
461
  for (let attempt = 0; attempt < retryAttempts; attempt++) {
462
462
  try {
463
463
  logger_1.logger.debug(`Authentication attempt ${attempt + 1} of ${retryAttempts}`);
464
- // First, try to detect user's region from email
465
- const detectedRegion = (0, helpers_1.getRegionFromCountryCode)((0, helpers_1.getCountryCodeFromRegion)(this._region, this.username));
466
- if (detectedRegion !== this._region) {
467
- logger_1.logger.debug(`Adjusting region from ${this._region} to ${detectedRegion} based on email domain`);
468
- this._region = detectedRegion;
469
- (0, helpers_1.setCurrentRegion)(detectedRegion);
470
- }
464
+ // Skip email-based region detection for now since we'll handle cross-region errors
465
+ // The cross-region error handling below will automatically switch to the correct region
471
466
  // Try new authentication flow first with detected region
472
467
  let [success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, this._region);
473
468
  // Track if new flow succeeded
@@ -475,15 +470,32 @@ class VeSync {
475
470
  this.authFlowUsed = 'new';
476
471
  }
477
472
  if (!success) {
473
+ // Check if it's a credential error (no point retrying)
474
+ if (countryCode === 'credential_error') {
475
+ logger_1.logger.error('Authentication failed due to invalid credentials');
476
+ return false; // Exit immediately, don't retry
477
+ }
478
478
  // Check if we need to try a different region
479
- if (countryCode === 'cross_region') {
479
+ if (countryCode === 'cross_region' || countryCode === 'cross_region_retry') {
480
480
  logger_1.logger.debug('Cross-region error detected, trying opposite region...');
481
481
  const alternateRegion = this._region === 'US' ? 'EU' : 'US';
482
+ logger_1.logger.debug(`Switching from ${this._region} to ${alternateRegion} region`);
482
483
  this._region = alternateRegion;
483
484
  (0, helpers_1.setCurrentRegion)(alternateRegion);
485
+ // Update the API endpoint for the new region
486
+ const regionEndpoints = {
487
+ 'US': 'https://smartapi.vesync.com',
488
+ 'EU': 'https://smartapi.vesync.eu'
489
+ };
490
+ if (alternateRegion in regionEndpoints) {
491
+ (0, helpers_1.setApiBaseUrl)(regionEndpoints[alternateRegion]);
492
+ logger_1.logger.debug(`API endpoint set to ${regionEndpoints[alternateRegion]}`);
493
+ }
494
+ // Retry with the alternate region
484
495
  [success, token, accountId, countryCode] = await helpers_1.Helpers.authNewFlow(this, this._appId, alternateRegion);
485
496
  if (success) {
486
497
  this.authFlowUsed = 'new';
498
+ logger_1.logger.debug(`Authentication successful with ${alternateRegion} region`);
487
499
  }
488
500
  }
489
501
  // If new flow still fails, try legacy authentication as fallback
@@ -493,6 +505,11 @@ class VeSync {
493
505
  if (success) {
494
506
  this.authFlowUsed = 'legacy';
495
507
  }
508
+ else if (countryCode === 'credential_error') {
509
+ // Legacy auth also detected bad credentials
510
+ logger_1.logger.error('Both authentication methods report invalid credentials');
511
+ return false; // Exit immediately
512
+ }
496
513
  }
497
514
  }
498
515
  if (success && token && accountId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.0.102",
3
+ "version": "1.0.103",
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",
package/src/index.ts CHANGED
@@ -11,4 +11,12 @@ export { VeSyncWallSwitch } from './lib/vesyncSwitchImpl';
11
11
  export { VeSyncBaseDevice } from './lib/vesyncBaseDevice';
12
12
 
13
13
  // Export helper functions
14
- export { Helpers, setApiBaseUrl } from './lib/helpers';
14
+ export { Helpers, setApiBaseUrl } from './lib/helpers';
15
+
16
+ // Export constants
17
+ export {
18
+ CREDENTIAL_ERROR_CODES,
19
+ CROSS_REGION_ERROR_CODES,
20
+ isCredentialError,
21
+ isCrossRegionError
22
+ } from './lib/constants';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * VeSync API Error Codes
3
+ */
4
+
5
+ // Authentication error codes that indicate bad credentials (no point retrying)
6
+ export const CREDENTIAL_ERROR_CODES = [
7
+ -11201129, // account or password incorrect
8
+ -11202129, // the account does not exist
9
+ -11000129, // illegal argument (empty credentials)
10
+ ];
11
+
12
+ // Cross-region error codes that require region switching
13
+ export const CROSS_REGION_ERROR_CODES = [
14
+ -11260022, // cross region error
15
+ -11261022, // access region conflict error
16
+ ];
17
+
18
+ // Other error codes
19
+ export const APP_VERSION_ERROR_CODES = [
20
+ -11012022, // app version is too low
21
+ ];
22
+
23
+ /**
24
+ * Check if an error code indicates bad credentials
25
+ */
26
+ export function isCredentialError(code: number): boolean {
27
+ return CREDENTIAL_ERROR_CODES.includes(code);
28
+ }
29
+
30
+ /**
31
+ * Check if an error code indicates cross-region issue
32
+ */
33
+ export function isCrossRegionError(code: number): boolean {
34
+ return CROSS_REGION_ERROR_CODES.includes(code);
35
+ }
@@ -6,6 +6,7 @@ import axios from 'axios';
6
6
  import crypto from 'crypto';
7
7
  import { VeSync } from './vesync';
8
8
  import { logger } from './logger';
9
+ import { isCredentialError, isCrossRegionError, CROSS_REGION_ERROR_CODES } from './constants';
9
10
 
10
11
  // API configuration - Support regional endpoints
11
12
  let _apiBaseUrl = 'https://smartapi.vesync.com';
@@ -72,7 +73,7 @@ export const REGION_ENDPOINTS = {
72
73
  };
73
74
 
74
75
  // Cross-region error codes - multiple versions exist
75
- export const CROSS_REGION_ERROR_CODES = [-11260022, -11261022];
76
+ // CROSS_REGION_ERROR_CODES moved to constants.ts
76
77
  export const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
77
78
 
78
79
  // Authentication error codes
@@ -594,8 +595,15 @@ export class Helpers {
594
595
  if (authResponse.code !== 0) {
595
596
  logger.error('Step 1 error code:', authResponse.code, 'message:', authResponse.msg);
596
597
 
598
+ // Check if it's a credential error (no point retrying)
599
+ if (isCredentialError(authResponse.code)) {
600
+ logger.error('Credential error detected - invalid username or password');
601
+ setApiBaseUrl(originalUrl); // Restore original URL
602
+ return [false, null, null, 'credential_error'];
603
+ }
604
+
597
605
  // Handle cross-region error (multiple error codes possible)
598
- if (CROSS_REGION_ERROR_CODES.includes(authResponse.code)) {
606
+ if (isCrossRegionError(authResponse.code)) {
599
607
  logger.debug('Cross-region error detected:', authResponse.code, 'will try different region');
600
608
  setApiBaseUrl(originalUrl); // Restore original URL
601
609
  return [false, null, null, 'cross_region'];
@@ -630,6 +638,13 @@ export class Helpers {
630
638
  manager
631
639
  );
632
640
 
641
+ logger.debug('Step 2 response:', {
642
+ status: loginStatus,
643
+ code: loginResponse?.code,
644
+ hasResult: !!loginResponse?.result,
645
+ msg: loginResponse?.msg
646
+ });
647
+
633
648
  if (!loginResponse || loginStatus !== 200) {
634
649
  logger.error('Step 2 failed:', loginResponse);
635
650
  setApiBaseUrl(originalUrl); // Restore original URL on failure
@@ -637,61 +652,14 @@ export class Helpers {
637
652
  }
638
653
 
639
654
  if (loginResponse.code !== 0) {
640
- // Handle cross-region error with retry (like pyvesync PR #340)
655
+ logger.debug('Step 2 error, code:', loginResponse.code, 'checking if cross-region...');
656
+ // Handle cross-region error - for EU accounts, we need to switch regions entirely
641
657
  if (CROSS_REGION_ERROR_CODES.includes(loginResponse.code)) {
642
- logger.debug('Cross-region error detected in Step 2, retrying with region change token...');
643
-
644
- const { bizToken: regionChangeToken, countryCode: newCountryCode } = loginResponse.result || {};
645
-
646
- if (regionChangeToken && newCountryCode) {
647
- // Retry Step 2 with region change token on the SAME endpoint
648
- // The region change token tells the server to handle the cross-region authentication
649
- const retryBody = {
650
- method: 'loginByAuthorizeCode4Vesync',
651
- authorizeCode: authorizeCode,
652
- acceptLanguage: 'en',
653
- clientInfo: CLIENT_INFO,
654
- clientType: 'vesyncApp',
655
- clientVersion: CLIENT_VERSION,
656
- debugMode: false,
657
- emailSubscriptions: false,
658
- osInfo: PHONE_OS,
659
- terminalId: terminalId,
660
- timeZone: DEFAULT_TZ,
661
- userCountryCode: newCountryCode,
662
- traceId: `APP${appId}${Math.floor(Date.now() / 1000)}`,
663
- bizToken: regionChangeToken,
664
- regionChange: 'last_region'
665
- };
666
-
667
- logger.debug('Retrying Step 2 with region change token...', {
668
- newCountryCode,
669
- currentEndpoint: getApiBaseUrl()
670
- });
671
-
672
- const [retryResponse, retryStatus] = await this.callApi(
673
- '/user/api/accountManage/v1/loginByAuthorizeCode4Vesync',
674
- 'post',
675
- retryBody,
676
- step1Headers,
677
- manager
678
- );
679
-
680
- if (retryResponse && retryResponse.code === 0) {
681
- const { token, accountID, countryCode } = retryResponse.result || {};
682
- if (token && accountID) {
683
- logger.debug('Step 2 retry successful with region change');
684
- setApiBaseUrl(originalUrl); // Restore original URL on success
685
- return [true, token, accountID, countryCode || newCountryCode];
686
- }
687
- }
688
-
689
- logger.error('Step 2 retry failed:', retryResponse);
690
- setApiBaseUrl(originalUrl); // Restore original URL on failure
691
- return [false, null, null, null];
692
- } else {
693
- logger.error('No region change token in cross-region error response');
694
- }
658
+ logger.debug('Cross-region error detected in Step 2 - need to use different region');
659
+ // For cross-region errors, we need to retry the entire flow with a different region
660
+ // The authorization code from Step 1 is tied to the endpoint it was obtained from
661
+ setApiBaseUrl(originalUrl); // Restore original URL
662
+ return [false, null, null, 'cross_region_retry'];
695
663
  }
696
664
 
697
665
  logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
@@ -744,6 +712,13 @@ export class Helpers {
744
712
 
745
713
  if (response.code && response.code !== 0) {
746
714
  logger.error('Legacy login error code:', response.code, 'message:', response.msg);
715
+
716
+ // Check if it's a credential error
717
+ if (isCredentialError(response.code)) {
718
+ logger.error('Legacy auth: Credential error detected');
719
+ return [false, null, null, 'credential_error'];
720
+ }
721
+
747
722
  return [false, null, null, null];
748
723
  }
749
724
 
package/src/lib/vesync.ts CHANGED
@@ -575,13 +575,8 @@ export class VeSync {
575
575
  try {
576
576
  logger.debug(`Authentication attempt ${attempt + 1} of ${retryAttempts}`);
577
577
 
578
- // First, try to detect user's region from email
579
- const detectedRegion = getRegionFromCountryCode(getCountryCodeFromRegion(this._region, this.username));
580
- if (detectedRegion !== this._region) {
581
- logger.debug(`Adjusting region from ${this._region} to ${detectedRegion} based on email domain`);
582
- this._region = detectedRegion;
583
- setCurrentRegion(detectedRegion);
584
- }
578
+ // Skip email-based region detection for now since we'll handle cross-region errors
579
+ // The cross-region error handling below will automatically switch to the correct region
585
580
 
586
581
  // Try new authentication flow first with detected region
587
582
  let [success, token, accountId, countryCode] = await Helpers.authNewFlow(this, this._appId, this._region);
@@ -592,15 +587,36 @@ export class VeSync {
592
587
  }
593
588
 
594
589
  if (!success) {
590
+ // Check if it's a credential error (no point retrying)
591
+ if (countryCode === 'credential_error') {
592
+ logger.error('Authentication failed due to invalid credentials');
593
+ return false; // Exit immediately, don't retry
594
+ }
595
+
595
596
  // Check if we need to try a different region
596
- if (countryCode === 'cross_region') {
597
+ if (countryCode === 'cross_region' || countryCode === 'cross_region_retry') {
597
598
  logger.debug('Cross-region error detected, trying opposite region...');
598
599
  const alternateRegion = this._region === 'US' ? 'EU' : 'US';
600
+ logger.debug(`Switching from ${this._region} to ${alternateRegion} region`);
601
+
599
602
  this._region = alternateRegion;
600
603
  setCurrentRegion(alternateRegion);
604
+
605
+ // Update the API endpoint for the new region
606
+ const regionEndpoints: Record<string, string> = {
607
+ 'US': 'https://smartapi.vesync.com',
608
+ 'EU': 'https://smartapi.vesync.eu'
609
+ };
610
+ if (alternateRegion in regionEndpoints) {
611
+ setApiBaseUrl(regionEndpoints[alternateRegion]);
612
+ logger.debug(`API endpoint set to ${regionEndpoints[alternateRegion]}`);
613
+ }
614
+
615
+ // Retry with the alternate region
601
616
  [success, token, accountId, countryCode] = await Helpers.authNewFlow(this, this._appId, alternateRegion);
602
617
  if (success) {
603
618
  this.authFlowUsed = 'new';
619
+ logger.debug(`Authentication successful with ${alternateRegion} region`);
604
620
  }
605
621
  }
606
622
 
@@ -610,6 +626,10 @@ export class VeSync {
610
626
  [success, token, accountId, countryCode] = await Helpers.authLegacyFlow(this);
611
627
  if (success) {
612
628
  this.authFlowUsed = 'legacy';
629
+ } else if (countryCode === 'credential_error') {
630
+ // Legacy auth also detected bad credentials
631
+ logger.error('Both authentication methods report invalid credentials');
632
+ return false; // Exit immediately
613
633
  }
614
634
  }
615
635
  }