From 3032ea76bc0a4165385238a58348bb845f765edf Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:20:53 -0300 Subject: [PATCH] Refactor redirect guards for tasks --- packages/clerk-js/src/core/clerk.ts | 30 ++++++++++++++----- packages/clerk-js/src/core/events.ts | 4 ++- .../clerk-js/src/core/resources/Session.ts | 4 +-- .../clerk-js/src/ui/common/withRedirect.tsx | 18 +++++++---- .../clerk-js/src/ui/components/Task/Task.tsx | 5 ++-- .../src/ui/components/Task/useTaskRoute.tsx | 2 +- .../clerk-js/src/utils/componentGuards.ts | 7 ++++- packages/types/src/clerk.ts | 4 +-- packages/types/src/session.ts | 2 +- 9 files changed, 51 insertions(+), 25 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 1b017113ad9..8a49060fa2e 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -66,6 +66,7 @@ import type { Web3Provider, } from '@clerk/types'; +import { sessionTaskRoutePaths } from '../ui/common/tasks'; import type { MountComponentRenderer } from '../ui/Components'; import { ALLOWED_PROTOCOLS, @@ -89,11 +90,11 @@ import { isError, isOrganizationId, isRedirectForFAPIInitiatedFlow, + isSignedInAndSingleSessionModeEnabled, noOrganizationExists, noUserExists, removeClerkQueryParam, requiresUserInput, - sessionExistsAndSingleSessionModeEnabled, stripOrigin, windowNavigate, } from '../utils'; @@ -427,7 +428,7 @@ export class Clerk implements ClerkInterface { public openSignIn = (props?: SignInProps): void => { this.assertComponentsReady(this.#componentControls); - if (sessionExistsAndSingleSessionModeEnabled(this, this.environment)) { + if (isSignedInAndSingleSessionModeEnabled(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenSignInOrSignUp, { code: 'cannot_render_single_session_enabled', @@ -481,7 +482,7 @@ export class Clerk implements ClerkInterface { public openSignUp = (props?: SignUpProps): void => { this.assertComponentsReady(this.#componentControls); - if (sessionExistsAndSingleSessionModeEnabled(this, this.environment)) { + if (isSignedInAndSingleSessionModeEnabled(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenSignInOrSignUp, { code: 'cannot_render_single_session_enabled', @@ -930,6 +931,11 @@ export class Clerk implements ClerkInterface { eventBus.dispatch(events.TokenUpdate, { token: null }); } + const hasPendingTask = !!newSession?.tasks; + if (hasPendingTask && newSession) { + eventBus.dispatch(events.PendingTask, newSession); + } + //2. If there's a beforeEmit, typically we're navigating. Emit the session as // undefined, then wait for beforeEmit to complete before emitting the new session. // When undefined, neither SignedIn nor SignedOut renders, which avoids flickers or @@ -1114,13 +1120,17 @@ export class Clerk implements ClerkInterface { return buildURL({ base: waitlistUrl, hashSearchParams: [initValues] }, { stringify: true }); } - public buildTasksUrl(options?: { initialValues?: Record }): string { - if (!this.environment) { + public buildTasksUrl(origin: 'SignIn' | 'SignUp'): string { + if (!this.session?.currentTask) { return ''; } - const taskUrl = this.#options['tasksUrl']; - const initValues = new URLSearchParams(options?.initialValues || {}); - return buildURL({ base: taskUrl, hashSearchParams: [initValues] }, { stringify: true }); + + const defaultUrlByOrigin = origin === 'SignIn' ? this.#options.signInUrl : this.#options.signUpUrl; + + return buildURL( + { base: defaultUrlByOrigin, hashPath: sessionTaskRoutePaths[this.session.currentTask.key] }, + { stringify: true }, + ); } public buildAfterMultiSessionSingleSignOutUrl(): string { @@ -2085,6 +2095,10 @@ export class Clerk implements ClerkInterface { eventBus.on(events.UserSignOut, () => { this.#broadcastChannel?.postMessage({ type: 'signout' }); }); + + eventBus.on(events.PendingTask, () => { + console.log('Hello pending task! 🍪'); + }); }; // TODO: Be more conservative about touches. Throttle, don't touch when only one user, etc diff --git a/packages/clerk-js/src/core/events.ts b/packages/clerk-js/src/core/events.ts index 7401dd91370..84ab6ced5d0 100644 --- a/packages/clerk-js/src/core/events.ts +++ b/packages/clerk-js/src/core/events.ts @@ -1,8 +1,9 @@ -import type { TokenResource } from '@clerk/types'; +import type { SignedInSessionResource, TokenResource } from '@clerk/types'; export const events = { TokenUpdate: 'token:update', UserSignOut: 'user:signOut', + PendingTask: 'session:pendingTask', } as const; type ClerkEvent = (typeof events)[keyof typeof events]; @@ -13,6 +14,7 @@ type TokenUpdatePayload = { token: TokenResource | null }; type EventPayload = { [events.TokenUpdate]: TokenUpdatePayload; [events.UserSignOut]: null; + [events.PendingTask]: SignedInSessionResource; }; const createEventBus = () => { diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 67b4d6c52e5..4ac8f4ad4a6 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -304,7 +304,7 @@ export class Session extends BaseResource implements SessionResource { }); } - get hasTask() { - return (this.tasks ?? []).length > 0; + get currentTask(): SessionTask | undefined { + return (this.tasks ?? [])[0]; } } diff --git a/packages/clerk-js/src/ui/common/withRedirect.tsx b/packages/clerk-js/src/ui/common/withRedirect.tsx index 43ec9b95172..801a1c84fa3 100644 --- a/packages/clerk-js/src/ui/common/withRedirect.tsx +++ b/packages/clerk-js/src/ui/common/withRedirect.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { warnings } from '../../core/warnings'; import type { ComponentGuard } from '../../utils'; -import { sessionExistsAndSingleSessionModeEnabled } from '../../utils'; +import { isSignedInAndSingleSessionModeEnabled } from '../../utils'; import { useEnvironment, useOptions, useSignInContext, useSignUpContext } from '../contexts'; import { useRouter } from '../router'; import type { AvailableComponentProps } from '../types'; @@ -60,8 +60,11 @@ export const withRedirectToAfterSignIn =

(Com const signInCtx = useSignInContext(); return withRedirect( Component, - sessionExistsAndSingleSessionModeEnabled, - ({ clerk }) => signInCtx.afterSignInUrl || clerk.buildAfterSignInUrl(), + isSignedInAndSingleSessionModeEnabled, + ({ clerk }) => + clerk.session?.currentTask + ? clerk.buildTasksUrl('SignIn') + : signInCtx.afterSignInUrl || clerk.buildAfterSignInUrl(), warnings.cannotRenderSignInComponentWhenSessionExists, )(props); }; @@ -79,8 +82,11 @@ export const withRedirectToAfterSignUp =

(Com const signUpCtx = useSignUpContext(); return withRedirect( Component, - sessionExistsAndSingleSessionModeEnabled, - ({ clerk }) => signUpCtx.afterSignUpUrl || clerk.buildAfterSignUpUrl(), + isSignedInAndSingleSessionModeEnabled, + ({ clerk }) => + clerk.session?.currentTask + ? clerk.buildTasksUrl('SignUp') + : signUpCtx.afterSignInUrl || clerk.buildAfterSignInUrl(), warnings.cannotRenderSignUpComponentWhenSessionExists, )(props); }; @@ -93,7 +99,7 @@ export const withRedirectToAfterSignUp =

(Com export const withRedirectToHomeSingleSessionGuard =

(Component: ComponentType

) => withRedirect( Component, - sessionExistsAndSingleSessionModeEnabled, + isSignedInAndSingleSessionModeEnabled, ({ environment }) => environment.displayConfig.homeUrl, warnings.cannotRenderComponentWhenSessionExists, ); diff --git a/packages/clerk-js/src/ui/components/Task/Task.tsx b/packages/clerk-js/src/ui/components/Task/Task.tsx index b4f027807fa..97c9ae25b2d 100644 --- a/packages/clerk-js/src/ui/components/Task/Task.tsx +++ b/packages/clerk-js/src/ui/components/Task/Task.tsx @@ -1,6 +1,6 @@ import { useSessionContext } from '@clerk/shared/react/index'; import type { SessionTask } from '@clerk/types'; -import type { ComponentType } from 'react'; +import { type ComponentType } from 'react'; import { OrganizationListContext } from '../../contexts'; import { OrganizationList } from '../OrganizationList'; @@ -19,7 +19,8 @@ const TaskRegistry: Record = { export function Task(): React.ReactNode { const session = useSessionContext(); - if (!session?.hasTask) { + // todo -> redirect to after sign in / after sign up + if (!session?.currentTask) { return null; } diff --git a/packages/clerk-js/src/ui/components/Task/useTaskRoute.tsx b/packages/clerk-js/src/ui/components/Task/useTaskRoute.tsx index 2153b65138f..d15cb8eb993 100644 --- a/packages/clerk-js/src/ui/components/Task/useTaskRoute.tsx +++ b/packages/clerk-js/src/ui/components/Task/useTaskRoute.tsx @@ -12,7 +12,7 @@ import { Task } from './Task'; export function useTaskRoute(): ComponentProps | null { const session = useSessionContext(); - if (!session?.hasTask) { + if (!session?.currentTask) { return null; } diff --git a/packages/clerk-js/src/utils/componentGuards.ts b/packages/clerk-js/src/utils/componentGuards.ts index 2552f347c48..cfc68da013e 100644 --- a/packages/clerk-js/src/utils/componentGuards.ts +++ b/packages/clerk-js/src/utils/componentGuards.ts @@ -6,7 +6,12 @@ export type ComponentGuard = ( options?: ClerkOptions, ) => boolean; -export const sessionExistsAndSingleSessionModeEnabled: ComponentGuard = (clerk, environment) => { +// todo -> add tests +// is signed in, and single session mode enabled, and active session -> redirect to after sign in +// is signed in, and single session mode, and pending session => redirect to task +// is signed in, and multi session mode, and active session -> let it be +// is signed in, and multi session mode, and pending session -> let it be +export const isSignedInAndSingleSessionModeEnabled: ComponentGuard = (clerk, environment) => { return !!(clerk.isSignedIn && environment?.authConfig.singleSessionMode); }; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 60f5a9f8e5d..f7101f02ce1 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -479,7 +479,7 @@ export interface Clerk { /** * Returns the url where a custom task page is rendered. */ - buildTasksUrl(opts?: { initialValues?: Record }): string; + buildTasksUrl(origin: 'SignIn' | 'SignUp'): string; /** * @@ -770,8 +770,6 @@ export type ClerkOptions = ClerkOptionsNavigation & sdkMetadata?: SDKMetadata; /** This URL will be used for any redirects that might happen and needs to point to your primary application on the client-side. This option is optional for production instances and required for development instances. */ waitlistUrl?: string; - /** This URL will be used for any redirects that might happen and needs to point to your primary application on the client-side. This option is optional and defaults to `signInUrl` */ - tasksUrl?: string; /** * Enable experimental flags to gain access to new features. These flags are not guaranteed to be stable and may change drastically in between patch or minor versions. */ diff --git a/packages/types/src/session.ts b/packages/types/src/session.ts index 87ed3571a14..ac809ce0d7c 100644 --- a/packages/types/src/session.ts +++ b/packages/types/src/session.ts @@ -104,7 +104,7 @@ export interface SessionResource extends ClerkResource { lastActiveAt: Date; actor: ActJWTClaim | null; tasks: Array | null; - hasTask: boolean; + currentTask?: SessionTask; user: UserResource | null; publicUserData: PublicUserData; end: () => Promise;