import { useState , useRef } from 'react' ; import { useExternalScript } from './useExternalScript' ; import { DOM_ID , GOOGLE_API_URL } from './constants' ; import { GoogleUser , HookConfig , HookState , HookReturnValue , TokenObj , } from './types' ; export type GoogleLoginHookReturnValue = HookReturnValue ; private param user const getAdditionalUserData = ( user , fetchBasicProfile ) => { const authResponse = user . getAuthResponse ( ) ; user . tokenObj = authResponse ; user . tokenId = authResponse . id_token ; user . accessToken = authResponse . access_token ; user . expiresAt = authResponse . expires_at ; if ( ! fetchBasicProfile ) return ; const basicProfile = user . getBasicProfile ( ) ; user . googleId = basicProfile . getId ( ) ; user . profileObj = { googleId : basicProfile . getId ( ) , imageUrl : basicProfile . getImageUrl ( ) , email : basicProfile . getEmail ( ) , name : basicProfile . getName ( ) , givenName : basicProfile . getGivenName ( ) , familyName : basicProfile . getFamilyName ( ) , } ; } ; param config returns export const useGoogleLogin = ( { clientId , hostedDomain , redirectUri , scope = 'profile email openid' , cookiePolicy = 'single_host_origin' , fetchBasicProfile = true , uxMode = 'popup' , persist = true , } ) : HookReturnValue => { if ( ! clientId ) throw new Error ( 'clientId is required.' ) ; const [ state , setState ] = useState < HookState > ( { googleUser : undefined , auth2 : undefined , isSignedIn : false , isInitialized : false , } ) ; const latestAccessTokenRef = useRef < string | undefined > ( undefined ) ; const latestExpiresAtRef = useRef < number | undefined > ( undefined ) ; public param options returns const signIn = async ( options ? ) : Promise < GoogleUser | undefined > => { const auth2 = window . gapi . auth2 . getAuthInstance ( ) ; try { const googleUser = await auth2 . signIn ( options ) ; getAdditionalUserData ( googleUser , fetchBasicProfile ) ; return googleUser ; } catch ( err ) { if ( __DEV__ ) console . error ( 'Received error when signing in: ' + err ?. details ) ; return ; } } ; public returns const signOut = async ( ) : Promise < boolean > => { const auth2 = window . gapi . auth2 . getAuthInstance ( ) ; if ( ! auth2 ) return false ; await auth2 . signOut ( ) ; auth2 . disconnect ( ) ; return true ; } ; public remarks param options returns const grantOfflineAccess = async ( options ? ) : Promise < string | undefined > => { const auth2 = window . gapi . auth2 . getAuthInstance ( ) ; try { const { code } = await auth2 . grantOfflineAccess ( options ) ; return code ; } catch ( err ) { if ( __DEV__ ) console . error ( 'Received error when granting offline access: ' + err ?. details ) ; return ; } } ; remarks returns const refreshUser = async ( ) : Promise < TokenObj | undefined > => { try { const auth2 = window . gapi . auth2 . getAuthInstance ( ) ; const googleUser = auth2 . currentUser . get ( ) ; const tokenObj = await googleUser ?. reloadAuthResponse ( ) ; if ( ! tokenObj ) { if ( __DEV__ ) console . error ( 'Something went wrong refreshing the current user.' ) ; return ; } latestAccessTokenRef . current = tokenObj . access_token ; latestExpiresAtRef . current = tokenObj . expires_at ; return { accessToken : tokenObj . access_token , expiresAt : tokenObj . expires_at , } ; } catch ( err ) { if ( __DEV__ ) console . error ( 'Received error when refreshing tokens: ' + err ?. details ) ; return ; } } ; private remarks param googleUser const handleAuthChange = ( googleUser ) => { const isSignedIn = googleUser . isSignedIn ( ) ; const auth2 = window . gapi . auth2 . getAuthInstance ( ) ; if ( isSignedIn && ! googleUser . tokenId ) getAdditionalUserData ( googleUser , fetchBasicProfile ) ; setState ( { auth2 , googleUser : isSignedIn ? { ... googleUser , accessToken : latestAccessTokenRef . current ?? googleUser . accessToken , expiresAt : latestExpiresAtRef . current ?? googleUser . expiresAt , tokenObj : { ... ( googleUser . tokenObj as gapi . auth2 . AuthResponse ) , access_token : latestAccessTokenRef . current ?? googleUser . tokenObj ! . access_token , expires_at : latestExpiresAtRef . current ?? googleUser . tokenObj ! . expires_at , } , } : undefined , isSignedIn , isInitialized : true , } ) ; } ; useExternalScript ( DOM_ID , GOOGLE_API_URL , ( ) => { const config = { client_id : clientId , cookie_policy : cookiePolicy , hosted_domain : hostedDomain , fetch_basic_profile : fetchBasicProfile , ux_mode : uxMode , redirect_uri : redirectUri , scope , } ; /**
         * According to Google's documentation:
         *
         * Warning: do not call Promise.resolve() or similar with the result of gapi.auth2.init().
         * The GoogleAuth object returned implements the `then()` method which resolves with itself.
         * As a result, `Promise.resolve()` or `await` will cause infinite recursion.
         */ const handleLoad = ( ) => { window . gapi . auth2 . init ( config ) . then ( auth2 => { const googleUser = auth2 . currentUser . get ( ) ; const isSignedIn = googleUser . isSignedIn ( ) ; auth2 . currentUser . listen ( handleAuthChange ) ; if ( ! persist ) { signOut ( ) ; return ; } if ( isSignedIn ) getAdditionalUserData ( googleUser , fetchBasicProfile ) ; setState ( { googleUser , auth2 , isSignedIn , isInitialized : true } ) ; } ) ; } ; window . gapi . load ( 'auth2' , handleLoad ) ; } ) ; return { ... state , signIn , signOut , grantOfflineAccess , refreshUser , } ; } ;