diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 361038208e..2a8f53ae0a 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -155,9 +155,19 @@ export function createUrlWithIdentifiers(url: string): string { return createUrl(url) + "?X-SCM-Client=WUI&X-SCM-Session-ID=" + sessionId; } +type ErrorListener = (error: Error) => void; + class ApiClient { + constructor() { + this.notifyAndRethrow = this.notifyAndRethrow.bind(this); + } + + errorListeners: ErrorListener[] = []; + get(url: string): Promise { - return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure); + return fetch(createUrl(url), applyFetchOptions({})) + .then(handleFailure) + .catch(this.notifyAndRethrow); } post(url: string, payload?: any, contentType = "application/json", additionalHeaders: Record = {}) { @@ -193,7 +203,9 @@ class ApiClient { method: "HEAD" }; options = applyFetchOptions(options); - return fetch(createUrl(url), options).then(handleFailure); + return fetch(createUrl(url), options) + .then(handleFailure) + .catch(this.notifyAndRethrow); } delete(url: string): Promise { @@ -201,7 +213,9 @@ class ApiClient { method: "DELETE" }; options = applyFetchOptions(options); - return fetch(createUrl(url), options).then(handleFailure); + return fetch(createUrl(url), options) + .then(handleFailure) + .catch(this.notifyAndRethrow); } httpRequestWithJSONBody( @@ -245,7 +259,9 @@ class ApiClient { options.headers["Content-Type"] = contentType; } - return fetch(createUrl(url), options).then(handleFailure); + return fetch(createUrl(url), options) + .then(handleFailure) + .catch(this.notifyAndRethrow); } subscribe(url: string, argument: SubscriptionArgument): Cancel { @@ -276,6 +292,15 @@ class ApiClient { return () => es.close(); } + + onError(errorListener: ErrorListener) { + this.errorListeners.push(errorListener); + } + + private notifyAndRethrow(error: Error): never { + this.errorListeners.forEach(errorListener => errorListener(error)); + throw error; + } } export const apiClient = new ApiClient(); diff --git a/scm-ui/ui-webapp/src/containers/Index.tsx b/scm-ui/ui-webapp/src/containers/Index.tsx index 0a3d80aa5d..a92d56d901 100644 --- a/scm-ui/ui-webapp/src/containers/Index.tsx +++ b/scm-ui/ui-webapp/src/containers/Index.tsx @@ -64,6 +64,15 @@ class Index extends Component { this.props.fetchIndexResources(); } + componentDidUpdate() { + const { indexResources, loading, error } = this.props; + const { pluginsLoaded } = this.state; + if (!indexResources && !loading && !error && pluginsLoaded) { + this.props.fetchIndexResources(); + this.setState({ pluginsLoaded: false }); + } + } + pluginLoaderCallback = () => { this.setState({ pluginsLoaded: true diff --git a/scm-ui/ui-webapp/src/createReduxStore.ts b/scm-ui/ui-webapp/src/createReduxStore.ts index 1a391d17ae..ffdbb7a859 100644 --- a/scm-ui/ui-webapp/src/createReduxStore.ts +++ b/scm-ui/ui-webapp/src/createReduxStore.ts @@ -24,14 +24,14 @@ import thunk from "redux-thunk"; import logger from "redux-logger"; -import { applyMiddleware, combineReducers, compose, createStore } from "redux"; +import { AnyAction, applyMiddleware, combineReducers, compose, createStore } from "redux"; import users from "./users/modules/users"; import repos from "./repos/modules/repos"; import repositoryTypes from "./repos/modules/repositoryTypes"; import changesets from "./repos/modules/changesets"; import sources from "./repos/sources/modules/sources"; import groups from "./groups/modules/groups"; -import auth from "./modules/auth"; +import auth, { isAuthenticated } from "./modules/auth"; import pending from "./modules/pending"; import failure from "./modules/failure"; import permissions from "./repos/permissions/modules/permissions"; @@ -40,13 +40,15 @@ import roles from "./admin/roles/modules/roles"; import namespaceStrategies from "./admin/modules/namespaceStrategies"; import indexResources from "./modules/indexResource"; import plugins from "./admin/plugins/modules/plugins"; +import { apiClient } from "@scm-manager/ui-components"; import branches from "./repos/branches/modules/branches"; +import { UnauthorizedError } from "@scm-manager/ui-components/src"; function createReduxStore() { const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - const reducer = combineReducers({ + const appReducer = combineReducers({ pending, failure, indexResources, @@ -65,7 +67,28 @@ function createReduxStore() { plugins }); - return createStore(reducer, composeEnhancers(applyMiddleware(thunk, logger))); + const reducer = (state: any, action: AnyAction) => { + console.log(action.type, state?.tokenExpired); + if (state?.tokenExpired && action.type.indexOf("FAILURE") === -1) { + console.log("reset state"); + return appReducer({}, action); + } + + if (action.type === "API_CLIENT_UNAUTHORIZED" && isAuthenticated(state)) { + return { ...state, tokenExpired: true }; + } + + return { ...appReducer(state, action), tokenExpired: state?.tokenExpired }; + }; + + const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk, logger))); + apiClient.onError(error => { + if (error instanceof UnauthorizedError) { + store.dispatch({ type: "API_CLIENT_UNAUTHORIZED", error }); + } + }); + + return store; } export default createReduxStore;