diff --git a/src/auth/callback.ts b/src/auth/callback.ts index 2c2c482..7ae6576 100644 --- a/src/auth/callback.ts +++ b/src/auth/callback.ts @@ -19,6 +19,8 @@ export async function onMcpAuthorization() { let provider: BrowserOAuthClientProvider | null = null let storedStateData: StoredState | null = null + let broadcastChannel: BroadcastChannel | null = null + const stateKey = state ? `mcp:auth:state_${state}` : null // Reconstruct state key prefix assumption try { @@ -44,6 +46,8 @@ export async function onMcpAuthorization() { throw new Error('Failed to parse stored OAuth state.') } + // Ensure we have a BroadcastChannel for communication + broadcastChannel = new BroadcastChannel(`mcp-auth-${storedStateData.providerOptions.serverUrl}`) // Validate expiry if (!storedStateData.expiry || storedStateData.expiry < Date.now()) { localStorage.removeItem(stateKey) // Clean up expired state @@ -72,13 +76,8 @@ export async function onMcpAuthorization() { if (authResult === 'AUTHORIZED') { console.log(`${logPrefix} Authorization successful via SDK auth(). Notifying opener...`) // --- Notify Opener and Close (Success) --- - if (window.opener && !window.opener.closed) { - window.opener.postMessage({ type: 'mcp_auth_callback', success: true }, window.location.origin) - window.close() - } else { - console.warn(`${logPrefix} No opener window detected. Redirecting to root.`) - window.location.href = '/' // Or a configured post-auth destination - } + broadcastChannel.postMessage({ type: 'mcp_auth_callback', success: true }) + window.close() // Clean up state ONLY on success and after notifying opener localStorage.removeItem(stateKey) } else { @@ -91,11 +90,9 @@ export async function onMcpAuthorization() { const errorMessage = err instanceof Error ? err.message : String(err) // --- Notify Opener and Display Error (Failure) --- - if (window.opener && !window.opener.closed) { - window.opener.postMessage({ type: 'mcp_auth_callback', success: false, error: errorMessage }, window.location.origin) - // Optionally close even on error, depending on UX preference - // window.close(); - } + broadcastChannel?.postMessage({ type: 'mcp_auth_callback', success: false, error: errorMessage }) + // Optionally close even on error, depending on UX preference + // window.close(); // Display error in the callback window try { diff --git a/src/react/useMcp.ts b/src/react/useMcp.ts index 697f74a..60dcacc 100644 --- a/src/react/useMcp.ts +++ b/src/react/useMcp.ts @@ -58,6 +58,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { const [error, setError] = useState(undefined) const [log, setLog] = useState([]) const [authUrl, setAuthUrl] = useState(undefined) + const [broadcastChannel, setBroadcastChannel] = useState(null) const clientRef = useRef(null) // Transport ref can hold either type now @@ -744,6 +745,8 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { // Effect for handling auth callback messages from popup (Stable dependencies) useEffect(() => { + if (!broadcastChannel) return + const messageHandler = (event: MessageEvent) => { if (event.origin !== window.location.origin) return if (event.data?.type === 'mcp_auth_callback') { @@ -759,15 +762,15 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { } } } - window.addEventListener('message', messageHandler) + + broadcastChannel.addEventListener('message', messageHandler) addLog('debug', 'Auth callback message listener added.') return () => { - window.removeEventListener('message', messageHandler) + broadcastChannel.removeEventListener('message', messageHandler) addLog('debug', 'Auth callback message listener removed.') if (authTimeoutRef.current) clearTimeout(authTimeoutRef.current) } - // Dependencies are stable callbacks - }, [addLog, failConnection, connect]) + }, [broadcastChannel, addLog, failConnection, connect]) // Initial Connection (depends on config and stable callbacks) useEffect(() => { @@ -786,6 +789,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { onPopupWindow, }) addLog('debug', 'BrowserOAuthClientProvider initialized/updated on mount/option change.') + setBroadcastChannel(new BroadcastChannel(`mcp-auth-${url}`)) } connect() // Call stable connect return () => {