@etsoo/materialui 1.3.88 → 1.3.89

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.
@@ -1,4 +1,4 @@
1
- import { IAppSettings, IUser, RefreshTokenProps, RefreshTokenRQ } from "@etsoo/appscript";
1
+ import { IAppSettings, IUser, RefreshTokenProps } from "@etsoo/appscript";
2
2
  import { IPageData } from "@etsoo/react";
3
3
  import { ReactApp } from "./ReactApp";
4
4
  /**
@@ -27,12 +27,11 @@ export declare abstract class CommonApp<U extends IUser = IUser, P extends IPage
27
27
  * Refresh token
28
28
  * @param props Props
29
29
  */
30
- refreshToken<D extends object = Partial<RefreshTokenRQ>>(props?: RefreshTokenProps<D>): Promise<boolean>;
30
+ refreshToken(props?: RefreshTokenProps): Promise<boolean>;
31
31
  /**
32
32
  * Try login
33
- * @param data Additional data
34
33
  * @param showLoading Show loading bar or not
35
34
  * @returns Result
36
35
  */
37
- tryLogin<D extends object = RefreshTokenRQ>(data?: D, showLoading?: boolean): Promise<boolean>;
36
+ tryLogin(showLoading?: boolean): Promise<boolean>;
38
37
  }
