Skip to content

Commit

Permalink
Refactor redirect guards for tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
LauraBeatris committed Feb 20, 2025
1 parent e6c10fa commit 3032ea7
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 25 deletions.
30 changes: 22 additions & 8 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -89,11 +90,11 @@ import {
isError,
isOrganizationId,
isRedirectForFAPIInitiatedFlow,
isSignedInAndSingleSessionModeEnabled,
noOrganizationExists,
noUserExists,
removeClerkQueryParam,
requiresUserInput,
sessionExistsAndSingleSessionModeEnabled,
stripOrigin,
windowNavigate,
} from '../utils';
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1114,13 +1120,17 @@ export class Clerk implements ClerkInterface {
return buildURL({ base: waitlistUrl, hashSearchParams: [initValues] }, { stringify: true });
}

public buildTasksUrl(options?: { initialValues?: Record<string, string> }): 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 {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion packages/clerk-js/src/core/events.ts
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -13,6 +14,7 @@ type TokenUpdatePayload = { token: TokenResource | null };
type EventPayload = {
[events.TokenUpdate]: TokenUpdatePayload;
[events.UserSignOut]: null;
[events.PendingTask]: SignedInSessionResource;
};

const createEventBus = () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/clerk-js/src/core/resources/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
}
18 changes: 12 additions & 6 deletions packages/clerk-js/src/ui/common/withRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -60,8 +60,11 @@ export const withRedirectToAfterSignIn = <P extends AvailableComponentProps>(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);
};
Expand All @@ -79,8 +82,11 @@ export const withRedirectToAfterSignUp = <P extends AvailableComponentProps>(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);
};
Expand All @@ -93,7 +99,7 @@ export const withRedirectToAfterSignUp = <P extends AvailableComponentProps>(Com
export const withRedirectToHomeSingleSessionGuard = <P extends AvailableComponentProps>(Component: ComponentType<P>) =>
withRedirect(
Component,
sessionExistsAndSingleSessionModeEnabled,
isSignedInAndSingleSessionModeEnabled,
({ environment }) => environment.displayConfig.homeUrl,
warnings.cannotRenderComponentWhenSessionExists,
);
5 changes: 3 additions & 2 deletions packages/clerk-js/src/ui/components/Task/Task.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -19,7 +19,8 @@ const TaskRegistry: Record<SessionTask['key'], ComponentType> = {
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;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/ui/components/Task/useTaskRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Task } from './Task';
export function useTaskRoute(): ComponentProps<typeof Route> | null {
const session = useSessionContext();

if (!session?.hasTask) {
if (!session?.currentTask) {
return null;
}

Expand Down
7 changes: 6 additions & 1 deletion packages/clerk-js/src/utils/componentGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down
4 changes: 1 addition & 3 deletions packages/types/src/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ export interface Clerk {
/**
* Returns the url where a custom task page is rendered.
*/
buildTasksUrl(opts?: { initialValues?: Record<string, string> }): string;
buildTasksUrl(origin: 'SignIn' | 'SignUp'): string;

/**
*
Expand Down Expand Up @@ -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.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export interface SessionResource extends ClerkResource {
lastActiveAt: Date;
actor: ActJWTClaim | null;
tasks: Array<SessionTask> | null;
hasTask: boolean;
currentTask?: SessionTask;
user: UserResource | null;
publicUserData: PublicUserData;
end: () => Promise<SessionResource>;
Expand Down

0 comments on commit 3032ea7

Please sign in to comment.