Skip to content

Commit 8ae2da5

Browse files
Enable static site deployment with OAuth callback support
- Update OAuth callbacks to use query parameters instead of path routing - Change redirect URIs to use current page URL (works with any base path) - Add callback type prefix to state parameter (inference: or mcp:) - Update App.tsx to route OAuth callbacks based on query params - Add base path configuration to vite.config.ts for GitHub Pages - Now works with static hosting (GitHub Pages, python http.server, etc) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 728fea3 commit 8ae2da5

File tree

4 files changed

+31
-15
lines changed

4 files changed

+31
-15
lines changed

src/App.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@ import { ConversationApp } from '@/components/ConversationApp'
44
import { OAuthCallback } from '@/components/OAuthCallback'
55

66
function App() {
7-
// Simple routing based on pathname
8-
const pathname = window.location.pathname;
9-
const isInferenceOAuthCallback = pathname === '/oauth/inference/callback';
10-
const isMcpOAuthCallback = pathname === '/oauth/mcp/callback';
11-
12-
if (isInferenceOAuthCallback) {
13-
return <OAuthCallback type="inference" />;
7+
// Check if this is an OAuth callback based on query parameters
8+
const urlParams = new URLSearchParams(window.location.search);
9+
const code = urlParams.get('code');
10+
const state = urlParams.get('state');
11+
12+
// Determine OAuth callback type from state parameter
13+
let oauthType: 'inference' | 'mcp' | null = null;
14+
if (code && state) {
15+
// The state parameter includes the callback type
16+
if (state.includes('inference:')) {
17+
oauthType = 'inference';
18+
} else if (state.includes('mcp:')) {
19+
oauthType = 'mcp';
20+
}
1421
}
1522

16-
if (isMcpOAuthCallback) {
17-
return <OAuthCallback type="mcp" />;
23+
if (oauthType) {
24+
return <OAuthCallback type={oauthType} />;
1825
}
1926

2027
return (

src/mcp/connection.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
6969
}
7070

7171
get redirectUrl(): string {
72-
return `${window.location.origin}/oauth/mcp/callback`;
72+
return `${window.location.origin}${window.location.pathname}`;
7373
}
7474

7575
get clientMetadata(): OAuthClientMetadata {
@@ -85,7 +85,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
8585
state(): string {
8686
// Encode connection ID in the state parameter for callback identification
8787
const randomPart = this.generateRandomString(8);
88-
return `${this.connectionId}.${randomPart}`;
88+
return `mcp:${this.connectionId}.${randomPart}`;
8989
}
9090

9191
// Generate a consistent key for the server based on URL
@@ -141,7 +141,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
141141

142142
// Extract connection ID from state parameter
143143
const state = event.data.state;
144-
if (!state || !state.startsWith(this.connectionId + '.')) return;
144+
if (!state || !state.startsWith(`mcp:${this.connectionId}.`)) return;
145145

146146
window.removeEventListener('message', handleMessage);
147147
popup.close();

src/providers/openrouter/oauth-provider.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class OpenRouterOAuthProvider extends InferenceProvider {
3333
constructor(config?: OpenRouterOAuthConfig) {
3434
super();
3535
this.oauthConfig = {
36-
redirectUri: `${window.location.origin}/oauth/inference/callback`,
36+
redirectUri: `${window.location.origin}${window.location.pathname}`,
3737
...config,
3838
};
3939
this.client = new OpenRouterClient(config || {});
@@ -253,7 +253,11 @@ export class OpenRouterOAuthProvider extends InferenceProvider {
253253
}
254254

255255
const storedState: OAuthState = JSON.parse(storedStateJson);
256-
if (storedState.state !== state || Date.now() > storedState.expiresAt) {
256+
// Extract the actual state without the prefix for comparison
257+
const stateWithoutPrefix = state.replace(/^inference:/, '');
258+
const storedStateWithoutPrefix = storedState.state.replace(/^inference:/, '');
259+
260+
if (storedStateWithoutPrefix !== stateWithoutPrefix || Date.now() > storedState.expiresAt) {
257261
localStorage.removeItem('openrouter_oauth_state');
258262
throw new Error('Invalid or expired OAuth state');
259263
}
@@ -319,7 +323,9 @@ export class OpenRouterOAuthProvider extends InferenceProvider {
319323
private generateState(): string {
320324
const array = new Uint8Array(16);
321325
crypto.getRandomValues(array);
322-
return btoa(String.fromCharCode.apply(null, Array.from(array)));
326+
const randomPart = btoa(String.fromCharCode.apply(null, Array.from(array)));
327+
// Include callback type in state for routing
328+
return `inference:${randomPart}`;
323329
}
324330

325331
private storeTokens(): void {

vite.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import path from 'path'
55
// https://vitejs.dev/config/
66
export default defineConfig({
77
plugins: [react()],
8+
// Set base to '/' for local testing, or to your repo name for GitHub Pages
9+
// e.g., base: '/example-remote-client/' for https://username.github.io/example-remote-client/
10+
base: '/',
811
resolve: {
912
alias: {
1013
'@': path.resolve(__dirname, './src'),

0 commit comments

Comments
 (0)