Skip to content

Use BroadcastChannel API instead of window.opener to communicate between auth popup & original window #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
21 changes: 9 additions & 12 deletions src/auth/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
12 changes: 8 additions & 4 deletions src/react/useMcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
const [error, setError] = useState<string | undefined>(undefined)
const [log, setLog] = useState<UseMcpResult['log']>([])
const [authUrl, setAuthUrl] = useState<string | undefined>(undefined)
const [broadcastChannel, setBroadcastChannel] = useState<BroadcastChannel | null>(null)

const clientRef = useRef<Client | null>(null)
// Transport ref can hold either type now
Expand Down Expand Up @@ -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') {
Expand All @@ -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(() => {
Expand All @@ -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 () => {
Expand Down