text: "use strict";
  
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.default = void 0;
  
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
  
  var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
  
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
  
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
  
  var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
  
  var _set = _interopRequireDefault(require("@babel/runtime/core-js/set"));
  
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
  
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
  
  var _url2 = require("url");
  
  var _EventEmitter = _interopRequireDefault(require("../EventEmitter"));
  
  var _shallowEquals = _interopRequireDefault(require("../shallow-equals"));
  
  var _pQueue = _interopRequireDefault(require("../p-queue"));
  
  var _utils = require("../utils");
  
  var _ = require("./");
  
  /* global __NEXT_DATA__ */
  var historyUnavailableWarning = (0, _utils.execOnce)(function () {
    (0, _utils.warn)("Warning: window.history is not available.");
  });
  var historyMethodWarning = (0, _utils.execOnce)(function (method) {
    (0, _utils.warn)("Warning: window.history.".concat(method, " is not available"));
  });
  
  var Router =
  /*#__PURE__*/
  function () {
    function Router(pathname, query, as) {
      var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
          initialProps = _ref.initialProps,
          pageLoader = _ref.pageLoader,
          App = _ref.App,
          Component = _ref.Component,
          ErrorComponent = _ref.ErrorComponent,
          err = _ref.err;
  
      (0, _classCallCheck2.default)(this, Router);
      // represents the current component key
      this.route = toRoute(pathname); // set up the component cache (by route keys)
  
      this.components = {}; // We should not keep the cache, if there's an error
      // Otherwise, this cause issues when when going back and
      // come again to the errored page.
  
      if (Component !== ErrorComponent) {
        this.components[this.route] = {
          Component: Component,
          props: initialProps,
          err: err
        };
      }
  
      this.components['/_app'] = {
        Component: App // Handling Router Events
  
      };
      this.events = new _EventEmitter.default();
      this.pageLoader = pageLoader;
      this.prefetchQueue = new _pQueue.default({
        concurrency: 2
      });
      this.ErrorComponent = ErrorComponent;
      this.pathname = pathname;
      this.query = query;
      this.asPath = as;
      this.subscriptions = new _set.default();
      this.componentLoadCancel = null;
      this.onPopState = this.onPopState.bind(this);
  
      this._beforePopState = function () {
        return true;
      };
  
      if (typeof window !== 'undefined') {
        // in order for `e.state` to work on the `onpopstate` event
        // we have to register the initial route upon initialization
        this.changeState('replaceState', (0, _url2.format)({
          pathname: pathname,
          query: query
        }), (0, _utils.getURL)());
        window.addEventListener('popstate', this.onPopState);
      }
    }
  
    (0, _createClass2.default)(Router, [{
      key: "onPopState",
      value: function onPopState(e) {
        if (!e.state) {
          // We get state as undefined for two reasons.
          //  1. With older safari (< 8) and older chrome (< 34)
          //  2. When the URL changed with #
          //
          // In the both cases, we don't need to proceed and change the route.
          // (as it's already changed)
          // But we can simply replace the state with the new changes.
          // Actually, for (1) we don't need to nothing. But it's hard to detect that event.
          // So, doing the following for (1) does no harm.
          var pathname = this.pathname,
              query = this.query;
          this.changeState('replaceState', (0, _url2.format)({
            pathname: pathname,
            query: query
          }), (0, _utils.getURL)());
          return;
        } // If the downstream application returns falsy, return.
        // They will then be responsible for handling the event.
  
  
        if (!this._beforePopState(e.state)) {
          return;
        }
  
        var _e$state = e.state,
            url = _e$state.url,
            as = _e$state.as,
            options = _e$state.options;
        this.replace(url, as, options);
      }
    }, {
      key: "update",
      value: function update(route, Component) {
        var data = this.components[route];
  
        if (!data) {
          throw new Error("Cannot update unavailable route: ".concat(route));
        }
  
        var newData = (0, _objectSpread2.default)({}, data, {
          Component: Component
        });
        this.components[route] = newData;
  
        if (route === this.route) {
          this.notify(newData);
        }
      }
    }, {
      key: "reload",
      value: function () {
        var _reload = (0, _asyncToGenerator2.default)(
        /*#__PURE__*/
        _regenerator.default.mark(function _callee(route) {
          var pathname, query, url, as, routeInfo, error;
          return _regenerator.default.wrap(function _callee$(_context) {
            while (1) {
              switch (_context.prev = _context.next) {
                case 0:
                  delete this.components[route];
                  this.pageLoader.clearCache(route);
  
                  if (!(route !== this.route)) {
                    _context.next = 4;
                    break;
                  }
  
                  return _context.abrupt("return");
  
                case 4:
                  pathname = this.pathname, query = this.query;
                  url = window.location.href; // This makes sure we only use pathname + query + hash, to mirror `asPath` coming from the server.
  
                  as = window.location.pathname + window.location.search + window.location.hash;
                  this.events.emit('routeChangeStart', url);
                  _context.next = 10;
                  return this.getRouteInfo(route, pathname, query, as);
  
                case 10:
                  routeInfo = _context.sent;
                  error = routeInfo.error;
  
                  if (!(error && error.cancelled)) {
                    _context.next = 14;
                    break;
                  }
  
                  return _context.abrupt("return");
  
                case 14:
                  this.notify(routeInfo);
  
                  if (!error) {
                    _context.next = 18;
                    break;
                  }
  
                  this.events.emit('routeChangeError', error, url);
                  throw error;
  
                case 18:
                  this.events.emit('routeChangeComplete', url);
  
                case 19:
                case "end":
                  return _context.stop();
              }
            }
          }, _callee, this);
        }));
  
        return function reload(_x) {
          return _reload.apply(this, arguments);
        };
      }()
    }, {
      key: "back",
      value: function back() {
        window.history.back();
      }
    }, {
      key: "push",
      value: function push(url) {
        var as = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : url;
        var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
        return this.change('pushState', url, as, options);
      }
    }, {
      key: "replace",
      value: function replace(url) {
        var as = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : url;
        var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
        return this.change('replaceState', url, as, options);
      }
    }, {
      key: "change",
      value: function () {
        var _change = (0, _asyncToGenerator2.default)(
        /*#__PURE__*/
        _regenerator.default.mark(function _callee2(method, _url, _as, options) {
          var url, as, _parse, pathname, query, route, _options$shallow, shallow, routeInfo, _routeInfo, error, hash;
  
          return _regenerator.default.wrap(function _callee2$(_context2) {
            while (1) {
              switch (_context2.prev = _context2.next) {
                case 0:
                  // If url and as provided as an object representation,
                  // we'll format them into the string version here.
                  url = (0, _typeof2.default)(_url) === 'object' ? (0, _url2.format)(_url) : _url;
                  as = (0, _typeof2.default)(_as) === 'object' ? (0, _url2.format)(_as) : _as; // Add the ending slash to the paths. So, we can serve the
                  // "<page>/index.html" directly for the SSR page.
  
                  if (__NEXT_DATA__.nextExport) {
                    as = (0, _._rewriteUrlForNextExport)(as);
                  }
  
                  this.abortComponentLoad(as); // If the url change is only related to a hash change
                  // We should not proceed. We should only change the state.
  
                  if (!this.onlyAHashChange(as)) {
                    _context2.next = 10;
                    break;
                  }
  
                  this.events.emit('hashChangeStart', as);
                  this.changeState(method, url, as);
                  this.scrollToHash(as);
                  this.events.emit('hashChangeComplete', as);
                  return _context2.abrupt("return", true);
  
                case 10:
                  _parse = (0, _url2.parse)(url, true), pathname = _parse.pathname, query = _parse.query; // If asked to change the current URL we should reload the current page
                  // (not location.reload() but reload getInitalProps and other Next.js stuffs)
                  // We also need to set the method = replaceState always
                  // as this should not go into the history (That's how browsers work)
  
                  if (!this.urlIsNew(pathname, query)) {
                    method = 'replaceState';
                  }
  
                  route = toRoute(pathname);
                  _options$shallow = options.shallow, shallow = _options$shallow === void 0 ? false : _options$shallow;
                  routeInfo = null;
                  this.events.emit('routeChangeStart', as); // If shallow === false and other conditions met, we reuse the
                  // existing routeInfo for this route.
                  // Because of this, getInitialProps would not run.
  
                  if (!(shallow && this.isShallowRoutingPossible(route))) {
                    _context2.next = 20;
                    break;
                  }
  
                  routeInfo = this.components[route];
                  _context2.next = 23;
                  break;
  
                case 20:
                  _context2.next = 22;
                  return this.getRouteInfo(route, pathname, query, as);
  
                case 22:
                  routeInfo = _context2.sent;
  
                case 23:
                  _routeInfo = routeInfo, error = _routeInfo.error;
  
                  if (!(error && error.cancelled)) {
                    _context2.next = 26;
                    break;
                  }
  
                  return _context2.abrupt("return", false);
  
                case 26:
                  this.events.emit('beforeHistoryChange', as);
                  this.changeState(method, url, as, options);
                  hash = window.location.hash.substring(1);
                  this.set(route, pathname, query, as, (0, _objectSpread2.default)({}, routeInfo, {
                    hash: hash
                  }));
  
                  if (!error) {
                    _context2.next = 33;
                    break;
                  }
  
                  this.events.emit('routeChangeError', error, as);
                  throw error;
  
                case 33:
                  this.events.emit('routeChangeComplete', as);
                  return _context2.abrupt("return", true);
  
                case 35:
                case "end":
                  return _context2.stop();
              }
            }
          }, _callee2, this);
        }));
  
        return function change(_x2, _x3, _x4, _x5) {
          return _change.apply(this, arguments);
        };
      }()
    }, {
      key: "changeState",
      value: function changeState(method, url, as) {
        var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  
        if (typeof window.history === 'undefined') {
          historyUnavailableWarning();
          return;
        }
  
        if (typeof window.history[method] === 'undefined') {
          historyMethodWarning(method);
          return;
        }
  
        if (method !== 'pushState' || (0, _utils.getURL)() !== as) {
          window.history[method]({
            url: url,
            as: as,
            options: options
          }, null, as);
        }
      }
    }, {
      key: "getRouteInfo",
      value: function () {
        var _getRouteInfo = (0, _asyncToGenerator2.default)(
        /*#__PURE__*/
        _regenerator.default.mark(function _callee3(route, pathname, query, as) {
          var routeInfo, _routeInfo2, Component, ctx, _Component, _ctx;
  
          return _regenerator.default.wrap(function _callee3$(_context3) {
            while (1) {
              switch (_context3.prev = _context3.next) {
                case 0:
                  routeInfo = null;
                  _context3.prev = 1;
                  routeInfo = this.components[route];
  
                  if (routeInfo) {
                    _context3.next = 8;
                    break;
                  }
  
                  _context3.next = 6;
                  return this.fetchComponent(route, as);
  
                case 6:
                  _context3.t0 = _context3.sent;
                  routeInfo = {
                    Component: _context3.t0
                  };
  
                case 8:
                  _routeInfo2 = routeInfo, Component = _routeInfo2.Component;
  
                  if (!(typeof Component !== 'function')) {
                    _context3.next = 11;
                    break;
                  }
  
                  throw new Error("The default export is not a React Component in page: \"".concat(pathname, "\""));
  
                case 11:
                  ctx = {
                    pathname: pathname,
                    query: query,
                    asPath: as
                  };
                  _context3.next = 14;
                  return this.getInitialProps(Component, ctx);
  
                case 14:
                  routeInfo.props = _context3.sent;
                  this.components[route] = routeInfo;
                  _context3.next = 40;
                  break;
  
                case 18:
                  _context3.prev = 18;
                  _context3.t1 = _context3["catch"](1);
  
                  if (!(_context3.t1.code === 'PAGE_LOAD_ERROR')) {
                    _context3.next = 24;
                    break;
                  }
  
                  // If we can't load the page it could be one of following reasons
                  //  1. Page doesn't exists
                  //  2. Page does exist in a different zone
                  //  3. Internal error while loading the page
                  // So, doing a hard reload is the proper way to deal with this.
                  window.location.href = as; // Changing the URL doesn't block executing the current code path.
                  // So, we need to mark it as a cancelled error and stop the routing logic.
  
                  _context3.t1.cancelled = true;
                  return _context3.abrupt("return", {
                    error: _context3.t1
                  });
  
                case 24:
                  if (!_context3.t1.cancelled) {
                    _context3.next = 26;
                    break;
                  }
  
                  return _context3.abrupt("return", {
                    error: _context3.t1
                  });
  
                case 26:
                  _Component = this.ErrorComponent;
                  routeInfo = {
                    Component: _Component,
                    err: _context3.t1
                  };
                  _ctx = {
                    err: _context3.t1,
                    pathname: pathname,
                    query: query
                  };
                  _context3.prev = 29;
                  _context3.next = 32;
                  return this.getInitialProps(_Component, _ctx);
  
                case 32:
                  routeInfo.props = _context3.sent;
                  _context3.next = 39;
                  break;
  
                case 35:
                  _context3.prev = 35;
                  _context3.t2 = _context3["catch"](29);
                  console.error('Error in error page `getInitialProps`: ', _context3.t2);
                  routeInfo.props = {};
  
                case 39:
                  routeInfo.error = _context3.t1;
  
                case 40:
                  return _context3.abrupt("return", routeInfo);
  
                case 41:
                case "end":
                  return _context3.stop();
              }
            }
          }, _callee3, this, [[1, 18], [29, 35]]);
        }));
  
        return function getRouteInfo(_x6, _x7, _x8, _x9) {
          return _getRouteInfo.apply(this, arguments);
        };
      }()
    }, {
      key: "set",
      value: function set(route, pathname, query, as, data) {
        this.route = route;
        this.pathname = pathname;
        this.query = query;
        this.asPath = as;
        this.notify(data);
      }
    }, {
      key: "beforePopState",
      value: function beforePopState(cb) {
        this._beforePopState = cb;
      }
    }, {
      key: "onlyAHashChange",
      value: function onlyAHashChange(as) {
        if (!this.asPath) return false;
  
        var _asPath$split = this.asPath.split('#'),
            _asPath$split2 = (0, _slicedToArray2.default)(_asPath$split, 2),
            oldUrlNoHash = _asPath$split2[0],
            oldHash = _asPath$split2[1];
  
        var _as$split = as.split('#'),
            _as$split2 = (0, _slicedToArray2.default)(_as$split, 2),
            newUrlNoHash = _as$split2[0],
            newHash = _as$split2[1]; // Makes sure we scroll to the provided hash if the url/hash are the same
  
  
        if (newHash && oldUrlNoHash === newUrlNoHash && oldHash === newHash) {
          return true;
        } // If the urls are change, there's more than a hash change
  
  
        if (oldUrlNoHash !== newUrlNoHash) {
          return false;
        } // If the hash has changed, then it's a hash only change.
        // This check is necessary to handle both the enter and
        // leave hash === '' cases. The identity case falls through
        // and is treated as a next reload.
  
  
        return oldHash !== newHash;
      }
    }, {
      key: "scrollToHash",
      value: function scrollToHash(as) {
        var _as$split3 = as.split('#'),
            _as$split4 = (0, _slicedToArray2.default)(_as$split3, 2),
            hash = _as$split4[1]; // Scroll to top if the hash is just `#` with no value
  
  
        if (hash === '') {
          window.scrollTo(0, 0);
          return;
        }
  
        var el = document.getElementById(hash);
  
        if (el) {
          el.scrollIntoView();
        }
      }
    }, {
      key: "urlIsNew",
      value: function urlIsNew(pathname, query) {
        return this.pathname !== pathname || !(0, _shallowEquals.default)(query, this.query);
      }
    }, {
      key: "isShallowRoutingPossible",
      value: function isShallowRoutingPossible(route) {
        return (// If there's cached routeInfo for the route.
          Boolean(this.components[route]) && // If the route is already rendered on the screen.
          this.route === route
        );
      }
    }, {
      key: "prefetch",
      value: function () {
        var _prefetch = (0, _asyncToGenerator2.default)(
        /*#__PURE__*/
        _regenerator.default.mark(function _callee4(url) {
          var _this = this;
  
          var _parse2, pathname, route;
  
          return _regenerator.default.wrap(function _callee4$(_context4) {
            while (1) {
              switch (_context4.prev = _context4.next) {
                case 0:
                  if (!(process.env.NODE_ENV === 'development')) {
                    _context4.next = 2;
                    break;
                  }
  
                  return _context4.abrupt("return");
  
                case 2:
                  _parse2 = (0, _url2.parse)(url), pathname = _parse2.pathname;
                  route = toRoute(pathname);
                  return _context4.abrupt("return", this.prefetchQueue.add(function () {
                    return _this.fetchRoute(route);
                  }));
  
                case 5:
                case "end":
                  return _context4.stop();
              }
            }
          }, _callee4, this);
        }));
  
        return function prefetch(_x10) {
          return _prefetch.apply(this, arguments);
        };
      }()
    }, {
      key: "fetchComponent",
      value: function () {
        var _fetchComponent = (0, _asyncToGenerator2.default)(
        /*#__PURE__*/
        _regenerator.default.mark(function _callee5(route, as) {
          var cancelled, cancel, Component, error;
          return _regenerator.default.wrap(function _callee5$(_context5) {
            while (1) {
              switch (_context5.prev = _context5.next) {
                case 0:
                  cancelled = false;
  
                  cancel = this.componentLoadCancel = function () {
                    cancelled = true;
                  };
  
                  _context5.next = 4;
                  return this.fetchRoute(route);
  
                case 4:
                  Component = _context5.sent;
  
                  if (!cancelled) {
                    _context5.next = 9;
                    break;
                  }
  
                  error = new Error("Abort fetching component for route: \"".concat(route, "\""));
                  error.cancelled = true;
                  throw error;
  
                case 9:
                  if (cancel === this.componentLoadCancel) {
                    this.componentLoadCancel = null;
                  }
  
                  return _context5.abrupt("return", Component);
  
                case 11:
                case "end":
                  return _context5.stop();
              }
            }
          }, _callee5, this);
        }));
  
        return function fetchComponent(_x11, _x12) {
          return _fetchComponent.apply(this, arguments);
        };
      }()
    }, {
      key: "getInitialProps",
      value: function () {
        var _getInitialProps = (0, _asyncToGenerator2.default)(
        /*#__PURE__*/
        _regenerator.default.mark(function _callee6(Component, ctx) {
          var cancelled, cancel, App, props, err;
          return _regenerator.default.wrap(function _callee6$(_context6) {
            while (1) {
              switch (_context6.prev = _context6.next) {
                case 0:
                  cancelled = false;
  
                  cancel = function cancel() {
                    cancelled = true;
                  };
  
                  this.componentLoadCancel = cancel;
                  App = this.components['/_app'].Component;
                  _context6.next = 6;
                  return (0, _utils.loadGetInitialProps)(App, {
                    Component: Component,
                    router: this,
                    ctx: ctx
                  });
  
                case 6:
                  props = _context6.sent;
  
                  if (cancel === this.componentLoadCancel) {
                    this.componentLoadCancel = null;
                  }
  
                  if (!cancelled) {
                    _context6.next = 12;
                    break;
                  }
  
                  err = new Error('Loading initial props cancelled');
                  err.cancelled = true;
                  throw err;
  
                case 12:
                  return _context6.abrupt("return", props);
  
                case 13:
                case "end":
                  return _context6.stop();
              }
            }
          }, _callee6, this);
        }));
  
        return function getInitialProps(_x13, _x14) {
          return _getInitialProps.apply(this, arguments);
        };
      }()
    }, {
      key: "fetchRoute",
      value: function () {
        var _fetchRoute = (0, _asyncToGenerator2.default)(
        /*#__PURE__*/
        _regenerator.default.mark(function _callee7(route) {
          return _regenerator.default.wrap(function _callee7$(_context7) {
            while (1) {
              switch (_context7.prev = _context7.next) {
                case 0:
                  return _context7.abrupt("return", this.pageLoader.loadPage(route));
  
                case 1:
                case "end":
                  return _context7.stop();
              }
            }
          }, _callee7, this);
        }));
  
        return function fetchRoute(_x15) {
          return _fetchRoute.apply(this, arguments);
        };
      }()
    }, {
      key: "abortComponentLoad",
      value: function abortComponentLoad(as) {
        if (this.componentLoadCancel) {
          this.events.emit('routeChangeError', new Error('Route Cancelled'), as);
          this.componentLoadCancel();
          this.componentLoadCancel = null;
        }
      }
    }, {
      key: "notify",
      value: function notify(data) {
        var App = this.components['/_app'].Component;
        this.subscriptions.forEach(function (fn) {
          return fn((0, _objectSpread2.default)({}, data, {
            App: App
          }));
        });
      }
    }, {
      key: "subscribe",
      value: function subscribe(fn) {
        var _this2 = this;
  
        this.subscriptions.add(fn);
        return function () {
          return _this2.subscriptions.delete(fn);
        };
      }
    }]);
    return Router;
  }();
  
  exports.default = Router;
  
  function toRoute(path) {
    return path.replace(/\/$/, '') || '/';
  }