@etsoo/appscript 1.1.85 → 1.1.86

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.
@@ -136,7 +136,6 @@ test('Tests for encryptEnhanced / decryptEnhanced', () => {
136
136
 
137
137
  // Act
138
138
  const encrypted = app.encryptEnhanced(input, passphrase);
139
- console.log(encrypted);
140
139
  const plain = app.decryptEnhanced(encrypted, passphrase);
141
140
  expect(plain).toEqual(input);
142
141
  });
@@ -222,7 +222,7 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
222
222
  * Get cached token
223
223
  * @returns Cached token
224
224
  */
225
- getCacheToken(): string | null;
225
+ getCacheToken(): string | undefined;
226
226
  /**
227
227
  * Get all regions
228
228
  * @returns Regions
@@ -375,18 +375,26 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
375
375
  * Label delegate
376
376
  */
377
377
  get labelDelegate(): <T = string>(key: string) => T | undefined;
378
+ private _ipData?;
378
379
  /**
379
380
  * IP data
380
381
  */
381
- ipData?: IPData;
382
+ get ipData(): IPData | undefined;
383
+ protected set ipData(value: IPData | undefined);
384
+ private _userData?;
382
385
  /**
383
386
  * User data
384
387
  */
385
- userData?: IUserData;
388
+ get userData(): IUserData | undefined;
389
+ protected set userData(value: IUserData | undefined);
386
390
  /**
387
391
  * Response token header field name
388
392
  */
389
- headerTokenField: string;
393
+ readonly headerTokenField = "SmartERPRefreshToken";
394
+ /**
395
+ * Serverside device id encrypted field name
396
+ */
397
+ protected readonly serversideDeviceIdField = "SmartERPServersideDeviceId";
390
398
  private ipDetectCallbacks?;
391
399
  /**
392
400
  * Search input element
@@ -410,7 +418,11 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
410
418
  /**
411
419
  * Device id field name
412
420
  */
413
- protected deviceIdField: string;
421
+ protected readonly deviceIdField: string;
422
+ /**
423
+ * Device id update time field name
424
+ */
425
+ protected readonly deviceIdUpdateTimeField: string;
414
426
  /**
415
427
  * Init call Api URL
416
428
  */
@@ -435,6 +447,11 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
435
447
  * @returns Result
436
448
  */
437
449
  protected apiInitCall(data: InitCallDto): Promise<InitCallResult | undefined>;
450
+ /**
451
+ * Get device last updte miliseconds
452
+ * @returns Miliseconds
453
+ */
454
+ protected getDeviceUpdateTime(): number;
438
455
  /**
439
456
  * Init call
440
457
  * @param callback Callback
@@ -579,7 +596,7 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
579
596
  * Get cached token
580
597
  * @returns Cached token
581
598
  */
582
- getCacheToken(): string | null;
599
+ getCacheToken(): string | undefined;
583
600
  /**
584
601
  * Get all regions
585
602
  * @returns Regions
@@ -20,11 +20,15 @@ class CoreApp {
20
20
  * @param name Application name
21
21
  */
