tsvesync 1.4.2 → 1.4.3
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 +15 -34
- package/dist/lib/constants.d.ts +5 -0
- package/dist/lib/constants.js +13 -1
- package/dist/lib/helpers.d.ts +4 -3
- package/dist/lib/helpers.js +151 -97
- package/dist/lib/vesync.d.ts +5 -0
- package/dist/lib/vesync.js +173 -45
- package/package.json +1 -1
- package/src/lib/constants.ts +14 -1
- package/src/lib/helpers.ts +178 -108
- package/src/lib/vesync.ts +201 -51
package/src/lib/helpers.ts
CHANGED
|
@@ -6,7 +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
|
|
9
|
+
import { isCredentialError, isCrossRegionError } from './constants';
|
|
10
10
|
|
|
11
11
|
// API configuration - Support regional endpoints
|
|
12
12
|
let _apiBaseUrl = 'https://smartapi.vesync.com';
|
|
@@ -31,21 +31,28 @@ export function setCurrentRegion(region: string): void {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// pyvesync parity:
|
|
35
|
+
// VeSync only exposes two API base URLs (US + EU). pyvesync intentionally uses a
|
|
36
|
+
// *deny-list* mapping here:
|
|
37
|
+
// - `US` region: US/CA/MX/JP
|
|
38
|
+
// - `EU` region: everything else
|
|
39
|
+
//
|
|
40
|
+
// This is counter-intuitive if you read it as "EU countries", but it's really
|
|
41
|
+
// "the EU *endpoint*". In practice, many accounts outside Europe (e.g. AU/NZ/SG)
|
|
42
|
+
// can be hosted on either endpoint depending on how/when the account was created.
|
|
43
|
+
//
|
|
44
|
+
// The correct/robust behavior comes from the Step 2 cross-region flow: when the
|
|
45
|
+
// region guess is wrong, the API returns `currentRegion`/`countryCode` plus a
|
|
46
|
+
// `bizToken`, and we retry Step 2 using the server-provided values (pyvesync-style).
|
|
47
|
+
export const NON_EU_COUNTRY_CODES = ['US', 'CA', 'MX', 'JP'];
|
|
48
|
+
|
|
34
49
|
// Determine region from country code
|
|
35
|
-
export function getRegionFromCountryCode(countryCode: string):
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', 'IS', 'LI',
|
|
40
|
-
'NO', 'CH'
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
if (euCountries.includes(countryCode)) {
|
|
44
|
-
return 'EU';
|
|
50
|
+
export function getRegionFromCountryCode(countryCode: string): 'US' | 'EU' {
|
|
51
|
+
const normalized = countryCode.trim().toUpperCase();
|
|
52
|
+
if (NON_EU_COUNTRY_CODES.includes(normalized)) {
|
|
53
|
+
return 'US';
|
|
45
54
|
}
|
|
46
|
-
|
|
47
|
-
// Default to US for other countries
|
|
48
|
-
return 'US';
|
|
55
|
+
return 'EU';
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
|
|
@@ -97,27 +104,8 @@ export function getUSEndpointCountryCodes(): string[] {
|
|
|
97
104
|
* Get the appropriate API endpoint based on country code
|
|
98
105
|
*/
|
|
99
106
|
export function getEndpointForCountryCode(countryCode: string): 'US' | 'EU' {
|
|
100
|
-
//
|
|
101
|
-
|
|
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';
|
|
107
|
+
// Alias for clarity: this is the region-to-endpoint mapping used by pyvesync.
|
|
108
|
+
return getRegionFromCountryCode(countryCode);
|
|
121
109
|
}
|
|
122
110
|
|
|
123
111
|
/**
|
|
@@ -169,12 +157,8 @@ export function generateAppId(): string {
|
|
|
169
157
|
|
|
170
158
|
// Generate unique terminal ID for authentication
|
|
171
159
|
export function generateTerminalId(): string {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
for (let i = 0; i < 16; i++) {
|
|
175
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
176
|
-
}
|
|
177
|
-
return result;
|
|
160
|
+
// pyvesync parity: terminalId is a UUID-like identifier prefixed with "2".
|
|
161
|
+
return `2${crypto.randomUUID().replace(/-/g, '')}`;
|
|
178
162
|
}
|
|
179
163
|
|
|
180
164
|
export function generateTraceId(): string {
|
|
@@ -183,6 +167,14 @@ export function generateTraceId(): string {
|
|
|
183
167
|
return `APP${MOBILE_ID.slice(-5, -1)}${timestamp}-${randomPart}`;
|
|
184
168
|
}
|
|
185
169
|
|
|
170
|
+
let _authTraceCallNumber = 0;
|
|
171
|
+
function generateAuthTraceId(terminalId: string): string {
|
|
172
|
+
_authTraceCallNumber += 1;
|
|
173
|
+
const suffix = terminalId.slice(-5, -1);
|
|
174
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
175
|
+
return `APP${suffix}${timestamp}-${String(_authTraceCallNumber).padStart(5, '0')}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
186
178
|
export interface RequestBody {
|
|
187
179
|
acceptLanguage?: string;
|
|
188
180
|
accountID?: string;
|
|
@@ -378,18 +370,12 @@ export class Helpers {
|
|
|
378
370
|
/**
|
|
379
371
|
* Build request body for initial authentication step
|
|
380
372
|
*/
|
|
381
|
-
static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string,
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
email: manager.username,
|
|
388
|
-
detectedRegion: userRegion,
|
|
389
|
-
requestRegion: region,
|
|
390
|
-
userCountryCode: userCountryCode
|
|
391
|
-
});
|
|
392
|
-
|
|
373
|
+
static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string, userCountryCode: string): Record<string, any> {
|
|
374
|
+
// pyvesync parity:
|
|
375
|
+
// - Mirrors `pyvesync.models.vesync_models.RequestGetTokenModel`
|
|
376
|
+
// - `userCountryCode` defaults to `DEFAULT_REGION` ("US") unless the caller overrides it
|
|
377
|
+
// - `timeZone` defaults to `DEFAULT_TZ` ("America/New_York") during auth; the API later updates the
|
|
378
|
+
// effective timezone used for device operations.
|
|
393
379
|
return {
|
|
394
380
|
'email': manager.username,
|
|
395
381
|
'method': 'authByPWDOrOTM',
|
|
@@ -397,30 +383,43 @@ export class Helpers {
|
|
|
397
383
|
'acceptLanguage': 'en',
|
|
398
384
|
'accountID': '',
|
|
399
385
|
'authProtocolType': 'generic',
|
|
400
|
-
'clientInfo':
|
|
386
|
+
'clientInfo': PHONE_BRAND,
|
|
401
387
|
'clientType': 'vesyncApp',
|
|
402
388
|
'clientVersion': CLIENT_VERSION,
|
|
403
389
|
'debugMode': false,
|
|
404
390
|
'osInfo': PHONE_OS,
|
|
405
391
|
'terminalId': terminalId,
|
|
406
|
-
'timeZone':
|
|
392
|
+
'timeZone': DEFAULT_TZ,
|
|
407
393
|
'token': '',
|
|
408
|
-
'userCountryCode': userCountryCode,
|
|
394
|
+
'userCountryCode': userCountryCode.trim().toUpperCase(),
|
|
409
395
|
'appID': appId,
|
|
410
396
|
'sourceAppID': appId,
|
|
411
|
-
'traceId':
|
|
397
|
+
'traceId': generateAuthTraceId(terminalId)
|
|
412
398
|
};
|
|
413
399
|
}
|
|
414
400
|
|
|
415
401
|
/**
|
|
416
402
|
* Build request body for second authentication step (login with authorize code)
|
|
417
403
|
*/
|
|
418
|
-
static reqBodyAuthStep2(authorizeCode: string, bizToken: string | null,
|
|
404
|
+
static reqBodyAuthStep2(authorizeCode: string, bizToken: string | null, _appId: string, terminalId: string, userCountryCode?: string, regionChange?: string): Record<string, any> {
|
|
405
|
+
// pyvesync parity:
|
|
406
|
+
// - Mirrors `pyvesync.models.vesync_models.RequestLoginTokenModel`
|
|
407
|
+
// - `timeZone` remains `DEFAULT_TZ` during auth (device timezone is handled elsewhere)
|
|
408
|
+
// - Step 2 intentionally does NOT include `appID` / `sourceAppID`. Those are only sent in Step 1
|
|
409
|
+
// (`RequestGetTokenModel`). pyvesync's Step 2 request model has no appID fields.
|
|
410
|
+
// If you see older diagnostics including appID/sourceAppID in Step 2, treat it as a superset payload
|
|
411
|
+
// rather than a requirement — our goal here is to match the known-working pyvesync request shape.
|
|
412
|
+
// - IMPORTANT: `bizToken` here is the *region-change token* returned by VeSync on Step 2 CROSS_REGION
|
|
413
|
+
// errors. It is not part of the normal Step 1 -> Step 2 flow. pyvesync does not send a Step 1
|
|
414
|
+
// bizToken in the initial Step 2 request (Step 1 result is just `{ accountID, authorizeCode }`);
|
|
415
|
+
// it only includes `bizToken` + `regionChange='lastRegion'`
|
|
416
|
+
// when retrying Step 2 after a cross-region response. See `pyvesync/auth.py:_exchange_authorization_code`.
|
|
419
417
|
const body: Record<string, any> = {
|
|
420
418
|
'method': 'loginByAuthorizeCode4Vesync',
|
|
421
419
|
'authorizeCode': authorizeCode,
|
|
422
420
|
'acceptLanguage': 'en',
|
|
423
|
-
'
|
|
421
|
+
'accountID': '',
|
|
422
|
+
'clientInfo': PHONE_BRAND,
|
|
424
423
|
'clientType': 'vesyncApp',
|
|
425
424
|
'clientVersion': CLIENT_VERSION,
|
|
426
425
|
'debugMode': false,
|
|
@@ -428,8 +427,9 @@ export class Helpers {
|
|
|
428
427
|
'osInfo': PHONE_OS,
|
|
429
428
|
'terminalId': terminalId,
|
|
430
429
|
'timeZone': DEFAULT_TZ,
|
|
431
|
-
'
|
|
432
|
-
'
|
|
430
|
+
'token': '',
|
|
431
|
+
'userCountryCode': (userCountryCode || DEFAULT_REGION).trim().toUpperCase(),
|
|
432
|
+
'traceId': generateAuthTraceId(terminalId)
|
|
433
433
|
};
|
|
434
434
|
|
|
435
435
|
// Only include bizToken if it's not null
|
|
@@ -524,14 +524,20 @@ export class Helpers {
|
|
|
524
524
|
headers: Record<string, string> = {},
|
|
525
525
|
manager: VeSync
|
|
526
526
|
): Promise<[any, number]> {
|
|
527
|
+
let url = '';
|
|
527
528
|
try {
|
|
529
|
+
// Prefer manager-scoped base URL (pyvesync resolves per-manager from current region).
|
|
530
|
+
// Keep the module-level base URL as a fallback for older callers.
|
|
531
|
+
let baseUrl = manager.apiBaseUrl || _apiBaseUrl;
|
|
532
|
+
|
|
528
533
|
// Ensure API base URL is properly set
|
|
529
|
-
if (!
|
|
530
|
-
logger.error('API base URL is not properly configured.
|
|
531
|
-
|
|
534
|
+
if (!baseUrl || baseUrl === 'undefined') {
|
|
535
|
+
logger.error('API base URL is not properly configured. Falling back to US endpoint...');
|
|
536
|
+
baseUrl = 'https://smartapi.vesync.com';
|
|
537
|
+
manager.apiBaseUrl = baseUrl;
|
|
532
538
|
}
|
|
533
|
-
|
|
534
|
-
|
|
539
|
+
|
|
540
|
+
url = baseUrl + endpoint;
|
|
535
541
|
logger.debug(`Making API call to: ${url}`);
|
|
536
542
|
|
|
537
543
|
const response = await axios({
|
|
@@ -571,7 +577,7 @@ export class Helpers {
|
|
|
571
577
|
status: error.response.status,
|
|
572
578
|
code: responseData?.code,
|
|
573
579
|
message: responseData?.msg,
|
|
574
|
-
url
|
|
580
|
+
url
|
|
575
581
|
});
|
|
576
582
|
|
|
577
583
|
return [responseData, error.response.status];
|
|
@@ -580,7 +586,7 @@ export class Helpers {
|
|
|
580
586
|
logger.error('API call failed:', {
|
|
581
587
|
code: error.code,
|
|
582
588
|
message: error.message,
|
|
583
|
-
url: _apiBaseUrl + endpoint
|
|
589
|
+
url: url || (manager.apiBaseUrl || _apiBaseUrl || '') + endpoint
|
|
584
590
|
});
|
|
585
591
|
return [null, 0];
|
|
586
592
|
}
|
|
@@ -660,20 +666,29 @@ export class Helpers {
|
|
|
660
666
|
try {
|
|
661
667
|
// Generate terminal ID to be used in both steps
|
|
662
668
|
const terminalId = generateTerminalId();
|
|
669
|
+
// pyvesync parity:
|
|
670
|
+
// - `userCountryCode` defaults to `DEFAULT_REGION` ("US"), even if we first try the EU base URL.
|
|
671
|
+
// - If the account truly belongs to another region, the Step 2 response includes the correct
|
|
672
|
+
// `countryCode`/`currentRegion` and a `bizToken` to retry Step 2 (handled below).
|
|
673
|
+
// Avoid "guessing" a country code from `region` or email heuristics here; it diverges from pyvesync.
|
|
674
|
+
const userCountryCode = (countryCodeOverride || DEFAULT_REGION).trim().toUpperCase();
|
|
663
675
|
|
|
664
676
|
// Step 1: Get authorization code
|
|
665
|
-
const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId,
|
|
677
|
+
const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId, userCountryCode);
|
|
666
678
|
const step1Headers = this.reqHeaderAuth();
|
|
667
679
|
|
|
668
680
|
// Set the correct regional endpoint
|
|
669
|
-
const originalUrl = getApiBaseUrl();
|
|
670
|
-
|
|
671
|
-
|
|
681
|
+
const originalUrl = manager.apiBaseUrl || getApiBaseUrl();
|
|
682
|
+
const knownBaseUrls = new Set(Object.values(REGION_ENDPOINTS));
|
|
683
|
+
const hasCustomBaseUrlOverride = !!manager.apiUrlOverride || !knownBaseUrls.has(originalUrl);
|
|
684
|
+
if (!hasCustomBaseUrlOverride && region in REGION_ENDPOINTS) {
|
|
685
|
+
const nextUrl = REGION_ENDPOINTS[region as keyof typeof REGION_ENDPOINTS];
|
|
686
|
+
manager.apiBaseUrl = nextUrl;
|
|
672
687
|
}
|
|
673
688
|
|
|
674
689
|
logger.debug('Step 1: Getting authorization code...', {
|
|
675
690
|
region,
|
|
676
|
-
endpoint: getApiBaseUrl(),
|
|
691
|
+
endpoint: manager.apiBaseUrl || getApiBaseUrl(),
|
|
677
692
|
body: { ...step1Body, password: '[REDACTED]' }
|
|
678
693
|
});
|
|
679
694
|
|
|
@@ -687,7 +702,7 @@ export class Helpers {
|
|
|
687
702
|
|
|
688
703
|
if (!authResponse || authStatus !== 200) {
|
|
689
704
|
logger.error('Step 1 failed:', authResponse);
|
|
690
|
-
|
|
705
|
+
manager.apiBaseUrl = originalUrl;
|
|
691
706
|
return [false, null, null, null];
|
|
692
707
|
}
|
|
693
708
|
|
|
@@ -697,26 +712,30 @@ export class Helpers {
|
|
|
697
712
|
// Check if it's a credential error (no point retrying)
|
|
698
713
|
if (isCredentialError(authResponse.code)) {
|
|
699
714
|
logger.error('Credential error detected - invalid username or password');
|
|
700
|
-
|
|
715
|
+
manager.apiBaseUrl = originalUrl;
|
|
701
716
|
return [false, null, null, 'credential_error'];
|
|
702
717
|
}
|
|
703
718
|
|
|
704
719
|
// Handle cross-region error (multiple error codes possible)
|
|
705
720
|
if (isCrossRegionError(authResponse.code)) {
|
|
706
721
|
logger.debug('Cross-region error detected:', authResponse.code, 'will try different region');
|
|
707
|
-
|
|
722
|
+
manager.apiBaseUrl = originalUrl;
|
|
708
723
|
return [false, null, null, 'cross_region'];
|
|
709
724
|
}
|
|
710
725
|
|
|
711
|
-
|
|
726
|
+
manager.apiBaseUrl = originalUrl;
|
|
712
727
|
return [false, null, null, null];
|
|
713
728
|
}
|
|
714
729
|
|
|
715
|
-
|
|
730
|
+
// pyvesync parity note:
|
|
731
|
+
// - Step 1 "authByPWDOrOTM" returns a result shaped like RespGetTokenResultModel:
|
|
732
|
+
// `{ accountID, authorizeCode }`. There is no Step 1 bizToken to forward.
|
|
733
|
+
// See `pyvesync/models/vesync_models.py:RespGetTokenResultModel`.
|
|
734
|
+
const { authorizeCode } = authResponse.result || {};
|
|
716
735
|
|
|
717
736
|
if (!authorizeCode) {
|
|
718
737
|
logger.error('Missing authorization code in step 1 response:', authResponse.result);
|
|
719
|
-
|
|
738
|
+
manager.apiBaseUrl = originalUrl;
|
|
720
739
|
return [false, null, null, null];
|
|
721
740
|
}
|
|
722
741
|
|
|
@@ -725,8 +744,17 @@ export class Helpers {
|
|
|
725
744
|
// Step 2: Login with authorization code
|
|
726
745
|
// Use the override if provided, otherwise default to 'US'
|
|
727
746
|
// Users should specify their actual country code in the configuration
|
|
728
|
-
const countryCodeForStep2 =
|
|
729
|
-
|
|
747
|
+
const countryCodeForStep2 = userCountryCode;
|
|
748
|
+
// pyvesync parity note:
|
|
749
|
+
// - Step 1 returns `authorizeCode` (and an `accountID`) but does not require/provide a bizToken for
|
|
750
|
+
// the initial Step 2 request.
|
|
751
|
+
// - Step 2 initial request omits `bizToken`.
|
|
752
|
+
// - Only if Step 2 returns a CROSS_REGION error does VeSync provide `result.bizToken`, which is then
|
|
753
|
+
// echoed back as `bizToken` along with `regionChange='lastRegion'` for a Step 2 retry.
|
|
754
|
+
// See `pyvesync/auth.py:_exchange_authorization_code` for the canonical behavior.
|
|
755
|
+
// - Step 2 also intentionally omits appID/sourceAppID (pyvesync parity); we still pass `appId` through
|
|
756
|
+
// to keep the function signature stable/historical.
|
|
757
|
+
const step2Body = this.reqBodyAuthStep2(authorizeCode, null, appId, terminalId, countryCodeForStep2);
|
|
730
758
|
|
|
731
759
|
logger.debug('Step 2: Logging in with authorization code...');
|
|
732
760
|
|
|
@@ -747,43 +775,85 @@ export class Helpers {
|
|
|
747
775
|
|
|
748
776
|
if (!loginResponse || loginStatus !== 200) {
|
|
749
777
|
logger.error('Step 2 failed:', loginResponse);
|
|
750
|
-
|
|
778
|
+
manager.apiBaseUrl = originalUrl;
|
|
751
779
|
return [false, null, null, null];
|
|
752
780
|
}
|
|
753
781
|
|
|
754
782
|
if (loginResponse.code !== 0) {
|
|
755
783
|
logger.debug('Step 2 error, code:', loginResponse.code, 'checking if cross-region...');
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
logger.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
784
|
+
|
|
785
|
+
// Align with pyvesync: on cross-region errors, retry Step 2 using the server-provided
|
|
786
|
+
// country/region and bizToken + regionChange.
|
|
787
|
+
if (isCrossRegionError(loginResponse.code)) {
|
|
788
|
+
const serverCountryCode: string | undefined = loginResponse.result?.countryCode;
|
|
789
|
+
const serverRegion: string | undefined = loginResponse.result?.currentRegion;
|
|
790
|
+
const regionChangeToken: string | undefined = loginResponse.result?.bizToken;
|
|
791
|
+
|
|
792
|
+
logger.debug('Cross-region error detected in Step 2; attempting pyvesync-style retry', {
|
|
793
|
+
requestedRegion: region,
|
|
794
|
+
requestedCountryCode: countryCodeForStep2,
|
|
795
|
+
serverCountryCode,
|
|
796
|
+
serverRegion,
|
|
797
|
+
hasRegionChangeToken: !!regionChangeToken,
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// If VeSync tells us which region to use, switch to it for subsequent requests.
|
|
801
|
+
if (!hasCustomBaseUrlOverride && serverRegion && serverRegion in REGION_ENDPOINTS) {
|
|
802
|
+
manager.region = serverRegion;
|
|
803
|
+
logger.debug(`Updated region to ${serverRegion} for Step 2 retry`);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// If VeSync gives us a region-change token, retry Step 2 without redoing Step 1.
|
|
807
|
+
if (regionChangeToken) {
|
|
808
|
+
const retryCountryCode = serverCountryCode || countryCodeForStep2;
|
|
809
|
+
const retryBody = this.reqBodyAuthStep2(
|
|
810
|
+
authorizeCode,
|
|
811
|
+
regionChangeToken,
|
|
812
|
+
appId,
|
|
813
|
+
terminalId,
|
|
814
|
+
retryCountryCode,
|
|
815
|
+
'lastRegion'
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
const [retryResponse, retryStatus] = await this.callApi(
|
|
819
|
+
'/user/api/accountManage/v1/loginByAuthorizeCode4Vesync',
|
|
820
|
+
'post',
|
|
821
|
+
retryBody,
|
|
822
|
+
step1Headers,
|
|
823
|
+
manager
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
if (retryResponse && retryStatus === 200 && retryResponse.code === 0) {
|
|
827
|
+
const { token, accountID, countryCode } = retryResponse.result || {};
|
|
828
|
+
if (token && accountID) {
|
|
829
|
+
logger.debug('Step 2 retry successful, got token and accountID');
|
|
830
|
+
return [true, token, accountID, countryCode || retryCountryCode];
|
|
831
|
+
}
|
|
832
|
+
logger.error('Missing required fields in Step 2 retry response:', retryResponse.result);
|
|
833
|
+
manager.apiBaseUrl = originalUrl;
|
|
834
|
+
return [false, null, null, null];
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
logger.error('Step 2 retry failed:', {
|
|
838
|
+
status: retryStatus,
|
|
839
|
+
code: retryResponse?.code,
|
|
840
|
+
msg: retryResponse?.msg,
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
manager.apiBaseUrl = originalUrl;
|
|
844
|
+
return [false, null, null, 'cross_region_retry'];
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
logger.error('Cross-region error returned no region-change token; cannot retry Step 2');
|
|
848
|
+
manager.apiBaseUrl = originalUrl;
|
|
777
849
|
return [false, null, null, 'cross_region_retry'];
|
|
778
850
|
}
|
|
779
|
-
|
|
851
|
+
|
|
780
852
|
logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
|
|
781
|
-
|
|
853
|
+
manager.apiBaseUrl = originalUrl;
|
|
782
854
|
return [false, null, null, null];
|
|
783
855
|
}
|
|
784
856
|
|
|
785
|
-
setApiBaseUrl(originalUrl); // Restore original URL on success
|
|
786
|
-
|
|
787
857
|
const { token, accountID, countryCode } = loginResponse.result || {};
|
|
788
858
|
|
|
789
859
|
if (!token || !accountID) {
|
|
@@ -808,7 +878,7 @@ export class Helpers {
|
|
|
808
878
|
const body = this.reqBody(manager, 'login');
|
|
809
879
|
|
|
810
880
|
logger.debug('Legacy login attempt...', {
|
|
811
|
-
endpoint: getApiBaseUrl(),
|
|
881
|
+
endpoint: manager.apiBaseUrl || getApiBaseUrl(),
|
|
812
882
|
body: { ...body, password: '[REDACTED]' }
|
|
813
883
|
});
|
|
814
884
|
|