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
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const InitScreen = () => {
}

if (resStart.val instanceof ConnectUserNotFound) {
return handleSituation(LoginSituationCode.UserNotFound);
return handleSituation(LoginSituationCode.PreAuthenticatorUserNotFound);
}

return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import useShared from '../../hooks/useShared';
import { LoginScreenType } from '../../types/screenTypes';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import LoginErrorHard from './base/LoginErrorHard';
import { connectLoginFinishToComplete } from './LoginInitScreen';

const LoginErrorScreenHard = () => {
const { config, navigateToScreen, currentIdentifier, loadedMs } = useLoginProcess();
const { config, navigateToScreen, currentIdentifier, loadedMs, fallback } = useLoginProcess();
const { getConnectService } = useShared();
const [loading, setLoading] = useState(false);
const [hardErrorCount, setHardErrorCount] = useState(1);
Expand Down Expand Up @@ -41,7 +42,7 @@ const LoginErrorScreenHard = () => {
setLoading(false);

try {
await config.onComplete(resFinish.val.session);
await config.onComplete(connectLoginFinishToComplete(resFinish.val));
} catch {
return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator);
}
Expand All @@ -58,14 +59,14 @@ const LoginErrorScreenHard = () => {
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, message);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

setLoading(false);
break;
case LoginSituationCode.ClientPasskeyOperationCancelledTooManyTimes:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, message);
void getConnectService().recordEventLoginError(messageCode);

setLoading(false);
Expand All @@ -79,7 +80,7 @@ const LoginErrorScreenHard = () => {
break;
case LoginSituationCode.ExplicitFallbackByUser:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, null);

void getConnectService().recordEventLoginExplicitAbort();
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import useShared from '../../hooks/useShared';
import { LoginScreenType } from '../../types/screenTypes';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import LoginErrorSoft from './base/LoginErrorSoft';
import { connectLoginFinishToComplete } from './LoginInitScreen';

const LoginErrorScreenSoft = () => {
const { config, navigateToScreen, currentIdentifier, loadedMs } = useLoginProcess();
const { config, navigateToScreen, currentIdentifier, loadedMs, fallback } = useLoginProcess();
const { getConnectService } = useShared();
const [loading, setLoading] = useState(false);

Expand All @@ -34,7 +35,7 @@ const LoginErrorScreenSoft = () => {
}

try {
await config.onComplete(resFinish.val.session);
await config.onComplete(connectLoginFinishToComplete(resFinish.val));
setLoading(false);
} catch {
handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator);
Expand All @@ -52,7 +53,7 @@ const LoginErrorScreenSoft = () => {
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, message);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

setLoading(false);
Expand All @@ -66,7 +67,7 @@ const LoginErrorScreenSoft = () => {
break;
case LoginSituationCode.ExplicitFallbackByUser:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, null);

void getConnectService().recordEventLoginExplicitAbort();
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import useShared from '../../hooks/useShared';
import { LoginScreenType } from '../../types/screenTypes';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import LoginHybrid from './base/LoginHybrid';
import { connectLoginFinishToComplete } from './LoginInitScreen';

const LoginHybridScreen = (resStart: ConnectLoginStartRsp) => {
const { config, navigateToScreen, currentIdentifier } = useLoginProcess();
const { config, navigateToScreen, currentIdentifier, fallback } = useLoginProcess();
const [loading, setLoading] = useState(false);
const { getConnectService } = useShared();

Expand All @@ -30,7 +31,7 @@ const LoginHybridScreen = (resStart: ConnectLoginStartRsp) => {
}

try {
await config.onComplete(res.val.session);
await config.onComplete(connectLoginFinishToComplete(res.val));
} catch {
return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator);
}
Expand All @@ -47,7 +48,7 @@ const LoginHybridScreen = (resStart: ConnectLoginStartRsp) => {
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, message);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

setLoading(false);
Expand All @@ -61,7 +62,7 @@ const LoginHybridScreen = (resStart: ConnectLoginStartRsp) => {
break;
case LoginSituationCode.ExplicitFallbackByUser:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, null);

void getConnectService().recordEventLoginExplicitAbort();
break;
Expand Down
60 changes: 47 additions & 13 deletions packages/connect-react/src/components/login/LoginInitScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import {
ConnectConditionalUIPasskeyDeleted,
ConnectCustomError,
ConnectExistingPasskeysNotAvailable,
ConnectUserNotFound,
PasskeyChallengeCancelledError,
PasskeyLoginSource,
} from '@corbado/web-core';
import type { ConnectLoginFinishRsp } from '@corbado/web-core/dist/api/v2';
import log from 'loglevel';
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
Expand All @@ -12,6 +15,7 @@ import useLoginProcess from '../../hooks/useLoginProcess';
import useShared from '../../hooks/useShared';
import { Flags } from '../../types/flags';
import { LoginScreenType } from '../../types/screenTypes';
import type { PreAuthenticatorCustomErrorData } from '../../types/situations';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import { StatefulLoader } from '../../utils/statefulLoader';
import LoginInitLoaded from './base/LoginInitLoaded';
Expand All @@ -28,8 +32,17 @@ interface Props {
prefilledIdentifier?: string;
}

export const connectLoginFinishToComplete = (v: ConnectLoginFinishRsp): string => {
if (v.session.length > 0) {
return v.session;
}

return v.signedPasskeyData;
};

const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
const { config, navigateToScreen, setCurrentIdentifier, setFlags, flags, loadedMs } = useLoginProcess();
const { config, navigateToScreen, setCurrentIdentifier, setFlags, flags, loadedMs, fallback, fallbackCustom } =
useLoginProcess();
const { sharedConfig, getConnectService } = useShared();
const [cuiBasedLoading, setCuiBasedLoading] = useState(false);
const [identifierBasedLoading, setIdentifierBasedLoading] = useState(false);
Expand Down Expand Up @@ -159,7 +172,7 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
}

