text: (function() {
  
  
  // Create all modules and define dependencies to make sure they exist
  // and are loaded in the correct order to satisfy dependency injection
  // before all nested files are concatenated by Grunt
  
  // Modules
  angular.module('angular-jwt',
      [
          'angular-jwt.options',
          'angular-jwt.interceptor',
          'angular-jwt.jwt',
          'angular-jwt.authManager'
      ]);
  
  angular.module('angular-jwt.authManager', [])
    .provider('authManager', function () {
  
      this.$get = ["$rootScope", "$injector", "$location", "jwtHelper", "jwtInterceptor", "jwtOptions", function ($rootScope, $injector, $location, jwtHelper, jwtInterceptor, jwtOptions) {
  
        var config = jwtOptions.getConfig();
  
        function invokeToken(tokenGetter) {
          var token = null;
          if (Array.isArray(tokenGetter)) {
            token = $injector.invoke(tokenGetter, this, {options: null});
          } else {
            token = tokenGetter();
          }
          return token;
        }
  
        function invokeRedirector(redirector) {
          if (Array.isArray(redirector) || angular.isFunction(redirector)) {
            return $injector.invoke(redirector, config, {});
          } else {
            throw new Error('unauthenticatedRedirector must be a function');
          }
        }
  
        function isAuthenticated() {
          var token = invokeToken(config.tokenGetter);
          if (token) {
            return !jwtHelper.isTokenExpired(token);
          }
        }
  
        $rootScope.isAuthenticated = false;
  
        function authenticate() {
          $rootScope.isAuthenticated = true;
        }
  
        function unauthenticate() {
          $rootScope.isAuthenticated = false;
        }
  
        function validateToken() {
          var token = invokeToken(config.tokenGetter);
          if (token) {
            if (!jwtHelper.isTokenExpired(token)) {
              authenticate();
            } else {
              $rootScope.$broadcast('tokenHasExpired', token);
            }
          }
        }
  
        function checkAuthOnRefresh() {
          if ($injector.has('$transitions')) {
            var $transitions = $injector.get('$transitions');
            $transitions.onStart({}, validateToken);
          } else {
            $rootScope.$on('$locationChangeStart', validateToken);
          }
        }
  
        function redirectWhenUnauthenticated() {
          $rootScope.$on('unauthenticated', function () {
            invokeRedirector(config.unauthenticatedRedirector);
            unauthenticate();
          });
        }
  
        function verifyRoute(event, next) {
          if (!next) {
            return false;
          }
  
          var routeData = (next.$$route) ? next.$$route : next.data;
  
          if (routeData && routeData.requiresLogin === true && !isAuthenticated()) {
            event.preventDefault();
            invokeRedirector(config.unauthenticatedRedirector);
          }
        }
  
        function verifyState(transition) {
          var route = transition.to();
          var $state = transition.router.stateService;
            if (route && route.data && route.data.requiresLogin === true && !isAuthenticated()) {
              return $state.target(config.loginPath);
            }
        }
  
        if ($injector.has('$transitions')) {
          var $transitions = $injector.get('$transitions');
          $transitions.onStart({}, verifyState);
        } else {
          var eventName = ($injector.has('$state')) ? '$stateChangeStart' : '$routeChangeStart';
          $rootScope.$on(eventName, verifyRoute);
        }
  
  
  
        return {
          authenticate: authenticate,
          unauthenticate: unauthenticate,
          getToken: function(){ return invokeToken(config.tokenGetter); },
          redirect: function() { return invokeRedirector(config.unauthenticatedRedirector); },
          checkAuthOnRefresh: checkAuthOnRefresh,
          redirectWhenUnauthenticated: redirectWhenUnauthenticated,
          isAuthenticated: isAuthenticated
        }
      }]
    });
  
  angular.module('angular-jwt.interceptor', [])
    .provider('jwtInterceptor', function() {
  
      this.urlParam;
      this.authHeader;
      this.authPrefix;
      this.whiteListedDomains;
      this.tokenGetter;
  
      var config = this;
  
      this.$get = ["$q", "$injector", "$rootScope", "urlUtils", "jwtOptions", function($q, $injector, $rootScope, urlUtils, jwtOptions) {
  
        var options = angular.extend({}, jwtOptions.getConfig(), config);
  
        function isSafe (url) {
          if (!urlUtils.isSameOrigin(url) && !options.whiteListedDomains.length) {
            throw new Error('As of v0.1.0, requests to domains other than the application\'s origin must be white listed. Use jwtOptionsProvider.config({ whiteListedDomains: [<domain>] }); to whitelist.')
          }
          var hostname = urlUtils.urlResolve(url).hostname.toLowerCase();
          for (var i = 0; i < options.whiteListedDomains.length; i++) {
            var domain = options.whiteListedDomains[i];
            if (domain instanceof RegExp) {
              if (hostname.match(domain)) {
                return true;
              }
            } else {
              if (hostname === domain.toLowerCase()) {
                return true;
              }
            }
          }
  
          if (urlUtils.isSameOrigin(url)) {
            return true;
          }
  
          return false;
        }
  
        return {
          request: function (request) {
            if (request.skipAuthorization || !isSafe(request.url)) {
              return request;
            }
  
            if (options.urlParam) {
              request.params = request.params || {};
              // Already has the token in the url itself
              if (request.params[options.urlParam]) {
                return request;
              }
            } else {
              request.headers = request.headers || {};
              // Already has an Authorization header
              if (request.headers[options.authHeader]) {
                return request;
              }
            }
  
            var tokenPromise = $q.when($injector.invoke(options.tokenGetter, this, {
              options: request
            }));
  
            return tokenPromise.then(function(token) {
              if (token) {
                if (options.urlParam) {
                  request.params[options.urlParam] = token;
                } else {
                  request.headers[options.authHeader] = options.authPrefix + token;
                }
              }
              return request;
            });
          },
          responseError: function (response) {
            // handle the case where the user is not authenticated
            if (response !== undefined && response.status === 401) {
              $rootScope.$broadcast('unauthenticated', response);
            }
            return $q.reject(response);
          }
        };
      }]
    });
  
   angular.module('angular-jwt.jwt', [])
    .service('jwtHelper', ["$window", function($window) {
  
      this.urlBase64Decode = function(str) {
        var output = str.replace(/-/g, '+').replace(/_/g, '/');
        switch (output.length % 4) {
          case 0: { break; }
          case 2: { output += '=='; break; }
          case 3: { output += '='; break; }
          default: {
            throw 'Illegal base64url string!';
          }
        }
        return $window.decodeURIComponent(escape($window.atob(output))); //polyfill https://github.com/davidchambers/Base64.js
      };
  
  
      this.decodeToken = function(token) {
        var parts = token.split('.');
  
        if (parts.length !== 3) {
          throw new Error('JWT must have 3 parts');
        }
  
        var decoded = this.urlBase64Decode(parts[1]);
        if (!decoded) {
          throw new Error('Cannot decode the token');
        }
  
        return angular.fromJson(decoded);
      };
  
      this.getTokenExpirationDate = function(token) {
        var decoded = this.decodeToken(token);
  
        if(typeof decoded.exp === "undefined") {
          return null;
        }
  
        var d = new Date(0); // The 0 here is the key, which sets the date to the epoch
        d.setUTCSeconds(decoded.exp);
  
        return d;
      };
  
      this.isTokenExpired = function(token, offsetSeconds) {
        var d = this.getTokenExpirationDate(token);
        offsetSeconds = offsetSeconds || 0;
        if (d === null) {
          return false;
        }
  
        // Token expired?
        return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
      };
    }]);
  
  angular.module('angular-jwt.options', [])
    .provider('jwtOptions', function() {
      var globalConfig = {};
      this.config = function(value) {
        globalConfig = value;
      };
      this.$get = function() {
  
        var options = {
          urlParam: null,
          authHeader: 'Authorization',
          authPrefix: 'Bearer ',
          whiteListedDomains: [],
          tokenGetter: function() {
            return null;
          },
          loginPath: '/',
          unauthenticatedRedirectPath: '/',
          unauthenticatedRedirector: ['$location', function($location) {
            $location.path(this.unauthenticatedRedirectPath);
          }]
        };
  
        function JwtOptions() {
          var config = this.config = angular.extend({}, options, globalConfig);
        }
  
        JwtOptions.prototype.getConfig = function() {
          return this.config;
        };
  
        return new JwtOptions();
      }
    });
  
   /**
    * The content from this file was directly lifted from Angular. It is
    * unfortunately not a public API, so the best we can do is copy it.
    *
    * Angular References:
    *   https://github.com/angular/angular.js/issues/3299
    *   https://github.com/angular/angular.js/blob/d077966ff1ac18262f4615ff1a533db24d4432a7/src/ng/urlUtils.js
    */
  
   angular.module('angular-jwt.interceptor')
    .service('urlUtils', function () {
  
      // NOTE:  The usage of window and document instead of $window and $document here is
      // deliberate.  This service depends on the specific behavior of anchor nodes created by the
      // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and
      // cause us to break tests.  In addition, when the browser resolves a URL for XHR, it
      // doesn't know about mocked locations and resolves URLs to the real document - which is
      // exactly the behavior needed here.  There is little value is mocking these out for this
      // service.
      var urlParsingNode = document.createElement("a");
      var originUrl = urlResolve(window.location.href);
  
      /**
       *
       * Implementation Notes for non-IE browsers
       * ----------------------------------------
       * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
       * results both in the normalizing and parsing of the URL.  Normalizing means that a relative
       * URL will be resolved into an absolute URL in the context of the application document.
       * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
       * properties are all populated to reflect the normalized URL.  This approach has wide
       * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc.  See
       * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
       *
       * Implementation Notes for IE
       * ---------------------------
       * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
       * browsers.  However, the parsed components will not be set if the URL assigned did not specify
       * them.  (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.)  We
       * work around that by performing the parsing in a 2nd step by taking a previously normalized
       * URL (e.g. by assigning to a.href) and assigning it a.href again.  This correctly populates the
       * properties such as protocol, hostname, port, etc.
       *
       * References:
       *   http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
       *   http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
       *   http://url.spec.whatwg.org/#urlutils
       *   https://github.com/angular/angular.js/pull/2902
       *   http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
       *
       * @kind function
       * @param {string} url The URL to be parsed.
       * @description Normalizes and parses a URL.
       * @returns {object} Returns the normalized URL as a dictionary.
       *
       *   | member name   | Description    |
       *   |---------------|----------------|
       *   | href          | A normalized version of the provided URL if it was not an absolute URL |
       *   | protocol      | The protocol including the trailing colon                              |
       *   | host          | The host and port (if the port is non-default) of the normalizedUrl    |
       *   | search        | The search params, minus the question mark                             |
       *   | hash          | The hash string, minus the hash symbol
       *   | hostname      | The hostname
       *   | port          | The port, without ":"
       *   | pathname      | The pathname, beginning with "/"
       *
       */
      function urlResolve(url) {
        var href = url;
  
        // Normalize before parse.  Refer Implementation Notes on why this is
        // done in two steps on IE.
        urlParsingNode.setAttribute("href", href);
        href = urlParsingNode.href;
        urlParsingNode.setAttribute('href', href);
  
        // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
        return {
          href: urlParsingNode.href,
          protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
          host: urlParsingNode.host,
          search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
          hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
          hostname: urlParsingNode.hostname,
          port: urlParsingNode.port,
          pathname: (urlParsingNode.pathname.charAt(0) === '/')
            ? urlParsingNode.pathname
            : '/' + urlParsingNode.pathname
        };
      }
  
      /**
       * Parse a request URL and determine whether this is a same-origin request as the application document.
       *
       * @param {string|object} requestUrl The url of the request as a string that will be resolved
       * or a parsed URL object.
       * @returns {boolean} Whether the request is for the same origin as the application document.
       */
      function urlIsSameOrigin(requestUrl) {
        var parsed = (angular.isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
        return (parsed.protocol === originUrl.protocol &&
                parsed.host === originUrl.host);
      }
  
      return {
        urlResolve: urlResolve,
        isSameOrigin: urlIsSameOrigin
      };
  
    });
  
  }());