@tinkoff/router 0.5.65 → 0.5.67
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/router/abstract.browser.js +133 -87
- package/lib/router/abstract.d.ts +45 -8
- package/lib/router/abstract.es.js +133 -87
- package/lib/router/abstract.js +132 -86
- package/lib/router/client.browser.js +4 -2
- package/lib/router/client.es.js +4 -2
- package/lib/router/client.js +4 -2
- package/lib/router/server.es.js +9 -7
- package/lib/router/server.js +9 -7
- package/lib/types.d.ts +4 -0
- package/lib/utils.browser.js +1 -7
- package/lib/utils.d.ts +1 -2
- package/lib/utils.es.js +1 -7
- package/lib/utils.js +0 -7
- package/package.json +3 -2
|
@@ -1,34 +1,89 @@
|
|
|
1
1
|
import isString from '@tinkoff/utils/is/string';
|
|
2
2
|
import isObject from '@tinkoff/utils/is/object';
|
|
3
3
|
import { parse, rawResolveUrl, rawParse, convertRawUrl, rawAssignUrl } from '@tinkoff/url';
|
|
4
|
+
import { TapableHooks } from '@tinkoff/hook-runner';
|
|
4
5
|
import { makePath } from '../tree/utils.browser.js';
|
|
5
6
|
import { logger } from '../logger.browser.js';
|
|
6
|
-
import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost
|
|
7
|
+
import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost } from '../utils.browser.js';
|
|
7
8
|
|
|
8
9
|
class AbstractRouter {
|
|
9
|
-
|
|
10
|
+
// eslint-disable-next-line max-statements
|
|
11
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, hooksFactory, plugins, }) {
|
|
10
12
|
this.started = false;
|
|
11
13
|
this.trailingSlash = false;
|
|
12
14
|
this.strictTrailingSlash = true;
|
|
13
15
|
this.viewTransitionsEnabled = false;
|
|
14
16
|
this.mergeSlashes = false;
|
|
17
|
+
this.hooksIndex = 0;
|
|
18
|
+
this.syncHooksIndex = 0;
|
|
19
|
+
this.guardsIndex = 0;
|
|
15
20
|
this.trailingSlash = trailingSlash ?? false;
|
|
16
21
|
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
17
22
|
this.mergeSlashes = mergeSlashes ?? false;
|
|
18
23
|
this.viewTransitionsEnabled = enableViewTransitions ?? false;
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
['afterNavigate', new Set(afterNavigate)],
|
|
23
|
-
['beforeUpdateCurrent', new Set(beforeUpdateCurrent)],
|
|
24
|
-
['afterUpdateCurrent', new Set(afterUpdateCurrent)],
|
|
25
|
-
]);
|
|
26
|
-
this.guards = new Set(guards);
|
|
27
|
-
this.syncHooks = new Map([['change', new Set(onChange)]]);
|
|
24
|
+
this.hooksFactory = hooksFactory ?? new TapableHooks();
|
|
25
|
+
this.plugins = plugins ?? [];
|
|
26
|
+
this.currentUuid = 0;
|
|
28
27
|
this.onRedirect = onRedirect;
|
|
29
28
|
this.onNotFound = onNotFound;
|
|
30
29
|
this.onBlock = onBlock;
|
|
31
|
-
this.
|
|
30
|
+
this.hooks = new Map([
|
|
31
|
+
['beforeResolve', this.hooksFactory.createAsyncParallel('beforeResolve')],
|
|
32
|
+
['beforeNavigate', this.hooksFactory.createAsyncParallel('beforeNavigate')],
|
|
33
|
+
['afterNavigate', this.hooksFactory.createAsyncParallel('afterNavigate')],
|
|
34
|
+
['beforeUpdateCurrent', this.hooksFactory.createAsyncParallel('beforeUpdateCurrent')],
|
|
35
|
+
['afterUpdateCurrent', this.hooksFactory.createAsyncParallel('afterUpdateCurrent')],
|
|
36
|
+
]);
|
|
37
|
+
beforeResolve.forEach((hook) => this.registerHook('beforeResolve', hook));
|
|
38
|
+
beforeNavigate.forEach((hook) => this.registerHook('beforeNavigate', hook));
|
|
39
|
+
afterNavigate.forEach((hook) => this.registerHook('afterNavigate', hook));
|
|
40
|
+
beforeUpdateCurrent.forEach((hook) => this.registerHook('beforeUpdateCurrent', hook));
|
|
41
|
+
afterUpdateCurrent.forEach((hook) => this.registerHook('afterUpdateCurrent', hook));
|
|
42
|
+
this.guards = this.hooksFactory.createAsyncParallel('guards');
|
|
43
|
+
guards.forEach((guard) => this.registerGuard(guard));
|
|
44
|
+
this.syncHooks = new Map([['change', this.hooksFactory.createSync('change')]]);
|
|
45
|
+
onChange.forEach((hook) => this.registerSyncHook('change', hook));
|
|
46
|
+
this.navigateHook = this.hooksFactory.createAsync('navigate');
|
|
47
|
+
this.updateHook = this.hooksFactory.createAsync('update');
|
|
48
|
+
this.runNavigateHook = this.hooksFactory.createAsync('runNavigate');
|
|
49
|
+
this.runUpdateHook = this.hooksFactory.createAsync('runUpdate');
|
|
50
|
+
this.redirectHook = this.hooksFactory.createAsync('redirect');
|
|
51
|
+
this.notfoundHook = this.hooksFactory.createAsync('notfound');
|
|
52
|
+
this.blockHook = this.hooksFactory.createAsync('block');
|
|
53
|
+
this.navigateHook.tapPromise('router', async (_, { navigateOptions }) => {
|
|
54
|
+
await this.internalNavigate(makeNavigateOptions(navigateOptions), {});
|
|
55
|
+
});
|
|
56
|
+
this.updateHook.tapPromise('router', async (_, { updateRouteOptions }) => {
|
|
57
|
+
await this.internalUpdateCurrentRoute(updateRouteOptions, {});
|
|
58
|
+
});
|
|
59
|
+
this.runNavigateHook.tapPromise('router', async (_, { navigation }) => {
|
|
60
|
+
// TODO navigate
|
|
61
|
+
// check for redirect in new route description
|
|
62
|
+
if (navigation.to.redirect) {
|
|
63
|
+
return this.redirect(navigation, makeNavigateOptions(navigation.to.redirect));
|
|
64
|
+
}
|
|
65
|
+
await this.runGuards(navigation);
|
|
66
|
+
await this.runHooks('beforeNavigate', navigation);
|
|
67
|
+
this.commitNavigation(navigation);
|
|
68
|
+
await this.runHooks('afterNavigate', navigation);
|
|
69
|
+
});
|
|
70
|
+
this.runUpdateHook.tapPromise('router', async (_, { navigation }) => {
|
|
71
|
+
await this.runHooks('beforeUpdateCurrent', navigation);
|
|
72
|
+
this.commitNavigation(navigation);
|
|
73
|
+
await this.runHooks('afterUpdateCurrent', navigation);
|
|
74
|
+
});
|
|
75
|
+
this.redirectHook.tapPromise('router', async (_, { navigation }) => {
|
|
76
|
+
await this.onRedirect?.(navigation);
|
|
77
|
+
});
|
|
78
|
+
this.notfoundHook.tapPromise('router', async (_, { navigation }) => {
|
|
79
|
+
await this.onNotFound?.(navigation);
|
|
80
|
+
});
|
|
81
|
+
this.blockHook.tapPromise('router', async (_, { navigation }) => {
|
|
82
|
+
await this.onBlock?.(navigation);
|
|
83
|
+
});
|
|
84
|
+
this.plugins.forEach((plugin) => {
|
|
85
|
+
plugin.apply(this);
|
|
86
|
+
});
|
|
32
87
|
}
|
|
33
88
|
// start is using as marker that any preparation for proper work has done in the app
|
|
34
89
|
// and now router can manage any navigations
|
|
@@ -67,7 +122,7 @@ class AbstractRouter {
|
|
|
67
122
|
this.runSyncHooks('change', navigation);
|
|
68
123
|
}
|
|
69
124
|
async updateCurrentRoute(updateRouteOptions) {
|
|
70
|
-
|
|
125
|
+
await this.updateHook.callPromise({ updateRouteOptions });
|
|
71
126
|
}
|
|
72
127
|
async internalUpdateCurrentRoute(updateRouteOptions, { history }) {
|
|
73
128
|
const prevNavigation = this.currentNavigation ?? this.lastNavigation;
|
|
@@ -96,12 +151,10 @@ class AbstractRouter {
|
|
|
96
151
|
await this.run(navigation);
|
|
97
152
|
}
|
|
98
153
|
async runUpdateCurrentRoute(navigation) {
|
|
99
|
-
await this.
|
|
100
|
-
this.commitNavigation(navigation);
|
|
101
|
-
await this.runHooks('afterUpdateCurrent', navigation);
|
|
154
|
+
await this.runUpdateHook.callPromise({ navigation });
|
|
102
155
|
}
|
|
103
156
|
async navigate(navigateOptions) {
|
|
104
|
-
|
|
157
|
+
await this.navigateHook.callPromise({ navigateOptions });
|
|
105
158
|
}
|
|
106
159
|
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
107
160
|
const { url, replace, params, navigateState, code, viewTransition } = navigateOptions;
|
|
@@ -148,14 +201,7 @@ class AbstractRouter {
|
|
|
148
201
|
await this.run(navigation);
|
|
149
202
|
}
|
|
150
203
|
async runNavigate(navigation) {
|
|
151
|
-
|
|
152
|
-
if (navigation.to.redirect) {
|
|
153
|
-
return this.redirect(navigation, makeNavigateOptions(navigation.to.redirect));
|
|
154
|
-
}
|
|
155
|
-
await this.runGuards(navigation);
|
|
156
|
-
await this.runHooks('beforeNavigate', navigation);
|
|
157
|
-
this.commitNavigation(navigation);
|
|
158
|
-
await this.runHooks('afterNavigate', navigation);
|
|
204
|
+
await this.runNavigateHook.callPromise({ navigation });
|
|
159
205
|
}
|
|
160
206
|
async run(navigation) {
|
|
161
207
|
this.currentNavigation = navigation;
|
|
@@ -201,12 +247,14 @@ class AbstractRouter {
|
|
|
201
247
|
navigation,
|
|
202
248
|
target,
|
|
203
249
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
250
|
+
await this.redirectHook.callPromise({
|
|
251
|
+
navigation: {
|
|
252
|
+
...navigation,
|
|
253
|
+
from: navigation.to,
|
|
254
|
+
fromUrl: navigation.url,
|
|
255
|
+
to: null,
|
|
256
|
+
url: this.resolveUrl(target),
|
|
257
|
+
},
|
|
210
258
|
});
|
|
211
259
|
}
|
|
212
260
|
async notfound(navigation) {
|
|
@@ -214,7 +262,9 @@ class AbstractRouter {
|
|
|
214
262
|
event: 'not-found',
|
|
215
263
|
navigation,
|
|
216
264
|
});
|
|
217
|
-
|
|
265
|
+
await this.notfoundHook.callPromise({
|
|
266
|
+
navigation,
|
|
267
|
+
});
|
|
218
268
|
}
|
|
219
269
|
async block(navigation) {
|
|
220
270
|
logger.debug({
|
|
@@ -223,7 +273,8 @@ class AbstractRouter {
|
|
|
223
273
|
});
|
|
224
274
|
this.currentNavigation = null;
|
|
225
275
|
if (this.onBlock) {
|
|
226
|
-
|
|
276
|
+
await this.blockHook.callPromise({ navigation });
|
|
277
|
+
return;
|
|
227
278
|
}
|
|
228
279
|
throw new Error('Navigation blocked');
|
|
229
280
|
}
|
|
@@ -288,19 +339,8 @@ class AbstractRouter {
|
|
|
288
339
|
event: 'guards.run',
|
|
289
340
|
navigation,
|
|
290
341
|
});
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
event: 'guards.empty',
|
|
294
|
-
navigation,
|
|
295
|
-
});
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const results = await Promise.all(Array.from(this.guards).map((guard) => Promise.resolve(guard(navigation)).catch((error) => {
|
|
299
|
-
logger.warn({
|
|
300
|
-
event: 'guard.error',
|
|
301
|
-
error,
|
|
302
|
-
});
|
|
303
|
-
})));
|
|
342
|
+
const results = [];
|
|
343
|
+
await this.guards.callPromise({ navigation, allResults: results });
|
|
304
344
|
logger.debug({
|
|
305
345
|
event: 'guards.done',
|
|
306
346
|
navigation,
|
|
@@ -316,7 +356,20 @@ class AbstractRouter {
|
|
|
316
356
|
}
|
|
317
357
|
}
|
|
318
358
|
registerGuard(guard) {
|
|
319
|
-
|
|
359
|
+
const untap = this.guards.tapPromise(guard.name ?? `guard-${this.guardsIndex++}`, async (_, { allResults, navigation }) => {
|
|
360
|
+
try {
|
|
361
|
+
const result = await guard(navigation);
|
|
362
|
+
allResults.push(result);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
logger.warn({
|
|
366
|
+
event: 'guard.error',
|
|
367
|
+
error,
|
|
368
|
+
});
|
|
369
|
+
allResults.push(undefined);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
return untap;
|
|
320
373
|
}
|
|
321
374
|
runSyncHooks(hookName, navigation) {
|
|
322
375
|
logger.debug({
|
|
@@ -324,18 +377,19 @@ class AbstractRouter {
|
|
|
324
377
|
hookName,
|
|
325
378
|
navigation,
|
|
326
379
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
380
|
+
this.syncHooks.get(hookName).call({ navigation });
|
|
381
|
+
logger.debug({
|
|
382
|
+
event: 'sync-hooks.done',
|
|
383
|
+
hookName,
|
|
384
|
+
navigation,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
registerSyncHook(hookName, hook) {
|
|
388
|
+
const untap = this.syncHooks
|
|
389
|
+
.get(hookName)
|
|
390
|
+
.tap(hook.name ?? `sync-hook-${this.syncHooksIndex++}`, (_, { navigation }) => {
|
|
337
391
|
try {
|
|
338
|
-
hook(navigation);
|
|
392
|
+
return hook(navigation);
|
|
339
393
|
}
|
|
340
394
|
catch (error) {
|
|
341
395
|
logger.warn({
|
|
@@ -343,15 +397,8 @@ class AbstractRouter {
|
|
|
343
397
|
error,
|
|
344
398
|
});
|
|
345
399
|
}
|
|
346
|
-
}
|
|
347
|
-
logger.debug({
|
|
348
|
-
event: 'sync-hooks.done',
|
|
349
|
-
hookName,
|
|
350
|
-
navigation,
|
|
351
400
|
});
|
|
352
|
-
|
|
353
|
-
registerSyncHook(hookName, hook) {
|
|
354
|
-
return registerHook(this.syncHooks.get(hookName), hook);
|
|
401
|
+
return untap;
|
|
355
402
|
}
|
|
356
403
|
async runHooks(hookName, navigation) {
|
|
357
404
|
logger.debug({
|
|
@@ -359,26 +406,7 @@ class AbstractRouter {
|
|
|
359
406
|
hookName,
|
|
360
407
|
navigation,
|
|
361
408
|
});
|
|
362
|
-
|
|
363
|
-
if (!hooks) {
|
|
364
|
-
logger.debug({
|
|
365
|
-
event: 'hooks.empty',
|
|
366
|
-
hookName,
|
|
367
|
-
navigation,
|
|
368
|
-
});
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
await Promise.all(Array.from(hooks).map((hook) => Promise.resolve(hook(navigation)).catch((error) => {
|
|
372
|
-
logger.warn({
|
|
373
|
-
event: 'hook.error',
|
|
374
|
-
error,
|
|
375
|
-
});
|
|
376
|
-
// rethrow error for beforeResolve to prevent showing not found page
|
|
377
|
-
// if app has problems while loading info about routes
|
|
378
|
-
if (hookName === 'beforeResolve') {
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
})));
|
|
409
|
+
await this.hooks.get(hookName).callPromise({ navigation });
|
|
382
410
|
logger.debug({
|
|
383
411
|
event: 'hooks.done',
|
|
384
412
|
hookName,
|
|
@@ -386,7 +414,25 @@ class AbstractRouter {
|
|
|
386
414
|
});
|
|
387
415
|
}
|
|
388
416
|
registerHook(hookName, hook) {
|
|
389
|
-
|
|
417
|
+
const untap = this.hooks
|
|
418
|
+
.get(hookName)
|
|
419
|
+
.tapPromise(hook.name ?? `hook-${this.hooksIndex++}`, async (_, { navigation }) => {
|
|
420
|
+
try {
|
|
421
|
+
await hook(navigation);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
logger.warn({
|
|
425
|
+
event: 'hook.error',
|
|
426
|
+
error,
|
|
427
|
+
});
|
|
428
|
+
// rethrow error for beforeResolve to prevent showing not found page
|
|
429
|
+
// if app has problems while loading info about routes
|
|
430
|
+
if (hookName === 'beforeResolve') {
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
return untap;
|
|
390
436
|
}
|
|
391
437
|
uuid() {
|
|
392
438
|
return this.currentUuid++;
|
package/lib/router/abstract.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Url } from '@tinkoff/url';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AsyncParallelTapableHookInstance, AsyncTapableHookInstance, SyncTapableHookInstance } from '@tinkoff/hook-runner';
|
|
3
|
+
import { TapableHooks } from '@tinkoff/hook-runner';
|
|
4
|
+
import type { Route, NavigateOptions, UpdateCurrentRouteOptions, Navigation, NavigationGuard, NavigationHook, NavigationSyncHook, HookName, Params, SyncHookName, HistoryOptions, RouterPlugin } from '../types';
|
|
3
5
|
import type { History } from '../history/base';
|
|
4
6
|
import type { RouteTree } from '../tree/tree';
|
|
5
7
|
export interface Options {
|
|
@@ -18,6 +20,8 @@ export interface Options {
|
|
|
18
20
|
guards?: NavigationGuard[];
|
|
19
21
|
onChange?: NavigationSyncHook[];
|
|
20
22
|
defaultRedirectCode?: number;
|
|
23
|
+
hooksFactory?: TapableHooks;
|
|
24
|
+
plugins?: RouterPlugin[];
|
|
21
25
|
}
|
|
22
26
|
interface InternalOptions {
|
|
23
27
|
history?: boolean;
|
|
@@ -33,11 +37,44 @@ export declare abstract class AbstractRouter {
|
|
|
33
37
|
protected lastNavigation: Navigation;
|
|
34
38
|
protected history: History;
|
|
35
39
|
protected tree?: RouteTree;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
readonly guards: AsyncParallelTapableHookInstance<{
|
|
41
|
+
navigation: Navigation;
|
|
42
|
+
allResults: Array<void | NavigateOptions | string | boolean>;
|
|
43
|
+
}>;
|
|
44
|
+
readonly hooks: Map<HookName, AsyncParallelTapableHookInstance<{
|
|
45
|
+
navigation: Navigation;
|
|
46
|
+
}>>;
|
|
47
|
+
readonly syncHooks: Map<SyncHookName, SyncTapableHookInstance<{
|
|
48
|
+
navigation: Navigation;
|
|
49
|
+
}>>;
|
|
50
|
+
readonly navigateHook: AsyncTapableHookInstance<{
|
|
51
|
+
navigateOptions: NavigateOptions | string;
|
|
52
|
+
}>;
|
|
53
|
+
readonly updateHook: AsyncTapableHookInstance<{
|
|
54
|
+
updateRouteOptions: UpdateCurrentRouteOptions;
|
|
55
|
+
}>;
|
|
56
|
+
readonly runNavigateHook: AsyncTapableHookInstance<{
|
|
57
|
+
navigation: Navigation;
|
|
58
|
+
}>;
|
|
59
|
+
readonly runUpdateHook: AsyncTapableHookInstance<{
|
|
60
|
+
navigation: Navigation;
|
|
61
|
+
}>;
|
|
62
|
+
readonly redirectHook: AsyncTapableHookInstance<{
|
|
63
|
+
navigation: Navigation;
|
|
64
|
+
}>;
|
|
65
|
+
readonly notfoundHook: AsyncTapableHookInstance<{
|
|
66
|
+
navigation: Navigation;
|
|
67
|
+
}>;
|
|
68
|
+
readonly blockHook: AsyncTapableHookInstance<{
|
|
69
|
+
navigation: Navigation;
|
|
70
|
+
}>;
|
|
71
|
+
protected hooksFactory: TapableHooks;
|
|
72
|
+
protected plugins?: RouterPlugin[];
|
|
39
73
|
private currentUuid;
|
|
40
|
-
|
|
74
|
+
private hooksIndex;
|
|
75
|
+
private syncHooksIndex;
|
|
76
|
+
private guardsIndex;
|
|
77
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve, beforeNavigate, afterNavigate, beforeUpdateCurrent, afterUpdateCurrent, guards, onChange, onRedirect, onNotFound, onBlock, hooksFactory, plugins, }: Options);
|
|
41
78
|
protected onRedirect?: NavigationHook;
|
|
42
79
|
protected onNotFound?: NavigationHook;
|
|
43
80
|
protected onBlock?: NavigationHook;
|
|
@@ -76,11 +113,11 @@ export declare abstract class AbstractRouter {
|
|
|
76
113
|
wildcard?: boolean;
|
|
77
114
|
}): import("../types").NavigationRoute;
|
|
78
115
|
protected runGuards(navigation: Navigation): Promise<void>;
|
|
79
|
-
registerGuard(guard: NavigationGuard): ()
|
|
116
|
+
registerGuard(guard: NavigationGuard): import("@tinkoff/hook-runner/lib/types").UntapCallback;
|
|
80
117
|
protected runSyncHooks(hookName: SyncHookName, navigation: Navigation): void;
|
|
81
|
-
registerSyncHook(hookName: SyncHookName, hook: NavigationSyncHook): ()
|
|
118
|
+
registerSyncHook(hookName: SyncHookName, hook: NavigationSyncHook): import("@tinkoff/hook-runner/lib/types").UntapCallback;
|
|
82
119
|
protected runHooks(hookName: HookName, navigation: Navigation): Promise<void>;
|
|
83
|
-
registerHook(hookName: HookName, hook: NavigationHook): ()
|
|
120
|
+
registerHook(hookName: HookName, hook: NavigationHook): import("@tinkoff/hook-runner/lib/types").UntapCallback;
|
|
84
121
|
private uuid;
|
|
85
122
|
}
|
|
86
123
|
export {};
|
|
@@ -1,34 +1,89 @@
|
|
|
1
1
|
import isString from '@tinkoff/utils/is/string';
|
|
2
2
|
import isObject from '@tinkoff/utils/is/object';
|
|
3
3
|
import { parse, rawResolveUrl, rawParse, convertRawUrl, rawAssignUrl } from '@tinkoff/url';
|
|
4
|
+
import { TapableHooks } from '@tinkoff/hook-runner';
|
|
4
5
|
import { makePath } from '../tree/utils.es.js';
|
|
5
6
|
import { logger } from '../logger.es.js';
|
|
6
|
-
import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost
|
|
7
|
+
import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost } from '../utils.es.js';
|
|
7
8
|
|
|
8
9
|
class AbstractRouter {
|
|
9
|
-
|
|
10
|
+
// eslint-disable-next-line max-statements
|
|
11
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, hooksFactory, plugins, }) {
|
|
10
12
|
this.started = false;
|
|
11
13
|
this.trailingSlash = false;
|
|
12
14
|
this.strictTrailingSlash = true;
|
|
13
15
|
this.viewTransitionsEnabled = false;
|
|
14
16
|
this.mergeSlashes = false;
|
|
17
|
+
this.hooksIndex = 0;
|
|
18
|
+
this.syncHooksIndex = 0;
|
|
19
|
+
this.guardsIndex = 0;
|
|
15
20
|
this.trailingSlash = trailingSlash ?? false;
|
|
16
21
|
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
17
22
|
this.mergeSlashes = mergeSlashes ?? false;
|
|
18
23
|
this.viewTransitionsEnabled = enableViewTransitions ?? false;
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
['afterNavigate', new Set(afterNavigate)],
|
|
23
|
-
['beforeUpdateCurrent', new Set(beforeUpdateCurrent)],
|
|
24
|
-
['afterUpdateCurrent', new Set(afterUpdateCurrent)],
|
|
25
|
-
]);
|
|
26
|
-
this.guards = new Set(guards);
|
|
27
|
-
this.syncHooks = new Map([['change', new Set(onChange)]]);
|
|
24
|
+
this.hooksFactory = hooksFactory ?? new TapableHooks();
|
|
25
|
+
this.plugins = plugins ?? [];
|
|
26
|
+
this.currentUuid = 0;
|
|
28
27
|
this.onRedirect = onRedirect;
|
|
29
28
|
this.onNotFound = onNotFound;
|
|
30
29
|
this.onBlock = onBlock;
|
|
31
|
-
this.
|
|
30
|
+
this.hooks = new Map([
|
|
31
|
+
['beforeResolve', this.hooksFactory.createAsyncParallel('beforeResolve')],
|
|
32
|
+
['beforeNavigate', this.hooksFactory.createAsyncParallel('beforeNavigate')],
|
|
33
|
+
['afterNavigate', this.hooksFactory.createAsyncParallel('afterNavigate')],
|
|
34
|
+
['beforeUpdateCurrent', this.hooksFactory.createAsyncParallel('beforeUpdateCurrent')],
|
|
35
|
+
['afterUpdateCurrent', this.hooksFactory.createAsyncParallel('afterUpdateCurrent')],
|
|
36
|
+
]);
|
|
37
|
+
beforeResolve.forEach((hook) => this.registerHook('beforeResolve', hook));
|
|
38
|
+
beforeNavigate.forEach((hook) => this.registerHook('beforeNavigate', hook));
|
|
39
|
+
afterNavigate.forEach((hook) => this.registerHook('afterNavigate', hook));
|
|
40
|
+
beforeUpdateCurrent.forEach((hook) => this.registerHook('beforeUpdateCurrent', hook));
|
|
41
|
+
afterUpdateCurrent.forEach((hook) => this.registerHook('afterUpdateCurrent', hook));
|
|
42
|
+
this.guards = this.hooksFactory.createAsyncParallel('guards');
|
|
43
|
+
guards.forEach((guard) => this.registerGuard(guard));
|
|
44
|
+
this.syncHooks = new Map([['change', this.hooksFactory.createSync('change')]]);
|
|
45
|
+
onChange.forEach((hook) => this.registerSyncHook('change', hook));
|
|
46
|
+
this.navigateHook = this.hooksFactory.createAsync('navigate');
|
|
47
|
+
this.updateHook = this.hooksFactory.createAsync('update');
|
|
48
|
+
this.runNavigateHook = this.hooksFactory.createAsync('runNavigate');
|
|
49
|
+
this.runUpdateHook = this.hooksFactory.createAsync('runUpdate');
|
|
50
|
+
this.redirectHook = this.hooksFactory.createAsync('redirect');
|
|
51
|
+
this.notfoundHook = this.hooksFactory.createAsync('notfound');
|
|
52
|
+
this.blockHook = this.hooksFactory.createAsync('block');
|
|
53
|
+
this.navigateHook.tapPromise('router', async (_, { navigateOptions }) => {
|
|
54
|
+
await this.internalNavigate(makeNavigateOptions(navigateOptions), {});
|
|
55
|
+
});
|
|
56
|
+
this.updateHook.tapPromise('router', async (_, { updateRouteOptions }) => {
|
|
57
|
+
await this.internalUpdateCurrentRoute(updateRouteOptions, {});
|
|
58
|
+
});
|
|
59
|
+
this.runNavigateHook.tapPromise('router', async (_, { navigation }) => {
|
|
60
|
+
// TODO navigate
|
|
61
|
+
// check for redirect in new route description
|
|
62
|
+
if (navigation.to.redirect) {
|
|
63
|
+
return this.redirect(navigation, makeNavigateOptions(navigation.to.redirect));
|
|
64
|
+
}
|
|
65
|
+
await this.runGuards(navigation);
|
|
66
|
+
await this.runHooks('beforeNavigate', navigation);
|
|
67
|
+
this.commitNavigation(navigation);
|
|
68
|
+
await this.runHooks('afterNavigate', navigation);
|
|
69
|
+
});
|
|
70
|
+
this.runUpdateHook.tapPromise('router', async (_, { navigation }) => {
|
|
71
|
+
await this.runHooks('beforeUpdateCurrent', navigation);
|
|
72
|
+
this.commitNavigation(navigation);
|
|
73
|
+
await this.runHooks('afterUpdateCurrent', navigation);
|
|
74
|
+
});
|
|
75
|
+
this.redirectHook.tapPromise('router', async (_, { navigation }) => {
|
|
76
|
+
await this.onRedirect?.(navigation);
|
|
77
|
+
});
|
|
78
|
+
this.notfoundHook.tapPromise('router', async (_, { navigation }) => {
|
|
79
|
+
await this.onNotFound?.(navigation);
|
|
80
|
+
});
|
|
81
|
+
this.blockHook.tapPromise('router', async (_, { navigation }) => {
|
|
82
|
+
await this.onBlock?.(navigation);
|
|
83
|
+
});
|
|
84
|
+
this.plugins.forEach((plugin) => {
|
|
85
|
+
plugin.apply(this);
|
|
86
|
+
});
|
|
32
87
|
}
|
|
33
88
|
// start is using as marker that any preparation for proper work has done in the app
|
|
34
89
|
// and now router can manage any navigations
|
|
@@ -67,7 +122,7 @@ class AbstractRouter {
|
|
|
67
122
|
this.runSyncHooks('change', navigation);
|
|
68
123
|
}
|
|
69
124
|
async updateCurrentRoute(updateRouteOptions) {
|
|
70
|
-
|
|
125
|
+
await this.updateHook.callPromise({ updateRouteOptions });
|
|
71
126
|
}
|
|
72
127
|
async internalUpdateCurrentRoute(updateRouteOptions, { history }) {
|
|
73
128
|
const prevNavigation = this.currentNavigation ?? this.lastNavigation;
|
|
@@ -96,12 +151,10 @@ class AbstractRouter {
|
|
|
96
151
|
await this.run(navigation);
|
|
97
152
|
}
|
|
98
153
|
async runUpdateCurrentRoute(navigation) {
|
|
99
|
-
await this.
|
|
100
|
-
this.commitNavigation(navigation);
|
|
101
|
-
await this.runHooks('afterUpdateCurrent', navigation);
|
|
154
|
+
await this.runUpdateHook.callPromise({ navigation });
|
|
102
155
|
}
|
|
103
156
|
async navigate(navigateOptions) {
|
|
104
|
-
|
|
157
|
+
await this.navigateHook.callPromise({ navigateOptions });
|
|
105
158
|
}
|
|
106
159
|
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
107
160
|
const { url, replace, params, navigateState, code, viewTransition } = navigateOptions;
|
|
@@ -148,14 +201,7 @@ class AbstractRouter {
|
|
|
148
201
|
await this.run(navigation);
|
|
149
202
|
}
|
|
150
203
|
async runNavigate(navigation) {
|
|
151
|
-
|
|
152
|
-
if (navigation.to.redirect) {
|
|
153
|
-
return this.redirect(navigation, makeNavigateOptions(navigation.to.redirect));
|
|
154
|
-
}
|
|
155
|
-
await this.runGuards(navigation);
|
|
156
|
-
await this.runHooks('beforeNavigate', navigation);
|
|
157
|
-
this.commitNavigation(navigation);
|
|
158
|
-
await this.runHooks('afterNavigate', navigation);
|
|
204
|
+
await this.runNavigateHook.callPromise({ navigation });
|
|
159
205
|
}
|
|
160
206
|
async run(navigation) {
|
|
161
207
|
this.currentNavigation = navigation;
|
|
@@ -201,12 +247,14 @@ class AbstractRouter {
|
|
|
201
247
|
navigation,
|
|
202
248
|
target,
|
|
203
249
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
250
|
+
await this.redirectHook.callPromise({
|
|
251
|
+
navigation: {
|
|
252
|
+
...navigation,
|
|
253
|
+
from: navigation.to,
|
|
254
|
+
fromUrl: navigation.url,
|
|
255
|
+
to: null,
|
|
256
|
+
url: this.resolveUrl(target),
|
|
257
|
+
},
|
|
210
258
|
});
|
|
211
259
|
}
|
|
212
260
|
async notfound(navigation) {
|
|
@@ -214,7 +262,9 @@ class AbstractRouter {
|
|
|
214
262
|
event: 'not-found',
|
|
215
263
|
navigation,
|
|
216
264
|
});
|
|
217
|
-
|
|
265
|
+
await this.notfoundHook.callPromise({
|
|
266
|
+
navigation,
|
|
267
|
+
});
|
|
218
268
|
}
|
|
219
269
|
async block(navigation) {
|
|
220
270
|
logger.debug({
|
|
@@ -223,7 +273,8 @@ class AbstractRouter {
|
|
|
223
273
|
});
|
|
224
274
|
this.currentNavigation = null;
|
|
225
275
|
if (this.onBlock) {
|
|
226
|
-
|
|
276
|
+
await this.blockHook.callPromise({ navigation });
|
|
277
|
+
return;
|
|
227
278
|
}
|
|
228
279
|
throw new Error('Navigation blocked');
|
|
229
280
|
}
|
|
@@ -288,19 +339,8 @@ class AbstractRouter {
|
|
|
288
339
|
event: 'guards.run',
|
|
289
340
|
navigation,
|
|
290
341
|
});
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
event: 'guards.empty',
|
|
294
|
-
navigation,
|
|
295
|
-
});
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const results = await Promise.all(Array.from(this.guards).map((guard) => Promise.resolve(guard(navigation)).catch((error) => {
|
|
299
|
-
logger.warn({
|
|
300
|
-
event: 'guard.error',
|
|
301
|
-
error,
|
|
302
|
-
});
|
|
303
|
-
})));
|
|
342
|
+
const results = [];
|
|
343
|
+
await this.guards.callPromise({ navigation, allResults: results });
|
|
304
344
|
logger.debug({
|
|
305
345
|
event: 'guards.done',
|
|
306
346
|
navigation,
|
|
@@ -316,7 +356,20 @@ class AbstractRouter {
|
|
|
316
356
|
}
|
|
317
357
|
}
|
|
318
358
|
registerGuard(guard) {
|
|
319
|
-
|
|
359
|
+
const untap = this.guards.tapPromise(guard.name ?? `guard-${this.guardsIndex++}`, async (_, { allResults, navigation }) => {
|
|
360
|
+
try {
|
|
361
|
+
const result = await guard(navigation);
|
|
362
|
+
allResults.push(result);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
logger.warn({
|
|
366
|
+
event: 'guard.error',
|
|
367
|
+
error,
|
|
368
|
+
});
|
|
369
|
+
allResults.push(undefined);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
return untap;
|
|
320
373
|
}
|
|
321
374
|
runSyncHooks(hookName, navigation) {
|
|
322
375
|
logger.debug({
|
|
@@ -324,18 +377,19 @@ class AbstractRouter {
|
|
|
324
377
|
hookName,
|
|
325
378
|
navigation,
|
|
326
379
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
380
|
+
this.syncHooks.get(hookName).call({ navigation });
|
|
381
|
+
logger.debug({
|
|
382
|
+
event: 'sync-hooks.done',
|
|
383
|
+
hookName,
|
|
384
|
+
navigation,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
registerSyncHook(hookName, hook) {
|
|
388
|
+
const untap = this.syncHooks
|
|
389
|
+
.get(hookName)
|
|
390
|
+
.tap(hook.name ?? `sync-hook-${this.syncHooksIndex++}`, (_, { navigation }) => {
|
|
337
391
|
try {
|
|
338
|
-
hook(navigation);
|
|
392
|
+
return hook(navigation);
|
|
339
393
|
}
|
|
340
394
|
catch (error) {
|
|
341
395
|
logger.warn({
|
|
@@ -343,15 +397,8 @@ class AbstractRouter {
|
|
|
343
397
|
error,
|
|
344
398
|
});
|
|
345
399
|
}
|
|
346
|
-
}
|
|
347
|
-
logger.debug({
|
|
348
|
-
event: 'sync-hooks.done',
|
|
349
|
-
hookName,
|
|
350
|
-
navigation,
|
|
351
400
|
});
|
|
352
|
-
|
|
353
|
-
registerSyncHook(hookName, hook) {
|
|
354
|
-
return registerHook(this.syncHooks.get(hookName), hook);
|
|
401
|
+
return untap;
|
|
355
402
|
}
|
|
356
403
|
async runHooks(hookName, navigation) {
|
|
357
404
|
logger.debug({
|
|
@@ -359,26 +406,7 @@ class AbstractRouter {
|
|
|
359
406
|
hookName,
|
|
360
407
|
navigation,
|
|
361
408
|
});
|
|
362
|
-
|
|
363
|
-
if (!hooks) {
|
|
364
|
-
logger.debug({
|
|
365
|
-
event: 'hooks.empty',
|
|
366
|
-
hookName,
|
|
367
|
-
navigation,
|
|
368
|
-
});
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
await Promise.all(Array.from(hooks).map((hook) => Promise.resolve(hook(navigation)).catch((error) => {
|
|
372
|
-
logger.warn({
|
|
373
|
-
event: 'hook.error',
|
|
374
|
-
error,
|
|
375
|
-
});
|
|
376
|
-
// rethrow error for beforeResolve to prevent showing not found page
|
|
377
|
-
// if app has problems while loading info about routes
|
|
378
|
-
if (hookName === 'beforeResolve') {
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
})));
|
|
409
|
+
await this.hooks.get(hookName).callPromise({ navigation });
|
|
382
410
|
logger.debug({
|
|
383
411
|
event: 'hooks.done',
|
|
384
412
|
hookName,
|
|
@@ -386,7 +414,25 @@ class AbstractRouter {
|
|
|
386
414
|
});
|
|
387
415
|
}
|
|
388
416
|
registerHook(hookName, hook) {
|
|
389
|
-
|
|
417
|
+
const untap = this.hooks
|
|
418
|
+
.get(hookName)
|
|
419
|
+
.tapPromise(hook.name ?? `hook-${this.hooksIndex++}`, async (_, { navigation }) => {
|
|
420
|
+
try {
|
|
421
|
+
await hook(navigation);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
logger.warn({
|
|
425
|
+
event: 'hook.error',
|
|
426
|
+
error,
|
|
427
|
+
});
|
|
428
|
+
// rethrow error for beforeResolve to prevent showing not found page
|
|
429
|
+
// if app has problems while loading info about routes
|
|
430
|
+
if (hookName === 'beforeResolve') {
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
return untap;
|
|
390
436
|
}
|
|
391
437
|
uuid() {
|
|
392
438
|
return this.currentUuid++;
|
package/lib/router/abstract.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var isString = require('@tinkoff/utils/is/string');
|
|
6
6
|
var isObject = require('@tinkoff/utils/is/object');
|
|
7
7
|
var url = require('@tinkoff/url');
|
|
8
|
+
var hookRunner = require('@tinkoff/hook-runner');
|
|
8
9
|
var utils$1 = require('../tree/utils.js');
|
|
9
10
|
var logger = require('../logger.js');
|
|
10
11
|
var utils = require('../utils.js');
|
|
@@ -15,29 +16,83 @@ var isString__default = /*#__PURE__*/_interopDefaultLegacy(isString);
|
|
|
15
16
|
var isObject__default = /*#__PURE__*/_interopDefaultLegacy(isObject);
|
|
16
17
|
|
|
17
18
|
class AbstractRouter {
|
|
18
|
-
|
|
19
|
+
// eslint-disable-next-line max-statements
|
|
20
|
+
constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, hooksFactory, plugins, }) {
|
|
19
21
|
this.started = false;
|
|
20
22
|
this.trailingSlash = false;
|
|
21
23
|
this.strictTrailingSlash = true;
|
|
22
24
|
this.viewTransitionsEnabled = false;
|
|
23
25
|
this.mergeSlashes = false;
|
|
26
|
+
this.hooksIndex = 0;
|
|
27
|
+
this.syncHooksIndex = 0;
|
|
28
|
+
this.guardsIndex = 0;
|
|
24
29
|
this.trailingSlash = trailingSlash ?? false;
|
|
25
30
|
this.strictTrailingSlash = typeof trailingSlash === 'undefined';
|
|
26
31
|
this.mergeSlashes = mergeSlashes ?? false;
|
|
27
32
|
this.viewTransitionsEnabled = enableViewTransitions ?? false;
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
['afterNavigate', new Set(afterNavigate)],
|
|
32
|
-
['beforeUpdateCurrent', new Set(beforeUpdateCurrent)],
|
|
33
|
-
['afterUpdateCurrent', new Set(afterUpdateCurrent)],
|
|
34
|
-
]);
|
|
35
|
-
this.guards = new Set(guards);
|
|
36
|
-
this.syncHooks = new Map([['change', new Set(onChange)]]);
|
|
33
|
+
this.hooksFactory = hooksFactory ?? new hookRunner.TapableHooks();
|
|
34
|
+
this.plugins = plugins ?? [];
|
|
35
|
+
this.currentUuid = 0;
|
|
37
36
|
this.onRedirect = onRedirect;
|
|
38
37
|
this.onNotFound = onNotFound;
|
|
39
38
|
this.onBlock = onBlock;
|
|
40
|
-
this.
|
|
39
|
+
this.hooks = new Map([
|
|
40
|
+
['beforeResolve', this.hooksFactory.createAsyncParallel('beforeResolve')],
|
|
41
|
+
['beforeNavigate', this.hooksFactory.createAsyncParallel('beforeNavigate')],
|
|
42
|
+
['afterNavigate', this.hooksFactory.createAsyncParallel('afterNavigate')],
|
|
43
|
+
['beforeUpdateCurrent', this.hooksFactory.createAsyncParallel('beforeUpdateCurrent')],
|
|
44
|
+
['afterUpdateCurrent', this.hooksFactory.createAsyncParallel('afterUpdateCurrent')],
|
|
45
|
+
]);
|
|
46
|
+
beforeResolve.forEach((hook) => this.registerHook('beforeResolve', hook));
|
|
47
|
+
beforeNavigate.forEach((hook) => this.registerHook('beforeNavigate', hook));
|
|
48
|
+
afterNavigate.forEach((hook) => this.registerHook('afterNavigate', hook));
|
|
49
|
+
beforeUpdateCurrent.forEach((hook) => this.registerHook('beforeUpdateCurrent', hook));
|
|
50
|
+
afterUpdateCurrent.forEach((hook) => this.registerHook('afterUpdateCurrent', hook));
|
|
51
|
+
this.guards = this.hooksFactory.createAsyncParallel('guards');
|
|
52
|
+
guards.forEach((guard) => this.registerGuard(guard));
|
|
53
|
+
this.syncHooks = new Map([['change', this.hooksFactory.createSync('change')]]);
|
|
54
|
+
onChange.forEach((hook) => this.registerSyncHook('change', hook));
|
|
55
|
+
this.navigateHook = this.hooksFactory.createAsync('navigate');
|
|
56
|
+
this.updateHook = this.hooksFactory.createAsync('update');
|
|
57
|
+
this.runNavigateHook = this.hooksFactory.createAsync('runNavigate');
|
|
58
|
+
this.runUpdateHook = this.hooksFactory.createAsync('runUpdate');
|
|
59
|
+
this.redirectHook = this.hooksFactory.createAsync('redirect');
|
|
60
|
+
this.notfoundHook = this.hooksFactory.createAsync('notfound');
|
|
61
|
+
this.blockHook = this.hooksFactory.createAsync('block');
|
|
62
|
+
this.navigateHook.tapPromise('router', async (_, { navigateOptions }) => {
|
|
63
|
+
await this.internalNavigate(utils.makeNavigateOptions(navigateOptions), {});
|
|
64
|
+
});
|
|
65
|
+
this.updateHook.tapPromise('router', async (_, { updateRouteOptions }) => {
|
|
66
|
+
await this.internalUpdateCurrentRoute(updateRouteOptions, {});
|
|
67
|
+
});
|
|
68
|
+
this.runNavigateHook.tapPromise('router', async (_, { navigation }) => {
|
|
69
|
+
// TODO navigate
|
|
70
|
+
// check for redirect in new route description
|
|
71
|
+
if (navigation.to.redirect) {
|
|
72
|
+
return this.redirect(navigation, utils.makeNavigateOptions(navigation.to.redirect));
|
|
73
|
+
}
|
|
74
|
+
await this.runGuards(navigation);
|
|
75
|
+
await this.runHooks('beforeNavigate', navigation);
|
|
76
|
+
this.commitNavigation(navigation);
|
|
77
|
+
await this.runHooks('afterNavigate', navigation);
|
|
78
|
+
});
|
|
79
|
+
this.runUpdateHook.tapPromise('router', async (_, { navigation }) => {
|
|
80
|
+
await this.runHooks('beforeUpdateCurrent', navigation);
|
|
81
|
+
this.commitNavigation(navigation);
|
|
82
|
+
await this.runHooks('afterUpdateCurrent', navigation);
|
|
83
|
+
});
|
|
84
|
+
this.redirectHook.tapPromise('router', async (_, { navigation }) => {
|
|
85
|
+
await this.onRedirect?.(navigation);
|
|
86
|
+
});
|
|
87
|
+
this.notfoundHook.tapPromise('router', async (_, { navigation }) => {
|
|
88
|
+
await this.onNotFound?.(navigation);
|
|
89
|
+
});
|
|
90
|
+
this.blockHook.tapPromise('router', async (_, { navigation }) => {
|
|
91
|
+
await this.onBlock?.(navigation);
|
|
92
|
+
});
|
|
93
|
+
this.plugins.forEach((plugin) => {
|
|
94
|
+
plugin.apply(this);
|
|
95
|
+
});
|
|
41
96
|
}
|
|
42
97
|
// start is using as marker that any preparation for proper work has done in the app
|
|
43
98
|
// and now router can manage any navigations
|
|
@@ -76,7 +131,7 @@ class AbstractRouter {
|
|
|
76
131
|
this.runSyncHooks('change', navigation);
|
|
77
132
|
}
|
|
78
133
|
async updateCurrentRoute(updateRouteOptions) {
|
|
79
|
-
|
|
134
|
+
await this.updateHook.callPromise({ updateRouteOptions });
|
|
80
135
|
}
|
|
81
136
|
async internalUpdateCurrentRoute(updateRouteOptions, { history }) {
|
|
82
137
|
const prevNavigation = this.currentNavigation ?? this.lastNavigation;
|
|
@@ -105,12 +160,10 @@ class AbstractRouter {
|
|
|
105
160
|
await this.run(navigation);
|
|
106
161
|
}
|
|
107
162
|
async runUpdateCurrentRoute(navigation) {
|
|
108
|
-
await this.
|
|
109
|
-
this.commitNavigation(navigation);
|
|
110
|
-
await this.runHooks('afterUpdateCurrent', navigation);
|
|
163
|
+
await this.runUpdateHook.callPromise({ navigation });
|
|
111
164
|
}
|
|
112
165
|
async navigate(navigateOptions) {
|
|
113
|
-
|
|
166
|
+
await this.navigateHook.callPromise({ navigateOptions });
|
|
114
167
|
}
|
|
115
168
|
async internalNavigate(navigateOptions, { history, redirect }) {
|
|
116
169
|
const { url, replace, params, navigateState, code, viewTransition } = navigateOptions;
|
|
@@ -157,14 +210,7 @@ class AbstractRouter {
|
|
|
157
210
|
await this.run(navigation);
|
|
158
211
|
}
|
|
159
212
|
async runNavigate(navigation) {
|
|
160
|
-
|
|
161
|
-
if (navigation.to.redirect) {
|
|
162
|
-
return this.redirect(navigation, utils.makeNavigateOptions(navigation.to.redirect));
|
|
163
|
-
}
|
|
164
|
-
await this.runGuards(navigation);
|
|
165
|
-
await this.runHooks('beforeNavigate', navigation);
|
|
166
|
-
this.commitNavigation(navigation);
|
|
167
|
-
await this.runHooks('afterNavigate', navigation);
|
|
213
|
+
await this.runNavigateHook.callPromise({ navigation });
|
|
168
214
|
}
|
|
169
215
|
async run(navigation) {
|
|
170
216
|
this.currentNavigation = navigation;
|
|
@@ -210,12 +256,14 @@ class AbstractRouter {
|
|
|
210
256
|
navigation,
|
|
211
257
|
target,
|
|
212
258
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
259
|
+
await this.redirectHook.callPromise({
|
|
260
|
+
navigation: {
|
|
261
|
+
...navigation,
|
|
262
|
+
from: navigation.to,
|
|
263
|
+
fromUrl: navigation.url,
|
|
264
|
+
to: null,
|
|
265
|
+
url: this.resolveUrl(target),
|
|
266
|
+
},
|
|
219
267
|
});
|
|
220
268
|
}
|
|
221
269
|
async notfound(navigation) {
|
|
@@ -223,7 +271,9 @@ class AbstractRouter {
|
|
|
223
271
|
event: 'not-found',
|
|
224
272
|
navigation,
|
|
225
273
|
});
|
|
226
|
-
|
|
274
|
+
await this.notfoundHook.callPromise({
|
|
275
|
+
navigation,
|
|
276
|
+
});
|
|
227
277
|
}
|
|
228
278
|
async block(navigation) {
|
|
229
279
|
logger.logger.debug({
|
|
@@ -232,7 +282,8 @@ class AbstractRouter {
|
|
|
232
282
|
});
|
|
233
283
|
this.currentNavigation = null;
|
|
234
284
|
if (this.onBlock) {
|
|
235
|
-
|
|
285
|
+
await this.blockHook.callPromise({ navigation });
|
|
286
|
+
return;
|
|
236
287
|
}
|
|
237
288
|
throw new Error('Navigation blocked');
|
|
238
289
|
}
|
|
@@ -297,19 +348,8 @@ class AbstractRouter {
|
|
|
297
348
|
event: 'guards.run',
|
|
298
349
|
navigation,
|
|
299
350
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
event: 'guards.empty',
|
|
303
|
-
navigation,
|
|
304
|
-
});
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const results = await Promise.all(Array.from(this.guards).map((guard) => Promise.resolve(guard(navigation)).catch((error) => {
|
|
308
|
-
logger.logger.warn({
|
|
309
|
-
event: 'guard.error',
|
|
310
|
-
error,
|
|
311
|
-
});
|
|
312
|
-
})));
|
|
351
|
+
const results = [];
|
|
352
|
+
await this.guards.callPromise({ navigation, allResults: results });
|
|
313
353
|
logger.logger.debug({
|
|
314
354
|
event: 'guards.done',
|
|
315
355
|
navigation,
|
|
@@ -325,7 +365,20 @@ class AbstractRouter {
|
|
|
325
365
|
}
|
|
326
366
|
}
|
|
327
367
|
registerGuard(guard) {
|
|
328
|
-
|
|
368
|
+
const untap = this.guards.tapPromise(guard.name ?? `guard-${this.guardsIndex++}`, async (_, { allResults, navigation }) => {
|
|
369
|
+
try {
|
|
370
|
+
const result = await guard(navigation);
|
|
371
|
+
allResults.push(result);
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
logger.logger.warn({
|
|
375
|
+
event: 'guard.error',
|
|
376
|
+
error,
|
|
377
|
+
});
|
|
378
|
+
allResults.push(undefined);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
return untap;
|
|
329
382
|
}
|
|
330
383
|
runSyncHooks(hookName, navigation) {
|
|
331
384
|
logger.logger.debug({
|
|
@@ -333,18 +386,19 @@ class AbstractRouter {
|
|
|
333
386
|
hookName,
|
|
334
387
|
navigation,
|
|
335
388
|
});
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
389
|
+
this.syncHooks.get(hookName).call({ navigation });
|
|
390
|
+
logger.logger.debug({
|
|
391
|
+
event: 'sync-hooks.done',
|
|
392
|
+
hookName,
|
|
393
|
+
navigation,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
registerSyncHook(hookName, hook) {
|
|
397
|
+
const untap = this.syncHooks
|
|
398
|
+
.get(hookName)
|
|
399
|
+
.tap(hook.name ?? `sync-hook-${this.syncHooksIndex++}`, (_, { navigation }) => {
|
|
346
400
|
try {
|
|
347
|
-
hook(navigation);
|
|
401
|
+
return hook(navigation);
|
|
348
402
|
}
|
|
349
403
|
catch (error) {
|
|
350
404
|
logger.logger.warn({
|
|
@@ -352,15 +406,8 @@ class AbstractRouter {
|
|
|
352
406
|
error,
|
|
353
407
|
});
|
|
354
408
|
}
|
|
355
|
-
}
|
|
356
|
-
logger.logger.debug({
|
|
357
|
-
event: 'sync-hooks.done',
|
|
358
|
-
hookName,
|
|
359
|
-
navigation,
|
|
360
409
|
});
|
|
361
|
-
|
|
362
|
-
registerSyncHook(hookName, hook) {
|
|
363
|
-
return utils.registerHook(this.syncHooks.get(hookName), hook);
|
|
410
|
+
return untap;
|
|
364
411
|
}
|
|
365
412
|
async runHooks(hookName, navigation) {
|
|
366
413
|
logger.logger.debug({
|
|
@@ -368,26 +415,7 @@ class AbstractRouter {
|
|
|
368
415
|
hookName,
|
|
369
416
|
navigation,
|
|
370
417
|
});
|
|
371
|
-
|
|
372
|
-
if (!hooks) {
|
|
373
|
-
logger.logger.debug({
|
|
374
|
-
event: 'hooks.empty',
|
|
375
|
-
hookName,
|
|
376
|
-
navigation,
|
|
377
|
-
});
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
await Promise.all(Array.from(hooks).map((hook) => Promise.resolve(hook(navigation)).catch((error) => {
|
|
381
|
-
logger.logger.warn({
|
|
382
|
-
event: 'hook.error',
|
|
383
|
-
error,
|
|
384
|
-
});
|
|
385
|
-
// rethrow error for beforeResolve to prevent showing not found page
|
|
386
|
-
// if app has problems while loading info about routes
|
|
387
|
-
if (hookName === 'beforeResolve') {
|
|
388
|
-
throw error;
|
|
389
|
-
}
|
|
390
|
-
})));
|
|
418
|
+
await this.hooks.get(hookName).callPromise({ navigation });
|
|
391
419
|
logger.logger.debug({
|
|
392
420
|
event: 'hooks.done',
|
|
393
421
|
hookName,
|
|
@@ -395,7 +423,25 @@ class AbstractRouter {
|
|
|
395
423
|
});
|
|
396
424
|
}
|
|
397
425
|
registerHook(hookName, hook) {
|
|
398
|
-
|
|
426
|
+
const untap = this.hooks
|
|
427
|
+
.get(hookName)
|
|
428
|
+
.tapPromise(hook.name ?? `hook-${this.hooksIndex++}`, async (_, { navigation }) => {
|
|
429
|
+
try {
|
|
430
|
+
await hook(navigation);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
logger.logger.warn({
|
|
434
|
+
event: 'hook.error',
|
|
435
|
+
error,
|
|
436
|
+
});
|
|
437
|
+
// rethrow error for beforeResolve to prevent showing not found page
|
|
438
|
+
// if app has problems while loading info about routes
|
|
439
|
+
if (hookName === 'beforeResolve') {
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
return untap;
|
|
399
445
|
}
|
|
400
446
|
uuid() {
|
|
401
447
|
return this.currentUuid++;
|
|
@@ -12,7 +12,7 @@ class ClientRouter extends AbstractRouter {
|
|
|
12
12
|
this.history = new ClientHistory();
|
|
13
13
|
this.history.listen(async ({ type, url, navigateState, replace, history }) => {
|
|
14
14
|
const currentUrl = this.getCurrentUrl();
|
|
15
|
-
const { pathname, query } = this.resolveUrl({ url });
|
|
15
|
+
const { pathname, query, hash } = this.resolveUrl({ url });
|
|
16
16
|
const isSameUrlNavigation = (currentUrl ? currentUrl.pathname : window.location.pathname) === pathname;
|
|
17
17
|
const isUpdateCurrentRoute = type === 'updateCurrentRoute' || (!type && isSameUrlNavigation);
|
|
18
18
|
//
|
|
@@ -37,6 +37,7 @@ class ClientRouter extends AbstractRouter {
|
|
|
37
37
|
await this.internalUpdateCurrentRoute({
|
|
38
38
|
params: route?.params,
|
|
39
39
|
query,
|
|
40
|
+
hash,
|
|
40
41
|
replace,
|
|
41
42
|
navigateState,
|
|
42
43
|
}, { history });
|
|
@@ -120,7 +121,8 @@ class ClientRouter extends AbstractRouter {
|
|
|
120
121
|
}
|
|
121
122
|
else if (this.onBlock) {
|
|
122
123
|
// last resort case for CSR fallback
|
|
123
|
-
|
|
124
|
+
await this.blockHook.callPromise({ navigation });
|
|
125
|
+
return;
|
|
124
126
|
}
|
|
125
127
|
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
126
128
|
return new Promise(() => { });
|
package/lib/router/client.es.js
CHANGED
|
@@ -12,7 +12,7 @@ class ClientRouter extends AbstractRouter {
|
|
|
12
12
|
this.history = new ClientHistory();
|
|
13
13
|
this.history.listen(async ({ type, url, navigateState, replace, history }) => {
|
|
14
14
|
const currentUrl = this.getCurrentUrl();
|
|
15
|
-
const { pathname, query } = this.resolveUrl({ url });
|
|
15
|
+
const { pathname, query, hash } = this.resolveUrl({ url });
|
|
16
16
|
const isSameUrlNavigation = (currentUrl ? currentUrl.pathname : window.location.pathname) === pathname;
|
|
17
17
|
const isUpdateCurrentRoute = type === 'updateCurrentRoute' || (!type && isSameUrlNavigation);
|
|
18
18
|
//
|
|
@@ -37,6 +37,7 @@ class ClientRouter extends AbstractRouter {
|
|
|
37
37
|
await this.internalUpdateCurrentRoute({
|
|
38
38
|
params: route?.params,
|
|
39
39
|
query,
|
|
40
|
+
hash,
|
|
40
41
|
replace,
|
|
41
42
|
navigateState,
|
|
42
43
|
}, { history });
|
|
@@ -120,7 +121,8 @@ class ClientRouter extends AbstractRouter {
|
|
|
120
121
|
}
|
|
121
122
|
else if (this.onBlock) {
|
|
122
123
|
// last resort case for CSR fallback
|
|
123
|
-
|
|
124
|
+
await this.blockHook.callPromise({ navigation });
|
|
125
|
+
return;
|
|
124
126
|
}
|
|
125
127
|
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
126
128
|
return new Promise(() => { });
|
package/lib/router/client.js
CHANGED
|
@@ -16,7 +16,7 @@ class ClientRouter extends abstract.AbstractRouter {
|
|
|
16
16
|
this.history = new client.ClientHistory();
|
|
17
17
|
this.history.listen(async ({ type, url, navigateState, replace, history }) => {
|
|
18
18
|
const currentUrl = this.getCurrentUrl();
|
|
19
|
-
const { pathname, query } = this.resolveUrl({ url });
|
|
19
|
+
const { pathname, query, hash } = this.resolveUrl({ url });
|
|
20
20
|
const isSameUrlNavigation = (currentUrl ? currentUrl.pathname : window.location.pathname) === pathname;
|
|
21
21
|
const isUpdateCurrentRoute = type === 'updateCurrentRoute' || (!type && isSameUrlNavigation);
|
|
22
22
|
//
|
|
@@ -41,6 +41,7 @@ class ClientRouter extends abstract.AbstractRouter {
|
|
|
41
41
|
await this.internalUpdateCurrentRoute({
|
|
42
42
|
params: route?.params,
|
|
43
43
|
query,
|
|
44
|
+
hash,
|
|
44
45
|
replace,
|
|
45
46
|
navigateState,
|
|
46
47
|
}, { history });
|
|
@@ -124,7 +125,8 @@ class ClientRouter extends abstract.AbstractRouter {
|
|
|
124
125
|
}
|
|
125
126
|
else if (this.onBlock) {
|
|
126
127
|
// last resort case for CSR fallback
|
|
127
|
-
|
|
128
|
+
await this.blockHook.callPromise({ navigation });
|
|
129
|
+
return;
|
|
128
130
|
}
|
|
129
131
|
// prevent routing from any continues navigation returning promise which will be not resolved
|
|
130
132
|
return new Promise(() => { });
|
package/lib/router/server.es.js
CHANGED
|
@@ -39,13 +39,15 @@ class Router extends AbstractRouter {
|
|
|
39
39
|
target,
|
|
40
40
|
});
|
|
41
41
|
this.blocked = true;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
await this.redirectHook.callPromise({
|
|
43
|
+
navigation: {
|
|
44
|
+
...navigation,
|
|
45
|
+
from: navigation.to,
|
|
46
|
+
fromUrl: navigation.url,
|
|
47
|
+
to: null,
|
|
48
|
+
url: this.resolveUrl(target),
|
|
49
|
+
code: target.code,
|
|
50
|
+
},
|
|
49
51
|
});
|
|
50
52
|
}
|
|
51
53
|
async notfound(navigation) {
|
package/lib/router/server.js
CHANGED
|
@@ -43,13 +43,15 @@ class Router extends abstract.AbstractRouter {
|
|
|
43
43
|
target,
|
|
44
44
|
});
|
|
45
45
|
this.blocked = true;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
await this.redirectHook.callPromise({
|
|
47
|
+
navigation: {
|
|
48
|
+
...navigation,
|
|
49
|
+
from: navigation.to,
|
|
50
|
+
fromUrl: navigation.url,
|
|
51
|
+
to: null,
|
|
52
|
+
url: this.resolveUrl(target),
|
|
53
|
+
code: target.code,
|
|
54
|
+
},
|
|
53
55
|
});
|
|
54
56
|
}
|
|
55
57
|
async notfound(navigation) {
|
package/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Url, Query } from '@tinkoff/url';
|
|
2
|
+
import { AbstractRouter } from './router/abstract';
|
|
2
3
|
export type Params = Record<string, string>;
|
|
3
4
|
export interface RouteConfig {
|
|
4
5
|
[key: string]: any;
|
|
@@ -63,4 +64,7 @@ export type NavigationHook = (navigation: Navigation) => Promise<void>;
|
|
|
63
64
|
export type NavigationSyncHook = (navigation: Navigation) => void;
|
|
64
65
|
export type HookName = 'beforeResolve' | 'beforeNavigate' | 'afterNavigate' | 'beforeUpdateCurrent' | 'afterUpdateCurrent';
|
|
65
66
|
export type SyncHookName = 'change';
|
|
67
|
+
export type RouterPlugin = {
|
|
68
|
+
apply(router: AbstractRouter): void;
|
|
69
|
+
};
|
|
66
70
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/utils.browser.js
CHANGED
|
@@ -25,11 +25,5 @@ const makeNavigateOptions = (options) => {
|
|
|
25
25
|
}
|
|
26
26
|
return options;
|
|
27
27
|
};
|
|
28
|
-
const registerHook = (hooksSet, hook) => {
|
|
29
|
-
hooksSet.add(hook);
|
|
30
|
-
return () => {
|
|
31
|
-
hooksSet.delete(hook);
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
28
|
|
|
35
|
-
export { isFilePath, isSameHost, makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash
|
|
29
|
+
export { isFilePath, isSameHost, makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash };
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { Url } from '@tinkoff/url';
|
|
2
|
-
import type { NavigateOptions
|
|
2
|
+
import type { NavigateOptions } from './types';
|
|
3
3
|
export declare const isFilePath: (pathname: string) => boolean;
|
|
4
4
|
export declare const normalizeTrailingSlash: (pathname: string, trailingSlash?: boolean) => string;
|
|
5
5
|
export declare const normalizeManySlashes: (hrefOrPath: string) => string;
|
|
6
6
|
export declare const isSameHost: import("@tinkoff/utils/typings/types").Func<true> | ((url: Url | URL) => boolean);
|
|
7
7
|
export declare const makeNavigateOptions: (options: string | NavigateOptions) => NavigateOptions;
|
|
8
|
-
export declare const registerHook: <T extends NavigationHook | NavigationSyncHook | NavigationGuard>(hooksSet: Set<T>, hook: T) => () => void;
|
|
9
8
|
//# sourceMappingURL=utils.d.ts.map
|
package/lib/utils.es.js
CHANGED
|
@@ -25,11 +25,5 @@ const makeNavigateOptions = (options) => {
|
|
|
25
25
|
}
|
|
26
26
|
return options;
|
|
27
27
|
};
|
|
28
|
-
const registerHook = (hooksSet, hook) => {
|
|
29
|
-
hooksSet.add(hook);
|
|
30
|
-
return () => {
|
|
31
|
-
hooksSet.delete(hook);
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
28
|
|
|
35
|
-
export { isFilePath, isSameHost, makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash
|
|
29
|
+
export { isFilePath, isSameHost, makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash };
|
package/lib/utils.js
CHANGED
|
@@ -33,16 +33,9 @@ const makeNavigateOptions = (options) => {
|
|
|
33
33
|
}
|
|
34
34
|
return options;
|
|
35
35
|
};
|
|
36
|
-
const registerHook = (hooksSet, hook) => {
|
|
37
|
-
hooksSet.add(hook);
|
|
38
|
-
return () => {
|
|
39
|
-
hooksSet.delete(hook);
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
36
|
|
|
43
37
|
exports.isFilePath = isFilePath;
|
|
44
38
|
exports.isSameHost = isSameHost;
|
|
45
39
|
exports.makeNavigateOptions = makeNavigateOptions;
|
|
46
40
|
exports.normalizeManySlashes = normalizeManySlashes;
|
|
47
41
|
exports.normalizeTrailingSlash = normalizeTrailingSlash;
|
|
48
|
-
exports.registerHook = registerHook;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinkoff/router",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.67",
|
|
4
4
|
"description": "router",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
"@tinkoff/react-hooks": "0.4.2",
|
|
25
25
|
"@tinkoff/url": "0.11.2",
|
|
26
26
|
"@tinkoff/utils": "^2.1.2",
|
|
27
|
+
"@tinkoff/hook-runner": "0.7.3",
|
|
27
28
|
"use-sync-external-store": "^1.2.0"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
|
-
"@tramvai/core": "5.
|
|
31
|
+
"@tramvai/core": "5.24.0",
|
|
31
32
|
"react": ">=16.14.0",
|
|
32
33
|
"tslib": "^2.4.0"
|
|
33
34
|
},
|