-
-
Notifications
You must be signed in to change notification settings - Fork 5.3k
/
Copy pathuseLogout.ts
160 lines (148 loc) · 5.99 KB
/
useLogout.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { useCallback, useEffect, useRef } from 'react';
import { useLocation, useNavigate, Path } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
import useAuthProvider, { defaultAuthParams } from './useAuthProvider';
import { useResetStore } from '../store';
import { useBasename } from '../routing';
import { removeDoubleSlashes } from '../routing/useCreatePath';
/**
* Get a callback for calling the authProvider.logout() method,
* redirect to the login page, and clear the store.
*
* @see useAuthProvider
*
* @returns {Function} logout callback
*
* @example
*
* import { useLogout } from 'react-admin';
*
* const LogoutButton = () => {
* const logout = useLogout();
* const handleClick = () => logout();
* return <button onClick={handleClick}>Logout</button>;
* }
*/
const useLogout = (): Logout => {
const authProvider = useAuthProvider();
const queryClient = useQueryClient();
const resetStore = useResetStore();
const navigate = useNavigate();
// useNavigate forces rerenders on every navigation, even if we don't use the result
// see https://github.com/remix-run/react-router/issues/7634
// so we use a ref to bail out of rerenders when we don't need to
const navigateRef = useRef(navigate);
const location = useLocation();
const locationRef = useRef(location);
const basename = useBasename();
const loginUrl = removeDoubleSlashes(
`${basename}/${defaultAuthParams.loginUrl}`
);
/*
* We need the current location to pass in the router state
* so that the login hook knows where to redirect to as next route after login.
*
* But if we used the location from useLocation as a dependency of the logout
* function, it would be rebuilt each time the user changes location.
* Consequently, that would force a rerender of all components using this hook
* upon navigation (CoreAdminRouter for example).
*
* To avoid that, we store the location in a ref.
*/
useEffect(() => {
locationRef.current = location;
navigateRef.current = navigate;
}, [location, navigate]);
const logout: Logout = useCallback(
(
params = {},
redirectTo = loginUrl,
redirectToCurrentLocationAfterLogin = true
) => {
if (authProvider) {
return authProvider
.logout(params)
.then(redirectToFromProvider => {
if (
redirectToFromProvider === false ||
redirectTo === false
) {
resetStore();
queryClient.clear();
// do not redirect
return;
}
const finalRedirectTo =
redirectToFromProvider || redirectTo;
if (finalRedirectTo?.startsWith('http')) {
// absolute link (e.g. https://my.oidc.server/login)
resetStore();
queryClient.clear();
window.location.href = finalRedirectTo;
return finalRedirectTo;
}
// redirectTo is an internal location that may contain a query string, e.g. '/login?foo=bar'
// we must split it to pass a structured location to navigate()
const redirectToParts = finalRedirectTo.split('?');
const newLocation: Partial<Path> = {
pathname: redirectToParts[0],
};
let newLocationOptions = {};
if (
redirectToCurrentLocationAfterLogin &&
locationRef.current &&
locationRef.current.pathname
) {
newLocationOptions = {
state: {
nextPathname: locationRef.current.pathname,
nextSearch: locationRef.current.search,
},
};
}
if (redirectToParts[1]) {
newLocation.search = redirectToParts[1];
}
navigateRef.current(newLocation, newLocationOptions);
resetStore();
queryClient.clear();
return redirectToFromProvider;
});
} else {
navigateRef.current(
{
pathname: loginUrl,
},
{
state: {
nextPathname:
locationRef.current &&
locationRef.current.pathname,
},
}
);
resetStore();
queryClient.clear();
return Promise.resolve();
}
},
[authProvider, resetStore, loginUrl, queryClient]
);
return logout;
};
/**
* Log the current user out by calling the authProvider.logout() method,
* and redirect them to the login screen.
*
* @param {Object} params The parameters to pass to the authProvider
* @param {string} redirectTo The path name to redirect the user to (optional, defaults to login)
* @param {boolean} redirectToCurrentLocationAfterLogin Whether the button shall record the current location to redirect to it after login. true by default.
*
* @return {Promise} The authProvider response
*/
type Logout = (
params?: any,
redirectTo?: string | false,
redirectToCurrentLocationAfterLogin?: boolean
) => Promise<any>;
export default useLogout;