From f4f867409fd40e2a5eafd276ad41a93110ef8dbe Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 10 Jun 2024 16:28:25 +0300 Subject: [PATCH 1/3] fix(clerk-js): Deprecate `afterSignOutUrl` from UserButton. Also handles in introduction of `afterMultiSessionSingleSignOutUrl` in ClerkProvider --- .changeset/selfish-ladybugs-smile.md | 6 ++++++ packages/clerk-js/src/core/clerk.ts | 8 ++++++++ .../UserButton/useMultisessionActions.tsx | 7 +++---- .../components/UserProfile/DeleteUserForm.tsx | 11 +++++++++-- .../ui/contexts/ClerkUIComponentsContext.tsx | 19 +++++++++++++++++-- .../src/ui/hooks/useMultipleSessions.ts | 19 +++++++++++++++++++ packages/types/src/clerk.ts | 11 ++++++++++- packages/types/src/redirects.ts | 8 ++++++++ 8 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 .changeset/selfish-ladybugs-smile.md create mode 100644 packages/clerk-js/src/ui/hooks/useMultipleSessions.ts diff --git a/.changeset/selfish-ladybugs-smile.md b/.changeset/selfish-ladybugs-smile.md new file mode 100644 index 00000000000..739b610a092 --- /dev/null +++ b/.changeset/selfish-ladybugs-smile.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': minor +'@clerk/types': minor +--- + +Deprecate `afterSignOutUrl` from UserButton. diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index d9607721469..bdd85620b90 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -867,6 +867,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/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 2db870fb7ac..d7d005d6687 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. */ From 31b988d3cd78a009356b221363f2bad90a9cb7d9 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 25 Jun 2024 13:43:08 +0300 Subject: [PATCH 2/3] fix(clerk-js): Handle afterSignOut in ImpersonationFab --- .../ImpersonationFab/ImpersonationFab.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) 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!) + } /> ); From 59e79f352e66e6c24ce7a48955e346a3a78e6c3e Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 28 Jun 2024 09:50:04 +0300 Subject: [PATCH 3/3] chore(clerk-js): Update changeset --- .changeset/selfish-ladybugs-smile.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/selfish-ladybugs-smile.md b/.changeset/selfish-ladybugs-smile.md index 739b610a092..216bcc982b6 100644 --- a/.changeset/selfish-ladybugs-smile.md +++ b/.changeset/selfish-ladybugs-smile.md @@ -3,4 +3,6 @@ '@clerk/types': minor --- -Deprecate `afterSignOutUrl` from UserButton. +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.