export interface OAuthUiState { code: string | null; state: string | null; error: string | null; } const STATE_STORAGE_KEY = "omo.dashboard.oauth.state"; export function readOAuthUiState(): OAuthUiState { const params = new URLSearchParams(window.location.search); return { code: params.get("code"), state: params.get("state"), error: params.get("error"), }; } export function buildDiscordAuthorizeUrl( clientId: string, redirectUri: string, ): string { const state = crypto.randomUUID(); window.localStorage.setItem(STATE_STORAGE_KEY, state); const url = new URL("https://discord.com/oauth2/authorize"); url.searchParams.set("client_id", clientId); url.searchParams.set("response_type", "code"); url.searchParams.set("scope", "identify guilds"); url.searchParams.set("redirect_uri", redirectUri); url.searchParams.set("state", state); url.searchParams.set("prompt", "consent"); return url.toString(); } export function isExpectedOAuthState(state: string | null): boolean { if (!state) { return false; } const expected = window.localStorage.getItem(STATE_STORAGE_KEY); return expected === state; } export function clearOAuthQueryParams(): void { const url = new URL(window.location.href); url.searchParams.delete("code"); url.searchParams.delete("state"); url.searchParams.delete("error"); window.history.replaceState({}, document.title, url.toString()); }