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 <noreply@anthropic.com>
This commit is contained in:
parent
728fea3665
commit
8ae2da590f
4 changed files with 31 additions and 15 deletions
25
src/App.tsx
25
src/App.tsx
|
|
@ -4,17 +4,24 @@ import { ConversationApp } from '@/components/ConversationApp'
|
||||||
import { OAuthCallback } from '@/components/OAuthCallback'
|
import { OAuthCallback } from '@/components/OAuthCallback'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// Simple routing based on pathname
|
// Check if this is an OAuth callback based on query parameters
|
||||||
const pathname = window.location.pathname;
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const isInferenceOAuthCallback = pathname === '/oauth/inference/callback';
|
const code = urlParams.get('code');
|
||||||
const isMcpOAuthCallback = pathname === '/oauth/mcp/callback';
|
const state = urlParams.get('state');
|
||||||
|
|
||||||
if (isInferenceOAuthCallback) {
|
// Determine OAuth callback type from state parameter
|
||||||
return <OAuthCallback type="inference" />;
|
let oauthType: 'inference' | 'mcp' | null = null;
|
||||||
|
if (code && state) {
|
||||||
|
// The state parameter includes the callback type
|
||||||
|
if (state.includes('inference:')) {
|
||||||
|
oauthType = 'inference';
|
||||||
|
} else if (state.includes('mcp:')) {
|
||||||
|
oauthType = 'mcp';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMcpOAuthCallback) {
|
if (oauthType) {
|
||||||
return <OAuthCallback type="mcp" />;
|
return <OAuthCallback type={oauthType} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
get redirectUrl(): string {
|
get redirectUrl(): string {
|
||||||
return `${window.location.origin}/oauth/mcp/callback`;
|
return `${window.location.origin}${window.location.pathname}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get clientMetadata(): OAuthClientMetadata {
|
get clientMetadata(): OAuthClientMetadata {
|
||||||
|
|
@ -85,7 +85,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
|
||||||
state(): string {
|
state(): string {
|
||||||
// Encode connection ID in the state parameter for callback identification
|
// Encode connection ID in the state parameter for callback identification
|
||||||
const randomPart = this.generateRandomString(8);
|
const randomPart = this.generateRandomString(8);
|
||||||
return `${this.connectionId}.${randomPart}`;
|
return `mcp:${this.connectionId}.${randomPart}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a consistent key for the server based on URL
|
// Generate a consistent key for the server based on URL
|
||||||
|
|
@ -141,7 +141,7 @@ class MCPOAuthProvider implements OAuthClientProvider {
|
||||||
|
|
||||||
// Extract connection ID from state parameter
|
// Extract connection ID from state parameter
|
||||||
const state = event.data.state;
|
const state = event.data.state;
|
||||||
if (!state || !state.startsWith(this.connectionId + '.')) return;
|
if (!state || !state.startsWith(`mcp:${this.connectionId}.`)) return;
|
||||||
|
|
||||||
window.removeEventListener('message', handleMessage);
|
window.removeEventListener('message', handleMessage);
|
||||||
popup.close();
|
popup.close();
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export class OpenRouterOAuthProvider extends InferenceProvider {
|
||||||
constructor(config?: OpenRouterOAuthConfig) {
|
constructor(config?: OpenRouterOAuthConfig) {
|
||||||
super();
|
super();
|
||||||
this.oauthConfig = {
|
this.oauthConfig = {
|
||||||
redirectUri: `${window.location.origin}/oauth/inference/callback`,
|
redirectUri: `${window.location.origin}${window.location.pathname}`,
|
||||||
...config,
|
...config,
|
||||||
};
|
};
|
||||||
this.client = new OpenRouterClient(config || {});
|
this.client = new OpenRouterClient(config || {});
|
||||||
|
|
@ -253,7 +253,11 @@ export class OpenRouterOAuthProvider extends InferenceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedState: OAuthState = JSON.parse(storedStateJson);
|
const storedState: OAuthState = JSON.parse(storedStateJson);
|
||||||
if (storedState.state !== state || Date.now() > storedState.expiresAt) {
|
// Extract the actual state without the prefix for comparison
|
||||||
|
const stateWithoutPrefix = state.replace(/^inference:/, '');
|
||||||
|
const storedStateWithoutPrefix = storedState.state.replace(/^inference:/, '');
|
||||||
|
|
||||||
|
if (storedStateWithoutPrefix !== stateWithoutPrefix || Date.now() > storedState.expiresAt) {
|
||||||
localStorage.removeItem('openrouter_oauth_state');
|
localStorage.removeItem('openrouter_oauth_state');
|
||||||
throw new Error('Invalid or expired OAuth state');
|
throw new Error('Invalid or expired OAuth state');
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +323,9 @@ export class OpenRouterOAuthProvider extends InferenceProvider {
|
||||||
private generateState(): string {
|
private generateState(): string {
|
||||||
const array = new Uint8Array(16);
|
const array = new Uint8Array(16);
|
||||||
crypto.getRandomValues(array);
|
crypto.getRandomValues(array);
|
||||||
return btoa(String.fromCharCode.apply(null, Array.from(array)));
|
const randomPart = btoa(String.fromCharCode.apply(null, Array.from(array)));
|
||||||
|
// Include callback type in state for routing
|
||||||
|
return `inference:${randomPart}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private storeTokens(): void {
|
private storeTokens(): void {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import path from 'path'
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
// Set base to '/' for local testing, or to your repo name for GitHub Pages
|
||||||
|
// e.g., base: '/example-remote-client/' for https://username.github.io/example-remote-client/
|
||||||
|
base: '/',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, './src'),
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue