Skip to content

Commit

Permalink
fix(passport): safari mobile sign (#1858)
Browse files Browse the repository at this point in the history
* fix(passport): safari sign

wip

wip

wip

Wip

wip

wip

fix

wip

wip

toast

fix: storybook

wip

better error throwS

revert apple change

fix storybook again

wip

another storybook fix

prommpt user to check wallet

wip: more storybook

more storybook

fix disconnect

cleanup

storybook

* continuing state?

* wip: disconnect states

* wip: better disabled
  • Loading branch information
maurerbot authored Mar 6, 2023
1 parent 8410538 commit c5b47e6
Show file tree
Hide file tree
Showing 47 changed files with 724 additions and 231 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@ import circleLogo from './circle-logo.svg'
import subtractLogo from '../../assets/subtract-logo.svg'

import { Text } from '@kubelt/design-system/src/atoms/text/Text'
import { lazy } from 'react'
import { Avatar } from '@kubelt/design-system'

const ConnectButton = lazy(() =>
import('../../../app/components/connect-button/ConnectButton').then(
(module) => ({ default: module.ConnectButton })
)
)

export type AuthenticationProps = {
logoURL?: string
appName?: string
Expand Down
187 changes: 133 additions & 54 deletions apps/passport/app/components/connect-button/ConnectButton.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,88 @@
import { lazy } from 'react'
import { useEffect } from 'react'
import classNames from 'classnames'
import { Button } from '@kubelt/design-system/src/atoms/buttons/Button'
import type { ButtonProps } from '@kubelt/design-system/src/atoms/buttons/Button'

import walletsSvg from './wallets.png'
import { Avatar } from 'connectkit'
import { Spinner } from '@kubelt/design-system/src/atoms/spinner/Spinner'
import { useAccount, useDisconnect, useSignMessage } from 'wagmi'
import { ConnectKitProvider, ConnectKitButton } from 'connectkit'

const ConnectKitProvider = lazy(() =>
import('connectkit').then((module) => ({
default: module.ConnectKitProvider,
}))
)

const CustomConnectKitButton = lazy(() =>
import('connectkit').then((module) => ({
default: module.ConnectKitButton.Custom,
}))
)
import { signMessageTemplate } from '../../routes/connect/$address/sign'

export type ConnectButtonProps = {
connectCallback: (address: string) => void
signCallback: (
address: string,
signature: string,
nonce: string,
state: string
) => void
connectErrorCallback: (error: Error) => void
disabled?: boolean
provider?: string
signData?: {
nonce: string | undefined
state: string | undefined
address: string | undefined
signature: string | undefined
}
isLoading?: boolean
className?: string
} & ButtonProps

export function ConnectButton({
connectCallback,
connectErrorCallback,
signCallback,
isLoading,
className,
signData,
}: ConnectButtonProps) {
const { connector, isConnected, isReconnecting } = useAccount()
const { disconnect } = useDisconnect()
const {
isLoading: isSigning,
error,
status,
signMessage,
signMessageAsync,
reset,
} = useSignMessage({
onSuccess(data, variables) {
console.debug('message signed')
if (!signData?.nonce || !signData?.state || !signData?.address) {
connectErrorCallback(new Error('No signature data present.'))
return
}
console.debug('sign callback')
signCallback(signData.address, data, signData.nonce, signData.state)
},
onError(error) {
console.debug('should sign?', { error, isSigning })
if (error && !isSigning) {
connectErrorCallback(error)
}
},
})

useEffect(() => {
if (!signData?.signature && signData?.nonce) {
console.debug('signing...')
const nonceMessage = signMessageTemplate.replace(
'{{nonce}}',
signData.nonce
)
// sign message
signMessage({ message: nonceMessage })
} else {
console.debug('no sign data')
}
}, [signData, isReconnecting, isConnected, connector, signMessage])

return (
<ConnectKitProvider>
<CustomConnectKitButton>
<ConnectKitButton.Custom>
{({
isConnected,
isConnecting,
Expand All @@ -42,53 +92,82 @@ export function ConnectButton({
truncatedAddress,
ensName,
}) => {
if (isConnected) {
hide!()
}
return (
<Button
btnType="secondary-alt"
className={classNames('button', className)}
disabled={isConnected ? !address : isConnecting}
onClick={
isConnected ? () => address && connectCallback(address) : show
}
style={{
height: 50,
width: '100%',
fontSize: 16,
fontWeight: 500,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{ensName && (
<span className="mr-[7px]">
<Avatar size={20} name={ensName} />
</span>
)}
<>
<Button
btnType="secondary-alt"
className={classNames('button', className)}
disabled={isConnecting || isSigning || isLoading}
onClick={
isConnected ? () => address && connectCallback(address) : show
}
style={{
height: 50,
width: '100%',
fontSize: 16,
fontWeight: 500,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{(isSigning || isLoading) && isConnected ? (
<>
<Spinner />
{isSigning
? 'Signing... (please check wallet)'
: 'Continuing...'}
</>
) : (
<>
{ensName && (
<span className="mr-[7px]">
<Avatar size={20} name={ensName} />
</span>
)}

{!ensName && (
<span
style={{
backgroundRepeat: 'no-repeat',
backgroundSize: '100%',
height: 20,
width: 20,
margin: '0 7px',
}}
>
<img src={walletsSvg} />
</span>
)}

{!ensName && (
<span
style={{
backgroundRepeat: 'no-repeat',
backgroundSize: '100%',
height: 20,
width: 20,
margin: '0 7px',
{isConnected && address
? `Login with ${ensName ?? truncatedAddress}`
: !isConnecting
? 'Connect Wallet'
: 'Connecting'}
</>
)}
</Button>
{isConnected && (
<button
className={
'text-xs text-indigo-400 underline -mt-2 cursor-pointer'
}
onClick={(e) => {
e.preventDefault()
disconnect()
}}
>
<img src={walletsSvg} />
</span>
Disconnect Wallet
</button>
)}

{isConnected && address
? `Login with ${ensName ?? truncatedAddress}`
: !isConnecting
? 'Connect Wallet'
: 'Connecting'}
</Button>
</>
)
}}
</CustomConnectKitButton>
</ConnectKitButton.Custom>
</ConnectKitProvider>
)
}
14 changes: 5 additions & 9 deletions apps/passport/app/routes/authenticate.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { json, LoaderFunction } from '@remix-run/cloudflare'
import type { LoaderFunction } from '@remix-run/cloudflare'
import { json } from '@remix-run/cloudflare'
import { redirect } from '@remix-run/cloudflare'
import { Suspense } from 'react'

import { getUserSession, setConsoleParamsSession } from '~/session.server'

import React from 'react'
import type { CatchBoundaryComponent } from '@remix-run/react/dist/routeModules'
import { useCatch, useOutletContext } from '@remix-run/react'
import { ErrorPage } from '@kubelt/design-system/src/pages/error/ErrorPage'

import { LazyAuth } from '~/web3/lazyAuth'
import sideGraphics from '~/assets/auth-side-graphics.svg'

// TODO: loader function check if we have a session already
Expand Down Expand Up @@ -41,10 +41,6 @@ export const loader: LoaderFunction = async ({ request, context }) => {
return null
}

const LazyAuth = React.lazy(() =>
import('~/web3/lazyAuth').then((module) => ({ default: module.LazyAuth }))
)

export default function Index() {
const context = useOutletContext()

Expand All @@ -58,8 +54,8 @@ export default function Index() {
<img src={sideGraphics} />
</div>
<div className={'basis-full basis-full lg:basis-3/5'}>
<Suspense fallback={/*Show some spinner*/ ''}>
<LazyAuth context={context} />
<Suspense fallback={''}>
<LazyAuth context={context} autoConnect={true} />
</Suspense>
</div>
</div>
Expand Down
85 changes: 76 additions & 9 deletions apps/passport/app/routes/authenticate/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { Form, useOutletContext, useTransition } from '@remix-run/react'
import { useState } from 'react'
import {
Form,
useOutletContext,
useSubmit,
useTransition,
} from '@remix-run/react'
import { useEffect, useState } from 'react'
import { Authentication, ConnectButton } from '~/components'
import ConnectOAuthButton from '~/components/connect-oauth-button'
import { Text } from '@kubelt/design-system/src/atoms/text/Text'
import { Loader } from '@kubelt/design-system/src/molecules/loader/Loader'
import { toast, ToastType } from '@kubelt/design-system/src/atoms/toast'

export default function Authenticate() {
const [enableWalletConnect, setEnableWalletConnect] = useState(true)

const [signData, setSignData] = useState<{
nonce: string | undefined
state: string | undefined
address: string | undefined
signature: string | undefined
}>({
nonce: undefined,
state: undefined,
address: undefined,
signature: undefined,
})
const [loading, setLoading] = useState(false)
const context = useOutletContext<{
appProps?: {
name: string
Expand All @@ -19,6 +35,13 @@ export default function Authenticate() {
const iconURL = context.appProps?.iconURL

const transition = useTransition()
const submit = useSubmit()

useEffect(() => {
if (transition.state === 'idle') {
setLoading(false)
}
}, [transition.state])

return (
<>
Expand All @@ -27,14 +50,58 @@ export default function Authenticate() {
<Authentication logoURL={iconURL} appName={name}>
<>
<ConnectButton
disabled={!enableWalletConnect}
signData={signData}
isLoading={loading}
connectCallback={async (address) => {
window.location.href = `/connect/${address}/sign`
if (loading) return
// fetch nonce and kickoff sign flow
setLoading(true)
fetch(`/connect/${address}/sign`) // NOTE: note using fetch because it messes with wagmi state
.then((res) =>
res.json<{ nonce: string; state: string; address: string }>()
)
.then(({ nonce, state, address }) => {
setSignData({
nonce,
state,
address,
signature: undefined,
})
})
.catch((err) => {
toast(ToastType.Error, {
message:
'Could not fetch nonce for signing authentication message',
})
})
}}
signCallback={(address, signature, nonce, state) => {
console.debug('signing complete')
setSignData({
...signData,
signature,
})
submit(
{ signature, nonce, state },
{
method: 'post',
action: `/connect/${address}/sign`,
}
)
}}
connectErrorCallback={(error) => {
console.error(error)
alert('Error connecting to wallet')
setEnableWalletConnect(false)
console.debug('transition.state: ', transition.state)
if (transition.state !== 'idle' || !loading) {
return
}
if (error) {
console.error(error)
toast(ToastType.Error, {
message:
'Failed to complete signing. Please try again or contact support.',
})
setLoading(false)
}
}}
/>
<div className="my-5 flex flex-row items-center space-x-3">
Expand Down
Loading

0 comments on commit c5b47e6

Please sign in to comment.