From 0cf34f615c8681f67acfee72dbbafe82fb6f4f08 Mon Sep 17 00:00:00 2001 From: Jaume Alavedra Date: Thu, 13 Jun 2024 21:24:41 +0200 Subject: [PATCH] chore: update sample with openfort provider --- .../EvmProvider/EvmProviderButton.tsx | 7 +- .../src/components/NFT/MintNFTButton.tsx | 8 +- .../Signatures/SignMessageButton.tsx | 12 +- .../Signatures/SignTypedDataButton.tsx | 12 +- .../src/components/User/GetUserButton.tsx | 8 +- .../apps/auth-sample/src/hooks/useOpenfort.ts | 150 ------------ .../auth-sample/src/hooks/useOpenfort.tsx | 228 ++++++++++++++++++ examples/apps/auth-sample/src/pages/_app.tsx | 11 +- examples/apps/auth-sample/src/pages/index.tsx | 10 +- examples/apps/auth-sample/src/pages/login.tsx | 34 +-- examples/apps/auth-sample/src/utils/getUrl.js | 2 +- yarn.lock | 29 ++- 12 files changed, 302 insertions(+), 209 deletions(-) delete mode 100644 examples/apps/auth-sample/src/hooks/useOpenfort.ts create mode 100644 examples/apps/auth-sample/src/hooks/useOpenfort.tsx diff --git a/examples/apps/auth-sample/src/components/EvmProvider/EvmProviderButton.tsx b/examples/apps/auth-sample/src/components/EvmProvider/EvmProviderButton.tsx index b61a53c..c569c28 100644 --- a/examples/apps/auth-sample/src/components/EvmProvider/EvmProviderButton.tsx +++ b/examples/apps/auth-sample/src/components/EvmProvider/EvmProviderButton.tsx @@ -7,7 +7,7 @@ import Loading from '../Loading'; const Provider1193ActionButton: React.FC<{ handleSetMessage: (message: string) => void; }> = ({handleSetMessage}) => { - const {getEvmProvider, embeddedState, error} = useOpenfort(); + const {getEvmProvider, state} = useOpenfort(); const [loading, setLoading] = useState(false); const handleSendTransaction = async () => { @@ -49,14 +49,11 @@ const Provider1193ActionButton: React.FC<{
- {error && ( -

{`Error: ${error.message}`}

- )}
); }; diff --git a/examples/apps/auth-sample/src/components/NFT/MintNFTButton.tsx b/examples/apps/auth-sample/src/components/NFT/MintNFTButton.tsx index f5a9ab1..132f505 100644 --- a/examples/apps/auth-sample/src/components/NFT/MintNFTButton.tsx +++ b/examples/apps/auth-sample/src/components/NFT/MintNFTButton.tsx @@ -6,7 +6,7 @@ import Loading from '../Loading'; const MintNFTButton: React.FC<{ handleSetMessage: (message: string) => void; }> = ({handleSetMessage}) => { - const {mintNFT, embeddedState, error} = useOpenfort(); + const {mintNFT, state} = useOpenfort(); const [loading, setLoading] = useState(false); const handleMintNFT = async () => { @@ -29,15 +29,11 @@ const MintNFTButton: React.FC<{
- - {error && ( -

{`Error: ${error.message}`}

- )}
); }; diff --git a/examples/apps/auth-sample/src/components/Signatures/SignMessageButton.tsx b/examples/apps/auth-sample/src/components/Signatures/SignMessageButton.tsx index 2a54374..1593d4b 100644 --- a/examples/apps/auth-sample/src/components/Signatures/SignMessageButton.tsx +++ b/examples/apps/auth-sample/src/components/Signatures/SignMessageButton.tsx @@ -6,17 +6,17 @@ import Loading from '../Loading'; const SignMessageButton: React.FC<{ handleSetMessage: (message: string) => void; }> = ({handleSetMessage}) => { - const {signMessage, embeddedState, error} = useOpenfort(); + const {signMessage, state} = useOpenfort(); const [loading, setLoading] = useState(false); const handleSignMessage = async () => { try { setLoading(true); const signature = await signMessage('Hello World!'); setLoading(false); - if (!signature) { + if (signature.error) { throw new Error('Failed to sign message'); } - handleSetMessage(signature); + handleSetMessage(signature.data!); } catch (err) { // Handle errors from minting process console.error('Failed to sign message:', err); @@ -28,15 +28,11 @@ const SignMessageButton: React.FC<{
- - {error && ( -

{`Error: ${error.message}`}

- )}
); }; diff --git a/examples/apps/auth-sample/src/components/Signatures/SignTypedDataButton.tsx b/examples/apps/auth-sample/src/components/Signatures/SignTypedDataButton.tsx index 74408c2..44334a1 100644 --- a/examples/apps/auth-sample/src/components/Signatures/SignTypedDataButton.tsx +++ b/examples/apps/auth-sample/src/components/Signatures/SignTypedDataButton.tsx @@ -6,7 +6,7 @@ import Loading from '../Loading'; const SignTypedDataButton: React.FC<{ handleSetMessage: (message: string) => void; }> = ({handleSetMessage}) => { - const {signTypedData, embeddedState, error} = useOpenfort(); + const {signTypedData, state} = useOpenfort(); const [loading, setLoading] = useState(false); const handleSignTypedData = async () => { try { @@ -41,10 +41,10 @@ const SignTypedDataButton: React.FC<{ }; const signature = await signTypedData(domain, types, data); setLoading(false); - if (!signature) { + if (signature.error) { throw new Error('Failed to sign message'); } - handleSetMessage(signature); + handleSetMessage(signature.data!); } catch (err) { // Handle errors from minting process console.error('Failed to sign message:', err); @@ -56,15 +56,11 @@ const SignTypedDataButton: React.FC<{
- - {error && ( -

{`Error: ${error.message}`}

- )}
); }; diff --git a/examples/apps/auth-sample/src/components/User/GetUserButton.tsx b/examples/apps/auth-sample/src/components/User/GetUserButton.tsx index 99de826..3f8d5bb 100644 --- a/examples/apps/auth-sample/src/components/User/GetUserButton.tsx +++ b/examples/apps/auth-sample/src/components/User/GetUserButton.tsx @@ -1,16 +1,18 @@ import React, {useState} from 'react'; -import {useOpenfort} from '../../hooks/useOpenfort'; import Loading from '../Loading'; +import openfort from '../../utils/openfortConfig'; const GetUserButton: React.FC<{ handleSetMessage: (message: string) => void; }> = ({handleSetMessage}) => { - const {getUser} = useOpenfort(); const [loading, setLoading] = useState(false); + const handleUserMessage = async () => { try { setLoading(true); - const user = await getUser(); + const user = await openfort.getUser().catch((error: Error) => { + console.log('error', error); + }); setLoading(false); if (!user) { throw new Error('Failed to get user'); diff --git a/examples/apps/auth-sample/src/hooks/useOpenfort.ts b/examples/apps/auth-sample/src/hooks/useOpenfort.ts deleted file mode 100644 index d184920..0000000 --- a/examples/apps/auth-sample/src/hooks/useOpenfort.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { useState, useCallback, useEffect, useRef } from 'react'; -import { AuthType, EmbeddedState, Provider, ShieldAuthentication, TypedDataDomain, TypedDataField } from '@openfort/openfort-js'; -import openfort from '../utils/openfortConfig'; - -export const useOpenfort = () => { - const [error, setError] = useState(null); - const [embeddedState, setEmbeddedState] = useState(EmbeddedState.NONE); - const pollingRef = useRef(null); - - useEffect(() => { - const pollEmbeddedState = async () => { - try { - const state = await openfort.getEmbeddedState(); - setEmbeddedState(state); - } catch (error) { - console.error('Error checking embedded state with Openfort:', error); - if (pollingRef.current) clearInterval(pollingRef.current); - } - }; - - if (!pollingRef.current) { - pollingRef.current = setInterval(pollEmbeddedState, 300); - } - - return () => { - if (pollingRef.current) clearInterval(pollingRef.current); - pollingRef.current = null; - }; - }, []); - - const getEvmProvider = useCallback((): Provider | null => { - try { - const externalProvider = openfort.getEthereumProvider({announceProvider:true,policy: "pol_e7491b89-528e-40bb-b3c2-9d40afa4fefc"}); - if (!externalProvider) { - throw new Error('EVM provider is undefined'); - } - return externalProvider - } catch (error) { - setError(error instanceof Error ? error : new Error('An error occurred getting EVM provider')); - return null - } - }, []); - - const mintNFT = useCallback(async (): Promise => { - try { - const collectResponse = await fetch(`/api/protected-collect`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${openfort.getAccessToken()}`, - }, - }); - - if (!collectResponse.ok) { - alert("Failed to mint NFT status: " + collectResponse.status); - return null - } - const collectResponseJSON = await collectResponse.json(); - - if (collectResponseJSON.data?.nextAction) { - const response = await openfort.sendSignatureTransactionIntentRequest( - collectResponseJSON.data.id, - collectResponseJSON.data.nextAction.payload.userOperationHash - ); - return response.response?.transactionHash ?? null - }else return null - } catch (error) { - console.error('Error minting NFT with Openfort:', error); - setError(error instanceof Error ? error : new Error('An error occurred minting the NFT')); - return null; - } - }, []); - - const signMessage = useCallback(async (message: string): Promise => { - try { - return await openfort.signMessage(message); - } catch (error) { - console.error('Error signing message:', error); - setError(error instanceof Error ? error : new Error('An error occurred signing the message')); - return null; - } - }, []); - - const signTypedData = useCallback(async (domain: TypedDataDomain, types: Record>, value: Record): Promise => { - try { - return await openfort.signTypedData(domain, types, value); - } catch (error) { - console.error('Error signing message:', error); - setError(error instanceof Error ? error : new Error('An error occurred signing the message')); - return null; - } - }, []); - - - const handleRecovery = useCallback(async (method: "password"|"automatic", pin?: string) => { - try { - const chainId = 80002; - const shieldAuth: ShieldAuthentication = { - auth: AuthType.OPENFORT, - token: openfort.getAccessToken()!, - }; - if(method==="automatic"){ - - await openfort.configureEmbeddedSigner(chainId, shieldAuth) - } else if(method==="password"){ - if (!pin || pin.length < 4) { - alert("Password recovery must be at least 4 characters"); - return; - } - await openfort.configureEmbeddedSigner(chainId, shieldAuth, pin); - } - } catch (error) { - console.error('Error handling recovery with Openfort:', error); - setError(error instanceof Error ? error : new Error('An error occurred during recovery handling')); - } - }, []); - - const logout = useCallback(async () => { - try { - await openfort.logout(); - } catch (error) { - console.error('Error logging out with Openfort:', error); - setError(error instanceof Error ? error : new Error('An error occurred during logout')); - } - }, []); - - const getUser = useCallback(async () => { - try { - return await openfort.getUser(); - } catch (error) { - console.error('Error getting user:', error); - setError(error instanceof Error ? error : new Error('An error occurred getting the user')); - return null; - } - } ,[]); - - - - return { - embeddedState, - mintNFT, - signMessage, - getEvmProvider, - signTypedData, - handleRecovery, - error, - logout, - getUser - } -}; diff --git a/examples/apps/auth-sample/src/hooks/useOpenfort.tsx b/examples/apps/auth-sample/src/hooks/useOpenfort.tsx new file mode 100644 index 0000000..ba9cb04 --- /dev/null +++ b/examples/apps/auth-sample/src/hooks/useOpenfort.tsx @@ -0,0 +1,228 @@ +import { + AuthType, + EmbeddedState, + ThirdPartyOAuthProvider, + TokenType, +} from '@openfort/openfort-js'; + +import type { + AuthPlayerResponse, + Provider, + ShieldAuthentication, +} from '@openfort/openfort-js'; +import React, { + type PropsWithChildren, + createContext, + useCallback, + useEffect, + useRef, + useState, + useContext, +} from 'react'; +import openfort from '../utils/openfortConfig'; +import {TypedDataDomain, TypedDataField} from 'ethers'; + +type ContextType = { + state: EmbeddedState; + getEvmProvider: () => Provider; + handleRecovery: ( + method: 'password' | 'automatic', + pin?: string + ) => Promise; + auth: (accessToken: string) => Promise; + signMessage: ( + hashedMessage: string + ) => Promise<{data?: string; error?: Error}>; + mintNFT: () => Promise; + signTypedData: ( + domain: TypedDataDomain, + types: Record>, + value: Record + ) => Promise<{data?: string; error?: Error}>; + logout: () => Promise; +}; + +const OpenfortContext = createContext(null); + +const useOpenfort = () => { + const context = useContext(OpenfortContext); + + if (!context) { + throw new Error('useOpenfort must be used inside the OpenfortProvider'); + } + + return context; +}; + +const OpenfortProvider = ({children}: PropsWithChildren) => { + const [state, setState] = useState(EmbeddedState.NONE); + const poller = useRef(null); + + useEffect(() => { + const pollEmbeddedState = async () => { + try { + const currentState = await openfort.getEmbeddedState(); + setState(currentState); + } catch (err) { + console.error('Error checking embedded state with Openfort:', err); + if (poller.current) clearInterval(poller.current); + } + }; + + if (!poller.current) { + poller.current = setInterval(pollEmbeddedState, 300); + } + + return () => { + if (poller.current) clearInterval(poller.current); + poller.current = null; + }; + }, []); + + const getEvmProvider = useCallback((): Provider => { + const externalProvider = openfort.getEthereumProvider({ + announceProvider: true, + policy: 'pol_e7491b89-528e-40bb-b3c2-9d40afa4fefc', + }); + if (!externalProvider) { + throw new Error('EVM provider is undefined'); + } + return externalProvider as Provider; + }, []); + + const auth = useCallback( + async (accessToken: string): Promise => { + try { + return await openfort.authenticateWithThirdPartyProvider({ + provider: ThirdPartyOAuthProvider.SUPABASE, + token: accessToken, + tokenType: TokenType.CUSTOM_TOKEN, + }); + } catch (err) { + console.error('Error authenticating:', err); + throw err; + } + }, + [] + ); + + const mintNFT = useCallback(async (): Promise => { + const collectResponse = await fetch(`/api/protected-collect`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${openfort.getAccessToken()}`, + }, + }); + + if (!collectResponse.ok) { + alert('Failed to mint NFT status: ' + collectResponse.status); + throw new Error('Failed to perform API call'); + } + const collectResponseJSON = await collectResponse.json(); + + if (collectResponseJSON.data?.nextAction) { + const response = await openfort.sendSignatureTransactionIntentRequest( + collectResponseJSON.data.id, + collectResponseJSON.data.nextAction.payload.userOperationHash + ); + return response.response?.transactionHash; + } else { + return collectResponseJSON.response?.transactionHash; + } + }, []); + + const signMessage = useCallback( + async (message: string): Promise<{data?: string; error?: Error}> => { + try { + const data = await openfort.signMessage(message); + return {data: data}; + } catch (err) { + console.log('Error signing message:', err); + return { + error: + err instanceof Error + ? err + : new Error('An error occurred signing the message'), + }; + } + }, + [] + ); + + const signTypedData = useCallback( + async ( + domain: TypedDataDomain, + types: Record>, + value: Record + ): Promise<{data?: string; error?: Error}> => { + try { + return {data: await openfort.signTypedData(domain, types, value)}; + } catch (err) { + console.error('Error signing typed data:', err); + return { + error: + err instanceof Error + ? err + : new Error('An error occurred signing the typed data'), + }; + } + }, + [] + ); + + const handleRecovery = useCallback( + async (method: 'password' | 'automatic', pin?: string) => { + try { + const chainId = 80002; + const shieldAuth: ShieldAuthentication = { + auth: AuthType.OPENFORT, + token: openfort.getAccessToken()!, + }; + if (method === 'automatic') { + await openfort.configureEmbeddedSigner(chainId, shieldAuth); + } else if (method === 'password') { + if (!pin || pin.length < 4) { + alert('Password recovery must be at least 4 characters'); + return; + } + await openfort.configureEmbeddedSigner(chainId, shieldAuth, pin); + } + } catch (err) { + console.error('Error handling recovery with Openfort:', err); + err instanceof Error + ? err + : new Error('An error occurred during recovery handling'); + } + }, + [] + ); + + const logout = useCallback(async () => { + try { + await openfort.logout(); + } catch (err) { + console.error('Error logging out with Openfort:', err); + err instanceof Error ? err : new Error('An error occurred during logout'); + } + }, []); + + return ( + + {children} + + ); +}; + +export {OpenfortProvider, useOpenfort}; diff --git a/examples/apps/auth-sample/src/pages/_app.tsx b/examples/apps/auth-sample/src/pages/_app.tsx index 3d9be91..655e086 100644 --- a/examples/apps/auth-sample/src/pages/_app.tsx +++ b/examples/apps/auth-sample/src/pages/_app.tsx @@ -2,13 +2,16 @@ import '../styles/tailwind.css'; import type {AppProps} from 'next/app'; import * as ToastPrimitive from '@radix-ui/react-toast'; +import {OpenfortProvider} from '../hooks/useOpenfort'; function MyApp({Component, pageProps}: AppProps) { return ( - - - - + + + + + + ); } diff --git a/examples/apps/auth-sample/src/pages/index.tsx b/examples/apps/auth-sample/src/pages/index.tsx index 51e2741..3f704d2 100644 --- a/examples/apps/auth-sample/src/pages/index.tsx +++ b/examples/apps/auth-sample/src/pages/index.tsx @@ -18,7 +18,7 @@ import {Logo} from '../components/Logo'; import GetUserButton from '../components/User/GetUserButton'; const HomePage: NextPage = () => { - const {embeddedState} = useOpenfort(); + const {state} = useOpenfort(); const [user, setUser] = useState(null); const router = useRouter(); const [message, setMessage] = useState(''); @@ -37,9 +37,9 @@ const HomePage: NextPage = () => { if (sessionData) setUser(sessionData); else router.push('/login'); }; - if (embeddedState === EmbeddedState.UNAUTHENTICATED) router.push('/login'); + if (state === EmbeddedState.UNAUTHENTICATED) router.push('/login'); fetchUser(); - }, [openfort, embeddedState]); + }, [openfort, state]); useEffect(() => { if (textareaRef.current) { @@ -61,7 +61,7 @@ const HomePage: NextPage = () => { } }, [linkedAccount, router]); - if (embeddedState === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) { + if (state === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) { return ( }>
@@ -78,7 +78,7 @@ const HomePage: NextPage = () => { ); } - if (embeddedState !== EmbeddedState.READY) { + if (state !== EmbeddedState.READY) { return (
diff --git a/examples/apps/auth-sample/src/pages/login.tsx b/examples/apps/auth-sample/src/pages/login.tsx index 3459687..f5dbf51 100644 --- a/examples/apps/auth-sample/src/pages/login.tsx +++ b/examples/apps/auth-sample/src/pages/login.tsx @@ -29,31 +29,31 @@ function LoginPage() { useEffect(() => { if (router.query.access_token && router.query.refresh_token) { - console.log('router.query', router.query); + setStatus({ + type: 'loading', + title: 'Signing in...', + }); openfort.storeCredentials({ player: undefined, - accessToken: router.query.access_token as string, - refreshToken: router.query.refresh_token as string, + accessToken: router.query.access_token, + refreshToken: router.query.refresh_token, }); - router.push('/'); + location.href = '/'; } }, [router.query]); + useEffect(() => { - try { - const loadData = async () => { - setStatus({ - type: 'loading', - title: 'Signing in...', - }); + const loadData = async () => { + setStatus({ + type: 'loading', + title: 'Signing in...', + }); - location.href = '/'; - }; + router.push('/'); + }; - if (user) loadData(); - } catch (error) { - // console.log('error', error) - } - }, [user, openfort]); + if (user) loadData(); + }, [user]); const handleSubmit = async (event: React.FormEvent) => { setStatus({ diff --git a/examples/apps/auth-sample/src/utils/getUrl.js b/examples/apps/auth-sample/src/utils/getUrl.js index 7bb46c8..0c70319 100644 --- a/examples/apps/auth-sample/src/utils/getUrl.js +++ b/examples/apps/auth-sample/src/utils/getUrl.js @@ -2,7 +2,7 @@ export const getURL = () => { let url = process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env. process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel. - 'http://localhost:3001'; + 'http://localhost:3000'; // Make sure to include `https://` when not localhost. url = url.includes('http') ? url : `https://${url}`; // Make sure to including trailing `/`. diff --git a/yarn.lock b/yarn.lock index 1ec728a..b7a6d88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1261,7 +1261,32 @@ __metadata: languageName: unknown linkType: soft -"@openfort/openfort-js@0.7.8, @openfort/openfort-js@workspace:sdk": +"@openfort/openfort-js@npm:0.7.8": + version: 0.7.8 + resolution: "@openfort/openfort-js@npm:0.7.8" + dependencies: + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/providers": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@noble/curves": ^1.1.0 + "@openfort/shield-js": ^0.0.3 + axios: 1.6.7 + buffer: ^6.0.3 + crypto-browserify: ^3.12.0 + crypto-js: ^4.2.0 + es6-promise: ^4.2.8 + jose: ^5.2.2 + path: ^0.12.7 + stream-browserify: ^3.0.0 + uuid: ^8.3.2 + checksum: a9049252b5934d95272e3bf841b63cddf7dbbdf9aea48dc7f0971aeddad9571df157409f4622860d12985a105dcf5f74ddf26fe316fcecc79f43ebb5f924f300 + languageName: node + linkType: hard + +"@openfort/openfort-js@workspace:sdk": version: 0.0.0-use.local resolution: "@openfort/openfort-js@workspace:sdk" dependencies: @@ -3562,7 +3587,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.5.0||^6.0.0": +"buffer@npm:^5.5.0||^6.0.0, buffer@npm:^6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" dependencies: