@etsoo/appscript 1.1.67 → 1.1.71

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.
@@ -3,6 +3,7 @@ import { ApiDataError, IApi, IPData } from '@etsoo/restclient';
3
3
  import { DataTypes, DateUtils } from '@etsoo/shared';
4
4
  import { AddressRegion } from '../address/AddressRegion';
5
5
  import { IActionResult } from '../result/IActionResult';
6
+ import { InitCallResultData } from '../result/InitCallResult';
6
7
  import { IUserData } from '../state/User';
7
8
  import { IAppSettings } from './AppSettings';
8
9
  import { UserRole } from './UserRole';
@@ -87,10 +88,6 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
87
88
  * User data
88
89
  */
89
90
  userData?: IUserData;
90
- /**
91
- * Passphrase for encryption
92
- */
93
- passphrase?: string;
94
91
  /**
95
92
  * Search input element
96
93
  */
@@ -123,7 +120,15 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
123
120
  * @param passphrase Secret passphrase
124
121
  * @returns Pure text
125
122
  */
126
- decrypt(messageEncrypted: string, passphrase: string): string;
123
+ decrypt(messageEncrypted: string, passphrase?: string): string | undefined;
124
+ /**
125
+ * Enhanced decrypt message
126
+ * @param messageEncrypted Encrypted message
127
+ * @param passphrase Secret passphrase
128
+ * @param durationSeconds Duration seconds, <= 12 will be considered as month
129
+ * @returns Pure text
130
+ */
131
+ decryptEnhanced(messageEncrypted: string, passphrase?: string, durationSeconds?: number): string | undefined;
127
132
  /**
128
133
  * Detect IP data, call only one time
129
134
  * @param callback Callback will be called when the IP is ready
@@ -136,7 +141,15 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
136
141
  * @param iterations Iterations, 1000 times, 1 - 99
137
142
  * @returns Result
138
143
  */
139
- encrypt(message: string, passphrase: string, iterations?: number): string;
144
+ encrypt(message: string, passphrase?: string, iterations?: number): string;
145
+ /**
146
+ * Enhanced encrypt message
147
+ * @param message Message
148
+ * @param passphrase Secret passphrase
149
+ * @param iterations Iterations, 1000 times, 1 - 99
150
+ * @returns Result
151
+ */
152
+ encryptEnhanced(message: string, passphrase?: string, iterations?: number): string;
140
153
  /**
141
154
  * Format date to string
142
155
  * @param input Input date
@@ -224,12 +237,25 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
224
237
  * @param passphrase Secret passphrase
225
238
  */
226
239
  hash(message: string, passphrase?: string): string;
240
+ /**
241
+ * Hash message Hex, SHA3 or HmacSHA512, 512 as Base64
242
+ * https://cryptojs.gitbook.io/docs/
243
+ * @param message Message
244
+ * @param passphrase Secret passphrase
245
+ */
246
+ hashHex(message: string, passphrase?: string): string;
227
247
  /**
228
248
  * Check use has the specific role permission or not
229
249
  * @param roles Roles to check
230
250
  * @returns Result
231
251
  */
232
252
  hasPermission(roles: number | UserRole | number[] | UserRole[]): boolean;
253
+ /**
254
+ * Init call
255
+ * @param callback Callback
256
+ * @returns Result
257
+ */
258
+ initCall(callback?: (result: boolean) => void): Promise<void>;
233
259
  /**
234
260
  * Callback where exit a page
235
261
  */
@@ -335,10 +361,6 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
335
361
  * User data
336
362
  */
337
363
  userData?: IUserData;
338
- /**
339
- * Passphrase for encryption
340
- */
341
- passphrase?: string;
342
364
  /**
343
365
  * Response token header field name
344
366
  */
@@ -363,6 +385,18 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
363
385
  * Token refresh count down seed
364
386
  */
365
387
  protected refreshCountdownSeed: number;
388
+ /**
389
+ * Device id field name
390
+ */
391
+ protected deviceIdField: string;
392
+ /**
393
+ * Device id
394
+ */
395
+ protected deviceId: string;
396
+ /**
397
+ * Passphrase for encryption
398
+ */
399
+ protected passphrase: string;
366
400
  /**
367
401
  * Protected constructor
368
402
  * @param settings Settings
@@ -372,6 +406,23 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
372
406
  */
373
407
  protected constructor(settings: S, api: IApi, notifier: INotifier<N, C>, name: string);
374
408
  protected setApi(api: IApi): void;
