Skip to content
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
8 changes: 8 additions & 0 deletions .changeset/selfish-ladybugs-smile.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';
Expand Down Expand Up @@ -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 (
<Col
Expand Down Expand Up @@ -88,10 +100,10 @@ const FabContent = ({ title, signOutText }: FabContentProps) => {
},
})}
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!)
}
/>
</Col>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this investigation before merge?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was waiting for #3518 to be merged, and i will check again, but simply calling setActive would not refresh the router.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So i investigated further, even with #3518 the issue persisted. I've identified this as a FAPI issue, because after user.delete() client_uat comes back with a valid timestamp instead of being 0. This will need a separate ticket to be address and should not be part of this PR

if (otherSessions.length === 0) {
return navigateAfterSignOut();
}
await navigateAfterMultiSessionSingleSignOutUrl();
Comment on lines +42 to +46

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 I wouldn't necessarily expect that this method would need to be aware of multi-session. I understand why it was done this way as part of this PR, but it feels like a better internal abstraction might be to consolidate the multi-session logic into navigateAfterSignOut().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think creating an abstraction here would make our lifer harder, we have useMultisessionActions which needs to know the difference between single session sign out and multisession sign out. We can refactor but i don't see a clear benefit here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benefit would be not having to spread some of this logic across many different methods. That's fine to leave it for now, let's jut keep an eye on these code paths and we can abstract if it feels like multi-session logic is too difficult to manage.

} catch (e) {
handleError(e, [], card.setError);
}
Expand Down
19 changes: 17 additions & 2 deletions packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -185,15 +186,17 @@ export const useSignInContext = (): SignInContextType => {

export type SignOutContextType = {
navigateAfterSignOut: () => any;
navigateAfterMultiSessionSingleSignOutUrl: () => any;
};

export const useSignOutContext = (): SignOutContextType => {
const { navigate } = useRouter();
const clerk = useClerk();

const navigateAfterSignOut = () => navigate(clerk.buildAfterSignOutUrl());
const navigateAfterMultiSessionSingleSignOutUrl = () => navigate(clerk.buildAfterMultiSessionSingleSignOutUrl());

return { navigateAfterSignOut };
return { navigateAfterSignOut, navigateAfterMultiSessionSingleSignOutUrl };
};

type PagesType = {
Expand Down Expand Up @@ -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 '<ClerkProvider/>`);
}

const afterSignOutUrl = ctx.afterSignOutUrl || clerk.buildAfterSignOutUrl();
const navigateAfterSignOut = () => navigate(afterSignOutUrl);

const afterMultiSessionSingleSignOutUrl = ctx.afterMultiSessionSingleSignOutUrl || afterSignOutUrl;
if (ctx.afterSignOutUrl) {
deprecatedObjectProperty(
ctx,
'afterMultiSessionSingleSignOutUrl',
`Move 'afterMultiSessionSingleSignOutUrl' to '<ClerkProvider/>`,
);
}
const afterMultiSessionSingleSignOutUrl =
ctx.afterMultiSessionSingleSignOutUrl || clerk.buildAfterMultiSessionSingleSignOutUrl();
const navigateAfterMultiSessionSingleSignOut = () => clerk.redirectWithAuth(afterMultiSessionSingleSignOutUrl);

const afterSwitchSessionUrl = ctx.afterSwitchSessionUrl || displayConfig.afterSwitchSessionUrl;
Expand Down
19 changes: 19 additions & 0 deletions packages/clerk-js/src/ui/hooks/useMultipleSessions.ts
Original file line number Diff line number Diff line change
@@ -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 };
11 changes: 10 additions & 1 deletion packages/types/src/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -564,7 +570,8 @@ export type ClerkOptions = ClerkOptionsNavigation &
SignUpForceRedirectUrl &
SignUpFallbackRedirectUrl &
LegacyRedirectProps &
AfterSignOutUrl & {
AfterSignOutUrl &
AfterMultiSessionSingleSignOutUrl & {
appearance?: Appearance;
localization?: LocalizationResource;
polling?: boolean;
Expand Down Expand Up @@ -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 <ClerkProvider/> 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 <ClerkProvider/> or in await Clerk.load()
*/
afterMultiSessionSingleSignOutUrl?: string;
/**
Expand Down
8 changes: 8 additions & 0 deletions packages/types/src/redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down