Skip to content

Commit

Permalink
STCOR-832 invalidate react-query cache on login, logout
Browse files Browse the repository at this point in the history
Replaces PR #1457, which was just too cumbersome to rebase after all the
work that came in STCOR-776 / PR #1463.

Invalidate all react-query caches on login and logout. Because the RQ
cache is persistent across sessions, it was possible that empty values
were cached if a query returns 4xx when a session ended, and then
(incorrectly) reused when the session restarted.

Approach

Pass `<QueryClient>` down from `<RootWithIntl>` to some of its children
so they can clear the cache immediately after login and before logout.
The exact locations of this logic in `<MainNavigation>` and
`<SessionEventContainer>` are certainly debatable, but using existing
components instead of embarking on a larger refactor does have merits.

Refs STCOR-832
  • Loading branch information
zburke committed Jul 16, 2024
1 parent f915b02 commit 2b69124
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Correctly populate `stripes.user.user` on reload. Refs STCOR-860.
* Correctly evaluate `stripes.okapi` before rendering `<RootWithIntl>`. Refs STCOR-864.
* Change main navigation's skip link label to "Skip to main content". Refs STCOR-863.
* Invalidate `QueryClient` cache on login/logout. Refs STCOR-832.

## [10.1.0](https://github.com/folio-org/stripes-core/tree/v10.1.0) (2024-03-12)
[Full Changelog](https://github.com/folio-org/stripes-core/compare/v10.0.0...v10.1.0)
Expand Down
7 changes: 4 additions & 3 deletions src/RootWithIntl.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import StaleBundleWarning from './components/StaleBundleWarning';
import { StripesContext } from './StripesContext';
import { CalloutContext } from './CalloutContext';

const RootWithIntl = ({ stripes, token = '', isAuthenticated = false, disableAuth, history = {} }) => {
const RootWithIntl = ({ stripes, token = '', isAuthenticated = false, disableAuth, history = {}, queryClient }) => {
const connect = connectFor('@folio/core', stripes.epics, stripes.logger);
const connectedStripes = stripes.clone({ connect });

Expand All @@ -64,7 +64,7 @@ const RootWithIntl = ({ stripes, token = '', isAuthenticated = false, disableAut
<>
<MainContainer>
<AppCtxMenuProvider>
<MainNav stripes={connectedStripes} />
<MainNav stripes={connectedStripes} queryClient={queryClient} />
{typeof connectedStripes?.config?.staleBundleWarning === 'object' && <StaleBundleWarning />}
<HandlerManager
event={events.LOGIN}
Expand All @@ -73,7 +73,7 @@ const RootWithIntl = ({ stripes, token = '', isAuthenticated = false, disableAut
{ (typeof connectedStripes.okapi !== 'object' || connectedStripes.discovery.isFinished) && (
<ModuleContainer id="content">
<OverlayContainer />
{connectedStripes.config.useSecureTokens && <SessionEventContainer history={history} />}
{connectedStripes.config.useSecureTokens && <SessionEventContainer history={history} queryClient={queryClient} />}
<Switch>
<TitledRoute
name="home"
Expand Down Expand Up @@ -179,6 +179,7 @@ RootWithIntl.propTypes = {
isAuthenticated: PropTypes.bool,
disableAuth: PropTypes.bool.isRequired,
history: PropTypes.shape({}),
queryClient: PropTypes.object.isRequired,
};

export default RootWithIntl;
Expand Down
1 change: 0 additions & 1 deletion src/RootWithIntl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,4 @@ describe('RootWithIntl', () => {

expect(screen.getByText(/<SessionEventContainer>/)).toBeInTheDocument();
});

});
8 changes: 6 additions & 2 deletions src/components/MainNav/MainNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class MainNav extends Component {
}).isRequired,
modules: PropTypes.shape({
app: PropTypes.arrayOf(PropTypes.object),
})
}),
queryClient: PropTypes.object.isRequired,
};

constructor(props) {
Expand Down Expand Up @@ -95,6 +96,9 @@ class MainNav extends Component {
}
}
});

// remove QueryProvider cache to be 100% sure we're starting from a clean slate.
this.props.queryClient.removeQueries();
}

