diff --git a/.changeset/cuddly-guests-hug.md b/.changeset/cuddly-guests-hug.md new file mode 100644 index 00000000..ea4be080 --- /dev/null +++ b/.changeset/cuddly-guests-hug.md @@ -0,0 +1,7 @@ +--- +"@graphprotocol/hypergraph-react": patch +"create-hypergraph": patch +--- + +improve processConnectAuthSuccess behaviour + \ No newline at end of file diff --git a/apps/events/src/routes/authenticate-success.tsx b/apps/events/src/routes/authenticate-success.tsx index b546efec..dc21be2b 100644 --- a/apps/events/src/routes/authenticate-success.tsx +++ b/apps/events/src/routes/authenticate-success.tsx @@ -1,6 +1,6 @@ import { useHypergraphApp } from '@graphprotocol/hypergraph-react'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; export const Route = createFileRoute('/authenticate-success')({ component: RouteComponent, @@ -16,11 +16,17 @@ function RouteComponent() { const { ciphertext, nonce } = Route.useSearch(); const { processConnectAuthSuccess } = useHypergraphApp(); const navigate = useNavigate(); + const isProcessingRef = useRef(false); useEffect(() => { - processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); - console.log('redirecting to /'); - navigate({ to: '/', replace: true }); + if (isProcessingRef.current) return; // prevent multiple calls from useEffect double calling in StrictMode + const result = processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); + if (result.success) { + isProcessingRef.current = true; + navigate({ to: '/', replace: true }); + } else { + alert(result.error); + } }, [ciphertext, nonce, processConnectAuthSuccess, navigate]); return
Authenticating …
; diff --git a/apps/privy-login-example/src/routes/authenticate-success.tsx b/apps/privy-login-example/src/routes/authenticate-success.tsx index b546efec..dc21be2b 100644 --- a/apps/privy-login-example/src/routes/authenticate-success.tsx +++ b/apps/privy-login-example/src/routes/authenticate-success.tsx @@ -1,6 +1,6 @@ import { useHypergraphApp } from '@graphprotocol/hypergraph-react'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; export const Route = createFileRoute('/authenticate-success')({ component: RouteComponent, @@ -16,11 +16,17 @@ function RouteComponent() { const { ciphertext, nonce } = Route.useSearch(); const { processConnectAuthSuccess } = useHypergraphApp(); const navigate = useNavigate(); + const isProcessingRef = useRef(false); useEffect(() => { - processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); - console.log('redirecting to /'); - navigate({ to: '/', replace: true }); + if (isProcessingRef.current) return; // prevent multiple calls from useEffect double calling in StrictMode + const result = processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); + if (result.success) { + isProcessingRef.current = true; + navigate({ to: '/', replace: true }); + } else { + alert(result.error); + } }, [ciphertext, nonce, processConnectAuthSuccess, navigate]); return
Authenticating …
; diff --git a/apps/template-nextjs/Components/Login/AuthCallback.tsx b/apps/template-nextjs/Components/Login/AuthCallback.tsx index adde8ec4..a76c2e41 100644 --- a/apps/template-nextjs/Components/Login/AuthCallback.tsx +++ b/apps/template-nextjs/Components/Login/AuthCallback.tsx @@ -2,7 +2,7 @@ import { useHypergraphApp } from '@graphprotocol/hypergraph-react'; import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; export type AuthCallbackProps = { ciphertext: string; @@ -11,10 +11,17 @@ export type AuthCallbackProps = { export function AuthCallback({ ciphertext, nonce }: Readonly) { const router = useRouter(); const { processConnectAuthSuccess } = useHypergraphApp(); + const isProcessingRef = useRef(false); useEffect(() => { - processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); - router.replace('/'); + if (isProcessingRef.current) return; // prevent multiple calls from useEffect double calling in StrictMode + const result = processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); + if (result.success) { + isProcessingRef.current = true; + router.replace('/'); + } else { + alert(result.error); + } }, [ciphertext, nonce, processConnectAuthSuccess, router]); return
Authenticating …
; diff --git a/apps/template-vite-react/src/routes/authenticate-success.tsx b/apps/template-vite-react/src/routes/authenticate-success.tsx index b546efec..dc21be2b 100644 --- a/apps/template-vite-react/src/routes/authenticate-success.tsx +++ b/apps/template-vite-react/src/routes/authenticate-success.tsx @@ -1,6 +1,6 @@ import { useHypergraphApp } from '@graphprotocol/hypergraph-react'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; export const Route = createFileRoute('/authenticate-success')({ component: RouteComponent, @@ -16,11 +16,17 @@ function RouteComponent() { const { ciphertext, nonce } = Route.useSearch(); const { processConnectAuthSuccess } = useHypergraphApp(); const navigate = useNavigate(); + const isProcessingRef = useRef(false); useEffect(() => { - processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); - console.log('redirecting to /'); - navigate({ to: '/', replace: true }); + if (isProcessingRef.current) return; // prevent multiple calls from useEffect double calling in StrictMode + const result = processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); + if (result.success) { + isProcessingRef.current = true; + navigate({ to: '/', replace: true }); + } else { + alert(result.error); + } }, [ciphertext, nonce, processConnectAuthSuccess, navigate]); return
Authenticating …
; diff --git a/docs/docs/authentication.md b/docs/docs/authentication.md index d31beece..a48212f7 100644 --- a/docs/docs/authentication.md +++ b/docs/docs/authentication.md @@ -126,11 +126,17 @@ function RouteComponent() { const { ciphertext, nonce } = Route.useSearch(); // get the ciphertext and nonce from the URL const { processConnectAuthSuccess } = useHypergraphApp(); const navigate = useNavigate(); + const isProcessingRef = useRef(false); useEffect(() => { - processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); - console.log("redirecting to /"); - navigate({ to: "/", replace: true }); + if (isProcessingRef.current) return; // prevent multiple calls from useEffect double calling in StrictMode + const result = processConnectAuthSuccess({ storage: localStorage, ciphertext, nonce }); + if (result.success) { + isProcessingRef.current = true; + navigate({ to: '/', replace: true }); + } else { + alert(result.error); + } }, [ciphertext, nonce, processConnectAuthSuccess, navigate]); return
Authenticating …
; diff --git a/packages/hypergraph-react/src/HypergraphAppContext.tsx b/packages/hypergraph-react/src/HypergraphAppContext.tsx index cfd5161a..e50eb65e 100644 --- a/packages/hypergraph-react/src/HypergraphAppContext.tsx +++ b/packages/hypergraph-react/src/HypergraphAppContext.tsx @@ -115,7 +115,15 @@ export type HypergraphAppCtx = { connectUrl: string; redirectFn: (url: URL) => void; }): void; - processConnectAuthSuccess(params: { storage: Identity.Storage; ciphertext: string; nonce: string }): void; + processConnectAuthSuccess(params: { storage: Identity.Storage; ciphertext: string; nonce: string }): + | { + success: true; + identity: Connect.PrivateAppIdentity; + } + | { + success: false; + error: string; + }; syncServerUri: string; }; @@ -250,9 +258,6 @@ export function HypergraphAppProvider({ const identity = useSelectorStore(store, (state) => state.context.identity); const privyIdentity = useSelectorStore(store, (state) => state.context.privyIdentity); - console.log('identity', identity); - console.log('privyIdentity', privyIdentity); - const logout = useCallback(() => { websocketConnection?.close(); setWebsocketConnection(undefined); @@ -1464,15 +1469,26 @@ export function HypergraphAppProvider({ ); const processConnectAuthSuccessForContext = useCallback( - (params: { storage: Identity.Storage; ciphertext: string; nonce: string }) => { + (params: { + storage: Identity.Storage; + ciphertext: string; + nonce: string; + }): + | { + success: true; + identity: Connect.PrivateAppIdentity; + } + | { + success: false; + error: string; + } => { const { storage, ciphertext, nonce } = params; const storedNonce = storage.getItem('geo-connect-auth-nonce'); const storedExpiry = Number.parseInt(storage.getItem('geo-connect-auth-expiry') ?? '0', 10); const storedSecretKey = storage.getItem('geo-connect-auth-secret-key'); const storedPublicKey = storage.getItem('geo-connect-auth-public-key'); if (!storedNonce || !storedExpiry || !storedSecretKey || !storedPublicKey) { - alert('Failed to authenticate due missing data in the local storage'); - return; + return { success: false, error: 'Failed to authenticate due missing data in the local storage' }; } try { @@ -1487,7 +1503,7 @@ export function HypergraphAppProvider({ }), ); - setIdentity({ + const identity: Connect.PrivateAppIdentity = { address: parsedAuthParams.appIdentityAddress, addressPrivateKey: parsedAuthParams.appIdentityAddressPrivateKey, accountAddress: parsedAuthParams.accountAddress, @@ -1498,14 +1514,17 @@ export function HypergraphAppProvider({ encryptionPrivateKey: parsedAuthParams.encryptionPrivateKey, sessionToken: parsedAuthParams.sessionToken, sessionTokenExpires: parsedAuthParams.sessionTokenExpires, - }); + }; + + setIdentity(identity); storage.removeItem('geo-connect-auth-nonce'); storage.removeItem('geo-connect-auth-expiry'); storage.removeItem('geo-connect-auth-secret-key'); storage.removeItem('geo-connect-auth-public-key'); + return { success: true, identity }; } catch (error) { console.error(error); - alert('Failed to authenticate due to invalid callback'); + return { success: false, error: 'Failed to authenticate due to invalid callback' }; } }, [setIdentity],