Skip to content

Commit 83ae82c

Browse files
committed
Abstract route paths resolution
Introduces a resourse class for tasks in order to resolve paths strings, avoiding placing that logic in the main `Clerk` interface which is more developer facing All methods are appended with `__internal` as it's not intented for public usage even by custom flows
1 parent fafd3b7 commit 83ae82c

File tree

11 files changed

+97
-43
lines changed

11 files changed

+97
-43
lines changed

packages/clerk-js/src/core/clerk.ts

+1-21
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ export class Clerk implements ClerkInterface {
934934
eventBus.dispatch(events.InternalComponentNavigate, resolveNavigation),
935935
);
936936
} else {
937-
await this.navigate(this.internal__buildSessionTaskUrl());
937+
await this.navigate(newSession?.__internal_buildSessionTaskUrl(this.#options, this.environment));
938938
}
939939
}
940940

@@ -949,9 +949,7 @@ export class Clerk implements ClerkInterface {
949949
'Use the `redirectUrl` property instead. Example `Clerk.setActive({redirectUrl:"/"})`',
950950
);
951951
beforeUnloadTracker?.startTracking();
952-
953952
this.#setTransitiveState();
954-
955953
await beforeEmit(newSession);
956954
beforeUnloadTracker?.stopTracking();
957955
}
@@ -1157,24 +1155,6 @@ export class Clerk implements ClerkInterface {
11571155
return this.buildUrlWithAuth(this.environment.displayConfig.organizationProfileUrl);
11581156
}
11591157

