From 1cbb17f67646cca03ee630f0eb615604b3aab80f Mon Sep 17 00:00:00 2001 From: Maks <41080668+Maks19@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:32:49 +0300 Subject: [PATCH] [WebApp] Add passkey support logic (#1162) --- packages/web-app/src/Store.tsx | 3 + .../PasskeySetupPageContainer.tsx | 2 +- .../modules/passkey-setup/PasskeyStore.tsx | 26 ++++++++ .../components/PasskeySetupPage.tsx | 66 +++++++++---------- .../src/modules/passkey-setup/index.tsx | 1 + .../src/modules/passkey-setup/utils.ts | 25 +++++++ 6 files changed, 88 insertions(+), 35 deletions(-) create mode 100644 packages/web-app/src/modules/passkey-setup/PasskeyStore.tsx create mode 100644 packages/web-app/src/modules/passkey-setup/utils.ts diff --git a/packages/web-app/src/Store.tsx b/packages/web-app/src/Store.tsx index 5acc5ebb4..3197241a2 100644 --- a/packages/web-app/src/Store.tsx +++ b/packages/web-app/src/Store.tsx @@ -14,6 +14,7 @@ import { ErrorBoundaryStore } from './modules/error-boundary' import { HelpScoutStore } from './modules/helpscout/HelpScoutStore' import { HomeStore } from './modules/home/HomeStore' import { NotificationStore } from './modules/notifications' +import { PasskeyStore } from './modules/passkey-setup' import { ProfileStore } from './modules/profile' import type { Profile } from './modules/profile/models' import { ReferralStore } from './modules/referral' @@ -69,6 +70,7 @@ export class RootStore { public readonly startButtonUI: StartButtonUIStore public readonly saladCard: SaladCardStore public readonly errorBoundary: ErrorBoundaryStore + public readonly passkey: PasskeyStore constructor(axios: AxiosInstance, private readonly featureManager: FeatureManager) { this.routing = new RouterStore() @@ -94,6 +96,7 @@ export class RootStore { this.bonuses = new BonusStore(this, axios) this.startButtonUI = new StartButtonUIStore(this) this.errorBoundary = new ErrorBoundaryStore() + this.passkey = new PasskeyStore() // Pass AnalyticsStore to FeatureManager featureManager.setAnalyticsStore(this.analytics) diff --git a/packages/web-app/src/modules/passkey-setup/PasskeySetupPageContainer.tsx b/packages/web-app/src/modules/passkey-setup/PasskeySetupPageContainer.tsx index 88a045287..e39c31664 100644 --- a/packages/web-app/src/modules/passkey-setup/PasskeySetupPageContainer.tsx +++ b/packages/web-app/src/modules/passkey-setup/PasskeySetupPageContainer.tsx @@ -5,7 +5,7 @@ import { PasskeySetupPage } from './components' const mapStoreToProps = (store: RootStore): any => ({ registerPasskey: () => {}, backToProfile: () => store.routing.push(`/account/summary`), - pendingBonuses: store.bonuses.pendingBonuses, + isPasskeySupported: store.passkey.isPasskeySupported, }) export const PasskeySetupPageContainer = connect(mapStoreToProps, PasskeySetupPage) diff --git a/packages/web-app/src/modules/passkey-setup/PasskeyStore.tsx b/packages/web-app/src/modules/passkey-setup/PasskeyStore.tsx new file mode 100644 index 000000000..3223ebfbc --- /dev/null +++ b/packages/web-app/src/modules/passkey-setup/PasskeyStore.tsx @@ -0,0 +1,26 @@ +import { action, observable, runInAction } from 'mobx' +import { getIsPasskeySupported } from './utils' + +export class PasskeyStore { + @observable + public isPasskeySupported: boolean = false + + constructor() { + this.setIsPasskeySupported() + } + + @action + private async setIsPasskeySupported(): Promise { + try { + const isPasskeySupported = await getIsPasskeySupported() + runInAction(() => { + this.isPasskeySupported = isPasskeySupported + }) + } catch (error) { + console.error('Error checking passkey support:', error) + runInAction(() => { + this.isPasskeySupported = false + }) + } + } +} diff --git a/packages/web-app/src/modules/passkey-setup/components/PasskeySetupPage.tsx b/packages/web-app/src/modules/passkey-setup/components/PasskeySetupPage.tsx index 9dd45a3c5..659a2ee1a 100644 --- a/packages/web-app/src/modules/passkey-setup/components/PasskeySetupPage.tsx +++ b/packages/web-app/src/modules/passkey-setup/components/PasskeySetupPage.tsx @@ -46,44 +46,42 @@ const styles: (theme: SaladTheme) => Record = (theme: Sa }) interface Props extends WithStyles { + isPasskeySupported: boolean backToProfile: () => void registerPasskey: () => void } -const _PasskeySetupPage: FC = ({ backToProfile, registerPasskey, classes }) => { - const isDeviceSupportPasskey = false - return ( - -
-
- - Passkey Setup - - {isDeviceSupportPasskey ? ( - <> - - Your device supports passkeys. Once you click the button below please continue your device’s Passkey - setup flow. Once done you’ll be redirected back to Salad. - -
-
- - ) : ( - <> - - This device does not support Passkeys. Please login on a device or browser that supports passkeys and - try again. - -
- Referrals Background +const _PasskeySetupPage: FC = ({ isPasskeySupported, backToProfile, registerPasskey, classes }) => ( + +
+
+ + Passkey Setup + + {isPasskeySupported ? ( + <> + + Your device supports passkeys. Once you click the button below please continue your device’s Passkey setup + flow. Once done you’ll be redirected back to Salad. + +
+
+ + ) : ( + <> + + This device does not support Passkeys. Please login on a device or browser that supports passkeys and try + again. + +
- - ) -} + Referrals Background +
+
+) export const PasskeySetupPage = withLogin(withStyles(styles)(_PasskeySetupPage)) diff --git a/packages/web-app/src/modules/passkey-setup/index.tsx b/packages/web-app/src/modules/passkey-setup/index.tsx index 972dc375f..db6e80005 100644 --- a/packages/web-app/src/modules/passkey-setup/index.tsx +++ b/packages/web-app/src/modules/passkey-setup/index.tsx @@ -1 +1,2 @@ export * from './PasskeySetupPageContainer' +export * from './PasskeyStore' diff --git a/packages/web-app/src/modules/passkey-setup/utils.ts b/packages/web-app/src/modules/passkey-setup/utils.ts new file mode 100644 index 000000000..8eb3a05ea --- /dev/null +++ b/packages/web-app/src/modules/passkey-setup/utils.ts @@ -0,0 +1,25 @@ +export const getIsPasskeySupported = async (): Promise => { + // Availability of `window.PublicKeyCredential` means WebAuthn is usable. + // `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable. + // `​​isConditionalMediationAvailable` means the feature detection is usable. + if ( + window.PublicKeyCredential && + typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function' && + typeof PublicKeyCredential.isConditionalMediationAvailable === 'function' + ) { + try { + const [isAuthenticatorAvailable, isMediationAvailable] = await Promise.all([ + PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(), + PublicKeyCredential.isConditionalMediationAvailable(), + ]) + + return isAuthenticatorAvailable && isMediationAvailable + } catch (error) { + console.error('Error checking passkey support:', error) + return false + } + } + + return false +} +