@tinkoff/router 0.2.7 → 0.2.8
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 +116 -0
- package/lib/router/client.es.js +116 -0
- package/lib/router/client.js +120 -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/index.browser.js
CHANGED
|
@@ -1,1169 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { useShallowEqual } from '@tinkoff/react-hooks';
|
|
14
|
-
|
|
15
|
-
const PARAMETER_DELIMITER = ':';
|
|
16
|
-
const WILDCARD_REGEXP = /\*/;
|
|
17
|
-
const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
|
|
18
|
-
const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
|
|
19
|
-
|
|
20
|
-
const isFilePath = (pathname) => {
|
|
21
|
-
return /\/.+\.[^/]+$/.test(pathname);
|
|
22
|
-
};
|
|
23
|
-
const normalizeTrailingSlash = (pathname, trailingSlash = false) => {
|
|
24
|
-
const hasTrailingSlash = pathname.endsWith('/');
|
|
25
|
-
if (trailingSlash) {
|
|
26
|
-
return hasTrailingSlash || isFilePath(pathname) ? pathname : `${pathname}/`;
|
|
27
|
-
}
|
|
28
|
-
return pathname.length > 1 && hasTrailingSlash ? pathname.slice(0, -1) : pathname;
|
|
29
|
-
};
|
|
30
|
-
const normalizeManySlashes = (hrefOrPath) => {
|
|
31
|
-
const [href, ...search] = hrefOrPath.split('?');
|
|
32
|
-
return [href.replace(/\/+/g, '/').replace(/^(\w+):\//, '$1://'), ...search].join('?');
|
|
33
|
-
};
|
|
34
|
-
const isSameHost = typeof window === 'undefined'
|
|
35
|
-
? T
|
|
36
|
-
: (url) => {
|
|
37
|
-
return !url.host || url.host === window.location.host;
|
|
38
|
-
};
|
|
39
|
-
const makeNavigateOptions = (options) => {
|
|
40
|
-
if (typeof options === 'string') {
|
|
41
|
-
return { url: options };
|
|
42
|
-
}
|
|
43
|
-
return options;
|
|
44
|
-
};
|
|
45
|
-
const registerHook = (hooksSet, hook) => {
|
|
46
|
-
hooksSet.add(hook);
|
|
47
|
-
return () => {
|
|
48
|
-
hooksSet.delete(hook);
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const getParts = (pathname) => pathname
|
|
53
|
-
.split('/')
|
|
54
|
-
.slice(pathname.startsWith('/') ? 1 : 0, pathname.endsWith('/') ? -1 : Infinity);
|
|
55
|
-
const isHistoryFallback = (part) => {
|
|
56
|
-
return HISTORY_FALLBACK_REGEXP.test(part);
|
|
57
|
-
};
|
|
58
|
-
const isWildcard = (part) => {
|
|
59
|
-
return WILDCARD_REGEXP.test(part);
|
|
60
|
-
};
|
|
61
|
-
const isParameterized = (part) => {
|
|
62
|
-
return part.includes(PARAMETER_DELIMITER);
|
|
63
|
-
};
|
|
64
|
-
const parseParameter = (part) => {
|
|
65
|
-
const [prefix = '', param, postfix = ''] = part.split(PARAMETER_DELIMITER);
|
|
66
|
-
const match = PARAM_PARSER_REGEXP.exec(param);
|
|
67
|
-
if (!match) {
|
|
68
|
-
throw new Error('parameters should satisfy pattern "prefix:paramName(regexp)\\?:postfix"');
|
|
69
|
-
}
|
|
70
|
-
const [, paramName, regexp, optional] = match;
|
|
71
|
-
const useRegexp = prefix || postfix || regexp;
|
|
72
|
-
return {
|
|
73
|
-
type: 3 /* PartType.parameter */,
|
|
74
|
-
paramName,
|
|
75
|
-
regexp: useRegexp
|
|
76
|
-
? new RegExp(`^${prefix}(${regexp || '.+'})${optional ? '?' : ''}${postfix}$`)
|
|
77
|
-
: undefined,
|
|
78
|
-
optional: !!optional && !prefix && !postfix,
|
|
79
|
-
};
|
|
80
|
-
};
|
|
81
|
-
const parse = (part) => {
|
|
82
|
-
if (isHistoryFallback(part)) {
|
|
83
|
-
return { type: 1 /* PartType.historyFallback */ };
|
|
84
|
-
}
|
|
85
|
-
if (isWildcard(part)) {
|
|
86
|
-
return { type: 2 /* PartType.wildcard */ };
|
|
87
|
-
}
|
|
88
|
-
if (isParameterized(part)) {
|
|
89
|
-
return parseParameter(part);
|
|
90
|
-
}
|
|
91
|
-
return { type: 0 /* PartType.literal */, value: part };
|
|
92
|
-
};
|
|
93
|
-
const makePath = (pathname, params) => {
|
|
94
|
-
const parts = getParts(pathname);
|
|
95
|
-
const result = map((part) => {
|
|
96
|
-
var _a;
|
|
97
|
-
if (isHistoryFallback(part) || isWildcard(part)) {
|
|
98
|
-
throw new Error(`Pathname should be only a string with dynamic parameters, not a special string, got ${pathname}`);
|
|
99
|
-
}
|
|
100
|
-
if (isParameterized(part)) {
|
|
101
|
-
const [prefix = '', param = '', postfix = ''] = part.split(PARAMETER_DELIMITER);
|
|
102
|
-
const match = PARAM_PARSER_REGEXP.exec(param);
|
|
103
|
-
if (!match) {
|
|
104
|
-
throw new Error('parameters should satisfy pattern "prefix:paramName(regexp)\\?:postfix"');
|
|
105
|
-
}
|
|
106
|
-
const [, paramName, regexp, optional] = match;
|
|
107
|
-
const value = (_a = params[paramName]) === null || _a === void 0 ? void 0 : _a.toString();
|
|
108
|
-
if (optional && !value) {
|
|
109
|
-
return '';
|
|
110
|
-
}
|
|
111
|
-
if (!value) {
|
|
112
|
-
throw new Error(`value for parameter for ${paramName} should be defined in params`);
|
|
113
|
-
}
|
|
114
|
-
if (regexp && !new RegExp(regexp).test(value)) {
|
|
115
|
-
throw new Error(`passed parameter for ${paramName} should satisfy regxep: ${regexp}, got: ${value}`);
|
|
116
|
-
}
|
|
117
|
-
return prefix + value + postfix || part;
|
|
118
|
-
}
|
|
119
|
-
return part;
|
|
120
|
-
}, parts).join('/');
|
|
121
|
-
return normalizeTrailingSlash(`/${result}`, pathname.endsWith('/'));
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// eslint-disable-next-line import/no-mutable-exports
|
|
125
|
-
let logger = {
|
|
126
|
-
trace: noop,
|
|
127
|
-
debug: noop,
|
|
128
|
-
info: noop,
|
|
129
|
-
warn: noop,
|
|
130
|
-
error: noop,
|
|
131
|
-
};
|
|
132
|
-
const setLogger = (newLogger) => {
|
|
133
|
-
logger = newLogger;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
class AbstractRouter {
|
|
137
|
-
constructor({ trailingSlash, mergeSlashes, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
138
|
-
this.started = false;
|
|
139
|
-
this.trailingSlash = false;
|
|
140
|
-
this.strictTrailingSlash = true;
|
|
141
|
-
this.mergeSlashes = false;
|
|
142
|
-
this.trailingSlash = trailingSlash !== null && trailingSlash !== void 0 ? trailingSlash : false;
|
|
143
|
-
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
144
|
-
this.mergeSlashes = mergeSlashes !== null && mergeSlashes !== void 0 ? mergeSlashes : false;
|
|
145
|
-
this.hooks = new Map([
|
|
146
|
-
['beforeResolve', new Set(beforeResolve)],
|
|
147
|
-
['beforeNavigate', new Set(beforeNavigate)],
|
|
148
|
-
['afterNavigate', new Set(afterNavigate)],
|
|
149
|
-
['beforeUpdateCurrent', new Set(beforeUpdateCurrent)],
|
|
150
|
-
['afterUpdateCurrent', new Set(afterUpdateCurrent)],
|
|
151
|
-
]);
|
|
152
|
-
this.guards = new Set(guards);
|
|
153
|
-
this.syncHooks = new Map([['change', new Set(onChange)]]);
|
|
154
|
-
this.onRedirect = onRedirect;
|
|
155
|
-
this.onNotFound = onNotFound;
|
|
156
|
-
this.onBlock = onBlock;
|
|
157
|
-
}
|
|
158
|
-
// start is using as marker that any preparation for proper work has done in the app
|
|
159
|
-
// and now router can manage any navigations
|
|
160
|
-
async start() {
|
|
161
|
-
logger.debug({
|
|
162
|
-
event: 'start',
|
|
163
|
-
});
|
|
164
|
-
this.started = true;
|
|
165
|
-
}
|
|
166
|
-
getCurrentRoute() {
|
|
167
|
-
var _a, _b, _c;
|
|
168
|
-
// when something will try to get currentRoute while navigating, it will get route which router currently navigating
|
|
169
|
-
// in case some handler supposed to load data of route or similar
|
|
170
|
-
return (_b = (_a = this.currentNavigation) === null || _a === void 0 ? void 0 : _a.to) !== null && _b !== void 0 ? _b : (_c = this.lastNavigation) === null || _c === void 0 ? void 0 : _c.to;
|
|
171
|
-
}
|
|
172
|
-
getCurrentUrl() {
|
|
173
|
-
var _a, _b, _c;
|
|
174
|
-
// same as getCurrentRoute
|
|
175
|
-
return (_b = (_a = this.currentNavigation) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : (_c = this.lastNavigation) === null || _c === void 0 ? void 0 : _c.url;
|
|
176
|
-
}
|
|
177
|
-
getLastRoute() {
|
|
178
|
-
var _a;
|
|
179
|
-
return (_a = this.lastNavigation) === null || _a === void 0 ? void 0 : _a.to;
|
|
180
|
-
}
|
|
181
|
-
getLastUrl() {
|
|
182
|
-
var _a;
|
|
183
|
-
return (_a = this.lastNavigation) === null || _a === void 0 ? void 0 : _a.url;
|
|
184
|
-
}
|
|
185
|
-
commitNavigation(navigation) {
|
|
186
|
-
logger.debug({
|
|
187
|
-
event: 'commit-navigation',
|
|
188
|
-
navigation,
|
|
189
|
-
});
|
|
190
|
-
if (!navigation.history) {
|
|
191
|
-
// in case we came from history do not history back to prevent infinity recursive calls
|
|
192
|
-
this.history.save(navigation);
|
|
193
|
-
}
|
|
194
|
-
this.lastNavigation = navigation;
|
|
195
|
-
this.currentNavigation = null;
|
|
196
|
-
this.runSyncHooks('change', navigation);
|
|
197
|
-
}
|
|
198
|
-
async updateCurrentRoute(updateRouteOptions) {
|
|
199
|
-
return this.internalUpdateCurrentRoute(updateRouteOptions, {});
|
|
200
|
-
}
|
|
201
|
-
async internalUpdateCurrentRoute(updateRouteOptions, { history }) {
|
|
202
|
-
var _a;
|
|
203
|
-
const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
204
|
-
if (!prevNavigation) {
|
|
205
|
-
throw new Error('updateCurrentRoute should only be called after navigate to some route');
|
|
206
|
-
}
|
|
207
|
-
const { replace, params, navigateState } = updateRouteOptions;
|
|
208
|
-
const { to: from, url: fromUrl } = prevNavigation;
|
|
209
|
-
const navigation = {
|
|
210
|
-
type: 'updateCurrentRoute',
|
|
211
|
-
from,
|
|
212
|
-
to: this.resolveRoute({ params, navigateState }, { wildcard: true }),
|
|
213
|
-
url: this.resolveUrl(updateRouteOptions),
|
|
214
|
-
fromUrl,
|
|
215
|
-
replace,
|
|
216
|
-
history,
|
|
217
|
-
navigateState,
|
|
218
|
-
code: updateRouteOptions.code,
|
|
219
|
-
};
|
|
220
|
-
logger.debug({
|
|
221
|
-
event: 'update-current-route',
|
|
222
|
-
updateRouteOptions,
|
|
223
|
-
navigation,
|
|
224
|
-
});
|
|
225
|
-
await this.run(navigation);
|
|
226
|
-
}
|
|
227
|
-
async runUpdateCurrentRoute(navigation) {
|
|
228
|
-
await this.runHooks('beforeUpdateCurrent', navigation);
|
|
229
|
-
this.commitNavigation(navigation);
|
|
230
|
-
await this.runHooks('afterUpdateCurrent', navigation);
|
|
231
|
-
}
|
|
232
|
-
async navigate(navigateOptions) {
|
|
233
|
-
return this.internalNavigate(makeNavigateOptions(navigateOptions), {});
|
|
234
|
-
}
|
|
235
|
-
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
236
|
-
var _a;
|
|
237
|
-
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
238
|
-
const prevNavigation = redirect
|
|
239
|
-
? this.lastNavigation
|
|
240
|
-
: (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
241
|
-
if (!url && !prevNavigation) {
|
|
242
|
-
throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
|
|
243
|
-
}
|
|
244
|
-
const resolvedUrl = this.resolveUrl(navigateOptions);
|
|
245
|
-
const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
|
|
246
|
-
const redirectFrom = redirect ? this.currentNavigation.to : undefined;
|
|
247
|
-
let navigation = {
|
|
248
|
-
type: 'navigate',
|
|
249
|
-
from,
|
|
250
|
-
url: resolvedUrl,
|
|
251
|
-
fromUrl,
|
|
252
|
-
replace,
|
|
253
|
-
history,
|
|
254
|
-
navigateState,
|
|
255
|
-
code,
|
|
256
|
-
redirect,
|
|
257
|
-
redirectFrom,
|
|
258
|
-
};
|
|
259
|
-
await this.runHooks('beforeResolve', navigation);
|
|
260
|
-
const to = this.resolveRoute({ url: resolvedUrl, params, navigateState }, { wildcard: true });
|
|
261
|
-
if (to) {
|
|
262
|
-
navigation = {
|
|
263
|
-
...navigation,
|
|
264
|
-
to,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
logger.debug({
|
|
268
|
-
event: 'navigation',
|
|
269
|
-
navigation,
|
|
270
|
-
});
|
|
271
|
-
if (!navigation.to) {
|
|
272
|
-
return this.notfound(navigation);
|
|
273
|
-
}
|
|
274
|
-
await this.run(navigation);
|
|
275
|
-
}
|
|
276
|
-
async runNavigate(navigation) {
|
|
277
|
-
// check for redirect in new route description
|
|
278
|
-
if (navigation.to.redirect) {
|
|
279
|
-
return this.redirect(navigation, makeNavigateOptions(navigation.to.redirect));
|
|
280
|
-
}
|
|
281
|
-
await this.runGuards(navigation);
|
|
282
|
-
await this.runHooks('beforeNavigate', navigation);
|
|
283
|
-
this.commitNavigation(navigation);
|
|
284
|
-
await this.runHooks('afterNavigate', navigation);
|
|
285
|
-
}
|
|
286
|
-
async run(navigation) {
|
|
287
|
-
this.currentNavigation = navigation;
|
|
288
|
-
if (navigation.type === 'navigate') {
|
|
289
|
-
await this.runNavigate(navigation);
|
|
290
|
-
}
|
|
291
|
-
if (navigation.type === 'updateCurrentRoute') {
|
|
292
|
-
await this.runUpdateCurrentRoute(navigation);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
resolve(resolveOptions, options) {
|
|
296
|
-
const opts = typeof resolveOptions === 'string' ? { url: resolveOptions } : resolveOptions;
|
|
297
|
-
return this.resolveRoute({ ...opts, url: parse$1(opts.url) }, options);
|
|
298
|
-
}
|
|
299
|
-
back(options) {
|
|
300
|
-
return this.go(-1, options);
|
|
301
|
-
}
|
|
302
|
-
forward() {
|
|
303
|
-
return this.go(1);
|
|
304
|
-
}
|
|
305
|
-
async go(to, options) {
|
|
306
|
-
logger.debug({
|
|
307
|
-
event: 'history.go',
|
|
308
|
-
to,
|
|
309
|
-
});
|
|
310
|
-
return this.history.go(to, options);
|
|
311
|
-
}
|
|
312
|
-
isNavigating() {
|
|
313
|
-
return !!this.currentNavigation;
|
|
314
|
-
}
|
|
315
|
-
async dehydrate() {
|
|
316
|
-
throw new Error('Not implemented');
|
|
317
|
-
}
|
|
318
|
-
async rehydrate(navigation) {
|
|
319
|
-
throw new Error('Not implemented');
|
|
320
|
-
}
|
|
321
|
-
addRoute(route) {
|
|
322
|
-
var _a;
|
|
323
|
-
(_a = this.tree) === null || _a === void 0 ? void 0 : _a.addRoute(route);
|
|
324
|
-
}
|
|
325
|
-
async redirect(navigation, target) {
|
|
326
|
-
var _a;
|
|
327
|
-
logger.debug({
|
|
328
|
-
event: 'redirect',
|
|
329
|
-
navigation,
|
|
330
|
-
target,
|
|
331
|
-
});
|
|
332
|
-
return (_a = this.onRedirect) === null || _a === void 0 ? void 0 : _a.call(this, {
|
|
333
|
-
...navigation,
|
|
334
|
-
from: navigation.to,
|
|
335
|
-
fromUrl: navigation.url,
|
|
336
|
-
to: null,
|
|
337
|
-
url: this.resolveUrl(target),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
async notfound(navigation) {
|
|
341
|
-
var _a;
|
|
342
|
-
logger.debug({
|
|
343
|
-
event: 'not-found',
|
|
344
|
-
navigation,
|
|
345
|
-
});
|
|
346
|
-
return (_a = this.onNotFound) === null || _a === void 0 ? void 0 : _a.call(this, navigation);
|
|
347
|
-
}
|
|
348
|
-
async block(navigation) {
|
|
349
|
-
logger.debug({
|
|
350
|
-
event: 'blocked',
|
|
351
|
-
navigation,
|
|
352
|
-
});
|
|
353
|
-
this.currentNavigation = null;
|
|
354
|
-
if (this.onBlock) {
|
|
355
|
-
return this.onBlock(navigation);
|
|
356
|
-
}
|
|
357
|
-
throw new Error('Navigation blocked');
|
|
358
|
-
}
|
|
359
|
-
normalizePathname(pathname) {
|
|
360
|
-
let normalized = pathname;
|
|
361
|
-
if (this.mergeSlashes) {
|
|
362
|
-
normalized = normalizeManySlashes(normalized);
|
|
363
|
-
}
|
|
364
|
-
if (!this.strictTrailingSlash) {
|
|
365
|
-
normalized = normalizeTrailingSlash(normalized, this.trailingSlash);
|
|
366
|
-
}
|
|
367
|
-
return normalized;
|
|
368
|
-
}
|
|
369
|
-
resolveUrl({ url, query = {}, params, preserveQuery, hash }) {
|
|
370
|
-
var _a;
|
|
371
|
-
const currentRoute = this.getCurrentRoute();
|
|
372
|
-
const currentUrl = this.getCurrentUrl();
|
|
373
|
-
const resultUrl = url ? rawResolveUrl((_a = currentUrl === null || currentUrl === void 0 ? void 0 : currentUrl.href) !== null && _a !== void 0 ? _a : '', url) : rawParse(currentUrl.href);
|
|
374
|
-
let { pathname } = resultUrl;
|
|
375
|
-
if (params) {
|
|
376
|
-
if (url) {
|
|
377
|
-
pathname = makePath(resultUrl.pathname, params);
|
|
378
|
-
}
|
|
379
|
-
else if (currentRoute) {
|
|
380
|
-
pathname = makePath(currentRoute.path, { ...currentRoute.params, ...params });
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (isSameHost(resultUrl)) {
|
|
384
|
-
pathname = this.normalizePathname(pathname);
|
|
385
|
-
}
|
|
386
|
-
return convertRawUrl(rawAssignUrl(resultUrl, {
|
|
387
|
-
pathname,
|
|
388
|
-
search: url ? resultUrl.search : '',
|
|
389
|
-
query: {
|
|
390
|
-
...(preserveQuery ? this.getCurrentUrl().query : {}),
|
|
391
|
-
...query,
|
|
392
|
-
},
|
|
393
|
-
hash: hash !== null && hash !== void 0 ? hash : resultUrl.hash,
|
|
394
|
-
}));
|
|
395
|
-
}
|
|
396
|
-
resolveRoute({ url, params, navigateState }, { wildcard } = {}) {
|
|
397
|
-
var _a, _b;
|
|
398
|
-
let route = url ? (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(url.pathname) : this.getCurrentRoute();
|
|
399
|
-
if (wildcard && !route && url) {
|
|
400
|
-
// if ordinary route not found look for a wildcard route
|
|
401
|
-
route = (_b = this.tree) === null || _b === void 0 ? void 0 : _b.getWildcard(url.pathname);
|
|
402
|
-
}
|
|
403
|
-
if (!route) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
// if condition is true route data not changed, so no need to create new reference for route object
|
|
407
|
-
if (!params && navigateState === route.navigateState) {
|
|
408
|
-
return route;
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
...route,
|
|
412
|
-
params: { ...route.params, ...params },
|
|
413
|
-
navigateState,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
async runGuards(navigation) {
|
|
417
|
-
logger.debug({
|
|
418
|
-
event: 'guards.run',
|
|
419
|
-
navigation,
|
|
420
|
-
});
|
|
421
|
-
if (!this.guards) {
|
|
422
|
-
logger.debug({
|
|
423
|
-
event: 'guards.empty',
|
|
424
|
-
navigation,
|
|
425
|
-
});
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
const results = await Promise.all(Array.from(this.guards).map((guard) => Promise.resolve(guard(navigation)).catch((error) => {
|
|
429
|
-
logger.warn({
|
|
430
|
-
event: 'guard.error',
|
|
431
|
-
error,
|
|
432
|
-
});
|
|
433
|
-
})));
|
|
434
|
-
logger.debug({
|
|
435
|
-
event: 'guards.done',
|
|
436
|
-
navigation,
|
|
437
|
-
results,
|
|
438
|
-
});
|
|
439
|
-
for (const result of results) {
|
|
440
|
-
if (result === false) {
|
|
441
|
-
return this.block(navigation);
|
|
442
|
-
}
|
|
443
|
-
if (isString(result) || isObject(result)) {
|
|
444
|
-
return this.redirect(navigation, makeNavigateOptions(result));
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
registerGuard(guard) {
|
|
449
|
-
return registerHook(this.guards, guard);
|
|
450
|
-
}
|
|
451
|
-
runSyncHooks(hookName, navigation) {
|
|
452
|
-
logger.debug({
|
|
453
|
-
event: 'sync-hooks.run',
|
|
454
|
-
hookName,
|
|
455
|
-
navigation,
|
|
456
|
-
});
|
|
457
|
-
const hooks = this.syncHooks.get(hookName);
|
|
458
|
-
if (!hooks) {
|
|
459
|
-
logger.debug({
|
|
460
|
-
event: 'sync-hooks.empty',
|
|
461
|
-
hookName,
|
|
462
|
-
navigation,
|
|
463
|
-
});
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
for (const hook of hooks) {
|
|
467
|
-
try {
|
|
468
|
-
hook(navigation);
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
logger.warn({
|
|
472
|
-
event: 'sync-hooks.error',
|
|
473
|
-
error,
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
logger.debug({
|
|
478
|
-
event: 'sync-hooks.done',
|
|
479
|
-
hookName,
|
|
480
|
-
navigation,
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
registerSyncHook(hookName, hook) {
|
|
484
|
-
return registerHook(this.syncHooks.get(hookName), hook);
|
|
485
|
-
}
|
|
486
|
-
async runHooks(hookName, navigation) {
|
|
487
|
-
logger.debug({
|
|
488
|
-
event: 'hooks.run',
|
|
489
|
-
hookName,
|
|
490
|
-
navigation,
|
|
491
|
-
});
|
|
492
|
-
const hooks = this.hooks.get(hookName);
|
|
493
|
-
if (!hooks) {
|
|
494
|
-
logger.debug({
|
|
495
|
-
event: 'hooks.empty',
|
|
496
|
-
hookName,
|
|
497
|
-
navigation,
|
|
498
|
-
});
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
await Promise.all(Array.from(hooks).map((hook) => Promise.resolve(hook(navigation)).catch((error) => {
|
|
502
|
-
logger.warn({
|
|
503
|
-
event: 'hook.error',
|
|
504
|
-
error,
|
|
505
|
-
});
|
|
506
|
-
// rethrow error for beforeResolve to prevent showing not found page
|
|
507
|
-
// if app has problems while loading info about routes
|
|
508
|
-
if (hookName === 'beforeResolve') {
|
|
509
|
-
throw error;
|
|
510
|
-
}
|
|
511
|
-
})));
|
|
512
|
-
logger.debug({
|
|
513
|
-
event: 'hooks.done',
|
|
514
|
-
hookName,
|
|
515
|
-
navigation,
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
registerHook(hookName, hook) {
|
|
519
|
-
return registerHook(this.hooks.get(hookName), hook);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
class History {
|
|
524
|
-
init(navigation) { }
|
|
525
|
-
listen(listener) {
|
|
526
|
-
this.listener = listener;
|
|
527
|
-
}
|
|
528
|
-
setTree(tree) {
|
|
529
|
-
this.tree = tree;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const supportsHtml5History = typeof window !== 'undefined' && window.history && window.history.pushState;
|
|
534
|
-
const wrapHistory = ({ onNavigate }) => {
|
|
535
|
-
if (!supportsHtml5History) {
|
|
536
|
-
const navigate = (data, title, url) => {
|
|
537
|
-
window.location.href = url.toString();
|
|
538
|
-
};
|
|
539
|
-
window.history.pushState = navigate;
|
|
540
|
-
window.history.replaceState = navigate;
|
|
541
|
-
return {
|
|
542
|
-
navigate: ({ path }) => navigate({}, '', path),
|
|
543
|
-
history: () => {
|
|
544
|
-
throw new Error('Method not implemented');
|
|
545
|
-
},
|
|
546
|
-
init: noop,
|
|
547
|
-
subscribe: noop,
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
let browserHistory = window.history;
|
|
551
|
-
if ('__originalHistory' in window.history) {
|
|
552
|
-
browserHistory = window.history.__originalHistory;
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
window.history.__originalHistory = {
|
|
556
|
-
pushState: browserHistory.pushState.bind(window.history),
|
|
557
|
-
replaceState: browserHistory.replaceState.bind(window.history),
|
|
558
|
-
go: browserHistory.go.bind(window.history),
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
const pushState = browserHistory.pushState.bind(window.history);
|
|
562
|
-
const replaceState = browserHistory.replaceState.bind(window.history);
|
|
563
|
-
const go = browserHistory.go.bind(window.history);
|
|
564
|
-
const navigate = ({ path, replace, state }) => {
|
|
565
|
-
if (replace) {
|
|
566
|
-
replaceState(state, '', path);
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
pushState(state, '', path);
|
|
570
|
-
}
|
|
571
|
-
};
|
|
572
|
-
const history = (delta) => {
|
|
573
|
-
go(delta);
|
|
574
|
-
};
|
|
575
|
-
const browserNavigate = (replace = false) => {
|
|
576
|
-
return (navigateState, title, url) => {
|
|
577
|
-
onNavigate({ url: url.toString(), replace, navigateState });
|
|
578
|
-
};
|
|
579
|
-
};
|
|
580
|
-
window.history.pushState = browserNavigate(false);
|
|
581
|
-
window.history.replaceState = browserNavigate(true);
|
|
582
|
-
window.history.go = history;
|
|
583
|
-
window.history.back = () => history(-1);
|
|
584
|
-
window.history.forward = () => history(1);
|
|
585
|
-
return {
|
|
586
|
-
navigate,
|
|
587
|
-
history,
|
|
588
|
-
init: (state) => {
|
|
589
|
-
replaceState(state, '');
|
|
590
|
-
},
|
|
591
|
-
subscribe: (handler) => {
|
|
592
|
-
window.addEventListener('popstate', ({ state }) => {
|
|
593
|
-
handler({
|
|
594
|
-
path: window.location.pathname + window.location.search + window.location.hash,
|
|
595
|
-
state,
|
|
596
|
-
});
|
|
597
|
-
});
|
|
598
|
-
},
|
|
599
|
-
};
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
const isHistoryState = (state) => {
|
|
603
|
-
return state && typeof state.key === 'string';
|
|
604
|
-
};
|
|
605
|
-
const generateKey = (navigation) => {
|
|
606
|
-
const { to } = navigation;
|
|
607
|
-
if (to) {
|
|
608
|
-
return `${to.name}_${to.path}`;
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
const generateState = (navigation, currentState) => {
|
|
612
|
-
const key = generateKey(navigation);
|
|
613
|
-
let { type } = navigation;
|
|
614
|
-
if (navigation.replace && currentState) {
|
|
615
|
-
type = currentState.type === type ? type : 'navigate';
|
|
616
|
-
}
|
|
617
|
-
return {
|
|
618
|
-
key,
|
|
619
|
-
type,
|
|
620
|
-
navigateState: navigation.navigateState,
|
|
621
|
-
};
|
|
622
|
-
};
|
|
623
|
-
class ClientHistory extends History {
|
|
624
|
-
constructor() {
|
|
625
|
-
super();
|
|
626
|
-
this.currentIndex = 0;
|
|
627
|
-
this.historyWrapper = wrapHistory({
|
|
628
|
-
onNavigate: ({ url, replace, navigateState }) => {
|
|
629
|
-
this.listener({
|
|
630
|
-
url,
|
|
631
|
-
replace,
|
|
632
|
-
navigateState,
|
|
633
|
-
});
|
|
634
|
-
},
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
init(navigation) {
|
|
638
|
-
var _a;
|
|
639
|
-
this.currentState = isHistoryState((_a = window.history) === null || _a === void 0 ? void 0 : _a.state)
|
|
640
|
-
? window.history.state
|
|
641
|
-
: generateState(navigation);
|
|
642
|
-
this.historyWrapper.init(this.currentState);
|
|
643
|
-
this.historyWrapper.subscribe(async ({ path, state }) => {
|
|
644
|
-
var _a, _b;
|
|
645
|
-
try {
|
|
646
|
-
let navigationType;
|
|
647
|
-
let navigateState;
|
|
648
|
-
if (isHistoryState(state)) {
|
|
649
|
-
const { key: prevKey, type: prevType } = this.currentState;
|
|
650
|
-
const { key, type } = state;
|
|
651
|
-
this.currentState = state;
|
|
652
|
-
navigateState = state.navigateState;
|
|
653
|
-
if (key === prevKey &&
|
|
654
|
-
(type === 'updateCurrentRoute' || prevType === 'updateCurrentRoute')) {
|
|
655
|
-
navigationType = 'updateCurrentRoute';
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
navigationType = 'navigate';
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
// if it is not HistoryState than it is probably not a state from @tinkoff/router so reset it
|
|
663
|
-
this.currentIndex = 0;
|
|
664
|
-
}
|
|
665
|
-
await this.listener({
|
|
666
|
-
type: navigationType,
|
|
667
|
-
history: true,
|
|
668
|
-
url: path,
|
|
669
|
-
navigateState,
|
|
670
|
-
});
|
|
671
|
-
(_a = this.goPromiseResolve) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
672
|
-
}
|
|
673
|
-
catch (err) {
|
|
674
|
-
(_b = this.goPromiseReject) === null || _b === void 0 ? void 0 : _b.call(this, err);
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
save(navigation) {
|
|
679
|
-
const { replace, url } = navigation;
|
|
680
|
-
if (!replace) {
|
|
681
|
-
this.currentIndex++;
|
|
682
|
-
}
|
|
683
|
-
this.currentState = generateState(navigation, this.currentState);
|
|
684
|
-
this.historyWrapper.navigate({
|
|
685
|
-
path: url.path,
|
|
686
|
-
replace,
|
|
687
|
-
state: this.currentState,
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
go(to, options) {
|
|
691
|
-
var _a;
|
|
692
|
-
const index = this.currentIndex + to;
|
|
693
|
-
if (index < 0) {
|
|
694
|
-
if (options === null || options === void 0 ? void 0 : options.historyFallback) {
|
|
695
|
-
return this.listener({
|
|
696
|
-
url: options.historyFallback,
|
|
697
|
-
type: 'navigate',
|
|
698
|
-
history: false,
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
const historyFallbackRoute = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getHistoryFallback(window.location.pathname);
|
|
702
|
-
if (historyFallbackRoute) {
|
|
703
|
-
return this.listener({
|
|
704
|
-
url: historyFallbackRoute.actualPath,
|
|
705
|
-
type: 'navigate',
|
|
706
|
-
history: false,
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
const promise = new Promise((resolve, reject) => {
|
|
711
|
-
this.goPromiseResolve = resolve;
|
|
712
|
-
this.goPromiseReject = reject;
|
|
713
|
-
});
|
|
714
|
-
this.historyWrapper.history(to);
|
|
715
|
-
return promise;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
class ClientRouter extends AbstractRouter {
|
|
720
|
-
constructor(options) {
|
|
721
|
-
super(options);
|
|
722
|
-
this.history = new ClientHistory();
|
|
723
|
-
this.history.listen(async ({ type, url, navigateState, replace, history }) => {
|
|
724
|
-
var _a;
|
|
725
|
-
const currentUrl = this.getCurrentUrl();
|
|
726
|
-
const { pathname, query } = this.resolveUrl({ url });
|
|
727
|
-
const isSameUrlNavigation = currentUrl.pathname === pathname;
|
|
728
|
-
if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
|
|
729
|
-
const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
|
|
730
|
-
await this.internalUpdateCurrentRoute({
|
|
731
|
-
params: route === null || route === void 0 ? void 0 : route.params,
|
|
732
|
-
query,
|
|
733
|
-
replace,
|
|
734
|
-
navigateState,
|
|
735
|
-
}, { history });
|
|
736
|
-
}
|
|
737
|
-
else {
|
|
738
|
-
await this.internalNavigate({
|
|
739
|
-
url,
|
|
740
|
-
replace,
|
|
741
|
-
navigateState,
|
|
742
|
-
}, { history });
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
async rehydrate(navigation) {
|
|
747
|
-
logger.debug({
|
|
748
|
-
event: 'rehydrate',
|
|
749
|
-
navigation,
|
|
750
|
-
});
|
|
751
|
-
const url = parse$1(window.location.href);
|
|
752
|
-
this.currentNavigation = {
|
|
753
|
-
...navigation,
|
|
754
|
-
type: 'navigate',
|
|
755
|
-
url,
|
|
756
|
-
};
|
|
757
|
-
this.lastNavigation = this.currentNavigation;
|
|
758
|
-
// rerun guard check in case it differs from server side
|
|
759
|
-
await this.runGuards(this.currentNavigation);
|
|
760
|
-
// and init any history listeners
|
|
761
|
-
this.history.init(this.currentNavigation);
|
|
762
|
-
this.currentNavigation = null;
|
|
763
|
-
// add dehydrated route to tree to prevent its loading
|
|
764
|
-
if (navigation.to) {
|
|
765
|
-
this.addRoute({
|
|
766
|
-
name: navigation.to.name,
|
|
767
|
-
path: navigation.to.path,
|
|
768
|
-
config: navigation.to.config,
|
|
769
|
-
// in case we have loaded page from some path-changing proxy
|
|
770
|
-
// save this actual path as alias
|
|
771
|
-
alias: url.pathname,
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
resolveRoute(...options) {
|
|
776
|
-
const { url } = options[0];
|
|
777
|
-
// navigation for other hosts should be considered as external navigation
|
|
778
|
-
if (url && !isSameHost(url)) {
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
return super.resolveRoute(...options);
|
|
782
|
-
}
|
|
783
|
-
async notfound(navigation) {
|
|
784
|
-
var _a, _b;
|
|
785
|
-
await super.notfound(navigation);
|
|
786
|
-
// in case we didn't find any matched route just force hard page navigation
|
|
787
|
-
const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
|
|
788
|
-
const nextUrl = navigation.url.href;
|
|
789
|
-
const isNoSpaNavigation = navigation.from && !navigation.to;
|
|
790
|
-
// prevent redirect cycle on the same page,
|
|
791
|
-
// except cases when we run no-spa navigations,
|
|
792
|
-
// because we need hard reload in this cases
|
|
793
|
-
if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
|
|
794
|
-
if (navigation.replace) {
|
|
795
|
-
window.location.replace(nextUrl);
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
window.location.assign(nextUrl);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
802
|
-
return new Promise(() => { });
|
|
803
|
-
}
|
|
804
|
-
async block(navigation) {
|
|
805
|
-
return this.notfound(navigation);
|
|
806
|
-
}
|
|
807
|
-
async redirect(navigation, target) {
|
|
808
|
-
await super.redirect(navigation, target);
|
|
809
|
-
return this.internalNavigate({
|
|
810
|
-
...target,
|
|
811
|
-
replace: target.replace || navigation.replace,
|
|
812
|
-
}, {
|
|
813
|
-
redirect: true,
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
const createTree = (route) => {
|
|
819
|
-
return {
|
|
820
|
-
route,
|
|
821
|
-
children: Object.create(null),
|
|
822
|
-
parameters: [],
|
|
823
|
-
};
|
|
824
|
-
};
|
|
825
|
-
const createNavigationRoute = (route, pathname, params) => {
|
|
826
|
-
return {
|
|
827
|
-
...route,
|
|
828
|
-
actualPath: pathname,
|
|
829
|
-
params: params !== null && params !== void 0 ? params : {},
|
|
830
|
-
};
|
|
831
|
-
};
|
|
832
|
-
class RouteTree {
|
|
833
|
-
constructor(routes) {
|
|
834
|
-
this.tree = createTree();
|
|
835
|
-
each((route) => this.addRoute(route), routes);
|
|
836
|
-
}
|
|
837
|
-
// eslint-disable-next-line max-statements
|
|
838
|
-
addRoute(route) {
|
|
839
|
-
const parts = getParts(route.path);
|
|
840
|
-
let currentTree = this.tree;
|
|
841
|
-
for (let i = 0; i < parts.length; i++) {
|
|
842
|
-
const part = parts[i];
|
|
843
|
-
const parsed = parse(part);
|
|
844
|
-
if (parsed.type === 1 /* PartType.historyFallback */) {
|
|
845
|
-
currentTree.historyFallbackRoute = route;
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
if (parsed.type === 2 /* PartType.wildcard */) {
|
|
849
|
-
currentTree.wildcardRoute = route;
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
if (parsed.type === 3 /* PartType.parameter */) {
|
|
853
|
-
const { paramName, regexp, optional } = parsed;
|
|
854
|
-
// prevent from creating new entries for same route
|
|
855
|
-
const found = find((par) => par.key === part, currentTree.parameters);
|
|
856
|
-
if (found) {
|
|
857
|
-
currentTree = found.tree;
|
|
858
|
-
}
|
|
859
|
-
else {
|
|
860
|
-
const parameter = {
|
|
861
|
-
key: part,
|
|
862
|
-
paramName,
|
|
863
|
-
regexp,
|
|
864
|
-
optional,
|
|
865
|
-
tree: createTree(),
|
|
866
|
-
};
|
|
867
|
-
if (regexp && !optional) {
|
|
868
|
-
// insert parameters with regexp before
|
|
869
|
-
const index = findIndex((par) => !par.regexp, currentTree.parameters);
|
|
870
|
-
currentTree.parameters.splice(index, 0, parameter);
|
|
871
|
-
}
|
|
872
|
-
else {
|
|
873
|
-
currentTree.parameters.push(parameter);
|
|
874
|
-
}
|
|
875
|
-
currentTree = parameter.tree;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
if (!currentTree.children[part]) {
|
|
880
|
-
currentTree.children[part] = createTree();
|
|
881
|
-
}
|
|
882
|
-
currentTree = currentTree.children[part];
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
currentTree.route = route;
|
|
886
|
-
}
|
|
887
|
-
getRoute(pathname) {
|
|
888
|
-
return this.findRoute(pathname, 'route');
|
|
889
|
-
}
|
|
890
|
-
getWildcard(pathname) {
|
|
891
|
-
return this.findRoute(pathname, 'wildcardRoute');
|
|
892
|
-
}
|
|
893
|
-
getHistoryFallback(pathname) {
|
|
894
|
-
const route = this.findRoute(pathname, 'historyFallbackRoute');
|
|
895
|
-
return (route && {
|
|
896
|
-
...route,
|
|
897
|
-
// remove <history-fallback> from path
|
|
898
|
-
actualPath: route.path.replace(HISTORY_FALLBACK_REGEXP, '') || '/',
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
// eslint-disable-next-line max-statements
|
|
902
|
-
findRoute(pathname, propertyName) {
|
|
903
|
-
// we should use exact match only for classic route
|
|
904
|
-
// as special routes (for not-found and history-fallback) are defined for whole subtree
|
|
905
|
-
const exactMatch = propertyName === 'route';
|
|
906
|
-
const parts = getParts(pathname);
|
|
907
|
-
const queue = [
|
|
908
|
-
[this.tree, 0],
|
|
909
|
-
];
|
|
910
|
-
while (queue.length) {
|
|
911
|
-
const [currentTree, index, params] = queue.pop();
|
|
912
|
-
const { children, parameters } = currentTree;
|
|
913
|
-
// this flag mean we can only check for optional parameters
|
|
914
|
-
// as we didn't find static route for this path, but still may find
|
|
915
|
-
// some inner route inside optional branch
|
|
916
|
-
// the value of parameter will be empty in this case ofc
|
|
917
|
-
let optionalOnly = false;
|
|
918
|
-
if (index >= parts.length) {
|
|
919
|
-
if (currentTree[propertyName]) {
|
|
920
|
-
return createNavigationRoute(currentTree[propertyName], pathname, params);
|
|
921
|
-
}
|
|
922
|
-
// here we cant check any options except for optional parameters
|
|
923
|
-
optionalOnly = true;
|
|
924
|
-
}
|
|
925
|
-
// first add to queue special routes (not-found or history-fallback) to check it after other cases, as it will be processed last
|
|
926
|
-
if (!exactMatch && currentTree[propertyName]) {
|
|
927
|
-
queue.push([currentTree, parts.length, params]);
|
|
928
|
-
}
|
|
929
|
-
const part = parts[index];
|
|
930
|
-
const child = children[part];
|
|
931
|
-
// then add checks for only optional
|
|
932
|
-
for (const param of parameters) {
|
|
933
|
-
const { optional, tree } = param;
|
|
934
|
-
if (optional) {
|
|
935
|
-
queue.push([tree, index, params]);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
if (optionalOnly) {
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
// for non-optional cases
|
|
942
|
-
for (let i = parameters.length - 1; i >= 0; i--) {
|
|
943
|
-
const param = parameters[i];
|
|
944
|
-
const { paramName, tree, regexp } = param;
|
|
945
|
-
const match = regexp === null || regexp === void 0 ? void 0 : regexp.exec(part);
|
|
946
|
-
const paramValue = regexp ? match === null || match === void 0 ? void 0 : match[1] : part;
|
|
947
|
-
if (paramValue) {
|
|
948
|
-
queue.push([tree, index + 1, { ...params, [paramName]: paramValue }]);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
if (child) {
|
|
952
|
-
// add checks for static child subtree last as it will be processed first after queue.pop
|
|
953
|
-
queue.push([child, index + 1, params]);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
const DELAY_CHECK_INTERVAL = 50;
|
|
960
|
-
class Router extends ClientRouter {
|
|
961
|
-
constructor(options) {
|
|
962
|
-
super(options);
|
|
963
|
-
this.tree = new RouteTree(options.routes);
|
|
964
|
-
this.history.setTree(this.tree);
|
|
965
|
-
}
|
|
966
|
-
async rehydrate(navigation) {
|
|
967
|
-
return this.resolveIfDelayFound(super.rehydrate(navigation));
|
|
968
|
-
}
|
|
969
|
-
async start() {
|
|
970
|
-
await super.start();
|
|
971
|
-
if (this.delayedNavigation) {
|
|
972
|
-
const { delayedNavigation } = this;
|
|
973
|
-
this.delayedNavigation = null;
|
|
974
|
-
return this.flattenDelayedNavigation(delayedNavigation);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
async run(navigation) {
|
|
978
|
-
// if router is not started yet delay current navigation without blocking promise resolving
|
|
979
|
-
if (!this.started) {
|
|
980
|
-
this.delayNavigation(navigation);
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
// if we have already running navigation delay current one and call it later
|
|
984
|
-
if (this.currentNavigation) {
|
|
985
|
-
return this.delayNavigation(navigation);
|
|
986
|
-
}
|
|
987
|
-
// ignore previous navigations that were put in delayedNavigation as we have more fresh navigation to execute
|
|
988
|
-
if (this.delayedNavigation) {
|
|
989
|
-
logger.info({
|
|
990
|
-
event: 'delay-navigation-drop',
|
|
991
|
-
delayedNavigation: this.delayedNavigation,
|
|
992
|
-
navigation,
|
|
993
|
-
});
|
|
994
|
-
this.delayedNavigation = null;
|
|
995
|
-
}
|
|
996
|
-
return this.flattenDelayedNavigation(navigation);
|
|
997
|
-
}
|
|
998
|
-
delayNavigation(navigation) {
|
|
999
|
-
this.delayedNavigation = navigation;
|
|
1000
|
-
logger.info({
|
|
1001
|
-
event: 'delay-navigation',
|
|
1002
|
-
navigation,
|
|
1003
|
-
});
|
|
1004
|
-
if (this.delayedPromise) {
|
|
1005
|
-
return this.delayedPromise;
|
|
1006
|
-
}
|
|
1007
|
-
// resolve promise only after latest navigation has been executed
|
|
1008
|
-
this.delayedPromise = new Promise((resolve, reject) => {
|
|
1009
|
-
this.delayedResolve = resolve;
|
|
1010
|
-
this.delayedReject = reject;
|
|
1011
|
-
});
|
|
1012
|
-
return this.delayedPromise;
|
|
1013
|
-
}
|
|
1014
|
-
commitNavigation(navigation) {
|
|
1015
|
-
// if we have parallel navigation do not update current url, as it outdated anyway
|
|
1016
|
-
if (navigation.cancelled) {
|
|
1017
|
-
logger.debug({
|
|
1018
|
-
event: 'delay-ignore-commit',
|
|
1019
|
-
navigation,
|
|
1020
|
-
});
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
return super.commitNavigation(navigation);
|
|
1024
|
-
}
|
|
1025
|
-
async runGuards(navigation) {
|
|
1026
|
-
// drop checking guards if we have delayed navigation
|
|
1027
|
-
if (navigation.cancelled) {
|
|
1028
|
-
logger.debug({
|
|
1029
|
-
event: 'delay-ignore-guards',
|
|
1030
|
-
navigation,
|
|
1031
|
-
});
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
return super.runGuards(navigation);
|
|
1035
|
-
}
|
|
1036
|
-
async runHooks(hookName, navigation) {
|
|
1037
|
-
// drop hook calls if we have an other navigation delayed
|
|
1038
|
-
// except only for case when current navigation already happened
|
|
1039
|
-
// and we should synchronize this update with app
|
|
1040
|
-
// (in case app has some logic for currently showing url on afterNavigate or afterRouteUpdate)
|
|
1041
|
-
if (navigation.cancelled && this.lastNavigation !== navigation) {
|
|
1042
|
-
logger.debug({
|
|
1043
|
-
event: 'delay-ignore-hooks',
|
|
1044
|
-
navigation,
|
|
1045
|
-
});
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
try {
|
|
1049
|
-
await super.runHooks(hookName, navigation);
|
|
1050
|
-
}
|
|
1051
|
-
catch (error) {
|
|
1052
|
-
return this.notfound(navigation);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
async resolveIfDelayFound(task) {
|
|
1056
|
-
let delayResolve;
|
|
1057
|
-
const timer = setInterval(() => {
|
|
1058
|
-
var _a;
|
|
1059
|
-
if (this.delayedNavigation) {
|
|
1060
|
-
if (this.delayedNavigation.type === 'navigate' ||
|
|
1061
|
-
this.delayedNavigation.type === ((_a = this.currentNavigation) === null || _a === void 0 ? void 0 : _a.type)) {
|
|
1062
|
-
logger.info({
|
|
1063
|
-
event: 'delay-navigation-found',
|
|
1064
|
-
navigation: this.delayedNavigation,
|
|
1065
|
-
});
|
|
1066
|
-
// set cancelled flag
|
|
1067
|
-
if (this.currentNavigation) {
|
|
1068
|
-
this.currentNavigation.cancelled = true;
|
|
1069
|
-
this.currentNavigation = null;
|
|
1070
|
-
}
|
|
1071
|
-
// resolve current navigation to start new navigation asap
|
|
1072
|
-
delayResolve();
|
|
1073
|
-
}
|
|
1074
|
-
else {
|
|
1075
|
-
// updateCurrentRoute should happen only after currentNavigation, so resolve it first to prevent dead-lock
|
|
1076
|
-
this.delayedResolve();
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
}, DELAY_CHECK_INTERVAL);
|
|
1080
|
-
await Promise.race([
|
|
1081
|
-
task,
|
|
1082
|
-
new Promise((resolve) => {
|
|
1083
|
-
delayResolve = resolve;
|
|
1084
|
-
}),
|
|
1085
|
-
]);
|
|
1086
|
-
clearInterval(timer);
|
|
1087
|
-
delayResolve();
|
|
1088
|
-
}
|
|
1089
|
-
async flattenDelayedNavigation(navigation) {
|
|
1090
|
-
const flatten = async (nav) => {
|
|
1091
|
-
await this.resolveIfDelayFound(super.run(nav));
|
|
1092
|
-
// if new navigation has been called while this navigation lasts
|
|
1093
|
-
// call new navigation execution
|
|
1094
|
-
if (this.delayedNavigation) {
|
|
1095
|
-
const { delayedNavigation } = this;
|
|
1096
|
-
logger.info({
|
|
1097
|
-
event: 'delay-navigation-run',
|
|
1098
|
-
navigation: delayedNavigation,
|
|
1099
|
-
});
|
|
1100
|
-
this.delayedNavigation = null;
|
|
1101
|
-
return flatten(delayedNavigation);
|
|
1102
|
-
}
|
|
1103
|
-
};
|
|
1104
|
-
return flatten(navigation)
|
|
1105
|
-
.then(() => {
|
|
1106
|
-
var _a;
|
|
1107
|
-
(_a = this.delayedResolve) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1108
|
-
}, (err) => {
|
|
1109
|
-
var _a;
|
|
1110
|
-
(_a = this.delayedReject) === null || _a === void 0 ? void 0 : _a.call(this, err);
|
|
1111
|
-
})
|
|
1112
|
-
.finally(() => {
|
|
1113
|
-
this.delayedPromise = null;
|
|
1114
|
-
this.delayedResolve = null;
|
|
1115
|
-
this.delayedReject = null;
|
|
1116
|
-
});
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
const omitHash = (url) => url.href.replace(/#.*$/, '');
|
|
1121
|
-
class NoSpaRouter extends ClientRouter {
|
|
1122
|
-
run(navigation) {
|
|
1123
|
-
const { type, fromUrl, url } = navigation;
|
|
1124
|
-
// support only updateCurrentRoute or hash navigations
|
|
1125
|
-
if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
|
|
1126
|
-
return super.run(navigation);
|
|
1127
|
-
}
|
|
1128
|
-
return this.notfound(navigation);
|
|
1129
|
-
}
|
|
1130
|
-
// do not call any hooks as it is only supports url updates with hash
|
|
1131
|
-
async runHooks() { }
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
const RouterContext = createContext(null);
|
|
1135
|
-
const RouteContext = createContext(null);
|
|
1136
|
-
const UrlContext = createContext(null);
|
|
1137
|
-
|
|
1138
|
-
const Provider = ({ router, serverState, children }) => {
|
|
1139
|
-
const route = useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => router.getLastRoute(), serverState ? () => serverState.currentRoute : () => router.getLastRoute());
|
|
1140
|
-
const url = useSyncExternalStore((cb) => router.registerSyncHook('change', cb), () => router.getLastUrl(), serverState ? () => serverState.currentUrl : () => router.getLastUrl());
|
|
1141
|
-
return (jsx(RouterContext.Provider, { value: router, children: jsx(RouteContext.Provider, { value: route, children: jsx(UrlContext.Provider, { value: url, children: children }) }) }));
|
|
1142
|
-
};
|
|
1143
|
-
Provider.displayName = 'Provider';
|
|
1144
|
-
|
|
1145
|
-
const useRouter = () => {
|
|
1146
|
-
return useContext(RouterContext);
|
|
1147
|
-
};
|
|
1148
|
-
|
|
1149
|
-
const useRoute = () => {
|
|
1150
|
-
return useContext(RouteContext);
|
|
1151
|
-
};
|
|
1152
|
-
|
|
1153
|
-
const useUrl = () => {
|
|
1154
|
-
return useContext(UrlContext);
|
|
1155
|
-
};
|
|
1156
|
-
|
|
1157
|
-
const convertToNavigateOptions = (options) => {
|
|
1158
|
-
return typeof options === 'string' ? { url: options } : options;
|
|
1159
|
-
};
|
|
1160
|
-
const useNavigate = (rootOptions) => {
|
|
1161
|
-
const router = useRouter();
|
|
1162
|
-
const rootOpts = useShallowEqual(convertToNavigateOptions(rootOptions));
|
|
1163
|
-
return useCallback((specificOptions) => {
|
|
1164
|
-
const opts = rootOpts !== null && rootOpts !== void 0 ? rootOpts : convertToNavigateOptions(specificOptions);
|
|
1165
|
-
return router.navigate(opts);
|
|
1166
|
-
}, [rootOpts, router]);
|
|
1167
|
-
};
|
|
1168
|
-
|
|
1169
|
-
export { AbstractRouter, History, NoSpaRouter, Provider, RouteTree, Router, getParts, isHistoryFallback, isParameterized, isWildcard, logger, makePath, parse, setLogger, useNavigate, useRoute, useRouter, useUrl };
|
|
1
|
+
export { Router } from './router/browser.browser.js';
|
|
2
|
+
export { NoSpaRouter } from './router/clientNoSpa.browser.js';
|
|
3
|
+
export { AbstractRouter } from './router/abstract.browser.js';
|
|
4
|
+
export { History } from './history/base.browser.js';
|
|
5
|
+
export { logger, setLogger } from './logger.browser.js';
|
|
6
|
+
export { Provider } from './components/react/provider.browser.js';
|
|
7
|
+
export { useRouter } from './components/react/useRouter.browser.js';
|
|
8
|
+
export { useRoute } from './components/react/useRoute.browser.js';
|
|
9
|
+
export { useUrl } from './components/react/useUrl.browser.js';
|
|
10
|
+
export { useNavigate } from './components/react/useNavigate.browser.js';
|
|
11
|
+
export { RouteTree } from './tree/tree.browser.js';
|
|
12
|
+
export { getParts, isHistoryFallback, isParameterized, isWildcard, makePath, parse } from './tree/utils.browser.js';
|