diff --git a/.changeset/selfish-ladybugs-smile.md b/.changeset/selfish-ladybugs-smile.md
new file mode 100644
index 00000000000..216bcc982b6
--- /dev/null
+++ b/.changeset/selfish-ladybugs-smile.md
@@ -0,0 +1,8 @@
+---
+'@clerk/clerk-js': minor
+'@clerk/types': minor
+---
+
+Deprecate `afterSignOutUrl` and `afterMultiSessionSingleSignOutUrl` from UserButton.
+
+Developers can now configure these directly in `ClerkProvider` and have them work properly without in UserButton, UserProfile and in impersonation mode.
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index d8463a1d5c5..cf047327ca0 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -870,6 +870,14 @@ export class Clerk implements ClerkInterface {
return this.buildUrlWithAuth(this.#options.afterSignOutUrl);
}
+ public buildAfterMultiSessionSingleSignOutUrl(): string {
+ if (!this.#options.afterMultiSessionSingleSignOutUrl) {
+ return this.buildAfterSignOutUrl();
+ }
+
+ return this.buildUrlWithAuth(this.#options.afterMultiSessionSingleSignOutUrl);
+ }
+
public buildCreateOrganizationUrl(): string {
if (!this.environment || !this.environment.displayConfig) {
return '';
diff --git a/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx b/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx
index afad36a5ee6..d9d7299424c 100644
--- a/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx
+++ b/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx
@@ -1,9 +1,10 @@
-import { useClerk, useSession } from '@clerk/shared/react';
+import { useClerk, useSession, useUser } from '@clerk/shared/react';
+import type { ActiveSessionResource } from '@clerk/types';
import type { PointerEventHandler } from 'react';
import React, { useEffect, useRef } from 'react';
import { getFullName, getIdentifier } from '../../../utils/user';
-import { withCoreUserGuard } from '../../contexts';
+import { useSignOutContext, withCoreUserGuard } from '../../contexts';
import type { LocalizationKey } from '../../customizables';
import {
Col,
@@ -17,6 +18,7 @@ import {
useLocalizations,
} from '../../customizables';
import { Portal } from '../../elements/Portal';
+import { useMultipleSessions } from '../../hooks/useMultipleSessions';
import { Eye } from '../../icons';
import type { PropsOfComponent } from '../../styledSystem';
import { InternalThemeProvider, mqu } from '../../styledSystem';
@@ -59,7 +61,17 @@ type FabContentProps = { title: LocalizationKey; signOutText: LocalizationKey };
const FabContent = ({ title, signOutText }: FabContentProps) => {
const { session } = useSession();
+ const { user } = useUser();
const { signOut } = useClerk();
+ const { otherSessions } = useMultipleSessions({ user });
+ const { navigateAfterSignOut, navigateAfterMultiSessionSingleSignOutUrl } = useSignOutContext();
+
+ const handleSignOutSessionClicked = (session: ActiveSessionResource) => () => {
+ if (otherSessions.length === 0) {
+ return signOut(navigateAfterSignOut);
+ }
+ return signOut(navigateAfterMultiSessionSingleSignOutUrl, { sessionId: session.id });
+ };
return (
{
},
})}
localizationKey={signOutText}
- onClick={async () => {
+ onClick={
// clerk-js has been loaded at this point so we can safely access session
- await signOut({ sessionId: session!.id });
- }}
+ handleSignOutSessionClicked(session!)
+ }
/>
);
diff --git a/packages/clerk-js/src/ui/components/UserButton/useMultisessionActions.tsx b/packages/clerk-js/src/ui/components/UserButton/useMultisessionActions.tsx
index e271bfd4d27..b9508cdc15d 100644
--- a/packages/clerk-js/src/ui/components/UserButton/useMultisessionActions.tsx
+++ b/packages/clerk-js/src/ui/components/UserButton/useMultisessionActions.tsx
@@ -1,8 +1,9 @@
-import { useClerk, useSessionList } from '@clerk/shared/react';
+import { useClerk } from '@clerk/shared/react';
import type { ActiveSessionResource, UserButtonProps, UserResource } from '@clerk/types';
import { windowNavigate } from '../../../utils/windowNavigate';
import { useCardState } from '../../elements';
+import { useMultipleSessions } from '../../hooks/useMultipleSessions';
import { useRouter } from '../../router';
import { sleep } from '../../utils';
@@ -19,10 +20,8 @@ type UseMultisessionActionsParams = {
export const useMultisessionActions = (opts: UseMultisessionActionsParams) => {
const { setActive, signOut, openUserProfile } = useClerk();
const card = useCardState();
- const { sessions } = useSessionList();
+ const { activeSessions, otherSessions } = useMultipleSessions({ user: opts.user });
const { navigate } = useRouter();
- const activeSessions = sessions?.filter(s => s.status === 'active') as ActiveSessionResource[];
- const otherSessions = activeSessions.filter(s => s.user?.id !== opts.user?.id);
const handleSignOutSessionClicked = (session: ActiveSessionResource) => () => {
if (otherSessions.length === 0) {
diff --git a/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx
index 7146a79d63a..37515f17f2f 100644
--- a/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx
+++ b/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx
@@ -4,15 +4,17 @@ import { useSignOutContext } from '../../contexts';
import { Col, localizationKeys, Text, useLocalizations } from '../../customizables';
import type { FormProps } from '../../elements';
import { Form, FormButtons, FormContainer, useCardState, withCardStateProvider } from '../../elements';
+import { useMultipleSessions } from '../../hooks/useMultipleSessions';
import { handleError, useFormControl } from '../../utils';
type DeleteUserFormProps = FormProps;
export const DeleteUserForm = withCardStateProvider((props: DeleteUserFormProps) => {
const { onReset } = props;
const card = useCardState();
- const { navigateAfterSignOut } = useSignOutContext();
+ const { navigateAfterSignOut, navigateAfterMultiSessionSingleSignOutUrl } = useSignOutContext();
const { user } = useUser();
const { t } = useLocalizations();
+ const { otherSessions } = useMultipleSessions({ user });
const confirmationField = useFormControl('deleteConfirmation', '', {
type: 'text',
@@ -36,7 +38,12 @@ export const DeleteUserForm = withCardStateProvider((props: DeleteUserFormProps)
}
await user.delete();
- await navigateAfterSignOut();
+
+ // TODO: Investigate if we need to call `setActive` with {session: null}
+ if (otherSessions.length === 0) {
+ return navigateAfterSignOut();
+ }
+ await navigateAfterMultiSessionSingleSignOutUrl();
} catch (e) {
handleError(e, [], card.setError);
}
diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
index dd0fc2dde59..72853b0758b 100644
--- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
+++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
@@ -1,3 +1,4 @@
+import { deprecatedObjectProperty } from '@clerk/shared/deprecated';
import { useClerk } from '@clerk/shared/react';
import { snakeToCamel } from '@clerk/shared/underscore';
import type { HandleOAuthCallbackParams, OrganizationResource, UserResource } from '@clerk/types';
@@ -185,6 +186,7 @@ export const useSignInContext = (): SignInContextType => {
export type SignOutContextType = {
navigateAfterSignOut: () => any;
+ navigateAfterMultiSessionSingleSignOutUrl: () => any;
};
export const useSignOutContext = (): SignOutContextType => {
@@ -192,8 +194,9 @@ export const useSignOutContext = (): SignOutContextType => {
const clerk = useClerk();
const navigateAfterSignOut = () => navigate(clerk.buildAfterSignOutUrl());
+ const navigateAfterMultiSessionSingleSignOutUrl = () => navigate(clerk.buildAfterMultiSessionSingleSignOutUrl());
- return { navigateAfterSignOut };
+ return { navigateAfterSignOut, navigateAfterMultiSessionSingleSignOutUrl };
};
type PagesType = {
@@ -244,10 +247,22 @@ export const useUserButtonContext = () => {
const signInUrl = ctx.signInUrl || options.signInUrl || displayConfig.signInUrl;
const userProfileUrl = ctx.userProfileUrl || displayConfig.userProfileUrl;
+ if (ctx.afterSignOutUrl) {
+ deprecatedObjectProperty(ctx, 'afterSignOutUrl', `Move 'afterSignOutUrl' to '`);
+ }
+
const afterSignOutUrl = ctx.afterSignOutUrl || clerk.buildAfterSignOutUrl();
const navigateAfterSignOut = () => navigate(afterSignOutUrl);
- const afterMultiSessionSingleSignOutUrl = ctx.afterMultiSessionSingleSignOutUrl || afterSignOutUrl;
+ if (ctx.afterSignOutUrl) {
+ deprecatedObjectProperty(
+ ctx,
+ 'afterMultiSessionSingleSignOutUrl',
+ `Move 'afterMultiSessionSingleSignOutUrl' to '`,
+ );
+ }
+ const afterMultiSessionSingleSignOutUrl =
+ ctx.afterMultiSessionSingleSignOutUrl || clerk.buildAfterMultiSessionSingleSignOutUrl();
const navigateAfterMultiSessionSingleSignOut = () => clerk.redirectWithAuth(afterMultiSessionSingleSignOutUrl);
const afterSwitchSessionUrl = ctx.afterSwitchSessionUrl || displayConfig.afterSwitchSessionUrl;
diff --git a/packages/clerk-js/src/ui/hooks/useMultipleSessions.ts b/packages/clerk-js/src/ui/hooks/useMultipleSessions.ts
new file mode 100644
index 00000000000..c62caba2936
--- /dev/null
+++ b/packages/clerk-js/src/ui/hooks/useMultipleSessions.ts
@@ -0,0 +1,19 @@
+import { useSessionList } from '@clerk/shared/react';
+import type { ActiveSessionResource, UserResource } from '@clerk/types';
+
+type UseMultipleSessionsParam = {
+ user: UserResource | null | undefined;
+};
+
+const useMultipleSessions = (params: UseMultipleSessionsParam) => {
+ const { sessions } = useSessionList();
+ const activeSessions = sessions?.filter(s => s.status === 'active') as ActiveSessionResource[];
+ const otherSessions = activeSessions.filter(s => s.user?.id !== params.user?.id);
+
+ return {
+ activeSessions,
+ otherSessions,
+ };
+};
+
+export { useMultipleSessions };
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 5f43f9ccd27..31338858ff9 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -20,6 +20,7 @@ import type { OAuthProvider, OAuthScope } from './oauth';
import type { OrganizationResource } from './organization';
import type { OrganizationCustomRoleKey } from './organizationMembership';
import type {
+ AfterMultiSessionSingleSignOutUrl,
AfterSignOutUrl,
LegacyRedirectProps,
RedirectOptions,
@@ -387,6 +388,11 @@ export interface Clerk {
*/
buildAfterSignOutUrl(): string;
+ /**
+ * Returns the configured afterMultiSessionSingleSignOutUrl of the instance.
+ */
+ buildAfterMultiSessionSingleSignOutUrl(): string;
+
/**
*
* Redirects to the provided url after decorating it with the auth token for development instances.
@@ -564,7 +570,8 @@ export type ClerkOptions = ClerkOptionsNavigation &
SignUpForceRedirectUrl &
SignUpFallbackRedirectUrl &
LegacyRedirectProps &
- AfterSignOutUrl & {
+ AfterSignOutUrl &
+ AfterMultiSessionSingleSignOutUrl & {
appearance?: Appearance;
localization?: LocalizationResource;
polling?: boolean;
@@ -879,11 +886,13 @@ export type UserButtonProps = UserButtonProfileMode & {
defaultOpen?: boolean;
/**
* Full URL or path to navigate after sign out is complete
+ * @deprecated Configure `afterSignOutUrl` as a global configuration, either in or in await Clerk.load()
*/
afterSignOutUrl?: string;
/**
* Full URL or path to navigate after signing out the current user is complete.
* This option applies to multi-session applications.
+ * @deprecated Configure `afterMultiSessionSingleSignOutUrl` as a global configuration, either in or in await Clerk.load()
*/
afterMultiSessionSingleSignOutUrl?: string;
/**
diff --git a/packages/types/src/redirects.ts b/packages/types/src/redirects.ts
index 9645fe0fd67..cfd52ede8e2 100644
--- a/packages/types/src/redirects.ts
+++ b/packages/types/src/redirects.ts
@@ -7,6 +7,14 @@ export type AfterSignOutUrl = {
afterSignOutUrl?: string | null;
};
+export type AfterMultiSessionSingleSignOutUrl = {
+ /**
+ * Full URL or path to navigate after signing out the current user is complete.
+ * This option applies to multi-session applications.
+ */
+ afterMultiSessionSingleSignOutUrl?: string | null;
+};
+
/**
* @deprecated This is deprecated and will be removed in a future release.
*/