Skip to content

Commit e722d1a

Browse files
committed
fix: Load config relativly to module path
1 parent 5311382 commit e722d1a

File tree

3 files changed

+175
-34
lines changed

3 files changed

+175
-34
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Logger } from "@lib/common/logger";
2+
import { waitForConfig } from "@lib/settings";
3+
import { useState, useEffect } from "react";
4+
5+
export const ConfigLoader = ({ children }: { children: React.ReactNode }) => {
6+
const [configLoaded, setConfigLoaded] = useState(false);
7+
const [error, setError] = useState<string | null>(null);
8+
9+
useEffect(() => {
10+
waitForConfig()
11+
.then(() => setConfigLoaded(true))
12+
.catch((error) => {
13+
Logger.error("Failed to load dynamic config:", error);
14+
setError(error);
15+
setConfigLoaded(true);
16+
});
17+
}, []);
18+
19+
if (!configLoaded) {
20+
return <div>Loading application configuration...</div>;
21+
}
22+
23+
if (error) {
24+
return <div>Error loading application configuration: {error}</div>;
25+
}
26+
27+
return <>{children}</>;
28+
};

src/StreamMaster.WebUI/app/index.tsx

+22-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
import store, { persistor } from '@lib/redux/store';
2-
import { PrimeReactProvider } from 'primereact/api';
3-
import React from 'react';
4-
import ReactDOM from 'react-dom/client';
5-
import { Provider } from 'react-redux';
6-
import { PersistGate } from 'redux-persist/integration/react';
7-
import App from './App';
1+
import store, { persistor } from "@lib/redux/store";
2+
import { PrimeReactProvider } from "primereact/api";
3+
import React from "react";
4+
import ReactDOM from "react-dom/client";
5+
import { Provider } from "react-redux";
6+
import { PersistGate } from "redux-persist/integration/react";
7+
import App from "./App";
8+
import { ConfigLoader } from "./ConfigLoader";
89

9-
const root = ReactDOM.createRoot(document.querySelector('#root') as HTMLElement);
10+
const root = ReactDOM.createRoot(
11+
document.querySelector("#root") as HTMLElement,
12+
);
1013

1114
root.render(
12-
<React.StrictMode>
13-
<Provider store={store}>
14-
<PersistGate persistor={persistor}>
15-
<PrimeReactProvider value={{ inputStyle: 'outlined', ripple: false }}>
16-
<App />
17-
</PrimeReactProvider>
18-
</PersistGate>
19-
</Provider>
20-
</React.StrictMode>
15+
<React.StrictMode>
16+
<ConfigLoader>
17+
<Provider store={store}>
18+
<PersistGate persistor={persistor}>
19+
<PrimeReactProvider value={{ inputStyle: "outlined", ripple: false }}>
20+
<App />
21+
</PrimeReactProvider>
22+
</PersistGate>
23+
</Provider>
24+
</ConfigLoader>
25+
</React.StrictMode>,
2126
);
+125-17
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,137 @@
1-
// settings.ts
1+
import { Logger } from "@lib/common/logger";
22

33
export const isClient = typeof window !== "undefined";
4-
54
export const isDev = process.env.NODE_ENV === "development";
65

