Skip to content

Commit

Permalink
[WebApp] Add passkey support logic (#1162)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maks19 authored Aug 1, 2024
1 parent 36060e7 commit 1cbb17f
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 35 deletions.
3 changes: 3 additions & 0 deletions packages/web-app/src/Store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
26 changes: 26 additions & 0 deletions packages/web-app/src/modules/passkey-setup/PasskeyStore.tsx
Original file line number Diff line number Diff line change
@@ -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<void> {
try {
const isPasskeySupported = await getIsPasskeySupported()
runInAction(() => {
this.isPasskeySupported = isPasskeySupported
})
} catch (error) {
console.error('Error checking passkey support:', error)
runInAction(() => {
this.isPasskeySupported = false
})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,44 +46,42 @@ const styles: (theme: SaladTheme) => Record<string, CSS.Properties> = (theme: Sa
})

interface Props extends WithStyles<typeof styles> {
isPasskeySupported: boolean
backToProfile: () => void
registerPasskey: () => void
}

const _PasskeySetupPage: FC<Props> = ({ backToProfile, registerPasskey, classes }) => {
const isDeviceSupportPasskey = false
return (
<Scrollbars>
<div className={classes.container}>
<div className={classes.textContainer}>
<Text className={classes.header} as="h1" variant="headline">
Passkey Setup
</Text>
{isDeviceSupportPasskey ? (
<>
<Text className={classes.description} variant="baseL">
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.
</Text>
<div className={classes.buttonContainer}>
<Button leadingIcon={<Key />} variant="primary-basic" label="Add Passkey" onClick={registerPasskey} />
<Button variant="outlined" label="Cancel" onClick={backToProfile} />
</div>
</>
) : (
<>
<Text className={classes.description} variant="baseL">
This device does not support Passkeys. Please login on a device or browser that supports passkeys and
try again.
</Text>
<Button variant="primary-basic" label="Back to Profile" onClick={backToProfile} />
</>
)}
</div>
<img className={classes.image} src={Referrals} alt="Referrals Background" />
const _PasskeySetupPage: FC<Props> = ({ isPasskeySupported, backToProfile, registerPasskey, classes }) => (
<Scrollbars>
<div className={classes.container}>
<div className={classes.textContainer}>
<Text className={classes.header} as="h1" variant="headline">
Passkey Setup
</Text>
{isPasskeySupported ? (
<>
<Text className={classes.description} variant="baseL">
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.
</Text>
<div className={classes.buttonContainer}>
<Button leadingIcon={<Key />} variant="primary-basic" label="Add Passkey" onClick={registerPasskey} />
<Button variant="outlined" label="Cancel" onClick={backToProfile} />
</div>
</>
) : (
<>
<Text className={classes.description} variant="baseL">
This device does not support Passkeys. Please login on a device or browser that supports passkeys and try
again.
</Text>
<Button variant="primary-basic" label="Back to Profile" onClick={backToProfile} />
</>
)}
</div>
</Scrollbars>
)
}
<img className={classes.image} src={Referrals} alt="Referrals Background" />
</div>
</Scrollbars>
)

export const PasskeySetupPage = withLogin(withStyles(styles)(_PasskeySetupPage))
1 change: 1 addition & 0 deletions packages/web-app/src/modules/passkey-setup/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './PasskeySetupPageContainer'
export * from './PasskeyStore'
25 changes: 25 additions & 0 deletions packages/web-app/src/modules/passkey-setup/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const getIsPasskeySupported = async (): Promise<boolean> => {
// 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
}

0 comments on commit 1cbb17f

Please sign in to comment.