409
+ /**
410
+ * Init call
411
+ * @param callback Callback
412
+ * @returns Result
413
+ */
414
+ initCall(callback?: (result: boolean) => void): Promise<void>;
415
+ /**
416
+ * Init call update
417
+ * @param data Result data
418
+ * @param timestamp Timestamp
419
+ */
420
+ protected initCallUpdate(data: InitCallResultData, timestamp: number): void;
421
+ /**
422
+ * Init call update fields in local storage
423
+ * @returns Fields
424
+ */
425
+ protected initCallUpdateFields(): string[];
375
426
  /**
376
427
  * Alert action result
377
428
  * @param result Action result
@@ -400,7 +451,15 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
400
451
  * @param passphrase Secret passphrase
401
452
  * @returns Pure text
402
453
  */
403
- decrypt(messageEncrypted: string, passphrase: string): string;
454
+ decrypt(messageEncrypted: string, passphrase?: string): string | undefined;
455
+ /**
456
+ * Enhanced decrypt message
457
+ * @param messageEncrypted Encrypted message
458
+ * @param passphrase Secret passphrase
459
+ * @param durationSeconds Duration seconds, <= 12 will be considered as month
460
+ * @returns Pure text
461
+ */
462
+ decryptEnhanced(messageEncrypted: string, passphrase?: string, durationSeconds?: number): string | undefined;
404
463
  /**
405
464
  * Detect IP data, call only one time
406
465
  * @param callback Callback will be called when the IP is ready
@@ -414,7 +473,15 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
414
473
  * @param iterations Iterations, 1000 times, 1 - 99
415
474
  * @returns Result
416
475
  */
417
- encrypt(message: string, passphrase: string, iterations?: number): string;
476
+ encrypt(message: string, passphrase?: string, iterations?: number): string;
477
+ /**
478
+ * Enhanced encrypt message
479
+ * @param message Message
480
+ * @param passphrase Secret passphrase
481
+ * @param iterations Iterations, 1000 times, 1 - 99
482
+ * @returns Result
483
+ */
484
+ encryptEnhanced(message: string, passphrase?: string, iterations?: number): string;
418
485
  /**
419
486
  * Enchance secret passphrase
420
487
  * @param passphrase Secret passphrase
@@ -504,6 +571,13 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
504
571
  * @param passphrase Secret passphrase
505
572
  */
506
573
  hash(message: string, passphrase?: string): string;
574
+ /**
575
+ * Hash message Hex, SHA3 or HmacSHA512, 512 as Base64
576
+ * https://cryptojs.gitbook.io/docs/
577
+ * @param message Message
578
+ * @param passphrase Secret passphrase
579
+ */
580
+ hashHex(message: string, passphrase?: string): string;
507
581
  /**
508
582
  * Check use has the specific role permission or not
509
583
  * @param roles Roles to check
@@ -31,10 +31,19 @@ export class CoreApp {
31
31
  * Token refresh count down seed
32
32
  */
33
33
  this.refreshCountdownSeed = 0;
34
+ /**
35
+ * Device id field name
36
+ */
37
+ this.deviceIdField = 'SmartERPDeviceId';
38
+ /**
39
+ * Passphrase for encryption
40
+ */
41
+ this.passphrase = '***';
34
42
  this.settings = settings;
35
43
  this.api = api;
36
44
  this.notifier = notifier;
37
45
  this.name = name;
46
+ this.deviceId = StorageUtils.getLocalData(this.deviceIdField, '');
38
47
  this.setApi(api);
39
48
  const { currentCulture, currentRegion } = settings;
40
49
  this.changeCulture(currentCulture);
@@ -105,6 +114,91 @@ export class CoreApp {
105
114
  }
106
115
  };
107
116
  }
