@tinkoff/router 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/components/react/context.browser.js +7 -0
  2. package/lib/components/react/context.es.js +7 -0
  3. package/lib/components/react/context.js +13 -0
  4. package/lib/components/react/provider.browser.js +12 -0
  5. package/lib/components/react/provider.es.js +12 -0
  6. package/lib/components/react/provider.js +16 -0
  7. package/lib/components/react/useNavigate.browser.js +17 -0
  8. package/lib/components/react/useNavigate.es.js +17 -0
  9. package/lib/components/react/useNavigate.js +21 -0
  10. package/lib/components/react/useRoute.browser.js +8 -0
  11. package/lib/components/react/useRoute.es.js +8 -0
  12. package/lib/components/react/useRoute.js +12 -0
  13. package/lib/components/react/useRouter.browser.js +8 -0
  14. package/lib/components/react/useRouter.es.js +8 -0
  15. package/lib/components/react/useRouter.js +12 -0
  16. package/lib/components/react/useUrl.browser.js +8 -0
  17. package/lib/components/react/useUrl.es.js +8 -0
  18. package/lib/components/react/useUrl.js +12 -0
  19. package/lib/history/base.browser.js +11 -0
  20. package/lib/history/base.es.js +11 -0
  21. package/lib/history/base.js +15 -0
  22. package/lib/history/client.browser.js +121 -0
  23. package/lib/history/client.es.js +121 -0
  24. package/lib/history/client.js +125 -0
  25. package/lib/history/server.es.js +15 -0
  26. package/lib/history/server.js +19 -0
  27. package/lib/history/wrapper.browser.js +72 -0
  28. package/lib/history/wrapper.es.js +72 -0
  29. package/lib/history/wrapper.js +80 -0
  30. package/lib/index.browser.js +12 -1169
  31. package/lib/index.es.js +12 -1097
  32. package/lib/index.js +36 -1124
  33. package/lib/logger.browser.js +15 -0
  34. package/lib/logger.es.js +15 -0
  35. package/lib/logger.js +23 -0
  36. package/lib/router/abstract.browser.js +395 -0
  37. package/lib/router/abstract.es.js +395 -0
  38. package/lib/router/abstract.js +404 -0
  39. package/lib/router/browser.browser.js +166 -0
  40. package/lib/router/client.browser.js +129 -0
  41. package/lib/router/client.d.ts +1 -0
  42. package/lib/router/client.es.js +129 -0
  43. package/lib/router/client.js +133 -0
  44. package/lib/router/clientNoSpa.browser.js +17 -0
  45. package/lib/router/clientNoSpa.es.js +17 -0
  46. package/lib/router/clientNoSpa.js +21 -0
  47. package/lib/router/server.es.js +85 -0
  48. package/lib/router/server.js +89 -0
  49. package/lib/tree/constants.browser.js +6 -0
  50. package/lib/tree/constants.es.js +6 -0
  51. package/lib/tree/constants.js +13 -0
  52. package/lib/tree/tree.browser.js +148 -0
  53. package/lib/tree/tree.es.js +148 -0
  54. package/lib/tree/tree.js +158 -0
  55. package/lib/tree/utils.browser.js +77 -0
  56. package/lib/tree/utils.es.js +77 -0
  57. package/lib/tree/utils.js +90 -0
  58. package/lib/utils.browser.js +35 -0
  59. package/lib/utils.es.js +35 -0
  60. package/lib/utils.js +48 -0
  61. package/package.json +5 -6
