@etsoo/appscript 1.2.57 → 1.2.60

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
@@ -15,6 +15,12 @@ Using yarn:
15
15
  $ yarn add @etsoo/appscript
16
16
  ```
17
17
 
18
+ ## Client data security framework
19
+ - CoreApp, constructor, reads deviceId from session storage
20
+ - restore, when deviceId is empty, try to restore from persisted storage, get the passphrase (encryption / decription) or remove all data keys
21
+ - initCall (everytime the application running), if passphrase is there, just return, otherwise read from serverside
22
+ - device updated will cause validataion failure. Please call initCall(undefined, true);
23
+
18
24
  ## Structure
19
25
 
20
26
  ### address - Address (region) related
@@ -44,18 +50,19 @@ $ yarn add @etsoo/appscript
44
50
  - IExternalSettings - External settings items
45
51
  - IExternalSettingsHost - External settings host passed by external script
46
52
 
53
+ #### UserRole.ts ####
54
+ - Standard user roles
55
+
47
56
  ### bridges - Works with Electron
48
57
 
49
- #### ElectronBridge.ts ####
50
- - AppRuntime - Action result to error type.
58
+ #### BridgeUtils.ts ####
59
+ - BridgeUtils - Bridge utils
51
60
 
52
- #### IAppData.ts ####
53
- - IAppData - App data interface.
61
+ #### FlutterHost.ts ####
62
+ - FlutterHost - Flutter JavaScript Host
54
63
 
55
- #### IBridge.ts ####
56
- - IBridgeUnsubscribe - Bridge unsubscribe interface
57
- - IBridgeListener - Bridge listener interface
58
- - IBridge - Bridge interface
64
+ #### IBridgeHost.ts ####
65
+ - IBridgeHost - Bridge host interface
59
66
 
60
67
  ### business - Business logics
61
68
 
@@ -71,9 +78,20 @@ $ yarn add @etsoo/appscript
71
78
  - getUnits - Get all product units
72
79
  - getRepeatOptions - Get all repeat options
73
80
 
81
+ #### EntityStatus.ts ####
82
+ - EntityStatus - Standard entity status enum
83
+
74
84
  #### ProductUnit.ts ####
75
85
  - ProductUnit - Product units enum
76
86
 
87
+ #### RepeatOption.ts ####
88
+ - RepeatOption - Repeat options
89
+
90
+ ### def - Type definition
91
+
92
+ #### ListItem.ts ####
93
+ - ListItem - List item definition
94
+
77
95
  ### dto - Data transfer object
78
96
 
79
97
  #### IdDto.ts ####
@@ -82,8 +100,11 @@ $ yarn add @etsoo/appscript
82
100
  #### IdLabelDto.ts ####
83
101
  - IdLabelDto - Dto with id and label field
84
102
 
85
- #### UpdateDto.ts ####
86
- - UpdateDto - Dto with id and changedFields
103
+ #### IdLabelPrimaryDto.ts ####
104
+ - IdLabelPrimaryDto - Dto with id, label and primary field
105
+
106
+ #### IdLabelPrimaryDto.ts ####
107
+ - InitCallDto - Init call dto
87
108
 
88
109
  ### i18n - Multiple cultures
89
110
 
@@ -93,7 +114,7 @@ $ yarn add @etsoo/appscript
93
114
  - ActionResult - API call action result extends IActionResult
94
115
 
95
116
  #### ActionResultError.ts ####
96
- - ActionResultError - Action result to error type.
117
+ - ActionResultError - Action result to error type
97
118
 
98
119
  #### IActionResult.ts ####
99
120
  - IResultData - Result data interface
@@ -102,6 +123,10 @@ $ yarn add @etsoo/appscript
102
123
  - IActionResult - Action result interface
103
124
  - ActionResultId - Action result with id data
104
125
 
126
+ #### InitCallResultData.ts ####
127
+ - InitCallResultData - Init call result data
128
+ - InitCallResult - Init call result
129
+
105
130
  ### state - State management
106
131
 
107
132
  #### Culture.ts ####
@@ -299,9 +299,10 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
299
299
  /**
300
300
  * Init call
301
301
  * @param callback Callback
302
+ * @param resetKeys Reset all keys first
302
303
  * @returns Result
303
304
  */
304
- initCall(callback?: (result: boolean) => void): Promise<void>;
305
+ initCall(callback?: (result: boolean) => void, resetKeys?: boolean): Promise<void>;
305
306
  /**
306
307
  * Callback where exit a page
307
308
  */
@@ -513,15 +514,16 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
513
514
  /**
514
515
  * Init call
515
516
  * @param callback Callback
517
+ * @param resetKeys Reset all keys first
516
518
  * @returns Result
517
519
  */
518
- initCall(callback?: (result: boolean) => void): Promise<void>;
520
+ initCall(callback?: (result: boolean) => void, resetKeys?: boolean): Promise<void>;
519
521
  /**
520
522
  * Init call update
521
523
  * @param data Result data
522
524
  * @param timestamp Timestamp
523
525
  */
524
- protected initCallUpdate(data: InitCallResultData, timestamp: number): void;
526
+ protected initCallUpdate(data: InitCallResultData, timestamp: number): boolean;
525
527
  /**
526
528
  * Init call encrypted fields update
527
529
  * @returns Fields
@@ -179,9 +179,9 @@ class CoreApp {
179
179
  this.resetKeys();
180
180
  return false;
181
181
  }
182
- // this.name to identifier different app's secret
183
182
  const passphraseEncrypted = this.storage.getData(this.fields.devicePassphrase);
184
183
  if (passphraseEncrypted) {
184
+ // this.name to identifier different app's secret
185
185
  const passphraseDecrypted = this.decrypt(passphraseEncrypted, this.name);
186
186
  if (passphraseDecrypted != null) {
187
187
  // Add the device to the list
@@ -258,10 +258,14 @@ class CoreApp {
258
258
  /**
259
259
  * Init call
260
260
  * @param callback Callback
261
+ * @param resetKeys Reset all keys first
261
262
  * @returns Result
262
263
  */
263
- async initCall(callback) {
264
+ async initCall(callback, resetKeys) {
264
265
  var _a;
266
+ // Reset keys
267
+ if (resetKeys)
268
+ this.resetKeys();
265
269
  // Passphrase exists?
266
270
  if (this.passphrase) {
267
271
  if (callback)
@@ -280,11 +284,13 @@ class CoreApp {
280
284
  };
281
285
  const result = await this.apiInitCall(data);
282
286
  if (result == null) {
287
+ // API error will popup
283
288
  if (callback)
284
289
  callback(false);
285
290
  return;
286
291
  }
287
292
  if (result.data == null) {
293
+ // Popup no data error
288
294
  this.notifier.alert(this.get('noData'));
289
295
  if (callback)
290
296
  callback(false);
@@ -308,9 +314,12 @@ class CoreApp {
308
314
  this.storage.setData(this.fields.deviceId, undefined);
309
315
  return;
310
316
  }
311
- this.initCallUpdate(result.data, data.timestamp);
317
+ const updateResult = this.initCallUpdate(result.data, data.timestamp);
318
+ if (!updateResult) {
319
+ this.notifier.alert(this.get('noData') + '(Update)');
320
+ }
312
321
  if (callback)
313
- callback(true);
322
+ callback(updateResult);
314
323
  }
315
324
  /**
316
325
  * Init call update
@@ -320,12 +329,12 @@ class CoreApp {
320
329
  initCallUpdate(data, timestamp) {
321
330
  // Data check
322
331
  if (data.deviceId == null || data.passphrase == null)
323
- return;
332
+ return false;
324
333
  // Decrypt
325
334
  // Should be done within 120 seconds after returning from the backend
326
335
  const passphrase = this.decrypt(data.passphrase, timestamp.toString());
327
336
  if (passphrase == null)
328
- return;
337
+ return false;
329
338
  // Update device id and cache it
330
339
  this._deviceId = data.deviceId;
331
340
  this.storage.setData(this.fields.deviceId, this._deviceId);
@@ -345,22 +354,31 @@ class CoreApp {
345
354
  const currentValue = this.storage.getData(field);
346
355
  if (currentValue == null || currentValue === '')
347
356
  continue;
357
+ if (prev == null) {
358
+ // Reset the field
359
+ this.storage.setData(field, undefined);
360
+ continue;
361
+ }
348
362
  const enhanced = currentValue.indexOf('!') >= 8;
349
- let newValueSource = null;
363
+ let newValueSource;
350
364
  if (enhanced) {
351
365
  newValueSource = this.decryptEnhanced(currentValue, prev, 12);
352
366
  }
353
367
  else {
354
368
  newValueSource = this.decrypt(currentValue, prev);
355
369
  }
356
- if (newValueSource == null || newValueSource === '')
370
+ if (newValueSource == null || newValueSource === '') {
371
+ // Reset the field
372
+ this.storage.setData(field, undefined);
357
373
  continue;
374
+ }
358
375
  const newValue = enhanced
359
376
  ? this.encryptEnhanced(newValueSource)
360
377
  : this.encrypt(newValueSource);
361
378
  this.storage.setData(field, newValue);
362
379
  }
363
380
  }
381
+ return true;
364
382
  }
365
383
  /**
366
384
  * Init call encrypted fields update
@@ -488,19 +506,25 @@ class CoreApp {
488
506
  const iterations = parseInt(messageEncrypted.substring(0, 2), 10);
489
507
  if (isNaN(iterations))
490
508
  return undefined;
491
- const salt = crypto_js_1.enc.Hex.parse(messageEncrypted.substring(2, 34));
492
- const iv = crypto_js_1.enc.Hex.parse(messageEncrypted.substring(34, 66));
493
- const encrypted = messageEncrypted.substring(66);
494
- const key = (0, crypto_js_1.PBKDF2)(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
495
- keySize: 8,
496
- hasher: crypto_js_1.algo.SHA256,
497
- iterations: 1000 * iterations
498
- });
499
- return crypto_js_1.AES.decrypt(encrypted, key, {
500
- iv,
501
- padding: crypto_js_1.pad.Pkcs7,
502
- mode: crypto_js_1.mode.CBC
503
- }).toString(crypto_js_1.enc.Utf8);
509
+ try {
510
+ const salt = crypto_js_1.enc.Hex.parse(messageEncrypted.substring(2, 34));
511
+ const iv = crypto_js_1.enc.Hex.parse(messageEncrypted.substring(34, 66));
512
+ const encrypted = messageEncrypted.substring(66);
513
+ const key = (0, crypto_js_1.PBKDF2)(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
514
+ keySize: 8,
515
+ hasher: crypto_js_1.algo.SHA256,
516
+ iterations: 1000 * iterations
517
+ });
518
+ return crypto_js_1.AES.decrypt(encrypted, key, {
519
+ iv,
520
+ padding: crypto_js_1.pad.Pkcs7,
521
+ mode: crypto_js_1.mode.CBC
522
+ }).toString(crypto_js_1.enc.Utf8);
523
+ }
524
+ catch (e) {
525
+ console.log('decrypt', e);
526
+ return undefined;
527
+ }
504
528
  }
505
529
  /**
506
530
  * Enhanced decrypt message
@@ -516,20 +540,26 @@ class CoreApp {
516
540
  if (pos < 8 || messageEncrypted.length <= 66)
517
541
  return undefined;
518
542
  const timestamp = messageEncrypted.substring(0, pos);
519
- if (durationSeconds != null && durationSeconds > 0) {
520
- const milseconds = shared_1.Utils.charsToNumber(timestamp);
521
- if (isNaN(milseconds) || milseconds < 1)
522
- return undefined;
523
- const timespan = new Date().substract(new Date(milseconds));
524
- if ((durationSeconds <= 12 &&
525
- timespan.totalMonths > durationSeconds) ||
526
- (durationSeconds > 12 &&
527
- timespan.totalSeconds > durationSeconds))
528
- return undefined;
543
+ try {
544
+ if (durationSeconds != null && durationSeconds > 0) {
545
+ const milseconds = shared_1.Utils.charsToNumber(timestamp);
546
+ if (isNaN(milseconds) || milseconds < 1)
547
+ return undefined;
548
+ const timespan = new Date().substract(new Date(milseconds));
549
+ if ((durationSeconds <= 12 &&
550
+ timespan.totalMonths > durationSeconds) ||
551
+ (durationSeconds > 12 &&
552
+ timespan.totalSeconds > durationSeconds))
553
+ return undefined;
554
+ }
555
+ const message = messageEncrypted.substring(pos + 1);
556
+ passphrase = this.encryptionEnhance(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, timestamp);
557
+ return this.decrypt(message, passphrase);
558
+ }
559
+ catch (e) {
560
+ console.log('decryptEnhanced', e);
561
+ return undefined;
529
562
  }
530
- const message = messageEncrypted.substring(pos + 1);
531
- passphrase = this.encryptionEnhance(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, timestamp);
532
- return this.decrypt(message, passphrase);
533
563
  }
534
564
  /**
535
565
  * Detect IP data, call only one time
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bridge host
2
+ * Bridge host interface
3
3
  */
4
4
  export interface IBridgeHost {
5
5
  /**
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "Are you sure you want to permanently delete this {0}?",
33
33
  "description": "Description",
34
34
  "done": "Done",
35
+ "download": "Download",
35
36
  "edit": "Edit",
36
37
  "email": "Email",
37
38
  "emailAddresses": "Email addresses",
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "确定要永久删除此{0}吗?",
33
33
  "description": "描述",
34
34
  "done": "完成",
35
+ "download": "下载",
35
36
  "edit": "修改",
36
37
  "email": "电子邮箱",
37
38
  "emailAddresses": "电子邮箱",
@@ -50,7 +51,7 @@
50
51
  "message": "留言",
51
52
  "mobile": "手机号码",
52
53
  "mobilePhones": "手机号码",
53
- "monthLabel": "{0}個月",
54
+ "monthLabel": "{0}个月",
54
55
  "months": [
55
56
  "1月",
56
57
  "2月",
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "確定要永久刪除此{0}嗎?",
33
33
  "description": "描述",
34
34
  "done": "完成",
35
+ "download": "下載",
35
36
  "edit": "修改",
36
37
  "email": "電子郵箱",
37
38
  "emailAddresses": "電子郵箱",
@@ -50,7 +51,7 @@
50
51
  "message": "留言",
51
52
  "mobilePhone": "手機號碼",
52
53
  "mobilePhones": "手機號碼",
53
- "monthLabel": "{0}个月",
54
+ "monthLabel": "{0}個月",
54
55
  "months": [
55
56
  "1月",
56
57
  "2月",
package/lib/cjs/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -299,9 +299,10 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
299
299
  /**
300
300
  * Init call
301
301
  * @param callback Callback
302
+ * @param resetKeys Reset all keys first
302
303
  * @returns Result
303
304
  */
304
- initCall(callback?: (result: boolean) => void): Promise<void>;
305
+ initCall(callback?: (result: boolean) => void, resetKeys?: boolean): Promise<void>;
305
306
  /**
306
307
  * Callback where exit a page
307
308
  */
@@ -513,15 +514,16 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
513
514
  /**
514
515
  * Init call
515
516
  * @param callback Callback
517
+ * @param resetKeys Reset all keys first
516
518
  * @returns Result
517
519
  */
518
- initCall(callback?: (result: boolean) => void): Promise<void>;
520
+ initCall(callback?: (result: boolean) => void, resetKeys?: boolean): Promise<void>;
519
521
  /**
520
522
  * Init call update
521
523
  * @param data Result data
522
524
  * @param timestamp Timestamp
523
525
  */
524
- protected initCallUpdate(data: InitCallResultData, timestamp: number): void;
526
+ protected initCallUpdate(data: InitCallResultData, timestamp: number): boolean;
525
527
  /**
526
528
  * Init call encrypted fields update
527
529
  * @returns Fields
@@ -176,9 +176,9 @@ export class CoreApp {
176
176
  this.resetKeys();
177
177
  return false;
178
178
  }
179
- // this.name to identifier different app's secret
180
179
  const passphraseEncrypted = this.storage.getData(this.fields.devicePassphrase);
181
180
  if (passphraseEncrypted) {
181
+ // this.name to identifier different app's secret
182
182
  const passphraseDecrypted = this.decrypt(passphraseEncrypted, this.name);
183
183
  if (passphraseDecrypted != null) {
184
184
  // Add the device to the list
@@ -255,10 +255,14 @@ export class CoreApp {
255
255
  /**
256
256
  * Init call
257
257
  * @param callback Callback
258
+ * @param resetKeys Reset all keys first
258
259
  * @returns Result
259
260
  */
260
- async initCall(callback) {
261
+ async initCall(callback, resetKeys) {
261
262
  var _a;
263
+ // Reset keys
264
+ if (resetKeys)
265
+ this.resetKeys();
262
266
  // Passphrase exists?
263
267
  if (this.passphrase) {
264
268
  if (callback)
@@ -277,11 +281,13 @@ export class CoreApp {
277
281
  };
278
282
  const result = await this.apiInitCall(data);
279
283
  if (result == null) {
284
+ // API error will popup
280
285
  if (callback)
281
286
  callback(false);
282
287
  return;
283
288
  }
284
289
  if (result.data == null) {
290
+ // Popup no data error
285
291
  this.notifier.alert(this.get('noData'));
286
292
  if (callback)
287
293
  callback(false);
@@ -305,9 +311,12 @@ export class CoreApp {
305
311
  this.storage.setData(this.fields.deviceId, undefined);
306
312
  return;
307
313
  }
308
- this.initCallUpdate(result.data, data.timestamp);
314
+ const updateResult = this.initCallUpdate(result.data, data.timestamp);
315
+ if (!updateResult) {
316
+ this.notifier.alert(this.get('noData') + '(Update)');
317
+ }
309
318
  if (callback)
310
- callback(true);
319
+ callback(updateResult);
311
320
  }
312
321
  /**
313
322
  * Init call update
@@ -317,12 +326,12 @@ export class CoreApp {
317
326
  initCallUpdate(data, timestamp) {
318
327
  // Data check
319
328
  if (data.deviceId == null || data.passphrase == null)
320
- return;
329
+ return false;
321
330
  // Decrypt
322
331
  // Should be done within 120 seconds after returning from the backend
323
332
  const passphrase = this.decrypt(data.passphrase, timestamp.toString());
324
333
  if (passphrase == null)
325
- return;
334
+ return false;
326
335
  // Update device id and cache it
327
336
  this._deviceId = data.deviceId;
328
337
  this.storage.setData(this.fields.deviceId, this._deviceId);
@@ -342,22 +351,31 @@ export class CoreApp {
342
351
  const currentValue = this.storage.getData(field);
343
352
  if (currentValue == null || currentValue === '')
344
353
  continue;
354
+ if (prev == null) {
355
+ // Reset the field
356
+ this.storage.setData(field, undefined);
357
+ continue;
358
+ }
345
359
  const enhanced = currentValue.indexOf('!') >= 8;
346
- let newValueSource = null;
360
+ let newValueSource;
347
361
  if (enhanced) {
348
362
  newValueSource = this.decryptEnhanced(currentValue, prev, 12);
349
363
  }
350
364
  else {
351
365
  newValueSource = this.decrypt(currentValue, prev);
352
366
  }
353
- if (newValueSource == null || newValueSource === '')
367
+ if (newValueSource == null || newValueSource === '') {
368
+ // Reset the field
369
+ this.storage.setData(field, undefined);
354
370
  continue;
371
+ }
355
372
  const newValue = enhanced
356
373
  ? this.encryptEnhanced(newValueSource)
357
374
  : this.encrypt(newValueSource);
358
375
  this.storage.setData(field, newValue);
359
376
  }
360
377
  }
378
+ return true;
361
379
  }
362
380
  /**
363
381
  * Init call encrypted fields update
@@ -485,19 +503,25 @@ export class CoreApp {
485
503
  const iterations = parseInt(messageEncrypted.substring(0, 2), 10);
486
504
  if (isNaN(iterations))
487
505
  return undefined;
488
- const salt = enc.Hex.parse(messageEncrypted.substring(2, 34));
489
- const iv = enc.Hex.parse(messageEncrypted.substring(34, 66));
490
- const encrypted = messageEncrypted.substring(66);
491
- const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
492
- keySize: 8,
493
- hasher: algo.SHA256,
494
- iterations: 1000 * iterations
495
- });
496
- return AES.decrypt(encrypted, key, {
497
- iv,
498
- padding: pad.Pkcs7,
499
- mode: mode.CBC
500
- }).toString(enc.Utf8);
506
+ try {
507
+ const salt = enc.Hex.parse(messageEncrypted.substring(2, 34));
508
+ const iv = enc.Hex.parse(messageEncrypted.substring(34, 66));
509
+ const encrypted = messageEncrypted.substring(66);
510
+ const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
511
+ keySize: 8,
512
+ hasher: algo.SHA256,
513
+ iterations: 1000 * iterations
514
+ });
515
+ return AES.decrypt(encrypted, key, {
516
+ iv,
517
+ padding: pad.Pkcs7,
518
+ mode: mode.CBC
519
+ }).toString(enc.Utf8);
520
+ }
521
+ catch (e) {
522
+ console.log('decrypt', e);
523
+ return undefined;
524
+ }
501
525
  }
502
526
  /**
503
527
  * Enhanced decrypt message
@@ -513,20 +537,26 @@ export class CoreApp {
513
537
  if (pos < 8 || messageEncrypted.length <= 66)
514
538
  return undefined;
515
539
  const timestamp = messageEncrypted.substring(0, pos);
516
- if (durationSeconds != null && durationSeconds > 0) {
517
- const milseconds = Utils.charsToNumber(timestamp);
518
- if (isNaN(milseconds) || milseconds < 1)
519
- return undefined;
520
- const timespan = new Date().substract(new Date(milseconds));
521
- if ((durationSeconds <= 12 &&
522
- timespan.totalMonths > durationSeconds) ||
523
- (durationSeconds > 12 &&
524
- timespan.totalSeconds > durationSeconds))
525
- return undefined;
540
+ try {
541
+ if (durationSeconds != null && durationSeconds > 0) {
542
+ const milseconds = Utils.charsToNumber(timestamp);
543
+ if (isNaN(milseconds) || milseconds < 1)
544
+ return undefined;
545
+ const timespan = new Date().substract(new Date(milseconds));
546
+ if ((durationSeconds <= 12 &&
547
+ timespan.totalMonths > durationSeconds) ||
548
+ (durationSeconds > 12 &&
549
+ timespan.totalSeconds > durationSeconds))
550
+ return undefined;
551
+ }
552
+ const message = messageEncrypted.substring(pos + 1);
553
+ passphrase = this.encryptionEnhance(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, timestamp);
554
+ return this.decrypt(message, passphrase);
555
+ }
556
+ catch (e) {
557
+ console.log('decryptEnhanced', e);
558
+ return undefined;
526
559
  }
527
- const message = messageEncrypted.substring(pos + 1);
528
- passphrase = this.encryptionEnhance(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, timestamp);
529
- return this.decrypt(message, passphrase);
530
560
  }
531
561
  /**
532
562
  * Detect IP data, call only one time
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bridge host
2
+ * Bridge host interface
3
3
  */
4
4
  export interface IBridgeHost {
5
5
  /**
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "Are you sure you want to permanently delete this {0}?",
33
33
  "description": "Description",
34
34
  "done": "Done",
35
+ "download": "Download",
35
36
  "edit": "Edit",
36
37
  "email": "Email",
37
38
  "emailAddresses": "Email addresses",
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "确定要永久删除此{0}吗?",
33
33
  "description": "描述",
34
34
  "done": "完成",
35
+ "download": "下载",
35
36
  "edit": "修改",
36
37
  "email": "电子邮箱",
37
38
  "emailAddresses": "电子邮箱",
@@ -50,7 +51,7 @@
50
51
  "message": "留言",
51
52
  "mobile": "手机号码",
52
53
  "mobilePhones": "手机号码",
53
- "monthLabel": "{0}個月",
54
+ "monthLabel": "{0}个月",
54
55
  "months": [
55
56
  "1月",
56
57
  "2月",
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "確定要永久刪除此{0}嗎?",
33
33
  "description": "描述",
34
34
  "done": "完成",
35
+ "download": "下載",
35
36
  "edit": "修改",
36
37
  "email": "電子郵箱",
37
38
  "emailAddresses": "電子郵箱",
@@ -50,7 +51,7 @@
50
51
  "message": "留言",
51
52
  "mobilePhone": "手機號碼",
52
53
  "mobilePhones": "手機號碼",
53
- "monthLabel": "{0}个月",
54
+ "monthLabel": "{0}個月",
54
55
  "months": [
55
56
  "1月",
56
57
  "2月",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.2.57",
3
+ "version": "1.2.60",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -52,26 +52,27 @@
52
52
  },
53
53
  "homepage": "https://github.com/ETSOO/AppScript#readme",
54
54
  "dependencies": {
55
- "@etsoo/notificationbase": "^1.1.2",
56
- "@etsoo/restclient": "^1.0.65",
57
- "@etsoo/shared": "^1.1.14",
55
+ "@etsoo/notificationbase": "^1.1.3",
56
+ "@etsoo/restclient": "^1.0.68",
57
+ "@etsoo/shared": "^1.1.38",
58
58
  "@types/crypto-js": "^4.1.1",
59
59
  "crypto-js": "^4.1.1"
60
60
  },
61
61
  "devDependencies": {
62
- "@babel/cli": "^7.17.6",
63
- "@babel/core": "^7.17.5",
64
- "@babel/plugin-transform-runtime": "^7.17.0",
65
- "@babel/preset-env": "^7.16.11",
66
- "@babel/runtime-corejs3": "^7.17.2",
67
- "@types/jest": "^27.4.1",
68
- "@typescript-eslint/eslint-plugin": "^5.12.1",
69
- "@typescript-eslint/parser": "^5.12.1",
70
- "eslint": "^8.10.0",
62
+ "@babel/cli": "^7.17.10",
63
+ "@babel/core": "^7.18.0",
64
+ "@babel/plugin-transform-runtime": "^7.18.0",
65
+ "@babel/preset-env": "^7.18.0",
66
+ "@babel/runtime-corejs3": "^7.18.0",
67
+ "@types/jest": "^27.5.1",
68
+ "@typescript-eslint/eslint-plugin": "^5.25.0",
69
+ "@typescript-eslint/parser": "^5.25.0",
70
+ "eslint": "^8.16.0",
71
71
  "eslint-config-airbnb-base": "^15.0.0",
72
- "eslint-plugin-import": "^2.25.4",
73
- "jest": "^27.5.1",
74
- "ts-jest": "^27.1.3",
75
- "typescript": "^4.5.5"
72
+ "eslint-plugin-import": "^2.26.0",
73
+ "jest": "^28.1.0",
74
+ "jest-environment-jsdom": "^28.1.0",
75
+ "ts-jest": "^28.0.2",
76
+ "typescript": "^4.6.4"
76
77
  }
77
78
  }
@@ -412,9 +412,13 @@ export interface ICoreApp<
412
412
  /**
413
413
  * Init call
414
414
  * @param callback Callback
415
+ * @param resetKeys Reset all keys first
415
416
  * @returns Result
416
417
  */
417
- initCall(callback?: (result: boolean) => void): Promise<void>;
418
+ initCall(
419
+ callback?: (result: boolean) => void,
420
+ resetKeys?: boolean
421
+ ): Promise<void>;
418
422
 
419
423
  /**
420
424
  * Callback where exit a page
@@ -765,11 +769,11 @@ export abstract class CoreApp<
765
769
  return false;
766
770
  }
767
771
 
768
- // this.name to identifier different app's secret
769
772
  const passphraseEncrypted = this.storage.getData<string>(
770
773
  this.fields.devicePassphrase
771
774
  );
772
775
  if (passphraseEncrypted) {
776
+ // this.name to identifier different app's secret
773
777
  const passphraseDecrypted = this.decrypt(
774
778
  passphraseEncrypted,
775
779
  this.name
@@ -861,9 +865,13 @@ export abstract class CoreApp<
861
865
  /**
862
866
  * Init call
863
867
  * @param callback Callback
868
+ * @param resetKeys Reset all keys first
864
869
  * @returns Result
865
870
  */
866
- async initCall(callback?: (result: boolean) => void) {
871
+ async initCall(callback?: (result: boolean) => void, resetKeys?: boolean) {
872
+ // Reset keys
873
+ if (resetKeys) this.resetKeys();
874
+
867
875
  // Passphrase exists?
868
876
  if (this.passphrase) {
869
877
  if (callback) callback(true);
@@ -887,11 +895,13 @@ export abstract class CoreApp<
887
895
 
888
896
  const result = await this.apiInitCall(data);
889
897
  if (result == null) {
898
+ // API error will popup
890
899
  if (callback) callback(false);
891
900
  return;
892
901
  }
893
902
 
894
903
  if (result.data == null) {
904
+ // Popup no data error
895
905
  this.notifier.alert(this.get<string>('noData')!);
896
906
  if (callback) callback(false);
897
907
  return;
@@ -922,9 +932,12 @@ export abstract class CoreApp<
922
932
  return;
923
933
  }
924
934
 
925
- this.initCallUpdate(result.data, data.timestamp);
935
+ const updateResult = this.initCallUpdate(result.data, data.timestamp);
936
+ if (!updateResult) {
937
+ this.notifier.alert(this.get<string>('noData')! + '(Update)');
938
+ }
926
939
 
927
- if (callback) callback(true);
940
+ if (callback) callback(updateResult);
928
941
  }
929
942
 
930
943
  /**
@@ -932,14 +945,17 @@ export abstract class CoreApp<
932
945
  * @param data Result data
933
946
  * @param timestamp Timestamp
934
947
  */
935
- protected initCallUpdate(data: InitCallResultData, timestamp: number) {
948
+ protected initCallUpdate(
949
+ data: InitCallResultData,
950
+ timestamp: number
951
+ ): boolean {
936
952
  // Data check
937
- if (data.deviceId == null || data.passphrase == null) return;
953
+ if (data.deviceId == null || data.passphrase == null) return false;
938
954
 
939
955
  // Decrypt
940
956
  // Should be done within 120 seconds after returning from the backend
941
957
  const passphrase = this.decrypt(data.passphrase, timestamp.toString());
942
- if (passphrase == null) return;
958
+ if (passphrase == null) return false;
943
959
 
944
960
  // Update device id and cache it
945
961
  this._deviceId = data.deviceId;
@@ -973,8 +989,14 @@ export abstract class CoreApp<
973
989
  const currentValue = this.storage.getData<string>(field);
974
990
  if (currentValue == null || currentValue === '') continue;
975
991
 
992
+ if (prev == null) {
993
+ // Reset the field
994
+ this.storage.setData(field, undefined);
995
+ continue;
996
+ }
997
+
976
998
  const enhanced = currentValue.indexOf('!') >= 8;
977
- let newValueSource = null;
999
+ let newValueSource: string | undefined;
978
1000
 
979
1001
  if (enhanced) {
980
1002
  newValueSource = this.decryptEnhanced(
@@ -986,7 +1008,11 @@ export abstract class CoreApp<
986
1008
  newValueSource = this.decrypt(currentValue, prev);
987
1009
  }
988
1010
 
989
- if (newValueSource == null || newValueSource === '') continue;
1011
+ if (newValueSource == null || newValueSource === '') {
1012
+ // Reset the field
1013
+ this.storage.setData(field, undefined);
1014
+ continue;
1015
+ }
990
1016
 
991
1017
  const newValue = enhanced
992
1018
  ? this.encryptEnhanced(newValueSource)
@@ -995,6 +1021,8 @@ export abstract class CoreApp<
995
1021
  this.storage.setData(field, newValue);
996
1022
  }
997
1023
  }
1024
+
1025
+ return true;
998
1026
  }
999
1027
 
1000
1028
  /**
@@ -1143,21 +1171,26 @@ export abstract class CoreApp<
1143
1171
  const iterations = parseInt(messageEncrypted.substring(0, 2), 10);
1144
1172
  if (isNaN(iterations)) return undefined;
1145
1173
 
1146
- const salt = enc.Hex.parse(messageEncrypted.substring(2, 34));
1147
- const iv = enc.Hex.parse(messageEncrypted.substring(34, 66));
1148
- const encrypted = messageEncrypted.substring(66);
1174
+ try {
1175
+ const salt = enc.Hex.parse(messageEncrypted.substring(2, 34));
1176
+ const iv = enc.Hex.parse(messageEncrypted.substring(34, 66));
1177
+ const encrypted = messageEncrypted.substring(66);
1149
1178
 
1150
- const key = PBKDF2(passphrase ?? this.passphrase, salt, {
1151
- keySize: 8, // 256 / 32
1152
- hasher: algo.SHA256,
1153
- iterations: 1000 * iterations
1154
- });
1179
+ const key = PBKDF2(passphrase ?? this.passphrase, salt, {
1180
+ keySize: 8, // 256 / 32
1181
+ hasher: algo.SHA256,
1182
+ iterations: 1000 * iterations
1183
+ });
1155
1184
 
1156
- return AES.decrypt(encrypted, key, {
1157
- iv,
1158
- padding: pad.Pkcs7,
1159
- mode: mode.CBC
1160
- }).toString(enc.Utf8);
1185
+ return AES.decrypt(encrypted, key, {
1186
+ iv,
1187
+ padding: pad.Pkcs7,
1188
+ mode: mode.CBC
1189
+ }).toString(enc.Utf8);
1190
+ } catch (e) {
1191
+ console.log('decrypt', e);
1192
+ return undefined;
1193
+ }
1161
1194
  }
1162
1195
 
1163
1196
  /**
@@ -1180,26 +1213,31 @@ export abstract class CoreApp<
1180
1213
 
1181
1214
  const timestamp = messageEncrypted.substring(0, pos);
1182
1215
 
1183
- if (durationSeconds != null && durationSeconds > 0) {
1184
- const milseconds = Utils.charsToNumber(timestamp);
1185
- if (isNaN(milseconds) || milseconds < 1) return undefined;
1186
- const timespan = new Date().substract(new Date(milseconds));
1187
- if (
1188
- (durationSeconds <= 12 &&
1189
- timespan.totalMonths > durationSeconds) ||
1190
- (durationSeconds > 12 &&
1191
- timespan.totalSeconds > durationSeconds)
1192
- )
1193
- return undefined;
1194
- }
1216
+ try {
1217
+ if (durationSeconds != null && durationSeconds > 0) {
1218
+ const milseconds = Utils.charsToNumber(timestamp);
1219
+ if (isNaN(milseconds) || milseconds < 1) return undefined;
1220
+ const timespan = new Date().substract(new Date(milseconds));
1221
+ if (
1222
+ (durationSeconds <= 12 &&
1223
+ timespan.totalMonths > durationSeconds) ||
1224
+ (durationSeconds > 12 &&
1225
+ timespan.totalSeconds > durationSeconds)
1226
+ )
1227
+ return undefined;
1228
+ }
1195
1229
 
1196
- const message = messageEncrypted.substring(pos + 1);
1197
- passphrase = this.encryptionEnhance(
1198
- passphrase ?? this.passphrase,
1199
- timestamp
1200
- );
1230
+ const message = messageEncrypted.substring(pos + 1);
1231
+ passphrase = this.encryptionEnhance(
1232
+ passphrase ?? this.passphrase,
1233
+ timestamp
1234
+ );
1201
1235
 
1202
- return this.decrypt(message, passphrase);
1236
+ return this.decrypt(message, passphrase);
1237
+ } catch (e) {
1238
+ console.log('decryptEnhanced', e);
1239
+ return undefined;
1240
+ }
1203
1241
  }
1204
1242
 
1205
1243
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bridge host
2
+ * Bridge host interface
3
3
  */
4
4
  export interface IBridgeHost {
5
5
  /**
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "Are you sure you want to permanently delete this {0}?",
33
33
  "description": "Description",
34
34
  "done": "Done",
35
+ "download": "Download",
35
36
  "edit": "Edit",
36
37
  "email": "Email",
37
38
  "emailAddresses": "Email addresses",
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "确定要永久删除此{0}吗?",
33
33
  "description": "描述",
34
34
  "done": "完成",
35
+ "download": "下载",
35
36
  "edit": "修改",
36
37
  "email": "电子邮箱",
37
38
  "emailAddresses": "电子邮箱",
@@ -50,7 +51,7 @@
50
51
  "message": "留言",
51
52
  "mobile": "手机号码",
52
53
  "mobilePhones": "手机号码",
53
- "monthLabel": "{0}個月",
54
+ "monthLabel": "{0}个月",
54
55
  "months": [
55
56
  "1月",
56
57
  "2月",
@@ -32,6 +32,7 @@
32
32
  "deleteConfirm": "確定要永久刪除此{0}嗎?",
33
33
  "description": "描述",
34
34
  "done": "完成",
35
+ "download": "下載",
35
36
  "edit": "修改",
36
37
  "email": "電子郵箱",
37
38
  "emailAddresses": "電子郵箱",
@@ -50,7 +51,7 @@
50
51
  "message": "留言",
51
52
  "mobilePhone": "手機號碼",
52
53
  "mobilePhones": "手機號碼",
53
- "monthLabel": "{0}个月",
54
+ "monthLabel": "{0}個月",
54
55
  "months": [
55
56
  "1月",
56
57
  "2月",