117
+ /**
118
+ * Init call
119
+ * @param callback Callback
120
+ * @returns Result
121
+ */
122
+ async initCall(callback) {
123
+ var _a;
124
+ const data = {
125
+ timestamp: new Date().getTime(),
126
+ deviceId: this.deviceId === '' ? undefined : this.deviceId
127
+ };
128
+ const result = await this.api.put('Auth/WebInitCall', data);
129
+ if (result == null) {
130
+ if (callback)
131
+ callback(false);
132
+ return;
133
+ }
134
+ if (result.data == null) {
135
+ this.notifier.alert(this.get('noData'));
136
+ if (callback)
137
+ callback(false);
138
+ return;
139
+ }
140
+ if (!result.ok) {
141
+ const seconds = result.data.seconds;
142
+ const validSeconds = result.data.validSeconds;
143
+ if (result.title === 'timeDifferenceInvalid' &&
144
+ seconds != null &&
145
+ validSeconds != null) {
146
+ const title = (_a = this.get('timeDifferenceInvalid')) === null || _a === void 0 ? void 0 : _a.format(seconds.toString(), validSeconds.toString());
147
+ this.notifier.alert(title);
148
+ }
149
+ else {
150
+ this.alertResult(result);
151
+ }
152
+ if (callback)
153
+ callback(false);
154
+ return;
155
+ }
156
+ this.initCallUpdate(result.data, data.timestamp);
157
+ if (callback)
158
+ callback(true);
159
+ }
160
+ /**
161
+ * Init call update
162
+ * @param data Result data
163
+ * @param timestamp Timestamp
164
+ */
165
+ initCallUpdate(data, timestamp) {
166
+ if (data.deviceId == null || data.passphrase == null)
167
+ return;
168
+ // Decrypt
169
+ // Should be done within 120 seconds after returning from the backend
170
+ const passphrase = this.decrypt(data.passphrase, timestamp.toString());
171
+ if (passphrase == null)
172
+ return;
173
+ // Update device id and cache it
174
+ this.deviceId = data.deviceId;
175
+ StorageUtils.setLocalData(this.deviceIdField, this.deviceId);
176
+ // Current passphrase
177
+ this.passphrase = passphrase;
178
+ // Previous passphrase
179
+ if (data.previousPassphrase) {
180
+ const prev = this.decrypt(data.previousPassphrase, timestamp.toString());
181
+ // Update
182
+ const fields = this.initCallUpdateFields();
183
+ for (const field of fields) {
184
+ const currentValue = StorageUtils.getLocalData(field, '');
185
+ if (currentValue === '' || currentValue.indexOf('+') === -1)
186
+ continue;
187
+ const newValueSource = this.decryptEnhanced(currentValue, prev, 12);
188
+ if (newValueSource == null)
189
+ continue;
190
+ const newValue = this.encryptEnhanced(newValueSource);
191
+ StorageUtils.setLocalData(field, newValue);
192
+ }
193
+ }
194
+ }
195
+ /**
196
+ * Init call update fields in local storage
197
+ * @returns Fields
198
+ */
199
+ initCallUpdateFields() {
200
+ return [];
201
+ }
108
202
  /**
109
203
  * Alert action result
110
204
  * @param result Action result
@@ -200,16 +294,14 @@ export class CoreApp {
200
294
  * @returns Pure text
201
295
  */