22
22
  constructor(settings, api, notifier, name) {
23
- this._deviceId = '***';
23
+ this._deviceId = '';
24
24
  /**
25
25
  * Response token header field name
26
26
  */
27
27
  this.headerTokenField = 'SmartERPRefreshToken';
28
+ /**
29
+ * Serverside device id encrypted field name
30
+ */
31
+ this.serversideDeviceIdField = 'SmartERPServersideDeviceId';
28
32
  this._authorized = false;
29
33
  this._isTryingLogin = false;
30
34
  /**
@@ -39,6 +43,10 @@ class CoreApp {
39
43
  * Device id field name
40
44
  */
41
45
  this.deviceIdField = 'SmartERPDeviceId';
46
+ /**
47
+ * Device id update time field name
48
+ */
49
+ this.deviceIdUpdateTimeField = 'SmartERPDeviceIdUpdateTime';
42
50
  /**
43
51
  * Init call Api URL
44
52
  */
@@ -92,6 +100,24 @@ class CoreApp {
92
100
  get labelDelegate() {
93
101
  return this.get.bind(this);
94
102
  }
103
+ /**
104
+ * IP data
105
+ */
106
+ get ipData() {
107
+ return this._ipData;
108
+ }
109
+ set ipData(value) {
110
+ this._ipData = value;
111
+ }
112
+ /**
113
+ * User data
114
+ */
115
+ get userData() {
116
+ return this._userData;
117
+ }
118
+ set userData(value) {
119
+ this._userData = value;
120
+ }
95
121
  /**
96
122
  * Is current authorized
97
123
  */
@@ -139,6 +165,13 @@ class CoreApp {
139
165
  async apiInitCall(data) {
140
166
  return await this.api.put(this.initCallApi, data);
141
167
  }
168
+ /**
169
+ * Get device last updte miliseconds
170
+ * @returns Miliseconds
171
+ */
172
+ getDeviceUpdateTime() {
173
+ return shared_1.StorageUtils.getLocalData(this.deviceIdUpdateTimeField, 0);
174
+ }
142
175
  /**
143
176
  * Init call
144
177
  * @param callback Callback
@@ -146,9 +179,24 @@ class CoreApp {
146
179
  */
147
180
  async initCall(callback) {
148
181
  var _a;
182
+ // Device
183
+ let hasDeviceId = this.deviceId != null && this.deviceId !== '';
184
+ const timestamp = new Date().getTime();
185
+ const lastTimestamp = this.getDeviceUpdateTime();
186
+ if (hasDeviceId &&
187
+ lastTimestamp > 0 &&
188
+ timestamp - lastTimestamp <= 1296000000) {
189
+ // When deviceId is there and less than 15 days = 1000 * 60 * 60 * 24 * 15
190
+ if (callback)
191
+ callback(true);
192
+ return;
193
+ }
194
+ // Serverside encrypted device id
195
+ const identifier = shared_1.StorageUtils.getLocalData(this.serversideDeviceIdField);
149
196
  const data = {
150
- timestamp: new Date().getTime(),
151
- deviceId: this.deviceId === '' ? undefined : this.deviceId
197
+ timestamp,
198
+ identifier,
199
+ deviceId: hasDeviceId ? this.deviceId : undefined
152
200
  };
153
201
  const result = await this.apiInitCall(data);
154
202
  if (result == null) {
@@ -200,6 +248,7 @@ class CoreApp {
200
248
  // Update device id and cache it
201
249
  this.deviceId = data.deviceId;
202
250
  shared_1.StorageUtils.setLocalData(this.deviceIdField, this.deviceId);
251
+ shared_1.StorageUtils.setLocalData(this.deviceIdUpdateTimeField, timestamp);
203
252
  // Current passphrase
204
253
  this.passphrase = passphrase;
205
254
  // Previous passphrase
@@ -578,10 +627,7 @@ class CoreApp {
578
627
  // Temp refresh token
579
628
  if (this.cachedRefreshToken)
580
629
  return this.cachedRefreshToken;
581
- const refreshToken = shared_1.StorageUtils.getLocalData(this.headerTokenField, '');
582
- if (refreshToken === '')
583
- return null;
584
- return refreshToken;
630
+ return shared_1.StorageUtils.getLocalData(this.headerTokenField);
585
631
  }
586
632
  /**
587
633
  * Get all regions
@@ -793,6 +839,8 @@ class CoreApp {
793
839
  */
794
840
  userLogin(user, refreshToken, keep) {
795
841
  this.userData = user;
842
+ // Cache the encrypted serverside device id
843
+ shared_1.StorageUtils.setLocalData(this.serversideDeviceIdField, user.deviceId);
796
844
  if (keep) {
797
845
  this.authorize(user.token, refreshToken);
798
846
  }
@@ -6,6 +6,10 @@ export declare type InitCallDto = {
6
6
  * Device id
7
7
  */
8
8
  deviceId?: string;
9
+ /**
10
+ * Serverside identifier
11
+ */
12
+ identifier?: string;
9
13
  /**
10
14
  * Timestamp
11
15
  */
@@ -3,6 +3,10 @@ import { IState } from './State';
3
3
  * User data interface
4
4
  */
5
5
  export interface IUserData {
6
+ /**
7
+ * Serverside device id encrypted
8
+ */
9
+ readonly deviceId: string;
6
10
  /**
7
11
  * User name
8
12
  */
@@ -222,7 +222,7 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
222
222
  * Get cached token
223
223
  * @returns Cached token
224
224
  */
225
- getCacheToken(): string | null;
225
+ getCacheToken(): string | undefined;
226
226
  /**
227
227
  * Get all regions
228
228
  * @returns Regions
@@ -375,18 +375,26 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
375
375
  * Label delegate
376
376
  */
377
377
  get labelDelegate(): <T = string>(key: string) => T | undefined;
378
+ private _ipData?;
378
379
  /**
379
380
  * IP data
380
381
  */
381
- ipData?: IPData;
382
+ get ipData(): IPData | undefined;
383
+ protected set ipData(value: IPData | undefined);
384
+ private _userData?;
382
385
  /**
383
386
  * User data
384
387
  */
385
- userData?: IUserData;
388
+ get userData(): IUserData | undefined;
389
+ protected set userData(value: IUserData | undefined);
386
390
  /**
387
391
  * Response token header field name
388
392
  */
389
- headerTokenField: string;
393
+ readonly headerTokenField = "SmartERPRefreshToken";
394
+ /**
395
+ * Serverside device id encrypted field name
396
+ */
397
+ protected readonly serversideDeviceIdField = "SmartERPServersideDeviceId";
390
398
  private ipDetectCallbacks?;
391
399
  /**
392
400
  * Search input element
@@ -410,7 +418,11 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
410
418
  /**
411
419
  * Device id field name
412
420
  */
413
- protected deviceIdField: string;
421
+ protected readonly deviceIdField: string;
422
+ /**
423
+ * Device id update time field name
424
+ */
425
+ protected readonly deviceIdUpdateTimeField: string;
414
426
  /**
415
427
  * Init call Api URL
416
428
  */
@@ -435,6 +447,11 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
435
447
  * @returns Result
436
448
  */
437
449
  protected apiInitCall(data: InitCallDto): Promise<InitCallResult | undefined>;
450
+ /**
451
+ * Get device last updte miliseconds
452
+ * @returns Miliseconds
453
+ */
454
+ protected getDeviceUpdateTime(): number;
438
455
  /**
439
456
  * Init call
440
457
  * @param callback Callback
@@ -579,7 +596,7 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
579
596
  * Get cached token
580
597
  * @returns Cached token
581
598
  */
582
- getCacheToken(): string | null;
599
+ getCacheToken(): string | undefined;
583
600
  /**
584
601
  * Get all regions
585
602
  * @returns Regions
@@ -17,11 +17,15 @@ export class CoreApp {
17
17
  * @param name Application name
18
18
  */
19
19
  constructor(settings, api, notifier, name) {
20
- this._deviceId = '***';
20
+ this._deviceId = '';
21
21
  /**
22
22
  * Response token header field name
23
23
  */
24
24
  this.headerTokenField = 'SmartERPRefreshToken';
25
+ /**
26
+ * Serverside device id encrypted field name
27
+ */
28
+ this.serversideDeviceIdField = 'SmartERPServersideDeviceId';
25
29
  this._authorized = false;
26
30
  this._isTryingLogin = false;
27
31
  /**
@@ -36,6 +40,10 @@ export class CoreApp {
36
40
  * Device id field name
37
41
  */
38
42
  this.deviceIdField = 'SmartERPDeviceId';
43
+ /**
44
+ * Device id update time field name
45
+ */
46
+ this.deviceIdUpdateTimeField = 'SmartERPDeviceIdUpdateTime';
39
47
  /**
40
48
  * Init call Api URL
41
49
  */
@@ -89,6 +97,24 @@ export class CoreApp {
89
97
  get labelDelegate() {
90
98
  return this.get.bind(this);
91
99
  }
100
+ /**
101
+ * IP data
102
+ */
103
+ get ipData() {
104
+ return this._ipData;
105
+ }
106
+ set ipData(value) {
107
+ this._ipData = value;
108
+ }
109
+ /**
110
+ * User data
111
+ */
112
+ get userData() {
113
+ return this._userData;
114
+ }
115
+ set userData(value) {
116
+ this._userData = value;
117
+ }
92
118
  /**
93
119
  * Is current authorized
94
120
  */
@@ -136,6 +162,13 @@ export class CoreApp {
136
162
  async apiInitCall(data) {
137
163
  return await this.api.put(this.initCallApi, data);
138
164
  }
165
+ /**
166
+ * Get device last updte miliseconds
167
+ * @returns Miliseconds
168
+ */
169
+ getDeviceUpdateTime() {
170
+ return StorageUtils.getLocalData(this.deviceIdUpdateTimeField, 0);
171
+ }
139
172
  /**
140
173
  * Init call
141
174
  * @param callback Callback
@@ -143,9 +176,24 @@ export class CoreApp {
143
176
  */
144
177
  async initCall(callback) {
145
178
  var _a;
179
+ // Device
180
+ let hasDeviceId = this.deviceId != null && this.deviceId !== '';
181
+ const timestamp = new Date().getTime();
182
+ const lastTimestamp = this.getDeviceUpdateTime();
183
+ if (hasDeviceId &&
184
+ lastTimestamp > 0 &&
185
+ timestamp - lastTimestamp <= 1296000000) {
186
+ // When deviceId is there and less than 15 days = 1000 * 60 * 60 * 24 * 15
187
+ if (callback)
188
+ callback(true);
189
+ return;
190
+ }
191
+ // Serverside encrypted device id
192
+ const identifier = StorageUtils.getLocalData(this.serversideDeviceIdField);
146
193
  const data = {
147
- timestamp: new Date().getTime(),
148
- deviceId: this.deviceId === '' ? undefined : this.deviceId
194
+ timestamp,
195
+ identifier,
196
+ deviceId: hasDeviceId ? this.deviceId : undefined
149
197
  };
150
198
  const result = await this.apiInitCall(data);
151
199
  if (result == null) {
@@ -197,6 +245,7 @@ export class CoreApp {
197
245
  // Update device id and cache it
198
246
  this.deviceId = data.deviceId;
199
247
  StorageUtils.setLocalData(this.deviceIdField, this.deviceId);
248
+ StorageUtils.setLocalData(this.deviceIdUpdateTimeField, timestamp);
200
249
  // Current passphrase
201
250
  this.passphrase = passphrase;
202
251
  // Previous passphrase
@@ -575,10 +624,7 @@ export class CoreApp {
575
624
  // Temp refresh token
576
625
  if (this.cachedRefreshToken)
577
626
  return this.cachedRefreshToken;
578
- const refreshToken = StorageUtils.getLocalData(this.headerTokenField, '');
579
- if (refreshToken === '')
580
- return null;
581
- return refreshToken;
627
+ return StorageUtils.getLocalData(this.headerTokenField);
582
628
  }
583
629
  /**
584
630
  * Get all regions
@@ -790,6 +836,8 @@ export class CoreApp {
790
836
  */
791
837
  userLogin(user, refreshToken, keep) {
792
838
  this.userData = user;
839
+ // Cache the encrypted serverside device id
840
+ StorageUtils.setLocalData(this.serversideDeviceIdField, user.deviceId);
793
841
  if (keep) {
794
842
  this.authorize(user.token, refreshToken);
795
843
  }
@@ -6,6 +6,10 @@ export declare type InitCallDto = {
6
6
  * Device id
7
7
  */
8
8
  deviceId?: string;
9
+ /**
10
+ * Serverside identifier
11
+ */
12
+ identifier?: string;
9
13
  /**
10
14
  * Timestamp
11
15
  */
@@ -3,6 +3,10 @@ import { IState } from './State';
3
3
  * User data interface
4
4
  */
5
5
  export interface IUserData {
6
+ /**
7
+ * Serverside device id encrypted
8
+ */
9
+ readonly deviceId: string;
6
10
  /**
7
11
  * User name
8
12
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.1.85",
3
+ "version": "1.1.86",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -52,9 +52,9 @@
52
52
  },
53
53
  "homepage": "https://github.com/ETSOO/AppScript#readme",
54
54
  "dependencies": {
55
- "@etsoo/notificationbase": "^1.0.94",
56
- "@etsoo/restclient": "^1.0.62",
57
- "@etsoo/shared": "^1.0.78",
55
+ "@etsoo/notificationbase": "^1.0.95",
56
+ "@etsoo/restclient": "^1.0.63",
57
+ "@etsoo/shared": "^1.0.82",
58
58
  "@types/crypto-js": "^4.0.2",
59
59
  "crypto-js": "^4.1.1"
60
60
  },
@@ -65,8 +65,8 @@
65
65
  "@babel/preset-env": "^7.16.5",
66
66
  "@babel/runtime-corejs3": "^7.16.5",
67
67
  "@types/jest": "^27.0.3",
68
- "@typescript-eslint/eslint-plugin": "^5.7.0",
69
- "@typescript-eslint/parser": "^5.7.0",
68
+ "@typescript-eslint/eslint-plugin": "^5.8.0",
69
+ "@typescript-eslint/parser": "^5.8.0",
70
70
  "eslint": "^8.5.0",
71
71
  "eslint-config-airbnb-base": "^15.0.0",
72
72
  "eslint-plugin-import": "^2.25.3",
@@ -313,7 +313,7 @@ export interface ICoreApp<
313
313
  * Get cached token
314
314
  * @returns Cached token
315
315
  */
316
- getCacheToken(): string | null;
316
+ getCacheToken(): string | undefined;
317
317
 
318
318
  /**
319
319
  * Get all regions
@@ -499,7 +499,7 @@ export abstract class CoreApp<
499
499
  return this._region;
500
500
  }
501
501
 
502
- private _deviceId: string = '***';
502
+ private _deviceId: string = '';
503
503
  /**
504
504
  * Country or region, like CN
505
505
  */
@@ -517,20 +517,37 @@ export abstract class CoreApp<
517
517
  return this.get.bind(this);
518
518
  }
519
519
 
520
+ private _ipData?: IPData;
520
521
  /**
521
522
  * IP data
522
523
  */
523
- ipData?: IPData;
524
+ get ipData() {
525
+ return this._ipData;
526
+ }
527
+ protected set ipData(value: IPData | undefined) {
528
+ this._ipData = value;
529
+ }
524
530
 
531
+ private _userData?: IUserData;
525
532
  /**
526
533
  * User data
527
534
  */
528
- userData?: IUserData;
535
+ get userData() {
536
+ return this._userData;
537
+ }
538
+ protected set userData(value: IUserData | undefined) {
539
+ this._userData = value;
540
+ }
529
541
 
530
542
  /**
531
543
  * Response token header field name
532
544
  */
533
- headerTokenField = 'SmartERPRefreshToken';
545
+ readonly headerTokenField = 'SmartERPRefreshToken';
546
+
547
+ /**
548
+ * Serverside device id encrypted field name
549
+ */
550
+ protected readonly serversideDeviceIdField = 'SmartERPServersideDeviceId';
534
551
 
535
552
  // IP detect ready callbacks
536
553
  private ipDetectCallbacks?: IDetectIPCallback[];
@@ -567,7 +584,13 @@ export abstract class CoreApp<
567
584
  /**
568
585
  * Device id field name
569
586
  */
570
- protected deviceIdField: string = 'SmartERPDeviceId';
587
+ protected readonly deviceIdField: string = 'SmartERPDeviceId';
588
+
589
+ /**
590
+ * Device id update time field name
591
+ */
592
+ protected readonly deviceIdUpdateTimeField: string =
593
+ 'SmartERPDeviceIdUpdateTime';
571
594
 
572
595
  /**
573
596
  * Init call Api URL
@@ -657,15 +680,43 @@ export abstract class CoreApp<
657
680
  return await this.api.put<InitCallResult>(this.initCallApi, data);
658
681
  }
659
682
 
683
+ /**
684
+ * Get device last updte miliseconds
685
+ * @returns Miliseconds
686
+ */
687
+ protected getDeviceUpdateTime() {
688
+ return StorageUtils.getLocalData(this.deviceIdUpdateTimeField, 0);
689
+ }
690
+
660
691
  /**
661
692
  * Init call
662
693
  * @param callback Callback
663
694
  * @returns Result
664
695
  */
665
696
  async initCall(callback?: (result: boolean) => void) {
697
+ // Device
698
+ let hasDeviceId = this.deviceId != null && this.deviceId !== '';
699
+ const timestamp = new Date().getTime();
700
+ const lastTimestamp = this.getDeviceUpdateTime();
701
+ if (
702
+ hasDeviceId &&
703
+ lastTimestamp > 0 &&
704
+ timestamp - lastTimestamp <= 1296000000
705
+ ) {
706
+ // When deviceId is there and less than 15 days = 1000 * 60 * 60 * 24 * 15
707
+ if (callback) callback(true);
708
+ return;
709
+ }
710
+
711
+ // Serverside encrypted device id
712
+ const identifier = StorageUtils.getLocalData<string>(
713
+ this.serversideDeviceIdField
714
+ );
715
+
666
716
  const data: InitCallDto = {
667
- timestamp: new Date().getTime(),
668
- deviceId: this.deviceId === '' ? undefined : this.deviceId
717
+ timestamp,
718
+ identifier,
719
+ deviceId: hasDeviceId ? this.deviceId : undefined
669
720
  };
670
721
  const result = await this.apiInitCall(data);
671
722
  if (result == null) {
@@ -725,6 +776,7 @@ export abstract class CoreApp<
725
776
  // Update device id and cache it
726
777
  this.deviceId = data.deviceId;
727
778
  StorageUtils.setLocalData(this.deviceIdField, this.deviceId);
779
+ StorageUtils.setLocalData(this.deviceIdUpdateTimeField, timestamp);
728
780
 
729
781
  // Current passphrase
730
782
  this.passphrase = passphrase;
@@ -1192,18 +1244,10 @@ export abstract class CoreApp<
1192
1244
  * Get cached token
1193
1245
  * @returns Cached token
1194
1246
  */
1195
- getCacheToken(): string | null {
1247
+ getCacheToken(): string | undefined {
1196
1248
  // Temp refresh token
1197
1249
  if (this.cachedRefreshToken) return this.cachedRefreshToken;
1198
-
1199
- const refreshToken = StorageUtils.getLocalData<string>(
1200
- this.headerTokenField,
1201
- ''
1202
- );
1203
-
1204
- if (refreshToken === '') return null;
1205
-
1206
- return refreshToken;
1250
+ return StorageUtils.getLocalData<string>(this.headerTokenField);
1207
1251
  }
1208
1252
 
1209
1253
  /**
@@ -1442,6 +1486,9 @@ export abstract class CoreApp<
1442
1486
  userLogin(user: IUserData, refreshToken: string, keep?: boolean) {
1443
1487
  this.userData = user;
1444
1488
 
1489
+ // Cache the encrypted serverside device id
1490
+ StorageUtils.setLocalData(this.serversideDeviceIdField, user.deviceId);
1491
+
1445
1492
  if (keep) {
1446
1493
  this.authorize(user.token, refreshToken);
1447
1494
  } else {
@@ -7,6 +7,11 @@ export type InitCallDto = {
7
7
  */
8
8
  deviceId?: string;
9
9
 
10
+ /**
11
+ * Serverside identifier
12
+ */
13
+ identifier?: string;
14
+
10
15
  /**
11
16
  * Timestamp
12
17
  */
package/src/state/User.ts CHANGED
@@ -4,6 +4,11 @@ import { IState } from './State';
4
4
  * User data interface
5
5
  */
6
6
  export interface IUserData {
7
+ /**
8
+ * Serverside device id encrypted
9
+ */
10
+ readonly deviceId: string;
11
+
7
12
  /**
8
13
  * User name
9
14
  */