@etsoo/appscript 1.5.98 → 1.6.0

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,19 @@ 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:
136
+ endpoints == null
137
+ ? undefined
138
+ : ExternalSettings.formatHost(endpoints, "localhost")
139
+ };
140
+ }
141
+
127
142
  freshCountdownUI(callback?: () => PromiseLike<unknown>): void {
128
143
  throw new Error("Method not implemented.");
129
144
  }
@@ -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
  /**
@@ -11,6 +11,7 @@ const EntityStatus_1 = require("../business/EntityStatus");
11
11
  const ActionResultError_1 = require("../result/ActionResultError");
12
12
  const IApp_1 = require("./IApp");
13
13
  const UserRole_1 = require("./UserRole");
14
+ const ExternalSettings_1 = require("./ExternalSettings");
14
15
  const AuthApi_1 = require("../api/AuthApi");
15
16
  let CJ;
16
17
  const loadCrypto = () => import("crypto-js");
@@ -164,11 +165,10 @@ class CoreApp {
164
165
  this.passphrase = "";
165
166
  this.apis = {};
166
167
  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]);
168
+ // Format settings
169
+ this.settings = this.formatSettings(settings);
170
+ // Current region
171
+ const region = AddressRegion_1.AddressRegion.getById(this.settings.regions[0]);
172
172
  if (region == null) {
173
173
  throw new Error("No default region defined");
174
174
  }
@@ -232,6 +232,23 @@ class CoreApp {
232
232
  this.setup();
233
233
  });
234
234
  }
235
+ /**
236
+ * Format settings
237
+ * @param settings Original settings
238
+ * @returns Result
239
+ */
240
+ formatSettings(settings) {
241
+ const { endpoint, webUrl, endpoints, hostname = globalThis.location.hostname, ...rest } = settings;
242
+ return {
243
+ ...rest,
244
+ hostname,
245
+ endpoint: ExternalSettings_1.ExternalSettings.formatHost(endpoint, hostname),
246
+ webUrl: ExternalSettings_1.ExternalSettings.formatHost(webUrl, hostname),
247
+ endpoints: endpoints == null
248
+ ? undefined
249
+ : ExternalSettings_1.ExternalSettings.formatHost(endpoints, hostname)
250
+ };
251
+ }
235
252
  getDeviceId() {
236
253
  return this.deviceId.substring(0, 15);
237
254
  }
@@ -28,18 +28,44 @@ export interface IExternalSettings extends ExternalEndpoint {
28
28
  * 程序编号
29
29
  */
30
30
  readonly appId: number;
31
+ /**
32
+ * Default hostname for substitution
33
+ * 用于替换的默认主机名
34
+ */
35
+ hostname?: string;
31
36
  /**
32
37
  * Endpoints to other services
33
38
  */
34
- readonly endpoints?: Record<"core" | "accounting" | "crm" | "calandar" | "task" | string, ExternalEndpoint>;
39
+ readonly endpoints?: Record<"core" | "admin" | "finance" | "crm" | "oa" | "agile" | string, ExternalEndpoint>;
35
40
  }
36
41
  /**
37
42
  * External settings namespace
38
43
  */
39
44
  export declare namespace ExternalSettings {
40
45
  /**
41
- * Create instance
46
+ * Sub domain match regular expression
47
+ */
48
+ let subDomainMatch: RegExp;
49
+ /**
50
+ * Create settings instance
51
+ * @param settings Settings
52
+ * @returns Result
53
+ */
54
+ function create<T extends IExternalSettings = IExternalSettings>(settings?: unknown, hostname?: string): T;
55
+ /**
56
+ * Format the app
57
+ * @param hostname Hostname
58
+ * @param app App key
59
+ * @param endpoint Endpoint
60
+ * @returns Result
61
+ */
62
+ function formatApp(hostname: string, app: string, endpoint: string): string;
63
+ /**
64
+ * Format the host
65
+ * @param setting Setting
66
+ * @param hostname Hostname
67
+ * @returns Result
42
68
  */
43
- function create<T extends IExternalSettings = IExternalSettings>(): T | undefined;
44
- function format(settings: any, hostname?: string): any;
69
+ function formatHost(setting: string, hostname: string): string;
70
+ function formatHost(setting: Record<string, ExternalEndpoint>, hostname?: string | null): Record<string, ExternalEndpoint>;
45
71
  }
