117 lines · 4.3 KB · TypeScript
RawDownload
import {  buildConnectClerkAuthorizeUrl,  connectCallbackUrl,  connectLoopbackRedirectUri,  CONNECT_OAUTH_SCOPES,  type ConnectAuthorizeRequest,} from "@t3tools/shared/connectAuth";import { clerkFrontendApiUrlFromPublishableKey } from "@t3tools/shared/relayAuth"; import { configuredHostedAppUrl, isHostedStaticApp } from "../hostedPairing";import { hasCloudPublicConfig, resolveCloudPublicConfig, trimNonEmpty } from "./publicConfig"; const CONNECT_CLI_AUTH_STATE_STORAGE_KEY = "t3code-connect-cli-auth-state"; function resolveConnectCliOAuthClientId(): string | null {  return trimNonEmpty(import.meta.env.VITE_CLERK_CLI_OAUTH_CLIENT_ID as string | undefined);} export function hasConnectCliAuthConfig(): boolean {  return Boolean(    resolveCloudPublicConfig().clerkPublishableKey && resolveConnectCliOAuthClientId(),  );} /** * Gate for the /connect routes: the CLI handshake only exists on the hosted * deployment (the same bundle ships inside local instances) and needs the * Clerk CLI OAuth client configured at build time. */export function connectCliAuthRoutesEnabled(): boolean {  return isHostedStaticApp() && hasCloudPublicConfig() && hasConnectCliAuthConfig();} /** * Builds the Clerk authorize URL for a CLI-initiated connect request. The * state is mirrored into sessionStorage so the callback page can verify the * response matches a request this browser actually started. * * A request carrying a loopback port came from a CLI with a local callback * listener: the authorization code must return to `127.0.0.1` directly, so * the hosted callback page never sees it. Clerk enforces its registered * redirect URI allowlist either way. */export function buildConnectCliClerkAuthorizeUrl(request: ConnectAuthorizeRequest): string | null {  const { clerkPublishableKey } = resolveCloudPublicConfig();  const clientId = resolveConnectCliOAuthClientId();  if (!clerkPublishableKey || !clientId) {    return null;  }  return buildConnectClerkAuthorizeUrl({    authorizationEndpoint: `${clerkFrontendApiUrlFromPublishableKey(clerkPublishableKey)}/oauth/authorize`,    clientId,    redirectUri:      request.loopbackPort === undefined        ? connectCallbackUrl(configuredHostedAppUrl())        : connectLoopbackRedirectUri(request.loopbackPort),    scopes: CONNECT_OAUTH_SCOPES,    state: request.state,    challenge: request.challenge,  });} /** * Where Clerk sends the browser once the sign-in modal on /connect completes. * It has to be the authorize endpoint rather than this page: /connect carries * the CLI request in its fragment, so navigating back to the same URL is a * same-document fragment navigation the browser never reloads — and Clerk * treats any post-sign-in navigation as a page unload and skips the state emit * that would otherwise re-render the surface, so the session never arrives * either. Falls back to the current URL when the authorize URL cannot be * built, which only happens on a deployment without the CLI OAuth config. */export function connectCliSignInRedirectUrl(  request: ConnectAuthorizeRequest,  currentHref: string,): string {  return buildConnectCliClerkAuthorizeUrl(request) ?? currentHref;} export function rememberConnectCliAuthState(state: string): void {  try {    window.sessionStorage.setItem(CONNECT_CLI_AUTH_STATE_STORAGE_KEY, state);  } catch {    // Session storage can be unavailable (e.g. blocked). The callback page    // then falls back to trusting the state Clerk echoed back.  }} /** * Read-only on purpose: this runs during render, where a removal would be * consumed by React's double-invoked/discarded renders (StrictMode) and * silently disable the state check. The value is not a secret and is * overwritten by the next /connect visit. */export function readConnectCliAuthState(): string | null {  try {    return window.sessionStorage.getItem(CONNECT_CLI_AUTH_STATE_STORAGE_KEY);  } catch {    return null;  }} export interface ConnectCliCallbackResult {  readonly code: string;  readonly state: string;} export function readConnectCliCallbackResult(  url: URL = new URL(window.location.href),): ConnectCliCallbackResult | null {  const code = url.searchParams.get("code")?.trim() ?? "";  const state = url.searchParams.get("state")?.trim() ?? "";  if (!code || !state) {    return null;  }  return { code, state };}