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