@etsoo/appscript 1.5.97 → 1.5.99

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/README.md CHANGED
@@ -55,7 +55,6 @@ $ yarn add @etsoo/appscript
55
55
  #### ExternalSettings.ts
56
56
 
57
57
  - IExternalSettings - External settings items
58
- - IExternalSettingsHost - External settings host passed by external script
59
58
 
60
59
  #### UserRole.ts
61
60
 
@@ -1,6 +1,11 @@
1
1
  import { ApiDataError, ApiMethod } from "@etsoo/restclient";
2
2
  import { DataTypes, IActionResult } from "@etsoo/shared";
3
- import { EntityStatus, UserRole } from "../../src";
3
+ import {
4
+ EntityStatus,
5
+ ExternalEndpoint,
6
+ ExternalSettings,
7
+ UserRole
8
+ } from "../../src";
4
9
  import { TestApp } from "./TestApp";
5
10
 
6
11
  // Arrange
@@ -16,7 +21,7 @@ const app = new appClass();
16
21
 
17
22
  await app.changeCulture(app.settings.cultures[0]);
18
23
 
19
- test("Test for domain replacement", () => {
24
+ test("Test for domain substitution", () => {
20
25
  expect(app.settings.endpoint).toBe("http://localhost:9000/api/");
21
26
 
22
27
  expect(app.settings.endpoints?.core.endpoint).toBe(
@@ -24,6 +29,52 @@ test("Test for domain replacement", () => {
24
29
  );
25
30
  });
26
31
 
32
+ test("Test for formatApp", () => {
33
+ expect(
34
+ ExternalSettings.formatApp(
35
+ "admin.app.local",
36
+ "core",
37
+ "https://{hostname}/api/"
38
+ )
39
+ ).toBe("https://core.app.local/api/");
40
+
41
+ expect(
42
+ ExternalSettings.formatApp("localhost", "admin", "https://{hostname}/api/")
43
+ ).toBe("https://localhost/api/");
44
+
45
+ // Custom sub domain match
46
+ ExternalSettings.subDomainMatch = /app(?=\.)/i;
47
+
48
+ expect(
49
+ ExternalSettings.formatApp(
50
+ "admin.app.local",
51
+ "core",
52
+ "https://{hostname}/api/"
53
+ )
54
+ ).toBe("https://admin.core.local/api/");
55
+ });
56
+
57
+ test("Test for formatHost with endpoints", () => {
58
+ // Reset sub domain match
59
+ ExternalSettings.subDomainMatch = /(?<=\/\/)[0-9a-z]+(?=\.)/i;
60
+
61
+ const endpoints: Record<string, ExternalEndpoint> = {
62
+ core: {
63
+ endpoint: "https://{hostname}/api/",
64
+ webUrl: "https://{hostname}/"
65
+ }
66
+ };
67
+
68
+ const result = ExternalSettings.formatHost(endpoints, "admin.app.local");
69
+
70
+ expect(result).toStrictEqual({
71
+ core: {
72
+ endpoint: "https://core.app.local/api/",
73
+ webUrl: "https://core.app.local/"
74
+ }
75
+ });
76
+ });
77
+
27
78
  test("Test for properties", () => {
28
79
  expect(app.settings.currentRegion.label).toBe("中国大陆");
29
80
  });
@@ -16,7 +16,7 @@ import {
16
16
  InitCallResultData,
17
17
  IUser
18
18
  } from "../../src";
19
- import { DataTypes, DomUtils, Utils, WindowStorage } from "@etsoo/shared";
19
+ import { DataTypes, DomUtils, WindowStorage } from "@etsoo/shared";
20
20
 
21
21
  // Detected country or region
22
22
  const { detectedCountry } = DomUtils;
@@ -72,7 +72,9 @@ export class TestApp extends CoreApp<
72
72
  */
73
73
  constructor() {
74
74
  super(
75
- ExternalSettings.format({
75
+ {
76
+ appId: 0,
77
+
76
78
  /**
77
79
  * Endpoint of the API service
78
80
  */
@@ -115,8 +117,8 @@ export class TestApp extends CoreApp<
115
117
  currentCulture: DomUtils.getCulture(
116
118
  supportedCultures,
117
119
  detectedCulture
118
- )![0]
119
- }),
120
+ )[0]
121
+ },
120
122
  createClient(),
121
123
  container,
122
124
  new WindowStorage(),
@@ -124,6 +126,16 @@ export class TestApp extends CoreApp<
124
126
  );
125
127
  }
126
128
 
129
+ // Example of local format settings
130
+ protected override formatSettings(settings: IAppSettings): IAppSettings {
131
+ const { endpoint, endpoints, ...rest } = settings;
132
+ return {
133
+ ...rest,
134
+ endpoint: ExternalSettings.formatHost(endpoint, "localhost"),
135
+ endpoints: ExternalSettings.formatHost(endpoints, "localhost")
136
+ };
137
+ }
138
+
127
139
  freshCountdownUI(callback?: () => PromiseLike<unknown>): void {
128
140
  throw new Error("Method not implemented.");
129
141
  }
@@ -4,7 +4,7 @@ import { BaseApi } from "./BaseApi";
4
4
  import { ResultPayload } from "./dto/ResultPayload";
5
5
  import { DataTypes, IActionResult } from "@etsoo/shared";
6
6
  import { RefreshTokenProps, RefreshTokenResult } from "../app/IApp";
7
- import { TokenRQ } from "./rq/TokenRQ";
7
+ import { TokenInputRQ } from "./rq/TokenRQ";
8
8
  import { ApiRefreshTokenDto } from "./dto/ApiRefreshTokenDto";
9
9
  import { GetLogInUrlRQ } from "./rq/GetLogInUrlRQ";
10
10
  import { LoginRQ } from "./rq/LoginRQ";
@@ -27,7 +27,7 @@ export declare class AuthApi extends BaseApi {
27
27
  * @param payload Payload
28
28
  * @returns Result
29
29
  */
30
- apiRefreshToken(rq: TokenRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
30
+ apiRefreshToken(rq: TokenInputRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
31
31
  /**
32
32
  * Authorization request
33
33
  * @param auth Authorization request data
@@ -56,7 +56,7 @@ export declare class AuthApi extends BaseApi {
56
56
  * @param payload Payload
57
57
  * @returns Result
58
58
  */
59
- exchangeToken(rq: TokenRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
59
+ exchangeToken(rq: TokenInputRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
60
60
  /**
61
61
  * Get log in url
62
62
  * @param rq Request data
@@ -14,7 +14,8 @@ class AuthApi extends BaseApi_1.BaseApi {
14
14
  * @returns Result
15
15
  */
16
16
  apiRefreshToken(rq, payload) {
17
- return this.api.put("Auth/ApiRefreshToken", rq, payload);
17
+ const data = { ...rq, timeZone: this.app.getTimeZone() };
18
+ return this.api.put("Auth/ApiRefreshToken", data, payload);
18
19
  }
19
20
  /**
20
21
  * Authorization request
@@ -63,7 +64,8 @@ class AuthApi extends BaseApi_1.BaseApi {
63
64
  * @returns Result
64
65
  */
65
66
  exchangeToken(rq, payload) {
66
- return this.api.put("Auth/ExchangeToken", rq, payload);
67
+ const data = { ...rq, timeZone: this.app.getTimeZone() };
68
+ return this.api.put("Auth/ExchangeToken", data, payload);
67
69
  }
68
70
  /**
69
71
  * Get log in url
@@ -1,8 +1,8 @@
1
- import { TokenRQ } from "./TokenRQ";
1
+ import { TokenInputRQ } from "./TokenRQ";
2
2
  /**
3
3
  * API Refresh Token Request data
4
4
  */
5
- export type ApiRefreshTokenRQ = TokenRQ & {
5
+ export type ApiRefreshTokenRQ = TokenInputRQ & {
6
6
  /**
7
7
  * Application ID, 0 for core system
8
8
  */
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * Token request data
3
3
  */
4
- export type TokenRQ = {
4
+ export type TokenInputRQ = {
5
5
  /**
6
6
  * Refresh token
7
7
  */
8
8
  token: string;
9
+ };
10
+ /**
11
+ * Token request data
12
+ */
13
+ export type TokenRQ = TokenInputRQ & {
9
14
  /**
10
15
  * Time zone
11
16
  */
@@ -176,6 +176,12 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
176
176
  * @param debug Debug mode
177
177
  */
178
178
  protected constructor(settings: S, api: IApi | undefined | null, notifier: INotifier<N, C>, storage: IStorage, name: string, debug?: boolean);
179
+ /**
180
+ * Format settings
181
+ * @param settings Original settings
182
+ * @returns Result
183
+ */
184
+ protected formatSettings(settings: S): S;
179
185
  private getDeviceId;
180
186
  private resetKeys;
181
187
  /**
@@ -164,11 +164,10 @@ class CoreApp {
164
164
  this.passphrase = "";
165
165
  this.apis = {};
166
166
  this.tasks = [];
167
- if (settings?.regions?.length === 0) {
168
- throw new Error("No regions defined");
169
- }
170
- this.settings = settings;
171
- const region = AddressRegion_1.AddressRegion.getById(settings.regions[0]);
167
+ // Format settings
168
+ this.settings = this.formatSettings(settings);
169
+ // Current region
170
+ const region = AddressRegion_1.AddressRegion.getById(this.settings.regions[0]);
172
171
  if (region == null) {
173
172
  throw new Error("No default region defined");
174
173
  }
@@ -177,7 +176,7 @@ class CoreApp {
177
176
  const refresh = async (api, rq) => {
178
177
  if (this.lastCalled) {
179
178
  // Call refreshToken to update access token
180
- await this.refreshToken({ token: rq.token, timeZone: rq.timeZone, showLoading: false }, (result) => {
179
+ await this.refreshToken({ token: rq.token, showLoading: false }, (result) => {
181
180
  if (result === true)
182
181
  return;
183
182
  console.log(`CoreApp.${this.name}.RefreshToken`, result);
@@ -232,6 +231,14 @@ class CoreApp {
232
231
  this.setup();
233
232
  });
234
233
  }
234
+ /**
235
+ * Format settings
236
+ * @param settings Original settings
237
+ * @returns Result
238
+ */
239
+ formatSettings(settings) {
240
+ return settings;
241
+ }
235
242
  getDeviceId() {
236
243
  return this.deviceId.substring(0, 15);
237
244
  }
@@ -1420,7 +1427,7 @@ class CoreApp {
1420
1427
  */
1421
1428
  async refreshToken(props, callback) {
1422
1429
  // Check props
1423
- props ?? (props = { timeZone: this.getTimeZone() });
1430
+ props ?? (props = {});
1424
1431
  props.token ?? (props.token = this.getCacheToken());
1425
1432
  // Call refresh token API
1426
1433
  let data = await this.createAuthApi().refreshToken(props);
@@ -1519,7 +1526,7 @@ class CoreApp {
1519
1526
  throw new Error("System API is not allowed to exchange token");
1520
1527
  }
1521
1528
  // Call the API quietly, no loading bar and no error popup
1522
- const data = await this.createAuthApi().exchangeToken({ token, timeZone: this.getTimeZone() }, {
1529
+ const data = await this.createAuthApi().exchangeToken({ token }, {
1523
1530
  showLoading: false,
1524
1531
  onError: (error) => {
1525
1532
  console.error(`CoreApp.${api.name}.ExchangeToken error`, error);
@@ -1610,8 +1617,6 @@ class CoreApp {
1610
1617
  return;
1611
1618
  // App id
1612
1619
  const appId = this.settings.appId;
1613
- // Timezone
1614
- const timeZone = this.getTimeZone();
1615
1620
  // APIs
1616
1621
  for (const name in this.apis) {
1617
1622
  // Get the API
@@ -1624,7 +1629,7 @@ class CoreApp {
1624
1629
  // Ready to trigger
1625
1630
  if (api[2] === 0) {
1626
1631
  // Refresh token
1627
- api[3](api[0], { appId, token: api[4], timeZone }).then((data) => {
1632
+ api[3](api[0], { appId, token: api[4] }).then((data) => {
1628
1633
  if (data == null) {
1629
1634
  // Failed, try it again in 2 seconds
1630
1635
  api[2] = 2;
@@ -31,15 +31,37 @@ export interface IExternalSettings extends ExternalEndpoint {
31
31
  /**
32
32
  * Endpoints to other services
33
33
  */
34
- readonly endpoints?: Record<"core" | "accounting" | "crm" | "calandar" | "task" | string, ExternalEndpoint>;
34
+ readonly endpoints?: Record<"core" | "admin" | "finance" | "crm" | "oa" | "agile" | string, ExternalEndpoint>;
35
35
  }
36
36
  /**
37
37
  * External settings namespace
38
38
  */
39
39
  export declare namespace ExternalSettings {
40
40
  /**
41
- * Create instance
41
+ * Sub domain match regular expression
42
42
  */
43
- function create<T extends IExternalSettings = IExternalSettings>(): T | undefined;
44
- function format(settings: any, hostname?: string): any;
43
+ let subDomainMatch: RegExp;
44
+ /**
45
+ * Create settings instance
46
+ * @param settings Settings
47
+ * @returns Result
48
+ */
49
+ function create<T extends IExternalSettings = IExternalSettings>(settings: unknown): T;
50
+ /**
51
+ * Format the app
52
+ * @param hostname Hostname
53
+ * @param app App key
54
+ * @param endpoint Endpoint
55
+ * @returns Result
56
+ */
57
+ function formatApp(hostname: string, app: string, endpoint: string): string;
58
+ /**
59
+ * Format the host
60
+ * @param setting Setting
61
+ * @param hostname Hostname
62
+ * @returns Result
63
+ */
64
+ function formatHost(setting: string, hostname?: string | null): string;
65
+ function formatHost(setting: Record<string, ExternalEndpoint>, hostname?: string | null): Record<string, ExternalEndpoint>;
66
+ function formatHost(setting: undefined, hostname?: string | null): undefined;
45
67
  }
@@ -7,39 +7,55 @@ exports.ExternalSettings = void 0;
7
7
  var ExternalSettings;
8
8
  (function (ExternalSettings) {
9
9
  /**
10
- * Create instance
10
+ * Sub domain match regular expression
11
11
  */
12
- function create() {
13
- if ("settings" in globalThis) {
14
- const settings = Reflect.get(globalThis, "settings");
15
- if (typeof settings === "object") {
16
- if (typeof window !== "undefined") {
17
- // Host name
18
- const hostname = globalThis.location.hostname;
19
- // replace {hostname}
20
- format(settings, hostname);
21
- }
22
- return settings;
23
- }
12
+ ExternalSettings.subDomainMatch = /(?<=\/\/)[0-9a-z]+(?=\.)/i;
13
+ /**
14
+ * Create settings instance
15
+ * @param settings Settings
16
+ * @returns Result
17
+ */
18
+ function create(settings) {
19
+ // Default settings reading from globalThis
20
+ settings ?? (settings = Reflect.get(globalThis, "settings"));
21
+ if (settings != null &&
22
+ typeof settings === "object" &&
23
+ "appId" in settings &&
24
+ "endpoint" in settings) {
25
+ return settings;
24
26
  }
25
- return undefined;
27
+ throw new Error("No external settings found");
26
28
  }
27
29
  ExternalSettings.create = create;
28
- function format(settings, hostname) {
30
+ /**
31
+ * Format the app
32
+ * @param hostname Hostname
33
+ * @param app App key
34
+ * @param endpoint Endpoint
35
+ * @returns Result
36
+ */
37
+ function formatApp(hostname, app, endpoint) {
38
+ return formatHost(endpoint, hostname).replace(ExternalSettings.subDomainMatch, app);
39
+ }
40
+ ExternalSettings.formatApp = formatApp;
41
+ function formatHost(setting, hostname) {
42
+ // No setting
43
+ if (setting == null)
44
+ return undefined;
29
45
  // Default hostname
30
- if (!hostname)
31
- hostname = "localhost";
32
- // replace {hostname}
33
- for (const key in settings) {
34
- const value = settings[key];
35
- if (typeof value === "string") {
36
- settings[key] = value.replace("{hostname}", hostname);
37
- }
38
- else if (typeof value === "object") {
39
- format(value, hostname);
40
- }
46
+ hostname ?? (hostname = globalThis.location.hostname);
47
+ if (typeof setting === "string") {
48
+ return setting.replace("{hostname}", hostname);
49
+ }
50
+ else {
51
+ return Object.fromEntries(Object.entries(setting).map(([key, value]) => [
52
+ key,
53
+ {
54
+ endpoint: formatApp(hostname, key, value.endpoint),
55
+ webUrl: formatApp(hostname, key, value.webUrl)
56
+ }
57
+ ]));
41
58
  }
42
- return settings;
43
59
  }
44
- ExternalSettings.format = format;
60
+ ExternalSettings.formatHost = formatHost;
45
61
  })(ExternalSettings || (exports.ExternalSettings = ExternalSettings = {}));
@@ -85,10 +85,6 @@ export interface RefreshTokenProps {
85
85
  * Refresh token
86
86
  */
87
87
  token?: string;
88
- /**
89
- * Time zone
90
- */
91
- timeZone: string;
92
88
  /**
93
89
  * API URL
94
90
  */
@@ -4,7 +4,7 @@ import { BaseApi } from "./BaseApi";
4
4
  import { ResultPayload } from "./dto/ResultPayload";
5
5
  import { DataTypes, IActionResult } from "@etsoo/shared";
6
6
  import { RefreshTokenProps, RefreshTokenResult } from "../app/IApp";
7
- import { TokenRQ } from "./rq/TokenRQ";
7
+ import { TokenInputRQ } from "./rq/TokenRQ";
8
8
  import { ApiRefreshTokenDto } from "./dto/ApiRefreshTokenDto";
9
9
  import { GetLogInUrlRQ } from "./rq/GetLogInUrlRQ";
10
10
  import { LoginRQ } from "./rq/LoginRQ";
@@ -27,7 +27,7 @@ export declare class AuthApi extends BaseApi {
27
27
  * @param payload Payload
28
28
  * @returns Result
29
29
  */
30
- apiRefreshToken(rq: TokenRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
30
+ apiRefreshToken(rq: TokenInputRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
31
31
  /**
32
32
  * Authorization request
33
33
  * @param auth Authorization request data
@@ -56,7 +56,7 @@ export declare class AuthApi extends BaseApi {
56
56
  * @param payload Payload
57
57
  * @returns Result
58
58
  */
59
- exchangeToken(rq: TokenRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
59
+ exchangeToken(rq: TokenInputRQ, payload?: IApiPayload<ApiRefreshTokenDto>): Promise<ApiRefreshTokenDto | undefined>;
60
60
  /**
61
61
  * Get log in url
62
62
  * @param rq Request data
@@ -11,7 +11,8 @@ export class AuthApi extends BaseApi {
11
11
  * @returns Result
12
12
  */
13
13
  apiRefreshToken(rq, payload) {
14
- return this.api.put("Auth/ApiRefreshToken", rq, payload);
14
+ const data = { ...rq, timeZone: this.app.getTimeZone() };
15
+ return this.api.put("Auth/ApiRefreshToken", data, payload);
15
16
  }
16
17
  /**
17
18
  * Authorization request
@@ -60,7 +61,8 @@ export class AuthApi extends BaseApi {
60
61
  * @returns Result
61
62
  */
62
63
  exchangeToken(rq, payload) {
63
- return this.api.put("Auth/ExchangeToken", rq, payload);
64
+ const data = { ...rq, timeZone: this.app.getTimeZone() };
65
+ return this.api.put("Auth/ExchangeToken", data, payload);
64
66
  }
65
67
  /**
66
68
  * Get log in url
@@ -1,8 +1,8 @@
1
- import { TokenRQ } from "./TokenRQ";
1
+ import { TokenInputRQ } from "./TokenRQ";
2
2
  /**
3
3
  * API Refresh Token Request data
4
4
  */
5
- export type ApiRefreshTokenRQ = TokenRQ & {
5
+ export type ApiRefreshTokenRQ = TokenInputRQ & {
6
6
  /**
7
7
  * Application ID, 0 for core system
8
8
  */
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * Token request data
3
3
  */
4
- export type TokenRQ = {
4
+ export type TokenInputRQ = {
5
5
  /**
6
6
  * Refresh token
7
7
  */
8
8
  token: string;
9
+ };
10
+ /**
11
+ * Token request data
12
+ */
13
+ export type TokenRQ = TokenInputRQ & {
9
14
  /**
10
15
  * Time zone
11
16
  */
@@ -176,6 +176,12 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
176
176
  * @param debug Debug mode
177
177
  */
178
178
  protected constructor(settings: S, api: IApi | undefined | null, notifier: INotifier<N, C>, storage: IStorage, name: string, debug?: boolean);
179
+ /**
180
+ * Format settings
181
+ * @param settings Original settings
182
+ * @returns Result
183
+ */
184
+ protected formatSettings(settings: S): S;
179
185
  private getDeviceId;
180
186
  private resetKeys;
181
187
  /**
@@ -161,11 +161,10 @@ export class CoreApp {
161
161
  this.passphrase = "";
162
162
  this.apis = {};
163
163
  this.tasks = [];
164
- if (settings?.regions?.length === 0) {
165
- throw new Error("No regions defined");
166
- }
167
- this.settings = settings;
168
- const region = AddressRegion.getById(settings.regions[0]);
164
+ // Format settings
165
+ this.settings = this.formatSettings(settings);
166
+ // Current region
167
+ const region = AddressRegion.getById(this.settings.regions[0]);
169
168
  if (region == null) {
170
169
  throw new Error("No default region defined");
171
170
  }
@@ -174,7 +173,7 @@ export class CoreApp {
174
173
  const refresh = async (api, rq) => {
175
174
  if (this.lastCalled) {
176
175
  // Call refreshToken to update access token
177
- await this.refreshToken({ token: rq.token, timeZone: rq.timeZone, showLoading: false }, (result) => {
176
+ await this.refreshToken({ token: rq.token, showLoading: false }, (result) => {
178
177
  if (result === true)
179
178
  return;
180
179
  console.log(`CoreApp.${this.name}.RefreshToken`, result);
@@ -229,6 +228,14 @@ export class CoreApp {
229
228
  this.setup();
230
229
  });
231
230
  }
231
+ /**
232
+ * Format settings
233
+ * @param settings Original settings
234
+ * @returns Result
235
+ */
236
+ formatSettings(settings) {
237
+ return settings;
238
+ }
232
239
  getDeviceId() {
233
240
  return this.deviceId.substring(0, 15);
234
241
  }
@@ -1417,7 +1424,7 @@ export class CoreApp {
1417
1424
  */
1418
1425
  async refreshToken(props, callback) {
1419
1426
  // Check props
1420
- props ?? (props = { timeZone: this.getTimeZone() });
1427
+ props ?? (props = {});
1421
1428
  props.token ?? (props.token = this.getCacheToken());
1422
1429
  // Call refresh token API
1423
1430
  let data = await this.createAuthApi().refreshToken(props);
@@ -1516,7 +1523,7 @@ export class CoreApp {
1516
1523
  throw new Error("System API is not allowed to exchange token");
1517
1524
  }
1518
1525
  // Call the API quietly, no loading bar and no error popup
1519
- const data = await this.createAuthApi().exchangeToken({ token, timeZone: this.getTimeZone() }, {
1526
+ const data = await this.createAuthApi().exchangeToken({ token }, {
1520
1527
  showLoading: false,
1521
1528
  onError: (error) => {
1522
1529
  console.error(`CoreApp.${api.name}.ExchangeToken error`, error);
@@ -1607,8 +1614,6 @@ export class CoreApp {
1607
1614
  return;
1608
1615
  // App id
1609
1616
  const appId = this.settings.appId;
1610
- // Timezone
1611
- const timeZone = this.getTimeZone();
1612
1617
  // APIs
1613
1618
  for (const name in this.apis) {
1614
1619
  // Get the API
@@ -1621,7 +1626,7 @@ export class CoreApp {
1621
1626
  // Ready to trigger
1622
1627
  if (api[2] === 0) {
1623
1628
  // Refresh token
1624
- api[3](api[0], { appId, token: api[4], timeZone }).then((data) => {
1629
+ api[3](api[0], { appId, token: api[4] }).then((data) => {
1625
1630
  if (data == null) {
1626
1631
  // Failed, try it again in 2 seconds
1627
1632
  api[2] = 2;
@@ -31,15 +31,37 @@ export interface IExternalSettings extends ExternalEndpoint {
31
31
  /**
32
32
  * Endpoints to other services
33
33
  */
34
- readonly endpoints?: Record<"core" | "accounting" | "crm" | "calandar" | "task" | string, ExternalEndpoint>;
34
+ readonly endpoints?: Record<"core" | "admin" | "finance" | "crm" | "oa" | "agile" | string, ExternalEndpoint>;
35
35
  }
36
36
  /**
37
37
  * External settings namespace
38
38
  */
39
39
  export declare namespace ExternalSettings {
40
40
  /**
41
- * Create instance
41
+ * Sub domain match regular expression
42
42
  */
43
- function create<T extends IExternalSettings = IExternalSettings>(): T | undefined;
44
- function format(settings: any, hostname?: string): any;
43
+ let subDomainMatch: RegExp;
44
+ /**
45
+ * Create settings instance
46
+ * @param settings Settings
47
+ * @returns Result
48
+ */
49
+ function create<T extends IExternalSettings = IExternalSettings>(settings: unknown): T;
50
+ /**
51
+ * Format the app
52
+ * @param hostname Hostname
53
+ * @param app App key
54
+ * @param endpoint Endpoint
55
+ * @returns Result
56
+ */
57
+ function formatApp(hostname: string, app: string, endpoint: string): string;
58
+ /**
59
+ * Format the host
60
+ * @param setting Setting
61
+ * @param hostname Hostname
62
+ * @returns Result
63
+ */
64
+ function formatHost(setting: string, hostname?: string | null): string;
65
+ function formatHost(setting: Record<string, ExternalEndpoint>, hostname?: string | null): Record<string, ExternalEndpoint>;
66
+ function formatHost(setting: undefined, hostname?: string | null): undefined;
45
67
  }
@@ -4,39 +4,55 @@
4
4
  export var ExternalSettings;
5
5
  (function (ExternalSettings) {
6
6
  /**
7
- * Create instance
7
+ * Sub domain match regular expression
8
8
  */
9
- function create() {
10
- if ("settings" in globalThis) {
11
- const settings = Reflect.get(globalThis, "settings");
12
- if (typeof settings === "object") {
13
- if (typeof window !== "undefined") {
14
- // Host name
15
- const hostname = globalThis.location.hostname;
16
- // replace {hostname}
17
- format(settings, hostname);
18
- }
19
- return settings;
20
- }
9
+ ExternalSettings.subDomainMatch = /(?<=\/\/)[0-9a-z]+(?=\.)/i;
10
+ /**
11
+ * Create settings instance
12
+ * @param settings Settings
13
+ * @returns Result
14
+ */
15
+ function create(settings) {
16
+ // Default settings reading from globalThis
17
+ settings ?? (settings = Reflect.get(globalThis, "settings"));
18
+ if (settings != null &&
19
+ typeof settings === "object" &&
20
+ "appId" in settings &&
21
+ "endpoint" in settings) {
22
+ return settings;
21
23
  }
22
- return undefined;
24
+ throw new Error("No external settings found");
23
25
  }
24
26
  ExternalSettings.create = create;
25
- function format(settings, hostname) {
27
+ /**
28
+ * Format the app
29
+ * @param hostname Hostname
30
+ * @param app App key
31
+ * @param endpoint Endpoint
32
+ * @returns Result
33
+ */
34
+ function formatApp(hostname, app, endpoint) {
35
+ return formatHost(endpoint, hostname).replace(ExternalSettings.subDomainMatch, app);
36
+ }
37
+ ExternalSettings.formatApp = formatApp;
38
+ function formatHost(setting, hostname) {
39
+ // No setting
40
+ if (setting == null)
41
+ return undefined;
26
42
  // Default hostname
27
- if (!hostname)
28
- hostname = "localhost";
29
- // replace {hostname}
30
- for (const key in settings) {
31
- const value = settings[key];
32
- if (typeof value === "string") {
33
- settings[key] = value.replace("{hostname}", hostname);
34
- }
35
- else if (typeof value === "object") {
36
- format(value, hostname);
37
- }
43
+ hostname ?? (hostname = globalThis.location.hostname);
44
+ if (typeof setting === "string") {
45
+ return setting.replace("{hostname}", hostname);
46
+ }
47
+ else {
48
+ return Object.fromEntries(Object.entries(setting).map(([key, value]) => [
49
+ key,
50
+ {
51
+ endpoint: formatApp(hostname, key, value.endpoint),
52
+ webUrl: formatApp(hostname, key, value.webUrl)
53
+ }
54
+ ]));
38
55
  }
39
- return settings;
40
56
  }
41
- ExternalSettings.format = format;
57
+ ExternalSettings.formatHost = formatHost;
42
58
  })(ExternalSettings || (ExternalSettings = {}));
@@ -85,10 +85,6 @@ export interface RefreshTokenProps {
85
85
  * Refresh token
86
86
  */
87
87
  token?: string;
88
- /**
89
- * Time zone
90
- */
91
- timeZone: string;
92
88
  /**
93
89
  * API URL
94
90
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.5.97",
3
+ "version": "1.5.99",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -37,19 +37,19 @@
37
37
  "dependencies": {
38
38
  "@etsoo/notificationbase": "^1.1.58",
39
39
  "@etsoo/restclient": "^1.1.23",
40
- "@etsoo/shared": "^1.2.60",
40
+ "@etsoo/shared": "^1.2.61",
41
41
  "crypto-js": "^4.2.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@babel/cli": "^7.26.4",
45
- "@babel/core": "^7.26.7",
46
- "@babel/plugin-transform-runtime": "^7.25.9",
47
- "@babel/preset-env": "^7.26.7",
45
+ "@babel/core": "^7.26.8",
46
+ "@babel/plugin-transform-runtime": "^7.26.8",
47
+ "@babel/preset-env": "^7.26.8",
48
48
  "@babel/runtime-corejs3": "^7.26.7",
49
49
  "@types/crypto-js": "^4.2.2",
50
50
  "@vitejs/plugin-react": "^4.3.4",
51
51
  "jsdom": "^26.0.0",
52
52
  "typescript": "^5.7.3",
53
- "vitest": "^3.0.4"
53
+ "vitest": "^3.0.5"
54
54
  }
55
55
  }
@@ -4,7 +4,7 @@ import { BaseApi } from "./BaseApi";
4
4
  import { ResultPayload } from "./dto/ResultPayload";
5
5
  import { ActionResult, DataTypes, IActionResult } from "@etsoo/shared";
6
6
  import { RefreshTokenProps, RefreshTokenResult } from "../app/IApp";
7
- import { TokenRQ } from "./rq/TokenRQ";
7
+ import { TokenInputRQ, TokenRQ } from "./rq/TokenRQ";
8
8
  import { ApiRefreshTokenDto } from "./dto/ApiRefreshTokenDto";
9
9
  import { GetLogInUrlRQ } from "./rq/GetLogInUrlRQ";
10
10
  import { LoginRQ } from "./rq/LoginRQ";
@@ -32,8 +32,9 @@ export class AuthApi extends BaseApi {
32
32
  * @param payload Payload
33
33
  * @returns Result
34
34
  */
35
- apiRefreshToken(rq: TokenRQ, payload?: IApiPayload<ApiRefreshTokenDto>) {
36
- return this.api.put("Auth/ApiRefreshToken", rq, payload);
35
+ apiRefreshToken(rq: TokenInputRQ, payload?: IApiPayload<ApiRefreshTokenDto>) {
36
+ const data: TokenRQ = { ...rq, timeZone: this.app.getTimeZone() };
37
+ return this.api.put("Auth/ApiRefreshToken", data, payload);
37
38
  }
38
39
 
39
40
  /**
@@ -93,8 +94,9 @@ export class AuthApi extends BaseApi {
93
94
  * @param payload Payload
94
95
  * @returns Result
95
96
  */
96
- exchangeToken(rq: TokenRQ, payload?: IApiPayload<ApiRefreshTokenDto>) {
97
- return this.api.put("Auth/ExchangeToken", rq, payload);
97
+ exchangeToken(rq: TokenInputRQ, payload?: IApiPayload<ApiRefreshTokenDto>) {
98
+ const data: TokenRQ = { ...rq, timeZone: this.app.getTimeZone() };
99
+ return this.api.put("Auth/ExchangeToken", data, payload);
98
100
  }
99
101
 
100
102
  /**
@@ -1,9 +1,9 @@
1
- import { TokenRQ } from "./TokenRQ";
1
+ import { TokenInputRQ } from "./TokenRQ";
2
2
 
3
3
  /**
4
4
  * API Refresh Token Request data
5
5
  */
6
- export type ApiRefreshTokenRQ = TokenRQ & {
6
+ export type ApiRefreshTokenRQ = TokenInputRQ & {
7
7
  /**
8
8
  * Application ID, 0 for core system
9
9
  */
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * Token request data
3
3
  */
4
- export type TokenRQ = {
4
+ export type TokenInputRQ = {
5
5
  /**
6
6
  * Refresh token
7
7
  */
8
8
  token: string;
9
+ };
9
10
 
11
+ /**
12
+ * Token request data
13
+ */
14
+ export type TokenRQ = TokenInputRQ & {
10
15
  /**
11
16
  * Time zone
12
17
  */
@@ -336,15 +336,15 @@ export abstract class CoreApp<
336
336
  name: string,
337
337
  debug: boolean = false
338
338
  ) {
339
- if (settings?.regions?.length === 0) {
340
- throw new Error("No regions defined");
341
- }
342
- this.settings = settings;
339
+ // Format settings
340
+ this.settings = this.formatSettings(settings);
343
341
 
344
- const region = AddressRegion.getById(settings.regions[0]);
342
+ // Current region
343
+ const region = AddressRegion.getById(this.settings.regions[0]);
345
344
  if (region == null) {
346
345
  throw new Error("No default region defined");
347
346
  }
347
+
348
348
  this.defaultRegion = region;
349
349
 
350
350
  // Current system refresh token
@@ -352,7 +352,7 @@ export abstract class CoreApp<
352
352
  if (this.lastCalled) {
353
353
  // Call refreshToken to update access token
354
354
  await this.refreshToken(
355
- { token: rq.token, timeZone: rq.timeZone, showLoading: false },
355
+ { token: rq.token, showLoading: false },
356
356
  (result) => {
357
357
  if (result === true) return;
358
358
  console.log(`CoreApp.${this.name}.RefreshToken`, result);
@@ -433,6 +433,15 @@ export abstract class CoreApp<
433
433
  );
434
434
  }
435
435
 
436
+ /**
437
+ * Format settings
438
+ * @param settings Original settings
439
+ * @returns Result
440
+ */
441
+ protected formatSettings(settings: S) {
442
+ return settings;
443
+ }
444
+
436
445
  private getDeviceId() {
437
446
  return this.deviceId.substring(0, 15);
438
447
  }
@@ -1938,7 +1947,7 @@ export abstract class CoreApp<
1938
1947
  callback?: (result?: boolean | IActionResult) => boolean | void
1939
1948
  ) {
1940
1949
  // Check props
1941
- props ??= { timeZone: this.getTimeZone() };
1950
+ props ??= {};
1942
1951
  props.token ??= this.getCacheToken();
1943
1952
 
1944
1953
  // Call refresh token API
@@ -2052,7 +2061,7 @@ export abstract class CoreApp<
2052
2061
 
2053
2062
  // Call the API quietly, no loading bar and no error popup
2054
2063
  const data = await this.createAuthApi().exchangeToken(
2055
- { token, timeZone: this.getTimeZone() },
2064
+ { token },
2056
2065
  {
2057
2066
  showLoading: false,
2058
2067
  onError: (error) => {
@@ -2165,9 +2174,6 @@ export abstract class CoreApp<
2165
2174
  // App id
2166
2175
  const appId = this.settings.appId;
2167
2176
 
2168
- // Timezone
2169
- const timeZone = this.getTimeZone();
2170
-
2171
2177
  // APIs
2172
2178
  for (const name in this.apis) {
2173
2179
  // Get the API
@@ -2182,7 +2188,7 @@ export abstract class CoreApp<
2182
2188
  // Ready to trigger
2183
2189
  if (api[2] === 0) {
2184
2190
  // Refresh token
2185
- api[3](api[0], { appId, token: api[4], timeZone }).then((data) => {
2191
+ api[3](api[0], { appId, token: api[4] }).then((data) => {
2186
2192
  if (data == null) {
2187
2193
  // Failed, try it again in 2 seconds
2188
2194
  api[2] = 2;
@@ -37,7 +37,7 @@ export interface IExternalSettings extends ExternalEndpoint {
37
37
  * Endpoints to other services
38
38
  */
39
39
  readonly endpoints?: Record<
40
- "core" | "accounting" | "crm" | "calandar" | "task" | string,
40
+ "core" | "admin" | "finance" | "crm" | "oa" | "agile" | string,
41
41
  ExternalEndpoint
42
42
  >;
43
43
  }
@@ -47,42 +47,84 @@ export interface IExternalSettings extends ExternalEndpoint {
47
47
  */
48
48
  export namespace ExternalSettings {
49
49
  /**
50
- * Create instance
50
+ * Sub domain match regular expression
51
51
  */
52
- export function create<T extends IExternalSettings = IExternalSettings>():
53
- | T
54
- | undefined {
55
- if ("settings" in globalThis) {
56
- const settings = Reflect.get(globalThis, "settings");
57
- if (typeof settings === "object") {
58
- if (typeof window !== "undefined") {
59
- // Host name
60
- const hostname = globalThis.location.hostname;
61
-
62
- // replace {hostname}
63
- format(settings, hostname);
64
- }
65
-
66
- return settings as T;
67
- }
52
+ export let subDomainMatch: RegExp = /(?<=\/\/)[0-9a-z]+(?=\.)/i;
53
+
54
+ /**
55
+ * Create settings instance
56
+ * @param settings Settings
57
+ * @returns Result
58
+ */
59
+ export function create<T extends IExternalSettings = IExternalSettings>(
60
+ settings: unknown
61
+ ): T {
62
+ // Default settings reading from globalThis
63
+ settings ??= Reflect.get(globalThis, "settings");
64
+
65
+ if (
66
+ settings != null &&
67
+ typeof settings === "object" &&
68
+ "appId" in settings &&
69
+ "endpoint" in settings
70
+ ) {
71
+ return settings as T;
68
72
  }
69
- return undefined;
73
+
74
+ throw new Error("No external settings found");
75
+ }
76
+
77
+ /**
78
+ * Format the app
79
+ * @param hostname Hostname
80
+ * @param app App key
81
+ * @param endpoint Endpoint
82
+ * @returns Result
83
+ */
84
+ export function formatApp(hostname: string, app: string, endpoint: string) {
85
+ return formatHost(endpoint, hostname).replace(subDomainMatch, app);
70
86
  }
71
87
 
72
- export function format(settings: any, hostname?: string) {
88
+ /**
89
+ * Format the host
90
+ * @param setting Setting
91
+ * @param hostname Hostname
92
+ * @returns Result
93
+ */
94
+ export function formatHost(setting: string, hostname?: string | null): string;
95
+
96
+ export function formatHost(
97
+ setting: Record<string, ExternalEndpoint>,
98
+ hostname?: string | null
99
+ ): Record<string, ExternalEndpoint>;
100
+
101
+ export function formatHost(
102
+ setting: undefined,
103
+ hostname?: string | null
104
+ ): undefined;
105
+
106
+ export function formatHost(
107
+ setting?: string | Record<string, ExternalEndpoint>,
108
+ hostname?: string | null
109
+ ): string | Record<string, ExternalEndpoint> | undefined {
110
+ // No setting
111
+ if (setting == null) return undefined;
112
+
73
113
  // Default hostname
74
- if (!hostname) hostname = "localhost";
75
-
76
- // replace {hostname}
77
- for (const key in settings) {
78
- const value = settings[key];
79
- if (typeof value === "string") {
80
- settings[key] = value.replace("{hostname}", hostname);
81
- } else if (typeof value === "object") {
82
- format(value, hostname);
83
- }
84
- }
114
+ hostname ??= globalThis.location.hostname;
85
115
 
86
- return settings;
116
+ if (typeof setting === "string") {
117
+ return setting.replace("{hostname}", hostname);
118
+ } else {
119
+ return Object.fromEntries(
120
+ Object.entries(setting).map(([key, value]) => [
121
+ key,
122
+ {
123
+ endpoint: formatApp(hostname, key, value.endpoint),
124
+ webUrl: formatApp(hostname, key, value.webUrl)
125
+ }
126
+ ])
127
+ );
128
+ }
87
129
  }
88
130
  }
package/src/app/IApp.ts CHANGED
@@ -114,11 +114,6 @@ export interface RefreshTokenProps {
114
114
  */
115
115
  token?: string;
116
116
 
117
- /**
118
- * Time zone
119
- */
120
- timeZone: string;
121
-
122
117
  /**
123
118
  * API URL
124
119
  */