Skip to content

Instantly share code, notes, and snippets.

@johanquiroga
Created August 27, 2021 00:06
Show Gist options
  • Save johanquiroga/9f08775bef389bf7d77ae0fef123ee04 to your computer and use it in GitHub Desktop.
Save johanquiroga/9f08775bef389bf7d77ae0fef123ee04 to your computer and use it in GitHub Desktop.
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