tsvesync 1.0.97 → 1.0.99

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.
@@ -12,6 +12,7 @@ export declare const API_TIMEOUT = 15000;
12
12
  export declare const APP_VERSION = "5.6.60";
13
13
  export declare const PHONE_BRAND = "SM N9005";
14
14
  export declare const PHONE_OS = "Android";
15
+ export declare const CLIENT_INFO = "SM N9005";
15
16
  export declare const USER_TYPE = "1";
16
17
  export declare const DEFAULT_TZ = "America/New_York";
17
18
  export declare const DEFAULT_REGION = "US";
@@ -28,6 +29,7 @@ export declare const REGION_ENDPOINTS: {
28
29
  export declare const CROSS_REGION_ERROR_CODE = -11260022;
29
30
  export declare const APP_VERSION_TOO_LOW_ERROR_CODE = -11012022;
30
31
  export declare function generateAppId(): string;
32
+ export declare function generateTerminalId(): string;
31
33
  export interface RequestBody {
32
34
  acceptLanguage?: string;
33
35
  accountID?: string;
@@ -111,11 +113,11 @@ export declare class Helpers {
111
113
  /**
112
114
  * Build request body for initial authentication step
113
115
  */
114
- static reqBodyAuthStep1(manager: VeSync, appId: string): Record<string, any>;
116
+ static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string): Record<string, any>;
115
117
  /**
116
118
  * Build request body for second authentication step (login with authorize code)
117
119
  */
118
- static reqBodyAuthStep2(authorizeCode: string, bizToken: string, appId: string, userCountryCode?: string): Record<string, any>;
120
+ static reqBodyAuthStep2(authorizeCode: string, bizToken: string | null, appId: string, terminalId: string, userCountryCode?: string, regionChange?: string): Record<string, any>;
119
121
  /**
120
122
  * Builder for body of api requests
121
123
  */
@@ -6,13 +6,14 @@ 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.APP_VERSION_TOO_LOW_ERROR_CODE = exports.CROSS_REGION_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.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = void 0;
9
+ exports.Helpers = exports.APP_VERSION_TOO_LOW_ERROR_CODE = exports.CROSS_REGION_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;
13
13
  exports.setCurrentRegion = setCurrentRegion;
14
14
  exports.getRegionFromCountryCode = getRegionFromCountryCode;
15
15
  exports.generateAppId = generateAppId;
16
+ exports.generateTerminalId = generateTerminalId;
16
17
  const axios_1 = __importDefault(require("axios"));
17
18
  const crypto_1 = __importDefault(require("crypto"));
18
19
  const logger_1 = require("./logger");
@@ -53,6 +54,7 @@ exports.API_TIMEOUT = 15000;
53
54
  exports.APP_VERSION = '5.6.60';
54
55
  exports.PHONE_BRAND = 'SM N9005';
55
56
  exports.PHONE_OS = 'Android';
57
+ exports.CLIENT_INFO = 'SM N9005';
56
58
  exports.USER_TYPE = '1';
57
59
  exports.DEFAULT_TZ = 'America/New_York';
58
60
  exports.DEFAULT_REGION = 'US';
@@ -79,6 +81,15 @@ function generateAppId() {
79
81
  }
80
82
  return result;
81
83
  }
84
+ // Generate unique terminal ID for authentication
85
+ function generateTerminalId() {
86
+ const chars = 'abcdef0123456789';
87
+ let result = '';
88
+ for (let i = 0; i < 16; i++) {
89
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
90
+ }
91
+ return result;
92
+ }
82
93
  class Helpers {
83
94
  /**
84
95
  * Calculate MD5 hash
@@ -158,36 +169,54 @@ class Helpers {
158
169
  /**
159
170
  * Build request body for initial authentication step
160
171
  */
161
- static reqBodyAuthStep1(manager, appId) {
172
+ static reqBodyAuthStep1(manager, appId, terminalId) {
162
173
  return {
163
174
  'email': manager.username,
175
+ 'method': 'authByPWDOrOTM',
164
176
  'password': this.hashPassword(manager.password),
165
- 'timeZone': manager.timeZone,
166
177
  'acceptLanguage': 'en',
167
- 'appVersion': exports.APP_VERSION,
168
- 'phoneBrand': exports.PHONE_BRAND,
169
- 'phoneOS': exports.PHONE_OS,
170
- 'traceId': Date.now().toString(),
171
- 'authProtocolType': 'pwd',
172
- 'clientType': 'android',
173
- 'sourceAppID': 'vesync_android',
178
+ 'accountID': '',
179
+ 'authProtocolType': 'generic',
180
+ 'clientInfo': exports.CLIENT_INFO,
181
+ 'clientType': 'vesyncApp',
182
+ 'clientVersion': exports.CLIENT_VERSION,
183
+ 'debugMode': false,
184
+ 'osInfo': exports.PHONE_OS,
185
+ 'terminalId': terminalId,
186
+ 'timeZone': manager.timeZone || exports.DEFAULT_TZ,
187
+ 'token': '',
188
+ 'userCountryCode': 'US',
174
189
  'appID': appId,
175
- 'clientVersion': exports.CLIENT_VERSION
190
+ 'sourceAppID': appId,
191
+ 'traceId': `APP${appId}${Math.floor(Date.now() / 1000)}`
176
192
  };
177
193
  }
178
194
  /**
179
195
  * Build request body for second authentication step (login with authorize code)
180
196
  */
181
- static reqBodyAuthStep2(authorizeCode, bizToken, appId, userCountryCode) {
197
+ static reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, userCountryCode, regionChange) {
182
198
  const body = {
199
+ 'method': 'loginByAuthorizeCode4Vesync',
183
200
  'authorizeCode': authorizeCode,
184
- 'bizToken': bizToken,
185
- 'appID': appId,
201
+ 'acceptLanguage': 'en',
202
+ 'clientInfo': exports.CLIENT_INFO,
203
+ 'clientType': 'vesyncApp',
186
204
  'clientVersion': exports.CLIENT_VERSION,
187
- 'acceptLanguage': 'en'
205
+ 'debugMode': false,
206
+ 'emailSubscriptions': false,
207
+ 'osInfo': exports.PHONE_OS,
208
+ 'terminalId': terminalId,
209
+ 'timeZone': exports.DEFAULT_TZ,
210
+ 'userCountryCode': userCountryCode || 'US',
211
+ 'traceId': `APP${appId}${Math.floor(Date.now() / 1000)}`
188
212
  };
189
- if (userCountryCode) {
190
- body.userCountryCode = userCountryCode;
213
+ // Only include bizToken if it's not null
214
+ if (bizToken) {
215
+ body.bizToken = bizToken;
216
+ }
217
+ // Only include regionChange if provided
218
+ if (regionChange) {
219
+ body.regionChange = regionChange;
191
220
  }
192
221
  return body;
193
222
  }
@@ -370,8 +399,10 @@ class Helpers {
370
399
  */
371
400
  static async authNewFlow(manager, appId, region = 'US') {
372
401
  try {
402
+ // Generate terminal ID to be used in both steps
403
+ const terminalId = generateTerminalId();
373
404
  // Step 1: Get authorization code
374
- const step1Body = this.reqBodyAuthStep1(manager, appId);
405
+ const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId);
375
406
  const step1Headers = this.reqHeaderAuth();
376
407
  // Set the correct regional endpoint
377
408
  const originalUrl = getApiBaseUrl();
@@ -401,14 +432,14 @@ class Helpers {
401
432
  return [false, null, null, null];
402
433
  }
403
434
  const { authorizeCode, bizToken, userCountryCode } = authResponse.result || {};
404
- if (!authorizeCode || !bizToken) {
405
- logger_1.logger.error('Missing required fields in step 1 response:', authResponse.result);
435
+ if (!authorizeCode) {
436
+ logger_1.logger.error('Missing authorization code in step 1 response:', authResponse.result);
406
437
  setApiBaseUrl(originalUrl); // Restore original URL
407
438
  return [false, null, null, null];
408
439
  }
409
440
  logger_1.logger.debug('Step 1 successful, got authorization code');
410
441
  // Step 2: Login with authorization code
411
- const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, userCountryCode);
442
+ const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, userCountryCode);
412
443
  logger_1.logger.debug('Step 2: Logging in with authorization code...');
413
444
  const [loginResponse, loginStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', step2Body, step1Headers, manager);
414
445
  setApiBaseUrl(originalUrl); // Restore original URL
@@ -417,6 +448,30 @@ class Helpers {
417
448
  return [false, null, null, null];
418
449
  }
419
450
  if (loginResponse.code !== 0) {
451
+ // Handle cross-region error with retry (like pyvesync PR #340)
452
+ if (loginResponse.code === exports.CROSS_REGION_ERROR_CODE) {
453
+ logger_1.logger.debug('Cross-region error detected in Step 2, retrying with region change token...');
454
+ const { bizToken: regionChangeToken, countryCode: newCountryCode } = loginResponse.result || {};
455
+ if (regionChangeToken) {
456
+ // Retry Step 2 with region change token
457
+ const retryBody = {
458
+ ...step2Body,
459
+ bizToken: regionChangeToken,
460
+ regionChange: 'last_region',
461
+ userCountryCode: newCountryCode || userCountryCode
462
+ };
463
+ logger_1.logger.debug('Retrying Step 2 with region change token...');
464
+ const [retryResponse, retryStatus] = await this.callApi('/user/api/accountManage/v1/loginByAuthorizeCode4Vesync', 'post', retryBody, step1Headers, manager);
465
+ if (retryResponse && retryResponse.code === 0) {
466
+ const { token, accountID, countryCode } = retryResponse.result || {};
467
+ if (token && accountID) {
468
+ logger_1.logger.debug('Step 2 retry successful with region change');
469
+ return [true, token, accountID, countryCode || newCountryCode];
470
+ }
471
+ }
472
+ logger_1.logger.error('Step 2 retry failed:', retryResponse);
473
+ }
474
+ }
420
475
  logger_1.logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
421
476
  return [false, null, null, null];
422
477
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.0.97",
3
+ "version": "1.0.99",
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",
@@ -54,6 +54,7 @@ export const API_TIMEOUT = 15000;
54
54
  export const APP_VERSION = '5.6.60';
55
55
  export const PHONE_BRAND = 'SM N9005';
56
56
  export const PHONE_OS = 'Android';
57
+ export const CLIENT_INFO = 'SM N9005';
57
58
  export const USER_TYPE = '1';
58
59
  export const DEFAULT_TZ = 'America/New_York';
59
60
  export const DEFAULT_REGION = 'US';
@@ -84,6 +85,16 @@ export function generateAppId(): string {
84
85
  return result;
85
86
  }
86
87
 
88
+ // Generate unique terminal ID for authentication
89
+ export function generateTerminalId(): string {
90
+ const chars = 'abcdef0123456789';
91
+ let result = '';
92
+ for (let i = 0; i < 16; i++) {
93
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
94
+ }
95
+ return result;
96
+ }
97
+
87
98
  export interface RequestBody {
88
99
  acceptLanguage?: string;
89
100
  accountID?: string;
@@ -227,38 +238,57 @@ export class Helpers {
227
238
  /**
228
239
  * Build request body for initial authentication step
229
240
  */
230
- static reqBodyAuthStep1(manager: VeSync, appId: string): Record<string, any> {
241
+ static reqBodyAuthStep1(manager: VeSync, appId: string, terminalId: string): Record<string, any> {
231
242
  return {
232
243
  'email': manager.username,
244
+ 'method': 'authByPWDOrOTM',
233
245
  'password': this.hashPassword(manager.password),
234
- 'timeZone': manager.timeZone,
235
246
  'acceptLanguage': 'en',
236
- 'appVersion': APP_VERSION,
237
- 'phoneBrand': PHONE_BRAND,
238
- 'phoneOS': PHONE_OS,
239
- 'traceId': Date.now().toString(),
240
- 'authProtocolType': 'pwd',
241
- 'clientType': 'android',
242
- 'sourceAppID': 'vesync_android',
247
+ 'accountID': '',
248
+ 'authProtocolType': 'generic',
249
+ 'clientInfo': CLIENT_INFO,
250
+ 'clientType': 'vesyncApp',
251
+ 'clientVersion': CLIENT_VERSION,
252
+ 'debugMode': false,
253
+ 'osInfo': PHONE_OS,
254
+ 'terminalId': terminalId,
255
+ 'timeZone': manager.timeZone || DEFAULT_TZ,
256
+ 'token': '',
257
+ 'userCountryCode': 'US',
243
258
  'appID': appId,
244
- 'clientVersion': CLIENT_VERSION
259
+ 'sourceAppID': appId,
260
+ 'traceId': `APP${appId}${Math.floor(Date.now() / 1000)}`
245
261
  };
246
262
  }
247
263
 
248
264
  /**
249
265
  * Build request body for second authentication step (login with authorize code)
250
266
  */
251
- static reqBodyAuthStep2(authorizeCode: string, bizToken: string, appId: string, userCountryCode?: string): Record<string, any> {
267
+ static reqBodyAuthStep2(authorizeCode: string, bizToken: string | null, appId: string, terminalId: string, userCountryCode?: string, regionChange?: string): Record<string, any> {
252
268
  const body: Record<string, any> = {
269
+ 'method': 'loginByAuthorizeCode4Vesync',
253
270
  'authorizeCode': authorizeCode,
254
- 'bizToken': bizToken,
255
- 'appID': appId,
271
+ 'acceptLanguage': 'en',
272
+ 'clientInfo': CLIENT_INFO,
273
+ 'clientType': 'vesyncApp',
256
274
  'clientVersion': CLIENT_VERSION,
257
- 'acceptLanguage': 'en'
275
+ 'debugMode': false,
276
+ 'emailSubscriptions': false,
277
+ 'osInfo': PHONE_OS,
278
+ 'terminalId': terminalId,
279
+ 'timeZone': DEFAULT_TZ,
280
+ 'userCountryCode': userCountryCode || 'US',
281
+ 'traceId': `APP${appId}${Math.floor(Date.now() / 1000)}`
258
282
  };
259
283
 
260
- if (userCountryCode) {
261
- body.userCountryCode = userCountryCode;
284
+ // Only include bizToken if it's not null
285
+ if (bizToken) {
286
+ body.bizToken = bizToken;
287
+ }
288
+
289
+ // Only include regionChange if provided
290
+ if (regionChange) {
291
+ body.regionChange = regionChange;
262
292
  }
263
293
 
264
294
  return body;
@@ -470,8 +500,11 @@ export class Helpers {
470
500
  */
471
501
  static async authNewFlow(manager: VeSync, appId: string, region: string = 'US'): Promise<[boolean, string | null, string | null, string | null]> {
472
502
  try {
503
+ // Generate terminal ID to be used in both steps
504
+ const terminalId = generateTerminalId();
505
+
473
506
  // Step 1: Get authorization code
474
- const step1Body = this.reqBodyAuthStep1(manager, appId);
507
+ const step1Body = this.reqBodyAuthStep1(manager, appId, terminalId);
475
508
  const step1Headers = this.reqHeaderAuth();
476
509
 
477
510
  // Set the correct regional endpoint
@@ -516,8 +549,8 @@ export class Helpers {
516
549
 
517
550
  const { authorizeCode, bizToken, userCountryCode } = authResponse.result || {};
518
551
 
519
- if (!authorizeCode || !bizToken) {
520
- logger.error('Missing required fields in step 1 response:', authResponse.result);
552
+ if (!authorizeCode) {
553
+ logger.error('Missing authorization code in step 1 response:', authResponse.result);
521
554
  setApiBaseUrl(originalUrl); // Restore original URL
522
555
  return [false, null, null, null];
523
556
  }
@@ -525,7 +558,7 @@ export class Helpers {
525
558
  logger.debug('Step 1 successful, got authorization code');
526
559
 
527
560
  // Step 2: Login with authorization code
528
- const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, userCountryCode);
561
+ const step2Body = this.reqBodyAuthStep2(authorizeCode, bizToken, appId, terminalId, userCountryCode);
529
562
 
530
563
  logger.debug('Step 2: Logging in with authorization code...');
531
564
 
@@ -545,6 +578,43 @@ export class Helpers {
545
578
  }
546
579
 
547
580
  if (loginResponse.code !== 0) {
581
+ // Handle cross-region error with retry (like pyvesync PR #340)
582
+ if (loginResponse.code === CROSS_REGION_ERROR_CODE) {
583
+ logger.debug('Cross-region error detected in Step 2, retrying with region change token...');
584
+
585
+ const { bizToken: regionChangeToken, countryCode: newCountryCode } = loginResponse.result || {};
586
+
587
+ if (regionChangeToken) {
588
+ // Retry Step 2 with region change token
589
+ const retryBody = {
590
+ ...step2Body,
591
+ bizToken: regionChangeToken,
592
+ regionChange: 'last_region',
593
+ userCountryCode: newCountryCode || userCountryCode
594
+ };
595
+
596
+ logger.debug('Retrying Step 2 with region change token...');
597
+
598
+ const [retryResponse, retryStatus] = await this.callApi(
599
+ '/user/api/accountManage/v1/loginByAuthorizeCode4Vesync',
600
+ 'post',
601
+ retryBody,
602
+ step1Headers,
603
+ manager
604
+ );
605
+
606
+ if (retryResponse && retryResponse.code === 0) {
607
+ const { token, accountID, countryCode } = retryResponse.result || {};
608
+ if (token && accountID) {
609
+ logger.debug('Step 2 retry successful with region change');
610
+ return [true, token, accountID, countryCode || newCountryCode];
611
+ }
612
+ }
613
+
614
+ logger.error('Step 2 retry failed:', retryResponse);
615
+ }
616
+ }
617
+
548
618
  logger.error('Step 2 error code:', loginResponse.code, 'message:', loginResponse.msg);
549
619
  return [false, null, null, null];
550
620
  }