@etsoo/appscript 1.5.20 → 1.5.22
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 +64 -12
- package/lib/cjs/app/CoreApp.js +223 -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 +38 -1
- package/lib/cjs/app/IApp.js +2 -1
- 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 +64 -12
- package/lib/mjs/app/CoreApp.js +225 -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 +38 -1
- package/lib/mjs/app/IApp.js +2 -1
- 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 +317 -66
- package/src/app/ExternalSettings.ts +21 -16
- package/src/app/IApp.ts +52 -1
- package/src/erp/AuthApi.ts +1 -1
- package/src/erp/dto/ApiRefreshTokenDto.ts +24 -0
package/__tests__/app/CoreApp.ts
CHANGED
|
@@ -88,6 +88,13 @@ class CoreAppTest extends CoreApp<
|
|
|
88
88
|
*/
|
|
89
89
|
endpoint: 'http://{hostname}/com.etsoo.SmartERPApi/api/',
|
|
90
90
|
|
|
91
|
+
endpoints: {
|
|
92
|
+
core: {
|
|
93
|
+
endpoint: 'http://{hostname}:9001/api/',
|
|
94
|
+
webUrl: ''
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
91
98
|
/**
|
|
92
99
|
* App root url
|
|
93
100
|
*/
|
|
@@ -155,6 +162,16 @@ const appClass = EnhanceApp(CoreAppTest);
|
|
|
155
162
|
const app = new appClass();
|
|
156
163
|
app.changeCulture(app.settings.cultures[0]);
|
|
157
164
|
|
|
165
|
+
test('Test for domain replacement', () => {
|
|
166
|
+
expect(app.settings.endpoint).toBe(
|
|
167
|
+
'http://localhost/com.etsoo.SmartERPApi/api/'
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
expect(app.settings.endpoints?.core.endpoint).toBe(
|
|
171
|
+
'http://localhost:9001/api/'
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
158
175
|
test('Test for properties', () => {
|
|
159
176
|
expect(app.settings.currentRegion.label).toBe('中国大陆');
|
|
160
177
|
});
|
package/lib/cjs/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
|
*/
|
|
@@ -119,15 +123,16 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
119
123
|
*/
|
|
120
124
|
get isReady(): boolean;
|
|
121
125
|
private set isReady(value);
|
|
126
|
+
/**
|
|
127
|
+
* Current cached URL
|
|
128
|
+
*/
|
|
129
|
+
get cachedUrl(): string | undefined | null;
|
|
130
|
+
set cachedUrl(value: string | undefined | null);
|
|
122
131
|
private _isTryingLogin;
|
|
123
132
|
/**
|
|
124
133
|
* Last called with token refresh
|
|
125
134
|
*/
|
|
126
135
|
protected lastCalled: boolean;
|
|
127
|
-
/**
|
|
128
|
-
* Token refresh count down seed
|
|
129
|
-
*/
|
|
130
|
-
protected refreshCountdownSeed: number;
|
|
131
136
|
/**
|
|
132
137
|
* Init call Api URL
|
|
133
138
|
*/
|
|
@@ -137,6 +142,8 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
137
142
|
*/
|
|
138
143
|
protected passphrase: string;
|
|
139
144
|
private cachedRefreshToken?;
|
|
145
|
+
private apis;
|
|
146
|
+
private tasks;
|
|
140
147
|
/**
|
|
141
148
|
* Get persisted fields
|
|
142
149
|
*/
|
|
@@ -150,7 +157,7 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
150
157
|
* @param name Application name
|
|
151
158
|
* @param debug Debug mode
|
|
152
159
|
*/
|
|
153
|
-
protected constructor(settings: S, api: IApi, notifier: INotifier<N, C>, storage: IStorage, name: string, debug?: boolean);
|
|
160
|
+
protected constructor(settings: S, api: IApi | undefined | null, notifier: INotifier<N, C>, storage: IStorage, name: string, debug?: boolean);
|
|
154
161
|
private getDeviceId;
|
|
155
162
|
private resetKeys;
|
|
156
163
|
/**
|
|
@@ -178,11 +185,32 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
178
185
|
* Persist settings to source when application exit
|
|
179
186
|
*/
|
|
180
187
|
persist(): void;
|
|
188
|
+
/**
|
|
189
|
+
* Add scheduled task
|
|
190
|
+
* @param task Task, return false to stop
|
|
191
|
+
* @param seconds Interval in seconds
|
|
192
|
+
*/
|
|
193
|
+
addTask(task: () => PromiseLike<void | false>, seconds: number): void;
|
|
194
|
+
/**
|
|
195
|
+
* Create API client, override to implement custom client creation by name
|
|
196
|
+
* @param name Client name
|
|
197
|
+
* @param item External endpoint item
|
|
198
|
+
* @returns Result
|
|
199
|
+
*/
|
|
200
|
+
createApi(name: string, item: ExternalEndpoint, refresh?: (api: IApi, token: string) => Promise<[string, number] | undefined>): IApi<any>;
|
|
201
|
+
/**
|
|
202
|
+
* Update API token and expires
|
|
203
|
+
* @param name Api name
|
|
204
|
+
* @param token Refresh token
|
|
205
|
+
* @param seconds Access token expires in seconds
|
|
206
|
+
*/
|
|
207
|
+
updateApi(name: string, token: string | undefined, seconds: number): void;
|
|
208
|
+
updateApi(data: ApiTaskData, token: string | undefined, seconds: number): void;
|
|
181
209
|
/**
|
|
182
210
|
* Setup Api
|
|
183
211
|
* @param api Api
|
|
184
212
|
*/
|
|
185
|
-
protected setApi(api: IApi): void;
|
|
213
|
+
protected setApi(api: IApi, refresh?: ApiRefreshTokenFunction): void;
|
|
186
214
|
/**
|
|
187
215
|
* Setup Api error handler
|
|
188
216
|
* @param api Api
|
|
@@ -535,12 +563,6 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
535
563
|
* Callback where exit a page
|
|
536
564
|
*/
|
|
537
565
|
pageExit(): void;
|
|
538
|
-
/**
|
|
539
|
-
* Refresh countdown
|
|
540
|
-
* @param seconds Seconds
|
|
541
|
-
*/
|
|
542
|
-
protected refreshCountdown(seconds: number): void;
|
|
543
|
-
protected refreshCountdownClear(): void;
|
|
544
566
|
/**
|
|
545
567
|
* Fresh countdown UI
|
|
546
568
|
* @param callback Callback
|
|
@@ -555,6 +577,35 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
555
577
|
* Setup callback
|
|
556
578
|
*/
|
|
557
579
|
setup(): void;
|
|
580
|
+
/**
|
|
581
|
+
* Exchange token data
|
|
582
|
+
* @param api API
|
|
583
|
+
* @param token Core system's refresh token to exchange
|
|
584
|
+
* @returns Result
|
|
585
|
+
*/
|
|
586
|
+
exchangeToken(api: IApi, token: string): Promise<void>;
|
|
587
|
+
/**
|
|
588
|
+
* Exchange token update, override to get the new token
|
|
589
|
+
* @param api API
|
|
590
|
+
* @param data API refresh token data
|
|
591
|
+
*/
|
|
592
|
+
protected exchangeTokenUpdate(api: IApi, data: ApiRefreshTokenDto): void;
|
|
593
|
+
/**
|
|
594
|
+
* Exchange intergration tokens for all APIs
|
|
595
|
+
* @param token Core system's refresh token to exchange
|
|
596
|
+
*/
|
|
597
|
+
exchangeTokenAll(token: string): void;
|
|
598
|
+
/**
|
|
599
|
+
* API refresh token
|
|
600
|
+
* @param api Current API
|
|
601
|
+
* @param token Refresh token
|
|
602
|
+
* @returns Result
|
|
603
|
+
*/
|
|
604
|
+
protected apiRefreshToken(api: IApi, token: string): Promise<[string, number] | undefined>;
|
|
605
|
+
/**
|
|
606
|
+
* Setup tasks
|
|
607
|
+
*/
|
|
608
|
+
protected setupTasks(): void;
|
|
558
609
|
/**
|
|
559
610
|
* Signout, with userLogout and toLoginPage
|
|
560
611
|
* @param apiUrl Signout API URL
|
|
@@ -597,3 +648,4 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
|
|
|
597
648
|
*/
|
|
598
649
|
warning(message: NotificationContent<N>, align?: NotificationAlign): void;
|
|
599
650
|
}
|
|
651
|
+
export {};
|
package/lib/cjs/app/CoreApp.js
CHANGED
|
@@ -83,6 +83,15 @@ class CoreApp {
|
|
|
83
83
|
set isReady(value) {
|
|
84
84
|
this._isReady = value;
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Current cached URL
|
|
88
|
+
*/
|
|
89
|
+
get cachedUrl() {
|
|
90
|
+
return this.storage.getData(this.fields.cachedUrl);
|
|
91
|
+
}
|
|
92
|
+
set cachedUrl(value) {
|
|
93
|
+
this.storage.setData(this.fields.cachedUrl, value);
|
|
94
|
+
}
|
|
86
95
|
/**
|
|
87
96
|
* Get persisted fields
|
|
88
97
|
*/
|
|
@@ -115,10 +124,6 @@ class CoreApp {
|
|
|
115
124
|
* Last called with token refresh
|
|
116
125
|
*/
|
|
117
126
|
this.lastCalled = false;
|
|
118
|
-
/**
|
|
119
|
-
* Token refresh count down seed
|
|
120
|
-
*/
|
|
121
|
-
this.refreshCountdownSeed = 0;
|
|
122
127
|
/**
|
|
123
128
|
* Init call Api URL
|
|
124
129
|
*/
|
|
@@ -127,6 +132,8 @@ class CoreApp {
|
|
|
127
132
|
* Passphrase for encryption
|
|
128
133
|
*/
|
|
129
134
|
this.passphrase = '';
|
|
135
|
+
this.apis = {};
|
|
136
|
+
this.tasks = [];
|
|
130
137
|
if (settings?.regions?.length === 0) {
|
|
131
138
|
throw new Error('No regions defined');
|
|
132
139
|
}
|
|
@@ -136,7 +143,30 @@ class CoreApp {
|
|
|
136
143
|
throw new Error('No default region defined');
|
|
137
144
|
}
|
|
138
145
|
this.defaultRegion = region;
|
|
139
|
-
|
|
146
|
+
const refresh = async (api, token) => {
|
|
147
|
+
if (this.lastCalled) {
|
|
148
|
+
// Call refreshToken to update access token
|
|
149
|
+
await this.refreshToken();
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Popup countdown for user action
|
|
153
|
+
this.freshCountdownUI();
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
};
|
|
157
|
+
if (api) {
|
|
158
|
+
// Base URL of the API
|
|
159
|
+
api.baseUrl = this.settings.endpoint;
|
|
160
|
+
api.name = 'system';
|
|
161
|
+
this.setApi(api, refresh);
|
|
162
|
+
this.api = api;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.api = this.createApi('system', {
|
|
166
|
+
endpoint: settings.endpoint,
|
|
167
|
+
webUrl: settings.webUrl
|
|
168
|
+
}, refresh);
|
|
169
|
+
}
|
|
140
170
|
this.notifier = notifier;
|
|
141
171
|
this.storage = storage;
|
|
142
172
|
this.name = name;
|
|
@@ -145,7 +175,6 @@ class CoreApp {
|
|
|
145
175
|
this.fields = IApp_1.appFields.reduce((a, v) => ({ ...a, [v]: 'smarterp-' + v + '-' + name }), {});
|
|
146
176
|
// Device id
|
|
147
177
|
this._deviceId = storage.getData(this.fields.deviceId, '');
|
|
148
|
-
this.setApi(api);
|
|
149
178
|
const { currentCulture, currentRegion } = settings;
|
|
150
179
|
// Load resources
|
|
151
180
|
Promise.all([loadCrypto(), this.changeCulture(currentCulture)]).then(([cj, _resources]) => {
|
|
@@ -263,17 +292,56 @@ class CoreApp {
|
|
|
263
292
|
return;
|
|
264
293
|
this.storage.copyTo(this.persistedFields);
|
|
265
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Add scheduled task
|
|
297
|
+
* @param task Task, return false to stop
|
|
298
|
+
* @param seconds Interval in seconds
|
|
299
|
+
*/
|
|
300
|
+
addTask(task, seconds) {
|
|
301
|
+
this.tasks.push([task, seconds, seconds]);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Create API client, override to implement custom client creation by name
|
|
305
|
+
* @param name Client name
|
|
306
|
+
* @param item External endpoint item
|
|
307
|
+
* @returns Result
|
|
308
|
+
*/
|
|
309
|
+
createApi(name, item, refresh) {
|
|
310
|
+
if (this.apis[name] != null) {
|
|
311
|
+
throw new Error(`API ${name} already exists`);
|
|
312
|
+
}
|
|
313
|
+
const api = (0, restclient_1.createClient)();
|
|
314
|
+
api.name = name;
|
|
315
|
+
api.baseUrl = item.endpoint;
|
|
316
|
+
this.setApi(api, refresh);
|
|
317
|
+
return api;
|
|
318
|
+
}
|
|
319
|
+
updateApi(nameOrData, token, seconds) {
|
|
320
|
+
const api = typeof nameOrData === 'string' ? this.apis[nameOrData] : nameOrData;
|
|
321
|
+
if (api == null)
|
|
322
|
+
return;
|
|
323
|
+
// Consider the API call delay
|
|
324
|
+
if (seconds > 0) {
|
|
325
|
+
seconds -= 30;
|
|
326
|
+
if (seconds < 10)
|
|
327
|
+
seconds = 10;
|
|
328
|
+
}
|
|
329
|
+
api[1] = seconds;
|
|
330
|
+
api[2] = seconds;
|
|
331
|
+
api[4] = token;
|
|
332
|
+
}
|
|
266
333
|
/**
|
|
267
334
|
* Setup Api
|
|
268
335
|
* @param api Api
|
|
269
336
|
*/
|
|
270
|
-
setApi(api) {
|
|
271
|
-
// Base URL of the API
|
|
272
|
-
api.baseUrl = this.settings.endpoint;
|
|
337
|
+
setApi(api, refresh) {
|
|
273
338
|
// onRequest, show loading or not, rewrite the property to override default action
|
|
274
339
|
this.setApiLoading(api);
|
|
275
340
|
// Global API error handler
|
|
276
341
|
this.setApiErrorHandler(api);
|
|
342
|
+
// Setup API countdown
|
|
343
|
+
refresh ?? (refresh = this.apiRefreshToken.bind(this));
|
|
344
|
+
this.apis[api.name] = [api, -1, -1, refresh];
|
|
277
345
|
}
|
|
278
346
|
/**
|
|
279
347
|
* Setup Api error handler
|
|
@@ -284,7 +352,7 @@ class CoreApp {
|
|
|
284
352
|
api.onError = (error) => {
|
|
285
353
|
// Debug
|
|
286
354
|
if (this.debug) {
|
|
287
|
-
console.debug(
|
|
355
|
+
console.debug(`CoreApp.${this.name}.setApiErrorHandler`, api, error, handlerFor401);
|
|
288
356
|
}
|
|
289
357
|
// Error code
|
|
290
358
|
const status = error.response
|
|
@@ -306,12 +374,12 @@ class CoreApp {
|
|
|
306
374
|
(error.message === 'Network Error' ||
|
|
307
375
|
error.message === 'Failed to fetch')) {
|
|
308
376
|
// Network error
|
|
309
|
-
this.notifier.alert(this.get('networkError'));
|
|
377
|
+
this.notifier.alert(this.get('networkError') + ` [${this.name}]`);
|
|
310
378
|
return;
|
|
311
379
|
}
|
|
312
380
|
else {
|
|
313
381
|
// Log
|
|
314
|
-
console.error(
|
|
382
|
+
console.error(`${this.name} API error`, error);
|
|
315
383
|
}
|
|
316
384
|
// Report the error
|
|
317
385
|
this.notifier.alert(this.formatError(error));
|
|
@@ -326,7 +394,7 @@ class CoreApp {
|
|
|
326
394
|
api.onRequest = (data) => {
|
|
327
395
|
// Debug
|
|
328
396
|
if (this.debug) {
|
|
329
|
-
console.debug(
|
|
397
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onRequest`, api, data, this.notifier.loadingCount);
|
|
330
398
|
}
|
|
331
399
|
if (data.showLoading == null || data.showLoading) {
|
|
332
400
|
this.notifier.showLoading();
|
|
@@ -336,13 +404,13 @@ class CoreApp {
|
|
|
336
404
|
api.onComplete = (data) => {
|
|
337
405
|
// Debug
|
|
338
406
|
if (this.debug) {
|
|
339
|
-
console.debug(
|
|
407
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onComplete`, api, data, this.notifier.loadingCount, this.lastCalled);
|
|
340
408
|
}
|
|
341
409
|
if (data.showLoading == null || data.showLoading) {
|
|
342
410
|
this.notifier.hideLoading();
|
|
343
411
|
// Debug
|
|
344
412
|
if (this.debug) {
|
|
345
|
-
console.debug(
|
|
413
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onComplete.showLoading`, api, this.notifier.loadingCount);
|
|
346
414
|
}
|
|
347
415
|
}
|
|
348
416
|
this.lastCalled = true;
|
|
@@ -553,11 +621,15 @@ class CoreApp {
|
|
|
553
621
|
// Reset tryLogin state
|
|
554
622
|
this._isTryingLogin = false;
|
|
555
623
|
// Token countdown
|
|
556
|
-
if (this.authorized)
|
|
557
|
-
this.
|
|
624
|
+
if (this.authorized) {
|
|
625
|
+
this.lastCalled = false;
|
|
626
|
+
if (refreshToken) {
|
|
627
|
+
this.updateApi(this.api.name, refreshToken, this.userData.seconds);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
558
630
|
else {
|
|
559
631
|
this.cachedRefreshToken = undefined;
|
|
560
|
-
this.
|
|
632
|
+
this.updateApi(this.api.name, undefined, -1);
|
|
561
633
|
}
|
|
562
634
|
// Host notice
|
|
563
635
|
BridgeUtils_1.BridgeUtils.host?.userAuthorization(this.authorized);
|
|
@@ -1289,40 +1361,6 @@ class CoreApp {
|
|
|
1289
1361
|
this.lastWarning?.dismiss();
|
|
1290
1362
|
this.notifier.hideLoading(true);
|
|
1291
1363
|
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Refresh countdown
|
|
1294
|
-
* @param seconds Seconds
|
|
1295
|
-
*/
|
|
1296
|
-
refreshCountdown(seconds) {
|
|
1297
|
-
// Make sure is big than 60 seconds
|
|
1298
|
-
// Take action 60 seconds before expiry
|
|
1299
|
-
seconds -= 60;
|
|
1300
|
-
if (seconds <= 0)
|
|
1301
|
-
return;
|
|
1302
|
-
// Clear the current timeout seed
|
|
1303
|
-
this.refreshCountdownClear();
|
|
1304
|
-
// Reset last call flag
|
|
1305
|
-
// Any success call will update it to true
|
|
1306
|
-
// So first time after login will be always silent
|
|
1307
|
-
this.lastCalled = false;
|
|
1308
|
-
this.refreshCountdownSeed = window.setTimeout(() => {
|
|
1309
|
-
if (this.lastCalled) {
|
|
1310
|
-
// Call refreshToken to update access token
|
|
1311
|
-
this.refreshToken();
|
|
1312
|
-
}
|
|
1313
|
-
else {
|
|
1314
|
-
// Popup countdown for user action
|
|
1315
|
-
this.freshCountdownUI();
|
|
1316
|
-
}
|
|
1317
|
-
}, 1000 * seconds);
|
|
1318
|
-
}
|
|
1319
|
-
refreshCountdownClear() {
|
|
1320
|
-
// Clear the current timeout seed
|
|
1321
|
-
if (this.refreshCountdownSeed > 0) {
|
|
1322
|
-
window.clearTimeout(this.refreshCountdownSeed);
|
|
1323
|
-
this.refreshCountdownSeed = 0;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
1364
|
/**
|
|
1327
1365
|
* Refresh token
|
|
1328
1366
|
* @param props Props
|
|
@@ -1336,12 +1374,142 @@ class CoreApp {
|
|
|
1336
1374
|
* Setup callback
|
|
1337
1375
|
*/
|
|
1338
1376
|
setup() {
|
|
1377
|
+
// Done already
|
|
1378
|
+
if (this.isReady)
|
|
1379
|
+
return;
|
|
1339
1380
|
// Ready
|
|
1340
1381
|
this.isReady = true;
|
|
1341
1382
|
// Restore
|
|
1342
1383
|
this.restore();
|
|
1343
1384
|
// Pending actions
|
|
1344
1385
|
this.pendings.forEach((p) => p());
|
|
1386
|
+
// Setup scheduled tasks
|
|
1387
|
+
this.setupTasks();
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Exchange token data
|
|
1391
|
+
* @param api API
|
|
1392
|
+
* @param token Core system's refresh token to exchange
|
|
1393
|
+
* @returns Result
|
|
1394
|
+
*/
|
|
1395
|
+
async exchangeToken(api, token) {
|
|
1396
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1397
|
+
const data = await api.put('Auth/ExchangeToken', {
|
|
1398
|
+
token
|
|
1399
|
+
}, {
|
|
1400
|
+
showLoading: false,
|
|
1401
|
+
onError: (error) => {
|
|
1402
|
+
console.error(`CoreApp.${api.name}.ExchangeToken error`, error);
|
|
1403
|
+
// Prevent further processing
|
|
1404
|
+
return false;
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
if (data) {
|
|
1408
|
+
// Update the access token
|
|
1409
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
1410
|
+
// Update the API
|
|
1411
|
+
this.updateApi(api.name, data.refreshToken, data.expiresIn);
|
|
1412
|
+
// Update notice
|
|
1413
|
+
this.exchangeTokenUpdate(api, data);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Exchange token update, override to get the new token
|
|
1418
|
+
* @param api API
|
|
1419
|
+
* @param data API refresh token data
|
|
1420
|
+
*/
|
|
1421
|
+
exchangeTokenUpdate(api, data) { }
|
|
1422
|
+
/**
|
|
1423
|
+
* Exchange intergration tokens for all APIs
|
|
1424
|
+
* @param token Core system's refresh token to exchange
|
|
1425
|
+
*/
|
|
1426
|
+
exchangeTokenAll(token) {
|
|
1427
|
+
for (const name in this.apis) {
|
|
1428
|
+
const api = this.apis[name];
|
|
1429
|
+
this.exchangeToken(api[0], token);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* API refresh token
|
|
1434
|
+
* @param api Current API
|
|
1435
|
+
* @param token Refresh token
|
|
1436
|
+
* @returns Result
|
|
1437
|
+
*/
|
|
1438
|
+
async apiRefreshToken(api, token) {
|
|
1439
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1440
|
+
const data = await api.put('Auth/ApiRefreshToken', {
|
|
1441
|
+
token
|
|
1442
|
+
}, {
|
|
1443
|
+
showLoading: false,
|
|
1444
|
+
onError: (error) => {
|
|
1445
|
+
console.error(`CoreApp.${api.name}.apiRefreshToken error`, error);
|
|
1446
|
+
// Prevent further processing
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
if (data == null)
|
|
1451
|
+
return undefined;
|
|
1452
|
+
// Update the access token
|
|
1453
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
1454
|
+
// Return the new refresh token and access token expiration seconds
|
|
1455
|
+
return [data.refreshToken, data.expiresIn];
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Setup tasks
|
|
1459
|
+
*/
|
|
1460
|
+
setupTasks() {
|
|
1461
|
+
shared_1.ExtendUtils.intervalFor(() => {
|
|
1462
|
+
// Exit when not authorized
|
|
1463
|
+
if (!this.authorized)
|
|
1464
|
+
return;
|
|
1465
|
+
// APIs
|
|
1466
|
+
for (const name in this.apis) {
|
|
1467
|
+
// Get the API
|
|
1468
|
+
const api = this.apis[name];
|
|
1469
|
+
// Skip the negative value or when refresh token is not set
|
|
1470
|
+
if (!api[4] || api[2] < 0)
|
|
1471
|
+
continue;
|
|
1472
|
+
// Minus one second
|
|
1473
|
+
api[2] -= 1;
|
|
1474
|
+
// Ready to trigger
|
|
1475
|
+
if (api[2] === 0) {
|
|
1476
|
+
// Refresh token
|
|
1477
|
+
api[3](api[0], api[4]).then((data) => {
|
|
1478
|
+
if (data == null) {
|
|
1479
|
+
// Failed, try it again in 2 seconds
|
|
1480
|
+
api[2] = 2;
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
// Reset the API
|
|
1484
|
+
const [token, seconds] = data;
|
|
1485
|
+
this.updateApi(api, token, seconds);
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
for (let t = this.tasks.length - 1; t >= 0; t--) {
|
|
1491
|
+
// Get the task
|
|
1492
|
+
const task = this.tasks[t];
|
|
1493
|
+
// Minus one second
|
|
1494
|
+
task[2] -= 1;
|
|
1495
|
+
// Remove the tasks with negative value with splice
|
|
1496
|
+
if (task[2] < 0) {
|
|
1497
|
+
this.tasks.splice(t, 1);
|
|
1498
|
+
}
|
|
1499
|
+
else if (task[2] === 0) {
|
|
1500
|
+
// Ready to trigger
|
|
1501
|
+
// Reset the task
|
|
1502
|
+
task[2] = task[1];
|
|
1503
|
+
// Trigger the task
|
|
1504
|
+
task[0]().then((result) => {
|
|
1505
|
+
if (result === false) {
|
|
1506
|
+
// Asynchronous task, unsafe to splice the index, flag as pending
|
|
1507
|
+
task[2] = -1;
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}, 1000);
|
|
1345
1513
|
}
|
|
1346
1514
|
/**
|
|
1347
1515
|
* Signout, with userLogout and toLoginPage
|
|
@@ -1366,8 +1534,9 @@ class CoreApp {
|
|
|
1366
1534
|
* @param removeUrl Remove current URL for reuse
|
|
1367
1535
|
*/
|
|
1368
1536
|
toLoginPage(tryLogin, removeUrl) {
|
|
1369
|
-
|
|
1370
|
-
|
|
1537
|
+
// Save the current URL
|
|
1538
|
+
this.cachedUrl = removeUrl ? undefined : globalThis.location.href;
|
|
1539
|
+
const url = `/?tryLogin=${tryLogin ?? false}`;
|
|
1371
1540
|
this.navigate(url);
|
|
1372
1541
|
}
|
|
1373
1542
|
/**
|
|
@@ -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
|