@tinkoff/router 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/react/context.browser.js +7 -0
- package/lib/components/react/context.es.js +7 -0
- package/lib/components/react/context.js +13 -0
- package/lib/components/react/provider.browser.js +12 -0
- package/lib/components/react/provider.es.js +12 -0
- package/lib/components/react/provider.js +16 -0
- package/lib/components/react/useNavigate.browser.js +17 -0
- package/lib/components/react/useNavigate.es.js +17 -0
- package/lib/components/react/useNavigate.js +21 -0
- package/lib/components/react/useRoute.browser.js +8 -0
- package/lib/components/react/useRoute.es.js +8 -0
- package/lib/components/react/useRoute.js +12 -0
- package/lib/components/react/useRouter.browser.js +8 -0
- package/lib/components/react/useRouter.es.js +8 -0
- package/lib/components/react/useRouter.js +12 -0
- package/lib/components/react/useUrl.browser.js +8 -0
- package/lib/components/react/useUrl.es.js +8 -0
- package/lib/components/react/useUrl.js +12 -0
- package/lib/history/base.browser.js +11 -0
- package/lib/history/base.es.js +11 -0
- package/lib/history/base.js +15 -0
- package/lib/history/client.browser.js +121 -0
- package/lib/history/client.es.js +121 -0
- package/lib/history/client.js +125 -0
- package/lib/history/server.es.js +15 -0
- package/lib/history/server.js +19 -0
- package/lib/history/wrapper.browser.js +72 -0
- package/lib/history/wrapper.es.js +72 -0
- package/lib/history/wrapper.js +80 -0
- package/lib/index.browser.js +12 -1169
- package/lib/index.es.js +12 -1097
- package/lib/index.js +36 -1124
- package/lib/logger.browser.js +15 -0
- package/lib/logger.es.js +15 -0
- package/lib/logger.js +23 -0
- package/lib/router/abstract.browser.js +395 -0
- package/lib/router/abstract.es.js +395 -0
- package/lib/router/abstract.js +404 -0
- package/lib/router/browser.browser.js +166 -0
- package/lib/router/client.browser.js +129 -0
- package/lib/router/client.d.ts +1 -0
- package/lib/router/client.es.js +129 -0
- package/lib/router/client.js +133 -0
- package/lib/router/clientNoSpa.browser.js +17 -0
- package/lib/router/clientNoSpa.es.js +17 -0
- package/lib/router/clientNoSpa.js +21 -0
- package/lib/router/server.es.js +85 -0
- package/lib/router/server.js +89 -0
- package/lib/tree/constants.browser.js +6 -0
- package/lib/tree/constants.es.js +6 -0
- package/lib/tree/constants.js +13 -0
- package/lib/tree/tree.browser.js +148 -0
- package/lib/tree/tree.es.js +148 -0
- package/lib/tree/tree.js +158 -0
- package/lib/tree/utils.browser.js +77 -0
- package/lib/tree/utils.es.js +77 -0
- package/lib/tree/utils.js +90 -0
- package/lib/utils.browser.js +35 -0
- package/lib/utils.es.js +35 -0
- package/lib/utils.js +48 -0
- package/package.json +5 -6
package/lib/router/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Options } from './abstract';
|
|
|
2
2
|
import { AbstractRouter } from './abstract';
|
|
3
3
|
import type { Navigation, NavigateOptions } from '../types';
|
|
4
4
|
export declare abstract class ClientRouter extends AbstractRouter {
|
|
5
|
+
protected fullRehydrationInProcess: boolean;
|
|
5
6
|
constructor(options: Options);
|
|
6
7
|
rehydrate(navigation: Navigation): Promise<void>;
|
|
7
8
|
protected resolveRoute(...options: Parameters<AbstractRouter['resolveRoute']>): import("../types").NavigationRoute;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { parse } from '@tinkoff/url';
|
|
2
|
+
import { AbstractRouter } from './abstract.es.js';
|
|
3
|
+
import { makeNavigateOptions, isSameHost } from '../utils.es.js';
|
|
4
|
+
import { logger } from '../logger.es.js';
|
|
5
|
+
import { ClientHistory } from '../history/client.es.js';
|
|
6
|
+
|
|
7
|
+
class ClientRouter extends AbstractRouter {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
// this flag for cases when we don't have initial router state from server - CSR fallback initialization
|
|
11
|
+
this.fullRehydrationInProcess = null;
|
|
12
|
+
this.history = new ClientHistory();
|
|
13
|
+
this.history.listen(async ({ type, url, navigateState, replace, history }) => {
|
|
14
|
+
var _a;
|
|
15
|
+
const currentUrl = this.getCurrentUrl();
|
|
16
|
+
const { pathname, query } = this.resolveUrl({ url });
|
|
17
|
+
const isSameUrlNavigation = currentUrl.pathname === pathname;
|
|
18
|
+
if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
|
|
19
|
+
const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
|
|
20
|
+
await this.internalUpdateCurrentRoute({
|
|
21
|
+
params: route === null || route === void 0 ? void 0 : route.params,
|
|
22
|
+
query,
|
|
23
|
+
replace,
|
|
24
|
+
navigateState,
|
|
25
|
+
}, { history });
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
await this.internalNavigate({
|
|
29
|
+
url,
|
|
30
|
+
replace,
|
|
31
|
+
navigateState,
|
|
32
|
+
}, { history });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async rehydrate(navigation) {
|
|
37
|
+
this.fullRehydrationInProcess = !navigation.to;
|
|
38
|
+
logger.debug({
|
|
39
|
+
event: 'rehydrate',
|
|
40
|
+
navigation,
|
|
41
|
+
});
|
|
42
|
+
const url = parse(window.location.href);
|
|
43
|
+
this.currentNavigation = {
|
|
44
|
+
...navigation,
|
|
45
|
+
type: 'navigate',
|
|
46
|
+
url,
|
|
47
|
+
};
|
|
48
|
+
this.lastNavigation = this.currentNavigation;
|
|
49
|
+
if (this.fullRehydrationInProcess) {
|
|
50
|
+
await this.runHooks('beforeResolve', this.currentNavigation);
|
|
51
|
+
const to = this.resolveRoute({ url }, { wildcard: true });
|
|
52
|
+
const redirect = to === null || to === void 0 ? void 0 : to.redirect;
|
|
53
|
+
this.currentNavigation.to = to;
|
|
54
|
+
if (redirect) {
|
|
55
|
+
return this.redirect(this.currentNavigation, makeNavigateOptions(redirect));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// rerun guard check in case it differs from server side
|
|
59
|
+
await this.runGuards(this.currentNavigation);
|
|
60
|
+
// and init any history listeners
|
|
61
|
+
this.history.init(this.currentNavigation);
|
|
62
|
+
if (this.fullRehydrationInProcess) {
|
|
63
|
+
this.runSyncHooks('change', this.currentNavigation);
|
|
64
|
+
}
|
|
65
|
+
this.currentNavigation = null;
|
|
66
|
+
// add dehydrated route to tree to prevent its loading
|
|
67
|
+
if (!this.fullRehydrationInProcess) {
|
|
68
|
+
this.addRoute({
|
|
69
|
+
name: navigation.to.name,
|
|
70
|
+
path: navigation.to.path,
|
|
71
|
+
config: navigation.to.config,
|
|
72
|
+
// in case we have loaded page from some path-changing proxy
|
|
73
|
+
// save this actual path as alias
|
|
74
|
+
alias: url.pathname,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
this.fullRehydrationInProcess = null;
|
|
78
|
+
}
|
|
79
|
+
resolveRoute(...options) {
|
|
80
|
+
const { url } = options[0];
|
|
81
|
+
// navigation for other hosts should be considered as external navigation
|
|
82
|
+
if (url && !isSameHost(url)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return super.resolveRoute(...options);
|
|
86
|
+
}
|
|
87
|
+
async notfound(navigation) {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
await super.notfound(navigation);
|
|
90
|
+
// in case we didn't find any matched route just force hard page navigation
|
|
91
|
+
const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
|
|
92
|
+
const nextUrl = navigation.url.href;
|
|
93
|
+
const isNoSpaNavigation = navigation.from && !navigation.to;
|
|
94
|
+
// prevent redirect cycle on the same page,
|
|
95
|
+
// except cases when we run no-spa navigations,
|
|
96
|
+
// because we need hard reload in this cases
|
|
97
|
+
if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
|
|
98
|
+
if (navigation.replace) {
|
|
99
|
+
window.location.replace(nextUrl);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
window.location.assign(nextUrl);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
106
|
+
return new Promise(() => { });
|
|
107
|
+
}
|
|
108
|
+
async block(navigation) {
|
|
109
|
+
return this.notfound(navigation);
|
|
110
|
+
}
|
|
111
|
+
async redirect(navigation, target) {
|
|
112
|
+
await super.redirect(navigation, target);
|
|
113
|
+
// on CSR fallback initialization, if we found a redirect,
|
|
114
|
+
// we need to make hard reload for prevent current page rendering
|
|
115
|
+
if (this.fullRehydrationInProcess) {
|
|
116
|
+
window.location.replace(target.url);
|
|
117
|
+
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
118
|
+
return new Promise(() => { });
|
|
119
|
+
}
|
|
120
|
+
return this.internalNavigate({
|
|
121
|
+
...target,
|
|
122
|
+
replace: target.replace || navigation.replace,
|
|
123
|
+
}, {
|
|
124
|
+
redirect: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { ClientRouter };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var url = require('@tinkoff/url');
|
|
6
|
+
var abstract = require('./abstract.js');
|
|
7
|
+
var utils = require('../utils.js');
|
|
8
|
+
var logger = require('../logger.js');
|
|
9
|
+
var client = require('../history/client.js');
|
|
10
|
+
|
|
11
|
+
class ClientRouter extends abstract.AbstractRouter {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options);
|
|
14
|
+
// this flag for cases when we don't have initial router state from server - CSR fallback initialization
|
|
15
|
+
this.fullRehydrationInProcess = null;
|
|
16
|
+
this.history = new client.ClientHistory();
|
|
17
|
+
this.history.listen(async ({ type, url, navigateState, replace, history }) => {
|
|
18
|
+
var _a;
|
|
19
|
+
const currentUrl = this.getCurrentUrl();
|
|
20
|
+
const { pathname, query } = this.resolveUrl({ url });
|
|
21
|
+
const isSameUrlNavigation = currentUrl.pathname === pathname;
|
|
22
|
+
if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
|
|
23
|
+
const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
|
|
24
|
+
await this.internalUpdateCurrentRoute({
|
|
25
|
+
params: route === null || route === void 0 ? void 0 : route.params,
|
|
26
|
+
query,
|
|
27
|
+
replace,
|
|
28
|
+
navigateState,
|
|
29
|
+
}, { history });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
await this.internalNavigate({
|
|
33
|
+
url,
|
|
34
|
+
replace,
|
|
35
|
+
navigateState,
|
|
36
|
+
}, { history });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async rehydrate(navigation) {
|
|
41
|
+
this.fullRehydrationInProcess = !navigation.to;
|
|
42
|
+
logger.logger.debug({
|
|
43
|
+
event: 'rehydrate',
|
|
44
|
+
navigation,
|
|
45
|
+
});
|
|
46
|
+
const url$1 = url.parse(window.location.href);
|
|
47
|
+
this.currentNavigation = {
|
|
48
|
+
...navigation,
|
|
49
|
+
type: 'navigate',
|
|
50
|
+
url: url$1,
|
|
51
|
+
};
|
|
52
|
+
this.lastNavigation = this.currentNavigation;
|
|
53
|
+
if (this.fullRehydrationInProcess) {
|
|
54
|
+
await this.runHooks('beforeResolve', this.currentNavigation);
|
|
55
|
+
const to = this.resolveRoute({ url: url$1 }, { wildcard: true });
|
|
56
|
+
const redirect = to === null || to === void 0 ? void 0 : to.redirect;
|
|
57
|
+
this.currentNavigation.to = to;
|
|
58
|
+
if (redirect) {
|
|
59
|
+
return this.redirect(this.currentNavigation, utils.makeNavigateOptions(redirect));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// rerun guard check in case it differs from server side
|
|
63
|
+
await this.runGuards(this.currentNavigation);
|
|
64
|
+
// and init any history listeners
|
|
65
|
+
this.history.init(this.currentNavigation);
|
|
66
|
+
if (this.fullRehydrationInProcess) {
|
|
67
|
+
this.runSyncHooks('change', this.currentNavigation);
|
|
68
|
+
}
|
|
69
|
+
this.currentNavigation = null;
|
|
70
|
+
// add dehydrated route to tree to prevent its loading
|
|
71
|
+
if (!this.fullRehydrationInProcess) {
|
|
72
|
+
this.addRoute({
|
|
73
|
+
name: navigation.to.name,
|
|
74
|
+
path: navigation.to.path,
|
|
75
|
+
config: navigation.to.config,
|
|
76
|
+
// in case we have loaded page from some path-changing proxy
|
|
77
|
+
// save this actual path as alias
|
|
78
|
+
alias: url$1.pathname,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
this.fullRehydrationInProcess = null;
|
|
82
|
+
}
|
|
83
|
+
resolveRoute(...options) {
|
|
84
|
+
const { url } = options[0];
|
|
85
|
+
// navigation for other hosts should be considered as external navigation
|
|
86
|
+
if (url && !utils.isSameHost(url)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
return super.resolveRoute(...options);
|
|
90
|
+
}
|
|
91
|
+
async notfound(navigation) {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
await super.notfound(navigation);
|
|
94
|
+
// in case we didn't find any matched route just force hard page navigation
|
|
95
|
+
const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
|
|
96
|
+
const nextUrl = navigation.url.href;
|
|
97
|
+
const isNoSpaNavigation = navigation.from && !navigation.to;
|
|
98
|
+
// prevent redirect cycle on the same page,
|
|
99
|
+
// except cases when we run no-spa navigations,
|
|
100
|
+
// because we need hard reload in this cases
|
|
101
|
+
if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
|
|
102
|
+
if (navigation.replace) {
|
|
103
|
+
window.location.replace(nextUrl);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
window.location.assign(nextUrl);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
110
|
+
return new Promise(() => { });
|
|
111
|
+
}
|
|
112
|
+
async block(navigation) {
|
|
113
|
+
return this.notfound(navigation);
|
|
114
|
+
}
|
|
115
|
+
async redirect(navigation, target) {
|
|
116
|
+
await super.redirect(navigation, target);
|
|
117
|
+
// on CSR fallback initialization, if we found a redirect,
|
|
118
|
+
// we need to make hard reload for prevent current page rendering
|
|
119
|
+
if (this.fullRehydrationInProcess) {
|
|
120
|
+
window.location.replace(target.url);
|
|
121
|
+
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
122
|
+
return new Promise(() => { });
|
|
123
|
+
}
|
|
124
|
+
return this.internalNavigate({
|
|
125
|
+
...target,
|
|
126
|
+
replace: target.replace || navigation.replace,
|
|
127
|
+
}, {
|
|
128
|
+
redirect: true,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
exports.ClientRouter = ClientRouter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ClientRouter } from './client.browser.js';
|
|
2
|
+
|
|
3
|
+
const omitHash = (url) => url.href.replace(/#.*$/, '');
|
|
4
|
+
class NoSpaRouter extends ClientRouter {
|
|
5
|
+
run(navigation) {
|
|
6
|
+
const { type, fromUrl, url } = navigation;
|
|
7
|
+
// support only updateCurrentRoute or hash navigations
|
|
8
|
+
if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
|
|
9
|
+
return super.run(navigation);
|
|
10
|
+
}
|
|
11
|
+
return this.notfound(navigation);
|
|
12
|
+
}
|
|
13
|
+
// do not call any hooks as it is only supports url updates with hash
|
|
14
|
+
async runHooks() { }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { NoSpaRouter };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ClientRouter } from './client.es.js';
|
|
2
|
+
|
|
3
|
+
const omitHash = (url) => url.href.replace(/#.*$/, '');
|
|
4
|
+
class NoSpaRouter extends ClientRouter {
|
|
5
|
+
run(navigation) {
|
|
6
|
+
const { type, fromUrl, url } = navigation;
|
|
7
|
+
// support only updateCurrentRoute or hash navigations
|
|
8
|
+
if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
|
|
9
|
+
return super.run(navigation);
|
|
10
|
+
}
|
|
11
|
+
return this.notfound(navigation);
|
|
12
|
+
}
|
|
13
|
+
// do not call any hooks as it is only supports url updates with hash
|
|
14
|
+
async runHooks() { }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { NoSpaRouter };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var client = require('./client.js');
|
|
6
|
+
|
|
7
|
+
const omitHash = (url) => url.href.replace(/#.*$/, '');
|
|
8
|
+
class NoSpaRouter extends client.ClientRouter {
|
|
9
|
+
run(navigation) {
|
|
10
|
+
const { type, fromUrl, url } = navigation;
|
|
11
|
+
// support only updateCurrentRoute or hash navigations
|
|
12
|
+
if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
|
|
13
|
+
return super.run(navigation);
|
|
14
|
+
}
|
|
15
|
+
return this.notfound(navigation);
|
|
16
|
+
}
|
|
17
|
+
// do not call any hooks as it is only supports url updates with hash
|
|
18
|
+
async runHooks() { }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.NoSpaRouter = NoSpaRouter;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { isInvalidUrl } from '@tinkoff/url';
|
|
2
|
+
import { AbstractRouter } from './abstract.es.js';
|
|
3
|
+
import { ServerHistory } from '../history/server.es.js';
|
|
4
|
+
import { RouteTree } from '../tree/tree.es.js';
|
|
5
|
+
import { logger } from '../logger.es.js';
|
|
6
|
+
|
|
7
|
+
class Router extends AbstractRouter {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
var _a;
|
|
10
|
+
super(options);
|
|
11
|
+
this.blocked = false;
|
|
12
|
+
this.tree = new RouteTree(options.routes);
|
|
13
|
+
this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
|
|
14
|
+
this.history = new ServerHistory();
|
|
15
|
+
}
|
|
16
|
+
async dehydrate() {
|
|
17
|
+
logger.debug({
|
|
18
|
+
event: 'dehydrate',
|
|
19
|
+
navigation: this.lastNavigation,
|
|
20
|
+
});
|
|
21
|
+
return this.lastNavigation;
|
|
22
|
+
}
|
|
23
|
+
async internalNavigate(navigateOptions, internalOptions) {
|
|
24
|
+
// any navigation after initial should be considered as redirects
|
|
25
|
+
if (this.getCurrentRoute()) {
|
|
26
|
+
return this.redirect(this.lastNavigation, navigateOptions);
|
|
27
|
+
}
|
|
28
|
+
return super.internalNavigate(navigateOptions, internalOptions);
|
|
29
|
+
}
|
|
30
|
+
async run(navigation) {
|
|
31
|
+
if (this.redirectCode) {
|
|
32
|
+
return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
|
|
33
|
+
}
|
|
34
|
+
await super.run(navigation);
|
|
35
|
+
}
|
|
36
|
+
async redirect(navigation, target) {
|
|
37
|
+
logger.debug({
|
|
38
|
+
event: 'redirect',
|
|
39
|
+
navigation,
|
|
40
|
+
target,
|
|
41
|
+
});
|
|
42
|
+
this.blocked = true;
|
|
43
|
+
return this.onRedirect({
|
|
44
|
+
...navigation,
|
|
45
|
+
from: navigation.to,
|
|
46
|
+
fromUrl: navigation.url,
|
|
47
|
+
to: null,
|
|
48
|
+
url: this.resolveUrl(target),
|
|
49
|
+
code: target.code,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async notfound(navigation) {
|
|
53
|
+
this.blocked = true;
|
|
54
|
+
return super.notfound(navigation);
|
|
55
|
+
}
|
|
56
|
+
normalizePathname(pathname) {
|
|
57
|
+
const normalized = super.normalizePathname(pathname);
|
|
58
|
+
if (normalized !== pathname) {
|
|
59
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
60
|
+
}
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
resolveUrl(options) {
|
|
64
|
+
if (options.url && isInvalidUrl(options.url)) {
|
|
65
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
66
|
+
}
|
|
67
|
+
return super.resolveUrl(options);
|
|
68
|
+
}
|
|
69
|
+
async runHooks(hookName, navigation) {
|
|
70
|
+
// do not run hooks if another parallel navigation has been called
|
|
71
|
+
if (this.blocked) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
return super.runHooks(hookName, navigation);
|
|
75
|
+
}
|
|
76
|
+
async runGuards(navigation) {
|
|
77
|
+
// do not run guards if another parallel navigation has been called
|
|
78
|
+
if (this.blocked) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
return super.runGuards(navigation);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { Router };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var url = require('@tinkoff/url');
|
|
6
|
+
var abstract = require('./abstract.js');
|
|
7
|
+
var server = require('../history/server.js');
|
|
8
|
+
var tree = require('../tree/tree.js');
|
|
9
|
+
var logger = require('../logger.js');
|
|
10
|
+
|
|
11
|
+
class Router extends abstract.AbstractRouter {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
var _a;
|
|
14
|
+
super(options);
|
|
15
|
+
this.blocked = false;
|
|
16
|
+
this.tree = new tree.RouteTree(options.routes);
|
|
17
|
+
this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
|
|
18
|
+
this.history = new server.ServerHistory();
|
|
19
|
+
}
|
|
20
|
+
async dehydrate() {
|
|
21
|
+
logger.logger.debug({
|
|
22
|
+
event: 'dehydrate',
|
|
23
|
+
navigation: this.lastNavigation,
|
|
24
|
+
});
|
|
25
|
+
return this.lastNavigation;
|
|
26
|
+
}
|
|
27
|
+
async internalNavigate(navigateOptions, internalOptions) {
|
|
28
|
+
// any navigation after initial should be considered as redirects
|
|
29
|
+
if (this.getCurrentRoute()) {
|
|
30
|
+
return this.redirect(this.lastNavigation, navigateOptions);
|
|
31
|
+
}
|
|
32
|
+
return super.internalNavigate(navigateOptions, internalOptions);
|
|
33
|
+
}
|
|
34
|
+
async run(navigation) {
|
|
35
|
+
if (this.redirectCode) {
|
|
36
|
+
return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
|
|
37
|
+
}
|
|
38
|
+
await super.run(navigation);
|
|
39
|
+
}
|
|
40
|
+
async redirect(navigation, target) {
|
|
41
|
+
logger.logger.debug({
|
|
42
|
+
event: 'redirect',
|
|
43
|
+
navigation,
|
|
44
|
+
target,
|
|
45
|
+
});
|
|
46
|
+
this.blocked = true;
|
|
47
|
+
return this.onRedirect({
|
|
48
|
+
...navigation,
|
|
49
|
+
from: navigation.to,
|
|
50
|
+
fromUrl: navigation.url,
|
|
51
|
+
to: null,
|
|
52
|
+
url: this.resolveUrl(target),
|
|
53
|
+
code: target.code,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async notfound(navigation) {
|
|
57
|
+
this.blocked = true;
|
|
58
|
+
return super.notfound(navigation);
|
|
59
|
+
}
|
|
60
|
+
normalizePathname(pathname) {
|
|
61
|
+
const normalized = super.normalizePathname(pathname);
|
|
62
|
+
if (normalized !== pathname) {
|
|
63
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
64
|
+
}
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
resolveUrl(options) {
|
|
68
|
+
if (options.url && url.isInvalidUrl(options.url)) {
|
|
69
|
+
this.redirectCode = this.defaultRedirectCode;
|
|
70
|
+
}
|
|
71
|
+
return super.resolveUrl(options);
|
|
72
|
+
}
|
|
73
|
+
async runHooks(hookName, navigation) {
|
|
74
|
+
// do not run hooks if another parallel navigation has been called
|
|
75
|
+
if (this.blocked) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
return super.runHooks(hookName, navigation);
|
|
79
|
+
}
|
|
80
|
+
async runGuards(navigation) {
|
|
81
|
+
// do not run guards if another parallel navigation has been called
|
|
82
|
+
if (this.blocked) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return super.runGuards(navigation);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.Router = Router;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const PARAMETER_DELIMITER = ':';
|
|
2
|
+
const WILDCARD_REGEXP = /\*/;
|
|
3
|
+
const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
|
|
4
|
+
const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
|
|
5
|
+
|
|
6
|
+
export { HISTORY_FALLBACK_REGEXP, PARAMETER_DELIMITER, PARAM_PARSER_REGEXP, WILDCARD_REGEXP };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const PARAMETER_DELIMITER = ':';
|
|
2
|
+
const WILDCARD_REGEXP = /\*/;
|
|
3
|
+
const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
|
|
4
|
+
const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
|
|
5
|
+
|
|
6
|
+
export { HISTORY_FALLBACK_REGEXP, PARAMETER_DELIMITER, PARAM_PARSER_REGEXP, WILDCARD_REGEXP };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const PARAMETER_DELIMITER = ':';
|
|
6
|
+
const WILDCARD_REGEXP = /\*/;
|
|
7
|
+
const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
|
|
8
|
+
const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
|
|
9
|
+
|
|
10
|
+
exports.HISTORY_FALLBACK_REGEXP = HISTORY_FALLBACK_REGEXP;
|
|
11
|
+
exports.PARAMETER_DELIMITER = PARAMETER_DELIMITER;
|
|
12
|
+
exports.PARAM_PARSER_REGEXP = PARAM_PARSER_REGEXP;
|
|
13
|
+
exports.WILDCARD_REGEXP = WILDCARD_REGEXP;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import each from '@tinkoff/utils/array/each';
|
|
2
|
+
import find from '@tinkoff/utils/array/find';
|
|
3
|
+
import findIndex from '@tinkoff/utils/array/findIndex';
|
|
4
|
+
import { getParts, parse } from './utils.browser.js';
|
|
5
|
+
import { HISTORY_FALLBACK_REGEXP } from './constants.browser.js';
|
|
6
|
+
|
|
7
|
+
const createTree = (route) => {
|
|
8
|
+
return {
|
|
9
|
+
route,
|
|
10
|
+
children: Object.create(null),
|
|
11
|
+
parameters: [],
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
const createNavigationRoute = (route, pathname, params) => {
|
|
15
|
+
return {
|
|
16
|
+
...route,
|
|
17
|
+
actualPath: pathname,
|
|
18
|
+
params: params !== null && params !== void 0 ? params : {},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
class RouteTree {
|
|
22
|
+
constructor(routes) {
|
|
23
|
+
this.tree = createTree();
|
|
24
|
+
each((route) => this.addRoute(route), routes);
|
|
25
|
+
}
|
|
26
|
+
// eslint-disable-next-line max-statements
|
|
27
|
+
addRoute(route) {
|
|
28
|
+
const parts = getParts(route.path);
|
|
29
|
+
let currentTree = this.tree;
|
|
30
|
+
for (let i = 0; i < parts.length; i++) {
|
|
31
|
+
const part = parts[i];
|
|
32
|
+
const parsed = parse(part);
|
|
33
|
+
if (parsed.type === 1 /* PartType.historyFallback */) {
|
|
34
|
+
currentTree.historyFallbackRoute = route;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (parsed.type === 2 /* PartType.wildcard */) {
|
|
38
|
+
currentTree.wildcardRoute = route;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (parsed.type === 3 /* PartType.parameter */) {
|
|
42
|
+
const { paramName, regexp, optional } = parsed;
|
|
43
|
+
// prevent from creating new entries for same route
|
|
44
|
+
const found = find((par) => par.key === part, currentTree.parameters);
|
|
45
|
+
if (found) {
|
|
46
|
+
currentTree = found.tree;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const parameter = {
|
|
50
|
+
key: part,
|
|
51
|
+
paramName,
|
|
52
|
+
regexp,
|
|
53
|
+
optional,
|
|
54
|
+
tree: createTree(),
|
|
55
|
+
};
|
|
56
|
+
if (regexp && !optional) {
|
|
57
|
+
// insert parameters with regexp before
|
|
58
|
+
const index = findIndex((par) => !par.regexp, currentTree.parameters);
|
|
59
|
+
currentTree.parameters.splice(index, 0, parameter);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
currentTree.parameters.push(parameter);
|
|
63
|
+
}
|
|
64
|
+
currentTree = parameter.tree;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (!currentTree.children[part]) {
|
|
69
|
+
currentTree.children[part] = createTree();
|
|
70
|
+
}
|
|
71
|
+
currentTree = currentTree.children[part];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
currentTree.route = route;
|
|
75
|
+
}
|
|
76
|
+
getRoute(pathname) {
|
|
77
|
+
return this.findRoute(pathname, 'route');
|
|
78
|
+
}
|
|
79
|
+
getWildcard(pathname) {
|
|
80
|
+
return this.findRoute(pathname, 'wildcardRoute');
|
|
81
|
+
}
|
|
82
|
+
getHistoryFallback(pathname) {
|
|
83
|
+
const route = this.findRoute(pathname, 'historyFallbackRoute');
|
|
84
|
+
return (route && {
|
|
85
|
+
...route,
|
|
86
|
+
// remove <history-fallback> from path
|
|
87
|
+
actualPath: route.path.replace(HISTORY_FALLBACK_REGEXP, '') || '/',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// eslint-disable-next-line max-statements
|
|
91
|
+
findRoute(pathname, propertyName) {
|
|
92
|
+
// we should use exact match only for classic route
|
|
93
|
+
// as special routes (for not-found and history-fallback) are defined for whole subtree
|
|
94
|
+
const exactMatch = propertyName === 'route';
|
|
95
|
+
const parts = getParts(pathname);
|
|
96
|
+
const queue = [
|
|
97
|
+
[this.tree, 0],
|
|
98
|
+
];
|
|
99
|
+
while (queue.length) {
|
|
100
|
+
const [currentTree, index, params] = queue.pop();
|
|
101
|
+
const { children, parameters } = currentTree;
|
|
102
|
+
// this flag mean we can only check for optional parameters
|
|
103
|
+
// as we didn't find static route for this path, but still may find
|
|
104
|
+
// some inner route inside optional branch
|
|
105
|
+
// the value of parameter will be empty in this case ofc
|
|
106
|
+
let optionalOnly = false;
|
|
107
|
+
if (index >= parts.length) {
|
|
108
|
+
if (currentTree[propertyName]) {
|
|
109
|
+
return createNavigationRoute(currentTree[propertyName], pathname, params);
|
|
110
|
+
}
|
|
111
|
+
// here we cant check any options except for optional parameters
|
|
112
|
+
optionalOnly = true;
|
|
113
|
+
}
|
|
114
|
+
// first add to queue special routes (not-found or history-fallback) to check it after other cases, as it will be processed last
|
|
115
|
+
if (!exactMatch && currentTree[propertyName]) {
|
|
116
|
+
queue.push([currentTree, parts.length, params]);
|
|
117
|
+
}
|
|
118
|
+
const part = parts[index];
|
|
119
|
+
const child = children[part];
|
|
120
|
+
// then add checks for only optional
|
|
121
|
+
for (const param of parameters) {
|
|
122
|
+
const { optional, tree } = param;
|
|
123
|
+
if (optional) {
|
|
124
|
+
queue.push([tree, index, params]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (optionalOnly) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// for non-optional cases
|
|
131
|
+
for (let i = parameters.length - 1; i >= 0; i--) {
|
|
132
|
+
const param = parameters[i];
|
|
133
|
+
const { paramName, tree, regexp } = param;
|
|
134
|
+
const match = regexp === null || regexp === void 0 ? void 0 : regexp.exec(part);
|
|
135
|
+
const paramValue = regexp ? match === null || match === void 0 ? void 0 : match[1] : part;
|
|
136
|
+
if (paramValue) {
|
|
137
|
+
queue.push([tree, index + 1, { ...params, [paramName]: paramValue }]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (child) {
|
|
141
|
+
// add checks for static child subtree last as it will be processed first after queue.pop
|
|
142
|
+
queue.push([child, index + 1, params]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { RouteTree };
|