@etsoo/materialui 1.2.26 → 1.2.28

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/lib/Tiplist.js CHANGED
@@ -11,7 +11,7 @@ import { InputField } from "./InputField";
11
11
  * @returns Component
12
12
  */
13
13
  export function Tiplist(props) {
14
- var _a, _b;
14
+ var _a;
15
15
  // Labels
16
16
  const { noOptions, loading, more, open: openDefault } = (_a = globalApp === null || globalApp === void 0 ? void 0 : globalApp.getLabels("noOptions", "loading", "more", "open")) !== null && _a !== void 0 ? _a : {};
17
17
  // Destruct
@@ -41,9 +41,12 @@ export function Tiplist(props) {
41
41
  if (localValue != null)
42
42
  stateUpdate({ value: localValue });
43
43
  }, [localValue]);
44
- // State
45
- const [state] = React.useState({});
46
- const isMounted = React.useRef(true);
44
+ // Ref
45
+ const state = React.useRef({
46
+ idLoaded: false,
47
+ idSet: false,
48
+ isMounted: false
49
+ });
47
50
  // Add readOnly
48
51
  const addReadOnly = (params) => {
49
52
  if (readOnly != null) {
@@ -87,7 +90,7 @@ export function Tiplist(props) {
87
90
  stateUpdate({ loading: true });
88
91
  // Load list
89
92
  loadData(keyword, id, maxItems).then((options) => {
90
- if (!isMounted.current)
93
+ if (!state.current.isMounted)
91
94
  return;
92
95
  if (options != null && options.length >= maxItems) {
93
96
  options.push({ [idField]: "n/a" });
@@ -123,42 +126,44 @@ export function Tiplist(props) {
123
126
  if (localIdValue == null) {
124
127
  if (inputValue != null)
125
128
  setInputValue(null);
129
+ return;
126
130
  }
127
- else if (state.idLoaded) {
128
- state.idLoaded = false;
129
- state.idSet = false;
131
+ if (state.current.idLoaded) {
132
+ state.current.idLoaded = false;
133
+ state.current.idSet = false;
130
134
  }
131
135
  }, [localIdValue]);
132
136
  React.useEffect(() => {
133
137
  if (localIdValue != null && localIdValue !== "") {
134
- if (state.idLoaded) {
138
+ if (state.current.idLoaded) {
135
139
  // Set default
136
- if (!state.idSet && states.options.length > 0) {
140
+ if (!state.current.idSet && states.options.length > 0) {
137
141
  const item = states.options.find((o) => o[idField] === localIdValue);
138
142
  if (item) {
139
143
  stateUpdate({
140
144
  value: states.options[0]
141
145
  });
142
- state.idSet = true;
146
+ state.current.idSet = true;
143
147
  }
144
148
  }
145
149
  }
146
150
  else {
147
151
  // Load id data
148
152
  loadDataDirect(undefined, localIdValue);
149
- state.idLoaded = true;
153
+ state.current.idLoaded = true;
150
154
  }
151
155
  }
152
- }, [state.idLoaded, state.idSet, localIdValue, states.options]);
156
+ }, [localIdValue, states.options]);
153
157
  React.useEffect(() => {
158
+ state.current.isMounted = true;
154
159
  return () => {
155
- isMounted.current = false;
160
+ state.current.isMounted = false;
156
161
  delayed.clear();
157
162
  };
158
163
  }, []);
159
164
  // Layout
160
165
  return (React.createElement("div", null,
161
- React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: "none" }, name: name, value: `${(_b = inputValue !== null && inputValue !== void 0 ? inputValue : localIdValue) !== null && _b !== void 0 ? _b : ""}`, readOnly: true, onChange: inputOnChange }),
166
+ React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: "none" }, name: name, value: `${inputValue !== null && inputValue !== void 0 ? inputValue : (state.current.idSet ? "" : localIdValue !== null && localIdValue !== void 0 ? localIdValue : "")}`, readOnly: true, onChange: inputOnChange }),
162
167
  React.createElement(Autocomplete, { filterOptions: (options, _state) => options, value: states.value, options: states.options, onChange: (event, value, reason, details) => {
163
168
  // Set value
164
169
  setInputValue(value);
package/lib/TiplistPro.js CHANGED
@@ -9,7 +9,7 @@ import { globalApp } from "./app/ReactApp";
9
9
  * @returns Component
10
10
  */
11
11
  export function TiplistPro(props) {
12
- var _a, _b;
12
+ var _a;
13
13
  // Labels
14
14
  const { noOptions, loading, more, open: openDefault } = (_a = globalApp === null || globalApp === void 0 ? void 0 : globalApp.getLabels("noOptions", "loading", "more", "open")) !== null && _a !== void 0 ? _a : {};
15
15
  // Destruct
@@ -41,9 +41,12 @@ export function TiplistPro(props) {
41
41
  const inputValue = React.useMemo(() => states.value && typeof states.value === "object"
42
42
  ? states.value.id
43
43
  : undefined, [states.value]);
44
- // State
45
- const [state] = React.useState({});
46
- const isMounted = React.useRef(true);
44
+ // Ref
45
+ const state = React.useRef({
46
+ idLoaded: false,
47
+ idSet: false,
48
+ isMounted: false
49
+ });
47
50
  // Change handler
48
51
  const changeHandle = (event) => {
49
52
  // Stop processing with auto trigger event
@@ -77,7 +80,7 @@ export function TiplistPro(props) {
77
80
  stateUpdate({ loading: true });
78
81
  // Load list
79
82
  loadData(keyword, id, maxItems).then((options) => {
80
- if (!isMounted.current)
83
+ if (!state.current.isMounted)
81
84
  return;
82
85
  if (options != null && options.length >= maxItems) {
83
86
  options.push({ id: -1, name: "n/a" });
@@ -113,39 +116,41 @@ export function TiplistPro(props) {
113
116
  if (localIdValue == null) {
114
117
  if (inputValue != null)
115
118
  setInputValue(null);
119
+ return;
116
120
  }
117
- else if (state.idLoaded) {
118
- state.idLoaded = false;
119
- state.idSet = false;
121
+ if (state.current.idLoaded) {
122
+ state.current.idLoaded = false;
123
+ state.current.idSet = false;
120
124
  }
121
125
  }, [localIdValue]);
122
126
  React.useEffect(() => {
123
127
  if (localIdValue != null && localIdValue !== "") {
124
- if (state.idLoaded) {
128
+ if (state.current.idLoaded) {
125
129
  // Set default
126
- if (!state.idSet && states.options.length > 0) {
130
+ if (!state.current.idSet && states.options.length > 0) {
127
131
  stateUpdate({
128
132
  value: states.options.find((o) => o.id === localIdValue)
129
133
  });
130
- state.idSet = true;
134
+ state.current.idSet = true;
131
135
  }
132
136
  }
133
137
  else {
134
138
  // Load id data
135
139
  loadDataDirect(undefined, localIdValue);
136
- state.idLoaded = true;
140
+ state.current.idLoaded = true;
137
141
  }
138
142
  }
139
- }, [state.idLoaded, state.idSet, localIdValue, states.options]);
143
+ }, [localIdValue, states.options]);
140
144
  React.useEffect(() => {
145
+ state.current.isMounted = true;
141
146
  return () => {
142
- isMounted.current = false;
147
+ state.current.isMounted = false;
143
148
  delayed.clear();
144
149
  };
145
150
  }, []);
146
151
  // Layout
147
152
  return (React.createElement("div", null,
148
- React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: "none" }, name: name, value: (_b = inputValue !== null && inputValue !== void 0 ? inputValue : localIdValue) !== null && _b !== void 0 ? _b : "", readOnly: true, onChange: inputOnChange }),
153
+ React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: "none" }, name: name, value: inputValue !== null && inputValue !== void 0 ? inputValue : (state.current.idSet ? "" : localIdValue !== null && localIdValue !== void 0 ? localIdValue : ""), readOnly: true, onChange: inputOnChange }),
149
154
  React.createElement(Autocomplete, { filterOptions: (options, _state) => options, value: states.value, options: states.options, freeSolo: true, clearOnBlur: false, onChange: (event, value, reason, details) => {
150
155
  if (typeof value === "object") {
151
156
  // Set value
@@ -1,4 +1,4 @@
1
- import { IActionResult, IUser } from '@etsoo/appscript';
1
+ import { IActionResult, IUser } from "@etsoo/appscript";
2
2
  /**
3
3
  * Service user interface
4
4
  */
@@ -7,6 +7,10 @@ export interface IServiceUser extends IUser {
7
7
  * Service device id
8
8
  */
9
9
  serviceDeviceId: string;
10
+ /**
11
+ * Service passphrase encrypted
12
+ */
13
+ servicePassphrase: string;
10
14
  }
11
15
  /**
12
16
  * Service user login result
@@ -1,10 +1,10 @@
1
- import { IApi, RefreshTokenProps, RefreshTokenRQ } from '@etsoo/appscript';
2
- import { IServiceApp } from './IServiceApp';
3
- import { IServiceAppSettings } from './IServiceAppSettings';
4
- import { IServicePageData } from './IServicePage';
5
- import { IServiceUser } from './IServiceUser';
6
- import { ISmartERPUser } from './ISmartERPUser';
7
- import { ReactApp } from './ReactApp';
1
+ import { IApi, RefreshTokenProps, RefreshTokenRQ } from "@etsoo/appscript";
2
+ import { IServiceApp } from "./IServiceApp";
3
+ import { IServiceAppSettings } from "./IServiceAppSettings";
4
+ import { IServicePageData } from "./IServicePage";
5
+ import { IServiceUser } from "./IServiceUser";
6
+ import { ISmartERPUser } from "./ISmartERPUser";
7
+ import { ReactApp } from "./ReactApp";
8
8
  /**
9
9
  * Core Service App
10
10
  * Service login to core system, get the refesh token and access token
@@ -1,7 +1,7 @@
1
- import { BridgeUtils, createClient } from '@etsoo/appscript';
2
- import { CoreConstants } from '@etsoo/react';
3
- import { DomUtils } from '@etsoo/shared';
4
- import { ReactApp } from './ReactApp';
1
+ import { BridgeUtils, createClient } from "@etsoo/appscript";
2
+ import { CoreConstants } from "@etsoo/react";
3
+ import { DomUtils } from "@etsoo/shared";
4
+ import { ReactApp } from "./ReactApp";
5
5
  /**
6
6
  * Core Service App
7
7
  * Service login to core system, get the refesh token and access token
@@ -28,10 +28,10 @@ export class ServiceApp extends ReactApp {
28
28
  /**
29
29
  * Service passphrase
30
30
  */
31
- this.servicePassphrase = '';
31
+ this.servicePassphrase = "";
32
32
  // Check
33
33
  if (settings.serviceId == null || settings.serviceEndpoint == null) {
34
- throw new Error('No service settings');
34
+ throw new Error("No service settings");
35
35
  }
36
36
  // Service API
37
37
  const api = createClient();
@@ -48,7 +48,7 @@ export class ServiceApp extends ReactApp {
48
48
  window.location.href = this.settings.webUrl;
49
49
  }
50
50
  else {
51
- BridgeUtils.host.loadApp('core');
51
+ BridgeUtils.host.loadApp("core");
52
52
  }
53
53
  }
54
54
  /**
@@ -56,13 +56,13 @@ export class ServiceApp extends ReactApp {
56
56
  * @param tryLogin Try to login again
57
57
  */
58
58
  toLoginPage(tryLogin) {
59
- const parameters = `?serviceId=${this.settings.serviceId}&${DomUtils.CultureField}=${this.culture}${tryLogin ? '' : '&tryLogin=false'}`;
59
+ const parameters = `?serviceId=${this.settings.serviceId}&${DomUtils.CultureField}=${this.culture}${tryLogin ? "" : "&tryLogin=false"}`;
60
60
  if (BridgeUtils.host == null) {
61
61
  const coreUrl = this.settings.webUrl;
62
62
  window.location.href = coreUrl + parameters;
63
63
  }
64
64
  else {
65
- BridgeUtils.host.loadApp('core', parameters);
65
+ BridgeUtils.host.loadApp("core", parameters);
66
66
  }
67
67
  }
68
68
  /**
@@ -74,7 +74,7 @@ export class ServiceApp extends ReactApp {
74
74
  const { callback, data, relogin = false, showLoading = false } = props !== null && props !== void 0 ? props : {};
75
75
  // Token
76
76
  const token = this.getCacheToken();
77
- if (token == null || token === '') {
77
+ if (token == null || token === "") {
78
78
  if (callback)
79
79
  callback(false);
80
80
  return false;
@@ -103,13 +103,13 @@ export class ServiceApp extends ReactApp {
103
103
  const refreshToken = this.getResponseToken(payload.response);
104
104
  if (refreshToken == null || result.data == null) {
105
105
  if (failCallback)
106
- failCallback(this.get('noData'));
106
+ failCallback(this.get("noData"));
107
107
  return false;
108
108
  }
109
109
  // User data
110
110
  const userData = result.data;
111
111
  // Use core system access token to service api to exchange service access token
112
- const serviceResult = await this.serviceApi.put('Auth/ExchangeToken', {
112
+ const serviceResult = await this.serviceApi.put("Auth/ExchangeToken", {
113
113
  token: this.encryptEnhanced(userData.token, this.settings.serviceId.toString())
114
114
  }, {
115
115
  showLoading,
@@ -129,7 +129,7 @@ export class ServiceApp extends ReactApp {
129
129
  }
130
130
  if (serviceResult.data == null) {
131
131
  if (failCallback)
132
- failCallback(this.get('noData'));
132
+ failCallback(this.get("noData"));
133
133
  return false;
134
134
  }
135
135
  // Login
@@ -140,14 +140,14 @@ export class ServiceApp extends ReactApp {
140
140
  return true;
141
141
  };
142
142
  // Call API
143
- const result = await this.api.put('Auth/RefreshToken', rq, payload);
143
+ const result = await this.api.put("Auth/RefreshToken", rq, payload);
144
144
  if (result == null)
145
145
  return false;
146
146
  if (!result.ok) {
147
- if (result.type === 'TokenExpired' && relogin) {
147
+ if (result.type === "TokenExpired" && relogin) {
148
148
  // Try login
149
149
  // Dialog to receive password
150
- var labels = this.getLabels('reloginTip', 'login');
150
+ var labels = this.getLabels("reloginTip", "login");
151
151
  this.notifier.prompt(labels.reloginTip, async (pwd) => {
152
152
  if (pwd == null) {
153
153
  this.toLoginPage();
@@ -156,7 +156,7 @@ export class ServiceApp extends ReactApp {
156
156
  // Set password for the action
157
157
  rq.pwd = this.encrypt(this.hash(pwd));
158
158
  // Submit again
159
- const result = await this.api.put('Auth/RefreshToken', rq, payload);
159
+ const result = await this.api.put("Auth/RefreshToken", rq, payload);
160
160
  if (result == null)
161
161
  return;
162
162
  if (result.ok) {
@@ -175,7 +175,7 @@ export class ServiceApp extends ReactApp {
175
175
  // Popup message
176
176
  this.alertResult(result);
177
177
  return false;
178
- }, labels.login, { type: 'password' });
178
+ }, labels.login, { type: "password" });
179
179
  // Fake truth to avoid reloading
180
180
  return true;
181
181
  }
@@ -233,7 +233,7 @@ export class ServiceApp extends ReactApp {
233
233
  var _a;
234
234
  // Service user login
235
235
  this.servicePassphrase =
236
- (_a = this.decrypt(serviceUser.serviceDeviceId, this.settings.serviceId.toString())) !== null && _a !== void 0 ? _a : '';
236
+ (_a = this.decrypt(serviceUser.servicePassphrase, this.settings.serviceId.toString())) !== null && _a !== void 0 ? _a : "";
237
237
  // Service user
238
238
  this.serviceUser = serviceUser;
239
239
  // Service API token
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.2.26",
3
+ "version": "1.2.28",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
package/src/Tiplist.tsx CHANGED
@@ -129,12 +129,12 @@ export function Tiplist<
129
129
  if (localValue != null) stateUpdate({ value: localValue });
130
130
  }, [localValue]);
131
131
 
132
- // State
133
- const [state] = React.useState<{
134
- idLoaded?: boolean;
135
- idSet?: boolean;
136
- }>({});
137
- const isMounted = React.useRef(true);
132
+ // Ref
133
+ const state = React.useRef({
134
+ idLoaded: false,
135
+ idSet: false,
136
+ isMounted: false
137
+ });
138
138
 
139
139
  // Add readOnly
140
140
  const addReadOnly = (params: AutocompleteRenderInputParams) => {
@@ -189,7 +189,7 @@ export function Tiplist<
189
189
 
190
190
  // Load list
191
191
  loadData(keyword, id, maxItems).then((options) => {
192
- if (!isMounted.current) return;
192
+ if (!state.current.isMounted) return;
193
193
 
194
194
  if (options != null && options.length >= maxItems) {
195
195
  options.push({ [idField]: "n/a" } as T);
@@ -228,36 +228,40 @@ export function Tiplist<
228
228
  React.useEffect(() => {
229
229
  if (localIdValue == null) {
230
230
  if (inputValue != null) setInputValue(null);
231
- } else if (state.idLoaded) {
232
- state.idLoaded = false;
233
- state.idSet = false;
231
+ return;
232
+ }
233
+
234
+ if (state.current.idLoaded) {
235
+ state.current.idLoaded = false;
236
+ state.current.idSet = false;
234
237
  }
235
238
  }, [localIdValue]);
236
239
 
237
240
  React.useEffect(() => {
238
241
  if (localIdValue != null && (localIdValue as any) !== "") {
239
- if (state.idLoaded) {
242
+ if (state.current.idLoaded) {
240
243
  // Set default
241
- if (!state.idSet && states.options.length > 0) {
244
+ if (!state.current.idSet && states.options.length > 0) {
242
245
  const item = states.options.find((o) => o[idField] === localIdValue);
243
246
  if (item) {
244
247
  stateUpdate({
245
248
  value: states.options[0]
246
249
  });
247
- state.idSet = true;
250
+ state.current.idSet = true;
248
251
  }
249
252
  }
250
253
  } else {
251
254
  // Load id data
252
255
  loadDataDirect(undefined, localIdValue);
253
- state.idLoaded = true;
256
+ state.current.idLoaded = true;
254
257
  }
255
258
  }
256
- }, [state.idLoaded, state.idSet, localIdValue, states.options]);
259
+ }, [localIdValue, states.options]);
257
260
 
258
261
  React.useEffect(() => {
262
+ state.current.isMounted = true;
259
263
  return () => {
260
- isMounted.current = false;
264
+ state.current.isMounted = false;
261
265
  delayed.clear();
262
266
  };
263
267
  }, []);
@@ -271,7 +275,9 @@ export function Tiplist<
271
275
  type="text"
272
276
  style={{ display: "none" }}
273
277
  name={name}
274
- value={`${inputValue ?? localIdValue ?? ""}`}
278
+ value={`${
279
+ inputValue ?? (state.current.idSet ? "" : localIdValue ?? "")
280
+ }`}
275
281
  readOnly
276
282
  onChange={inputOnChange}
277
283
  />
@@ -152,12 +152,12 @@ export function TiplistPro<T extends ListType2 = ListType2>(
152
152
  [states.value]
153
153
  );
154
154
 
155
- // State
156
- const [state] = React.useState<{
157
- idLoaded?: boolean;
158
- idSet?: boolean;
159
- }>({});
160
- const isMounted = React.useRef(true);
155
+ // Ref
156
+ const state = React.useRef({
157
+ idLoaded: false,
158
+ idSet: false,
159
+ isMounted: false
160
+ });
161
161
 
162
162
  // Change handler
163
163
  const changeHandle = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -199,7 +199,7 @@ export function TiplistPro<T extends ListType2 = ListType2>(
199
199
 
200
200
  // Load list
201
201
  loadData(keyword, id, maxItems).then((options) => {
202
- if (!isMounted.current) return;
202
+ if (!state.current.isMounted) return;
203
203
 
204
204
  if (options != null && options.length >= maxItems) {
205
205
  options.push({ id: -1, name: "n/a" } as T);
@@ -238,33 +238,37 @@ export function TiplistPro<T extends ListType2 = ListType2>(
238
238
  React.useEffect(() => {
239
239
  if (localIdValue == null) {
240
240
  if (inputValue != null) setInputValue(null);
241
- } else if (state.idLoaded) {
242
- state.idLoaded = false;
243
- state.idSet = false;
241
+ return;
242
+ }
243
+
244
+ if (state.current.idLoaded) {
245
+ state.current.idLoaded = false;
246
+ state.current.idSet = false;
244
247
  }
245
248
  }, [localIdValue]);
246
249
 
247
250
  React.useEffect(() => {
248
251
  if (localIdValue != null && (localIdValue as any) !== "") {
249
- if (state.idLoaded) {
252
+ if (state.current.idLoaded) {
250
253
  // Set default
251
- if (!state.idSet && states.options.length > 0) {
254
+ if (!state.current.idSet && states.options.length > 0) {
252
255
  stateUpdate({
253
256
  value: states.options.find((o) => o.id === localIdValue)
254
257
  });
255
- state.idSet = true;
258
+ state.current.idSet = true;
256
259
  }
257
260
  } else {
258
261
  // Load id data
259
262
  loadDataDirect(undefined, localIdValue);
260
- state.idLoaded = true;
263
+ state.current.idLoaded = true;
261
264
  }
262
265
  }
263
- }, [state.idLoaded, state.idSet, localIdValue, states.options]);
266
+ }, [localIdValue, states.options]);
264
267
 
265
268
  React.useEffect(() => {
269
+ state.current.isMounted = true;
266
270
  return () => {
267
- isMounted.current = false;
271
+ state.current.isMounted = false;
268
272
  delayed.clear();
269
273
  };
270
274
  }, []);
@@ -278,7 +282,7 @@ export function TiplistPro<T extends ListType2 = ListType2>(
278
282
  type="text"
279
283
  style={{ display: "none" }}
280
284
  name={name}
281
- value={inputValue ?? localIdValue ?? ""}
285
+ value={inputValue ?? (state.current.idSet ? "" : localIdValue ?? "")}
282
286
  readOnly
283
287
  onChange={inputOnChange}
284
288
  />
@@ -1,17 +1,22 @@
1
- import { IActionResult, IUser } from '@etsoo/appscript';
1
+ import { IActionResult, IUser } from "@etsoo/appscript";
2
2
 
3
3
  /**
4
4
  * Service user interface
5
5
  */
6
6
  export interface IServiceUser extends IUser {
7
- /**
8
- * Service device id
9
- */
10
- serviceDeviceId: string;
7
+ /**
8
+ * Service device id
9
+ */
10
+ serviceDeviceId: string;
11
+
12
+ /**
13
+ * Service passphrase encrypted
14
+ */
15
+ servicePassphrase: string;
11
16
  }
12
17
 
13
18
  /**
14
19
  * Service user login result
15
20
  */
16
21
  export type ServiceLoginResult<U extends IServiceUser = IServiceUser> =
17
- IActionResult<U>;
22
+ IActionResult<U>;
@@ -1,21 +1,21 @@
1
1
  import {
2
- BridgeUtils,
3
- createClient,
4
- IActionResult,
5
- IApi,
6
- IApiPayload,
7
- RefreshTokenProps,
8
- RefreshTokenResult,
9
- RefreshTokenRQ
10
- } from '@etsoo/appscript';
11
- import { CoreConstants } from '@etsoo/react';
12
- import { DomUtils } from '@etsoo/shared';
13
- import { IServiceApp } from './IServiceApp';
14
- import { IServiceAppSettings } from './IServiceAppSettings';
15
- import { IServicePageData } from './IServicePage';
16
- import { IServiceUser, ServiceLoginResult } from './IServiceUser';
17
- import { ISmartERPUser } from './ISmartERPUser';
18
- import { ReactApp } from './ReactApp';
2
+ BridgeUtils,
3
+ createClient,
4
+ IActionResult,
5
+ IApi,
6
+ IApiPayload,
7
+ RefreshTokenProps,
8
+ RefreshTokenResult,
9
+ RefreshTokenRQ
10
+ } from "@etsoo/appscript";
11
+ import { CoreConstants } from "@etsoo/react";
12
+ import { DomUtils } from "@etsoo/shared";
13
+ import { IServiceApp } from "./IServiceApp";
14
+ import { IServiceAppSettings } from "./IServiceAppSettings";
15
+ import { IServicePageData } from "./IServicePage";
16
+ import { IServiceUser, ServiceLoginResult } from "./IServiceUser";
17
+ import { ISmartERPUser } from "./ISmartERPUser";
18
+ import { ReactApp } from "./ReactApp";
19
19
 
20
20
  /**
21
21
  * Core Service App
@@ -24,332 +24,321 @@ import { ReactApp } from './ReactApp';
24
24
  * Use the new acess token and refresh token to login
25
25
  */
26
26
  export class ServiceApp<
27
- U extends IServiceUser = IServiceUser,
28
- P extends IServicePageData = IServicePageData,
29
- S extends IServiceAppSettings = IServiceAppSettings
30
- >
31
- extends ReactApp<S, ISmartERPUser, P>
32
- implements IServiceApp
27
+ U extends IServiceUser = IServiceUser,
28
+ P extends IServicePageData = IServicePageData,
29
+ S extends IServiceAppSettings = IServiceAppSettings
30
+ >
31
+ extends ReactApp<S, ISmartERPUser, P>
32
+ implements IServiceApp
33
33
  {
34
- /**
35
- * Service API
36
- */
37
- readonly serviceApi: IApi;
38
-
39
- private _serviceUser?: U;
40
- /**
41
- * Service user
42
- */
43
- get serviceUser() {
44
- return this._serviceUser;
45
- }
46
- protected set serviceUser(value: U | undefined) {
47
- this._serviceUser = value;
34
+ /**
35
+ * Service API
36
+ */
37
+ readonly serviceApi: IApi;
38
+
39
+ private _serviceUser?: U;
40
+ /**
41
+ * Service user
42
+ */
43
+ get serviceUser() {
44
+ return this._serviceUser;
45
+ }
46
+ protected set serviceUser(value: U | undefined) {
47
+ this._serviceUser = value;
48
+ }
49
+
50
+ /**
51
+ * Service passphrase
52
+ */
53
+ protected servicePassphrase: string = "";
54
+
55
+ /**
56
+ * Constructor
57
+ * @param settings Settings
58
+ * @param name Application name
59
+ */
60
+ constructor(settings: S, name: string) {
61
+ super(settings, name);
62
+
63
+ // Check
64
+ if (settings.serviceId == null || settings.serviceEndpoint == null) {
65
+ throw new Error("No service settings");
48
66
  }
49
67
 
50
- /**
51
- * Service passphrase
52
- */
53
- protected servicePassphrase: string = '';
54
-
55
- /**
56
- * Constructor
57
- * @param settings Settings
58
- * @param name Application name
59
- */
60
- constructor(settings: S, name: string) {
61
- super(settings, name);
62
-
63
- // Check
64
- if (settings.serviceId == null || settings.serviceEndpoint == null) {
65
- throw new Error('No service settings');
66
- }
68
+ // Service API
69
+ const api = createClient();
70
+ this.setApi(api);
67
71
 
68
- // Service API
69
- const api = createClient();
70
- this.setApi(api);
72
+ // Fix the baseUrl done by setupApi (Default is the settings.endpoint)
73
+ api.baseUrl = settings.serviceEndpoint;
71
74
 
72
- // Fix the baseUrl done by setupApi (Default is the settings.endpoint)
73
- api.baseUrl = settings.serviceEndpoint;
75
+ this.serviceApi = api;
76
+ }
74
77
 
75
- this.serviceApi = api;
78
+ /**
79
+ * Load SmartERP core
80
+ */
81
+ loadSmartERP() {
82
+ if (BridgeUtils.host == null) {
83
+ window.location.href = this.settings.webUrl;
84
+ } else {
85
+ BridgeUtils.host.loadApp("core");
76
86
  }
77
-
78
- /**
79
- * Load SmartERP core
80
- */
81
- loadSmartERP() {
82
- if (BridgeUtils.host == null) {
83
- window.location.href = this.settings.webUrl;
84
- } else {
85
- BridgeUtils.host.loadApp('core');
86
- }
87
+ }
88
+
89
+ /**
90
+ * Go to the login page
91
+ * @param tryLogin Try to login again
92
+ */
93
+ override toLoginPage(tryLogin?: boolean) {
94
+ const parameters = `?serviceId=${this.settings.serviceId}&${
95
+ DomUtils.CultureField
96
+ }=${this.culture}${tryLogin ? "" : "&tryLogin=false"}`;
97
+
98
+ if (BridgeUtils.host == null) {
99
+ const coreUrl = this.settings.webUrl;
100
+ window.location.href = coreUrl + parameters;
101
+ } else {
102
+ BridgeUtils.host.loadApp("core", parameters);
87
103
  }
88
-
89
- /**
90
- * Go to the login page
91
- * @param tryLogin Try to login again
92
- */
93
- override toLoginPage(tryLogin?: boolean) {
94
- const parameters = `?serviceId=${this.settings.serviceId}&${
95
- DomUtils.CultureField
96
- }=${this.culture}${tryLogin ? '' : '&tryLogin=false'}`;
97
-
98
- if (BridgeUtils.host == null) {
99
- const coreUrl = this.settings.webUrl;
100
- window.location.href = coreUrl + parameters;
101
- } else {
102
- BridgeUtils.host.loadApp('core', parameters);
103
- }
104
+ }
105
+
106
+ /**
107
+ * Refresh token
108
+ * @param props Props
109
+ */
110
+ override async refreshToken<D extends object = RefreshTokenRQ>(
111
+ props?: RefreshTokenProps<D>
112
+ ) {
113
+ // Destruct
114
+ const {
115
+ callback,
116
+ data,
117
+ relogin = false,
118
+ showLoading = false
119
+ } = props ?? {};
120
+
121
+ // Token
122
+ const token = this.getCacheToken();
123
+ if (token == null || token === "") {
124
+ if (callback) callback(false);
125
+ return false;
104
126
  }
105
127
 
106
- /**
107
- * Refresh token
108
- * @param props Props
109
- */
110
- override async refreshToken<D extends object = RefreshTokenRQ>(
111
- props?: RefreshTokenProps<D>
112
- ) {
113
- // Destruct
114
- const {
115
- callback,
116
- data,
117
- relogin = false,
118
- showLoading = false
119
- } = props ?? {};
120
-
121
- // Token
122
- const token = this.getCacheToken();
123
- if (token == null || token === '') {
124
- if (callback) callback(false);
128
+ // Reqest data
129
+ // Merge additional data passed
130
+ const rq: RefreshTokenRQ = {
131
+ deviceId: this.deviceId,
132
+ timezone: this.getTimeZone(),
133
+ ...data
134
+ };
135
+
136
+ // Login result type
137
+ type LoginResult = IActionResult<U>;
138
+
139
+ // Payload
140
+ const payload: IApiPayload<LoginResult, any> = {
141
+ showLoading,
142
+ config: { headers: { [CoreConstants.TokenHeaderRefresh]: token } },
143
+ onError: (error) => {
144
+ if (callback) callback(error);
145
+
146
+ // Prevent further processing
147
+ return false;
148
+ }
149
+ };
150
+
151
+ // Success callback
152
+ const success = async (
153
+ result: LoginResult,
154
+ failCallback?: (result: RefreshTokenResult) => void
155
+ ) => {
156
+ // Token
157
+ const refreshToken = this.getResponseToken(payload.response);
158
+ if (refreshToken == null || result.data == null) {
159
+ if (failCallback) failCallback(this.get("noData")!);
160
+ return false;
161
+ }
162
+
163
+ // User data
164
+ const userData = result.data;
165
+
166
+ // Use core system access token to service api to exchange service access token
167
+ const serviceResult = await this.serviceApi.put<ServiceLoginResult<U>>(
168
+ "Auth/ExchangeToken",
169
+ {
170
+ token: this.encryptEnhanced(
171
+ userData.token,
172
+ this.settings.serviceId.toString()
173
+ )
174
+ },
175
+ {
176
+ showLoading,
177
+ onError: (error) => {
178
+ if (failCallback) failCallback(error);
179
+
180
+ // Prevent further processing
125
181
  return false;
182
+ }
126
183
  }
127
-
128
- // Reqest data
129
- // Merge additional data passed
130
- const rq: RefreshTokenRQ = {
131
- deviceId: this.deviceId,
132
- timezone: this.getTimeZone(),
133
- ...data
134
- };
135
-
136
- // Login result type
137
- type LoginResult = IActionResult<U>;
138
-
139
- // Payload
140
- const payload: IApiPayload<LoginResult, any> = {
141
- showLoading,
142
- config: { headers: { [CoreConstants.TokenHeaderRefresh]: token } },
143
- onError: (error) => {
144
- if (callback) callback(error);
145
-
146
- // Prevent further processing
147
- return false;
148
- }
149
- };
150
-
151
- // Success callback
152
- const success = async (
153
- result: LoginResult,
154
- failCallback?: (result: RefreshTokenResult) => void
155
- ) => {
156
- // Token
157
- const refreshToken = this.getResponseToken(payload.response);
158
- if (refreshToken == null || result.data == null) {
159
- if (failCallback) failCallback(this.get('noData')!);
160
- return false;
161
- }
162
-
163
- // User data
164
- const userData = result.data;
165
-
166
- // Use core system access token to service api to exchange service access token
167
- const serviceResult = await this.serviceApi.put<
168
- ServiceLoginResult<U>
169
- >(
170
- 'Auth/ExchangeToken',
171
- {
172
- token: this.encryptEnhanced(
173
- userData.token,
174
- this.settings.serviceId.toString()
175
- )
176
- },
177
- {
178
- showLoading,
179
- onError: (error) => {
180
- if (failCallback) failCallback(error);
181
-
182
- // Prevent further processing
183
- return false;
184
- }
185
- }
186
- );
187
-
188
- if (serviceResult == null) return false;
189
-
190
- if (!serviceResult.ok) {
191
- if (failCallback) failCallback(serviceResult);
192
- return false;
184
+ );
185
+
186
+ if (serviceResult == null) return false;
187
+
188
+ if (!serviceResult.ok) {
189
+ if (failCallback) failCallback(serviceResult);
190
+ return false;
191
+ }
192
+
193
+ if (serviceResult.data == null) {
194
+ if (failCallback) failCallback(this.get("noData")!);
195
+ return false;
196
+ }
197
+
198
+ // Login
199
+ this.userLoginEx(userData, refreshToken, serviceResult.data);
200
+
201
+ // Success callback
202
+ if (failCallback) failCallback(true);
203
+
204
+ return true;
205
+ };
206
+
207
+ // Call API
208
+ const result = await this.api.put<LoginResult>(
209
+ "Auth/RefreshToken",
210
+ rq,
211
+ payload
212
+ );
213
+ if (result == null) return false;
214
+
215
+ if (!result.ok) {
216
+ if (result.type === "TokenExpired" && relogin) {
217
+ // Try login
218
+ // Dialog to receive password
219
+ var labels = this.getLabels("reloginTip", "login");
220
+ this.notifier.prompt(
221
+ labels.reloginTip,
222
+ async (pwd) => {
223
+ if (pwd == null) {
224
+ this.toLoginPage();
225
+ return;
193
226
  }
194
227
 
195
- if (serviceResult.data == null) {
196
- if (failCallback) failCallback(this.get('noData')!);
197
- return false;
198
- }
228
+ // Set password for the action
229
+ rq.pwd = this.encrypt(this.hash(pwd));
199
230
 
200
- // Login
201
- this.userLoginEx(userData, refreshToken, serviceResult.data);
231
+ // Submit again
232
+ const result = await this.api.put<LoginResult>(
233
+ "Auth/RefreshToken",
234
+ rq,
235
+ payload
236
+ );
202
237
 
203
- // Success callback
204
- if (failCallback) failCallback(true);
238
+ if (result == null) return;
205
239
 
206
- return true;
207
- };
240
+ if (result.ok) {
241
+ await success(result, (loginResult: RefreshTokenResult) => {
242
+ if (loginResult === true) {
243
+ if (callback) callback(true);
244
+ return;
245
+ }
208
246
 
209
- // Call API
210
- const result = await this.api.put<LoginResult>(
211
- 'Auth/RefreshToken',
212
- rq,
213
- payload
214
- );
215
- if (result == null) return false;
216
-
217
- if (!result.ok) {
218
- if (result.type === 'TokenExpired' && relogin) {
219
- // Try login
220
- // Dialog to receive password
221
- var labels = this.getLabels('reloginTip', 'login');
222
- this.notifier.prompt(
223
- labels.reloginTip,
224
- async (pwd) => {
225
- if (pwd == null) {
226
- this.toLoginPage();
227
- return;
228
- }
229
-
230
- // Set password for the action
231
- rq.pwd = this.encrypt(this.hash(pwd));
232
-
233
- // Submit again
234
- const result = await this.api.put<LoginResult>(
235
- 'Auth/RefreshToken',
236
- rq,
237
- payload
238
- );
239
-
240
- if (result == null) return;
241
-
242
- if (result.ok) {
243
- await success(
244
- result,
245
- (loginResult: RefreshTokenResult) => {
246
- if (loginResult === true) {
247
- if (callback) callback(true);
248
- return;
249
- }
250
-
251
- const message =
252
- this.formatRefreshTokenResult(
253
- loginResult
254
- );
255
- if (message) this.notifier.alert(message);
256
- }
257
- );
258
- return;
259
- }
260
-
261
- // Popup message
262
- this.alertResult(result);
263
- return false;
264
- },
265
- labels.login,
266
- { type: 'password' }
267
- );
268
-
269
- // Fake truth to avoid reloading
270
- return true;
247
+ const message = this.formatRefreshTokenResult(loginResult);
248
+ if (message) this.notifier.alert(message);
249
+ });
250
+ return;
271
251
  }
272
252
 
273
- if (callback) callback(result);
253
+ // Popup message
254
+ this.alertResult(result);
274
255
  return false;
275
- }
276
-
277
- return await success(result, callback);
278
- }
279
-
280
- /**
281
- * Service decrypt message
282
- * @param messageEncrypted Encrypted message
283
- * @param passphrase Secret passphrase
284
- * @returns Pure text
285
- */
286
- serviceDecrypt(messageEncrypted: string, passphrase?: string) {
287
- return this.decrypt(
288
- messageEncrypted,
289
- passphrase ?? this.servicePassphrase
256
+ },
257
+ labels.login,
258
+ { type: "password" }
290
259
  );
291
- }
292
260
 
293
- /**
294
- * Service encrypt message
295
- * @param message Message
296
- * @param passphrase Secret passphrase
297
- * @param iterations Iterations, 1000 times, 1 - 99
298
- * @returns Result
299
- */
300
- serviceEncrypt(message: string, passphrase?: string, iterations?: number) {
301
- return this.encrypt(
302
- message,
303
- passphrase ?? this.servicePassphrase,
304
- iterations
305
- );
306
- }
261
+ // Fake truth to avoid reloading
262
+ return true;
263
+ }
307
264
 
308
- /**
309
- * Try login
310
- * @param data Additional data
311
- * @param showLoading Show loading bar or not
312
- * @returns Result
313
- */
314
- override async tryLogin<D extends object = {}>(
315
- data?: D,
316
- showLoading?: boolean
317
- ) {
318
- // Reset user state
319
- const result = await super.tryLogin(data, showLoading);
320
- if (!result) return false;
321
-
322
- // Refresh token
323
- return await this.refreshToken({
324
- callback: (result) => this.doRefreshTokenResult(result),
325
- data,
326
- showLoading,
327
- relogin: true
328
- });
265
+ if (callback) callback(result);
266
+ return false;
329
267
  }
330
268
 
331
- /**
332
- * User login extended
333
- * @param user Core system user
334
- * @param refreshToken Refresh token
335
- * @param serviceUser Service user
336
- */
337
- userLoginEx(user: ISmartERPUser, refreshToken: string, serviceUser: U) {
338
- // Service user login
339
- this.servicePassphrase =
340
- this.decrypt(
341
- serviceUser.serviceDeviceId,
342
- this.settings.serviceId.toString()
343
- ) ?? '';
344
-
345
- // Service user
346
- this.serviceUser = serviceUser;
347
-
348
- // Service API token
349
- this.serviceApi.authorize(this.settings.authScheme, serviceUser.token);
350
-
351
- // Keep = true, means service could hold the refresh token for long access
352
- // Trigger Context change and serviceUser is ready then
353
- super.userLogin(user, refreshToken, true);
354
- }
269
+ return await success(result, callback);
270
+ }
271
+
272
+ /**
273
+ * Service decrypt message
274
+ * @param messageEncrypted Encrypted message
275
+ * @param passphrase Secret passphrase
276
+ * @returns Pure text
277
+ */
278
+ serviceDecrypt(messageEncrypted: string, passphrase?: string) {
279
+ return this.decrypt(messageEncrypted, passphrase ?? this.servicePassphrase);
280
+ }
281
+
282
+ /**
283
+ * Service encrypt message
284
+ * @param message Message
285
+ * @param passphrase Secret passphrase
286
+ * @param iterations Iterations, 1000 times, 1 - 99
287
+ * @returns Result
288
+ */
289
+ serviceEncrypt(message: string, passphrase?: string, iterations?: number) {
290
+ return this.encrypt(
291
+ message,
292
+ passphrase ?? this.servicePassphrase,
293
+ iterations
294
+ );
295
+ }
296
+
297
+ /**
298
+ * Try login
299
+ * @param data Additional data
300
+ * @param showLoading Show loading bar or not
301
+ * @returns Result
302
+ */
303
+ override async tryLogin<D extends object = {}>(
304
+ data?: D,
305
+ showLoading?: boolean
306
+ ) {
307
+ // Reset user state
308
+ const result = await super.tryLogin(data, showLoading);
309
+ if (!result) return false;
310
+
311
+ // Refresh token
312
+ return await this.refreshToken({
313
+ callback: (result) => this.doRefreshTokenResult(result),
314
+ data,
315
+ showLoading,
316
+ relogin: true
317
+ });
318
+ }
319
+
320
+ /**
321
+ * User login extended
322
+ * @param user Core system user
323
+ * @param refreshToken Refresh token
324
+ * @param serviceUser Service user
325
+ */
326
+ userLoginEx(user: ISmartERPUser, refreshToken: string, serviceUser: U) {
327
+ // Service user login
328
+ this.servicePassphrase =
329
+ this.decrypt(
330
+ serviceUser.servicePassphrase,
331
+ this.settings.serviceId.toString()
332
+ ) ?? "";
333
+
334
+ // Service user
335
+ this.serviceUser = serviceUser;
336
+
337
+ // Service API token
338
+ this.serviceApi.authorize(this.settings.authScheme, serviceUser.token);
339
+
340
+ // Keep = true, means service could hold the refresh token for long access
341
+ // Trigger Context change and serviceUser is ready then
342
+ super.userLogin(user, refreshToken, true);
343
+ }
355
344
  }