global-interceptor.service.js

195 lines | 6.357 kB Blame History Raw Download
/*
 * Copyright © 2016-2018 The Thingsboard Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*@ngInject*/
export default function GlobalInterceptor($rootScope, $q, $injector) {

    var toast;
    var translate;
    var userService;
    var types;
    var http;

    var internalUrlPrefixes = [
        '/api/auth/token',
        '/api/plugins/rpc'
    ];

    var service = {
        request: request,
        requestError: requestError,
        response: response,
        responseError: responseError
    }

    return service;

    function getToast() {
        if (!toast) {
            toast = $injector.get("toast");
        }
        return toast;
    }

    function getTranslate() {
        if (!translate) {
            translate = $injector.get("$translate");
        }
        return translate;
    }

    function getUserService() {
        if (!userService) {
            userService = $injector.get("userService");
        }
        return userService;
    }

    function getTypes() {
        if (!types) {
            types = $injector.get("types");
        }
        return types;
    }

    function getHttp() {
        if (!http) {
            http = $injector.get("$http");
        }
        return http;
    }

    function rejectionErrorCode(rejection) {
        if (rejection && rejection.data && rejection.data.errorCode) {
            return rejection.data.errorCode;
        } else {
            return undefined;
        }
    }

    function isTokenBasedAuthEntryPoint(url) {
        return  url.startsWith('/api/') &&
               !url.startsWith(getTypes().entryPoints.login) &&
               !url.startsWith(getTypes().entryPoints.tokenRefresh) &&
               !url.startsWith(getTypes().entryPoints.nonTokenBased);
    }

    function refreshTokenAndRetry(request) {
        return getUserService().refreshJwtToken().then(function success() {
            getUserService().updateAuthorizationHeader(request.config.headers);
            return getHttp()(request.config);
        }, function fail(message) {
            $rootScope.$broadcast('unauthenticated');
            request.status = 401;
            request.data = {};
            request.data.message = message || getTranslate().instant('access.unauthorized');
            return $q.reject(request);
        });
    }

    function isInternalUrlPrefix(url) {
        for (var index in internalUrlPrefixes) {
            if (url.startsWith(internalUrlPrefixes[index])) {
                return true;
            }
        }
        return false;
    }

    function request(config) {
        var rejected = false;
        if (config.url.startsWith('/api/')) {
            var isLoading = !isInternalUrlPrefix(config.url);
            updateLoadingState(config, isLoading);
            if (isTokenBasedAuthEntryPoint(config.url)) {
                if (!getUserService().updateAuthorizationHeader(config.headers) &&
                    !getUserService().refreshTokenPending()) {
                    updateLoadingState(config, false);
                    rejected = true;
                    getUserService().clearJwtToken(false);
                    return $q.reject({ data: {message: getTranslate().instant('access.unauthorized')}, status: 401, config: config});
                } else if (!getUserService().isJwtTokenValid()) {
                    return $q.reject({ refreshTokenPending: true, config: config });
                }
            }
        }
        if (!rejected) {
            return config;
        }
    }

    function requestError(rejection) {
        if (rejection.config.url.startsWith('/api/')) {
            updateLoadingState(rejection.config, false);
        }
        return $q.reject(rejection);
    }

    function response(response) {
        if (response.config.url.startsWith('/api/')) {
            updateLoadingState(response.config, false);
        }
        return response;
    }

    function responseError(rejection) {
        if (rejection.config.url.startsWith('/api/')) {
            updateLoadingState(rejection.config, false);
        }
        var unhandled = false;
        var ignoreErrors = rejection.config.ignoreErrors;
        if (rejection.refreshTokenPending || rejection.status === 401) {
            var errorCode = rejectionErrorCode(rejection);
            if (rejection.refreshTokenPending || (errorCode && errorCode === getTypes().serverErrorCode.jwtTokenExpired)) {
                return refreshTokenAndRetry(rejection);
            } else {
                unhandled = true;
            }
        } else if (rejection.status === 403) {
            if (!ignoreErrors) {
                $rootScope.$broadcast('forbidden');
            }
        } else if (rejection.status === 0 || rejection.status === -1) {
            getToast().showError(getTranslate().instant('error.unable-to-connect'));
        } else if (!rejection.config.url.startsWith('/api/plugins/rpc')) {
            if (rejection.status === 404) {
                if (!ignoreErrors) {
                    getToast().showError(rejection.config.method + ": " + rejection.config.url + "<br/>" +
                        rejection.status + ": " + rejection.statusText);
                }
            } else {
                unhandled = true;
            }
        }

        if (unhandled && !ignoreErrors) {
            if (rejection.data && !rejection.data.message) {
                getToast().showError(rejection.data);
            } else if (rejection.data && rejection.data.message) {
                getToast().showError(rejection.data.message);
            } else {
                getToast().showError(getTranslate().instant('error.unhandled-error-code', {errorCode: rejection.status}));
            }
        }
        return $q.reject(rejection);
    }

    function updateLoadingState(config, isLoading) {
        if (!config || angular.isUndefined(config.ignoreLoading) || !config.ignoreLoading) {
            $rootScope.loading = isLoading;
        }
    }
}