@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.
@@ -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, registerHook } from '../utils.browser.js';
7
+ import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost } from '../utils.browser.js';
7
8
 
8
9
  class AbstractRouter {
9
- constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
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.hooks = new Map([
20
- ['beforeResolve', new Set(beforeResolve)],
21
- ['beforeNavigate', new Set(beforeNavigate)],
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.currentUuid = 0;
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
- return this.internalUpdateCurrentRoute(updateRouteOptions, {});
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.runHooks('beforeUpdateCurrent', navigation);
100
- this.commitNavigation(navigation);
101
- await this.runHooks('afterUpdateCurrent', navigation);
154
+ await this.runUpdateHook.callPromise({ navigation });
102
155
  }
103
156
  async navigate(navigateOptions) {
104
- return this.internalNavigate(makeNavigateOptions(navigateOptions), {});
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
- // check for redirect in new route description
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
- return this.onRedirect?.({
205
- ...navigation,
206
- from: navigation.to,
207
- fromUrl: navigation.url,
208
- to: null,
209
- url: this.resolveUrl(target),
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
- return this.onNotFound?.(navigation);
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
- return this.onBlock(navigation);
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
- if (!this.guards) {
292
- logger.debug({
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
- return registerHook(this.guards, guard);
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
- const hooks = this.syncHooks.get(hookName);
328
- if (!hooks) {
329
- logger.debug({
330
- event: 'sync-hooks.empty',
331
- hookName,
332
- navigation,
333
- });
334
- return;
335
- }
336
- for (const hook of hooks) {
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
- const hooks = this.hooks.get(hookName);
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
- return registerHook(this.hooks.get(hookName), hook);
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++;
@@ -1,5 +1,7 @@
1
1
  import type { Url } from '@tinkoff/url';
2
- import type { Route, NavigateOptions, UpdateCurrentRouteOptions, Navigation, NavigationGuard, NavigationHook, NavigationSyncHook, HookName, Params, SyncHookName, HistoryOptions } from '../types';
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
- protected guards: Set<NavigationGuard>;
37
- protected hooks: Map<HookName, Set<NavigationHook>>;
38
- protected syncHooks: Map<SyncHookName, Set<NavigationSyncHook>>;
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
- constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve, beforeNavigate, afterNavigate, beforeUpdateCurrent, afterUpdateCurrent, guards, onChange, onRedirect, onNotFound, onBlock, }: Options);
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): () => void;
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): () => void;
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): () => void;
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, registerHook } from '../utils.es.js';
7
+ import { makeNavigateOptions, normalizeManySlashes, normalizeTrailingSlash, isSameHost } from '../utils.es.js';
7
8
 
8
9
  class AbstractRouter {
9
- constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
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.hooks = new Map([
20
- ['beforeResolve', new Set(beforeResolve)],
21
- ['beforeNavigate', new Set(beforeNavigate)],
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.currentUuid = 0;
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
- return this.internalUpdateCurrentRoute(updateRouteOptions, {});
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.runHooks('beforeUpdateCurrent', navigation);
100
- this.commitNavigation(navigation);
101
- await this.runHooks('afterUpdateCurrent', navigation);
154
+ await this.runUpdateHook.callPromise({ navigation });
102
155
  }
103
156
  async navigate(navigateOptions) {
104
- return this.internalNavigate(makeNavigateOptions(navigateOptions), {});
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
- // check for redirect in new route description
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
- return this.onRedirect?.({
205
- ...navigation,
206
- from: navigation.to,
207
- fromUrl: navigation.url,
208
- to: null,
209
- url: this.resolveUrl(target),
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
- return this.onNotFound?.(navigation);
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
- return this.onBlock(navigation);
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
- if (!this.guards) {
292
- logger.debug({
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
- return registerHook(this.guards, guard);
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
- const hooks = this.syncHooks.get(hookName);
328
- if (!hooks) {
329
- logger.debug({
330
- event: 'sync-hooks.empty',
331
- hookName,
332
- navigation,
333
- });
334
- return;
335
- }
336
- for (const hook of hooks) {
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
- const hooks = this.hooks.get(hookName);
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
- return registerHook(this.hooks.get(hookName), hook);
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++;
@@ -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
- constructor({ trailingSlash, mergeSlashes, enableViewTransitions, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
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.hooks = new Map([
29
- ['beforeResolve', new Set(beforeResolve)],
30
- ['beforeNavigate', new Set(beforeNavigate)],
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.currentUuid = 0;
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
- return this.internalUpdateCurrentRoute(updateRouteOptions, {});
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.runHooks('beforeUpdateCurrent', navigation);
109
- this.commitNavigation(navigation);
110
- await this.runHooks('afterUpdateCurrent', navigation);
163
+ await this.runUpdateHook.callPromise({ navigation });
111
164
  }
112
165
  async navigate(navigateOptions) {
113
- return this.internalNavigate(utils.makeNavigateOptions(navigateOptions), {});
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
- // check for redirect in new route description
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
- return this.onRedirect?.({
214
- ...navigation,
215
- from: navigation.to,
216
- fromUrl: navigation.url,
217
- to: null,
218
- url: this.resolveUrl(target),
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
- return this.onNotFound?.(navigation);
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
- return this.onBlock(navigation);
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
- if (!this.guards) {
301
- logger.logger.debug({
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
- return utils.registerHook(this.guards, guard);
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
- const hooks = this.syncHooks.get(hookName);
337
- if (!hooks) {
338
- logger.logger.debug({
339
- event: 'sync-hooks.empty',
340
- hookName,
341
- navigation,
342
- });
343
- return;
344
- }
345
- for (const hook of hooks) {
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
- const hooks = this.hooks.get(hookName);
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
- return utils.registerHook(this.hooks.get(hookName), hook);
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
- return this.onBlock(navigation);
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(() => { });
@@ -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
- return this.onBlock(navigation);
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(() => { });
@@ -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
- return this.onBlock(navigation);
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(() => { });
@@ -39,13 +39,15 @@ class Router extends AbstractRouter {
39
39
  target,
40
40
  });
41
41
  this.blocked = true;
42
- return this.onRedirect({
43
- ...navigation,
44
- from: navigation.to,
45
- fromUrl: navigation.url,
46
- to: null,
47
- url: this.resolveUrl(target),
48
- code: target.code,
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) {
@@ -43,13 +43,15 @@ class Router extends abstract.AbstractRouter {
43
43
  target,
44
44
  });
45
45
  this.blocked = true;
46
- return this.onRedirect({
47
- ...navigation,
48
- from: navigation.to,
49
- fromUrl: navigation.url,
50
- to: null,
51
- url: this.resolveUrl(target),
52
- code: target.code,
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
@@ -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, registerHook };
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, NavigationHook, NavigationSyncHook, NavigationGuard } from './types';
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, registerHook };
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.65",
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.22.0",
31
+ "@tramvai/core": "5.24.0",
31
32
  "react": ">=16.14.0",
32
33
  "tslib": "^2.4.0"
33
34
  },