7-
const basePath = window.location.pathname.split('/').slice(0, -1).join('/');
6+
interface AppConfig {
7+
defaultPort: number;
8+
defaultBaseUrl: string;
9+
}
10+
11+
const defaultConfig: AppConfig = {
12+
defaultPort: 7095,
13+
defaultBaseUrl: "",
14+
};
15+
16+
let configPromise: Promise<AppConfig> | null = null;
17+
let configData: AppConfig | null = null;
18+
19+
/**
20+
* Dynamically determines the base path of the application
21+
*/
22+
export const getBasePath = () => {
23+
if (!isClient) return "/";
24+
25+
try {
26+
// Get the URL of the current module
27+
// https://vite.dev/guide/assets#new-url-url-import-meta-url
28+
const currentUrl = new URL(import.meta.url);
29+
30+
// Module URL typically points to /assets/[hash].js
31+
// We need to go up to the root directory...
32+
const pathParts = currentUrl.pathname.split("/");
833

9-
const loadConfig = () => {
10-
const request = new XMLHttpRequest();
11-
request.open("GET", `${basePath}/config.json`, false); // use dynamic base path. Synchronous request
12-
request.send(null);
34+
// Find where the assets directory is
35+
const assetsIndex = pathParts.findIndex((part) => part === "assets");
1336

14-
if (request.status === 200) {
15-
return JSON.parse(request.responseText);
16-
} else {
17-
throw new Error(`Failed to load config: ${request.status}`);
37+
if (assetsIndex !== -1) {
38+
// Remove everything from assets onwards to get the app's root path
39+
const basePath = `${pathParts.slice(0, assetsIndex).join("/")}/`;
40+
return basePath;
41+
}
42+
43+
// Fallback if structure is different
44+
Logger.warn("Could not determine base path from module URL");
45+
return "/";
46+
} catch (error) {
47+
Logger.error("Error determining base path:", error);
48+
return "/";
1849
}
1950
};
2051

21-
const config = loadConfig();
52+
/**
53+
* Loads the configuration file using fetch
54+
* Ensures the path is relative to the application root
55+
*/
56+
export const loadConfig = async (): Promise<AppConfig> => {
57+
if (configData) {
58+
return configData;
59+
}
60+
61+
if (!configPromise) {
62+
configPromise = (async (): Promise<AppConfig> => {
63+
if (!isClient) {
64+
Logger.debug(
65+
"Config not loaded in server environment, using defaults.",
66+
);
67+
return defaultConfig;
68+
}
69+
70+
try {
71+
// Get the dynamically determined base path
72+
const basePath = getBasePath();
73+
74+
// Join the base path with config.json
75+
const configPath = `${basePath}config.json`;
76+
77+
const response = await fetch(configPath);
2278

23-
export const defaultPort = config.defaultPort;
24-
export const defaultBaseUrl = config.defaultBaseUrl;
79+
if (!response.ok) {
80+
Logger.error(`Failed to load config: ${response.status}`);
81+
throw new Error(`Failed to load config: ${response.status}`);
82+
}
2583

26-
export const baseHostURL =
27-
isClient && !isDev
84+
const data = (await response.json()) as AppConfig;
85+
configData = {
86+
...defaultConfig,
87+
...data,
88+
};
89+
return configData;
90+
} catch (error) {
91+
Logger.error("Config loading error:", error);
92+
configData = defaultConfig;
93+
return configData;
94+
}
95+
})();
96+
}
97+
98+
return configPromise;
99+
};
100+
101+
/**
102+
* Waits for configuration to be loaded before proceeding
103+
*/
104+
export const waitForConfig = async (): Promise<AppConfig> => {
105+
if (!isClient) {
106+
return defaultConfig;
107+
}
108+
return loadConfig();
109+
};
110+
111+
/**
112+
* Gets the base URL for API requests
113+
*/
114+
export const getBaseHostURL = async (): Promise<string> => {
115+
const config = await loadConfig();
116+
117+
return isClient && !isDev
28118
? `${window.location.protocol}//${window.location.host}`
29-
: `http://localhost:${defaultPort}${defaultBaseUrl}`;
119+
: `http://localhost:${config.defaultPort}${config.defaultBaseUrl}`;
120+
};
121+
122+
// For backward compatibility - these will be initialized after config loads
123+
export let defaultPort: number = defaultConfig.defaultPort;
124+
export let defaultBaseUrl: string = defaultConfig.defaultBaseUrl;
125+
export let baseHostURL = "";
126+
127+
// Initialize the values if we're in a client environment
128+
if (isClient) {
129+
loadConfig().then((config) => {
130+
defaultPort = config.defaultPort;
131+
defaultBaseUrl = config.defaultBaseUrl;
132+
133+
baseHostURL = !isDev
134+
? `${window.location.protocol}//${window.location.host}`
135+
: `http://localhost:${defaultPort}${defaultBaseUrl}`;
136+
});
137+
}

0 commit comments

Comments
 (0)