Created
August 27, 2021 00:06
-
-
Save johanquiroga/9f08775bef389bf7d77ae0fef123ee04 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import LoadingWrapper from '../components/LoadingWrapper'; | |
import * as Auth from '../services/auth'; | |
import * as User from '../services/user'; | |
import { BroadcastChannel } from 'broadcast-channel'; | |
import { isSafari, isMobileSafari } from 'react-device-detect'; | |
import { useAsync, StateType } from '../utils/hooks'; | |
import { | |
LoginPayload, | |
LoginResponse, | |
LogoutResponse, | |
RegisterPayload, | |
RegisterResponse, | |
OAuthProvider, | |
OAuthPayload, | |
OAuthResponse, | |
Identity, | |
UserInfo, | |
} from '../typings/services'; | |
type AuthBroadcastMessage = | |
| { type: 'LOGOUT' } | |
| { type: 'LOGIN' } | |
| { type: 'SET_IDENTITY' }; | |
type OAuthOptions = {}; | |
type AuthStateType = {}; | |
/** | |
* this function is to perform an initial check to know if we have an auth session active, | |
* if we do then we fetch the user data and use that as initial data. | |
* If we don't the user data is initialized with null, i.e. no user logged in. | |
*/ | |
const initializeUserData = async () => { | |
return {}; | |
}; | |
type AuthContextType = {}; | |
const AuthContext = React.createContext<AuthContextType | undefined>(undefined); | |
const AuthProvider = (props: { children: React.ReactNode }): JSX.Element => { | |
const { | |
data, | |
isLoading, | |
error, | |
isError, | |
isIdle, | |
run, | |
setData, | |
status, | |
} = useAsync<AuthStateType>(); | |
const [broadcastChannel] = React.useState< | |
BroadcastChannel<AuthBroadcastMessage> | |
>( | |
() => | |
new BroadcastChannel('auth', { | |
webWorkerSupport: false, | |
...((isSafari || isMobileSafari) && { type: 'idb' }), | |
}) | |
); | |
React.useEffect(() => { | |
const appDataPromise = initializeUserData(); | |
run(appDataPromise); | |
}, [run]); | |
const setAuthDataAfterLogin = React.useCallback( | |
(data: AuthStateType) => { | |
broadcastChannel.postMessage({ type: 'LOGIN' }); | |
setData(data); | |
}, | |
[broadcastChannel, setData] | |
); | |
const login = React.useCallback( | |
form => | |
Auth.login(form).then(setAuthDataAfterLogin), | |
[setAuthDataAfterLogin] | |
); | |
const logout = React.useCallback( | |
({ broadcast = true }: { broadcast?: boolean } = {}) => | |
Auth.logout().then((res: LogoutResponse) => { | |
if (broadcast) broadcastChannel.postMessage({ type: 'LOGOUT' }); | |
setData(null); | |
return res; | |
}), | |
[broadcastChannel, setData] | |
); | |
React.useEffect(() => { | |
broadcastChannel.onmessage = message => { | |
if (message.type === 'LOGIN' || message.type === 'SET_IDENTITY') { | |
const appDataPromise = initializeUserData(); | |
run(appDataPromise); | |
} else if (message.type === 'LOGOUT') { | |
// We need to disable broadcasting when we are logging out here | |
// because it would trigger a new broadcast from the receiving tabs | |
// causing an infinite loop of receiving and broadcasting LOGOUT messages. | |
logout({ broadcast: false }); | |
} else { | |
throw new Error( | |
`Unknown Authentication broadcast message with type "${ | |
(message as any).type | |
}": ${JSON.stringify(message)}` | |
); | |
} | |
}; | |
}, [broadcastChannel, logout, run]); | |
const value = {}; | |
if (isError) { | |
// In this app we don't want to block the app from loading if we encounter an error during | |
// authentication initialization. | |
// In case of an error just initialize the app as if no user is logged in. | |
// Also, log the error (in the future this can be reported to some error logging service) | |
console.error(error); | |
} | |
if (process.env.NODE_ENV === 'test') { | |
// We need to add some loading indicator when running tests so we have something | |
// to query and wait for that indicates us that the process has finished | |
return ( | |
<> | |
{isLoading || isIdle ? ( | |
<LoadingWrapper | |
isLoading={isLoading || isIdle} | |
LoadingIndicatorProps={{ | |
'data-testid': 'auth-provider-loading-indicator', | |
}} | |
/> | |
) : null} | |
<AuthContext.Provider value={value} {...props} /> | |
</> | |
); | |
} | |
return <AuthContext.Provider value={value} {...props} />; | |
}; | |
const useAuth = (): AuthContextType => { | |
const context = React.useContext(AuthContext); | |
if (context === undefined) { | |
throw new Error(`useAuth must be used within a AuthProvider`); | |
} | |
return context; | |
}; | |
export { AuthProvider, useAuth }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment