"use client"; //=========================================== // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template //=========================================== import { KnownError } from "@hexclave/shared"; import { captureError, HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors"; import { FilterUndefined, filterUndefined } from "@hexclave/shared/dist/utils/objects"; import { use } from "@hexclave/shared/dist/utils/react"; import { getRelativePart } from "@hexclave/shared/dist/utils/urls"; import { notFound, redirect, RedirectType, usePathname, useSearchParams } from 'next/navigation'; // THIS_LINE_PLATFORM next import { Suspense, useMemo, useSyncExternalStore } from 'react'; import { SignIn, SignUp, StackServerApp } from ".."; import { useStackApp } from "../lib/hooks"; import { HandlerUrls, StackClientApp, hexclaveAppInternalsSymbol } from "../lib/hexclave-app"; import { isLocalHandlerUrlTarget, resolveUnknownHandlerPathFallbackUrl } from "../lib/hexclave-app/url-targets"; import { AccountSettings } from "./account-settings"; import { CliAuthConfirmation } from "./cli-auth-confirm"; import { EmailVerification } from "./email-verification"; import { ErrorPage } from "./error-page"; import { ForgotPassword } from "./forgot-password"; import { MagicLinkCallback } from "./magic-link-callback"; import { MFA } from "./mfa"; import { OAuthCallback } from "./oauth-callback"; import { Onboarding } from "./onboarding"; import { PasswordReset } from "./password-reset"; import { SignOut } from "./sign-out"; import { TeamInvitation } from "./team-invitation"; import { KnownErrorMessageCard } from "../components/message-cards/known-error-message-card"; import { MessageCard } from "../components/message-cards/message-card"; import { PredefinedMessageCard } from "../components/message-cards/predefined-message-card"; type Components = { SignIn: typeof SignIn, SignUp: typeof SignUp, EmailVerification: typeof EmailVerification, PasswordReset: typeof PasswordReset, ForgotPassword: typeof ForgotPassword, SignOut: typeof SignOut, OAuthCallback: typeof OAuthCallback, MagicLinkCallback: typeof MagicLinkCallback, TeamInvitation: typeof TeamInvitation, ErrorPage: typeof ErrorPage, AccountSettings: typeof AccountSettings, CliAuthConfirmation: typeof CliAuthConfirmation, MFA: typeof MFA, Onboarding: typeof Onboarding, }; type RouteProps = { params: Promise<{ stack?: string[] }> | { stack?: string[] }, searchParams: Promise> | Record, }; type RedirectToPageResult = | { status: "success" } | { status: "known-error", error: KnownError } | { status: "unknown-error" }; const availablePaths = { signIn: 'sign-in', signUp: 'sign-up', emailVerification: 'email-verification', passwordReset: 'password-reset', forgotPassword: 'forgot-password', signOut: 'sign-out', oauthCallback: 'oauth-callback', magicLinkCallback: 'magic-link-callback', teamInvitation: 'team-invitation', accountSettings: 'account-settings', cliAuthConfirm: 'cli-auth-confirm', mfa: 'mfa', error: 'error', onboarding: 'onboarding', } as const; const placeholderOrigin = "http://example.com"; const pathAliases = { // also includes the uppercase and non-dashed versions ...Object.fromEntries(Object.entries(availablePaths).map(([key, value]) => [value, value])), "log-in": availablePaths.signIn, "register": availablePaths.signUp, } as const; export type BaseHandlerProps = { fullPage: boolean, componentProps?: { [K in keyof Components]?: Parameters[0]; }, }; function renderComponent(props: { path: string, searchParams: Record, fullPage: boolean, componentProps?: BaseHandlerProps['componentProps'], shouldRedirectToPage?: (name: keyof HandlerUrls) => boolean, getDefaultUnknownPathUrl?: (path: string) => string | null, onNotFound: () => any, app: StackClientApp | StackServerApp, }) { const { path, searchParams, fullPage, componentProps, shouldRedirectToPage, getDefaultUnknownPathUrl, onNotFound, app } = props; switch (path) { case availablePaths.signIn: { if (shouldRedirectToPage?.('signIn')) return { redirectToPage: 'signIn' as const }; return ; } case availablePaths.signUp: { if (shouldRedirectToPage?.('signUp')) return { redirectToPage: 'signUp' as const }; return ; } case availablePaths.emailVerification: { if (shouldRedirectToPage?.('emailVerification')) return { redirectToPage: 'emailVerification' as const }; return ; } case availablePaths.passwordReset: { if (shouldRedirectToPage?.('passwordReset')) return { redirectToPage: 'passwordReset' as const }; return ; } case availablePaths.forgotPassword: { if (shouldRedirectToPage?.('forgotPassword')) return { redirectToPage: 'forgotPassword' as const }; return ; } case availablePaths.signOut: { if (shouldRedirectToPage?.('signOut')) return { redirectToPage: 'signOut' as const }; return ; } case availablePaths.oauthCallback: { if (shouldRedirectToPage?.('oauthCallback')) return { redirectToPage: 'oauthCallback' as const }; return ; } case availablePaths.magicLinkCallback: { if (shouldRedirectToPage?.('magicLinkCallback')) return { redirectToPage: 'magicLinkCallback' as const }; return ; } case availablePaths.teamInvitation: { if (shouldRedirectToPage?.('teamInvitation')) return { redirectToPage: 'teamInvitation' as const }; return ; } case availablePaths.accountSettings: { return ; } case availablePaths.error: { return ; } case availablePaths.cliAuthConfirm: { if (shouldRedirectToPage?.('cliAuthConfirm')) return { redirectToPage: 'cliAuthConfirm' as const }; return ; } case availablePaths.mfa: { if (shouldRedirectToPage?.('mfa')) return { redirectToPage: 'mfa' as const }; return ; } case availablePaths.onboarding: { if (shouldRedirectToPage?.('onboarding')) return { redirectToPage: 'onboarding' as const }; return ; } default: { if (Object.values(availablePaths).includes(path as any)) { throw new HexclaveAssertionError(`Path alias ${path} not included in switch statement, but in availablePaths?`, { availablePaths }); } for (const [key, value] of Object.entries(pathAliases)) { if (path.toLowerCase().replaceAll('-', '') === key.toLowerCase().replaceAll('-', '')) { const redirectUrl = `${app[hexclaveAppInternalsSymbol].getUrls().handler}/${value}?${new URLSearchParams(searchParams).toString()}`; return { redirect: redirectUrl }; } } const defaultUnknownPathUrl = getDefaultUnknownPathUrl?.(path); if (defaultUnknownPathUrl != null) { const defaultUnknownPathUrlObject = new URL(defaultUnknownPathUrl, "http://example.com"); for (const [key, value] of Object.entries(searchParams)) { defaultUnknownPathUrlObject.searchParams.set(key, value); } return { redirect: toAbsoluteOrRelativeRedirectTarget(defaultUnknownPathUrlObject) }; } return onNotFound(); } } } export async function getRedirectToPageResult( app: StackClientApp, redirectToPage: keyof HandlerUrls, ): Promise { try { await app[hexclaveAppInternalsSymbol].redirectToHandler(redirectToPage, { replace: true }); return { status: "success" }; } catch (e) { if (KnownError.isKnownError(e)) { return { status: "known-error", error: e }; } captureError("", e); return { status: "unknown-error" }; } } function RedirectToPage(props: { app: StackClientApp, fullPage?: boolean, redirectToPage: keyof HandlerUrls, }) { const redirectResultPromise = useMemo( () => getRedirectToPageResult(props.app, props.redirectToPage), [props.app, props.redirectToPage], ); const redirectResult = use(redirectResultPromise); if (redirectResult.status === "known-error") { return ; } if (redirectResult.status === "unknown-error") { return ; } return ; } export function HexclaveHandlerClient(props: BaseHandlerProps & Partial & { location?: string }) { // Use hooks to get app const hexclaveApp = useStackApp(); const handlerUrls = hexclaveApp[hexclaveAppInternalsSymbol].getUrls(); const clientOrigin = useClientOriginAfterHydration(); const pathname = usePathname(); const searchParamsFromHook = useSearchParams(); const currentLocation = pathname; const searchParamsSource = searchParamsFromHook; const { path, searchParams, handlerPath } = useMemo(() => { const handlerPath = new URL(handlerUrls.handler, 'http://example.com').pathname; const relativePath = currentLocation.startsWith(handlerPath) ? currentLocation.slice(handlerPath.length).replace(/^\/+/, '') : currentLocation.replace(/^\/+/, ''); return { path: relativePath, searchParams: Object.fromEntries(searchParamsSource.entries()), handlerPath, }; }, [currentLocation, searchParamsSource, handlerUrls.handler]); const getDefaultUnknownPathUrl = (unknownPath: string): string | null => { return resolveUnknownHandlerPathFallbackUrl({ defaultTarget: hexclaveApp[hexclaveAppInternalsSymbol].getConstructorOptions().urls?.default, projectId: hexclaveApp.projectId, unknownPath, }); }; const shouldRedirectToPage = (name: keyof HandlerUrls): boolean => { const url = handlerUrls[name]; const isCrossDomainLocalOauthCallback = name === "oauthCallback" && searchParams.hexclave_cross_domain_auth === "1"; if (isCrossDomainLocalOauthCallback) { return false; } return !isLocalHandlerUrlTarget({ targetUrl: url, handlerPath, currentOrigin: clientOrigin, }); }; const result = renderComponent({ path, searchParams, fullPage: props.fullPage, componentProps: props.componentProps, shouldRedirectToPage, getDefaultUnknownPathUrl, onNotFound: () => notFound() , app: hexclaveApp, }); const redirectToPage = (result != null && typeof result === 'object' && 'redirectToPage' in result) ? result.redirectToPage : undefined; if (redirectToPage != null) { return ( }> ); } if (result && 'redirect' in result) { redirect(result.redirect, RedirectType.replace); } return result; } // filter undefined values in object. if object itself is undefined, return undefined function filterUndefinedINU(value: T | undefined): FilterUndefined | undefined { return value === undefined ? value : filterUndefined(value); } function toAbsoluteOrRelativeRedirectTarget(url: URL): string { return url.origin === "http://example.com" ? getRelativePart(url) : url.toString(); } function useClientOriginAfterHydration(): string | undefined { // The first hydrated render must match SSR. After hydration, React re-checks // the snapshot and lets us distinguish same-path cross-origin handler URLs. return useSyncExternalStore( () => () => {}, () => window.location.origin, () => undefined, ); }