Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(clerk-js,types): Load tasks based on environment settings #5422

Merged
merged 7 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/lazy-impalas-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/types': patch
---

Load tasks based on environment settings
2 changes: 0 additions & 2 deletions integration/templates/next-app-router/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
persistClient: process.env.NEXT_PUBLIC_EXPERIMENTAL_PERSIST_CLIENT
? process.env.NEXT_PUBLIC_EXPERIMENTAL_PERSIST_CLIENT === 'true'
: undefined,
// `experimental.withSessionTasks` will be removed soon in favor of checking via environment response
withSessionTasks: true,
}}
>
<html lang='en'>
Expand Down
7 changes: 4 additions & 3 deletions packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"files": [
{ "path": "./dist/clerk.js", "maxSize": "580.5kB" },
{ "path": "./dist/clerk.js", "maxSize": "580.7kB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "79.25kB" },
{ "path": "./dist/clerk.headless.js", "maxSize": "55KB" },
{ "path": "./dist/ui-common*.js", "maxSize": "94KB" },
{ "path": "./dist/ui-common*.js", "maxSize": "96KB" },
{ "path": "./dist/vendors*.js", "maxSize": "30KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "35.5KB" },
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
Expand All @@ -21,6 +21,7 @@
{ "path": "./dist/keylessPrompt*.js", "maxSize": "5.9KB" },
{ "path": "./dist/pricingTable*.js", "maxSize": "5KB" },
{ "path": "./dist/checkout*.js", "maxSize": "9KB" },
{ "path": "./dist/up-billing-page*.js", "maxSize": "1KB" }
{ "path": "./dist/up-billing-page*.js", "maxSize": "1KB" },
{ "path": "./dist/sessionTasks*.js", "maxSize": "1KB" }
]
}
8 changes: 4 additions & 4 deletions packages/clerk-js/src/core/__tests__/clerk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,14 +486,14 @@ describe('Clerk singleton', () => {
getToken: jest.fn(),
lastActiveToken: { getRawString: () => 'mocked-token' },
tasks: [{ key: 'org' }],
currentTask: { key: 'org', __internal_getUrl: () => 'https://foocorp.com/add-organization' },
currentTask: { key: 'org', __internal_getUrl: () => 'https://sut/tasks/add-organization' },
reload: jest.fn(() =>
Promise.resolve({
id: '1',
status: 'pending',
user: {},
tasks: [{ key: 'org' }],
currentTask: { key: 'org', __internal_getUrl: () => 'https://foocorp.com/add-organization' },
currentTask: { key: 'org', __internal_getUrl: () => 'https://sut/tasks/add-organization' },
}),
),
};
Expand Down Expand Up @@ -2287,7 +2287,7 @@ describe('Clerk singleton', () => {
status: 'pending',
user: {},
tasks: [{ key: 'org' }],
currentTask: { key: 'org', __internal_getUrl: () => 'https://foocorp.com/add-organization' },
currentTask: { key: 'org', __internal_getUrl: () => 'https://sut/tasks/add-organization' },
lastActiveToken: { getRawString: () => 'mocked-token' },
};

Expand Down Expand Up @@ -2316,7 +2316,7 @@ describe('Clerk singleton', () => {
await sut.setActive({ session: mockResource as any as PendingSessionResource });
await sut.__experimental_nextTask();

expect(mockNavigate.mock.calls[0][0]).toBe('/sign-in#/add-organization');
expect(mockNavigate.mock.calls[0][0]).toBe('/sign-in#/tasks/add-organization');
});
});

Expand Down
4 changes: 2 additions & 2 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,7 @@ export class Clerk implements ClerkInterface {
}

if (newSession?.currentTask) {
await navigateToTask(session.currentTask, {
await navigateToTask(session.currentTask.key, {
globalNavigate: this.navigate,
componentNavigationContext: this.#componentNavigationContext,
options: this.#options,
Expand All @@ -1109,7 +1109,7 @@ export class Clerk implements ClerkInterface {
}

if (session.status === 'pending') {
await navigateToTask(session.currentTask, {
await navigateToTask(session.currentTask.key, {
options: this.#options,
environment: this.environment,
globalNavigate: this.navigate,
Expand Down
4 changes: 2 additions & 2 deletions packages/clerk-js/src/core/sessionTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ interface NavigateToTaskOptions {
* @internal
*/
export function navigateToTask(
task: SessionTask,
routeKey: keyof typeof SESSION_TASK_ROUTE_BY_KEY,
{ componentNavigationContext, globalNavigate, options, environment }: NavigateToTaskOptions,
) {
const taskRoute = `/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`;
const taskRoute = `/tasks/${SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;

if (componentNavigationContext) {
return componentNavigationContext.navigate(componentNavigationContext.indexPath + taskRoute);
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/ui/common/redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function buildSessionTaskRedirectUrl({
routing,
baseUrl,
path,
endpoint: `/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
endpoint: `/tasks/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
authQueryString: null,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useOrganizationList, useUser } from '@clerk/shared/react';
import { useContext, useState } from 'react';

import { useEnvironment, useOrganizationListContext } from '../../contexts';
import { SessionTaskContext } from '../../contexts/components/SessionTask';
import { SessionTasksContext } from '../../contexts/components/SessionTasks';
import { Box, Col, descriptors, Flex, localizationKeys, Spinner } from '../../customizables';
import { Action, Actions, Card, Header, useCardState, withCardStateProvider } from '../../elements';
import { useInView } from '../../hooks';
Expand Down Expand Up @@ -112,7 +112,7 @@ export const OrganizationListPage = withCardStateProvider(() => {
const OrganizationListFlows = ({ showListInitially }: { showListInitially: boolean }) => {
const { navigateAfterCreateOrganization, skipInvitationScreen, hideSlug } = useOrganizationListContext();
const [isCreateOrganizationFlow, setCreateOrganizationFlow] = useState(!showListInitially);
const sessionTaskContext = useContext(SessionTaskContext);
const sessionTasksContext = useContext(SessionTasksContext);
return (
<>
{!isCreateOrganizationFlow && (
Expand All @@ -127,7 +127,7 @@ const OrganizationListFlows = ({ showListInitially }: { showListInitially: boole
>
<CreateOrganizationForm
flow='organizationList'
onComplete={sessionTaskContext?.nextTask}
onComplete={sessionTasksContext?.nextTask}
startPage={{ headerTitle: localizationKeys('organizationList.createOrganization') }}
skipInvitationScreen={skipInvitationScreen}
navigateAfterCreateOrganization={org =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { OrganizationResource } from '@clerk/types';
import { useContext } from 'react';

import { useOrganizationListContext } from '../../contexts';
import { SessionTaskContext } from '../../contexts/components/SessionTask';
import { SessionTasksContext } from '../../contexts/components/SessionTasks';
import { OrganizationPreview, PersonalWorkspacePreview, useCardState, withCardStateProvider } from '../../elements';
import { localizationKeys } from '../../localization';
import { OrganizationListPreviewButton, sharedMainIdentifierSx } from './shared';
Expand All @@ -12,7 +12,7 @@ export const MembershipPreview = withCardStateProvider((props: { organization: O
const card = useCardState();
const { navigateAfterSelectOrganization } = useOrganizationListContext();
const { isLoaded, setActive } = useOrganizationList();
const sessionTaskContext = useContext(SessionTaskContext);
const sessionTasksContext = useContext(SessionTasksContext);

if (!isLoaded) {
return null;
Expand All @@ -23,8 +23,8 @@ export const MembershipPreview = withCardStateProvider((props: { organization: O
organization,
});

if (sessionTaskContext?.nextTask) {
return sessionTaskContext?.nextTask();
if (sessionTasksContext?.nextTask) {
return sessionTasksContext?.nextTask();
}

await navigateAfterSelectOrganization(organization);
Expand Down
58 changes: 0 additions & 58 deletions packages/clerk-js/src/ui/components/SessionTask/SessionTask.tsx

This file was deleted.

1 change: 0 additions & 1 deletion packages/clerk-js/src/ui/components/SessionTask/index.ts

This file was deleted.

92 changes: 92 additions & 0 deletions packages/clerk-js/src/ui/components/SessionTasks/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useClerk } from '@clerk/shared/react';
import { eventComponentMounted } from '@clerk/shared/telemetry';
import type { SessionTask } from '@clerk/types';
import { useCallback, useContext, useEffect } from 'react';

import { SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
import { OrganizationListContext, SignInContext, SignUpContext } from '../../../ui/contexts';
import { Card, LoadingCardContainer, withCardStateProvider } from '../../../ui/elements';
import {
SessionTasksContext as SessionTasksContext,
useSessionTasksContext,
} from '../../contexts/components/SessionTasks';
import { Route, Switch, useRouter } from '../../router';
import { OrganizationList } from '../OrganizationList';

const SessionTasksStart = withCardStateProvider(() => {
const clerk = useClerk();
const { navigate } = useRouter();
const { redirectUrlComplete } = useSessionTasksContext();

useEffect(() => {
// Simulates additional latency to avoid a abrupt UI transition when navigating to the next task
const timeoutId = setTimeout(() => {
void clerk.__experimental_nextTask({ redirectUrlComplete });
}, 500);
return () => clearTimeout(timeoutId);
}, [navigate, clerk, redirectUrlComplete]);

return (
<Card.Root>
<Card.Content>
<LoadingCardContainer />
</Card.Content>
<Card.Footer />
</Card.Root>
);
});

function SessionTaskRoutes(): JSX.Element {
return (
<Switch>
<Route path={SESSION_TASK_ROUTE_BY_KEY['org']}>
<OrganizationListContext.Provider
value={{
componentName: 'OrganizationList',
skipInvitationScreen: true,
}}
>
<OrganizationList />
</OrganizationListContext.Provider>
</Route>
<Route index>
<SessionTasksStart />
</Route>
</Switch>
);
}

/**
* @internal
*/
export function SessionTask(): JSX.Element {
const clerk = useClerk();
const { navigate } = useRouter();
const signInContext = useContext(SignInContext);
const signUpContext = useContext(SignUpContext);

const redirectUrlComplete =
signInContext?.afterSignInUrl ?? signUpContext?.afterSignUpUrl ?? clerk?.buildAfterSignInUrl();

useEffect(() => {
const task = clerk.session?.currentTask;

if (!task) {
void navigate(redirectUrlComplete);
return;
}

clerk.telemetry?.record(eventComponentMounted('SessionTask', { task: task.key }));
}, [clerk, navigate, redirectUrlComplete]);

const nextTask = useCallback(
() => clerk.__experimental_nextTask({ redirectUrlComplete }),
[clerk, redirectUrlComplete],
);

return (
<SessionTasksContext.Provider value={{ nextTask, redirectUrlComplete }}>
<SessionTaskRoutes />
</SessionTasksContext.Provider>
);
}
32 changes: 9 additions & 23 deletions packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useClerk } from '@clerk/shared/react';
import type { SignInModalProps, SignInProps } from '@clerk/types';
import React from 'react';

import { SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
import { SessionTasks as LazySessionTasks } from '../../../ui/lazyModules/components';
import { normalizeRoutingOptions } from '../../../utils/normalizeRoutingOptions';
import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
import type { SignUpContextType } from '../../contexts';
Expand All @@ -15,7 +15,7 @@ import {
} from '../../contexts';
import { Flow } from '../../customizables';
import { useFetch } from '../../hooks';
import { preloadSessionTask, SessionTask } from '../../lazyModules/components';
import { usePreloadTasks } from '../../hooks/usePreloadTasks';
import { Route, Switch, useRouter, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
import {
LazySignUpContinue,
Expand Down Expand Up @@ -130,14 +130,9 @@ function SignInRoutes(): JSX.Element {
>
<LazySignUpVerifyPhone />
</Route>
{signInContext.withSessionTasks && (
<Route path={SESSION_TASK_ROUTE_BY_KEY['org']}>
<SessionTask
task='org'
redirectUrlComplete={signInContext.afterSignUpUrl}
/>
</Route>
)}
<Route path='tasks'>
<LazySessionTasks />
</Route>
<Route index>
<LazySignUpContinue />
</Route>
Expand All @@ -147,14 +142,9 @@ function SignInRoutes(): JSX.Element {
</Route>
</Route>
)}
{signInContext.withSessionTasks && (
<Route path={SESSION_TASK_ROUTE_BY_KEY['org']}>
<SessionTask
task='org'
redirectUrlComplete={signInContext.afterSignInUrl}
/>
</Route>
)}
<Route path='tasks'>
<LazySessionTasks />
</Route>
<Route index>
<SignInStart />
</Route>
Expand All @@ -169,9 +159,6 @@ function SignInRoutes(): JSX.Element {
const usePreloadSignUp = (enabled = false) =>
useFetch(enabled ? preloadSignUp : undefined, 'preloadComponent', { staleTime: Infinity });

const usePreloadSessionTask = (enabled = false) =>
useFetch(enabled ? preloadSessionTask : undefined, 'preloadComponent', { staleTime: Infinity });

function SignInRoot() {
const { __internal_setComponentNavigationContext } = useClerk();
const { navigate, indexPath } = useRouter();
Expand All @@ -193,8 +180,7 @@ function SignInRoot() {
*/
usePreloadSignUp(signInContext.isCombinedFlow);

// `experimental.withSessionTasks` will be removed soon in favor of checking via environment response
usePreloadSessionTask(signInContext.withSessionTasks);
usePreloadTasks();

React.useEffect(() => {
return __internal_setComponentNavigationContext?.({ indexPath, navigate });
Expand Down
Loading