9router / src /app /callback /page.js
2api
feat: configurable stream stall timeout + per-provider UI
88c4c60
Raw
History Blame Contribute Delete
5.39 kB
"use client";
import { Suspense, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
/**
* OAuth Callback Page Content
*/
function CallbackContent() {
const searchParams = useSearchParams();
const [status, setStatus] = useState("processing");
useEffect(() => {
const code = searchParams.get("code");
const state = searchParams.get("state");
const error = searchParams.get("error");
const errorDescription = searchParams.get("error_description");
const callbackData = {
code,
state,
error,
errorDescription,
fullUrl: window.location.href,
};
let relayed = false;
// Trusted origins that may receive this callback. The OAuth code/state
// must only be relayed to the dashboard window we expect to be the opener
// (same origin) or the Codex helper that listens on a fixed loopback port.
// Any other origin is treated as hostile (drive-by attacker that opened
// the popup against the well-known redirect_uri to phish the code).
const expectedOrigins = [
window.location.origin, // Same origin (for most providers)
"http://localhost:1455", // Codex specific port
];
// Method 1: postMessage to opener (popup mode)
// Send once per expected origin. The browser delivers the message only
// when the opener's origin matches the targetOrigin we pass — using "*"
// here would leak the code/state to any opener (e.g. an attacker page
// that opened this URL in a popup), so iterate over the allowlist.
if (window.opener) {
for (const origin of expectedOrigins) {
try {
window.opener.postMessage({ type: "oauth_callback", data: callbackData }, origin);
relayed = true;
} catch (e) {
console.log("postMessage failed:", e);
}
}
}
// Method 2: BroadcastChannel (same origin tabs)
try {
const channel = new BroadcastChannel("oauth_callback");
channel.postMessage(callbackData);
channel.close();
relayed = true;
} catch (e) {
console.log("BroadcastChannel failed:", e);
}
// Method 3: localStorage event (fallback)
try {
localStorage.setItem("oauth_callback", JSON.stringify({ ...callbackData, timestamp: Date.now() }));
relayed = true;
} catch (e) {
console.log("localStorage failed:", e);
}
if (!(code || error)) {
setTimeout(() => setStatus("manual"), 0);
return;
}
setStatus("success");
setTimeout(() => {
window.close();
setTimeout(() => setStatus("done"), 500);
}, 1500);
}, [searchParams]);
return (
<div className="min-h-screen flex items-center justify-center bg-bg">
<div className="text-center p-8 max-w-md">
{status === "processing" && (
<>
<div className="size-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center">
<span className="material-symbols-outlined text-3xl text-primary animate-spin">progress_activity</span>
</div>
<h1 className="text-xl font-semibold mb-2">Processing...</h1>
<p className="text-text-muted">Please wait while we complete the authorization.</p>
</>
)}
{(status === "success" || status === "done") && (
<>
<div className="size-16 mx-auto mb-4 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
<span className="material-symbols-outlined text-3xl text-green-600">check_circle</span>
</div>
<h1 className="text-xl font-semibold mb-2">Authorization Successful!</h1>
<p className="text-text-muted">
{status === "success" ? "This window will close automatically..." : "You can close this tab now."}
</p>
</>
)}
{status === "manual" && (
<>
<div className="size-16 mx-auto mb-4 rounded-full bg-yellow-100 dark:bg-yellow-900/30 flex items-center justify-center">
<span className="material-symbols-outlined text-3xl text-yellow-600">info</span>
</div>
<h1 className="text-xl font-semibold mb-2">Copy This URL</h1>
<p className="text-text-muted mb-4">
Please copy the URL from the address bar and paste it in the application.
</p>
<div className="bg-surface border border-border rounded-lg p-3 text-left">
<code className="text-xs break-all">{typeof window !== "undefined" ? window.location.href : ""}</code>
</div>
</>
)}
</div>
</div>
);
}
/**
* OAuth Callback Page
* Receives callback from OAuth providers and sends data back via multiple methods
*/
export default function CallbackPage() {
return (
<Suspense fallback={
<div className="min-h-screen flex items-center justify-center bg-bg">
<div className="text-center p-8">
<div className="size-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center">
<span className="material-symbols-outlined text-3xl text-primary animate-spin">progress_activity</span>
</div>
<p className="text-text-muted">Loading...</p>
</div>
</div>
}>
<CallbackContent />
</Suspense>
);
}