try {
await config.onComplete(res.val.session);
await config.onComplete(connectLoginFinishToComplete(res.val));
} catch {
return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator);
}
Expand All @@ -177,7 +190,13 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
const resStart = await getConnectService().loginStart(identifier, PasskeyLoginSource.TextField, loadedMs);
if (resStart.err) {
if (resStart.val instanceof ConnectUserNotFound) {
return handleSituation(LoginSituationCode.UserNotFound);
return handleSituation(LoginSituationCode.PreAuthenticatorUserNotFound);
}
if (resStart.val instanceof ConnectCustomError) {
return handleSituation(LoginSituationCode.PreAuthenticatorCustomError, resStart.val);
}
if (resStart.val instanceof ConnectExistingPasskeysNotAvailable) {
return handleSituation(LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable);
}

return handleSituation(LoginSituationCode.CboApiNotAvailablePreAuthenticator);
Expand All @@ -199,34 +218,39 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
}

try {
await config.onComplete(res.val.session);
await config.onComplete(connectLoginFinishToComplete(res.val));
} catch {
void getConnectService().recordEventLoginErrorUntyped();
return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator);
}
}, [getConnectService, config, loadedMs, identifier]);

const fallback = (identifier: string, message: string | null) => {
const automaticFallback = (identifier: string, message: string | null) => {
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
setIsFallbackInitiallyTriggered(true);
fallback(identifier, message);
};

const handleSituation = (situationCode: LoginSituationCode) => {
const explicitFallback = () => {
navigateToScreen(LoginScreenType.Invisible);
fallback(identifier, null);
};

const handleSituation = (situationCode: LoginSituationCode, data?: unknown) => {
const messageCode = `situation: ${situationCode}`;
log.debug(messageCode);

const message = getLoginErrorMessage(situationCode);

switch (situationCode) {
case LoginSituationCode.CboApiNotAvailablePreAuthenticator:
fallback(identifier, message);
automaticFallback(identifier, message);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

statefulLoader.current.finish();
break;
case LoginSituationCode.DeniedByPartialRollout:
fallback(identifier, message);
automaticFallback(identifier, message);

statefulLoader.current.finish();
break;
Expand All @@ -235,7 +259,8 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {
case LoginSituationCode.CboApiNotAvailablePreConditionalAuthenticator:
case LoginSituationCode.CtApiNotAvailablePostAuthenticator:
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
fallback(identifier, message);
case LoginSituationCode.PreAuthenticatorExistingPasskeysNotAvailable:
automaticFallback(identifier, message);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

setIdentifierBasedLoading(false);
Expand All @@ -247,18 +272,27 @@ const LoginInitScreen: FC<Props> = ({ showFallback = false }) => {

setIdentifierBasedLoading(false);
break;
case LoginSituationCode.UserNotFound:
case LoginSituationCode.PreAuthenticatorUserNotFound:
setError(message ?? '');
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

setIdentifierBasedLoading(false);
break;
case LoginSituationCode.ExplicitFallbackByUser:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
explicitFallback();

void getConnectService().recordEventLoginExplicitAbort();
break;
case LoginSituationCode.PreAuthenticatorCustomError: {
navigateToScreen(LoginScreenType.Invisible);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);
if (!data) {
return fallback(identifier, null);
}

const typed = data as PreAuthenticatorCustomErrorData;
fallbackCustom(identifier, typed.code, typed.message);
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import useShared from '../../hooks/useShared';
import { LoginScreenType } from '../../types/screenTypes';
import { getLoginErrorMessage, LoginSituationCode } from '../../types/situations';
import LoginOneTap from './base/LoginOneTap';
import { connectLoginFinishToComplete } from './LoginInitScreen';

export const LoginPasskeyReLoginScreen = () => {
const { config, navigateToScreen, setCurrentIdentifier, currentIdentifier, loadedMs } = useLoginProcess();
const { config, navigateToScreen, setCurrentIdentifier, currentIdentifier, loadedMs, fallback } = useLoginProcess();
const { getConnectService } = useShared();
const [loading, setLoading] = useState(false);

Expand Down Expand Up @@ -41,7 +42,7 @@ export const LoginPasskeyReLoginScreen = () => {
}

try {
await config.onComplete(resFinish.val.session);
await config.onComplete(connectLoginFinishToComplete(resFinish.val));
} catch {
return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator);
}
Expand All @@ -64,7 +65,7 @@ export const LoginPasskeyReLoginScreen = () => {
case LoginSituationCode.CboApiNotAvailablePostAuthenticator:
case LoginSituationCode.CboApiNotAvailablePreAuthenticator:
navigateToScreen(LoginScreenType.Invisible);
config.onFallback(identifier, message);
fallback(identifier, message);
void getConnectService().recordEventLoginErrorUnexpected(messageCode);

setLoading(false);
Expand Down
4 changes: 4 additions & 0 deletions packages/connect-react/src/contexts/LoginProcessContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface LoginProcessContextProps {
currentScreenType: LoginScreenType;
currentScreenOptions: any;
config: CorbadoConnectLoginConfig;
fallback: (identifier: string, message: string | null) => void;
fallbackCustom: (identifier: string, code: string, payload: string) => void;
navigateToScreen: (s: LoginScreenType, options?: any) => void;
setCurrentIdentifier: (s: string) => void;
currentIdentifier: string;
Expand All @@ -25,6 +27,8 @@ export const initialContext: LoginProcessContextProps = {
config: {} as CorbadoConnectLoginConfig,
navigateToScreen: missingImplementation,
setCurrentIdentifier: missingImplementation,
fallback: missingImplementation,
fallbackCustom: missingImplementation,
currentIdentifier: '',
currentScreenOptions: undefined,
flags: undefined,
Expand Down
24 changes: 24 additions & 0 deletions packages/connect-react/src/contexts/LoginProcessProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,28 @@ export const LoginProcessProvider: FC<PropsWithChildren<Props>> = ({ children, i
setCurrentScreenOptions(options);
}, []);

const fallback = useCallback(
(identifier: string, message: string | null) => {
if (!message || !config.onFallbackSilent) {
config.onFallback(identifier, message ?? '');
} else {
config.onFallbackSilent(identifier);
}
},
[config],
);

const fallbackCustom = useCallback(
(identifier: string, code: string, payload: string) => {
if (!config.onFallbackCustom) {
config.onFallback(identifier, code);
} else {
config.onFallbackCustom(identifier, code, payload);
}
},
[config],
);

const contextValue = useMemo<LoginProcessContextProps>(
() => ({
currentScreenType,
Expand All @@ -35,6 +57,8 @@ export const LoginProcessProvider: FC<PropsWithChildren<Props>> = ({ children, i
setFlags,
currentScreenOptions,
loadedMs,
fallback,
fallbackCustom,
}),
[currentScreenType, navigateToScreen, config, currentIdentifier, currentScreenOptions, flags],
);
Expand Down
Loading
Loading