diff --git a/.changeset/odd-coins-carry.md b/.changeset/odd-coins-carry.md new file mode 100644 index 00000000000..54f4427c7ae --- /dev/null +++ b/.changeset/odd-coins-carry.md @@ -0,0 +1,7 @@ +--- +'@clerk/elements': patch +--- + +- Bump XState from version 5.12.0 to 5.13.0 +- We now maintain your current state while completing authentication redirecting rather than transitioning to a "Complete" step. +- Fixes Sign In and Sign Up machines not appropriately attaching new form machines when returning to the Sign In or Sign Up pages. diff --git a/package-lock.json b/package-lock.json index 012dd48c1be..9599a2b80e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38410,7 +38410,7 @@ "@statelyai/inspect": "^0.3.0", "@xstate/react": "^4.1.1", "client-only": "^0.0.1", - "xstate": "^5.12.0" + "xstate": "^5.13.0" }, "devDependencies": { "@clerk/clerk-react": "5.0.4", @@ -38629,9 +38629,9 @@ } }, "packages/elements/node_modules/xstate": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.12.0.tgz", - "integrity": "sha512-4W/Hj553mwVnTLQ1itc3rni/cGtM5OkjyavTjaxCelc0ZZKE/ks6tYllc98KbekIoUrEPm4CJH/wTB5p5pPGEw==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.13.0.tgz", + "integrity": "sha512-Z0om784N5u8sAzUvQJBa32jiTCIGGF/2ZsmKkerQEqeeUktAeOMK20FIHFUMywC4GcAkNksSvaeX7lwoRNXPEQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/xstate" diff --git a/packages/elements/package.json b/packages/elements/package.json index be75485282b..17f3c2d6792 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -74,7 +74,7 @@ "@statelyai/inspect": "^0.3.0", "@xstate/react": "^4.1.1", "client-only": "^0.0.1", - "xstate": "^5.12.0" + "xstate": "^5.13.0" }, "devDependencies": { "@clerk/clerk-react": "5.0.4", diff --git a/packages/elements/src/internals/machines/sign-in/router.machine.ts b/packages/elements/src/internals/machines/sign-in/router.machine.ts index 31af06866ab..8356414e455 100644 --- a/packages/elements/src/internals/machines/sign-in/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-in/router.machine.ts @@ -13,6 +13,7 @@ import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/error import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party'; import { shouldUseVirtualRouting } from '~/internals/machines/utils/next'; +import { FormMachine } from '../form'; import { SignInResetPasswordMachine } from './reset-password.machine'; import type { SignInRouterContext, @@ -41,6 +42,7 @@ export const SignInRouterMachineId = 'SignInRouter'; export const SignInRouterMachine = setup({ actors: { firstFactorMachine: SignInFirstFactorMachine, + formMachine: FormMachine, resetPasswordMachine: SignInResetPasswordMachine, startMachine: SignInStartMachine, secondFactorMachine: SignInSecondFactorMachine, @@ -60,8 +62,8 @@ export const SignInRouterMachine = setup({ }, navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path), raiseNext: raise({ type: 'NEXT' }), - setActive({ context, event }) { - if (context.exampleMode) return; + setActive: enqueueActions(({ enqueue, check, context, event }) => { + if (check('isExampleMode')) return; const lastActiveSessionId = context.clerk.client.lastActiveSessionId; const createdSessionId = ((event as SignInRouterNextEvent)?.resource || context.clerk.client.signIn) @@ -71,7 +73,9 @@ export const SignInRouterMachine = setup({ const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignInUrl()); void context.clerk.setActive({ session, beforeEmit }); - }, + + enqueue.raise({ type: 'RESET' }, { delay: 2000 }); // Reset machine after 2s delay. + }), setError: assign({ error: (_, { error }: { error?: ClerkElementsError }) => { if (error) return error; @@ -182,6 +186,7 @@ export const SignInRouterMachine = setup({ }, })), }, + RESET: '.Idle', }, states: { Idle: { @@ -224,7 +229,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'isLoggedInAndSingleSession', @@ -285,7 +289,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsFirstFactor', @@ -323,7 +326,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsSecondFactor', @@ -390,7 +392,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsNewPassword', @@ -418,7 +419,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsFirstFactor', @@ -446,7 +446,6 @@ export const SignInRouterMachine = setup({ { guard: or(['isLoggedIn', 'isComplete', 'hasAuthenticatedViaClerkJS']), actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsIdentifier', @@ -470,13 +469,6 @@ export const SignInRouterMachine = setup({ ], }, }, - Complete: { - tags: 'route:complete', - entry: 'clearFormErrors', - after: { - 5000: 'Start', - }, - }, Error: { tags: 'route:error', on: { diff --git a/packages/elements/src/internals/machines/sign-in/router.types.ts b/packages/elements/src/internals/machines/sign-in/router.types.ts index 50a9257d6da..ada057c95c9 100644 --- a/packages/elements/src/internals/machines/sign-in/router.types.ts +++ b/packages/elements/src/internals/machines/sign-in/router.types.ts @@ -10,6 +10,7 @@ import type { BaseRouterNextEvent, BaseRouterPrevEvent, BaseRouterRedirectEvent, + BaseRouterResetEvent, BaseRouterSetClerkEvent, BaseRouterStartEvent, BaseRouterTransferEvent, @@ -54,6 +55,7 @@ export type SignInRouterForgotPasswordEvent = { type: 'NAVIGATE.FORGOT_PASSWORD' export type SignInRouterErrorEvent = BaseRouterErrorEvent; export type SignInRouterTransferEvent = BaseRouterTransferEvent; export type SignInRouterRedirectEvent = BaseRouterRedirectEvent; +export type SignInRouterResetEvent = BaseRouterResetEvent; export type SignInRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'reset-password'>; export type SignInRouterSetClerkEvent = BaseRouterSetClerkEvent; export type SignInRouterSubmitEvent = { type: 'SUBMIT' }; @@ -77,6 +79,7 @@ export type SignInRouterEvents = | SignInRouterErrorEvent | SignInRouterTransferEvent | SignInRouterRedirectEvent + | SignInRouterResetEvent | SignInVerificationFactorUpdateEvent | SignInRouterLoadingEvent | SignInRouterSetClerkEvent diff --git a/packages/elements/src/internals/machines/sign-up/router.machine.ts b/packages/elements/src/internals/machines/sign-up/router.machine.ts index 817b1563d34..8659db35265 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -1,7 +1,7 @@ import { joinURL } from '@clerk/shared/url'; import type { SignUpStatus, VerificationStatus } from '@clerk/types'; import type { NonReducibleUnknown } from 'xstate'; -import { and, assign, log, not, or, raise, sendTo, setup, spawnChild } from 'xstate'; +import { and, assign, enqueueActions, log, not, or, raise, sendTo, setup, spawnChild } from 'xstate'; import { ERROR_CODES, @@ -59,15 +59,20 @@ export const SignUpRouterMachine = setup({ }, navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path), raiseNext: raise({ type: 'NEXT' }), - setActive({ context, event }, params?: { sessionId?: string; useLastActiveSession?: boolean }) { - const session = - params?.sessionId || - (params?.useLastActiveSession && context.clerk.client.lastActiveSessionId) || - ((event as SignUpRouterNextEvent)?.resource || context.clerk.client.signUp).createdSessionId; + setActive: (_, params?: { sessionId?: string; useLastActiveSession?: boolean }) => + enqueueActions(({ enqueue, check, context, event }) => { + if (check('isExampleMode')) return; - const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignUpUrl()); - void context.clerk.setActive({ session, beforeEmit }); - }, + const session = + params?.sessionId || + (params?.useLastActiveSession && context.clerk.client.lastActiveSessionId) || + ((event as SignUpRouterNextEvent)?.resource || context.clerk.client.signUp).createdSessionId; + + const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignUpUrl()); + void context.clerk.setActive({ session, beforeEmit }); + + enqueue.raise({ type: 'RESET' }, { delay: 2000 }); // Reset machine after 2s delay. + }), setError: assign({ error: (_, { error }: { error?: ClerkElementsError }) => { if (error) return error; @@ -182,6 +187,7 @@ export const SignUpRouterMachine = setup({ }, })), }, + RESET: '.Idle', }, states: { Idle: { @@ -264,7 +270,6 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -298,7 +303,6 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -329,7 +333,6 @@ export const SignUpRouterMachine = setup({ type: 'setActive', params: { sessionId: context.router?.searchParams().get(SEARCH_PARAMS.createdSession) }, }), - target: 'Complete', }, { guard: { type: 'hasClerkStatus', params: { status: 'verified' } }, @@ -345,7 +348,6 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsContinue', @@ -363,13 +365,11 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { description: 'Handle a case where the user has already been authenticated via ClerkJS', guard: 'hasAuthenticatedViaClerkJS', actions: { type: 'setActive', params: { useLastActiveSession: true } }, - target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -388,13 +388,6 @@ export const SignUpRouterMachine = setup({ ], }, }, - Complete: { - tags: 'route:complete', - entry: 'clearFormErrors', - after: { - 5000: 'Start', - }, - }, Error: { tags: 'route:error', on: { diff --git a/packages/elements/src/internals/machines/sign-up/router.types.ts b/packages/elements/src/internals/machines/sign-up/router.types.ts index 8c359f7e7ea..2ea4fd4b0b6 100644 --- a/packages/elements/src/internals/machines/sign-up/router.types.ts +++ b/packages/elements/src/internals/machines/sign-up/router.types.ts @@ -10,6 +10,7 @@ import type { BaseRouterNextEvent, BaseRouterPrevEvent, BaseRouterRedirectEvent, + BaseRouterResetEvent, BaseRouterSetClerkEvent, BaseRouterStartEvent, BaseRouterTransferEvent, @@ -46,6 +47,7 @@ export type SignUpRouterPrevEvent = BaseRouterPrevEvent; export type SignUpRouterErrorEvent = BaseRouterErrorEvent; export type SignUpRouterTransferEvent = BaseRouterTransferEvent; export type SignUpRouterRedirectEvent = BaseRouterRedirectEvent; +export type SignUpRouterResetEvent = BaseRouterResetEvent; export type SignUpRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'continue'>; export type SignUpRouterSetClerkEvent = BaseRouterSetClerkEvent; @@ -64,6 +66,7 @@ export type SignUpRouterEvents = | SignUpRouterErrorEvent | SignUpRouterTransferEvent | SignUpRouterRedirectEvent + | SignUpRouterResetEvent | SignUpRouterLoadingEvent | SignUpRouterSetClerkEvent; diff --git a/packages/elements/src/internals/machines/types/router.types.ts b/packages/elements/src/internals/machines/types/router.types.ts index a4d6e7bf489..904fb56dd4e 100644 --- a/packages/elements/src/internals/machines/types/router.types.ts +++ b/packages/elements/src/internals/machines/types/router.types.ts @@ -17,6 +17,7 @@ export type BaseRouterLoadingStep = 'start' | 'verifications' | 'continue' | 're export type BaseRouterNextEvent = { type: 'NEXT'; resource?: T }; export type BaseRouterPrevEvent = { type: 'NAVIGATE.PREVIOUS' }; export type BaseRouterStartEvent = { type: 'NAVIGATE.START' }; +export type BaseRouterResetEvent = { type: 'RESET' }; export type BaseRouterErrorEvent = { type: 'ERROR'; error: Error }; export type BaseRouterTransferEvent = { type: 'TRANSFER' }; export type BaseRouterLoadingEvent = ( diff --git a/packages/elements/src/react/sign-in/root.tsx b/packages/elements/src/react/sign-in/root.tsx index 8a782caa7e2..1d12978205a 100644 --- a/packages/elements/src/react/sign-in/root.tsx +++ b/packages/elements/src/react/sign-in/root.tsx @@ -44,7 +44,9 @@ function SignInFlowProvider({ children, exampleMode }: SignInFlowProviderProps) actor.send(evt); } }); - }, [clerk, exampleMode, formRef, router]); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clerk, exampleMode, formRef?.id, !!router]); return {children}; } diff --git a/packages/elements/src/react/sign-in/verifications.tsx b/packages/elements/src/react/sign-in/verifications.tsx index a2682fa2b6e..6b3d647ae5d 100644 --- a/packages/elements/src/react/sign-in/verifications.tsx +++ b/packages/elements/src/react/sign-in/verifications.tsx @@ -80,7 +80,7 @@ export function SignInStrategy({ children, name }: SignInStrategyProps) { } return () => { - if (factorCtx) { + if (factorCtx?.getSnapshot().status === 'active') { factorCtx.send({ type: 'STRATEGY.UNREGISTER', factor: name as unknown as SignInFactor }); } }; diff --git a/packages/elements/src/react/sign-up/root.tsx b/packages/elements/src/react/sign-up/root.tsx index 6ef2883fe78..bf2c9c198e3 100644 --- a/packages/elements/src/react/sign-up/root.tsx +++ b/packages/elements/src/react/sign-up/root.tsx @@ -46,7 +46,8 @@ function SignUpFlowProvider({ children, exampleMode }: SignUpFlowProviderProps) ref.send(evt); } }); - }, [clerk, exampleMode, formRef, router]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clerk, exampleMode, formRef?.id, !!router]); return isReady ? {children} : null; }