@@ -7,39 +7,60 @@ 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;
12
+ ExternalSettings.subDomainMatch = /(?<=\/\/)[0-9a-z]+(?=\.)/i;
13
+ /**
14
+ * Create settings instance
15
+ * @param settings Settings
16
+ * @returns Result
17
+ */
18
+ function create(settings, hostname) {
19
+ // Default settings reading from globalThis
20
+ settings ?? (settings = Reflect.get(globalThis, "settings"));
21
+ if (settings) {
22
+ if (typeof settings === "string") {
23
+ settings = JSON.parse(settings);
24
+ }
25
+ if (settings != null &&
26
+ typeof settings === "object" &&
27
+ "appId" in settings &&
28
+ "endpoint" in settings) {
29
+ const s = settings;
30
+ if (hostname)
31
+ s.hostname = hostname;
32
+ return s;
23
33
  }
24
34
  }
25
- return undefined;
35
+ throw new Error("No external settings found");
26
36
  }
27
37
  ExternalSettings.create = create;
28
- function format(settings, hostname) {
38
+ /**
39
+ * Format the app
40
+ * @param hostname Hostname
41
+ * @param app App key
42
+ * @param endpoint Endpoint
43
+ * @returns Result
44
+ */
45
+ function formatApp(hostname, app, endpoint) {
46
+ return formatHost(endpoint, hostname).replace(ExternalSettings.subDomainMatch, app);
47
+ }
48
+ ExternalSettings.formatApp = formatApp;
49
+ function formatHost(setting, hostname) {
29
50
  // 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
- }
51
+ hostname ?? (hostname = globalThis.location.hostname);
52
+ if (typeof setting === "string") {
53
+ return setting.replace("{hostname}", hostname);
54
+ }
55
+ else {
56
+ return Object.fromEntries(Object.entries(setting).map(([key, value]) => [
57
+ key,
58
+ {
59
+ endpoint: formatApp(hostname, key, value.endpoint),
60
+ webUrl: formatApp(hostname, key, value.webUrl)
61
+ }
62
+ ]));
41
63
  }
42
- return settings;
43
64
  }
44
- ExternalSettings.format = format;
65
+ ExternalSettings.formatHost = formatHost;
45
66
  })(ExternalSettings || (exports.ExternalSettings = ExternalSettings = {}));
@@ -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
  /**
@@ -8,6 +8,7 @@ import { EntityStatus } from "../business/EntityStatus";
8
8
  import { ActionResultError } from "../result/ActionResultError";
9
9
  import { appFields } from "./IApp";
10
10
  import { UserRole } from "./UserRole";
11
+ import { ExternalSettings } from "./ExternalSettings";
11
12
  import { AuthApi } from "../api/AuthApi";
12
13
  let CJ;
13
14
  const loadCrypto = () => import("crypto-js");
@@ -161,11 +162,10 @@ export class CoreApp {
161
162
  this.passphrase = "";
162
163
  this.apis = {};
163
164
  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]);
165
+ // Format settings
166
+ this.settings = this.formatSettings(settings);
167
+ // Current region
168
+ const region = AddressRegion.getById(this.settings.regions[0]);
169
169
  if (region == null) {
170
170
  throw new Error("No default region defined");
171
171
  }
@@ -229,6 +229,23 @@ export class CoreApp {
229
229
  this.setup();
230
230
  });
231
231
  }
232
+ /**
233
+ * Format settings
234
+ * @param settings Original settings
235
+ * @returns Result
236
+ */
237
+ formatSettings(settings) {
238
+ const { endpoint, webUrl, endpoints, hostname = globalThis.location.hostname, ...rest } = settings;
239
+ return {
240
+ ...rest,
241
+ hostname,
242
+ endpoint: ExternalSettings.formatHost(endpoint, hostname),
243
+ webUrl: ExternalSettings.formatHost(webUrl, hostname),
244
+ endpoints: endpoints == null
245
+ ? undefined
246
+ : ExternalSettings.formatHost(endpoints, hostname)
247
+ };
248
+ }
232
249
  getDeviceId() {
233
250
  return this.deviceId.substring(0, 15);
234
251
  }
@@ -28,18 +28,44 @@ export interface IExternalSettings extends ExternalEndpoint {
28
28
  * 程序编号
29
29
  */
30
30
  readonly appId: number;
31
+ /**
32
+ * Default hostname for substitution
33
+ * 用于替换的默认主机名
34
+ */
35
+ hostname?: string;
31
36
  /**
32
37
  * Endpoints to other services
33
38
  */
34
- readonly endpoints?: Record<"core" | "accounting" | "crm" | "calandar" | "task" | string, ExternalEndpoint>;
39
+ readonly endpoints?: Record<"core" | "admin" | "finance" | "crm" | "oa" | "agile" | string, ExternalEndpoint>;
35
40
  }
