router5-plugin-browser
Version:
Router5 browser plugin
271 lines (262 loc) • 11.4 kB
JavaScript
import { errorCodes, constants } from 'router5';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
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
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}
var value = function (arg) { return function () { return arg; }; };
var noop = function () { };
var isBrowser = typeof window !== 'undefined' && window.history;
var getBase = function () { return window.location.pathname; };
var supportsPopStateOnHashChange = function () {
return window.navigator.userAgent.indexOf('Trident') === -1;
};
var pushState = function (state, title, path) {
return window.history.pushState(state, title, path);
};
var replaceState = function (state, title, path) {
return window.history.replaceState(state, title, path);
};
var addPopstateListener = function (fn, opts) {
var shouldAddHashChangeListener = opts.useHash && !supportsPopStateOnHashChange();
window.addEventListener('popstate', fn);
if (shouldAddHashChangeListener) {
window.addEventListener('hashchange', fn);
}
return function () {
window.removeEventListener('popstate', fn);
if (shouldAddHashChangeListener) {
window.removeEventListener('hashchange', fn);
}
};
};
var getLocation = function (opts) {
var path = opts.useHash
? window.location.hash.replace(new RegExp('^#' + opts.hashPrefix), '')
: window.location.pathname.replace(new RegExp('^' + opts.base), '');
// Fix issue with browsers that don't URL encode characters (Edge)
var correctedPath = safelyEncodePath(path);
return (correctedPath || '/') + window.location.search;
};
var safelyEncodePath = function (path) {
try {
return encodeURI(decodeURI(path));
}
catch (_) {
return path;
}
};
var getState = function () { return window.history.state; };
var getHash = function () { return window.location.hash; };
var browser = {};
if (isBrowser) {
browser = {
getBase: getBase,
pushState: pushState,
replaceState: replaceState,
addPopstateListener: addPopstateListener,
getLocation: getLocation,
getState: getState,
getHash: getHash
};
}
else {
browser = {
getBase: value(''),
pushState: noop,
replaceState: noop,
addPopstateListener: noop,
getLocation: value(''),
getState: value(null),
getHash: value('')
};
}
var safeBrowser = browser;
var defaultOptions = {
forceDeactivate: true,
useHash: false,
hashPrefix: '',
base: '',
mergeState: false,
preserveHash: true
};
var source = 'popstate';
function browserPluginFactory(opts, browser) {
if (browser === void 0) { browser = safeBrowser; }
var options = __assign(__assign({}, defaultOptions), opts);
var transitionOptions = {
forceDeactivate: options.forceDeactivate,
source: source
};
var removePopStateListener;
return function browserPlugin(router) {
var routerOptions = router.getOptions();
var routerStart = router.start;
router.buildUrl = function (route, params) {
var base = options.base || '';
var prefix = options.useHash ? "#" + options.hashPrefix : '';
var path = router.buildPath(route, params);
return base + prefix + path;
};
var urlToPath = function (url) {
var match = url.match(/^(?:http|https):\/\/(?:[0-9a-z_\-.:]+?)(?=\/)(.*)$/);
var path = match ? match[1] : url;
var pathParts = path.match(/^(.+?)(#.+?)?(\?.+)?$/);
if (!pathParts)
throw new Error("[router5] Could not parse url " + url);
var pathname = pathParts[1];
var hash = pathParts[2] || '';
var search = pathParts[3] || '';
return ((options.useHash
? hash.replace(new RegExp('^#' + options.hashPrefix), '')
: options.base
? pathname.replace(new RegExp('^' + options.base), '')
: pathname) + search);
};
router.matchUrl = function (url) { return router.matchPath(urlToPath(url)); };
router.start = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (args.length === 0 || typeof args[0] === 'function') {
routerStart.apply(void 0, __spreadArrays([browser.getLocation(options)], args));
}
else {
routerStart.apply(void 0, args);
}
return router;
};
router.replaceHistoryState = function (name, params, title) {
if (params === void 0) { params = {}; }
if (title === void 0) { title = ''; }
var route = router.buildState(name, params);
var state = router.makeState(route.name, route.params, router.buildPath(route.name, route.params), { params: route.meta });
var url = router.buildUrl(name, params);
router.lastKnownState = state;
browser.replaceState(state, title, url);
};
function updateBrowserState(state, url, replace) {
var trimmedState = state
? {
meta: state.meta,
name: state.name,
params: state.params,
path: state.path
}
: state;
var finalState = options.mergeState === true
? __assign(__assign({}, browser.getState()), trimmedState) : trimmedState;
if (replace)
browser.replaceState(finalState, '', url);
else
browser.pushState(finalState, '', url);
}
function onPopState(evt) {
var routerState = router.getState();
// Do nothing if no state or if last know state is poped state (it should never happen)
var newState = !evt.state || !evt.state.name;
var state = newState
? router.matchPath(browser.getLocation(options), source)
: router.makeState(evt.state.name, evt.state.params, evt.state.path, __assign(__assign({}, evt.state.meta), { source: source }), evt.state.meta.id);
var defaultRoute = routerOptions.defaultRoute, defaultParams = routerOptions.defaultParams;
if (!state) {
// If current state is already the default route, we will have a double entry
// Navigating back and forth will emit SAME_STATES error
defaultRoute &&
router.navigateToDefault(__assign(__assign({}, transitionOptions), { reload: true, replace: true }));
return;
}
if (routerState &&
router.areStatesEqual(state, routerState, false)) {
return;
}
router.transitionToState(state, routerState, transitionOptions, function (err, toState) {
if (err) {
if (err.redirect) {
var _a = err.redirect, name_1 = _a.name, params = _a.params;
router.navigate(name_1, params, __assign(__assign({}, transitionOptions), { replace: true, force: true, redirected: true }));
}
else if (err.code === errorCodes.CANNOT_DEACTIVATE) {
var url = router.buildUrl(routerState.name, routerState.params);
if (!newState) {
// Keep history state unchanged but use current URL
updateBrowserState(state, url, true);
}
// else do nothing or history will be messed up
// TODO: history.back()?
}
else {
// Force navigation to default state
defaultRoute &&
router.navigate(defaultRoute, defaultParams, __assign(__assign({}, transitionOptions), { reload: true, replace: true }));
}
}
else {
router.invokeEventListeners(constants.TRANSITION_SUCCESS, toState, routerState, { replace: true });
}
});
}
function onStart() {
if (options.useHash && !options.base) {
// Guess base
options.base = browser.getBase();
}
removePopStateListener = browser.addPopstateListener(onPopState, options);
}
function teardown() {
if (removePopStateListener) {
removePopStateListener();
removePopStateListener = undefined;
}
}
function onTransitionSuccess(toState, fromState, opts) {
var historyState = browser.getState();
var hasState = historyState &&
historyState.meta &&
historyState.name &&
historyState.params;
var statesAreEqual = fromState && router.areStatesEqual(fromState, toState, false);
var replace = opts.replace || !hasState || statesAreEqual;
var url = router.buildUrl(toState.name, toState.params);
if (fromState === null &&
options.useHash === false &&
options.preserveHash === true) {
url += browser.getHash();
}
updateBrowserState(toState, url, replace);
}
return {
onStart: onStart,
onStop: teardown,
teardown: teardown,
onTransitionSuccess: onTransitionSuccess,
onPopState: onPopState
};
};
}
export default browserPluginFactory;