@@ -37,7 +37,7 @@ export class CommonApp extends ReactApp {
37
37
  */
38
38
  async refreshToken(props) {
39
39
  // Destruct
40
- const { callback, data, relogin = false, showLoading = false } = props ?? {};
40
+ const { callback, showLoading = false } = props ?? {};
41
41
  // Token
42
42
  const token = this.getCacheToken();
43
43
  if (token == null || token === "") {
@@ -47,10 +47,7 @@ export class CommonApp extends ReactApp {
47
47
  }
48
48
  // Reqest data
49
49
  const rq = {
50
- deviceId: this.deviceId,
51
- region: this.region,
52
- timezone: this.getTimeZone(),
53
- ...data
50
+ deviceId: this.deviceId
54
51
  };
55
52
  // Payload
56
53
  const payload = {
@@ -89,41 +86,7 @@ export class CommonApp extends ReactApp {
89
86
  if (!result.ok) {
90
87
  // Remove the wrong token
91
88
  this.clearCacheToken();
92
- if (result.type === "TokenExpired" && relogin) {
93
- // Try login
94
- // Dialog to receive password
95
- var labels = this.getLabels("reloginTip", "login");
96
- this.notifier.prompt(labels.reloginTip, async (pwd) => {
97
- if (pwd == null) {
98
- this.toLoginPage();
99
- return;
100
- }
101
- // Set password for the action
102
- rq.pwd = this.encrypt(this.hash(pwd));
103
- // Submit again
104
- const result = await this.api.put("Auth/RefreshToken", rq, payload);
105
- if (result == null)
106
- return;
107
- if (result.ok) {
108
- success(result, (loginResult) => {
109
- if (loginResult === true) {
110
- if (callback)
111
- callback(true);
112
- return;
113
- }
114
- const message = this.formatRefreshTokenResult(loginResult);
115
- if (message)
116
- this.notifier.alert(message);
117
- });
118
- return;
119
- }
120
- // Popup message
121
- this.alertResult(result);
122
- return false;
123
- }, labels.login, { type: "password" });
124
- // Fake truth to avoid reloading
125
- return true;
126
- }
89
+ // Callback
127
90
  if (callback)
128
91
  callback(result);
129
92
  return false;
@@ -132,21 +95,18 @@ export class CommonApp extends ReactApp {
132
95
  }
133
96
  /**
134
97
  * Try login
135
- * @param data Additional data
136
98
  * @param showLoading Show loading bar or not
137
99
  * @returns Result
138
100
  */
139
- async tryLogin(data, showLoading) {
101
+ async tryLogin(showLoading) {
140
102
  // Reset user state
141
- const result = await super.tryLogin(data);
103
+ const result = await super.tryLogin(showLoading);
142
104
  if (!result)
143
105
  return false;
144
106
  // Refresh token
145
107
  return await this.refreshToken({
146
108
  callback: (result) => this.doRefreshTokenResult(result),
147
- data,
148
- showLoading,
149
- relogin: true
109
+ showLoading
150
110
  });
151
111
  }
152
112
  }
@@ -1,29 +1,12 @@
1
- import { IApi, RefreshTokenResult } from "@etsoo/appscript";
2
- import { IServiceUser } from "./IServiceUser";
3
1
  import { ReactAppType } from "./ReactApp";
4
- import { IAppApi } from "./IAppApi";
5
2
  /**
6
3
  * Service application interface
7
4
  */
8
5
  export interface IServiceApp extends ReactAppType {
9
6
  /**
10
- * Service API
7
+ * Load core system UI
11
8
  */
12
- readonly serviceApi: IApi;
13
- /**
14
- * Service user
15
- */
16
- readonly serviceUser?: IServiceUser;
17
- /**
18
- * Service application API login
19
- * @param appApi Service application API
20
- * @param callback Callback
21
- */
22
- apiLogin(appApi: IAppApi, callback?: (result: RefreshTokenResult, successData?: string) => void): Promise<boolean>;
23
- /**
24
- * Load SmartERP core
25
- */
26
- loadSmartERP(): void;
9
+ loadCore(): void;
27
10
  /**
28
11
  * Service decrypt message
29
12
  * @param messageEncrypted Encrypted message
@@ -5,7 +5,7 @@ import { IdType } from "@etsoo/shared";
5
5
  */
6
6
  export interface IServiceAppSettings<S extends IdType = number> extends IAppSettings {
7
7
  /**
8
- * Service id
8
+ * Service application id
9
9
  */
10
- readonly serviceId: S;
10
+ readonly appId: S;
11
11
  }
@@ -13,10 +13,6 @@ export interface IServiceUser extends IUser {
13
13
  * Organization name
14
14
  */
15
15
  readonly organizationName: string;
16
- /**
17
- * Service (App) device id
18
- */
19
- readonly serviceDeviceId: string;
20
16
  /**
21
17
  * Service (App) passphrase encrypted
22
18
  */
@@ -1,4 +1,4 @@
1
- import { BridgeUtils, CoreApp, createClient } from "@etsoo/appscript";
1
+ import { BridgeUtils, CoreApp } from "@etsoo/appscript";
2
2
  import { NotificationMessageType } from "@etsoo/notificationbase";
3
3
  import { WindowStorage } from "@etsoo/shared";
4
4
  import React from "react";
@@ -69,7 +69,7 @@ export class ReactApp extends CoreApp {
69
69
  * @param debug Debug mode
70
70
  */
71
71
  constructor(settings, name, debug = false) {
72
- super(settings, createClient(), ReactApp.createNotifier(debug), new WindowStorage(), name, debug);
72
+ super(settings, null, ReactApp.createNotifier(debug), new WindowStorage(), name, debug);
73
73
  /**
74
74
  * User state
75
75
  */
@@ -1,34 +1,24 @@
1
- import { IApi, RefreshTokenProps, RefreshTokenResult, RefreshTokenRQ } from "@etsoo/appscript";
1
+ import { ExternalEndpoint, IApi } from "@etsoo/appscript";
2
2
  import { IServiceApp } from "./IServiceApp";
3
3
  import { IServiceAppSettings } from "./IServiceAppSettings";
4
4
  import { IServicePageData } from "./IServicePage";
5
5
  import { IServiceUser } from "./IServiceUser";
6
- import { ISmartERPUser } from "./ISmartERPUser";
7
6
  import { ReactApp } from "./ReactApp";
8
- import { IAppApi } from "./IAppApi";
9
- /**
10
- * Service application refresh token properties
11
- */
12
- export interface ServiceRefreshTokenProps extends RefreshTokenProps<Partial<RefreshTokenRQ>> {
13
- appApi?: IAppApi;
14
- }
15
7
  /**
16
8
  * Core Service App
17
9
  * Service login to core system, get the refesh token and access token
18
10
  * Use the acess token to the service api, get a service access token
19
11
  * Use the new acess token and refresh token to login
20
12
  */
21
- export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends IServicePageData = IServicePageData, S extends IServiceAppSettings = IServiceAppSettings> extends ReactApp<S, ISmartERPUser, P> implements IServiceApp {
13
+ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends IServicePageData = IServicePageData, S extends IServiceAppSettings = IServiceAppSettings> extends ReactApp<S, U, P> implements IServiceApp {
22
14
  /**
23
- * Service API
15
+ * Core endpoint
24
16
  */
25
- readonly serviceApi: IApi;
26
- private _serviceUser?;
17
+ protected coreEndpoint: ExternalEndpoint;
27
18
  /**
28
- * Service user
19
+ * Core system API
29
20
  */
30
- get serviceUser(): U | undefined;
31
- protected set serviceUser(value: U | undefined);
21
+ readonly coreApi: IApi;
32
22
  /**
33
23
  * Service passphrase
34
24
  */
@@ -41,15 +31,9 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
41
31
  */
42
32
  constructor(settings: S, name: string, debug?: boolean);
43
33
  /**
44
- * Service application API login
45
- * @param appApi Service application API
46
- * @param callback Callback
34
+ * Load core system UI
47
35
  */
48
- apiLogin(appApi: IAppApi, callback?: (result: RefreshTokenResult, successData?: string) => void): Promise<boolean>;
49
- /**
50
- * Load SmartERP core
51
- */
52
- loadSmartERP(): void;
36
+ loadCore(): void;
53
37
  /**
54
38
  * Go to the login page
55
39
  * @param tryLogin Try to login again
@@ -57,10 +41,13 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
57
41
  */
58
42
  toLoginPage(tryLogin?: boolean, removeUrl?: boolean): void;
59
43
  /**
60
- * Refresh token
61
- * @param props Props
44
+ * User login extended
45
+ * @param user New user
46
+ * @param refreshToken Refresh token
47
+ * @param keep Keep in local storage or not
48
+ * @param dispatch User state dispatch
62
49
  */
63
- refreshToken(props?: ServiceRefreshTokenProps): Promise<boolean>;
50
+ userLogin(user: U, refreshToken: string, keep?: boolean, dispatch?: boolean): void;
64
51
  /**
65
52
  * Service decrypt message
66
53
  * @param messageEncrypted Encrypted message
@@ -76,18 +63,4 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
76
63
  * @returns Result
77
64
  */
78
65
  serviceEncrypt(message: string, passphrase?: string, iterations?: number): string;
79
- /**
80
- * Try login
81
- * @param data Additional data
82
- * @param showLoading Show loading bar or not
83
- * @returns Result
84
- */
85
- tryLogin<D extends object = {}>(data?: D, showLoading?: boolean): Promise<boolean>;
86
- /**
87
- * User login extended
88
- * @param user Core system user
89
- * @param refreshToken Refresh token
90
- * @param serviceUser Service user
91
- */
92
- userLoginEx(user: ISmartERPUser, refreshToken: string, serviceUser: U): void;
93
66
  }
@@ -1,7 +1,6 @@
1
- import { BridgeUtils, createClient } from "@etsoo/appscript";
2
- import { CoreConstants } from "@etsoo/react";
3
- import { DomUtils } from "@etsoo/shared";
1
+ import { BridgeUtils } from "@etsoo/appscript";
4
2
  import { ReactApp } from "./ReactApp";
3
+ const coreName = "core";
5
4
  /**
6
5
  * Core Service App
7
6
  * Service login to core system, get the refesh token and access token
@@ -9,15 +8,6 @@ import { ReactApp } from "./ReactApp";
9
8
  * Use the new acess token and refresh token to login
10
9
  */
11
10
  export class ServiceApp extends ReactApp {
12
- /**
13
- * Service user
14
- */
15
- get serviceUser() {
16
- return this._serviceUser;
17
- }
18
- set serviceUser(value) {
19
- this._serviceUser = value;
20
- }
21
11
  /**
22
12
  * Constructor
23
13
  * @param settings Settings
@@ -31,39 +21,25 @@ export class ServiceApp extends ReactApp {
31
21
  */
32
22
  this.servicePassphrase = "";
33
23
  // Check
34
- if (settings.serviceId == null || settings.serviceEndpoint == null) {
35
- throw new Error("No service settings");
24
+ if (settings.appId == null) {
25
+ throw new Error("Service Application ID is required.");
36
26
  }
37
- // Service API
38
- const api = createClient();
39
- this.setApi(api);
40
- // Fix the baseUrl done by setupApi (Default is the settings.endpoint)
41
- api.baseUrl = settings.serviceEndpoint;
42
- this.serviceApi = api;
43
- }
44
- /**
45
- * Service application API login
46
- * @param appApi Service application API
47
- * @param callback Callback
48
- */
49
- apiLogin(appApi, callback) {
50
- return this.refreshToken({
51
- callback,
52
- data: appApi.getRefreshTokenData(),
53
- relogin: false,
54
- showLoading: false,
55
- appApi
56
- });
27
+ const coreEndpoint = settings.endpoints?.core;
28
+ if (coreEndpoint == null) {
29
+ throw new Error("Core API endpont is required.");
30
+ }
31
+ this.coreEndpoint = coreEndpoint;
32
+ this.coreApi = this.createApi(coreName, coreEndpoint);
57
33
  }
58
34
  /**
59
- * Load SmartERP core
35
+ * Load core system UI
60
36
  */
61
- loadSmartERP() {
37
+ loadCore() {
62
38
  if (BridgeUtils.host == null) {
63
- window.location.href = this.settings.webUrl;
39
+ globalThis.location.href = this.coreEndpoint.webUrl;
64
40
  }
65
41
  else {
66
- BridgeUtils.host.loadApp("core");
42
+ BridgeUtils.host.loadApp(coreName);
67
43
  }
68
44
  }
69
45
  /**
@@ -72,145 +48,41 @@ export class ServiceApp extends ReactApp {
72
48
  * @param removeUrl Remove current URL for reuse
73
49
  */
74
50
  toLoginPage(tryLogin, removeUrl) {
75
- const parameters = `?serviceId=${this.settings.serviceId}&${DomUtils.CultureField}=${this.culture}${tryLogin ? "" : "&tryLogin=false"}${removeUrl ? "" : "&url=" + encodeURIComponent(location.href)}`;
51
+ // Cache current URL
52
+ this.cachedUrl = removeUrl ? undefined : globalThis.location.href;
76
53
  // Make sure apply new device id for new login
77
54
  this.clearDeviceId();
78
- if (BridgeUtils.host == null) {
79
- const coreUrl = this.settings.webUrl;
80
- window.location.href = coreUrl + parameters;
81
- }
82
- else {
83
- BridgeUtils.host.loadApp("core", parameters);
84
- }
85
- }
86
- /**
87
- * Refresh token
88
- * @param props Props
89
- */
90
- async refreshToken(props) {
91
- // Destruct
92
- const { appApi, callback, data, relogin = false, showLoading = false } = props ?? {};
93
- // Token
94
- const token = this.getCacheToken();
95
- if (token == null || token === "") {
96
- if (callback)
97
- callback(false);
98
- return false;
99
- }
100
- // Reqest data
101
- // Merge additional data passed
102
- const rq = {
103
- deviceId: this.deviceId,
55
+ // Get the redirect URL
56
+ this.api
57
+ .get("Auth/GetLogInUrl", {
104
58
  region: this.region,
105
- timezone: this.getTimeZone(),
106
- ...data
107
- };
108
- // Payload
109
- const payload = {
110
- showLoading,
111
- config: { headers: { [CoreConstants.TokenHeaderRefresh]: token } },
112
- onError: (error) => {
113
- if (callback)
114
- callback(error);
115
- // Prevent further processing
116
- return false;
117
- }
118
- };
119
- // Success callback
120
- const success = async (result, failCallback) => {
121
- // Token
122
- const refreshToken = this.getResponseToken(payload.response);
123
- if (refreshToken == null || result.data == null) {
124
- if (failCallback)
125
- failCallback(this.get("noData"));
126
- return false;
127
- }
128
- // User data
129
- const userData = result.data;
130
- // Use core system access token to service api to exchange service access token
131
- const api = appApi ? appApi.api : this.serviceApi;
132
- const serviceResult = await api.put("Auth/ExchangeToken", {
133
- token: this.encryptEnhanced(userData.token, (appApi?.serviceId ?? this.settings.serviceId).toString())
134
- }, {
135
- showLoading,
136
- onError: (error) => {
137
- if (failCallback)
138
- failCallback(error);
139
- // Prevent further processing
140
- return false;
141
- }
142
- });
143
- if (serviceResult == null)
144
- return false;
145
- if (!serviceResult.ok) {
146
- if (failCallback)
147
- failCallback(serviceResult);
148
- return false;
149
- }
150
- if (serviceResult.data == null) {
151
- if (failCallback)
152
- failCallback(this.get("noData"));
153
- return false;
154
- }
155
- // Login
156
- if (appApi) {
157
- // Authorize external service application API
158
- appApi.authorize(userData, refreshToken, serviceResult.data);
59
+ device: this.deviceId
60
+ })
61
+ .then((url) => {
62
+ if (!url)
63
+ return;
64
+ url += `?tryLogin=${tryLogin ?? false}`;
65
+ if (BridgeUtils.host == null) {
66
+ globalThis.location.href = url;
159
67
  }
160
68
  else {
161
- // Authorize local service
162
- this.userLoginEx(userData, refreshToken, serviceResult.data);
69
+ BridgeUtils.host.loadApp(coreName, url);
163
70
  }
164
- // Success callback
165
- if (failCallback)
166
- failCallback(true);
167
- return true;
168
- };
169
- // Call API
170
- const result = await this.api.put("Auth/RefreshToken", rq, payload);
171
- if (result == null)
172
- return false;
173
- if (!result.ok) {
174
- if (result.type === "TokenExpired" && relogin) {
175
- // Try login
176
- // Dialog to receive password
177
- var labels = this.getLabels("reloginTip", "login");
178
- this.notifier.prompt(labels.reloginTip, async (pwd) => {
179
- if (pwd == null) {
180
- this.toLoginPage();
181
- return;
182
- }
183
- // Set password for the action
184
- rq.pwd = this.encrypt(this.hash(pwd));
185
- // Submit again
186
- const result = await this.api.put("Auth/RefreshToken", rq, payload);
187
- if (result == null)
188
- return;
189
- if (result.ok) {
190
- await success(result, (loginResult) => {
191
- if (loginResult === true) {
192
- if (callback)
193
- callback(true);
194
- return;
195
- }
196
- const message = this.formatRefreshTokenResult(loginResult);
197
- if (message)
198
- this.notifier.alert(message);
199
- });
200
- return;
201
- }
202
- // Popup message
203
- this.alertResult(result);
204
- return false;
205
- }, labels.login, { type: "password" });
206
- // Fake truth to avoid reloading
207
- return true;
208
- }
209
- if (callback)
210
- callback(result);
211
- return false;
212
- }
213
- return await success(result, callback);
71
+ });
72
+ }
73
+ /**
74
+ * User login extended
75
+ * @param user New user
76
+ * @param refreshToken Refresh token
77
+ * @param keep Keep in local storage or not
78
+ * @param dispatch User state dispatch
79
+ */
80
+ userLogin(user, refreshToken, keep, dispatch) {
81
+ // Super call, set token
82
+ super.userLogin(user, refreshToken, keep, dispatch);
83
+ // Set service passphrase
84
+ this.servicePassphrase =
85
+ this.decrypt(user.servicePassphrase, `${user.uid}-${this.settings.appId}`) ?? "";
214
86
  }
215
87
  /**
216
88
  * Service decrypt message
@@ -231,41 +103,4 @@ export class ServiceApp extends ReactApp {
231
103
  serviceEncrypt(message, passphrase, iterations) {
232
104
  return this.encrypt(message, passphrase ?? this.servicePassphrase, iterations);
233
105
  }
234
- /**
235
- * Try login
236
- * @param data Additional data
237
- * @param showLoading Show loading bar or not
238
- * @returns Result
239
- */
240
- async tryLogin(data, showLoading) {
241
- // Reset user state
242
- const result = await super.tryLogin(data, showLoading);
243
- if (!result)
244
- return false;
245
- // Refresh token
246
- return await this.refreshToken({
247
- callback: (result) => this.doRefreshTokenResult(result),
248
- data,
249
- showLoading,
250
- relogin: true
251
- });
252
- }
253
- /**
254
- * User login extended
255
- * @param user Core system user
256
- * @param refreshToken Refresh token
257
- * @param serviceUser Service user
258
- */
259
- userLoginEx(user, refreshToken, serviceUser) {
260
- // Service user login
261
- this.servicePassphrase =
262
- this.decrypt(serviceUser.servicePassphrase, this.settings.serviceId.toString()) ?? "";
263
- // Service user
264
- this.serviceUser = serviceUser;
265
- // Service API token
266
- this.serviceApi.authorize(serviceUser.tokenScheme ?? "Bearer", serviceUser.token);
267
- // Keep = true, means service could hold the refresh token for long access
268
- // Trigger Context change and serviceUser is ready then
269
- super.userLogin(user, refreshToken, true);
270
- }
271
106
  }
package/lib/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from "./app/CommonApp";
2
- export * from "./app/IAppApi";
3
2
  export * from "./app/IServiceApp";
4
3
  export * from "./app/IServiceAppSettings";
5
4
  export * from "./app/IServicePage";
package/lib/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from "./app/CommonApp";
2
- export * from "./app/IAppApi";
3
2
  export * from "./app/IServiceApp";
4
3
  export * from "./app/IServiceAppSettings";
5
4
  export * from "./app/IServicePage";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.3.88",
3
+ "version": "1.3.89",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -47,16 +47,16 @@
47
47
  "dependencies": {
48
48
  "@dnd-kit/core": "^6.1.0",
49
49
  "@dnd-kit/sortable": "^8.0.0",
50
- "@emotion/css": "^11.13.0",
50
+ "@emotion/css": "^11.13.4",
51
51
  "@emotion/react": "^11.13.3",
52
52
  "@emotion/styled": "^11.13.0",
53
- "@etsoo/appscript": "^1.5.16",
54
- "@etsoo/notificationbase": "^1.1.47",
55
- "@etsoo/react": "^1.7.66",
56
- "@etsoo/shared": "^1.2.44",
57
- "@mui/icons-material": "^6.1.1",
58
- "@mui/material": "^6.1.1",
59
- "@mui/x-data-grid": "^7.18.0",
53
+ "@etsoo/appscript": "^1.5.22",
54
+ "@etsoo/notificationbase": "^1.1.48",
55
+ "@etsoo/react": "^1.7.68",
56
+ "@etsoo/shared": "^1.2.46",
57
+ "@mui/icons-material": "^6.1.2",
58
+ "@mui/material": "^6.1.2",
59
+ "@mui/x-data-grid": "^7.19.0",
60
60
  "chart.js": "^4.4.4",
61
61
  "chartjs-plugin-datalabels": "^2.2.0",
62
62
  "eventemitter3": "^5.0.1",
@@ -70,25 +70,25 @@
70
70
  "react-imask": "7.6.1"
71
71
  },
72
72
  "devDependencies": {
73
- "@babel/cli": "^7.25.6",
74
- "@babel/core": "^7.25.2",
75
- "@babel/plugin-transform-runtime": "^7.25.4",
76
- "@babel/preset-env": "^7.25.4",
77
- "@babel/preset-react": "^7.24.7",
78
- "@babel/preset-typescript": "^7.24.7",
79
- "@babel/runtime-corejs3": "^7.25.6",
73
+ "@babel/cli": "^7.25.7",
74
+ "@babel/core": "^7.25.7",
75
+ "@babel/plugin-transform-runtime": "^7.25.7",
76
+ "@babel/preset-env": "^7.25.7",
77
+ "@babel/preset-react": "^7.25.7",
78
+ "@babel/preset-typescript": "^7.25.7",
79
+ "@babel/runtime-corejs3": "^7.25.7",
80
80
  "@testing-library/jest-dom": "^6.5.0",
81
81
  "@testing-library/react": "^16.0.1",
82
82
  "@types/jest": "^29.5.13",
83
83
  "@types/pica": "^9.0.4",
84
84
  "@types/pulltorefreshjs": "^0.1.7",
85
- "@types/react": "^18.3.8",
85
+ "@types/react": "^18.3.11",
86
86
  "@types/react-avatar-editor": "^13.0.3",
87
87
  "@types/react-dom": "^18.3.0",
88
88
  "@types/react-input-mask": "^3.0.5",
89
89
  "@types/react-window": "^1.8.8",
90
- "@typescript-eslint/eslint-plugin": "^8.6.0",
91
- "@typescript-eslint/parser": "^8.6.0",
90
+ "@typescript-eslint/eslint-plugin": "^8.8.0",
91
+ "@typescript-eslint/parser": "^8.8.0",
92
92
  "jest": "^29.7.0",
93
93
  "jest-environment-jsdom": "^29.7.0",
94
94
  "typescript": "^5.6.2"
@@ -56,16 +56,9 @@ export abstract class CommonApp<
56
56
  * Refresh token
57
57
  * @param props Props
58
58
  */
59
- override async refreshToken<D extends object = Partial<RefreshTokenRQ>>(
60
- props?: RefreshTokenProps<D>
61
- ) {
59
+ override async refreshToken(props?: RefreshTokenProps) {
62
60
  // Destruct
63
- const {
64
- callback,
65
- data,
66
- relogin = false,
67
- showLoading = false
68
- } = props ?? {};
61
+ const { callback, showLoading = false } = props ?? {};
69
62
 
70
63
  // Token
71
64
  const token = this.getCacheToken();
@@ -76,10 +69,7 @@ export abstract class CommonApp<
76
69
 
77
70
  // Reqest data
78
71
  const rq: RefreshTokenRQ = {
79
- deviceId: this.deviceId,
80
- region: this.region,
81
- timezone: this.getTimeZone(),
82
- ...data
72
+ deviceId: this.deviceId
83
73
  };
84
74
 
85
75
  // Login result type
@@ -135,56 +125,9 @@ export abstract class CommonApp<
135
125
  // Remove the wrong token
136
126
  this.clearCacheToken();
137
127
 
138
- if (result.type === "TokenExpired" && relogin) {
139
- // Try login
140
- // Dialog to receive password
141
- var labels = this.getLabels("reloginTip", "login");
142
- this.notifier.prompt(
143
- labels.reloginTip,
144
- async (pwd) => {
145
- if (pwd == null) {
146
- this.toLoginPage();
147
- return;
148
- }
149
-
150
- // Set password for the action
151
- rq.pwd = this.encrypt(this.hash(pwd));
152
-
153
- // Submit again
154
- const result = await this.api.put<LoginResult>(
155
- "Auth/RefreshToken",
156
- rq,
157
- payload
158
- );
159
-
160
- if (result == null) return;
161
-
162
- if (result.ok) {
163
- success(result, (loginResult: RefreshTokenResult) => {
164
- if (loginResult === true) {
165
- if (callback) callback(true);
166
- return;
167
- }
168
-
169
- const message = this.formatRefreshTokenResult(loginResult);
170
- if (message) this.notifier.alert(message);
171
- });
172
- return;
173
- }
174
-
175
- // Popup message
176
- this.alertResult(result);
177
- return false;
178
- },
179
- labels.login,
180
- { type: "password" }
181
- );
182
-
183
- // Fake truth to avoid reloading
184
- return true;
185
- }
186
-
128
+ // Callback
187
129
  if (callback) callback(result);
130
+
188
131
  return false;
189
132
  }
190
133
 
@@ -193,24 +136,18 @@ export abstract class CommonApp<
193
136
 
194
137
  /**
195
138
  * Try login
196
- * @param data Additional data
197
139
  * @param showLoading Show loading bar or not
198
140
  * @returns Result
199
141
  */
200
- override async tryLogin<D extends object = RefreshTokenRQ>(
201
- data?: D,
202
- showLoading?: boolean
203
- ) {
142
+ override async tryLogin(showLoading?: boolean) {
204
143
  // Reset user state
205
- const result = await super.tryLogin(data);
144
+ const result = await super.tryLogin(showLoading);
206
145
  if (!result) return false;
207
146
 
208
147
  // Refresh token
209
148
  return await this.refreshToken({
210
149
  callback: (result) => this.doRefreshTokenResult(result),
211
- data,
212
- showLoading,
213
- relogin: true
150
+ showLoading
214
151
  });
215
152
  }
216
153
  }
@@ -1,36 +1,13 @@
1
- import { IApi, RefreshTokenResult } from "@etsoo/appscript";
2
- import { IServiceUser } from "./IServiceUser";
3
1
  import { ReactAppType } from "./ReactApp";
4
- import { IAppApi } from "./IAppApi";
5
2
 
6
3
  /**
7
4
  * Service application interface
8
5
  */
9
6
  export interface IServiceApp extends ReactAppType {
10
7
  /**
11
- * Service API
8
+ * Load core system UI
12
9
  */
13
- readonly serviceApi: IApi;
14
-
15
- /**
16
- * Service user
17
- */
18
- readonly serviceUser?: IServiceUser;
19
-
20
- /**
21
- * Service application API login
22
- * @param appApi Service application API
23
- * @param callback Callback
24
- */
25
- apiLogin(
26
- appApi: IAppApi,
27
- callback?: (result: RefreshTokenResult, successData?: string) => void
28
- ): Promise<boolean>;
29
-
30
- /**
31
- * Load SmartERP core
32
- */
33
- loadSmartERP(): void;
10
+ loadCore(): void;
34
11
 
35
12
  /**
36
13
  * Service decrypt message
@@ -7,7 +7,7 @@ import { IdType } from "@etsoo/shared";
7
7
  export interface IServiceAppSettings<S extends IdType = number>
8
8
  extends IAppSettings {
9
9
  /**
10
- * Service id
10
+ * Service application id
11
11
  */
12
- readonly serviceId: S;
12
+ readonly appId: S;
13
13
  }
@@ -16,11 +16,6 @@ export interface IServiceUser extends IUser {
16
16
  */
17
17
  readonly organizationName: string;
18
18
 
19
- /**
20
- * Service (App) device id
21
- */
22
- readonly serviceDeviceId: string;
23
-
24
19
  /**
25
20
  * Service (App) passphrase encrypted
26
21
  */
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  BridgeUtils,
3
3
  CoreApp,
4
- createClient,
5
4
  FormatResultCustomCallback,
6
5
  IApp,
7
6
  IAppSettings,
@@ -253,7 +252,7 @@ export class ReactApp<
253
252
  constructor(settings: S, name: string, debug: boolean = false) {
254
253
  super(
255
254
  settings,
256
- createClient(),
255
+ null,
257
256
  ReactApp.createNotifier(debug),
258
257
  new WindowStorage(),
259
258
  name,
@@ -1,29 +1,11 @@
1
- import {
2
- BridgeUtils,
3
- createClient,
4
- IApi,
5
- IApiPayload,
6
- RefreshTokenProps,
7
- RefreshTokenResult,
8
- RefreshTokenRQ
9
- } from "@etsoo/appscript";
10
- import { CoreConstants } from "@etsoo/react";
11
- import { DomUtils, IActionResult } from "@etsoo/shared";
1
+ import { BridgeUtils, ExternalEndpoint, IApi } from "@etsoo/appscript";
12
2
  import { IServiceApp } from "./IServiceApp";
13
3
  import { IServiceAppSettings } from "./IServiceAppSettings";
14
4
  import { IServicePageData } from "./IServicePage";
15
- import { IServiceUser, ServiceLoginResult } from "./IServiceUser";
16
- import { ISmartERPUser } from "./ISmartERPUser";
5
+ import { IServiceUser } from "./IServiceUser";
17
6
  import { ReactApp } from "./ReactApp";
18
- import { IAppApi } from "./IAppApi";
19
7
 
20
- /**
21
- * Service application refresh token properties
22
- */
23
- export interface ServiceRefreshTokenProps
24
- extends RefreshTokenProps<Partial<RefreshTokenRQ>> {
25
- appApi?: IAppApi;
26
- }
8
+ const coreName = "core";
27
9
 
28
10
  /**
29
11
  * Core Service App
@@ -36,24 +18,18 @@ export class ServiceApp<
36
18
  P extends IServicePageData = IServicePageData,
37
19
  S extends IServiceAppSettings = IServiceAppSettings
38
20
  >
39
- extends ReactApp<S, ISmartERPUser, P>
21
+ extends ReactApp<S, U, P>
40
22
  implements IServiceApp
41
23
  {
42
24
  /**
43
- * Service API
25
+ * Core endpoint
44
26
  */
45
- readonly serviceApi: IApi;
27
+ protected coreEndpoint: ExternalEndpoint;
46
28
 
47
- private _serviceUser?: U;
48
29
  /**
49
- * Service user
30
+ * Core system API
50
31
  */
51
- get serviceUser() {
52
- return this._serviceUser;
53
- }
54
- protected set serviceUser(value: U | undefined) {
55
- this._serviceUser = value;
56
- }
32
+ readonly coreApi: IApi;
57
33
 
58
34
  /**
59
35
  * Service passphrase
@@ -70,46 +46,27 @@ export class ServiceApp<
70
46
  super(settings, name, debug);
71
47
 
72
48
  // Check
73
- if (settings.serviceId == null || settings.serviceEndpoint == null) {
74
- throw new Error("No service settings");
49
+ if (settings.appId == null) {
50
+ throw new Error("Service Application ID is required.");
75
51
  }
76
52
 
77
- // Service API
78
- const api = createClient();
79
- this.setApi(api);
80
-
81
- // Fix the baseUrl done by setupApi (Default is the settings.endpoint)
82
- api.baseUrl = settings.serviceEndpoint;
83
-
84
- this.serviceApi = api;
85
- }
53
+ const coreEndpoint = settings.endpoints?.core;
54
+ if (coreEndpoint == null) {
55
+ throw new Error("Core API endpont is required.");
56
+ }
57
+ this.coreEndpoint = coreEndpoint;
86
58
 
87
- /**
88
- * Service application API login
89
- * @param appApi Service application API
90
- * @param callback Callback
91
- */
92
- apiLogin(
93
- appApi: IAppApi,
94
- callback?: (result: RefreshTokenResult, successData?: string) => void
95
- ) {
96
- return this.refreshToken({
97
- callback,
98
- data: appApi.getRefreshTokenData(),
99
- relogin: false,
100
- showLoading: false,
101
- appApi
102
- });
59
+ this.coreApi = this.createApi(coreName, coreEndpoint);
103
60
  }
104
61
 
105
62
  /**
106
- * Load SmartERP core
63
+ * Load core system UI
107
64
  */
108
- loadSmartERP() {
65
+ loadCore() {
109
66
  if (BridgeUtils.host == null) {
110
- window.location.href = this.settings.webUrl;
67
+ globalThis.location.href = this.coreEndpoint.webUrl;
111
68
  } else {
112
- BridgeUtils.host.loadApp("core");
69
+ BridgeUtils.host.loadApp(coreName);
113
70
  }
114
71
  }
115
72
 
@@ -119,193 +76,53 @@ export class ServiceApp<
119
76
  * @param removeUrl Remove current URL for reuse
120
77
  */
121
78
  override toLoginPage(tryLogin?: boolean, removeUrl?: boolean) {
122
- const parameters = `?serviceId=${this.settings.serviceId}&${
123
- DomUtils.CultureField
124
- }=${this.culture}${tryLogin ? "" : "&tryLogin=false"}${
125
- removeUrl ? "" : "&url=" + encodeURIComponent(location.href)
126
- }`;
79
+ // Cache current URL
80
+ this.cachedUrl = removeUrl ? undefined : globalThis.location.href;
81
+
127
82
  // Make sure apply new device id for new login
128
83
  this.clearDeviceId();
129
84
 
130
- if (BridgeUtils.host == null) {
131
- const coreUrl = this.settings.webUrl;
132
- window.location.href = coreUrl + parameters;
133
- } else {
134
- BridgeUtils.host.loadApp("core", parameters);
135
- }
85
+ // Get the redirect URL
86
+ this.api
87
+ .get<string>("Auth/GetLogInUrl", {
88
+ region: this.region,
89
+ device: this.deviceId
90
+ })
91
+ .then((url) => {
92
+ if (!url) return;
93
+
94
+ url += `?tryLogin=${tryLogin ?? false}`;
95
+
96
+ if (BridgeUtils.host == null) {
97
+ globalThis.location.href = url;
98
+ } else {
99
+ BridgeUtils.host.loadApp(coreName, url);
100
+ }
101
+ });
136
102
  }
137
103
 
138
104
  /**
139
- * Refresh token
140
- * @param props Props
105
+ * User login extended
106
+ * @param user New user
107
+ * @param refreshToken Refresh token
108
+ * @param keep Keep in local storage or not
109
+ * @param dispatch User state dispatch
141
110
  */
142
- override async refreshToken(props?: ServiceRefreshTokenProps) {
143
- // Destruct
144
- const {
145
- appApi,
146
- callback,
147
- data,
148
- relogin = false,
149
- showLoading = false
150
- } = props ?? {};
151
-
152
- // Token
153
- const token = this.getCacheToken();
154
- if (token == null || token === "") {
155
- if (callback) callback(false);
156
- return false;
157
- }
158
-
159
- // Reqest data
160
- // Merge additional data passed
161
- const rq: RefreshTokenRQ = {
162
- deviceId: this.deviceId,
163
- region: this.region,
164
- timezone: this.getTimeZone(),
165
- ...data
166
- };
167
-
168
- // Login result type
169
- type LoginResult = IActionResult<U>;
170
-
171
- // Payload
172
- const payload: IApiPayload<LoginResult, any> = {
173
- showLoading,
174
- config: { headers: { [CoreConstants.TokenHeaderRefresh]: token } },
175
- onError: (error) => {
176
- if (callback) callback(error);
177
-
178
- // Prevent further processing
179
- return false;
180
- }
181
- };
182
-
183
- // Success callback
184
- const success = async (
185
- result: LoginResult,
186
- failCallback?: (result: RefreshTokenResult) => void
187
- ) => {
188
- // Token
189
- const refreshToken = this.getResponseToken(payload.response);
190
- if (refreshToken == null || result.data == null) {
191
- if (failCallback) failCallback(this.get("noData")!);
192
- return false;
193
- }
194
-
195
- // User data
196
- const userData = result.data;
197
-
198
- // Use core system access token to service api to exchange service access token
199
- const api = appApi ? appApi.api : this.serviceApi;
200
- const serviceResult = await api.put<ServiceLoginResult<U>>(
201
- "Auth/ExchangeToken",
202
- {
203
- token: this.encryptEnhanced(
204
- userData.token,
205
- (appApi?.serviceId ?? this.settings.serviceId).toString()
206
- )
207
- },
208
- {
209
- showLoading,
210
- onError: (error) => {
211
- if (failCallback) failCallback(error);
212
-
213
- // Prevent further processing
214
- return false;
215
- }
216
- }
217
- );
218
-
219
- if (serviceResult == null) return false;
220
-
221
- if (!serviceResult.ok) {
222
- if (failCallback) failCallback(serviceResult);
223
- return false;
224
- }
225
-
226
- if (serviceResult.data == null) {
227
- if (failCallback) failCallback(this.get("noData")!);
228
- return false;
229
- }
230
-
231
- // Login
232
- if (appApi) {
233
- // Authorize external service application API
234
- appApi.authorize(userData, refreshToken, serviceResult.data);
235
- } else {
236
- // Authorize local service
237
- this.userLoginEx(userData, refreshToken, serviceResult.data);
238
- }
239
-
240
- // Success callback
241
- if (failCallback) failCallback(true);
242
-
243
- return true;
244
- };
245
-
246
- // Call API
247
- const result = await this.api.put<LoginResult>(
248
- "Auth/RefreshToken",
249
- rq,
250
- payload
251
- );
252
- if (result == null) return false;
253
-
254
- if (!result.ok) {
255
- if (result.type === "TokenExpired" && relogin) {
256
- // Try login
257
- // Dialog to receive password
258
- var labels = this.getLabels("reloginTip", "login");
259
- this.notifier.prompt(
260
- labels.reloginTip,
261
- async (pwd) => {
262
- if (pwd == null) {
263
- this.toLoginPage();
264
- return;
265
- }
266
-
267
- // Set password for the action
268
- rq.pwd = this.encrypt(this.hash(pwd));
269
-
270
- // Submit again
271
- const result = await this.api.put<LoginResult>(
272
- "Auth/RefreshToken",
273
- rq,
274
- payload
275
- );
276
-
277
- if (result == null) return;
278
-
279
- if (result.ok) {
280
- await success(result, (loginResult: RefreshTokenResult) => {
281
- if (loginResult === true) {
282
- if (callback) callback(true);
283
- return;
284
- }
285
-
286
- const message = this.formatRefreshTokenResult(loginResult);
287
- if (message) this.notifier.alert(message);
288
- });
289
- return;
290
- }
291
-
292
- // Popup message
293
- this.alertResult(result);
294
- return false;
295
- },
296
- labels.login,
297
- { type: "password" }
298
- );
299
-
300
- // Fake truth to avoid reloading
301
- return true;
302
- }
303
-
304
- if (callback) callback(result);
305
- return false;
306
- }
307
-
308
- return await success(result, callback);
111
+ override userLogin(
112
+ user: U,
113
+ refreshToken: string,
114
+ keep?: boolean,
115
+ dispatch?: boolean
116
+ ): void {
117
+ // Super call, set token
118
+ super.userLogin(user, refreshToken, keep, dispatch);
119
+
120
+ // Set service passphrase
121
+ this.servicePassphrase =
122
+ this.decrypt(
123
+ user.servicePassphrase,
124
+ `${user.uid}-${this.settings.appId}`
125
+ ) ?? "";
309
126
  }
310
127
 
311
128
  /**
@@ -332,55 +149,4 @@ export class ServiceApp<
332
149
  iterations
333
150
  );
334
151
  }
335
-
336
- /**
337
- * Try login
338
- * @param data Additional data
339
- * @param showLoading Show loading bar or not
340
- * @returns Result
341
- */
342
- override async tryLogin<D extends object = {}>(
343
- data?: D,
344
- showLoading?: boolean
345
- ) {
346
- // Reset user state
347
- const result = await super.tryLogin(data, showLoading);
348
- if (!result) return false;
349
-
350
- // Refresh token
351
- return await this.refreshToken({
352
- callback: (result) => this.doRefreshTokenResult(result),
353
- data,
354
- showLoading,
355
- relogin: true
356
- });
357
- }
358
-
359
- /**
360
- * User login extended
361
- * @param user Core system user
362
- * @param refreshToken Refresh token
363
- * @param serviceUser Service user
364
- */
365
- userLoginEx(user: ISmartERPUser, refreshToken: string, serviceUser: U) {
366
- // Service user login
367
- this.servicePassphrase =
368
- this.decrypt(
369
- serviceUser.servicePassphrase,
370
- this.settings.serviceId.toString()
371
- ) ?? "";
372
-
373
- // Service user
374
- this.serviceUser = serviceUser;
375
-
376
- // Service API token
377
- this.serviceApi.authorize(
378
- serviceUser.tokenScheme ?? "Bearer",
379
- serviceUser.token
380
- );
381
-
382
- // Keep = true, means service could hold the refresh token for long access
383
- // Trigger Context change and serviceUser is ready then
384
- super.userLogin(user, refreshToken, true);
385
- }
386
152
  }
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from "./app/CommonApp";
2
- export * from "./app/IAppApi";
3
2
  export * from "./app/IServiceApp";
4
3
  export * from "./app/IServiceAppSettings";
5
4
  export * from "./app/IServicePage";
@@ -1,29 +0,0 @@
1
- import { IApi } from "@etsoo/restclient";
2
- import { ISmartERPUser } from "./ISmartERPUser";
3
- import { RefreshTokenRQ } from "@etsoo/appscript";
4
- import { IServiceUser } from "./IServiceUser";
5
- /**
6
- * Service application API, Implement interface calls between different services
7
- * 服务程序接口,实现不同服务之间的接口调用
8
- */
9
- export interface IAppApi {
10
- /**
11
- * API
12
- */
13
- readonly api: IApi<any>;
14
- /**
15
- * Service id
16
- */
17
- readonly serviceId: number;
18
- /**
19
- * Authorize the API
20
- * @param user SmartERP user
21
- * @param refreshToken SmartERP user refresh token
22
- * @param serviceUser Service user
23
- */
24
- authorize(user: ISmartERPUser, refreshToken: string, serviceUser: IServiceUser): void;
25
- /**
26
- * Get refresh token data
27
- */
28
- getRefreshTokenData(): Partial<RefreshTokenRQ>;
29
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,37 +0,0 @@
1
- import { IApi } from "@etsoo/restclient";
2
- import { ISmartERPUser } from "./ISmartERPUser";
3
- import { RefreshTokenRQ } from "@etsoo/appscript";
4
- import { IServiceUser } from "./IServiceUser";
5
-
6
- /**
7
- * Service application API, Implement interface calls between different services
8
- * 服务程序接口,实现不同服务之间的接口调用
9
- */
10
- export interface IAppApi {
11
- /**
12
- * API
13
- */
14
- readonly api: IApi<any>;
15
-
16
- /**
17
- * Service id
18
- */
19
- readonly serviceId: number;
20
-
21
- /**
22
- * Authorize the API
23
- * @param user SmartERP user
24
- * @param refreshToken SmartERP user refresh token
25
- * @param serviceUser Service user
26
- */
27
- authorize(
28
- user: ISmartERPUser,
29
- refreshToken: string,
30
- serviceUser: IServiceUser
31
- ): void;
32
-
33
- /**
34
- * Get refresh token data
35
- */
36
- getRefreshTokenData(): Partial<RefreshTokenRQ>;
37
- }