36
41
  /**
37
42
  * External settings namespace
38
43
  */
39
44
  export declare namespace ExternalSettings {
40
45
  /**
41
- * Create instance
46
+ * Sub domain match regular expression
47
+ */
48
+ let subDomainMatch: RegExp;
49
+ /**
50
+ * Create settings instance
51
+ * @param settings Settings
52
+ * @returns Result
53
+ */
54
+ function create<T extends IExternalSettings = IExternalSettings>(settings?: unknown, hostname?: string): T;
55
+ /**
56
+ * Format the app
57
+ * @param hostname Hostname
58
+ * @param app App key
59
+ * @param endpoint Endpoint
60
+ * @returns Result
61
+ */
62
+ function formatApp(hostname: string, app: string, endpoint: string): string;
63
+ /**
64
+ * Format the host
65
+ * @param setting Setting
66
+ * @param hostname Hostname
67
+ * @returns Result
42
68
  */
43
- function create<T extends IExternalSettings = IExternalSettings>(): T | undefined;
44
- function format(settings: any, hostname?: string): any;
69
+ function formatHost(setting: string, hostname: string): string;
70
+ function formatHost(setting: Record<string, ExternalEndpoint>, hostname?: string | null): Record<string, ExternalEndpoint>;
45
71
  }
@@ -4,39 +4,60 @@
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;
9
+ ExternalSettings.subDomainMatch = /(?<=\/\/)[0-9a-z]+(?=\.)/i;
10
+ /**
11
+ * Create settings instance
12
+ * @param settings Settings
13
+ * @returns Result
14
+ */
15
+ function create(settings, hostname) {
16
+ // Default settings reading from globalThis
17
+ settings ?? (settings = Reflect.get(globalThis, "settings"));
18
+ if (settings) {
19
+ if (typeof settings === "string") {
20
+ settings = JSON.parse(settings);
21
+ }
22
+ if (settings != null &&
23
+ typeof settings === "object" &&
24
+ "appId" in settings &&
25
+ "endpoint" in settings) {
26
+ const s = settings;
27
+ if (hostname)
28
+ s.hostname = hostname;
29
+ return s;
20
30
  }
21
31
  }
22
- return undefined;
32
+ throw new Error("No external settings found");
23
33
  }
24
34
  ExternalSettings.create = create;
25
- function format(settings, hostname) {
35
+ /**
36
+ * Format the app
37
+ * @param hostname Hostname
38
+ * @param app App key
39
+ * @param endpoint Endpoint
40
+ * @returns Result
41
+ */
42
+ function formatApp(hostname, app, endpoint) {
43
+ return formatHost(endpoint, hostname).replace(ExternalSettings.subDomainMatch, app);
44
+ }
45
+ ExternalSettings.formatApp = formatApp;
46
+ function formatHost(setting, hostname) {
26
47
  // 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
- }
48
+ hostname ?? (hostname = globalThis.location.hostname);
49
+ if (typeof setting === "string") {
50
+ return setting.replace("{hostname}", hostname);
51
+ }
52
+ else {
53
+ return Object.fromEntries(Object.entries(setting).map(([key, value]) => [
54
+ key,
55
+ {
56
+ endpoint: formatApp(hostname, key, value.endpoint),
57
+ webUrl: formatApp(hostname, key, value.webUrl)
58
+ }
59
+ ]));
38
60
  }
39
- return settings;
40
61
  }
