@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/src/app/CoreApp.ts
CHANGED
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
NotificationMessageType,
|
|
8
8
|
NotificationReturn
|
|
9
9
|
} from '@etsoo/notificationbase';
|
|
10
|
-
import { ApiDataError, IApi, IPData } from '@etsoo/restclient';
|
|
10
|
+
import { ApiDataError, createClient, IApi, IPData } from '@etsoo/restclient';
|
|
11
11
|
import {
|
|
12
12
|
DataTypes,
|
|
13
13
|
DateUtils,
|
|
14
14
|
DomUtils,
|
|
15
15
|
ErrorData,
|
|
16
16
|
ErrorType,
|
|
17
|
+
ExtendUtils,
|
|
17
18
|
IActionResult,
|
|
18
19
|
IStorage,
|
|
19
20
|
ListType,
|
|
@@ -43,12 +44,23 @@ import {
|
|
|
43
44
|
import { UserRole } from './UserRole';
|
|
44
45
|
import type CryptoJS from 'crypto-js';
|
|
45
46
|
import { Currency } from '../business/Currency';
|
|
47
|
+
import { ExternalEndpoint } from './ExternalSettings';
|
|
48
|
+
import { ApiRefreshTokenDto } from '../erp/dto/ApiRefreshTokenDto';
|
|
46
49
|
|
|
47
50
|
type CJType = typeof CryptoJS;
|
|
48
51
|
let CJ: CJType;
|
|
49
52
|
|
|
50
53
|
const loadCrypto = () => import('crypto-js');
|
|
51
54
|
|
|
55
|
+
// API refresh token function interface
|
|
56
|
+
type ApiRefreshTokenFunction = (
|
|
57
|
+
api: IApi,
|
|
58
|
+
token: string
|
|
59
|
+
) => Promise<[string, number] | undefined>;
|
|
60
|
+
|
|
61
|
+
// API task data
|
|
62
|
+
type ApiTaskData = [IApi, number, number, ApiRefreshTokenFunction, string?];
|
|
63
|
+
|
|
52
64
|
/**
|
|
53
65
|
* Core application interface
|
|
54
66
|
*/
|
|
@@ -222,6 +234,16 @@ export abstract class CoreApp<
|
|
|
222
234
|
this._isReady = value;
|
|
223
235
|
}
|
|
224
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Current cached URL
|
|
239
|
+
*/
|
|
240
|
+
get cachedUrl() {
|
|
241
|
+
return this.storage.getData(this.fields.cachedUrl);
|
|
242
|
+
}
|
|
243
|
+
set cachedUrl(value: string | undefined | null) {
|
|
244
|
+
this.storage.setData(this.fields.cachedUrl, value);
|
|
245
|
+
}
|
|
246
|
+
|
|
225
247
|
private _isTryingLogin = false;
|
|
226
248
|
|
|
227
249
|
/**
|
|
@@ -229,11 +251,6 @@ export abstract class CoreApp<
|
|
|
229
251
|
*/
|
|
230
252
|
protected lastCalled = false;
|
|
231
253
|
|
|
232
|
-
/**
|
|
233
|
-
* Token refresh count down seed
|
|
234
|
-
*/
|
|
235
|
-
protected refreshCountdownSeed = 0;
|
|
236
|
-
|
|
237
254
|
/**
|
|
238
255
|
* Init call Api URL
|
|
239
256
|
*/
|
|
@@ -246,6 +263,10 @@ export abstract class CoreApp<
|
|
|
246
263
|
|
|
247
264
|
private cachedRefreshToken?: string;
|
|
248
265
|
|
|
266
|
+
private apis: Record<string, ApiTaskData> = {};
|
|
267
|
+
|
|
268
|
+
private tasks: [() => PromiseLike<void | false>, number, number][] = [];
|
|
269
|
+
|
|
249
270
|
/**
|
|
250
271
|
* Get persisted fields
|
|
251
272
|
*/
|
|
@@ -269,7 +290,7 @@ export abstract class CoreApp<
|
|
|
269
290
|
*/
|
|
270
291
|
protected constructor(
|
|
271
292
|
settings: S,
|
|
272
|
-
api: IApi,
|
|
293
|
+
api: IApi | undefined | null,
|
|
273
294
|
notifier: INotifier<N, C>,
|
|
274
295
|
storage: IStorage,
|
|
275
296
|
name: string,
|
|
@@ -286,7 +307,34 @@ export abstract class CoreApp<
|
|
|
286
307
|
}
|
|
287
308
|
this.defaultRegion = region;
|
|
288
309
|
|
|
289
|
-
|
|
310
|
+
const refresh: ApiRefreshTokenFunction = async (api, token) => {
|
|
311
|
+
if (this.lastCalled) {
|
|
312
|
+
// Call refreshToken to update access token
|
|
313
|
+
await this.refreshToken();
|
|
314
|
+
} else {
|
|
315
|
+
// Popup countdown for user action
|
|
316
|
+
this.freshCountdownUI();
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
if (api) {
|
|
322
|
+
// Base URL of the API
|
|
323
|
+
api.baseUrl = this.settings.endpoint;
|
|
324
|
+
api.name = 'system';
|
|
325
|
+
this.setApi(api, refresh);
|
|
326
|
+
this.api = api;
|
|
327
|
+
} else {
|
|
328
|
+
this.api = this.createApi(
|
|
329
|
+
'system',
|
|
330
|
+
{
|
|
331
|
+
endpoint: settings.endpoint,
|
|
332
|
+
webUrl: settings.webUrl
|
|
333
|
+
},
|
|
334
|
+
refresh
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
290
338
|
this.notifier = notifier;
|
|
291
339
|
this.storage = storage;
|
|
292
340
|
this.name = name;
|
|
@@ -301,8 +349,6 @@ export abstract class CoreApp<
|
|
|
301
349
|
// Device id
|
|
302
350
|
this._deviceId = storage.getData(this.fields.deviceId, '');
|
|
303
351
|
|
|
304
|
-
this.setApi(api);
|
|
305
|
-
|
|
306
352
|
const { currentCulture, currentRegion } = settings;
|
|
307
353
|
|
|
308
354
|
// Load resources
|
|
@@ -464,19 +510,86 @@ export abstract class CoreApp<
|
|
|
464
510
|
this.storage.copyTo(this.persistedFields);
|
|
465
511
|
}
|
|
466
512
|
|
|
513
|
+
/**
|
|
514
|
+
* Add scheduled task
|
|
515
|
+
* @param task Task, return false to stop
|
|
516
|
+
* @param seconds Interval in seconds
|
|
517
|
+
*/
|
|
518
|
+
addTask(task: () => PromiseLike<void | false>, seconds: number) {
|
|
519
|
+
this.tasks.push([task, seconds, seconds]);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Create API client, override to implement custom client creation by name
|
|
524
|
+
* @param name Client name
|
|
525
|
+
* @param item External endpoint item
|
|
526
|
+
* @returns Result
|
|
527
|
+
*/
|
|
528
|
+
createApi(
|
|
529
|
+
name: string,
|
|
530
|
+
item: ExternalEndpoint,
|
|
531
|
+
refresh?: (
|
|
532
|
+
api: IApi,
|
|
533
|
+
token: string
|
|
534
|
+
) => Promise<[string, number] | undefined>
|
|
535
|
+
) {
|
|
536
|
+
if (this.apis[name] != null) {
|
|
537
|
+
throw new Error(`API ${name} already exists`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const api = createClient();
|
|
541
|
+
api.name = name;
|
|
542
|
+
api.baseUrl = item.endpoint;
|
|
543
|
+
this.setApi(api, refresh);
|
|
544
|
+
return api;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Update API token and expires
|
|
549
|
+
* @param name Api name
|
|
550
|
+
* @param token Refresh token
|
|
551
|
+
* @param seconds Access token expires in seconds
|
|
552
|
+
*/
|
|
553
|
+
updateApi(name: string, token: string | undefined, seconds: number): void;
|
|
554
|
+
updateApi(
|
|
555
|
+
data: ApiTaskData,
|
|
556
|
+
token: string | undefined,
|
|
557
|
+
seconds: number
|
|
558
|
+
): void;
|
|
559
|
+
updateApi(
|
|
560
|
+
nameOrData: string | ApiTaskData,
|
|
561
|
+
token: string | undefined,
|
|
562
|
+
seconds: number
|
|
563
|
+
) {
|
|
564
|
+
const api =
|
|
565
|
+
typeof nameOrData === 'string' ? this.apis[nameOrData] : nameOrData;
|
|
566
|
+
if (api == null) return;
|
|
567
|
+
|
|
568
|
+
// Consider the API call delay
|
|
569
|
+
if (seconds > 0) {
|
|
570
|
+
seconds -= 30;
|
|
571
|
+
if (seconds < 10) seconds = 10;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
api[1] = seconds;
|
|
575
|
+
api[2] = seconds;
|
|
576
|
+
api[4] = token;
|
|
577
|
+
}
|
|
578
|
+
|
|
467
579
|
/**
|
|
468
580
|
* Setup Api
|
|
469
581
|
* @param api Api
|
|
470
582
|
*/
|
|
471
|
-
protected setApi(api: IApi) {
|
|
472
|
-
// Base URL of the API
|
|
473
|
-
api.baseUrl = this.settings.endpoint;
|
|
474
|
-
|
|
583
|
+
protected setApi(api: IApi, refresh?: ApiRefreshTokenFunction) {
|
|
475
584
|
// onRequest, show loading or not, rewrite the property to override default action
|
|
476
585
|
this.setApiLoading(api);
|
|
477
586
|
|
|
478
587
|
// Global API error handler
|
|
479
588
|
this.setApiErrorHandler(api);
|
|
589
|
+
|
|
590
|
+
// Setup API countdown
|
|
591
|
+
refresh ??= this.apiRefreshToken.bind(this);
|
|
592
|
+
this.apis[api.name] = [api, -1, -1, refresh];
|
|
480
593
|
}
|
|
481
594
|
|
|
482
595
|
/**
|
|
@@ -484,7 +597,7 @@ export abstract class CoreApp<
|
|
|
484
597
|
* @param api Api
|
|
485
598
|
* @param handlerFor401 Handler for 401 error
|
|
486
599
|
*/
|
|
487
|
-
|
|
600
|
+
setApiErrorHandler(
|
|
488
601
|
api: IApi,
|
|
489
602
|
handlerFor401?: boolean | (() => Promise<void>)
|
|
490
603
|
) {
|
|
@@ -492,7 +605,7 @@ export abstract class CoreApp<
|
|
|
492
605
|
// Debug
|
|
493
606
|
if (this.debug) {
|
|
494
607
|
console.debug(
|
|
495
|
-
|
|
608
|
+
`CoreApp.${this.name}.setApiErrorHandler`,
|
|
496
609
|
api,
|
|
497
610
|
error,
|
|
498
611
|
handlerFor401
|
|
@@ -519,11 +632,13 @@ export abstract class CoreApp<
|
|
|
519
632
|
error.message === 'Failed to fetch')
|
|
520
633
|
) {
|
|
521
634
|
// Network error
|
|
522
|
-
this.notifier.alert(
|
|
635
|
+
this.notifier.alert(
|
|
636
|
+
this.get('networkError') + ` [${this.name}]`
|
|
637
|
+
);
|
|
523
638
|
return;
|
|
524
639
|
} else {
|
|
525
640
|
// Log
|
|
526
|
-
console.error(
|
|
641
|
+
console.error(`${this.name} API error`, error);
|
|
527
642
|
}
|
|
528
643
|
|
|
529
644
|
// Report the error
|
|
@@ -535,13 +650,13 @@ export abstract class CoreApp<
|
|
|
535
650
|
* Setup Api loading
|
|
536
651
|
* @param api Api
|
|
537
652
|
*/
|
|
538
|
-
|
|
653
|
+
setApiLoading(api: IApi) {
|
|
539
654
|
// onRequest, show loading or not, rewrite the property to override default action
|
|
540
655
|
api.onRequest = (data) => {
|
|
541
656
|
// Debug
|
|
542
657
|
if (this.debug) {
|
|
543
658
|
console.debug(
|
|
544
|
-
|
|
659
|
+
`CoreApp.${this.name}.setApiLoading.onRequest`,
|
|
545
660
|
api,
|
|
546
661
|
data,
|
|
547
662
|
this.notifier.loadingCount
|
|
@@ -558,7 +673,7 @@ export abstract class CoreApp<
|
|
|
558
673
|
// Debug
|
|
559
674
|
if (this.debug) {
|
|
560
675
|
console.debug(
|
|
561
|
-
|
|
676
|
+
`CoreApp.${this.name}.setApiLoading.onComplete`,
|
|
562
677
|
api,
|
|
563
678
|
data,
|
|
564
679
|
this.notifier.loadingCount,
|
|
@@ -572,7 +687,7 @@ export abstract class CoreApp<
|
|
|
572
687
|
// Debug
|
|
573
688
|
if (this.debug) {
|
|
574
689
|
console.debug(
|
|
575
|
-
|
|
690
|
+
`CoreApp.${this.name}.setApiLoading.onComplete.showLoading`,
|
|
576
691
|
api,
|
|
577
692
|
this.notifier.loadingCount
|
|
578
693
|
);
|
|
@@ -587,7 +702,7 @@ export abstract class CoreApp<
|
|
|
587
702
|
* @param action Custom action
|
|
588
703
|
* @param preventDefault Is prevent default action
|
|
589
704
|
*/
|
|
590
|
-
|
|
705
|
+
setupLogging(
|
|
591
706
|
action?: (data: ErrorData) => void | Promise<void>,
|
|
592
707
|
preventDefault?: ((type: ErrorType) => boolean) | boolean
|
|
593
708
|
) {
|
|
@@ -869,10 +984,18 @@ export abstract class CoreApp<
|
|
|
869
984
|
this._isTryingLogin = false;
|
|
870
985
|
|
|
871
986
|
// Token countdown
|
|
872
|
-
if (this.authorized)
|
|
873
|
-
|
|
987
|
+
if (this.authorized) {
|
|
988
|
+
this.lastCalled = false;
|
|
989
|
+
if (refreshToken) {
|
|
990
|
+
this.updateApi(
|
|
991
|
+
this.api.name,
|
|
992
|
+
refreshToken,
|
|
993
|
+
this.userData!.seconds
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
} else {
|
|
874
997
|
this.cachedRefreshToken = undefined;
|
|
875
|
-
this.
|
|
998
|
+
this.updateApi(this.api.name, undefined, -1);
|
|
876
999
|
}
|
|
877
1000
|
|
|
878
1001
|
// Host notice
|
|
@@ -1766,43 +1889,6 @@ export abstract class CoreApp<
|
|
|
1766
1889
|
this.notifier.hideLoading(true);
|
|
1767
1890
|
}
|
|
1768
1891
|
|
|
1769
|
-
/**
|
|
1770
|
-
* Refresh countdown
|
|
1771
|
-
* @param seconds Seconds
|
|
1772
|
-
*/
|
|
1773
|
-
protected refreshCountdown(seconds: number) {
|
|
1774
|
-
// Make sure is big than 60 seconds
|
|
1775
|
-
// Take action 60 seconds before expiry
|
|
1776
|
-
seconds -= 60;
|
|
1777
|
-
if (seconds <= 0) return;
|
|
1778
|
-
|
|
1779
|
-
// Clear the current timeout seed
|
|
1780
|
-
this.refreshCountdownClear();
|
|
1781
|
-
|
|
1782
|
-
// Reset last call flag
|
|
1783
|
-
// Any success call will update it to true
|
|
1784
|
-
// So first time after login will be always silent
|
|
1785
|
-
this.lastCalled = false;
|
|
1786
|
-
|
|
1787
|
-
this.refreshCountdownSeed = window.setTimeout(() => {
|
|
1788
|
-
if (this.lastCalled) {
|
|
1789
|
-
// Call refreshToken to update access token
|
|
1790
|
-
this.refreshToken();
|
|
1791
|
-
} else {
|
|
1792
|
-
// Popup countdown for user action
|
|
1793
|
-
this.freshCountdownUI();
|
|
1794
|
-
}
|
|
1795
|
-
}, 1000 * seconds);
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
protected refreshCountdownClear() {
|
|
1799
|
-
// Clear the current timeout seed
|
|
1800
|
-
if (this.refreshCountdownSeed > 0) {
|
|
1801
|
-
window.clearTimeout(this.refreshCountdownSeed);
|
|
1802
|
-
this.refreshCountdownSeed = 0;
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
1892
|
/**
|
|
1807
1893
|
* Fresh countdown UI
|
|
1808
1894
|
* @param callback Callback
|
|
@@ -1822,6 +1908,9 @@ export abstract class CoreApp<
|
|
|
1822
1908
|
* Setup callback
|
|
1823
1909
|
*/
|
|
1824
1910
|
setup() {
|
|
1911
|
+
// Done already
|
|
1912
|
+
if (this.isReady) return;
|
|
1913
|
+
|
|
1825
1914
|
// Ready
|
|
1826
1915
|
this.isReady = true;
|
|
1827
1916
|
|
|
@@ -1830,6 +1919,167 @@ export abstract class CoreApp<
|
|
|
1830
1919
|
|
|
1831
1920
|
// Pending actions
|
|
1832
1921
|
this.pendings.forEach((p) => p());
|
|
1922
|
+
|
|
1923
|
+
// Setup scheduled tasks
|
|
1924
|
+
this.setupTasks();
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
/**
|
|
1928
|
+
* Exchange token data
|
|
1929
|
+
* @param api API
|
|
1930
|
+
* @param token Core system's refresh token to exchange
|
|
1931
|
+
* @returns Result
|
|
1932
|
+
*/
|
|
1933
|
+
async exchangeToken(api: IApi, token: string) {
|
|
1934
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1935
|
+
const data = await api.put<ApiRefreshTokenDto>(
|
|
1936
|
+
'Auth/ExchangeToken',
|
|
1937
|
+
{
|
|
1938
|
+
token
|
|
1939
|
+
},
|
|
1940
|
+
{
|
|
1941
|
+
showLoading: false,
|
|
1942
|
+
onError: (error) => {
|
|
1943
|
+
console.error(
|
|
1944
|
+
`CoreApp.${api.name}.ExchangeToken error`,
|
|
1945
|
+
error
|
|
1946
|
+
);
|
|
1947
|
+
|
|
1948
|
+
// Prevent further processing
|
|
1949
|
+
return false;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
);
|
|
1953
|
+
|
|
1954
|
+
if (data) {
|
|
1955
|
+
// Update the access token
|
|
1956
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
1957
|
+
|
|
1958
|
+
// Update the API
|
|
1959
|
+
this.updateApi(api.name, data.refreshToken, data.expiresIn);
|
|
1960
|
+
|
|
1961
|
+
// Update notice
|
|
1962
|
+
this.exchangeTokenUpdate(api, data);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
/**
|
|
1967
|
+
* Exchange token update, override to get the new token
|
|
1968
|
+
* @param api API
|
|
1969
|
+
* @param data API refresh token data
|
|
1970
|
+
*/
|
|
1971
|
+
protected exchangeTokenUpdate(api: IApi, data: ApiRefreshTokenDto) {}
|
|
1972
|
+
|
|
1973
|
+
/**
|
|
1974
|
+
* Exchange intergration tokens for all APIs
|
|
1975
|
+
* @param token Core system's refresh token to exchange
|
|
1976
|
+
*/
|
|
1977
|
+
exchangeTokenAll(token: string) {
|
|
1978
|
+
for (const name in this.apis) {
|
|
1979
|
+
const api = this.apis[name];
|
|
1980
|
+
this.exchangeToken(api[0], token);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* API refresh token
|
|
1986
|
+
* @param api Current API
|
|
1987
|
+
* @param token Refresh token
|
|
1988
|
+
* @returns Result
|
|
1989
|
+
*/
|
|
1990
|
+
protected async apiRefreshToken(
|
|
1991
|
+
api: IApi,
|
|
1992
|
+
token: string
|
|
1993
|
+
): Promise<[string, number] | undefined> {
|
|
1994
|
+
// Call the API quietly, no loading bar and no error popup
|
|
1995
|
+
const data = await api.put<ApiRefreshTokenDto>(
|
|
1996
|
+
'Auth/ApiRefreshToken',
|
|
1997
|
+
{
|
|
1998
|
+
token
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
showLoading: false,
|
|
2002
|
+
onError: (error) => {
|
|
2003
|
+
console.error(
|
|
2004
|
+
`CoreApp.${api.name}.apiRefreshToken error`,
|
|
2005
|
+
error
|
|
2006
|
+
);
|
|
2007
|
+
|
|
2008
|
+
// Prevent further processing
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
);
|
|
2013
|
+
|
|
2014
|
+
if (data == null) return undefined;
|
|
2015
|
+
|
|
2016
|
+
// Update the access token
|
|
2017
|
+
api.authorize(data.tokenType, data.accessToken);
|
|
2018
|
+
|
|
2019
|
+
// Return the new refresh token and access token expiration seconds
|
|
2020
|
+
return [data.refreshToken, data.expiresIn];
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* Setup tasks
|
|
2025
|
+
*/
|
|
2026
|
+
protected setupTasks() {
|
|
2027
|
+
ExtendUtils.intervalFor(() => {
|
|
2028
|
+
// Exit when not authorized
|
|
2029
|
+
if (!this.authorized) return;
|
|
2030
|
+
|
|
2031
|
+
// APIs
|
|
2032
|
+
for (const name in this.apis) {
|
|
2033
|
+
// Get the API
|
|
2034
|
+
const api = this.apis[name];
|
|
2035
|
+
|
|
2036
|
+
// Skip the negative value or when refresh token is not set
|
|
2037
|
+
if (!api[4] || api[2] < 0) continue;
|
|
2038
|
+
|
|
2039
|
+
// Minus one second
|
|
2040
|
+
api[2] -= 1;
|
|
2041
|
+
|
|
2042
|
+
// Ready to trigger
|
|
2043
|
+
if (api[2] === 0) {
|
|
2044
|
+
// Refresh token
|
|
2045
|
+
api[3](api[0], api[4]).then((data) => {
|
|
2046
|
+
if (data == null) {
|
|
2047
|
+
// Failed, try it again in 2 seconds
|
|
2048
|
+
api[2] = 2;
|
|
2049
|
+
} else {
|
|
2050
|
+
// Reset the API
|
|
2051
|
+
const [token, seconds] = data;
|
|
2052
|
+
this.updateApi(api, token, seconds);
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
for (let t = this.tasks.length - 1; t >= 0; t--) {
|
|
2059
|
+
// Get the task
|
|
2060
|
+
const task = this.tasks[t];
|
|
2061
|
+
|
|
2062
|
+
// Minus one second
|
|
2063
|
+
task[2] -= 1;
|
|
2064
|
+
|
|
2065
|
+
// Remove the tasks with negative value with splice
|
|
2066
|
+
if (task[2] < 0) {
|
|
2067
|
+
this.tasks.splice(t, 1);
|
|
2068
|
+
} else if (task[2] === 0) {
|
|
2069
|
+
// Ready to trigger
|
|
2070
|
+
// Reset the task
|
|
2071
|
+
task[2] = task[1];
|
|
2072
|
+
|
|
2073
|
+
// Trigger the task
|
|
2074
|
+
task[0]().then((result) => {
|
|
2075
|
+
if (result === false) {
|
|
2076
|
+
// Asynchronous task, unsafe to splice the index, flag as pending
|
|
2077
|
+
task[2] = -1;
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}, 1000);
|
|
1833
2083
|
}
|
|
1834
2084
|
|
|
1835
2085
|
/**
|
|
@@ -1862,9 +2112,10 @@ export abstract class CoreApp<
|
|
|
1862
2112
|
* @param removeUrl Remove current URL for reuse
|
|
1863
2113
|
*/
|
|
1864
2114
|
toLoginPage(tryLogin?: boolean, removeUrl?: boolean) {
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2115
|
+
// Save the current URL
|
|
2116
|
+
this.cachedUrl = removeUrl ? undefined : globalThis.location.href;
|
|
2117
|
+
|
|
2118
|
+
const url = `/?tryLogin=${tryLogin ?? false}`;
|
|
1868
2119
|
|
|
1869
2120
|
this.navigate(url);
|
|
1870
2121
|
}
|
|
@@ -1,36 +1,39 @@
|
|
|
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
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*/
|
|
13
|
-
readonly messageHub?: string;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Core system app root url
|
|
11
|
+
* Web url
|
|
17
12
|
*/
|
|
18
|
-
readonly
|
|
13
|
+
readonly webUrl: string;
|
|
14
|
+
};
|
|
19
15
|
|
|
16
|
+
/**
|
|
17
|
+
* External settings items
|
|
18
|
+
*/
|
|
19
|
+
export interface IExternalSettings extends ExternalEndpoint {
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Message hub endpoint
|
|
22
22
|
*/
|
|
23
|
-
readonly
|
|
23
|
+
readonly messageHub?: string;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* App root url
|
|
27
27
|
*/
|
|
28
|
-
readonly
|
|
28
|
+
readonly homepage: string;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Endpoints to other services
|
|
32
32
|
*/
|
|
33
|
-
readonly
|
|
33
|
+
readonly endpoints?: Record<
|
|
34
|
+
'core' | 'accounting' | 'crm' | 'calandar' | 'task' | string,
|
|
35
|
+
ExternalEndpoint
|
|
36
|
+
>;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
@@ -69,6 +72,8 @@ export namespace ExternalSettings {
|
|
|
69
72
|
const value = settings[key];
|
|
70
73
|
if (typeof value === 'string') {
|
|
71
74
|
settings[key] = value.replace('{hostname}', hostname);
|
|
75
|
+
} else if (typeof value === 'object') {
|
|
76
|
+
format(value, hostname);
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
|