@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/lib/cjs/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
|
*/
|
|
@@ -54,7 +55,7 @@ export interface RefreshTokenProps {
|
|
|
54
55
|
/**
|
|
55
56
|
* App fields
|
|
56
57
|
*/
|
|
57
|
-
export declare const appFields: readonly ["headerToken", "serversideDeviceId", "deviceId", "devices", "devicePassphrase"];
|
|
58
|
+
export declare const appFields: readonly ["headerToken", "serversideDeviceId", "deviceId", "devices", "devicePassphrase", "cachedUrl"];
|
|
58
59
|
/**
|
|
59
60
|
* Basic type template
|
|
60
61
|
*/
|
|
@@ -129,6 +130,10 @@ export interface IApp {
|
|
|
129
130
|
* Is debug mode
|
|
130
131
|
*/
|
|
131
132
|
readonly debug: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Cached URL
|
|
135
|
+
*/
|
|
136
|
+
cachedUrl: string | undefined | null;
|
|
132
137
|
/**
|
|
133
138
|
* IP data
|
|
134
139
|
*/
|
|
@@ -155,6 +160,12 @@ export interface IApp {
|
|
|
155
160
|
* @returns Result
|
|
156
161
|
*/
|
|
157
162
|
addRootUrl(url: string): string;
|
|
163
|
+
/**
|
|
164
|
+
* Add scheduled task
|
|
165
|
+
* @param task Task, return false to stop
|
|
166
|
+
* @param interval Interval in milliseconds
|
|
167
|
+
*/
|
|
168
|
+
addTask(task: () => PromiseLike<void | false>, interval: number): void;
|
|
158
169
|
/**
|
|
159
170
|
* Alert result
|
|
160
171
|
* @param result Result message
|
|
@@ -209,6 +220,13 @@ export interface IApp {
|
|
|
209
220
|
* Clear device id
|
|
210
221
|
*/
|
|
211
222
|
clearDeviceId(): void;
|
|
223
|
+
/**
|
|
224
|
+
* Create API client, override to implement custom client creation by name
|
|
225
|
+
* @param name Client name
|
|
226
|
+
* @param item External endpoint item
|
|
227
|
+
* @returns Result
|
|
228
|
+
*/
|
|
229
|
+
createApi(name: string, item: ExternalEndpoint, refresh?: (api: IApi, token: string) => Promise<[string, number] | undefined>): IApi;
|
|
212
230
|
/**
|
|
213
231
|
* Decrypt message
|
|
214
232
|
* @param messageEncrypted Encrypted message
|
|
@@ -252,6 +270,18 @@ export interface IApp {
|
|
|
252
270
|
* @returns Result
|
|
253
271
|
*/
|
|
254
272
|
encryptEnhanced(message: string, passphrase?: string, iterations?: number): string;
|
|
273
|
+
/**
|
|
274
|
+
* Exchange token data
|
|
275
|
+
* @param api API
|
|
276
|
+
* @param token Core system's refresh token to exchange
|
|
277
|
+
* @returns Result
|
|
278
|
+
*/
|
|
279
|
+
exchangeToken(api: IApi, token: string): Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* Exchange intergration tokens for all APIs
|
|
282
|
+
* @param token Core system's refresh token to exchange
|
|
283
|
+
*/
|
|
284
|
+
exchangeTokenAll(token: string): void;
|
|
255
285
|
/**
|
|
256
286
|
* Format date to string
|
|
257
287
|
* @param input Input date
|
|
@@ -508,6 +538,13 @@ export interface IApp {
|
|
|
508
538
|
* @param showLoading Show loading bar or not
|
|
509
539
|
*/
|
|
510
540
|
tryLogin(showLoading?: boolean): Promise<boolean>;
|
|
541
|
+
/**
|
|
542
|
+
* Update API token and expires
|
|
543
|
+
* @param name Api name
|
|
544
|
+
* @param token Refresh token
|
|
545
|
+
* @param seconds Access token expires in seconds
|
|
546
|
+
*/
|
|
547
|
+
updateApi(name: string, token: string | undefined, seconds: number): void;
|
|
511
548
|
/**
|
|
512
549
|
* User login
|
|
513
550
|
* @param user User data
|
package/lib/cjs/app/IApp.js
CHANGED
package/lib/cjs/erp/AuthApi.js
CHANGED
|
@@ -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
|
*/
|
|
@@ -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/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';
|
|
@@ -80,6 +80,15 @@ export class CoreApp {
|
|
|
80
80
|
set isReady(value) {
|
|
81
81
|
this._isReady = value;
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Current cached URL
|
|
85
|
+
*/
|
|
86
|
+
get cachedUrl() {
|
|
87
|
+
return this.storage.getData(this.fields.cachedUrl);
|
|
88
|
+
}
|
|
89
|
+
set cachedUrl(value) {
|
|
90
|
+
this.storage.setData(this.fields.cachedUrl, value);
|
|
91
|
+
}
|
|
83
92
|
/**
|
|
84
93
|
* Get persisted fields
|
|
85
94
|
*/
|
|
@@ -112,10 +121,6 @@ export class CoreApp {
|
|
|
112
121
|
* Last called with token refresh
|
|
113
122
|
*/
|
|
114
123
|
this.lastCalled = false;
|
|
115
|
-
/**
|
|
116
|
-
* Token refresh count down seed
|
|
117
|
-
*/
|
|
118
|
-
this.refreshCountdownSeed = 0;
|
|
119
124
|
/**
|
|
120
125
|
* Init call Api URL
|
|
121
126
|
*/
|
|
@@ -124,6 +129,8 @@ export class CoreApp {
|
|
|
124
129
|
* Passphrase for encryption
|
|
125
130
|
*/
|
|
126
131
|
this.passphrase = '';
|
|
132
|
+
this.apis = {};
|
|
133
|
+
this.tasks = [];
|
|
127
134
|
if (settings?.regions?.length === 0) {
|
|
128
135
|
throw new Error('No regions defined');
|
|
129
136
|
}
|
|
@@ -133,7 +140,30 @@ export class CoreApp {
|
|
|
133
140
|
throw new Error('No default region defined');
|
|
134
141
|
}
|
|
135
142
|
this.defaultRegion = region;
|
|
136
|
-
|
|
143
|
+
const refresh = async (api, token) => {
|
|
144
|
+
if (this.lastCalled) {
|
|
145
|
+
// Call refreshToken to update access token
|
|
146
|
+
await this.refreshToken();
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Popup countdown for user action
|
|
150
|
+
this.freshCountdownUI();
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
};
|
|
154
|
+
if (api) {
|
|
155
|
+
// Base URL of the API
|
|
156
|
+
api.baseUrl = this.settings.endpoint;
|
|
157
|
+
api.name = 'system';
|
|
158
|
+
this.setApi(api, refresh);
|
|
159
|
+
this.api = api;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.api = this.createApi('system', {
|
|
163
|
+
endpoint: settings.endpoint,
|
|
164
|
+
webUrl: settings.webUrl
|
|
165
|
+
}, refresh);
|
|
166
|
+
}
|
|
137
167
|
this.notifier = notifier;
|
|
138
168
|
this.storage = storage;
|
|
139
169
|
this.name = name;
|
|
@@ -142,7 +172,6 @@ export class CoreApp {
|
|
|
142
172
|
this.fields = appFields.reduce((a, v) => ({ ...a, [v]: 'smarterp-' + v + '-' + name }), {});
|
|
143
173
|
// Device id
|
|
144
174
|
this._deviceId = storage.getData(this.fields.deviceId, '');
|
|
145
|
-
this.setApi(api);
|
|
146
175
|
const { currentCulture, currentRegion } = settings;
|
|
147
176
|
// Load resources
|
|
148
177
|
Promise.all([loadCrypto(), this.changeCulture(currentCulture)]).then(([cj, _resources]) => {
|
|
@@ -260,17 +289,56 @@ export class CoreApp {
|
|
|
260
289
|
return;
|
|
261
290
|
this.storage.copyTo(this.persistedFields);
|
|
262
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Add scheduled task
|
|
294
|
+
* @param task Task, return false to stop
|
|
295
|
+
* @param seconds Interval in seconds
|
|
296
|
+
*/
|
|
297
|
+
addTask(task, seconds) {
|
|
298
|
+
this.tasks.push([task, seconds, seconds]);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Create API client, override to implement custom client creation by name
|
|
302
|
+
* @param name Client name
|
|
303
|
+
* @param item External endpoint item
|
|
304
|
+
* @returns Result
|
|
305
|
+
*/
|
|
306
|
+
createApi(name, item, refresh) {
|
|
307
|
+
if (this.apis[name] != null) {
|
|
308
|
+
throw new Error(`API ${name} already exists`);
|
|
309
|
+
}
|
|
310
|
+
const api = createClient();
|
|
311
|
+
api.name = name;
|
|
312
|
+
api.baseUrl = item.endpoint;
|
|
313
|
+
this.setApi(api, refresh);
|
|
314
|
+
return api;
|
|
315
|
+
}
|
|
316
|
+
updateApi(nameOrData, token, seconds) {
|
|
317
|
+
const api = typeof nameOrData === 'string' ? this.apis[nameOrData] : nameOrData;
|
|
318
|
+
if (api == null)
|
|
319
|
+
return;
|
|
320
|
+
// Consider the API call delay
|
|
321
|
+
if (seconds > 0) {
|
|
322
|
+
seconds -= 30;
|
|
323
|
+
if (seconds < 10)
|
|
324
|
+
seconds = 10;
|
|
325
|
+
}
|
|
326
|
+
api[1] = seconds;
|
|
327
|
+
api[2] = seconds;
|
|
328
|
+
api[4] = token;
|
|
329
|
+
}
|
|
263
330
|
/**
|
|
264
331
|
* Setup Api
|
|
265
332
|
* @param api Api
|
|
266
333
|
*/
|
|
267
|
-
setApi(api) {
|
|
268
|
-
// Base URL of the API
|
|
269
|
-
api.baseUrl = this.settings.endpoint;
|
|
334
|
+
setApi(api, refresh) {
|
|
270
335
|
// onRequest, show loading or not, rewrite the property to override default action
|
|
271
336
|
this.setApiLoading(api);
|
|
272
337
|
// Global API error handler
|
|
273
338
|
this.setApiErrorHandler(api);
|
|
339
|
+
// Setup API countdown
|
|
340
|
+
refresh ?? (refresh = this.apiRefreshToken.bind(this));
|
|
341
|
+
this.apis[api.name] = [api, -1, -1, refresh];
|
|
274
342
|
}
|
|
275
343
|
/**
|
|
276
344
|
* Setup Api error handler
|
|
@@ -281,7 +349,7 @@ export class CoreApp {
|
|
|
281
349
|
api.onError = (error) => {
|
|
282
350
|
// Debug
|
|
283
351
|
if (this.debug) {
|
|
284
|
-
console.debug(
|
|
352
|
+
console.debug(`CoreApp.${this.name}.setApiErrorHandler`, api, error, handlerFor401);
|
|
285
353
|
}
|
|
286
354
|
// Error code
|
|
287
355
|
const status = error.response
|
|
@@ -303,12 +371,12 @@ export class CoreApp {
|
|
|
303
371
|
(error.message === 'Network Error' ||
|
|
304
372
|
error.message === 'Failed to fetch')) {
|
|
305
373
|
// Network error
|
|
306
|
-
this.notifier.alert(this.get('networkError'));
|
|
374
|
+
this.notifier.alert(this.get('networkError') + ` [${this.name}]`);
|
|
307
375
|
return;
|
|
308
376
|
}
|
|
309
377
|
else {
|
|
310
378
|
// Log
|
|
311
|
-
console.error(
|
|
379
|
+
console.error(`${this.name} API error`, error);
|
|
312
380
|
}
|
|
313
381
|
// Report the error
|
|
314
382
|
this.notifier.alert(this.formatError(error));
|
|
@@ -323,7 +391,7 @@ export class CoreApp {
|
|
|
323
391
|
api.onRequest = (data) => {
|
|
324
392
|
// Debug
|
|
325
393
|
if (this.debug) {
|
|
326
|
-
console.debug(
|
|
394
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onRequest`, api, data, this.notifier.loadingCount);
|
|
327
395
|
}
|
|
328
396
|
if (data.showLoading == null || data.showLoading) {
|
|
329
397
|
this.notifier.showLoading();
|
|
@@ -333,13 +401,13 @@ export class CoreApp {
|
|
|
333
401
|
api.onComplete = (data) => {
|
|
334
402
|
// Debug
|
|
335
403
|
if (this.debug) {
|
|
336
|
-
console.debug(
|
|
404
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onComplete`, api, data, this.notifier.loadingCount, this.lastCalled);
|
|
337
405
|
}
|
|
338
406
|
if (data.showLoading == null || data.showLoading) {
|
|
339
407
|
this.notifier.hideLoading();
|
|
340
408
|
// Debug
|
|
341
409
|
if (this.debug) {
|
|
342
|
-
console.debug(
|
|
410
|
+
console.debug(`CoreApp.${this.name}.setApiLoading.onComplete.showLoading`, api, this.notifier.loadingCount);
|
|
343
411
|
}
|
|
344
412
|
}
|
|
345
413
|
this.lastCalled = true;
|
|
@@ -550,11 +618,15 @@ export class CoreApp {
|
|
|
550
618
|
// Reset tryLogin state
|
|
551
619
|
this._isTryingLogin = false;
|
|
552
620
|
// Token countdown
|
|
553
|
-
if (this.authorized)
|
|
554
|
-
this.
|
|
621
|
+
if (this.authorized) {
|
|
622
|
+
this.lastCalled = false;
|
|
623
|
+
if (refreshToken) {
|
|
624
|
+
this.updateApi(this.api.name, refreshToken, this.userData.seconds);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
555
627
|
else {
|
|
556
628
|
this.cachedRefreshToken = undefined;
|
|
557
|
-
this.
|
|
629
|
+
this.updateApi(this.api.name, undefined, -1);
|
|
558
630
|
}
|
|
559
631
|
// Host notice
|
|
560
632
|
BridgeUtils.host?.userAuthorization(this.authorized);
|
|
@@ -1286,40 +1358,6 @@ export class CoreApp {
|
|
|
1286
1358
|
this.lastWarning?.dismiss();
|
|
1287
1359
|
this.notifier.hideLoading(true);
|
|
1288
1360
|
}
|
|
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
1361
|
/**
|
|
1324
1362
|
* Refresh token
|
|
1325
1363
|
* @param props Props
|
|
@@ -1333,12 +1371,142 @@ export class CoreApp {
|
|
|
1333
1371
|
* Setup callback
|
|
1334
1372
|
*/
|
|
1335
1373
|
setup() {
|
|
1374
|
+
// Done already
|
|
1375
|
+
if (this.isReady)
|
|
1376
|
+
return;
|
|
1336
1377
|
// Ready
|
|
1337
1378
|
this.isReady = true;
|
|
1338
1379
|
// Restore
|
|
1339
1380
|
this.restore();
|
|
1340
1381
|
// Pending actions
|
|
1341
1382
|
this.pendings.forEach((p) => p());
|
|
1383
|
+
// Setup scheduled tasks
|
|
1384
|
+
this.setupTasks();
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Exchange token data
|
|
1388
|
+
* @param api API
|
|
1389
|
+
* @param token Core system's refresh token to exchange
|
|
1390
|
+
* @returns Result
|
|
1391
|
+
*/
|
|
1392
|
+
async exchangeToken(api, token) {
|
|
1393
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1394
|
+
const data = await api.put('Auth/ExchangeToken', {
|
|
1395
|
+
token
|
|
1396
|
+
}, {
|
|
1397
|
+
showLoading: false,
|
|
1398
|
+
onError: (error) => {
|
|
1399
|
+
console.error(`CoreApp.${api.name}.ExchangeToken error`, error);
|
|
1400
|
+
// Prevent further processing
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
if (data) {
|
|
1405
|
+
// Update the access token
|
|
1406
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
1407
|
+
// Update the API
|
|
1408
|
+
this.updateApi(api.name, data.refreshToken, data.expiresIn);
|
|
1409
|
+
// Update notice
|
|
1410
|
+
this.exchangeTokenUpdate(api, data);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Exchange token update, override to get the new token
|
|
1415
|
+
* @param api API
|
|
1416
|
+
* @param data API refresh token data
|
|
1417
|
+
*/
|
|
1418
|
+
exchangeTokenUpdate(api, data) { }
|
|
1419
|
+
/**
|
|
1420
|
+
* Exchange intergration tokens for all APIs
|
|
1421
|
+
* @param token Core system's refresh token to exchange
|
|
1422
|
+
*/
|
|
1423
|
+
exchangeTokenAll(token) {
|
|
1424
|
+
for (const name in this.apis) {
|
|
1425
|
+
const api = this.apis[name];
|
|
1426
|
+
this.exchangeToken(api[0], token);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* API refresh token
|
|
1431
|
+
* @param api Current API
|
|
1432
|
+
* @param token Refresh token
|
|
1433
|
+
* @returns Result
|
|
1434
|
+
*/
|
|
1435
|
+
async apiRefreshToken(api, token) {
|
|
1436
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1437
|
+
const data = await api.put('Auth/ApiRefreshToken', {
|
|
1438
|
+
token
|
|
1439
|
+
}, {
|
|
1440
|
+
showLoading: false,
|
|
1441
|
+
onError: (error) => {
|
|
1442
|
+
console.error(`CoreApp.${api.name}.apiRefreshToken error`, error);
|
|
1443
|
+
// Prevent further processing
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
if (data == null)
|
|
1448
|
+
return undefined;
|
|
1449
|
+
// Update the access token
|
|
1450
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
1451
|
+
// Return the new refresh token and access token expiration seconds
|
|
1452
|
+
return [data.refreshToken, data.expiresIn];
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Setup tasks
|
|
1456
|
+
*/
|
|
1457
|
+
setupTasks() {
|
|
1458
|
+
ExtendUtils.intervalFor(() => {
|
|
1459
|
+
// Exit when not authorized
|
|
1460
|
+
if (!this.authorized)
|
|
1461
|
+
return;
|
|
1462
|
+
// APIs
|
|
1463
|
+
for (const name in this.apis) {
|
|
1464
|
+
// Get the API
|
|
1465
|
+
const api = this.apis[name];
|
|
1466
|
+
// Skip the negative value or when refresh token is not set
|
|
1467
|
+
if (!api[4] || api[2] < 0)
|
|
1468
|
+
continue;
|
|
1469
|
+
// Minus one second
|
|
1470
|
+
api[2] -= 1;
|
|
1471
|
+
// Ready to trigger
|
|
1472
|
+
if (api[2] === 0) {
|
|
1473
|
+
// Refresh token
|
|
1474
|
+
api[3](api[0], api[4]).then((data) => {
|
|
1475
|
+
if (data == null) {
|
|
1476
|
+
// Failed, try it again in 2 seconds
|
|
1477
|
+
api[2] = 2;
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
// Reset the API
|
|
1481
|
+
const [token, seconds] = data;
|
|
1482
|
+
this.updateApi(api, token, seconds);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
for (let t = this.tasks.length - 1; t >= 0; t--) {
|
|
1488
|
+
// Get the task
|
|
1489
|
+
const task = this.tasks[t];
|
|
1490
|
+
// Minus one second
|
|
1491
|
+
task[2] -= 1;
|
|
1492
|
+
// Remove the tasks with negative value with splice
|
|
1493
|
+
if (task[2] < 0) {
|
|
1494
|
+
this.tasks.splice(t, 1);
|
|
1495
|
+
}
|
|
1496
|
+
else if (task[2] === 0) {
|
|
1497
|
+
// Ready to trigger
|
|
1498
|
+
// Reset the task
|
|
1499
|
+
task[2] = task[1];
|
|
1500
|
+
// Trigger the task
|
|
1501
|
+
task[0]().then((result) => {
|
|
1502
|
+
if (result === false) {
|
|
1503
|
+
// Asynchronous task, unsafe to splice the index, flag as pending
|
|
1504
|
+
task[2] = -1;
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}, 1000);
|
|
1342
1510
|
}
|
|
1343
1511
|
/**
|
|
1344
1512
|
* Signout, with userLogout and toLoginPage
|
|
@@ -1363,8 +1531,9 @@ export class CoreApp {
|
|
|
1363
1531
|
* @param removeUrl Remove current URL for reuse
|
|
1364
1532
|
*/
|
|
1365
1533
|
toLoginPage(tryLogin, removeUrl) {
|
|
1366
|
-
|
|
1367
|
-
|
|
1534
|
+
// Save the current URL
|
|
1535
|
+
this.cachedUrl = removeUrl ? undefined : globalThis.location.href;
|
|
1536
|
+
const url = `/?tryLogin=${tryLogin ?? false}`;
|
|
1368
1537
|
this.navigate(url);
|
|
1369
1538
|
}
|
|
1370
1539
|
/**
|