componentDidUpdate(prevProps) {
Expand All @@ -121,7 +125,7 @@ class MainNav extends Component {
const { okapi } = this.store.getState();

return getLocale(okapi.url, this.store, okapi.tenant)
.then(sessionLogout(okapi.url, this.store, this.props.history));
.then(sessionLogout(okapi.url, this.store, this.props.history, this.props.queryClient));
}

getAppList(lastVisited) {
Expand Down
1 change: 1 addition & 0 deletions src/components/Root/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class Root extends Component {
isAuthenticated={isAuthenticated}
disableAuth={disableAuth}
history={history}
queryClient={this.reactQueryClient}
/>
</IntlProvider>
</QueryClientProvider>
Expand Down
23 changes: 12 additions & 11 deletions src/components/SessionEventContainer/SessionEventContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ import { toggleRtrModal } from '../../okapiActions';
//

// RTR error in this window: logout
export const thisWindowRtrError = (_e, stripes, history) => {
export const thisWindowRtrError = (_e, stripes, history, queryClient) => {
console.warn('rtr error; logging out'); // eslint-disable-line no-console
return logout(stripes.okapi.url, stripes.store)
return logout(stripes.okapi.url, stripes.store, queryClient)
.then(() => {
history.push('/logout-timeout');
});
};

// idle session timeout in this window: logout
export const thisWindowRtrTimeout = (_e, stripes, history) => {
export const thisWindowRtrTimeout = (_e, stripes, history, queryClient) => {
stripes.logger.log('rtr', 'idle session timeout; logging out');
return logout(stripes.okapi.url, stripes.store)
return logout(stripes.okapi.url, stripes.store, queryClient)
.then(() => {
history.push('/logout-timeout');
});
Expand All @@ -40,16 +40,16 @@ export const thisWindowRtrTimeout = (_e, stripes, history) => {
// logout if it was a timeout event or if SESSION_NAME is being
// removed from localStorage, an indicator that logout is in-progress
// in another window and so must occur here as well
export const otherWindowStorage = (e, stripes, history) => {
export const otherWindowStorage = (e, stripes, history, queryClient) => {
if (e.key === RTR_TIMEOUT_EVENT) {
stripes.logger.log('rtr', 'idle session timeout; logging out');
return logout(stripes.okapi.url, stripes.store)
return logout(stripes.okapi.url, stripes.store, queryClient)
.then(() => {
history.push('/logout-timeout');
});
} else if (!localStorage.getItem(SESSION_NAME)) {
stripes.logger.log('rtr', 'external localstorage change; logging out');
return logout(stripes.okapi.url, stripes.store)
return logout(stripes.okapi.url, stripes.store, queryClient)
.then(() => {
history.push('/');
});
Expand Down Expand Up @@ -121,7 +121,7 @@ export const thisWindowActivity = (_e, stripes, timers, broadcastChannel) => {
* @param {object} history
* @returns KeepWorkingModal or null
*/
const SessionEventContainer = ({ history }) => {
const SessionEventContainer = ({ history, queryClient }) => {
// is the "keep working?" modal visible?
const [isVisible, setIsVisible] = useState(false);

Expand Down Expand Up @@ -200,13 +200,13 @@ const SessionEventContainer = ({ history }) => {
timers.current = { showModalIT, logoutIT };

// RTR error in this window: logout
channels.window[RTR_ERROR_EVENT] = (e) => thisWindowRtrError(e, stripes, history);
channels.window[RTR_ERROR_EVENT] = (e) => thisWindowRtrError(e, stripes, history, queryClient);

// idle session timeout in this window: logout
channels.window[RTR_TIMEOUT_EVENT] = (e) => thisWindowRtrTimeout(e, stripes, history);
channels.window[RTR_TIMEOUT_EVENT] = (e) => thisWindowRtrTimeout(e, stripes, history, queryClient);

// localstorage change in another window: logout?
channels.window.storage = (e) => otherWindowStorage(e, stripes, history);
channels.window.storage = (e) => otherWindowStorage(e, stripes, history, queryClient);

// activity in another window: send keep-alive to idle-timers.
channels.bc.message = (message) => otherWindowActivity(message, stripes, timers, setIsVisible);
Expand Down Expand Up @@ -259,6 +259,7 @@ const SessionEventContainer = ({ history }) => {

SessionEventContainer.propTypes = {
history: PropTypes.object,
queryClient: PropTypes.object,
};

export default SessionEventContainer;
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ describe('SessionEventContainer event listeners', () => {
}
};
const history = { push: jest.fn() };
const qc = {};

await otherWindowStorage(e, s, history);
expect(logout).toHaveBeenCalledWith(s.okapi.url, s.store);
await otherWindowStorage(e, s, history, qc);
expect(logout).toHaveBeenCalledWith(s.okapi.url, s.store, qc);
expect(history.push).toHaveBeenCalledWith('/logout-timeout');
});

Expand All @@ -132,9 +133,10 @@ describe('SessionEventContainer event listeners', () => {
}
};
const history = { push: jest.fn() };
const qc = {};

await otherWindowStorage(e, s, history);
expect(logout).toHaveBeenCalledWith(s.okapi.url, s.store);
await otherWindowStorage(e, s, history, qc);
expect(logout).toHaveBeenCalledWith(s.okapi.url, s.store, qc);
expect(history.push).toHaveBeenCalledWith('/');
});
});
Expand Down
5 changes: 4 additions & 1 deletion src/loginServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ export function spreadUserWithPerms(userWithPerms) {
* @returns {Promise}
*/
export const IS_LOGGING_OUT = '@folio/stripes/core::Logout';
export async function logout(okapiUrl, store) {
export async function logout(okapiUrl, store, queryClient) {
// check the private-storage sentinel: if logout has already started
// in this window, we don't want to start it again.
if (sessionStorage.getItem(IS_LOGGING_OUT)) {
Expand Down Expand Up @@ -483,6 +483,9 @@ export async function logout(okapiUrl, store) {
store.dispatch(clearCurrentUser());
store.dispatch(clearOkapiToken());
store.dispatch(resetStore());

// clear react-query cache
queryClient.removeQueries();
})
// clear shared storage
.then(localforage.removeItem(SESSION_NAME))
Expand Down

0 comments on commit 2b69124

Please sign in to comment.