41
- ExternalSettings.format = format;
62
+ ExternalSettings.formatHost = formatHost;
42
63
  })(ExternalSettings || (ExternalSettings = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.5.98",
3
+ "version": "1.6.0",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -37,7 +37,7 @@
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": {
@@ -45,7 +45,7 @@ import {
45
45
  import { UserRole } from "./UserRole";
46
46
  import type CryptoJS from "crypto-js";
47
47
  import { Currency } from "../business/Currency";
48
- import { ExternalEndpoint } from "./ExternalSettings";
48
+ import { ExternalEndpoint, ExternalSettings } from "./ExternalSettings";
49
49
  import { ApiRefreshTokenDto } from "../api/dto/ApiRefreshTokenDto";
50
50
  import { ApiRefreshTokenRQ } from "../api/rq/ApiRefreshTokenRQ";
51
51
  import { AuthApi } from "../api/AuthApi";
@@ -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
@@ -433,6 +433,31 @@ 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): S {
442
+ const {
443
+ endpoint,
444
+ webUrl,
445
+ endpoints,
446
+ hostname = globalThis.location.hostname,
447
+ ...rest
448
+ } = settings;
449
+ return {
450
+ ...rest,
451
+ hostname,
452
+ endpoint: ExternalSettings.formatHost(endpoint, hostname),
453
+ webUrl: ExternalSettings.formatHost(webUrl, hostname),
454
+ endpoints:
455
+ endpoints == null
456
+ ? undefined
457
+ : ExternalSettings.formatHost(endpoints, hostname)
458
+ } as S;
459
+ }
460
+
436
461
  private getDeviceId() {
437
462
  return this.deviceId.substring(0, 15);
438
463
  }
@@ -33,11 +33,17 @@ export interface IExternalSettings extends ExternalEndpoint {
33
33
  */
34
34
  readonly appId: number;
35
35
 
36
+ /**
37
+ * Default hostname for substitution
38
+ * 用于替换的默认主机名
39
+ */
40
+ hostname?: string;
41
+
36
42
  /**
37
43
  * Endpoints to other services
38
44
  */
39
45
  readonly endpoints?: Record<
40
- "core" | "accounting" | "crm" | "calandar" | "task" | string,
46
+ "core" | "admin" | "finance" | "crm" | "oa" | "agile" | string,
41
47
  ExternalEndpoint
42
48
  >;
43
49
  }
@@ -47,42 +53,85 @@ export interface IExternalSettings extends ExternalEndpoint {
47
53
  */
48
54
  export namespace ExternalSettings {
49
55
  /**
50
- * Create instance
56
+ * Sub domain match regular expression
57
+ */
58
+ export let subDomainMatch: RegExp = /(?<=\/\/)[0-9a-z]+(?=\.)/i;
59
+
60
+ /**
61
+ * Create settings instance
62
+ * @param settings Settings
63
+ * @returns Result
51
64
  */
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;
65
+ export function create<T extends IExternalSettings = IExternalSettings>(
66
+ settings?: unknown,
67
+ hostname?: string
68
+ ): T {
69
+ // Default settings reading from globalThis
70
+ settings ??= Reflect.get(globalThis, "settings");
71
+
72
+ if (settings) {
73
+ if (typeof settings === "string") {
74
+ settings = JSON.parse(settings);
75
+ }
76
+
77
+ if (
78
+ settings != null &&
79
+ typeof settings === "object" &&
80
+ "appId" in settings &&
81
+ "endpoint" in settings
82
+ ) {
83
+ const s = settings as T;
84
+ if (hostname) s.hostname = hostname;
85
+ return s;
67
86
  }
68
87
  }
69
- return undefined;
88
+
89
+ throw new Error("No external settings found");
70
90
  }
71
91
 
72
- export function format(settings: any, hostname?: string) {
92
+ /**
93
+ * Format the app
94
+ * @param hostname Hostname
95
+ * @param app App key
96
+ * @param endpoint Endpoint
97
+ * @returns Result
98
+ */
99
+ export function formatApp(hostname: string, app: string, endpoint: string) {
100
+ return formatHost(endpoint, hostname).replace(subDomainMatch, app);
101
+ }
102
+
103
+ /**
104
+ * Format the host
105
+ * @param setting Setting
106
+ * @param hostname Hostname
107
+ * @returns Result
108
+ */
109
+ export function formatHost(setting: string, hostname: string): string;
110
+
111
+ export function formatHost(
112
+ setting: Record<string, ExternalEndpoint>,
113
+ hostname?: string | null
114
+ ): Record<string, ExternalEndpoint>;
115
+
116
+ export function formatHost(
117
+ setting: string | Record<string, ExternalEndpoint>,
118
+ hostname?: string | null
119
+ ): string | Record<string, ExternalEndpoint> {
73
120
  // 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
- }
121
+ hostname ??= globalThis.location.hostname;
85
122
 
86
- return settings;
123
+ if (typeof setting === "string") {
124
+ return setting.replace("{hostname}", hostname);
125
+ } else {
126
+ return Object.fromEntries(
127
+ Object.entries(setting).map(([key, value]) => [
128
+ key,
129
+ {
130
+ endpoint: formatApp(hostname, key, value.endpoint),
131
+ webUrl: formatApp(hostname, key, value.webUrl)
132
+ }
133
+ ])
134
+ );
135
+ }
87
136
  }
88
137
  }