@@ -2,11 +2,13 @@ import Script from 'next/script'
22import { createClient } from '@utils/supabase/component'
33import { CredentialResponse } from 'google-one-tap'
44import { useRouter } from 'next/router'
5- import { useEffect , useCallback } from 'react'
5+ import { useEffect , useCallback , useRef } from 'react'
66
77const OneTapComponent = ( ) => {
88 const supabase = createClient ( )
99 const router = useRouter ( )
10+ const initializationRef = useRef ( false )
11+ const abortControllerRef = useRef < AbortController | null > ( null )
1012
1113 const handleCredentialResponse = useCallback (
1214 async ( response : CredentialResponse ) => {
@@ -39,64 +41,113 @@ const OneTapComponent = () => {
3941 [ supabase . auth , router ]
4042 )
4143
42- useEffect ( ( ) => {
43- const initializeGoogleOneTap = async ( ) => {
44- try {
45- // Check session first
46- const {
47- data : { session } ,
48- error : sessionError
49- } = await supabase . auth . getSession ( )
44+ const initializeGoogleOneTap = useCallback ( async ( ) => {
45+ // Prevent multiple initializations
46+ if ( initializationRef . current ) return
47+
48+ try {
49+ // Create AbortController for this operation
50+ const abortController = new AbortController ( )
51+ abortControllerRef . current = abortController
52+
53+ // Check if component is still mounted
54+ if ( abortController . signal . aborted ) return
55+
56+ // Check session first
57+ const {
58+ data : { session } ,
59+ error : sessionError
60+ } = await supabase . auth . getSession ( )
61+
62+ if ( sessionError ) throw sessionError
63+ if ( session ) return
5064
51- if ( sessionError ) throw sessionError
52- if ( session ) return
65+ if ( ! process . env . NEXT_PUBLIC_GOOGLE_CLIENT_ID ) {
66+ console . error ( 'NEXT_PUBLIC_GOOGLE_CLIENT_ID is not set' )
67+ return
68+ }
69+
70+ // Wait for Google script with retry mechanism
71+ let attempts = 0
72+ const maxAttempts = 20 // 4 seconds max wait
73+
74+ while ( attempts < maxAttempts ) {
75+ if ( abortController . signal . aborted ) return
76+
77+ if ( typeof window . google !== 'undefined' && window . google . accounts ) {
78+ break
79+ }
5380
54- if ( typeof window . google === 'undefined' ) {
55- console . error ( 'Google script not loaded' )
81+ attempts ++
82+ if ( attempts >= maxAttempts ) {
83+ console . error ( 'Google script failed to load after 4 seconds' )
5684 return
5785 }
5886
59- window . google . accounts . id . initialize ( {
60- client_id : process . env . NEXT_PUBLIC_GOOGLE_CLIENT_ID ! ,
61- callback : handleCredentialResponse ,
62- auto_select : false , // Don't auto select the account
63- cancel_on_tap_outside : true ,
64- use_fedcm_for_prompt : true
65- } )
66-
67- window . google . accounts . id . prompt ( ( notification : any ) => {
68- if ( notification . isNotDisplayed ( ) ) {
69- console . info ( 'One Tap not displayed:' , notification . getNotDisplayedReason ( ) )
70- } else if ( notification . isSkippedMoment ( ) ) {
71- console . info ( 'One Tap skipped:' , notification . getSkippedReason ( ) )
72- }
73- } )
74- } catch ( error ) {
75- console . error ( 'Error initializing Google One Tap:' , error )
87+ await new Promise ( ( resolve ) => setTimeout ( resolve , 200 ) )
7688 }
77- }
7889
79- // Small delay to ensure the script is loaded
80- const timeoutId = setTimeout ( initializeGoogleOneTap , 1000 )
90+ // Check if still mounted before initializing
91+ if ( abortController . signal . aborted ) return
92+
93+ // Mark as initialized to prevent multiple calls
94+ initializationRef . current = true
8195
96+ window . google . accounts . id . initialize ( {
97+ client_id : process . env . NEXT_PUBLIC_GOOGLE_CLIENT_ID ,
98+ callback : handleCredentialResponse ,
99+ auto_select : false ,
100+ cancel_on_tap_outside : true ,
101+ use_fedcm_for_prompt : true
102+ } )
103+
104+ window . google . accounts . id . prompt ( ( notification : any ) => {
105+ if ( notification . isNotDisplayed ( ) ) {
106+ console . info ( 'One Tap not displayed:' , notification . getNotDisplayedReason ( ) )
107+ } else if ( notification . isSkippedMoment ( ) ) {
108+ console . info ( 'One Tap skipped:' , notification . getSkippedReason ( ) )
109+ }
110+ } )
111+ } catch ( error ) {
112+ console . error ( 'Error initializing Google One Tap:' , error )
113+ initializationRef . current = false // Reset on error
114+ }
115+ } , [ supabase . auth , handleCredentialResponse ] )
116+
117+ useEffect ( ( ) => {
82118 return ( ) => {
83- clearTimeout ( timeoutId )
84- // Cleanup
119+ // Abort any ongoing operations
120+ if ( abortControllerRef . current ) {
121+ abortControllerRef . current . abort ( )
122+ abortControllerRef . current = null
123+ }
124+
125+ // Cancel Google One Tap
85126 if ( typeof window . google !== 'undefined' ) {
86127 window . google . accounts . id . cancel ( )
87128 }
129+
130+ // Reset initialization flag
131+ initializationRef . current = false
88132 }
89- } , [ handleCredentialResponse , router , supabase . auth ] )
133+ } , [ ] )
90134
91135 return (
92136 < >
93137 < Script
94138 src = "https://accounts.google.com/gsi/client"
95139 strategy = "afterInteractive"
96- onLoad = { ( ) => console . info ( 'Google script loaded' ) }
97- onError = { ( e ) => console . error ( 'Error loading Google script:' , e ) }
140+ onLoad = { ( ) => {
141+ // Initialize when script loads
142+ initializeGoogleOneTap ( )
143+ } }
144+ onError = { ( e ) => {
145+ console . error ( 'Failed to load Google One Tap script:' , e )
146+ // Reset initialization flag so we can try again if needed
147+ initializationRef . current = false
148+ } }
98149 />
99- < div id = "oneTap" className = "fixed right -0 top -0 z-[100]" />
150+ < div id = "oneTap" className = "fixed top -0 right -0 z-[100]" />
100151 </ >
101152 )
102153}
0 commit comments