Skip to content

Commit 817697d

Browse files
committed
Removed top-level await & add comprehensive error handling
1 parent 5321872 commit 817697d

File tree

2 files changed

+106
-22
lines changed

2 files changed

+106
-22
lines changed

samples/apps/oauth/src/config.tsx

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ interface RuntimeConfig {
2525
redirectUri?: string;
2626
}
2727

28-
let runtimeConfig: RuntimeConfig = {};
29-
if (!import.meta.env.DEV) {
30-
const response = await fetch('/runtime.json');
31-
runtimeConfig = await response.json();
32-
}
33-
3428
// Helper function to get config value, preferring env vars in development mode
3529
// and filtering out placeholder values from both runtime and env sources
3630
const getConfigValue = (runtimeValue: string | undefined, envValue: string | undefined): string | undefined => {
@@ -50,16 +44,98 @@ const getConfigValue = (runtimeValue: string | undefined, envValue: string | und
5044
return filteredRuntimeValue || filteredEnvValue;
5145
};
5246

47+
// Load runtime configuration asynchronously
48+
const loadRuntimeConfig = async (): Promise<RuntimeConfig> => {
49+
if (import.meta.env.DEV) {
50+
// In development mode, skip fetching runtime.json
51+
return {};
52+
}
53+
54+
try {
55+
const response = await fetch('/runtime.json');
56+
if (!response.ok) {
57+
console.warn(`Failed to fetch runtime.json: ${response.status} ${response.statusText}`);
58+
return {};
59+
}
60+
return await response.json();
61+
} catch (error) {
62+
console.error('Error loading runtime configuration:', error);
63+
// Return empty config to fallback to environment variables
64+
return {};
65+
}
66+
};
67+
68+
// Initialize runtime config with promise-based guard to prevent race conditions
69+
let runtimeConfig: RuntimeConfig = {};
70+
let configInitializationPromise: Promise<void> | null = null;
71+
72+
const initializeConfig = async (): Promise<void> => {
73+
// If already initializing or initialized, return the existing promise
74+
if (configInitializationPromise) {
75+
return configInitializationPromise;
76+
}
77+
78+
// Create and store the initialization promise
79+
// Wrap in try-catch to ensure the promise never rejects
80+
configInitializationPromise = (async () => {
81+
try {
82+
runtimeConfig = await loadRuntimeConfig();
83+
} catch (error) {
84+
// This should never happen since loadRuntimeConfig has its own error handling,
85+
// but we catch it here as a safety measure to ensure the promise always resolves
86+
console.error('Unexpected error during config initialization:', error);
87+
runtimeConfig = {};
88+
}
89+
})();
90+
91+
return configInitializationPromise;
92+
};
93+
94+
// Start initialization immediately
95+
const configReady = initializeConfig();
96+
97+
/**
98+
* Ensures the configuration is fully loaded before proceeding.
99+
*
100+
* IMPORTANT: You must call this function and await it before accessing config values
101+
* in production mode to ensure runtime.json has been loaded.
102+
*
103+
* @example
104+
* ```tsx
105+
* await ensureConfigReady();
106+
* const endpoint = config.applicationID; // Now safe to access
107+
* ```
108+
*/
109+
export const ensureConfigReady = (): Promise<void> => configReady;
110+
53111
const config = {
54-
applicationID: getConfigValue(runtimeConfig.applicationID, import.meta.env.VITE_REACT_APP_AUTH_APP_ID) || '',
55-
applicationsEndpoint: getConfigValue(runtimeConfig.applicationsEndpoint, import.meta.env.VITE_REACT_APPLICATIONS_ENDPOINT) || '',
56-
flowEndpoint: getConfigValue(runtimeConfig.flowEndpoint, import.meta.env.VITE_REACT_APP_SERVER_FLOW_ENDPOINT) || '',
57-
authorizationEndpoint: getConfigValue(runtimeConfig.authorizationEndpoint, import.meta.env.VITE_REACT_APP_SERVER_AUTHORIZATION_ENDPOINT) || '',
58-
tokenEndpoint: getConfigValue(runtimeConfig.tokenEndpoint, import.meta.env.VITE_REACT_APP_SERVER_TOKEN_ENDPOINT) || '',
59-
clientId: import.meta.env.VITE_REACT_APP_CLIENT_ID || '',
60-
clientSecret: import.meta.env.VITE_REACT_APP_CLIENT_SECRET || '',
61-
redirectUri: getConfigValue(runtimeConfig.redirectUri, import.meta.env.VITE_REACT_APP_REDIRECT_URI) || '',
62-
scope: import.meta.env.VITE_REACT_APP_SCOPE || ''
112+
get applicationID() {
113+
return getConfigValue(runtimeConfig.applicationID, import.meta.env.VITE_REACT_APP_AUTH_APP_ID) || '';
114+
},
115+
get applicationsEndpoint() {
116+
return getConfigValue(runtimeConfig.applicationsEndpoint, import.meta.env.VITE_REACT_APPLICATIONS_ENDPOINT) || '';
117+
},
118+
get flowEndpoint() {
119+
return getConfigValue(runtimeConfig.flowEndpoint, import.meta.env.VITE_REACT_APP_SERVER_FLOW_ENDPOINT) || '';
120+
},
121+
get authorizationEndpoint() {
122+
return getConfigValue(runtimeConfig.authorizationEndpoint, import.meta.env.VITE_REACT_APP_SERVER_AUTHORIZATION_ENDPOINT) || '';
123+
},
124+
get tokenEndpoint() {
125+
return getConfigValue(runtimeConfig.tokenEndpoint, import.meta.env.VITE_REACT_APP_SERVER_TOKEN_ENDPOINT) || '';
126+
},
127+
get clientId() {
128+
return import.meta.env.VITE_REACT_APP_CLIENT_ID || '';
129+
},
130+
get clientSecret() {
131+
return import.meta.env.VITE_REACT_APP_CLIENT_SECRET || '';
132+
},
133+
get redirectUri() {
134+
return getConfigValue(runtimeConfig.redirectUri, import.meta.env.VITE_REACT_APP_REDIRECT_URI) || '';
135+
},
136+
get scope() {
137+
return import.meta.env.VITE_REACT_APP_SCOPE || '';
138+
}
63139
};
64140

65141
export default config;

samples/apps/oauth/src/main.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,19 @@ import { createRoot } from 'react-dom/client'
2121
import './index.css'
2222
import App from './App.tsx'
2323
import ThemeProvider from './theme/ThemeProvider.tsx'
24+
import { ensureConfigReady } from './config.tsx'
2425

25-
createRoot(document.getElementById('root')!).render(
26-
<StrictMode>
27-
<ThemeProvider>
28-
<App />
29-
</ThemeProvider>
30-
</StrictMode>,
31-
)
26+
// Ensure configuration is loaded before rendering the app
27+
ensureConfigReady().then(() => {
28+
createRoot(document.getElementById('root')!).render(
29+
<StrictMode>
30+
<ThemeProvider>
31+
<App />
32+
</ThemeProvider>
33+
</StrictMode>,
34+
)
35+
}).catch((error) => {
36+
console.error('Failed to initialize application configuration:', error);
37+
// Render error state or fallback UI
38+
document.getElementById('root')!.innerHTML = '<div style="padding: 20px; text-align: center;"><h1>Configuration Error</h1><p>Failed to load application configuration. Please refresh the page.</p></div>';
39+
});

0 commit comments

Comments
 (0)