@etsoo/appscript 1.5.19 → 1.5.21
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/__tests__/app/CoreApp.ts +17 -0
- package/lib/cjs/app/CoreApp.d.ts +60 -14
- package/lib/cjs/app/CoreApp.js +212 -54
- package/lib/cjs/app/ExternalSettings.d.ts +15 -14
- package/lib/cjs/app/ExternalSettings.js +3 -0
- package/lib/cjs/app/IApp.d.ts +35 -2
- package/lib/cjs/erp/AuthApi.js +1 -1
- package/lib/cjs/erp/dto/ApiRefreshTokenDto.d.ts +21 -0
- package/lib/cjs/erp/dto/ApiRefreshTokenDto.js +2 -0
- package/lib/mjs/app/CoreApp.d.ts +60 -14
- package/lib/mjs/app/CoreApp.js +214 -56
- package/lib/mjs/app/ExternalSettings.d.ts +15 -14
- package/lib/mjs/app/ExternalSettings.js +3 -0
- package/lib/mjs/app/IApp.d.ts +35 -2
- package/lib/mjs/erp/AuthApi.js +1 -1
- package/lib/mjs/erp/dto/ApiRefreshTokenDto.d.ts +21 -0
- package/lib/mjs/erp/dto/ApiRefreshTokenDto.js +1 -0
- package/package.json +9 -9
- package/src/app/CoreApp.ts +304 -65
- package/src/app/ExternalSettings.ts +21 -16
- package/src/app/IApp.ts +47 -2
- package/src/erp/AuthApi.ts +1 -1
- package/src/erp/dto/ApiRefreshTokenDto.ts +24 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API refresh token data
|
|
3
|
+
*/
|
|
4
|
+
export type ApiRefreshTokenDto = {
|
|
5
|
+
/**
|
|
6
|
+
* Refresh token
|
|
7
|
+
*/
|
|
8
|
+
readonly refreshToken: string;
|
|
9
|
+
/**
|
|
10
|
+
* Access token
|
|
11
|
+
*/
|
|
12
|
+
readonly accessToken: string;
|
|
13
|
+
/**
|
|
14
|
+
* Token type
|
|
15
|
+
*/
|
|
16
|
+
readonly tokenType: string;
|
|
17
|
+
/**
|
|
18
|
+
* Expires in
|
|
19
|
+
*/
|
|
20
|
+
readonly expiresIn: number;
|
|
21
|
+
};
|
package/lib/mjs/app/CoreApp.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ import { IUser } from '../state/User';
|
|
|
9
9
|
import { IAppSettings } from './AppSettings';
|
|
10
10
|
import { FormatResultCustomCallback, IApp, IAppFields, IDetectIPCallback, NavigateOptions, RefreshTokenProps, RefreshTokenResult } from './IApp';
|
|
11
11
|
import { UserRole } from './UserRole';
|
|
12
|
+
import { ExternalEndpoint } from './ExternalSettings';
|
|
13
|
+
import { ApiRefreshTokenDto } from '../erp/dto/ApiRefreshTokenDto';
|
|
14
|
+
type ApiRefreshTokenFunction = (api: IApi, token: string) => Promise<[string, number] | undefined>;
|
|
15
|
+
type ApiTaskData = [IApi, number, number, ApiRefreshTokenFunction, string?];
|
|
12
16
|
/**
|
|
13
17
|
* Core application interface
|
|
14
18
|
*/
|
|
@@ -124,10 +128,6 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
124
128
|
* Last called with token refresh
|
|
125
129
|
*/
|
|
126
130
|
protected lastCalled: boolean;
|
|
127
|
-
/**
|
|
128
|
-
* Token refresh count down seed
|
|
129
|
-
*/
|
|
130
|
-
protected refreshCountdownSeed: number;
|
|
131
131
|
/**
|
|
132
132
|
* Init call Api URL
|
|
133
133
|
*/
|
|
@@ -137,6 +137,8 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
137
137
|
*/
|
|
138
138
|
protected passphrase: string;
|
|
139
139
|
private cachedRefreshToken?;
|
|
140
|
+
private apis;
|
|
141
|
+
private tasks;
|
|
140
142
|
/**
|
|
141
143
|
* Get persisted fields
|
|
142
144
|
*/
|
|
@@ -150,7 +152,7 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
150
152
|
* @param name Application name
|
|
151
153
|
* @param debug Debug mode
|
|
152
154
|
*/
|
|
153
|
-
protected constructor(settings: S, api: IApi, notifier: INotifier<N, C>, storage: IStorage, name: string, debug?: boolean);
|
|
155
|
+
protected constructor(settings: S, api: IApi | undefined | null, notifier: INotifier<N, C>, storage: IStorage, name: string, debug?: boolean);
|
|
154
156
|
private getDeviceId;
|
|
155
157
|
private resetKeys;
|
|
156
158
|
/**
|
|
@@ -178,11 +180,32 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
178
180
|
* Persist settings to source when application exit
|
|
179
181
|
*/
|
|
180
182
|
persist(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Add scheduled task
|
|
185
|
+
* @param task Task, return false to stop
|
|
186
|
+
* @param seconds Interval in seconds
|
|
187
|
+
*/
|
|
188
|
+
addTask(task: () => PromiseLike<void | false>, seconds: number): void;
|
|
189
|
+
/**
|
|
190
|
+
* Create API client, override to implement custom client creation by name
|
|
191
|
+
* @param name Client name
|
|
192
|
+
* @param item External endpoint item
|
|
193
|
+
* @returns Result
|
|
194
|
+
*/
|
|
195
|
+
createApi(name: string, item: ExternalEndpoint, refresh?: (api: IApi, token: string) => Promise<[string, number] | undefined>): IApi<any>;
|
|
196
|
+
/**
|
|
197
|
+
* Update API token and expires
|
|
198
|
+
* @param name Api name
|
|
199
|
+
* @param token Refresh token
|
|
200
|
+
* @param seconds Access token expires in seconds
|
|
201
|
+
*/
|
|
202
|
+
updateApi(name: string, token: string | undefined, seconds: number): void;
|
|
203
|
+
updateApi(data: ApiTaskData, token: string | undefined, seconds: number): void;
|
|
181
204
|
/**
|
|
182
205
|
* Setup Api
|
|
183
206
|
* @param api Api
|
|
184
207
|
*/
|
|
185
|
-
protected setApi(api: IApi): void;
|
|
208
|
+
protected setApi(api: IApi, refresh?: ApiRefreshTokenFunction): void;
|
|
186
209
|
/**
|
|
187
210
|
* Setup Api error handler
|
|
188
211
|
* @param api Api
|
|
@@ -535,12 +558,6 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
535
558
|
* Callback where exit a page
|
|
536
559
|
*/
|
|
537
560
|
pageExit(): void;
|
|
538
|
-
/**
|
|
539
|
-
* Refresh countdown
|
|
540
|
-
* @param seconds Seconds
|
|
541
|
-
*/
|
|
542
|
-
protected refreshCountdown(seconds: number): void;
|
|
543
|
-
protected refreshCountdownClear(): void;
|
|
544
561
|
/**
|
|
545
562
|
* Fresh countdown UI
|
|
546
563
|
* @param callback Callback
|
|
@@ -555,6 +572,35 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
555
572
|
* Setup callback
|
|
556
573
|
*/
|
|
557
574
|
setup(): void;
|
|
575
|
+
/**
|
|
576
|
+
* Exchange token data
|
|
577
|
+
* @param api API
|
|
578
|
+
* @param token Core system's refresh token to exchange
|
|
579
|
+
* @returns Result
|
|
580
|
+
*/
|
|
581
|
+
exchangeToken(api: IApi, token: string): Promise<void>;
|
|
582
|
+
/**
|
|
583
|
+
* Exchange token update, override to get the new token
|
|
584
|
+
* @param api API
|
|
585
|
+
* @param data API refresh token data
|
|
586
|
+
*/
|
|
587
|
+
protected exchangeTokenUpdate(api: IApi, data: ApiRefreshTokenDto): void;
|
|
588
|
+
/**
|
|
589
|
+
* Exchange intergration tokens for all APIs
|
|
590
|
+
* @param token Core system's refresh token to exchange
|
|
591
|
+
*/
|
|
592
|
+
exchangeTokenAll(token: string): void;
|
|
593
|
+
/**
|
|
594
|
+
* API refresh token
|
|
595
|
+
* @param api Current API
|
|
596
|
+
* @param token Refresh token
|
|
597
|
+
* @returns Result
|
|
598
|
+
*/
|
|
599
|
+
protected apiRefreshToken(api: IApi, token: string): Promise<[string, number] | undefined>;
|
|
600
|
+
/**
|
|
601
|
+
* Setup tasks
|
|
602
|
+
*/
|
|
603
|
+
protected setupTasks(): void;
|
|
558
604
|
/**
|
|
559
605
|
* Signout, with userLogout and toLoginPage
|
|
560
606
|
* @param apiUrl Signout API URL
|
|
@@ -569,10 +615,9 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
569
615
|
/**
|
|
570
616
|
* Try login, returning false means is loading
|
|
571
617
|
* UI get involved while refreshToken not intended
|
|
572
|
-
* @param data Additional request data
|
|
573
618
|
* @param showLoading Show loading bar or not during call
|
|
574
619
|
*/
|
|
575
|
-
tryLogin
|
|
620
|
+
tryLogin(_showLoading?: boolean): Promise<boolean>;
|
|
576
621
|
/**
|
|
577
622
|
* User login
|
|
578
623
|
* @param user User data
|
|
@@ -598,3 +643,4 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
598
643
|
*/
|
|
599
644
|
warning(message: NotificationContent<N>, align?: NotificationAlign): void;
|
|
600
645
|
}
|
|
646
|
+
export {};
|
package/lib/mjs/app/CoreApp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NotificationAlign, NotificationMessageType } from '@etsoo/notificationbase';
|
|
2
|
-
import { ApiDataError } from '@etsoo/restclient';
|
|
3
|
-
import { DataTypes, DateUtils, DomUtils, NumberUtils, Utils } from '@etsoo/shared';
|
|
2
|
+
import { ApiDataError, createClient } from '@etsoo/restclient';
|
|
3
|
+
import { DataTypes, DateUtils, DomUtils, ExtendUtils, NumberUtils, Utils } from '@etsoo/shared';
|
|
4
4
|
import { AddressRegion } from '../address/AddressRegion';
|
|
5
5
|
import { BridgeUtils } from '../bridges/BridgeUtils';
|
|
6
6
|
import { DataPrivacy } from '../business/DataPrivacy';
|
|
@@ -112,10 +112,6 @@ export class CoreApp {
|
|
|
112
112
|
* Last called with token refresh
|
|
113
113
|
*/
|
|
114
114
|
this.lastCalled = false;
|
|
115
|
-
/**
|
|
116
|
-
* Token refresh count down seed
|
|
117
|
-
*/
|
|
118
|
-
this.refreshCountdownSeed = 0;
|
|
119
115
|
/**
|
|
120
116
|
* Init call Api URL
|
|
121
117
|
*/
|
|
@@ -124,6 +120,8 @@ export class CoreApp {
|
|
|
124
120
|
* Passphrase for encryption
|
|
125
121
|
*/
|
|
126
122
|
this.passphrase = '';
|
|
123
|
+
this.apis = {};
|
|
124
|
+
this.tasks = [];
|
|
127
125
|
if (settings?.regions?.length === 0) {
|
|
128
126
|
throw new Error('No regions defined');
|
|
129
127
|
}
|
|
@@ -133,7 +131,30 @@ export class CoreApp {
|
|
|
133
131
|
throw new Error('No default region defined');
|
|
134
132
|
}
|
|
135
133
|
this.defaultRegion = region;
|
|
136
|
-
|
|
134
|
+
const refresh = async (api, token) => {
|
|
135
|
+
if (this.lastCalled) {
|
|
136
|
+
// Call refreshToken to update access token
|
|
137
|
+
await this.refreshToken();
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Popup countdown for user action
|
|
141
|
+
this.freshCountdownUI();
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
};
|
|
145
|
+
if (api) {
|
|
146
|
+
// Base URL of the API
|
|
147
|
+
api.baseUrl = this.settings.endpoint;
|
|
148
|
+
api.name = 'system';
|
|
149
|
+
this.setApi(api, refresh);
|
|
150
|
+
this.api = api;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
this.api = this.createApi('system', {
|
|
154
|
+
endpoint: settings.endpoint,
|
|
155
|
+
webUrl: settings.webUrl
|
|
156
|
+
}, refresh);
|
|
157
|
+
}
|
|
137
158
|
this.notifier = notifier;
|
|
138
159
|
this.storage = storage;
|
|
139
160
|
this.name = name;
|
|
@@ -142,7 +163,6 @@ export class CoreApp {
|
|
|
142
163
|
this.fields = appFields.reduce((a, v) => ({ ...a, [v]: 'smarterp-' + v + '-' + name }), {});
|
|
143
164
|
// Device id
|
|
144
165
|
this._deviceId = storage.getData(this.fields.deviceId, '');
|
|
145
|
-
this.setApi(api);
|
|
146
166
|
const { currentCulture, currentRegion } = settings;
|
|
147
167
|
// Load resources
|
|
148
168
|
Promise.all([loadCrypto(), this.changeCulture(currentCulture)]).then(([cj, _resources]) => {
|
|
@@ -260,17 +280,56 @@ export class CoreApp {
|
|
|
260
280
|
return;
|
|
261
281
|
this.storage.copyTo(this.persistedFields);
|
|
262
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Add scheduled task
|
|
285
|
+
* @param task Task, return false to stop
|
|
286
|
+
* @param seconds Interval in seconds
|
|
287
|
+
*/
|
|
288
|
+
addTask(task, seconds) {
|
|
289
|
+
this.tasks.push([task, seconds, seconds]);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Create API client, override to implement custom client creation by name
|
|
293
|
+
* @param name Client name
|
|
294
|
+
* @param item External endpoint item
|
|
295
|
+
* @returns Result
|
|
296
|
+
*/
|
|
297
|
+
createApi(name, item, refresh) {
|
|
298
|
+
if (this.apis[name] != null) {
|
|
299
|
+
throw new Error(`API ${name} already exists`);
|
|
300
|
+
}
|
|
301
|
+
const api = createClient();
|
|
302
|
+
api.name = name;
|
|
303
|
+
api.baseUrl = item.endpoint;
|
|
304
|
+
this.setApi(api, refresh);
|
|
305
|
+
return api;
|
|
306
|
+
}
|
|
307
|
+
updateApi(nameOrData, token, seconds) {
|
|
308
|
+
const api = typeof nameOrData === 'string' ? this.apis[nameOrData] : nameOrData;
|
|
309
|
+
if (api == null)
|
|
310
|
+
return;
|
|
311
|
+
// Consider the API call delay
|
|
312
|
+
if (seconds > 0) {
|
|
313
|
+
seconds -= 30;
|
|
314
|
+
if (seconds < 10)
|
|
315
|
+
seconds = 10;
|
|
316
|
+
}
|
|
317
|
+
api[1] = seconds;
|
|
318
|
+
api[2] = seconds;
|
|
319
|
+
api[4] = token;
|
|
320
|
+
}
|
|
263
321
|
/**
|
|
264
322
|
* Setup Api
|
|
265
323
|
* @param api Api
|
|
266
324
|
*/
|
|
267
|
-
setApi(api) {
|
|
268
|
-
// Base URL of the API
|
|
269
|
-
api.baseUrl = this.settings.endpoint;
|
|
325
|
+
setApi(api, refresh) {
|
|
270
326
|
// onRequest, show loading or not, rewrite the property to override default action
|
|
271
327
|
this.setApiLoading(api);
|
|
272
328
|
// Global API error handler
|
|
273
329
|
this.setApiErrorHandler(api);
|
|
330
|
+
// Setup API countdown
|
|
331
|
+
refresh ?? (refresh = this.apiRefreshToken.bind(this));
|
|
332
|
+
this.apis[api.name] = [api, -1, -1, refresh];
|
|
274
333
|
}
|
|
275
334
|
/**
|
|
276
335
|
* Setup Api error handler
|
|
@@ -281,7 +340,7 @@ export class CoreApp {
|
|
|
281
340
|
api.onError = (error) => {
|
|
282
341
|
// Debug
|
|
283
342
|
if (this.debug) {
|
|
284
|
-
console.debug(
|
|
343
|
+
console.debug(`CoreApp.${this.name}.setApiErrorHandler`, api, error, handlerFor401);
|
|
285
344
|
}
|
|
286
345
|
// Error code
|
|
287
346
|
const status = error.response
|
|
@@ -303,12 +362,12 @@ export class CoreApp {
|
|
|
303
362
|
(error.message === 'Network Error' ||
|
|
304
363
|
error.message === 'Failed to fetch')) {
|
|
305
364
|
// Network error
|
|
306
|
-
this.notifier.alert(this.get('networkError'));
|
|
365
|
+
this.notifier.alert(this.get('networkError') + ` [${this.name}]`);
|
|
307
366
|
return;
|
|
308
367
|
}
|
|
309
368
|
else {
|
|
310
369
|
// Log
|
|
311
|
-
console.error(
|
|
370
|
+
console.error(`${this.name} API error`, error);
|
|
312
371
|
}
|
|
313
372
|
// Report the error
|
|
314
373
|
this.notifier.alert(this.formatError(error));
|
|
@@ -323,7 +382,7 @@ export class CoreApp {
|
|
|
323
382
|
api.onRequest = (data) => {
|
|
324
383
|
// Debug
|
|
325
384
|
if (this.debug) {
|
|
326
|
-
console.debug(
|
|
385
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onRequest`, api, data, this.notifier.loadingCount);
|
|
327
386
|
}
|
|
328
387
|
if (data.showLoading == null || data.showLoading) {
|
|
329
388
|
this.notifier.showLoading();
|
|
@@ -333,13 +392,13 @@ export class CoreApp {
|
|
|
333
392
|
api.onComplete = (data) => {
|
|
334
393
|
// Debug
|
|
335
394
|
if (this.debug) {
|
|
336
|
-
console.debug(
|
|
395
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onComplete`, api, data, this.notifier.loadingCount, this.lastCalled);
|
|
337
396
|
}
|
|
338
397
|
if (data.showLoading == null || data.showLoading) {
|
|
339
398
|
this.notifier.hideLoading();
|
|
340
399
|
// Debug
|
|
341
400
|
if (this.debug) {
|
|
342
|
-
console.debug(
|
|
401
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onComplete.showLoading`, api, this.notifier.loadingCount);
|
|
343
402
|
}
|
|
344
403
|
}
|
|
345
404
|
this.lastCalled = true;
|
|
@@ -550,11 +609,15 @@ export class CoreApp {
|
|
|
550
609
|
// Reset tryLogin state
|
|
551
610
|
this._isTryingLogin = false;
|
|
552
611
|
// Token countdown
|
|
553
|
-
if (this.authorized)
|
|
554
|
-
this.
|
|
612
|
+
if (this.authorized) {
|
|
613
|
+
this.lastCalled = false;
|
|
614
|
+
if (refreshToken) {
|
|
615
|
+
this.updateApi(this.api.name, refreshToken, this.userData.seconds);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
555
618
|
else {
|
|
556
619
|
this.cachedRefreshToken = undefined;
|
|
557
|
-
this.
|
|
620
|
+
this.updateApi(this.api.name, undefined, -1);
|
|
558
621
|
}
|
|
559
622
|
// Host notice
|
|
560
623
|
BridgeUtils.host?.userAuthorization(this.authorized);
|
|
@@ -1286,40 +1349,6 @@ export class CoreApp {
|
|
|
1286
1349
|
this.lastWarning?.dismiss();
|
|
1287
1350
|
this.notifier.hideLoading(true);
|
|
1288
1351
|
}
|
|
1289
|
-
/**
|
|
1290
|
-
* Refresh countdown
|
|
1291
|
-
* @param seconds Seconds
|
|
1292
|
-
*/
|
|
1293
|
-
refreshCountdown(seconds) {
|
|
1294
|
-
// Make sure is big than 60 seconds
|
|
1295
|
-
// Take action 60 seconds before expiry
|
|
1296
|
-
seconds -= 60;
|
|
1297
|
-
if (seconds <= 0)
|
|
1298
|
-
return;
|
|
1299
|
-
// Clear the current timeout seed
|
|
1300
|
-
this.refreshCountdownClear();
|
|
1301
|
-
// Reset last call flag
|
|
1302
|
-
// Any success call will update it to true
|
|
1303
|
-
// So first time after login will be always silent
|
|
1304
|
-
this.lastCalled = false;
|
|
1305
|
-
this.refreshCountdownSeed = window.setTimeout(() => {
|
|
1306
|
-
if (this.lastCalled) {
|
|
1307
|
-
// Call refreshToken to update access token
|
|
1308
|
-
this.refreshToken();
|
|
1309
|
-
}
|
|
1310
|
-
else {
|
|
1311
|
-
// Popup countdown for user action
|
|
1312
|
-
this.freshCountdownUI();
|
|
1313
|
-
}
|
|
1314
|
-
}, 1000 * seconds);
|
|
1315
|
-
}
|
|
1316
|
-
refreshCountdownClear() {
|
|
1317
|
-
// Clear the current timeout seed
|
|
1318
|
-
if (this.refreshCountdownSeed > 0) {
|
|
1319
|
-
window.clearTimeout(this.refreshCountdownSeed);
|
|
1320
|
-
this.refreshCountdownSeed = 0;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
1352
|
/**
|
|
1324
1353
|
* Refresh token
|
|
1325
1354
|
* @param props Props
|
|
@@ -1333,12 +1362,142 @@ export class CoreApp {
|
|
|
1333
1362
|
* Setup callback
|
|
1334
1363
|
*/
|
|
1335
1364
|
setup() {
|
|
1365
|
+
// Done already
|
|
1366
|
+
if (this.isReady)
|
|
1367
|
+
return;
|
|
1336
1368
|
// Ready
|
|
1337
1369
|
this.isReady = true;
|
|
1338
1370
|
// Restore
|
|
1339
1371
|
this.restore();
|
|
1340
1372
|
// Pending actions
|
|
1341
1373
|
this.pendings.forEach((p) => p());
|
|
1374
|
+
// Setup scheduled tasks
|
|
1375
|
+
this.setupTasks();
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Exchange token data
|
|
1379
|
+
* @param api API
|
|
1380
|
+
* @param token Core system's refresh token to exchange
|
|
1381
|
+
* @returns Result
|
|
1382
|
+
*/
|
|
1383
|
+
async exchangeToken(api, token) {
|
|
1384
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1385
|
+
const data = await api.put('Auth/ExchangeToken', {
|
|
1386
|
+
token
|
|
1387
|
+
}, {
|
|
1388
|
+
showLoading: false,
|
|
1389
|
+
onError: (error) => {
|
|
1390
|
+
console.error(`CoreApp.${api.name}.ExchangeToken error`, error);
|
|
1391
|
+
// Prevent further processing
|
|
1392
|
+
return false;
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
if (data) {
|
|
1396
|
+
// Update the access token
|
|
1397
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
1398
|
+
// Update the API
|
|
1399
|
+
this.updateApi(api.name, data.refreshToken, data.expiresIn);
|
|
1400
|
+
// Update notice
|
|
1401
|
+
this.exchangeTokenUpdate(api, data);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Exchange token update, override to get the new token
|
|
1406
|
+
* @param api API
|
|
1407
|
+
* @param data API refresh token data
|
|
1408
|
+
*/
|
|
1409
|
+
exchangeTokenUpdate(api, data) { }
|
|
1410
|
+
/**
|
|
1411
|
+
* Exchange intergration tokens for all APIs
|
|
1412
|
+
* @param token Core system's refresh token to exchange
|
|
1413
|
+
*/
|
|
1414
|
+
exchangeTokenAll(token) {
|
|
1415
|
+
for (const name in this.apis) {
|
|
1416
|
+
const api = this.apis[name];
|
|
1417
|
+
this.exchangeToken(api[0], token);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* API refresh token
|
|
1422
|
+
* @param api Current API
|
|
1423
|
+
* @param token Refresh token
|
|
1424
|
+
* @returns Result
|
|
1425
|
+
*/
|
|
1426
|
+
async apiRefreshToken(api, token) {
|
|
1427
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1428
|
+
const data = await api.put('Auth/ApiRefreshToken', {
|
|
1429
|
+
token
|
|
1430
|
+
}, {
|
|
1431
|
+
showLoading: false,
|
|
1432
|
+
onError: (error) => {
|
|
1433
|
+
console.error(`CoreApp.${api.name}.apiRefreshToken error`, error);
|
|
1434
|
+
// Prevent further processing
|
|
1435
|
+
return false;
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
if (data == null)
|
|
1439
|
+
return undefined;
|
|
1440
|
+
// Update the access token
|
|
1441
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
1442
|
+
// Return the new refresh token and access token expiration seconds
|
|
1443
|
+
return [data.refreshToken, data.expiresIn];
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Setup tasks
|
|
1447
|
+
*/
|
|
1448
|
+
setupTasks() {
|
|
1449
|
+
ExtendUtils.intervalFor(() => {
|
|
1450
|
+
// Exit when not authorized
|
|
1451
|
+
if (!this.authorized)
|
|
1452
|
+
return;
|
|
1453
|
+
// APIs
|
|
1454
|
+
for (const name in this.apis) {
|
|
1455
|
+
// Get the API
|
|
1456
|
+
const api = this.apis[name];
|
|
1457
|
+
// Skip the negative value or when refresh token is not set
|
|
1458
|
+
if (!api[4] || api[2] < 0)
|
|
1459
|
+
continue;
|
|
1460
|
+
// Minus one second
|
|
1461
|
+
api[2] -= 1;
|
|
1462
|
+
// Ready to trigger
|
|
1463
|
+
if (api[2] === 0) {
|
|
1464
|
+
// Refresh token
|
|
1465
|
+
api[3](api[0], api[4]).then((data) => {
|
|
1466
|
+
if (data == null) {
|
|
1467
|
+
// Failed, try it again in 2 seconds
|
|
1468
|
+
api[2] = 2;
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
// Reset the API
|
|
1472
|
+
const [token, seconds] = data;
|
|
1473
|
+
this.updateApi(api, token, seconds);
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
for (let t = this.tasks.length - 1; t >= 0; t--) {
|
|
1479
|
+
// Get the task
|
|
1480
|
+
const task = this.tasks[t];
|
|
1481
|
+
// Minus one second
|
|
1482
|
+
task[2] -= 1;
|
|
1483
|
+
// Remove the tasks with negative value with splice
|
|
1484
|
+
if (task[2] < 0) {
|
|
1485
|
+
this.tasks.splice(t, 1);
|
|
1486
|
+
}
|
|
1487
|
+
else if (task[2] === 0) {
|
|
1488
|
+
// Ready to trigger
|
|
1489
|
+
// Reset the task
|
|
1490
|
+
task[2] = task[1];
|
|
1491
|
+
// Trigger the task
|
|
1492
|
+
task[0]().then((result) => {
|
|
1493
|
+
if (result === false) {
|
|
1494
|
+
// Asynchronous task, unsafe to splice the index, flag as pending
|
|
1495
|
+
task[2] = -1;
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}, 1000);
|
|
1342
1501
|
}
|
|
1343
1502
|
/**
|
|
1344
1503
|
* Signout, with userLogout and toLoginPage
|
|
@@ -1370,10 +1529,9 @@ export class CoreApp {
|
|
|
1370
1529
|
/**
|
|
1371
1530
|
* Try login, returning false means is loading
|
|
1372
1531
|
* UI get involved while refreshToken not intended
|
|
1373
|
-
* @param data Additional request data
|
|
1374
1532
|
* @param showLoading Show loading bar or not during call
|
|
1375
1533
|
*/
|
|
1376
|
-
async tryLogin(
|
|
1534
|
+
async tryLogin(_showLoading) {
|
|
1377
1535
|
if (this._isTryingLogin)
|
|
1378
1536
|
return false;
|
|
1379
1537
|
this._isTryingLogin = true;
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* External
|
|
2
|
+
* External endpoint
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export type ExternalEndpoint = {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* API endpoint
|
|
7
7
|
*/
|
|
8
8
|
readonly endpoint: string;
|
|
9
|
+
/**
|
|
10
|
+
* Web url
|
|
11
|
+
*/
|
|
12
|
+
readonly webUrl: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* External settings items
|
|
16
|
+
*/
|
|
17
|
+
export interface IExternalSettings extends ExternalEndpoint {
|
|
9
18
|
/**
|
|
10
19
|
* Message hub endpoint
|
|
11
20
|
*/
|
|
12
21
|
readonly messageHub?: string;
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
23
|
+
* App root url
|
|
15
24
|
*/
|
|
16
25
|
readonly homepage: string;
|
|
17
26
|
/**
|
|
18
|
-
*
|
|
19
|
-
*/
|
|
20
|
-
readonly webUrl: string;
|
|
21
|
-
/**
|
|
22
|
-
* Service API endpoint
|
|
23
|
-
*/
|
|
24
|
-
readonly serviceEndpoint?: string;
|
|
25
|
-
/**
|
|
26
|
-
* Service web Url
|
|
27
|
+
* Endpoints to other services
|
|
27
28
|
*/
|
|
28
|
-
readonly
|
|
29
|
+
readonly endpoints?: Record<'core' | 'accounting' | 'crm' | 'calandar' | 'task' | string, ExternalEndpoint>;
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* External settings namespace
|
package/lib/mjs/app/IApp.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { IAppSettings } from './AppSettings';
|
|
|
7
7
|
import { UserRole } from './UserRole';
|
|
8
8
|
import { EntityStatus } from '../business/EntityStatus';
|
|
9
9
|
import { Currency } from '../business/Currency';
|
|
10
|
+
import { ExternalEndpoint } from './ExternalSettings';
|
|
10
11
|
/**
|
|
11
12
|
* Detect IP callback interface
|
|
12
13
|
*/
|
|
@@ -155,6 +156,12 @@ export interface IApp {
|
|
|
155
156
|
* @returns Result
|
|
156
157
|
*/
|
|
157
158
|
addRootUrl(url: string): string;
|
|
159
|
+
/**
|
|
160
|
+
* Add scheduled task
|
|
161
|
+
* @param task Task, return false to stop
|
|
162
|
+
* @param interval Interval in milliseconds
|
|
163
|
+
*/
|
|
164
|
+
addTask(task: () => PromiseLike<void | false>, interval: number): void;
|
|
158
165
|
/**
|
|
159
166
|
* Alert result
|
|
160
167
|
* @param result Result message
|
|
@@ -209,6 +216,13 @@ export interface IApp {
|
|
|
209
216
|
* Clear device id
|
|
210
217
|
*/
|
|
211
218
|
clearDeviceId(): void;
|
|
219
|
+
/**
|
|
220
|
+
* Create API client, override to implement custom client creation by name
|
|
221
|
+
* @param name Client name
|
|
222
|
+
* @param item External endpoint item
|
|
223
|
+
* @returns Result
|
|
224
|
+
*/
|
|
225
|
+
createApi(name: string, item: ExternalEndpoint, refresh?: (api: IApi, token: string) => Promise<[string, number] | undefined>): IApi;
|
|
212
226
|
/**
|
|
213
227
|
* Decrypt message
|
|
214
228
|
* @param messageEncrypted Encrypted message
|
|
@@ -252,6 +266,18 @@ export interface IApp {
|
|
|
252
266
|
* @returns Result
|
|
253
267
|
*/
|
|
254
268
|
encryptEnhanced(message: string, passphrase?: string, iterations?: number): string;
|
|
269
|
+
/**
|
|
270
|
+
* Exchange token data
|
|
271
|
+
* @param api API
|
|
272
|
+
* @param token Core system's refresh token to exchange
|
|
273
|
+
* @returns Result
|
|
274
|
+
*/
|
|
275
|
+
exchangeToken(api: IApi, token: string): Promise<void>;
|
|
276
|
+
/**
|
|
277
|
+
* Exchange intergration tokens for all APIs
|
|
278
|
+
* @param token Core system's refresh token to exchange
|
|
279
|
+
*/
|
|
280
|
+
exchangeTokenAll(token: string): void;
|
|
255
281
|
/**
|
|
256
282
|
* Format date to string
|
|
257
283
|
* @param input Input date
|
|
@@ -505,9 +531,16 @@ export interface IApp {
|
|
|
505
531
|
/**
|
|
506
532
|
* Try login, returning false means is loading
|
|
507
533
|
* UI get involved while refreshToken not intended
|
|
508
|
-
* @param
|
|
534
|
+
* @param showLoading Show loading bar or not
|
|
535
|
+
*/
|
|
536
|
+
tryLogin(showLoading?: boolean): Promise<boolean>;
|
|
537
|
+
/**
|
|
538
|
+
* Update API token and expires
|
|
539
|
+
* @param name Api name
|
|
540
|
+
* @param token Refresh token
|
|
541
|
+
* @param seconds Access token expires in seconds
|
|
509
542
|
*/
|
|
510
|
-
|
|
543
|
+
updateApi(name: string, token: string | undefined, seconds: number): void;
|
|
511
544
|
/**
|
|
512
545
|
* User login
|
|
513
546
|
* @param user User data
|