@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.
@@ -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
@@ -9,5 +9,6 @@ exports.appFields = [
9
9
  'serversideDeviceId',
10
10
  'deviceId',
11
11
  'devices',
12
- 'devicePassphrase'
12
+ 'devicePassphrase',
13
+ 'cachedUrl'
13
14
  ];
@@ -54,7 +54,7 @@ class AuthApi extends BaseApi_1.BaseApi {
54
54
  const url = await this.getLogInUrl();
55
55
  if (url == null)
56
56
  return;
57
- window.location.replace(url);
57
+ globalThis.location.replace(url);
58
58
  }
59
59
  /**
60
60
  * Reset password
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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 {};
@@ -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
- this.api = api;
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('CoreApp.setApiErrorHandler', api, error, handlerFor401);
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('API error', 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('CoreApp.setApiLoading.onRequest', api, data, this.notifier.loadingCount);
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('CoreApp.setApiLoading.onComplete', api, data, this.notifier.loadingCount, this.lastCalled);
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('CoreApp.setApiLoading.onComplete.showLoading', api, this.notifier.loadingCount);
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.refreshCountdown(this.userData.seconds);
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.refreshCountdownClear();
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
- const url = `/?tryLogin=${tryLogin ?? false}` +
1367
- (removeUrl ? '' : '&url=' + encodeURIComponent(location.href));
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
  /**