202
296
  decrypt(messageEncrypted, passphrase) {
203
- // Timestamp splitter
204
- const pos = messageEncrypted.indexOf('+');
205
- const timestamp = messageEncrypted.substring(0, pos);
206
- const message = messageEncrypted.substring(pos + 1);
207
297
  // Iterations
208
- const iterations = parseInt(message.substring(0, 2), 10);
209
- const salt = enc.Hex.parse(message.substring(2, 34));
210
- const iv = enc.Hex.parse(message.substring(34, 66));
211
- const encrypted = message.substring(66);
212
- const key = PBKDF2(this.encryptionEnhance(passphrase, timestamp), salt, {
298
+ const iterations = parseInt(messageEncrypted.substring(0, 2), 10);
299
+ if (isNaN(iterations))
300
+ return undefined;
301
+ const salt = enc.Hex.parse(messageEncrypted.substring(2, 34));
302
+ const iv = enc.Hex.parse(messageEncrypted.substring(34, 66));
303
+ const encrypted = messageEncrypted.substring(66);
304
+ const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
213
305
  keySize: 8,
214
306
  hasher: algo.SHA256,
215
307
  iterations: 1000 * iterations
@@ -220,6 +312,34 @@ export class CoreApp {
220
312
  mode: mode.CBC
221
313
  }).toString(enc.Utf8);
222
314
  }
315
+ /**
316
+ * Enhanced decrypt message
317
+ * @param messageEncrypted Encrypted message
318
+ * @param passphrase Secret passphrase
319
+ * @param durationSeconds Duration seconds, <= 12 will be considered as month
320
+ * @returns Pure text
321
+ */
322
+ decryptEnhanced(messageEncrypted, passphrase, durationSeconds) {
323
+ // Timestamp splitter
324
+ const pos = messageEncrypted.indexOf('+');
325
+ if (pos === -1 || messageEncrypted.length <= 66)
326
+ return undefined;
327
+ const timestamp = messageEncrypted.substring(0, pos);
328
+ if (durationSeconds != null && durationSeconds > 0) {
329
+ const milseconds = Utils.charsToNumber(timestamp);
330
+ if (isNaN(milseconds) || milseconds < 1)
331
+ return undefined;
332
+ const timespan = new Date().substract(new Date(milseconds));
333
+ if ((durationSeconds <= 12 &&
334
+ timespan.totalMonths > durationSeconds) ||
335
+ (durationSeconds > 12 &&
336
+ timespan.totalSeconds > durationSeconds))
337
+ return undefined;
338
+ }
339
+ const message = messageEncrypted.substring(pos + 1);
340
+ passphrase = this.encryptionEnhance(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, timestamp);
341
+ return this.decrypt(message, passphrase);
342
+ }
223
343
  /**
224
344
  * Detect IP data, call only one time
225
345
  * @param callback Callback will be called when the IP is ready
@@ -263,19 +383,15 @@ export class CoreApp {
263
383
  encrypt(message, passphrase, iterations) {
264
384
  // Default 1 * 1000
265
385
  iterations !== null && iterations !== void 0 ? iterations : (iterations = 1);
266
- // Timestamp
267
- const timestamp = Utils.numberToChars(new Date().getTime());
268
386
  const bits = 16; // 128 / 8
269
387
  const salt = lib.WordArray.random(bits);
270
- const key = PBKDF2(this.encryptionEnhance(passphrase, timestamp), salt, {
388
+ const key = PBKDF2(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, salt, {
271
389
  keySize: 8,
272
390
  hasher: algo.SHA256,
273
391
  iterations: 1000 * iterations
274
392
  });
275
393
  const iv = lib.WordArray.random(bits);
276
- return (timestamp +
277
- '+' +
278
- iterations.toString().padStart(2, '0') +
394
+ return (iterations.toString().padStart(2, '0') +
279
395
  salt.toString(enc.Hex) +
280
396
  iv.toString(enc.Hex) +
281
397
  AES.encrypt(message, key, {
@@ -285,6 +401,19 @@ export class CoreApp {
285
401
  }).toString() // enc.Base64
286
402
  );
287
403
  }
404
+ /**
405
+ * Enhanced encrypt message
406
+ * @param message Message
407
+ * @param passphrase Secret passphrase
408
+ * @param iterations Iterations, 1000 times, 1 - 99
409
+ * @returns Result
410
+ */
411
+ encryptEnhanced(message, passphrase, iterations) {
412
+ // Timestamp
413
+ const timestamp = Utils.numberToChars(new Date().getTime());
414
+ passphrase = this.encryptionEnhance(passphrase !== null && passphrase !== void 0 ? passphrase : this.passphrase, timestamp);
415
+ return timestamp + '+' + this.encrypt(message, passphrase, iterations);
416
+ }
288
417
  /**
289
418
  * Enchance secret passphrase
290
419
  * @param passphrase Secret passphrase
@@ -292,10 +421,9 @@ export class CoreApp {
292
421
  * @returns Enhanced passphrase
293
422
  */
294
423
  encryptionEnhance(passphrase, timestamp) {
295
- var _a;
296
424
  passphrase += timestamp;
297
425
  passphrase += passphrase.length.toString();
298
- return passphrase + ((_a = this.passphrase) !== null && _a !== void 0 ? _a : '');
426
+ return passphrase;
299
427
  }
300
428
  /**
301
429
  * Format date to string
@@ -357,7 +485,19 @@ export class CoreApp {
357
485
  * @param forceToLocal Force to local labels
358
486
  */
359
487
  formatResult(result, forceToLocal) {
360
- if ((result.title == null || forceToLocal) && result.type != null) {
488
+ const title = result.title;
489
+ if (title && /^\w+$/.test(title)) {
490
+ const key = title.formatInitial(false);
491
+ const localTitle = this.get(key);
492
+ if (localTitle) {
493
+ result.title = localTitle;
494
+ // Hold the original title in type when type is null
495
+ if (result.type == null)
496
+ result.type = title;
497
+ }
498
+ }
499
+ else if ((title == null || forceToLocal) && result.type != null) {
500
+ // Get label from type
361
501
  const key = result.type.formatInitial(false);
362
502
  result.title = this.get(key);
363
503
  }
@@ -435,6 +575,18 @@ export class CoreApp {
435
575
  else
436
576
  return HmacSHA512(message, passphrase).toString(enc.Base64);
437
577
  }
578
+ /**
579
+ * Hash message Hex, SHA3 or HmacSHA512, 512 as Base64
580
+ * https://cryptojs.gitbook.io/docs/
581
+ * @param message Message
582
+ * @param passphrase Secret passphrase
583
+ */
584
+ hashHex(message, passphrase) {
585
+ if (passphrase == null)
586
+ return SHA3(message, { outputLength: 512 }).toString(enc.Hex);
587
+ else
588
+ return HmacSHA512(message, passphrase).toString(enc.Hex);
589
+ }
438
590
  /**
439
591
  * Check use has the specific role permission or not
440
592
  * @param roles Roles to check
@@ -584,7 +736,6 @@ export class CoreApp {
584
736
  */
585
737
  userLogin(user, refreshToken, keep = false) {
586
738
  this.userData = user;
587
- this.passphrase = user.passphrase;
588
739
  this.authorize(user.token, refreshToken, keep);
589
740
  }
590
741
  /**
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Init call dto
3
+ */
4
+ export declare type InitCallDto = {
5
+ /**
6
+ * Device id
7
+ */
8
+ deviceId?: string;
9
+ /**
10
+ * Timestamp
11
+ */
12
+ timestamp: number;
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -16,6 +16,7 @@ export * from './def/ListItem';
16
16
  export * from './dto/IdDto';
17
17
  export * from './dto/IdLabelDto';
18
18
  export * from './dto/IdLabelPrimaryDto';
19
+ export * from './dto/InitCallDto';
19
20
  export * from './dto/UpdateDto';
20
21
  export * from './i18n/enUS';
21
22
  export * from './i18n/zhCN';
package/lib/mjs/index.js CHANGED
@@ -22,6 +22,7 @@ export * from './def/ListItem';
22
22
  export * from './dto/IdDto';
23
23
  export * from './dto/IdLabelDto';
24
24
  export * from './dto/IdLabelPrimaryDto';
25
+ export * from './dto/InitCallDto';
25
26
  export * from './dto/UpdateDto';
26
27
  // i18n
27
28
  export * from './i18n/enUS';
@@ -48,11 +48,11 @@ export interface IActionResult<D extends IResultData = IResultData> {
48
48
  /**
49
49
  * Trace id
50
50
  */
51
- readonly traceId?: string;
51
+ traceId?: string;
52
52
  /**
53
53
  * Type
54
54
  */
55
- readonly type: string;
55
+ type: string;
56
56
  /**
57
57
  * Success or not
58
58
  */
@@ -1,20 +1,28 @@
1
1
  import { IActionResult, IResultData } from './IActionResult';
2
2
  /**
3
- * Result data with id, follow this style to extend for specific model
3
+ * Init call result data
4
4
  */
5
5
  export interface InitCallResultData extends IResultData {
6
+ /**
7
+ * Device id
8
+ */
9
+ deviceId?: string;
6
10
  /**
7
11
  * Secret passphrase
8
12
  */
9
- passphrase: string;
13
+ passphrase?: string;
14
+ /**
15
+ * Previous secret passphrase
16
+ */
17
+ previousPassphrase?: string;
10
18
  /**
11
19
  * Actual seconds gap
12
20
  */
13
- seconds: number;
21
+ seconds?: number;
14
22
  /**
15
23
  * Valid seconds gap
16
24
  */
17
- validSeconds: number;
25
+ validSeconds?: number;
18
26
  }
19
27
  /**
20
28
  * Init call result
@@ -27,10 +27,6 @@ export interface IUserData {
27
27
  * Access token
28
28
  */
29
29
  readonly token: string;
30
- /**
31
- * Secret passphrase
32
- */
33
- readonly passphrase: string;
34
30
  }
35
31
  /**
36
32
  * User interface
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.1.67",
3
+ "version": "1.1.71",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -70,8 +70,8 @@
70
70
  "eslint": "^8.4.1",
71
71
  "eslint-config-airbnb-base": "^15.0.0",
72
72
  "eslint-plugin-import": "^2.25.3",
73
- "jest": "^27.4.3",
74
- "ts-jest": "^27.1.0",
75
- "typescript": "^4.5.2"
73
+ "jest": "^27.4.4",
74
+ "ts-jest": "^27.1.1",
75
+ "typescript": "^4.5.3"
76
76
  }
77
77
  }