@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
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import isString from '@tinkoff/utils/is/string';
|
|
2
|
+
import isObject from '@tinkoff/utils/is/object';
|
|
3
|
+
import { parse, rawResolveUrl, rawParse, convertRawUrl, rawAssignUrl } from '@tinkoff/url';
|
|
4
|
+
import { makePath } from '../tree/utils.es.js';
|
|
5
|
+
import { logger } from '../logger.es.js';
|
|
6
|
+
import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost, registerHook } from '../utils.es.js';
|
|
7
|
+
|
|
8
|
+
class AbstractRouter {
|
|
9
|
+
constructor({ trailingSlash, mergeSlashes, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
|
|
10
|
+
this.started = false;
|
|
11
|
+
this.trailingSlash = false;
|
|
12
|
+
this.strictTrailingSlash = true;
|
|
13
|
+
this.mergeSlashes = false;
|
|
14
|
+
this.trailingSlash = trailingSlash !== null && trailingSlash !== void 0 ? trailingSlash : false;
|
|
15
|
+
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
16
|
+
this.mergeSlashes = mergeSlashes !== null && mergeSlashes !== void 0 ? mergeSlashes : false;
|
|
17
|
+
this.hooks = new Map([
|
|
18
|
+
['beforeResolve', new Set(beforeResolve)],
|
|
19
|
+
['beforeNavigate', new Set(beforeNavigate)],
|
|
20
|
+
['afterNavigate', new Set(afterNavigate)],
|
|
21
|
+
['beforeUpdateCurrent', new Set(beforeUpdateCurrent)],
|
|
22
|
+
['afterUpdateCurrent', new Set(afterUpdateCurrent)],
|
|
23
|
+
]);
|
|
24
|
+
this.guards = new Set(guards);
|
|
25
|
+
this.syncHooks = new Map([['change', new Set(onChange)]]);
|
|
26
|
+
this.onRedirect = onRedirect;
|
|
27
|
+
this.onNotFound = onNotFound;
|
|
28
|
+
this.onBlock = onBlock;
|
|
29
|
+
}
|
|
30
|
+
// start is using as marker that any preparation for proper work has done in the app
|
|
31
|
+
// and now router can manage any navigations
|
|
32
|
+
async start() {
|
|
33
|
+
logger.debug({
|
|
34
|
+
event: 'start',
|
|
35
|
+
});
|
|
36
|
+
this.started = true;
|
|
37
|
+
}
|
|
38
|
+
getCurrentRoute() {
|
|
39
|
+
var _a, _b, _c;
|
|
40
|
+
// when something will try to get currentRoute while navigating, it will get route which router currently navigating
|
|
41
|
+
// in case some handler supposed to load data of route or similar
|
|
42
|
+
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;
|
|
43
|
+
}
|
|
44
|
+
getCurrentUrl() {
|
|
45
|
+
var _a, _b, _c;
|
|
46
|
+
// same as getCurrentRoute
|
|
47
|
+
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;
|
|
48
|
+
}
|
|
49
|
+
getLastRoute() {
|
|
50
|
+
var _a;
|
|
51
|
+
return (_a = this.lastNavigation) === null || _a === void 0 ? void 0 : _a.to;
|
|
52
|
+
}
|
|
53
|
+
getLastUrl() {
|
|
54
|
+
var _a;
|
|
55
|
+
return (_a = this.lastNavigation) === null || _a === void 0 ? void 0 : _a.url;
|
|
56
|
+
}
|
|
57
|
+
commitNavigation(navigation) {
|
|
58
|
+
logger.debug({
|
|
59
|
+
event: 'commit-navigation',
|
|
60
|
+
navigation,
|
|
61
|
+
});
|
|
62
|
+
if (!navigation.history) {
|
|
63
|
+
// in case we came from history do not history back to prevent infinity recursive calls
|
|
64
|
+
this.history.save(navigation);
|
|
65
|
+
}
|
|
66
|
+
this.lastNavigation = navigation;
|
|
67
|
+
this.currentNavigation = null;
|
|
68
|
+
this.runSyncHooks('change', navigation);
|
|
69
|
+
}
|
|
70
|
+
async updateCurrentRoute(updateRouteOptions) {
|
|
71
|
+
return this.internalUpdateCurrentRoute(updateRouteOptions, {});
|
|
72
|
+
}
|
|
73
|
+
async internalUpdateCurrentRoute(updateRouteOptions, { history }) {
|
|
74
|
+
var _a;
|
|
75
|
+
const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
76
|
+
if (!prevNavigation) {
|
|
77
|
+
throw new Error('updateCurrentRoute should only be called after navigate to some route');
|
|
78
|
+
}
|
|
79
|
+
const { replace, params, navigateState } = updateRouteOptions;
|
|
80
|
+
const { to: from, url: fromUrl } = prevNavigation;
|
|
81
|
+
const navigation = {
|
|
82
|
+
type: 'updateCurrentRoute',
|
|
83
|
+
from,
|
|
84
|
+
to: this.resolveRoute({ params, navigateState }, { wildcard: true }),
|
|
85
|
+
url: this.resolveUrl(updateRouteOptions),
|
|
86
|
+
fromUrl,
|
|
87
|
+
replace,
|
|
88
|
+
history,
|
|
89
|
+
navigateState,
|
|
90
|
+
code: updateRouteOptions.code,
|
|
91
|
+
};
|
|
92
|
+
logger.debug({
|
|
93
|
+
event: 'update-current-route',
|
|
94
|
+
updateRouteOptions,
|
|
95
|
+
navigation,
|
|
96
|
+
});
|
|
97
|
+
await this.run(navigation);
|
|
98
|
+
}
|
|
99
|
+
async runUpdateCurrentRoute(navigation) {
|
|
100
|
+
await this.runHooks('beforeUpdateCurrent', navigation);
|
|
101
|
+
this.commitNavigation(navigation);
|
|
102
|
+
await this.runHooks('afterUpdateCurrent', navigation);
|
|
103
|
+
}
|
|
104
|
+
async navigate(navigateOptions) {
|
|
105
|
+
return this.internalNavigate(makeNavigateOptions(navigateOptions), {});
|
|
106
|
+
}
|
|
107
|
+
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
108
|
+
var _a;
|
|
109
|
+
const { url, replace, params, navigateState, code } = navigateOptions;
|
|
110
|
+
const prevNavigation = redirect
|
|
111
|
+
? this.lastNavigation
|
|
112
|
+
: (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
|
|
113
|
+
if (!url && !prevNavigation) {
|
|
114
|
+
throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
|
|
115
|
+
}
|
|
116
|
+
const resolvedUrl = this.resolveUrl(navigateOptions);
|
|
117
|
+
const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
|
|
118
|
+
const redirectFrom = redirect ? this.currentNavigation.to : undefined;
|
|
119
|
+
let navigation = {
|
|
120
|
+
type: 'navigate',
|
|
121
|
+
from,
|
|
122
|
+
url: resolvedUrl,
|
|
123
|
+
fromUrl,
|
|
124
|
+
replace,
|
|
125
|
+
history,
|
|
126
|
+
navigateState,
|
|
127
|
+
code,
|
|
128
|
+
redirect,
|
|
129
|
+
redirectFrom,
|
|
130
|
+
};
|
|
131
|
+
await this.runHooks('beforeResolve', navigation);
|
|
132
|
+
const to = this.resolveRoute({ url: resolvedUrl, params, navigateState }, { wildcard: true });
|
|
133
|
+
if (to) {
|
|
134
|
+
navigation = {
|
|
135
|
+
...navigation,
|
|
136
|
+
to,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
logger.debug({
|
|
140
|
+
event: 'navigation',
|
|
141
|
+
navigation,
|
|
142
|
+
});
|
|
143
|
+
if (!navigation.to) {
|
|
144
|
+
return this.notfound(navigation);
|
|
145
|
+
}
|
|
146
|
+
await this.run(navigation);
|
|
147
|
+
}
|
|
148
|
+
async runNavigate(navigation) {
|
|
149
|
+
// check for redirect in new route description
|
|
150
|
+
if (navigation.to.redirect) {
|
|
151
|
+
return this.redirect(navigation, makeNavigateOptions(navigation.to.redirect));
|
|
152
|
+
}
|
|
153
|
+
await this.runGuards(navigation);
|
|
154
|
+
await this.runHooks('beforeNavigate', navigation);
|
|
155
|
+
this.commitNavigation(navigation);
|
|
156
|
+
await this.runHooks('afterNavigate', navigation);
|
|
157
|
+
}
|
|
158
|
+
async run(navigation) {
|
|
159
|
+
this.currentNavigation = navigation;
|
|
160
|
+
if (navigation.type === 'navigate') {
|
|
161
|
+
await this.runNavigate(navigation);
|
|
162
|
+
}
|
|
163
|
+
if (navigation.type === 'updateCurrentRoute') {
|
|
164
|
+
await this.runUpdateCurrentRoute(navigation);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
resolve(resolveOptions, options) {
|
|
168
|
+
const opts = typeof resolveOptions === 'string' ? { url: resolveOptions } : resolveOptions;
|
|
169
|
+
return this.resolveRoute({ ...opts, url: parse(opts.url) }, options);
|
|
170
|
+
}
|
|
171
|
+
back(options) {
|
|
172
|
+
return this.go(-1, options);
|
|
173
|
+
}
|
|
174
|
+
forward() {
|
|
175
|
+
return this.go(1);
|
|
176
|
+
}
|
|
177
|
+
async go(to, options) {
|
|
178
|
+
logger.debug({
|
|
179
|
+
event: 'history.go',
|
|
180
|
+
to,
|
|
181
|
+
});
|
|
182
|
+
return this.history.go(to, options);
|
|
183
|
+
}
|
|
184
|
+
isNavigating() {
|
|
185
|
+
return !!this.currentNavigation;
|
|
186
|
+
}
|
|
187
|
+
async dehydrate() {
|
|
188
|
+
throw new Error('Not implemented');
|
|
189
|
+
}
|
|
190
|
+
async rehydrate(navigation) {
|
|
191
|
+
throw new Error('Not implemented');
|
|
192
|
+
}
|
|
193
|
+
addRoute(route) {
|
|
194
|
+
var _a;
|
|
195
|
+
(_a = this.tree) === null || _a === void 0 ? void 0 : _a.addRoute(route);
|
|
196
|
+
}
|
|
197
|
+
async redirect(navigation, target) {
|
|
198
|
+
var _a;
|
|
199
|
+
logger.debug({
|
|
200
|
+
event: 'redirect',
|
|
201
|
+
navigation,
|
|
202
|
+
target,
|
|
203
|
+
});
|
|
204
|
+
return (_a = this.onRedirect) === null || _a === void 0 ? void 0 : _a.call(this, {
|
|
205
|
+
...navigation,
|
|
206
|
+
from: navigation.to,
|
|
207
|
+
fromUrl: navigation.url,
|
|
208
|
+
to: null,
|
|
209
|
+
url: this.resolveUrl(target),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async notfound(navigation) {
|
|
213
|
+
var _a;
|
|
214
|
+
logger.debug({
|
|
215
|
+
event: 'not-found',
|
|
216
|
+
navigation,
|
|
217
|
+
});
|
|
218
|
+
return (_a = this.onNotFound) === null || _a === void 0 ? void 0 : _a.call(this, navigation);
|
|
219
|
+
}
|
|
220
|
+
async block(navigation) {
|
|
221
|
+
logger.debug({
|
|
222
|
+
event: 'blocked',
|
|
223
|
+
navigation,
|
|
224
|
+
});
|
|
225
|
+
this.currentNavigation = null;
|
|
226
|
+
if (this.onBlock) {
|
|
227
|
+
return this.onBlock(navigation);
|
|
228
|
+
}
|
|
229
|
+
throw new Error('Navigation blocked');
|
|
230
|
+
}
|
|
231
|
+
normalizePathname(pathname) {
|
|
232
|
+
let normalized = pathname;
|
|
233
|
+
if (this.mergeSlashes) {
|
|
234
|
+
normalized = normalizeManySlashes(normalized);
|
|
235
|
+
}
|
|
236
|
+
if (!this.strictTrailingSlash) {
|
|
237
|
+
normalized = normalizeTrailingSlash(normalized, this.trailingSlash);
|
|
238
|
+
}
|
|
239
|
+
return normalized;
|
|
240
|
+
}
|
|
241
|
+
resolveUrl({ url, query = {}, params, preserveQuery, hash }) {
|
|
242
|
+
var _a;
|
|
243
|
+
const currentRoute = this.getCurrentRoute();
|
|
244
|
+
const currentUrl = this.getCurrentUrl();
|
|
245
|
+
const resultUrl = url ? rawResolveUrl((_a = currentUrl === null || currentUrl === void 0 ? void 0 : currentUrl.href) !== null && _a !== void 0 ? _a : '', url) : rawParse(currentUrl.href);
|
|
246
|
+
let { pathname } = resultUrl;
|
|
247
|
+
if (params) {
|
|
248
|
+
if (url) {
|
|
249
|
+
pathname = makePath(resultUrl.pathname, params);
|
|
250
|
+
}
|
|
251
|
+
else if (currentRoute) {
|
|
252
|
+
pathname = makePath(currentRoute.path, { ...currentRoute.params, ...params });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (isSameHost(resultUrl)) {
|
|
256
|
+
pathname = this.normalizePathname(pathname);
|
|
257
|
+
}
|
|
258
|
+
return convertRawUrl(rawAssignUrl(resultUrl, {
|
|
259
|
+
pathname,
|
|
260
|
+
search: url ? resultUrl.search : '',
|
|
261
|
+
query: {
|
|
262
|
+
...(preserveQuery ? this.getCurrentUrl().query : {}),
|
|
263
|
+
...query,
|
|
264
|
+
},
|
|
265
|
+
hash: hash !== null && hash !== void 0 ? hash : resultUrl.hash,
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
resolveRoute({ url, params, navigateState }, { wildcard } = {}) {
|
|
269
|
+
var _a, _b;
|
|
270
|
+
let route = url ? (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(url.pathname) : this.getCurrentRoute();
|
|
271
|
+
if (wildcard && !route && url) {
|
|
272
|
+
// if ordinary route not found look for a wildcard route
|
|
273
|
+
route = (_b = this.tree) === null || _b === void 0 ? void 0 : _b.getWildcard(url.pathname);
|
|
274
|
+
}
|
|
275
|
+
if (!route) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// if condition is true route data not changed, so no need to create new reference for route object
|
|
279
|
+
if (!params && navigateState === route.navigateState) {
|
|
280
|
+
return route;
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
...route,
|
|
284
|
+
params: { ...route.params, ...params },
|
|
285
|
+
navigateState,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async runGuards(navigation) {
|
|
289
|
+
logger.debug({
|
|
290
|
+
event: 'guards.run',
|
|
291
|
+
navigation,
|
|
292
|
+
});
|
|
293
|
+
if (!this.guards) {
|
|
294
|
+
logger.debug({
|
|
295
|
+
event: 'guards.empty',
|
|
296
|
+
navigation,
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const results = await Promise.all(Array.from(this.guards).map((guard) => Promise.resolve(guard(navigation)).catch((error) => {
|
|
301
|
+
logger.warn({
|
|
302
|
+
event: 'guard.error',
|
|
303
|
+
error,
|
|
304
|
+
});
|
|
305
|
+
})));
|
|
306
|
+
logger.debug({
|
|
307
|
+
event: 'guards.done',
|
|
308
|
+
navigation,
|
|
309
|
+
results,
|
|
310
|
+
});
|
|
311
|
+
for (const result of results) {
|
|
312
|
+
if (result === false) {
|
|
313
|
+
return this.block(navigation);
|
|
314
|
+
}
|
|
315
|
+
if (isString(result) || isObject(result)) {
|
|
316
|
+
return this.redirect(navigation, makeNavigateOptions(result));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
registerGuard(guard) {
|
|
321
|
+
return registerHook(this.guards, guard);
|
|
322
|
+
}
|
|
323
|
+
runSyncHooks(hookName, navigation) {
|
|
324
|
+
logger.debug({
|
|
325
|
+
event: 'sync-hooks.run',
|
|
326
|
+
hookName,
|
|
327
|
+
navigation,
|
|
328
|
+
});
|
|
329
|
+
const hooks = this.syncHooks.get(hookName);
|
|
330
|
+
if (!hooks) {
|
|
331
|
+
logger.debug({
|
|
332
|
+
event: 'sync-hooks.empty',
|
|
333
|
+
hookName,
|
|
334
|
+
navigation,
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
for (const hook of hooks) {
|
|
339
|
+
try {
|
|
340
|
+
hook(navigation);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
logger.warn({
|
|
344
|
+
event: 'sync-hooks.error',
|
|
345
|
+
error,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
logger.debug({
|
|
350
|
+
event: 'sync-hooks.done',
|
|
351
|
+
hookName,
|
|
352
|
+
navigation,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
registerSyncHook(hookName, hook) {
|
|
356
|
+
return registerHook(this.syncHooks.get(hookName), hook);
|
|
357
|
+
}
|
|
358
|
+
async runHooks(hookName, navigation) {
|
|
359
|
+
logger.debug({
|
|
360
|
+
event: 'hooks.run',
|
|
361
|
+
hookName,
|
|
362
|
+
navigation,
|
|
363
|
+
});
|
|
364
|
+
const hooks = this.hooks.get(hookName);
|
|
365
|
+
if (!hooks) {
|
|
366
|
+
logger.debug({
|
|
367
|
+
event: 'hooks.empty',
|
|
368
|
+
hookName,
|
|
369
|
+
navigation,
|
|
370
|
+
});
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
await Promise.all(Array.from(hooks).map((hook) => Promise.resolve(hook(navigation)).catch((error) => {
|
|
374
|
+
logger.warn({
|
|
375
|
+
event: 'hook.error',
|
|
376
|
+
error,
|
|
377
|
+
});
|
|
378
|
+
// rethrow error for beforeResolve to prevent showing not found page
|
|
379
|
+
// if app has problems while loading info about routes
|
|
380
|
+
if (hookName === 'beforeResolve') {
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
})));
|
|
384
|
+
logger.debug({
|
|
385
|
+
event: 'hooks.done',
|
|
386
|
+
hookName,
|
|
387
|
+
navigation,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
registerHook(hookName, hook) {
|
|
391
|
+
return registerHook(this.hooks.get(hookName), hook);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export { AbstractRouter };
|