Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions samples/apps/oauth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,34 @@ Generate a self-signed SSL certificate by running the following command:
openssl req -nodes -new -x509 -keyout server.key -out server.cert
```

**Configure environment variables:**
**Configure the application:**

Add the following environment variables to your web server configuration or `.env` file. Replace `<your-app-id>` with your actual application ID.
There are two ways to configure the application depending on your use case:

**For Development (using `npm run dev`):**

Create or update the `.env` file in the root directory with your configuration. Replace `<your-app-id>` with your actual application ID:

```env
VITE_REACT_APPLICATIONS_ENDPOINT=https://localhost:8090/applications
VITE_REACT_APP_SERVER_FLOW_ENDPOINT=https://localhost:8090/flow
VITE_REACT_APP_AUTH_APP_ID=<your-app-id>
```

**For Production (using built app with `npm start` or custom web server):**

Update the `public/runtime.json` file with your configuration:

```json
{
"applicationID": "<your-app-id>",
"flowEndpoint": "https://localhost:8090/flow",
"applicationsEndpoint": "https://localhost:8090/applications"
}
```

**Note:** In development mode, `.env` values take precedence. In production mode, `runtime.json` values take precedence. Placeholder values (e.g., `{your-application-id}`) in `runtime.json` are automatically ignored.

## License

Licenses this source under the Apache License, Version 2.0 LICENSE, You may not use this file except in compliance with the License.
Expand Down
129 changes: 118 additions & 11 deletions samples/apps/oauth/src/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,126 @@
* under the License.
*/

const response = await fetch('/runtime.json');
const runtimeConfig = await response.json();
interface RuntimeConfig {
applicationID?: string;
applicationsEndpoint?: string;
flowEndpoint?: string;
authorizationEndpoint?: string;
tokenEndpoint?: string;
redirectUri?: string;
}

// Helper function to get config value, preferring env vars in development mode
// and filtering out placeholder values from both runtime and env sources
const getConfigValue = (runtimeValue: string | undefined, envValue: string | undefined): string | undefined => {
const isPlaceholder = (value: string | undefined): boolean => {
return !value || (value.startsWith('{') && value.endsWith('}'));
};

// In development mode, prefer env variables, filtering out placeholders from both
const filteredEnvValue = isPlaceholder(envValue) ? undefined : envValue;
const filteredRuntimeValue = isPlaceholder(runtimeValue) ? undefined : runtimeValue;

if (import.meta.env.DEV) {
return filteredEnvValue || filteredRuntimeValue;
}

// In production mode, prefer runtime config but filter out placeholders from both
return filteredRuntimeValue || filteredEnvValue;
};

// Load runtime configuration asynchronously
const loadRuntimeConfig = async (): Promise<RuntimeConfig> => {
if (import.meta.env.DEV) {
// In development mode, skip fetching runtime.json
return {};
}

try {
const response = await fetch('/runtime.json');
if (!response.ok) {
console.warn(`Failed to fetch runtime.json: ${response.status} ${response.statusText}`);
return {};
}
return await response.json();
} catch (error) {
console.error('Error loading runtime configuration:', error);
// Return empty config to fallback to environment variables
return {};
}
};

// Initialize runtime config with promise-based guard to prevent race conditions
let runtimeConfig: RuntimeConfig = {};
let configInitializationPromise: Promise<void> | null = null;

const initializeConfig = async (): Promise<void> => {
// If already initializing or initialized, return the existing promise
if (configInitializationPromise) {
return configInitializationPromise;
}

// Create and store the initialization promise
// Wrap in try-catch to ensure the promise never rejects
configInitializationPromise = (async () => {
try {
runtimeConfig = await loadRuntimeConfig();
} catch (error) {
// This should never happen since loadRuntimeConfig has its own error handling,
// but we catch it here as a safety measure to ensure the promise always resolves
console.error('Unexpected error during config initialization:', error);
runtimeConfig = {};
}
})();

return configInitializationPromise;
};

// Start initialization immediately
const configReady = initializeConfig();

/**
* Ensures the configuration is fully loaded before proceeding.
*
* IMPORTANT: You must call this function and await it before accessing config values
* in production mode to ensure runtime.json has been loaded.
*
* @example
* ```tsx
* await ensureConfigReady();
* const endpoint = config.applicationID; // Now safe to access
* ```
*/
export const ensureConfigReady = (): Promise<void> => configReady;

const config = {
applicationID: runtimeConfig.applicationID || import.meta.env.VITE_REACT_APP_AUTH_APP_ID,
applicationsEndpoint: runtimeConfig.applicationsEndpoint || import.meta.env.VITE_REACT_APPLICATIONS_ENDPOINT,
flowEndpoint: runtimeConfig.flowEndpoint || import.meta.env.VITE_REACT_APP_SERVER_FLOW_ENDPOINT,
authorizationEndpoint: runtimeConfig.authorizationEndpoint || import.meta.env.VITE_REACT_APP_SERVER_AUTHORIZATION_ENDPOINT,
tokenEndpoint: runtimeConfig.tokenEndpoint || import.meta.env.VITE_REACT_APP_SERVER_TOKEN_ENDPOINT,
clientId: import.meta.env.VITE_REACT_APP_CLIENT_ID,
clientSecret: import.meta.env.VITE_REACT_APP_CLIENT_SECRET,
redirectUri: runtimeConfig.redirectUri || import.meta.env.VITE_REACT_APP_REDIRECT_URI,
scope: import.meta.env.VITE_REACT_APP_SCOPE
get applicationID() {
return getConfigValue(runtimeConfig.applicationID, import.meta.env.VITE_REACT_APP_AUTH_APP_ID) || '';
},
get applicationsEndpoint() {
return getConfigValue(runtimeConfig.applicationsEndpoint, import.meta.env.VITE_REACT_APPLICATIONS_ENDPOINT) || '';
},
get flowEndpoint() {
return getConfigValue(runtimeConfig.flowEndpoint, import.meta.env.VITE_REACT_APP_SERVER_FLOW_ENDPOINT) || '';
},
get authorizationEndpoint() {
return getConfigValue(runtimeConfig.authorizationEndpoint, import.meta.env.VITE_REACT_APP_SERVER_AUTHORIZATION_ENDPOINT) || '';
},
get tokenEndpoint() {
return getConfigValue(runtimeConfig.tokenEndpoint, import.meta.env.VITE_REACT_APP_SERVER_TOKEN_ENDPOINT) || '';
},
get clientId() {
return import.meta.env.VITE_REACT_APP_CLIENT_ID || '';
},
get clientSecret() {
return import.meta.env.VITE_REACT_APP_CLIENT_SECRET || '';
},
get redirectUri() {
return getConfigValue(runtimeConfig.redirectUri, import.meta.env.VITE_REACT_APP_REDIRECT_URI) || '';
},
get scope() {
return import.meta.env.VITE_REACT_APP_SCOPE || '';
}
};

export default config;
22 changes: 15 additions & 7 deletions samples/apps/oauth/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@ import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import ThemeProvider from './theme/ThemeProvider.tsx'
import { ensureConfigReady } from './config.tsx'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</StrictMode>,
)
// Ensure configuration is loaded before rendering the app
ensureConfigReady().then(() => {
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</StrictMode>,
)
}).catch((error) => {
console.error('Failed to initialize application configuration:', error);
// Render error state or fallback UI
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>';
});
Loading