@@ -0,0 +1,404 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var isString = require('@tinkoff/utils/is/string');
6
+ var isObject = require('@tinkoff/utils/is/object');
7
+ var url = require('@tinkoff/url');
8
+ var utils$1 = require('../tree/utils.js');
9
+ var logger = require('../logger.js');
10
+ var utils = require('../utils.js');
11
+
12
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
+
14
+ var isString__default = /*#__PURE__*/_interopDefaultLegacy(isString);
15
+ var isObject__default = /*#__PURE__*/_interopDefaultLegacy(isObject);
16
+
17
+ class AbstractRouter {
18
+ constructor({ trailingSlash, mergeSlashes, beforeResolve = [], beforeNavigate = [], afterNavigate = [], beforeUpdateCurrent = [], afterUpdateCurrent = [], guards = [], onChange = [], onRedirect, onNotFound, onBlock, }) {
19
+ this.started = false;
20
+ this.trailingSlash = false;
21
+ this.strictTrailingSlash = true;
22
+ this.mergeSlashes = false;
23
+ this.trailingSlash = trailingSlash !== null && trailingSlash !== void 0 ? trailingSlash : false;
24
+ this.strictTrailingSlash = typeof trailingSlash === 'undefined';
25
+ this.mergeSlashes = mergeSlashes !== null && mergeSlashes !== void 0 ? mergeSlashes : false;
26
+ this.hooks = new Map([
27
+ ['beforeResolve', new Set(beforeResolve)],
28
+ ['beforeNavigate', new Set(beforeNavigate)],
29
+ ['afterNavigate', new Set(afterNavigate)],
30
+ ['beforeUpdateCurrent', new Set(beforeUpdateCurrent)],
31
+ ['afterUpdateCurrent', new Set(afterUpdateCurrent)],
32
+ ]);
33
+ this.guards = new Set(guards);
34
+ this.syncHooks = new Map([['change', new Set(onChange)]]);
35
+ this.onRedirect = onRedirect;
36
+ this.onNotFound = onNotFound;
37
+ this.onBlock = onBlock;
38
+ }
39
+ // start is using as marker that any preparation for proper work has done in the app
40
+ // and now router can manage any navigations
41
+ async start() {
42
+ logger.logger.debug({
43
+ event: 'start',
44
+ });
45
+ this.started = true;
46
+ }
47
+ getCurrentRoute() {
48
+ var _a, _b, _c;
49
+ // when something will try to get currentRoute while navigating, it will get route which router currently navigating
50
+ // in case some handler supposed to load data of route or similar
51
+ return (_b = (_a = this.currentNavigation) === null || _a === void 0 ? void 0 : _a.to) !== null && _b !== void 0 ? _b : (_c = this.lastNavigation) === null || _c === void 0 ? void 0 : _c.to;
52
+ }
53
+ getCurrentUrl() {
54
+ var _a, _b, _c;
55
+ // same as getCurrentRoute
56
+ return (_b = (_a = this.currentNavigation) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : (_c = this.lastNavigation) === null || _c === void 0 ? void 0 : _c.url;
57
+ }
58
+ getLastRoute() {
59
+ var _a;
60
+ return (_a = this.lastNavigation) === null || _a === void 0 ? void 0 : _a.to;
61
+ }
62
+ getLastUrl() {
63
+ var _a;
64
+ return (_a = this.lastNavigation) === null || _a === void 0 ? void 0 : _a.url;
65
+ }
66
+ commitNavigation(navigation) {
67
+ logger.logger.debug({
68
+ event: 'commit-navigation',
69
+ navigation,
70
+ });
71
+ if (!navigation.history) {
72
+ // in case we came from history do not history back to prevent infinity recursive calls
73
+ this.history.save(navigation);
74
+ }
75
+ this.lastNavigation = navigation;
76
+ this.currentNavigation = null;
77
+ this.runSyncHooks('change', navigation);
78
+ }
79
+ async updateCurrentRoute(updateRouteOptions) {
80
+ return this.internalUpdateCurrentRoute(updateRouteOptions, {});
81
+ }
82
+ async internalUpdateCurrentRoute(updateRouteOptions, { history }) {
83
+ var _a;
84
+ const prevNavigation = (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
85
+ if (!prevNavigation) {
86
+ throw new Error('updateCurrentRoute should only be called after navigate to some route');
87
+ }
88
+ const { replace, params, navigateState } = updateRouteOptions;
89
+ const { to: from, url: fromUrl } = prevNavigation;
90
+ const navigation = {
91
+ type: 'updateCurrentRoute',
92
+ from,
93
+ to: this.resolveRoute({ params, navigateState }, { wildcard: true }),
94
+ url: this.resolveUrl(updateRouteOptions),
95
+ fromUrl,
96
+ replace,
97
+ history,
98
+ navigateState,
99
+ code: updateRouteOptions.code,
100
+ };
101
+ logger.logger.debug({
102
+ event: 'update-current-route',
103
+ updateRouteOptions,
104
+ navigation,
105
+ });
106
+ await this.run(navigation);
107
+ }
108
+ async runUpdateCurrentRoute(navigation) {
109
+ await this.runHooks('beforeUpdateCurrent', navigation);
110
+ this.commitNavigation(navigation);
111
+ await this.runHooks('afterUpdateCurrent', navigation);
112
+ }
113
+ async navigate(navigateOptions) {
114
+ return this.internalNavigate(utils.makeNavigateOptions(navigateOptions), {});
115
+ }
116
+ async internalNavigate(navigateOptions, { history, redirect }) {
117
+ var _a;
118
+ const { url, replace, params, navigateState, code } = navigateOptions;
119
+ const prevNavigation = redirect
120
+ ? this.lastNavigation
121
+ : (_a = this.currentNavigation) !== null && _a !== void 0 ? _a : this.lastNavigation;
122
+ if (!url && !prevNavigation) {
123
+ throw new Error('Navigate url should be specified and cannot be omitted for first navigation');
124
+ }
125
+ const resolvedUrl = this.resolveUrl(navigateOptions);
126
+ const { to: from, url: fromUrl } = prevNavigation !== null && prevNavigation !== void 0 ? prevNavigation : {};
127
+ const redirectFrom = redirect ? this.currentNavigation.to : undefined;
128
+ let navigation = {
129
+ type: 'navigate',
130
+ from,
131
+ url: resolvedUrl,
132
+ fromUrl,
133
+ replace,
134
+ history,
135
+ navigateState,
136
+ code,
137
+ redirect,
138
+ redirectFrom,
139
+ };
140
+ await this.runHooks('beforeResolve', navigation);
141
+ const to = this.resolveRoute({ url: resolvedUrl, params, navigateState }, { wildcard: true });
142
+ if (to) {
143
+ navigation = {
144
+ ...navigation,
145
+ to,
146
+ };
147
+ }
148
+ logger.logger.debug({
149
+ event: 'navigation',
150
+ navigation,
151
+ });
152
+ if (!navigation.to) {
153
+ return this.notfound(navigation);
154
+ }
155
+ await this.run(navigation);
156
+ }
157
+ async runNavigate(navigation) {
158
+ // check for redirect in new route description
159
+ if (navigation.to.redirect) {
160
+ return this.redirect(navigation, utils.makeNavigateOptions(navigation.to.redirect));
161
+ }
162
+ await this.runGuards(navigation);
163
+ await this.runHooks('beforeNavigate', navigation);
164
+ this.commitNavigation(navigation);
165
+ await this.runHooks('afterNavigate', navigation);
166
+ }
167
+ async run(navigation) {
168
+ this.currentNavigation = navigation;
169
+ if (navigation.type === 'navigate') {
170
+ await this.runNavigate(navigation);
171
+ }
172
+ if (navigation.type === 'updateCurrentRoute') {
173
+ await this.runUpdateCurrentRoute(navigation);
174
+ }
175
+ }
176
+ resolve(resolveOptions, options) {
177
+ const opts = typeof resolveOptions === 'string' ? { url: resolveOptions } : resolveOptions;
178
+ return this.resolveRoute({ ...opts, url: url.parse(opts.url) }, options);
179
+ }
180
+ back(options) {
181
+ return this.go(-1, options);
182
+ }
183
+ forward() {
184
+ return this.go(1);
185
+ }
186
+ async go(to, options) {
187
+ logger.logger.debug({
188
+ event: 'history.go',
189
+ to,
190
+ });
191
+ return this.history.go(to, options);
192
+ }
193
+ isNavigating() {
194
+ return !!this.currentNavigation;
195
+ }
196
+ async dehydrate() {
197
+ throw new Error('Not implemented');
198
+ }
199
+ async rehydrate(navigation) {
200
+ throw new Error('Not implemented');
201
+ }
202
+ addRoute(route) {
203
+ var _a;
204
+ (_a = this.tree) === null || _a === void 0 ? void 0 : _a.addRoute(route);
205
+ }
206
+ async redirect(navigation, target) {
207
+ var _a;
208
+ logger.logger.debug({
209
+ event: 'redirect',
210
+ navigation,
211
+ target,
212
+ });
213
+ return (_a = this.onRedirect) === null || _a === void 0 ? void 0 : _a.call(this, {
214
+ ...navigation,
215
+ from: navigation.to,
216
+ fromUrl: navigation.url,
217
+ to: null,
218
+ url: this.resolveUrl(target),
219
+ });
220
+ }
221
+ async notfound(navigation) {
222
+ var _a;
223
+ logger.logger.debug({
224
+ event: 'not-found',
225
+ navigation,
226
+ });
227
+ return (_a = this.onNotFound) === null || _a === void 0 ? void 0 : _a.call(this, navigation);
228
+ }
229
+ async block(navigation) {
230
+ logger.logger.debug({
231
+ event: 'blocked',
232
+ navigation,
233
+ });
234
+ this.currentNavigation = null;
235
+ if (this.onBlock) {
236
+ return this.onBlock(navigation);
237
+ }
238
+ throw new Error('Navigation blocked');
239
+ }
240
+ normalizePathname(pathname) {
241
+ let normalized = pathname;
242
+ if (this.mergeSlashes) {
243
+ normalized = utils.normalizeManySlashes(normalized);
244
+ }
245
+ if (!this.strictTrailingSlash) {
246
+ normalized = utils.normalizeTrailingSlash(normalized, this.trailingSlash);
247
+ }
248
+ return normalized;
249
+ }
250
+ resolveUrl({ url: url$1, query = {}, params, preserveQuery, hash }) {
251
+ var _a;
252
+ const currentRoute = this.getCurrentRoute();
253
+ const currentUrl = this.getCurrentUrl();
254
+ const resultUrl = url$1 ? url.rawResolveUrl((_a = currentUrl === null || currentUrl === void 0 ? void 0 : currentUrl.href) !== null && _a !== void 0 ? _a : '', url$1) : url.rawParse(currentUrl.href);
255
+ let { pathname } = resultUrl;
256
+ if (params) {
257
+ if (url$1) {
258
+ pathname = utils$1.makePath(resultUrl.pathname, params);
259
+ }
260
+ else if (currentRoute) {
261
+ pathname = utils$1.makePath(currentRoute.path, { ...currentRoute.params, ...params });
262
+ }
263
+ }
264
+ if (utils.isSameHost(resultUrl)) {
265
+ pathname = this.normalizePathname(pathname);
266
+ }
267
+ return url.convertRawUrl(url.rawAssignUrl(resultUrl, {
268
+ pathname,
269
+ search: url$1 ? resultUrl.search : '',
270
+ query: {
271
+ ...(preserveQuery ? this.getCurrentUrl().query : {}),
272
+ ...query,
273
+ },
274
+ hash: hash !== null && hash !== void 0 ? hash : resultUrl.hash,
275
+ }));
276
+ }
277
+ resolveRoute({ url, params, navigateState }, { wildcard } = {}) {
278
+ var _a, _b;
279
+ let route = url ? (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(url.pathname) : this.getCurrentRoute();
280
+ if (wildcard && !route && url) {
281
+ // if ordinary route not found look for a wildcard route
282
+ route = (_b = this.tree) === null || _b === void 0 ? void 0 : _b.getWildcard(url.pathname);
283
+ }
284
+ if (!route) {
285
+ return;
286
+ }
287
+ // if condition is true route data not changed, so no need to create new reference for route object
288
+ if (!params && navigateState === route.navigateState) {
289
+ return route;
290
+ }
291
+ return {
292
+ ...route,
293
+ params: { ...route.params, ...params },
294
+ navigateState,
295
+ };
296
+ }
297
+ async runGuards(navigation) {
298
+ logger.logger.debug({
299
+ event: 'guards.run',
300
+ navigation,
301
+ });
302
+ if (!this.guards) {
303
+ logger.logger.debug({
304
+ event: 'guards.empty',
305
+ navigation,
306
+ });
307
+ return;
308
+ }
309
+ const results = await Promise.all(Array.from(this.guards).map((guard) => Promise.resolve(guard(navigation)).catch((error) => {
310
+ logger.logger.warn({
311
+ event: 'guard.error',
312
+ error,
313
+ });
314
+ })));
315
+ logger.logger.debug({
316
+ event: 'guards.done',
317
+ navigation,
318
+ results,
319
+ });
320
+ for (const result of results) {
321
+ if (result === false) {
322
+ return this.block(navigation);
323
+ }
324
+ if (isString__default["default"](result) || isObject__default["default"](result)) {
325
+ return this.redirect(navigation, utils.makeNavigateOptions(result));
326
+ }
327
+ }
328
+ }
329
+ registerGuard(guard) {
330
+ return utils.registerHook(this.guards, guard);
331
+ }
332
+ runSyncHooks(hookName, navigation) {
333
+ logger.logger.debug({
334
+ event: 'sync-hooks.run',
335
+ hookName,
336
+ navigation,
337
+ });
338
+ const hooks = this.syncHooks.get(hookName);
339
+ if (!hooks) {
340
+ logger.logger.debug({
341
+ event: 'sync-hooks.empty',
342
+ hookName,
343
+ navigation,
344
+ });
345
+ return;
346
+ }
347
+ for (const hook of hooks) {
348
+ try {
349
+ hook(navigation);
350
+ }
351
+ catch (error) {
352
+ logger.logger.warn({
353
+ event: 'sync-hooks.error',
354
+ error,
355
+ });
356
+ }
357
+ }
358
+ logger.logger.debug({
359
+ event: 'sync-hooks.done',
360
+ hookName,
361
+ navigation,
362
+ });
363
+ }
364
+ registerSyncHook(hookName, hook) {
365
+ return utils.registerHook(this.syncHooks.get(hookName), hook);
366
+ }
367
+ async runHooks(hookName, navigation) {
368
+ logger.logger.debug({
369
+ event: 'hooks.run',
370
+ hookName,
371
+ navigation,
372
+ });
373
+ const hooks = this.hooks.get(hookName);
374
+ if (!hooks) {
375
+ logger.logger.debug({
376
+ event: 'hooks.empty',
377
+ hookName,
378
+ navigation,
379
+ });
380
+ return;
381
+ }
382
+ await Promise.all(Array.from(hooks).map((hook) => Promise.resolve(hook(navigation)).catch((error) => {
383
+ logger.logger.warn({
384
+ event: 'hook.error',
385
+ error,
386
+ });
387
+ // rethrow error for beforeResolve to prevent showing not found page
388
+ // if app has problems while loading info about routes
389
+ if (hookName === 'beforeResolve') {
390
+ throw error;
391
+ }
392
+ })));
393
+ logger.logger.debug({
394
+ event: 'hooks.done',
395
+ hookName,
396
+ navigation,
397
+ });
398
+ }
399
+ registerHook(hookName, hook) {
400
+ return utils.registerHook(this.hooks.get(hookName), hook);
401
+ }
402
+ }
403
+
404
+ exports.AbstractRouter = AbstractRouter;
@@ -0,0 +1,166 @@
1
+ import { ClientRouter } from './client.browser.js';
2
+ import { logger } from '../logger.browser.js';
3
+ import { RouteTree } from '../tree/tree.browser.js';
4
+
5
+ const DELAY_CHECK_INTERVAL = 50;
6
+ class Router extends ClientRouter {
7
+ constructor(options) {
8
+ super(options);
9
+ this.tree = new RouteTree(options.routes);
10
+ this.history.setTree(this.tree);
11
+ }
12
+ async rehydrate(navigation) {
13
+ return this.resolveIfDelayFound(super.rehydrate(navigation));
14
+ }
15
+ async start() {
16
+ await super.start();
17
+ if (this.delayedNavigation) {
18
+ const { delayedNavigation } = this;
19
+ this.delayedNavigation = null;
20
+ return this.flattenDelayedNavigation(delayedNavigation);
21
+ }
22
+ }
23
+ async run(navigation) {
24
+ // if router is not started yet delay current navigation without blocking promise resolving
25
+ if (!this.started) {
26
+ this.delayNavigation(navigation);
27
+ return;
28
+ }
29
+ // if we have already running navigation delay current one and call it later
30
+ if (this.currentNavigation) {
31
+ return this.delayNavigation(navigation);
32
+ }
33
+ // ignore previous navigations that were put in delayedNavigation as we have more fresh navigation to execute
34
+ if (this.delayedNavigation) {
35
+ logger.info({
36
+ event: 'delay-navigation-drop',
37
+ delayedNavigation: this.delayedNavigation,
38
+ navigation,
39
+ });
40
+ this.delayedNavigation = null;
41
+ }
42
+ return this.flattenDelayedNavigation(navigation);
43
+ }
44
+ delayNavigation(navigation) {
45
+ this.delayedNavigation = navigation;
46
+ logger.info({
47
+ event: 'delay-navigation',
48
+ navigation,
49
+ });
50
+ if (this.delayedPromise) {
51
+ return this.delayedPromise;
52
+ }
53
+ // resolve promise only after latest navigation has been executed
54
+ this.delayedPromise = new Promise((resolve, reject) => {
55
+ this.delayedResolve = resolve;
56
+ this.delayedReject = reject;
57
+ });
58
+ return this.delayedPromise;
59
+ }
60
+ commitNavigation(navigation) {
61
+ // if we have parallel navigation do not update current url, as it outdated anyway
62
+ if (navigation.cancelled) {
63
+ logger.debug({
64
+ event: 'delay-ignore-commit',
65
+ navigation,
66
+ });
67
+ return;
68
+ }
69
+ return super.commitNavigation(navigation);
70
+ }
71
+ async runGuards(navigation) {
72
+ // drop checking guards if we have delayed navigation
73
+ if (navigation.cancelled) {
74
+ logger.debug({
75
+ event: 'delay-ignore-guards',
76
+ navigation,
77
+ });
78
+ return;
79
+ }
80
+ return super.runGuards(navigation);
81
+ }
82
+ async runHooks(hookName, navigation) {
83
+ // drop hook calls if we have an other navigation delayed
84
+ // except only for case when current navigation already happened
85
+ // and we should synchronize this update with app
86
+ // (in case app has some logic for currently showing url on afterNavigate or afterRouteUpdate)
87
+ if (navigation.cancelled && this.lastNavigation !== navigation) {
88
+ logger.debug({
89
+ event: 'delay-ignore-hooks',
90
+ navigation,
91
+ });
92
+ return;
93
+ }
94
+ try {
95
+ await super.runHooks(hookName, navigation);
96
+ }
97
+ catch (error) {
98
+ return this.notfound(navigation);
99
+ }
100
+ }
101
+ async resolveIfDelayFound(task) {
102
+ let delayResolve;
103
+ const timer = setInterval(() => {
104
+ var _a;
105
+ if (this.delayedNavigation) {
106
+ if (this.delayedNavigation.type === 'navigate' ||
107
+ this.delayedNavigation.type === ((_a = this.currentNavigation) === null || _a === void 0 ? void 0 : _a.type)) {
108
+ logger.info({
109
+ event: 'delay-navigation-found',
110
+ navigation: this.delayedNavigation,
111
+ });
112
+ // set cancelled flag
113
+ if (this.currentNavigation) {
114
+ this.currentNavigation.cancelled = true;
115
+ this.currentNavigation = null;
116
+ }
117
+ // resolve current navigation to start new navigation asap
118
+ delayResolve();
119
+ }
120
+ else {
121
+ // updateCurrentRoute should happen only after currentNavigation, so resolve it first to prevent dead-lock
122
+ this.delayedResolve();
123
+ }
124
+ }
125
+ }, DELAY_CHECK_INTERVAL);
126
+ await Promise.race([
127
+ task,
128
+ new Promise((resolve) => {
129
+ delayResolve = resolve;
130
+ }),
131
+ ]);
132
+ clearInterval(timer);
133
+ delayResolve();
134
+ }
135
+ async flattenDelayedNavigation(navigation) {
136
+ const flatten = async (nav) => {
137
+ await this.resolveIfDelayFound(super.run(nav));
138
+ // if new navigation has been called while this navigation lasts
139
+ // call new navigation execution
140
+ if (this.delayedNavigation) {
141
+ const { delayedNavigation } = this;
142
+ logger.info({
143
+ event: 'delay-navigation-run',
144
+ navigation: delayedNavigation,
145
+ });
146
+ this.delayedNavigation = null;
147
+ return flatten(delayedNavigation);
148
+ }
149
+ };
150
+ return flatten(navigation)
151
+ .then(() => {
152
+ var _a;
153
+ (_a = this.delayedResolve) === null || _a === void 0 ? void 0 : _a.call(this);
154
+ }, (err) => {
155
+ var _a;
156
+ (_a = this.delayedReject) === null || _a === void 0 ? void 0 : _a.call(this, err);
157
+ })
158
+ .finally(() => {
159
+ this.delayedPromise = null;
160
+ this.delayedResolve = null;
161
+ this.delayedReject = null;
162
+ });
163
+ }
164
+ }
165
+
166
+ export { Router };
@@ -0,0 +1,129 @@
1
+ import { parse } from '@tinkoff/url';
2
+ import { AbstractRouter } from './abstract.browser.js';
3
+ import { makeNavigateOptions, isSameHost } from '../utils.browser.js';
4
+ import { logger } from '../logger.browser.js';
5
+ import { ClientHistory } from '../history/client.browser.js';
6
+
7
+ class ClientRouter extends AbstractRouter {
8
+ constructor(options) {
9
+ super(options);
10
+ // this flag for cases when we don't have initial router state from server - CSR fallback initialization
11
+ this.fullRehydrationInProcess = null;
12
+ this.history = new ClientHistory();
13
+ this.history.listen(async ({ type, url, navigateState, replace, history }) => {
14
+ var _a;
15
+ const currentUrl = this.getCurrentUrl();
16
+ const { pathname, query } = this.resolveUrl({ url });
17
+ const isSameUrlNavigation = currentUrl.pathname === pathname;
18
+ if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
19
+ const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
20
+ await this.internalUpdateCurrentRoute({
21
+ params: route === null || route === void 0 ? void 0 : route.params,
22
+ query,
23
+ replace,
24
+ navigateState,
25
+ }, { history });
26
+ }
27
+ else {
28
+ await this.internalNavigate({
29
+ url,
30
+ replace,
31
+ navigateState,
32
+ }, { history });
33
+ }
34
+ });
35
+ }
36
+ async rehydrate(navigation) {
37
+ this.fullRehydrationInProcess = !navigation.to;
38
+ logger.debug({
39
+ event: 'rehydrate',
40
+ navigation,
41
+ });
42
+ const url = parse(window.location.href);
43
+ this.currentNavigation = {
44
+ ...navigation,
45
+ type: 'navigate',
46
+ url,
47
+ };
48
+ this.lastNavigation = this.currentNavigation;
49
+ if (this.fullRehydrationInProcess) {
50
+ await this.runHooks('beforeResolve', this.currentNavigation);
51
+ const to = this.resolveRoute({ url }, { wildcard: true });
52
+ const redirect = to === null || to === void 0 ? void 0 : to.redirect;
53
+ this.currentNavigation.to = to;
54
+ if (redirect) {
55
+ return this.redirect(this.currentNavigation, makeNavigateOptions(redirect));
56
+ }
57
+ }
58
+ // rerun guard check in case it differs from server side
59
+ await this.runGuards(this.currentNavigation);
60
+ // and init any history listeners
61
+ this.history.init(this.currentNavigation);
62
+ if (this.fullRehydrationInProcess) {
63
+ this.runSyncHooks('change', this.currentNavigation);
64
+ }
65
+ this.currentNavigation = null;
66
+ // add dehydrated route to tree to prevent its loading
67
+ if (!this.fullRehydrationInProcess) {
68
+ this.addRoute({
69
+ name: navigation.to.name,
70
+ path: navigation.to.path,
71
+ config: navigation.to.config,
72
+ // in case we have loaded page from some path-changing proxy
73
+ // save this actual path as alias
74
+ alias: url.pathname,
75
+ });
76
+ }
77
+ this.fullRehydrationInProcess = null;
78
+ }
79
+ resolveRoute(...options) {
80
+ const { url } = options[0];
81
+ // navigation for other hosts should be considered as external navigation
82
+ if (url && !isSameHost(url)) {
83
+ return;
84
+ }
85
+ return super.resolveRoute(...options);
86
+ }
87
+ async notfound(navigation) {
88
+ var _a, _b;
89
+ await super.notfound(navigation);
90
+ // in case we didn't find any matched route just force hard page navigation
91
+ const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
92
+ const nextUrl = navigation.url.href;
93
+ const isNoSpaNavigation = navigation.from && !navigation.to;
94
+ // prevent redirect cycle on the same page,
95
+ // except cases when we run no-spa navigations,
96
+ // because we need hard reload in this cases
97
+ if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
98
+ if (navigation.replace) {
99
+ window.location.replace(nextUrl);
100
+ }
101
+ else {
102
+ window.location.assign(nextUrl);
103
+ }
104
+ }
105
+ // prevent routing from any continues navigation returning promise which will be not resolved
106
+ return new Promise(() => { });
107
+ }
108
+ async block(navigation) {
109
+ return this.notfound(navigation);
110
+ }
111
+ async redirect(navigation, target) {
112
+ await super.redirect(navigation, target);
113
+ // on CSR fallback initialization, if we found a redirect,
114
+ // we need to make hard reload for prevent current page rendering
115
+ if (this.fullRehydrationInProcess) {
116
+ window.location.replace(target.url);
117
+ // prevent routing from any continues navigation returning promise which will be not resolved
118
+ return new Promise(() => { });
119
+ }
120
+ return this.internalNavigate({
121
+ ...target,
122
+ replace: target.replace || navigation.replace,
123
+ }, {
124
+ redirect: true,
125
+ });
126
+ }
127
+ }
128
+
129
+ export { ClientRouter };