1160-
// pass this to the session object as internal
1161-
public internal__buildSessionTaskUrl(): string {
1162-
const [currentTask] = this.session?.tasks ?? [];
1163-
1164-
if (!currentTask || !this.environment || !this.environment.displayConfig) {
1165-
return '';
1166-
}
1167-
1168-
const signInUrl = this.#options['signInUrl'] || this.environment.displayConfig.signInUrl;
1169-
const signUpUrl = this.#options['signUpUrl'] || this.environment.displayConfig.signUpUrl;
1170-
const isReferrerSignUpUrl = window.location.href.startsWith(signUpUrl);
1171-
1172-
return buildURL(
1173-
{ base: isReferrerSignUpUrl ? signUpUrl : signInUrl, hashPath: '/add-organization' },
1174-
{ stringify: true },
1175-
);
1176-
}
1177-
11781158
#redirectToSatellite = async (): Promise<unknown> => {
11791159
if (!inBrowser()) {
11801160
return;

packages/clerk-js/src/core/constants.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { SignUpModes } from '@clerk/types';
1+
import type { SessionTaskKey, SignUpModes } from '@clerk/types';
22

33
// TODO: Do we still have a use for this or can we simply preserve all params?
44
export const PRESERVED_QUERYSTRING_PARAMS = [
@@ -48,5 +48,10 @@ export const SIGN_UP_MODES: Record<string, SignUpModes> = {
4848
WAITLIST: 'waitlist',
4949
};
5050

51+
export const SESSION_TASK_ROUTE_PATH_BY_KEY: Record<SessionTaskKey, string> = {
52+
org: 'add-organization',
53+
};
54+
export const SESSION_TASK_ROUTE_PATHS = Object.values(SESSION_TASK_ROUTE_PATH_BY_KEY);
55+
5156
// This is the currently supported version of the Frontend API
5257
export const SUPPORTED_FAPI_VERSION = '2024-10-01';

packages/clerk-js/src/core/resources/Session.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ import { retry } from '@clerk/shared/retry';
44
import type {
55
ActJWTClaim,
66
CheckAuthorization,
7+
ClerkOptions,
78
EmailCodeConfig,
9+
EnvironmentResource,
810
GetToken,
911
GetTokenOptions,
1012
PhoneCodeConfig,
1113
SessionJSON,
1214
SessionJSONSnapshot,
1315
SessionResource,
1416
SessionStatus,
15-
SessionTask,
1617
SessionVerificationJSON,
1718
SessionVerificationResource,
1819
SessionVerifyAttemptFirstFactorParams,
@@ -24,11 +25,13 @@ import type {
2425
UserResource,
2526
} from '@clerk/types';
2627

28+
import { buildURL, inBrowser } from '../../utils';
2729
import { unixEpochToDate } from '../../utils/date';
2830
import { clerkInvalidStrategy } from '../errors';
2931
import { eventBus, events } from '../events';
3032
import { SessionTokenCache } from '../tokenCache';
3133
import { BaseResource, PublicUserData, Token, User } from './internal';
34+
import { SessionTask } from './SessionTask';
3235
import { SessionVerification } from './SessionVerification';
3336

3437
export class Session extends BaseResource implements SessionResource {
@@ -226,7 +229,7 @@ export class Session extends BaseResource implements SessionResource {
226229
this.createdAt = unixEpochToDate(data.created_at);
227230
this.updatedAt = unixEpochToDate(data.updated_at);
228231
this.user = new User(data.user);
229-
this.tasks = data.tasks;
232+
this.tasks = data.tasks?.map(task => new SessionTask(task)) ?? [];
230233

231234
if (data.public_user_data) {
232235
this.publicUserData = new PublicUserData(data.public_user_data);
@@ -303,4 +306,21 @@ export class Session extends BaseResource implements SessionResource {
303306
return token.getRawString() || null;
304307
});
305308
}
309+
310+
public __internal_buildSessionTaskUrl(options: ClerkOptions, environment?: EnvironmentResource | null): string {
311+
const [currentTask] = this.tasks ?? [];
312+
313+
if (!currentTask || !environment || !inBrowser()) {
314+
return '';
315+
}
316+
317+
const signInUrl = options['signInUrl'] || environment.displayConfig.signInUrl;
318+
const signUpUrl = options['signUpUrl'] || environment.displayConfig.signUpUrl;
319+
const isReferrerSignUpUrl = window.location.href.startsWith(signUpUrl);
320+
321+
return buildURL(
322+
{ base: isReferrerSignUpUrl ? signUpUrl : signInUrl, hashPath: currentTask.__internal_getNavigationPath() },
323+
{ stringify: true },
324+
);
325+
}
306326
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { SessionTaskJSON, SessionTaskJSONSnapshot, SessionTaskKey, SessionTaskResource } from '@clerk/types';
2+
3+
export const SESSION_TASK_PATHS = ['add-organization'] as const;
4+
type SessionTaskPath = (typeof SESSION_TASK_PATHS)[number];
5+
6+
export const SESSION_TASK_PATH_BY_KEY: Record<SessionTaskKey, SessionTaskPath> = {
7+
org: 'add-organization',
8+
} as const;
9+
10+
export class SessionTask implements SessionTaskResource {
11+
key!: SessionTaskKey;
12+
13+
constructor(data: SessionTaskJSON | SessionTaskJSONSnapshot) {
14+
this.fromJSON(data);
15+
}
16+
17+
protected fromJSON(data: SessionTaskJSON | SessionTaskJSONSnapshot): this {
18+
if (!data) {
19+
return this;
20+
}
21+
22+
this.key = data.key;
23+
24+
return this;
25+
}
26+
27+
public __internal_toSnapshot(): SessionTaskJSONSnapshot {
28+
return {
29+
key: this.key,
30+
};
31+
}
32+
33+
public __internal_getNavigationPath(): `/${SessionTaskPath}` {
34+
return `/${SESSION_TASK_PATH_BY_KEY[this.key]}`;
35+
}
36+
}

packages/clerk-js/src/ui/common/withRedirect.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ export function withRedirect<P extends AvailableComponentProps>(
3030

3131
const hasTaskAndSingleSessionMode = !!clerk.session?.tasks && environment?.authConfig.singleSessionMode;
3232
const shouldRedirect = hasTaskAndSingleSessionMode ? false : condition(clerk, environment, options);
33-
const redirectUrlWithDefault = hasTaskAndSingleSessionMode
34-
? () => clerk.internal__buildSessionTaskUrl()
35-
: redirectUrl;
3633

3734
React.useEffect(() => {
3835
if (shouldRedirect) {
@@ -41,7 +38,7 @@ export function withRedirect<P extends AvailableComponentProps>(
4138
}
4239
// TODO: Fix this properly
4340
// eslint-disable-next-line @typescript-eslint/no-floating-promises
44-
navigate(redirectUrlWithDefault({ clerk, environment, options }));
41+
navigate(redirectUrl({ clerk, environment, options }));
4542
}
4643
}, []);
4744

packages/clerk-js/src/ui/components/SessionTask/SessionTask.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useSessionContext } from '@clerk/shared/react/index';
2-
import type { SessionTask } from '@clerk/types';
2+
import type { SessionTaskKey } from '@clerk/types';
33
import { type ComponentType } from 'react';
44

55
import { OrganizationListContext } from '../../contexts';
@@ -8,7 +8,7 @@ import { OrganizationList } from '../OrganizationList';
88
/**
99
* @internal
1010
*/
11-
const SessionTaskRegistry: Record<SessionTask['key'], ComponentType> = {
11+
const SessionTaskRegistry: Record<SessionTaskKey, ComponentType> = {
1212
org: () => (
1313
// TODO - Hide personal workspace within organization list context based on environment
1414
<OrganizationListContext.Provider value={{ componentName: 'OrganizationList', hidePersonal: true }}>
@@ -20,7 +20,7 @@ const SessionTaskRegistry: Record<SessionTask['key'], ComponentType> = {
2020
/**
2121
* @internal
2222
*/
23-
export function Task(): React.ReactNode {
23+
export function SessionTask(): React.ReactNode {
2424
const session = useSessionContext();
2525
const [currentTask] = session?.tasks ?? [];
2626

packages/clerk-js/src/ui/components/SignIn/SignIn.tsx

+10-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useClerk } from '@clerk/shared/react';
22
import type { SignInModalProps, SignInProps } from '@clerk/types';
33
import React from 'react';
44

5+
import { SESSION_TASK_ROUTE_PATHS } from '../../../core/constants';
56
import { normalizeRoutingOptions } from '../../../utils/normalizeRoutingOptions';
67
import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
78
import type { SignUpContextType } from '../../contexts';
@@ -14,7 +15,7 @@ import {
1415
} from '../../contexts';
1516
import { Flow } from '../../customizables';
1617
import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
17-
import { Task } from '../SessionTask/SessionTask';
18+
import { SessionTask } from '../SessionTask/SessionTask';
1819
import { SignUpContinue } from '../SignUp/SignUpContinue';
1920
import { SignUpSSOCallback } from '../SignUp/SignUpSSOCallback';
2021
import { SignUpStart } from '../SignUp/SignUpStart';
@@ -77,10 +78,14 @@ function SignInRoutes(): JSX.Element {
7778
redirectUrl='../factor-two'
7879
/>
7980
</Route>
80-
{/* Make it type safe based on the possible route paths / introduce abstraction */}
81-
<Route path='add-organization'>
82-
<Task />
83-
</Route>
81+
{SESSION_TASK_ROUTE_PATHS.map(path => (
82+
<Route
83+
path={path}
84+
key={path}
85+
>
86+
<SessionTask />
87+
</Route>
88+
))}
8489
{signInContext.isCombinedFlow && (
8590
<Route path='create'>
8691
<Route

packages/types/src/clerk.ts

-2
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,6 @@ export interface Clerk {
476476
*/
477477
buildWaitlistUrl(opts?: { initialValues?: Record<string, string> }): string;
478478

479-
internal__buildSessionTaskUrl(): string;
480-
481479
/**
482480
*
483481
* Redirects to the provided url after decorating it with the auth token for development instances.

packages/types/src/json.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { OrganizationCustomRoleKey, OrganizationPermissionKey } from './org
1212
import type { OrganizationSettingsJSON } from './organizationSettings';
1313
import type { OrganizationSuggestionStatus } from './organizationSuggestion';
1414
import type { SamlIdpSlug } from './saml';
15-
import type { SessionStatus, SessionTask } from './session';
15+
import type { SessionStatus, SessionTaskKey } from './session';
1616
import type { SessionVerificationLevel, SessionVerificationStatus } from './sessionVerification';
1717
import type { SignInFirstFactor, SignInJSON, SignInSecondFactor } from './signIn';
1818
import type { SignUpField, SignUpIdentificationField, SignUpStatus } from './signUp';
@@ -101,6 +101,10 @@ export interface SignUpJSON extends ClerkResourceJSON {
101101
verifications: SignUpVerificationsJSON | null;
102102
}
103103

104+
export interface SessionTaskJSON {
105+
key: SessionTaskKey;
106+
}
107+
104108
export interface SessionJSON extends ClerkResourceJSON {
105109
object: 'session';
106110
id: string;
@@ -117,7 +121,7 @@ export interface SessionJSON extends ClerkResourceJSON {
117121
last_active_token: TokenJSON;
118122
last_active_organization_id: string | null;
119123
actor: ActJWTClaim | null;
120-
tasks: Array<SessionTask> | null;
124+
tasks: Array<SessionTaskJSON> | null;
121125
user: UserJSON;
122126
public_user_data: PublicUserDataJSON;
123127
created_at: number;

packages/types/src/session.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { ClerkOptions } from 'clerk';
2+
import type { EnvironmentResource } from 'environment';
3+
14
import type {
25
BackupCodeAttempt,
36
EmailCodeAttempt,
@@ -103,7 +106,7 @@ export interface SessionResource extends ClerkResource {
103106
lastActiveOrganizationId: string | null;
104107
lastActiveAt: Date;
105108
actor: ActJWTClaim | null;
106-
tasks: Array<SessionTask> | null;
109+
tasks: Array<SessionTaskResource> | null;
107110
user: UserResource | null;
108111
publicUserData: PublicUserData;
109112
end: () => Promise<SessionResource>;
@@ -129,6 +132,7 @@ export interface SessionResource extends ClerkResource {
129132
params: SessionVerifyAttemptSecondFactorParams,
130133
) => Promise<SessionVerificationResource>;
131134
__internal_toSnapshot: () => SessionJSONSnapshot;
135+
__internal_buildSessionTaskUrl: (options: ClerkOptions, environment?: EnvironmentResource | null) => string;
132136
}
133137

134138
/**
@@ -196,8 +200,10 @@ export interface PublicUserData {
196200
userId?: string;
197201
}
198202

199-
export interface SessionTask {
200-
key: 'org';
203+
export type SessionTaskKey = 'org';
204+
205+
export interface SessionTaskResource {
206+
key: SessionTaskKey;
201207
}
202208

203209
export type GetTokenOptions = {

packages/types/src/snapshots.ts

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
SamlAccountConnectionJSON,
1919
SamlAccountJSON,
2020
SessionJSON,
21+
SessionTaskJSON,
2122
SignUpJSON,
2223
SignUpVerificationJSON,
2324
SignUpVerificationsJSON,
@@ -93,6 +94,8 @@ export type SessionJSONSnapshot = Override<
9394
}
9495
>;
9596

97+
export type SessionTaskJSONSnapshot = SessionTaskJSON;
98+
9699
export type SignUpJSONSnapshot = Override<
97100
Nullable<SignUpJSON, 'status'>,
98101
{

0 commit comments

Comments
 (0)