navigation-stack 0.1.0

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 (113) hide show
  1. package/.babelrc.cjs +17 -0
  2. package/.eslintignore +8 -0
  3. package/.eslintrc.cjs +10 -0
  4. package/.github/workflows/main.yml +39 -0
  5. package/.yarn/install-state.gz +0 -0
  6. package/.yarnrc.yml +1 -0
  7. package/CODE_OF_CONDUCT.md +77 -0
  8. package/LICENSE +21 -0
  9. package/README.md +249 -0
  10. package/codecov.yml +1 -0
  11. package/karma.conf.cjs +63 -0
  12. package/lib/cjs/ActionTypes.js +14 -0
  13. package/lib/cjs/Actions.js +27 -0
  14. package/lib/cjs/LocationStateStorage.js +60 -0
  15. package/lib/cjs/addNavigationBlocker.js +7 -0
  16. package/lib/cjs/basePath.js +58 -0
  17. package/lib/cjs/createMiddlewares.js +43 -0
  18. package/lib/cjs/createSearchFromQuery.js +13 -0
  19. package/lib/cjs/environment/BrowserEnvironment.js +111 -0
  20. package/lib/cjs/environment/MemoryEnvironment.js +150 -0
  21. package/lib/cjs/environment/ServerEnvironment.js +53 -0
  22. package/lib/cjs/getLocationUrl.js +20 -0
  23. package/lib/cjs/index.js +30 -0
  24. package/lib/cjs/isPromise.js +9 -0
  25. package/lib/cjs/locationReducer.js +13 -0
  26. package/lib/cjs/middleware/createBasePathMiddleware.js +24 -0
  27. package/lib/cjs/middleware/createEnvironmentMiddleware.js +58 -0
  28. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +128 -0
  29. package/lib/cjs/middleware/createTransformLocationMiddleware.js +38 -0
  30. package/lib/cjs/middleware/navigationActionMiddleware.js +37 -0
  31. package/lib/cjs/middleware/normalizeInputLocationMiddleware.js +27 -0
  32. package/lib/cjs/navigationBlockers.js +146 -0
  33. package/lib/cjs/normalizeInputLocation.js +46 -0
  34. package/lib/cjs/onlyAllowedOnClientSide.js +10 -0
  35. package/lib/cjs/parseLocationUrl.js +39 -0
  36. package/lib/cjs/parseQueryFromSearch.js +16 -0
  37. package/lib/esm/ActionTypes.js +9 -0
  38. package/lib/esm/Actions.js +21 -0
  39. package/lib/esm/LocationStateStorage.js +53 -0
  40. package/lib/esm/addNavigationBlocker.js +2 -0
  41. package/lib/esm/basePath.js +53 -0
  42. package/lib/esm/createMiddlewares.js +37 -0
  43. package/lib/esm/createSearchFromQuery.js +8 -0
  44. package/lib/esm/environment/BrowserEnvironment.js +104 -0
  45. package/lib/esm/environment/MemoryEnvironment.js +143 -0
  46. package/lib/esm/environment/ServerEnvironment.js +46 -0
  47. package/lib/esm/getLocationUrl.js +15 -0
  48. package/lib/esm/index.js +12 -0
  49. package/lib/esm/isPromise.js +4 -0
  50. package/lib/esm/locationReducer.js +7 -0
  51. package/lib/esm/middleware/createBasePathMiddleware.js +19 -0
  52. package/lib/esm/middleware/createEnvironmentMiddleware.js +52 -0
  53. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +123 -0
  54. package/lib/esm/middleware/createTransformLocationMiddleware.js +33 -0
  55. package/lib/esm/middleware/navigationActionMiddleware.js +32 -0
  56. package/lib/esm/middleware/normalizeInputLocationMiddleware.js +22 -0
  57. package/lib/esm/navigationBlockers.js +138 -0
  58. package/lib/esm/normalizeInputLocation.js +41 -0
  59. package/lib/esm/onlyAllowedOnClientSide.js +5 -0
  60. package/lib/esm/parseLocationUrl.js +33 -0
  61. package/lib/esm/parseQueryFromSearch.js +11 -0
  62. package/lib/index.d.ts +301 -0
  63. package/package.json +100 -0
  64. package/renovate.json +3 -0
  65. package/src/ActionTypes.js +9 -0
  66. package/src/Actions.js +26 -0
  67. package/src/LocationStateStorage.js +59 -0
  68. package/src/addNavigationBlocker.js +2 -0
  69. package/src/basePath.js +65 -0
  70. package/src/createMiddlewares.js +41 -0
  71. package/src/createSearchFromQuery.js +9 -0
  72. package/src/environment/BrowserEnvironment.js +109 -0
  73. package/src/environment/MemoryEnvironment.js +151 -0
  74. package/src/environment/ServerEnvironment.js +54 -0
  75. package/src/getLocationUrl.js +12 -0
  76. package/src/index.js +12 -0
  77. package/src/isPromise.js +8 -0
  78. package/src/locationReducer.js +8 -0
  79. package/src/middleware/createBasePathMiddleware.js +20 -0
  80. package/src/middleware/createEnvironmentMiddleware.js +57 -0
  81. package/src/middleware/createNavigationBlockerMiddleware.js +128 -0
  82. package/src/middleware/createTransformLocationMiddleware.js +29 -0
  83. package/src/middleware/navigationActionMiddleware.js +27 -0
  84. package/src/middleware/normalizeInputLocationMiddleware.js +21 -0
  85. package/src/navigationBlockers.js +158 -0
  86. package/src/normalizeInputLocation.js +44 -0
  87. package/src/onlyAllowedOnClientSide.js +5 -0
  88. package/src/parseLocationUrl.js +40 -0
  89. package/src/parseQueryFromSearch.js +12 -0
  90. package/test/.eslintrc.cjs +17 -0
  91. package/test/Action.test.js +72 -0
  92. package/test/ActionTypes.test.js +13 -0
  93. package/test/LocationStateStorage.test.js +75 -0
  94. package/test/basePath.test.js +158 -0
  95. package/test/createMiddlewares.test.js +62 -0
  96. package/test/environment/BrowserEnvironment.test.js +165 -0
  97. package/test/environment/MemoryEnvironment.test.js +218 -0
  98. package/test/environment/ServerEnvironment.test.js +23 -0
  99. package/test/getLocationUrl.test.js +33 -0
  100. package/test/helpers.js +34 -0
  101. package/test/index.js +44 -0
  102. package/test/index.test.js +20 -0
  103. package/test/locationReducer.test.js +42 -0
  104. package/test/middleware/createBasePathMiddleware.test.js +67 -0
  105. package/test/middleware/createNavigationBlockerMiddleware.test.js +472 -0
  106. package/test/middleware/createTransformLocationMiddleware.test.js +44 -0
  107. package/test/middleware/navigationActionMiddleware.test.js +74 -0
  108. package/test/middleware/normalizeInputLocationMiddleware.test.js +62 -0
  109. package/test/normalizeInputLocation.test.js +81 -0
  110. package/test/parseLocationUrl.test.js +30 -0
  111. package/types/.eslintrc.cjs +3 -0
  112. package/types/index.d.ts +301 -0
  113. package/types/tsconfig.json +14 -0
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = createMiddlewares;
5
+ var _createBasePathMiddleware = _interopRequireDefault(require("./middleware/createBasePathMiddleware"));
6
+ var _createEnvironmentMiddleware = _interopRequireDefault(require("./middleware/createEnvironmentMiddleware"));
7
+ var _createNavigationBlockerMiddleware = _interopRequireDefault(require("./middleware/createNavigationBlockerMiddleware"));
8
+ var _navigationActionMiddleware = _interopRequireDefault(require("./middleware/navigationActionMiddleware"));
9
+ var _normalizeInputLocationMiddleware = _interopRequireDefault(require("./middleware/normalizeInputLocationMiddleware"));
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ function createMiddlewares(environment, options) {
12
+ // Allows temporarily ignoring certain environment location updates.
13
+ let shouldIgnoreEnvironmentLocationUpdates = false;
14
+ const ignoreEnvironmentLocationUpdates = func => {
15
+ shouldIgnoreEnvironmentLocationUpdates = true;
16
+ func();
17
+ shouldIgnoreEnvironmentLocationUpdates = false;
18
+ };
19
+ return [
20
+ // Validates that the action "payload" (input location) is a proper `NormalizedInputLocation`.
21
+ _normalizeInputLocationMiddleware.default,
22
+ // Transforms a "PUSH" / "REPLACE" action into a "NAVIGATE" action.
23
+ _navigationActionMiddleware.default,
24
+ // If a website is hosted under a certain path (`basePath`)
25
+ // then this middleware will automatically strip that starting segment from the `pathname` of `location`s.
26
+ (0, _createBasePathMiddleware.default)(options && options.basePath),
27
+ // Allows blocking navigation.
28
+ // Handles `NAVIGATE` actions dispatched by the application itself.
29
+ (0, _createNavigationBlockerMiddleware.default)(environment, {
30
+ ignoreEnvironmentLocationUpdates
31
+ }),
32
+ // This "middleware" performs the actual navigation according to the `environment` being used.
33
+ // For example, when `BrowserEnvironment` is used, it calls methods of the `history` object.
34
+ (0, _createEnvironmentMiddleware.default)(environment, {
35
+ shouldIgnoreEnvironmentLocationUpdates: () => shouldIgnoreEnvironmentLocationUpdates
36
+ }),
37
+ // Allows blocking navigation.
38
+ // Handles location `UPDATE` actions dispatched by the environment.
39
+ (0, _createNavigationBlockerMiddleware.default)(environment, {
40
+ ignoreEnvironmentLocationUpdates
41
+ })];
42
+ }
43
+ module.exports = exports.default;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = createSearchFromQuery;
5
+ var _queryString = require("query-string");
6
+ function createSearchFromQuery(query) {
7
+ const queryString = (0, _queryString.stringify)(query);
8
+ if (queryString) {
9
+ return `?${queryString}`;
10
+ }
11
+ return undefined;
12
+ }
13
+ module.exports = exports.default;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _getLocationUrl = _interopRequireDefault(require("../getLocationUrl"));
6
+ var _parseQueryFromSearch = _interopRequireDefault(require("../parseQueryFromSearch"));
7
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
+ class BrowserEnvironment {
9
+ constructor() {
10
+ this._keyPrefix = Math.random().toString(36).slice(2, 8);
11
+ this._keyIndex = 0;
12
+ this._index = null;
13
+ }
14
+ init() {
15
+ const {
16
+ pathname,
17
+ search,
18
+ hash
19
+ } = window.location;
20
+ const {
21
+ key,
22
+ index = 0,
23
+ state
24
+ } = window.history.state || {};
25
+ const delta = this._index != null ? index - this._index : 0;
26
+ this._index = index;
27
+ return {
28
+ action: 'POP',
29
+ pathname,
30
+ search,
31
+ query: search && (0, _parseQueryFromSearch.default)(search),
32
+ hash,
33
+ key,
34
+ index,
35
+ delta,
36
+ state
37
+ };
38
+ }
39
+
40
+ // Subscribes to changes in location,
41
+ // excluding ones that happened as a result of calling `.navigate()`.
42
+ subscribe(listener) {
43
+ const onPopState = () => {
44
+ listener(this.init());
45
+ };
46
+ window.addEventListener('popstate', onPopState);
47
+ return () => {
48
+ window.removeEventListener('popstate', onPopState);
49
+ };
50
+ }
51
+ navigate(location) {
52
+ const {
53
+ action,
54
+ state
55
+ } = location;
56
+ const push = action === 'PUSH';
57
+ if (!push && action !== 'REPLACE') {
58
+ throw Error(`Unrecognized browser environment action: ${action}`);
59
+ }
60
+ const delta = push ? 1 : 0;
61
+ const extraState = this._createExtraState(delta);
62
+ const browserState = Object.assign({
63
+ state
64
+ }, extraState);
65
+ const url = (0, _getLocationUrl.default)(location);
66
+ if (push) {
67
+ window.history.pushState(browserState, null, url);
68
+ } else {
69
+ window.history.replaceState(browserState, null, url);
70
+ }
71
+ return Object.assign({}, location, extraState, {
72
+ delta
73
+ });
74
+ }
75
+ shift(delta) {
76
+ window.history.go(delta);
77
+ }
78
+ addBeforeDestroyListener(onBeforeDestroy) {
79
+ const onBeforeUnload = event => {
80
+ if (onBeforeDestroy()) {
81
+ event.preventDefault();
82
+ }
83
+ };
84
+ window.addEventListener('beforeunload', onBeforeUnload);
85
+ return () => {
86
+ window.removeEventListener('beforeunload', onBeforeUnload);
87
+ };
88
+ }
89
+ _createExtraState(delta) {
90
+ const keyIndex = this._keyIndex++;
91
+ this._index += delta;
92
+ return {
93
+ key: `${this._keyPrefix}:${keyIndex.toString(36)}`,
94
+ index: this._index
95
+ };
96
+ }
97
+
98
+ // Returns either a `string` value or `null` if the key doesn't exist.
99
+ getState(key) {
100
+ // FYI: `sessionStorage` persists across page reloads.
101
+ return window.sessionStorage.getItem(key);
102
+ }
103
+ removeState(key) {
104
+ window.sessionStorage.removeItem(key);
105
+ }
106
+ setState(key, value) {
107
+ window.sessionStorage.setItem(key, value);
108
+ }
109
+ }
110
+ exports.default = BrowserEnvironment;
111
+ module.exports = exports.default;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _normalizeInputLocation = _interopRequireDefault(require("../normalizeInputLocation"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ class MemoryEnvironment {
8
+ constructor(initialLocation, {
9
+ save,
10
+ load
11
+ } = {}) {
12
+ this._save = save;
13
+ this._load = load;
14
+ const initialState = load ? this._loadState() : null;
15
+ if (initialState) {
16
+ this._stack = initialState.stack;
17
+ this._index = initialState.index;
18
+ this._state = initialState.state;
19
+ } else {
20
+ this._stack = [(0, _normalizeInputLocation.default)(initialLocation)];
21
+ this._index = 0;
22
+ this._state = {};
23
+ }
24
+ this._keyPrefix = Math.random().toString(36).slice(2, 8);
25
+ this._keyIndex = 0;
26
+ this._listener = null;
27
+ }
28
+ _loadState() {
29
+ try {
30
+ const {
31
+ stack,
32
+ index,
33
+ state
34
+ } = JSON.parse(this._load());
35
+
36
+ // Check that the stack and index at least seem reasonable before using
37
+ // them as state. This isn't foolproof, but it might prevent mistakes.
38
+ // Also perform a basic validation of `state`.
39
+ if (Array.isArray(stack) && typeof index === 'number' && stack[index] && typeof state === 'object' && state !== null) {
40
+ return {
41
+ stack,
42
+ index,
43
+ state
44
+ };
45
+ }
46
+ } catch (error) {} // eslint-disable-line no-empty
47
+
48
+ return null;
49
+ }
50
+ init(delta = 0) {
51
+ return Object.assign({}, this._stack[this._index], {
52
+ action: 'POP',
53
+ index: this._index,
54
+ delta
55
+ });
56
+ }
57
+ subscribe(listener) {
58
+ this._listener = listener;
59
+ return () => {
60
+ this._listener = null;
61
+ };
62
+ }
63
+ navigate(location) {
64
+ const {
65
+ action,
66
+ pathname,
67
+ search,
68
+ query,
69
+ hash,
70
+ state
71
+ } = location;
72
+ const push = action === 'PUSH';
73
+ if (!push && action !== 'REPLACE') throw Error(`Unrecognized browser environment action: ${action}`);
74
+ const delta = push ? 1 : 0;
75
+ this._index += delta;
76
+ const keyIndex = this._keyIndex++;
77
+ const key = `${this._keyPrefix}:${keyIndex.toString(36)}`;
78
+ this._stack[this._index] = {
79
+ pathname,
80
+ search,
81
+ query,
82
+ hash,
83
+ state,
84
+ key
85
+ };
86
+ if (push) {
87
+ this._stack.length = this._index + 1;
88
+ }
89
+ if (this._save) {
90
+ this._saveState();
91
+ }
92
+ return Object.assign({}, location, {
93
+ key,
94
+ index: this._index,
95
+ delta
96
+ });
97
+ }
98
+ shift(delta) {
99
+ const prevIndex = this._index;
100
+ this._index = Math.min(Math.max(this._index + delta, 0), this._stack.length - 1);
101
+ if (this._index === prevIndex) {
102
+ return;
103
+ }
104
+ if (this._save) {
105
+ this._saveState();
106
+ }
107
+ if (this._listener) {
108
+ this._listener(this.init(this._index - prevIndex));
109
+ }
110
+ }
111
+
112
+ // "Before destroy" listeners are currently ignored.
113
+ // If required, one could implement a `_destroy()` method
114
+ // and there check that the listeners actually do get called.
115
+ addBeforeDestroyListener(listener) {
116
+ return () => {
117
+ this._removeBeforeDestroyListener(listener);
118
+ };
119
+ }
120
+
121
+ // This method is used in tests.
122
+ _removeBeforeDestroyListener() {}
123
+ _saveState() {
124
+ try {
125
+ this._save(JSON.stringify({
126
+ stack: this._stack,
127
+ index: this._index,
128
+ state: this._state
129
+ }));
130
+ } catch (error) {} // eslint-disable-line no-empty
131
+ }
132
+
133
+ // Returns either a `string` value or `null` if the key doesn't exist.
134
+ getState(key) {
135
+ if (key in this._state) {
136
+ return this._state[key];
137
+ }
138
+ return null;
139
+ }
140
+ removeState(key) {
141
+ if (key in this._state) {
142
+ delete this._state[key];
143
+ }
144
+ }
145
+ setState(key, value) {
146
+ this._state[key] = value;
147
+ }
148
+ }
149
+ exports.default = MemoryEnvironment;
150
+ module.exports = exports.default;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _normalizeInputLocation = _interopRequireDefault(require("../normalizeInputLocation"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ function noop() {}
8
+ function serverSideNavigationNotPossible() {
9
+ throw new Error('Server-side navigation is not possible');
10
+ }
11
+ class ServerEnvironment {
12
+ constructor(initialLocation) {
13
+ this._location = (0, _normalizeInputLocation.default)(initialLocation);
14
+ }
15
+ init() {
16
+ return Object.assign({
17
+ action: 'POP'
18
+ }, this._location);
19
+ }
20
+ subscribe() {
21
+ // Server environment emits no location events.
22
+ return noop;
23
+ }
24
+
25
+ // Navigation methods are not implemented, because `ServerPEnvironment` instances
26
+ // cannot navigate.
27
+ navigate() {
28
+ serverSideNavigationNotPossible();
29
+ }
30
+
31
+ // Navigation methods are not implemented, because `ServerEnvironment` instances
32
+ // cannot navigate.
33
+ shift() {
34
+ serverSideNavigationNotPossible();
35
+ }
36
+
37
+ // "Before destroy" listeners are currently ignored.
38
+ // If required, one could implement a `_destroy()` method
39
+ // and there check that the listeners actually do get called.
40
+ addBeforeDestroyListener() {
41
+ return () => {};
42
+ }
43
+
44
+ // It doesn't seem to make any sense to store anything on server side.
45
+ // Hence, state management methods are "no op" stubs.
46
+ getState() {
47
+ return null;
48
+ }
49
+ removeState() {}
50
+ setState() {}
51
+ }
52
+ exports.default = ServerEnvironment;
53
+ module.exports = exports.default;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = getLocationUrl;
5
+ var _queryString = require("query-string");
6
+ function getLocationUrl({
7
+ pathname,
8
+ search,
9
+ query,
10
+ hash
11
+ }) {
12
+ if (!search && query) {
13
+ const queryString = (0, _queryString.stringify)(query);
14
+ if (queryString) {
15
+ search = `?${queryString}`;
16
+ }
17
+ }
18
+ return `${pathname}${search || ''}${hash || ''}`;
19
+ }
20
+ module.exports = exports.default;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.removeBasePath = exports.parseLocationUrl = exports.locationReducer = exports.getLocationUrl = exports.createMiddlewares = exports.addNavigationBlocker = exports.addBasePath = exports.ServerEnvironment = exports.MemoryEnvironment = exports.LocationStateStorage = exports.BrowserEnvironment = exports.Actions = exports.ActionTypes = void 0;
5
+ var _Actions = _interopRequireDefault(require("./Actions"));
6
+ exports.Actions = _Actions.default;
7
+ var _ActionTypes = _interopRequireDefault(require("./ActionTypes"));
8
+ exports.ActionTypes = _ActionTypes.default;
9
+ var _basePath = require("./basePath");
10
+ exports.addBasePath = _basePath.addBasePath;
11
+ exports.removeBasePath = _basePath.removeBasePath;
12
+ var _addNavigationBlocker = _interopRequireDefault(require("./addNavigationBlocker"));
13
+ exports.addNavigationBlocker = _addNavigationBlocker.default;
14
+ var _getLocationUrl = _interopRequireDefault(require("./getLocationUrl"));
15
+ exports.getLocationUrl = _getLocationUrl.default;
16
+ var _parseLocationUrl = _interopRequireDefault(require("./parseLocationUrl"));
17
+ exports.parseLocationUrl = _parseLocationUrl.default;
18
+ var _createMiddlewares = _interopRequireDefault(require("./createMiddlewares"));
19
+ exports.createMiddlewares = _createMiddlewares.default;
20
+ var _locationReducer = _interopRequireDefault(require("./locationReducer"));
21
+ exports.locationReducer = _locationReducer.default;
22
+ var _LocationStateStorage = _interopRequireDefault(require("./LocationStateStorage"));
23
+ exports.LocationStateStorage = _LocationStateStorage.default;
24
+ var _BrowserEnvironment = _interopRequireDefault(require("./environment/BrowserEnvironment"));
25
+ exports.BrowserEnvironment = _BrowserEnvironment.default;
26
+ var _MemoryEnvironment = _interopRequireDefault(require("./environment/MemoryEnvironment"));
27
+ exports.MemoryEnvironment = _MemoryEnvironment.default;
28
+ var _ServerEnvironment = _interopRequireDefault(require("./environment/ServerEnvironment"));
29
+ exports.ServerEnvironment = _ServerEnvironment.default;
30
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = isPromise;
5
+ function isPromise(anything) {
6
+ // Weirdly, `typeof null` is "object".
7
+ return typeof anything === 'object' && anything !== null && typeof anything.then === 'function';
8
+ }
9
+ module.exports = exports.default;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = locationReducer;
5
+ var _ActionTypes = _interopRequireDefault(require("./ActionTypes"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ function locationReducer(state, action) {
8
+ if (action.type === _ActionTypes.default.UPDATE) {
9
+ return action.payload;
10
+ }
11
+ return state;
12
+ }
13
+ module.exports = exports.default;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = createBasePathMiddleware;
5
+ var _basePath = require("../basePath");
6
+ var _createTransformLocationMiddleware = _interopRequireDefault(require("./createTransformLocationMiddleware"));
7
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
+ // Creates a "middleware" that, when a website is hosted under a certain path (`basePath`),
9
+ // automatically strips that starting segment from the `pathname` of `location`s.
10
+ function createBasePathMiddleware(basePath) {
11
+ return (0, _createTransformLocationMiddleware.default)({
12
+ // Transforms input `Location`:
13
+ // prepends `basePath` to the URL.
14
+ transformInputLocation: location => {
15
+ return (0, _basePath.addBasePath)(location, basePath);
16
+ },
17
+ // Transforms environment `Location` object:
18
+ // removes `basePath` from the URL.
19
+ transformEnvironmentLocation: location => {
20
+ return (0, _basePath.removeBasePath)(location, basePath);
21
+ }
22
+ });
23
+ }
24
+ module.exports = exports.default;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = createEnvironmentMiddleware;
5
+ var _ActionTypes = _interopRequireDefault(require("../ActionTypes"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ function updateLocation(location) {
8
+ return {
9
+ type: _ActionTypes.default.UPDATE,
10
+ payload: location
11
+ };
12
+ }
13
+
14
+ // Creates a "middleware" that performs the actual navigation according to the `environment` being used.
15
+ // For example, when `BrowserProtocol` is used, it calls methods of the `history` object.
16
+ // A better name for this function could be something like `createProtocolMiddleware(environment)`.
17
+ // A better name for "environment" could be something like "environment".
18
+ function createEnvironmentMiddleware(environment, {
19
+ shouldIgnoreEnvironmentLocationUpdates
20
+ }) {
21
+ return function environmentMiddleware() {
22
+ return next => {
23
+ // Whenever browser location changes,
24
+ // perform the same changes with the internal `location` object.
25
+ const unsubscribe = environment.subscribe(location => {
26
+ if (!shouldIgnoreEnvironmentLocationUpdates()) {
27
+ next(updateLocation(location));
28
+ }
29
+ });
30
+ return action => {
31
+ const {
32
+ type,
33
+ payload
34
+ } = action;
35
+ switch (type) {
36
+ case _ActionTypes.default.INIT:
37
+ return next(updateLocation(environment.init()));
38
+ case _ActionTypes.default.NAVIGATE:
39
+ // `environment.navigate()` doesn't trigger the `subscribe()` listener.
40
+ return next(updateLocation(environment.navigate(payload)));
41
+ case _ActionTypes.default.SHIFT:
42
+ // `shift()` will trigger the `subscribe()` listener,
43
+ // which will call `updateLocation()`.
44
+ environment.shift(payload);
45
+ // eslint-disable-next-line consistent-return
46
+ return;
47
+ case _ActionTypes.default.DISPOSE:
48
+ unsubscribe();
49
+ // eslint-disable-next-line consistent-return
50
+ return;
51
+ default:
52
+ return next(action);
53
+ }
54
+ };
55
+ };
56
+ };
57
+ }
58
+ module.exports = exports.default;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = createNavigationBlockerMiddleware;
5
+ var _ActionTypes = _interopRequireDefault(require("../ActionTypes"));
6
+ var _isPromise = _interopRequireDefault(require("../isPromise"));
7
+ var _navigationBlockers = require("../navigationBlockers");
8
+ var _onlyAllowedOnClientSide = _interopRequireDefault(require("../onlyAllowedOnClientSide"));
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ // Creates a "middleware" that applies navigation blockers.
11
+ function createNavigationBlockerMiddleware(environment, {
12
+ ignoreEnvironmentLocationUpdates
13
+ }) {
14
+ // A "dummy" initial value that will be ignored.
15
+ let navigationBlockersEvaluationStatus = {
16
+ cancelled: false
17
+ };
18
+ function createNavigationBlockersEvaluationStatus() {
19
+ (0, _onlyAllowedOnClientSide.default)();
20
+ navigationBlockersEvaluationStatus.cancelled = true;
21
+ navigationBlockersEvaluationStatus = {
22
+ cancelled: false
23
+ };
24
+ return navigationBlockersEvaluationStatus;
25
+ }
26
+ return function navigationListenerMiddleware() {
27
+ return next => action => {
28
+ const {
29
+ type,
30
+ payload
31
+ } = action;
32
+
33
+ // Declaring `result` variable here fixes ESLint error:
34
+ // "Unexpected lexical declaration in case block".
35
+ let result;
36
+ switch (type) {
37
+ // Prevent or allow navigation that was initiated by the application.
38
+ case _ActionTypes.default.NAVIGATE:
39
+ // `resultValue` variable name works around a stupid javascript error:
40
+ // "Cannot redeclare block-scoped variable 'result'".
41
+ result = (0, _navigationBlockers.runNavigationBlockers)((0, _navigationBlockers.getNavigationBlockers)(), payload);
42
+ if ((0, _isPromise.default)(result)) {
43
+ const status = createNavigationBlockersEvaluationStatus();
44
+ // eslint-disable-next-line consistent-return
45
+ result.then(resultValue => {
46
+ if (!status.cancelled) {
47
+ if (!resultValue) {
48
+ return next(action);
49
+ }
50
+ }
51
+ });
52
+ } else if (!result) {
53
+ return next(action);
54
+ }
55
+ // eslint-disable-next-line consistent-return
56
+ return;
57
+
58
+ // Prevent or allow navigation that was initiated by the environment itself.
59
+ // For example, in a web browser, it could happen when the user clicks the "Back"/"Forward" buttons.
60
+ //
61
+ // In this scenario, the web browser is already at the new location, i.e. the navigation has already happened.
62
+ // It could be "prevented" by rewinding back to the previous location.
63
+ //
64
+ case _ActionTypes.default.UPDATE:
65
+ // If no navigation blockers to run, don't do anything.
66
+ if ((0, _navigationBlockers.getNavigationBlockers)().length === 0) {
67
+ return next(action);
68
+ }
69
+
70
+ // If it was the initial page load or a redirect,
71
+ // it's not really a navigation that could be rolled back.
72
+ if (payload.delta === 0) {
73
+ return next(action);
74
+ }
75
+
76
+ // It's not really possible for a location to not have a `delta` property in a web browser environment.
77
+ // So this case is not something that's supposed to happen in real life.
78
+ // Rather, it's a guard against an unsupported or incorrectly-implemented environment or something like that.
79
+ // If there's no `delta` property on the location, it means that the previous location can't be rewound to,
80
+ // so it can't really "prevent" the navigation that has just happened.
81
+ if (payload.delta === null) {
82
+ return next(action);
83
+ }
84
+ result = (0, _navigationBlockers.runNavigationBlockers)((0, _navigationBlockers.getNavigationBlockers)(), payload);
85
+ if ((0, _isPromise.default)(result)) {
86
+ const status = createNavigationBlockersEvaluationStatus();
87
+
88
+ // While location blockers are running, rewind to the previous location.
89
+ ignoreEnvironmentLocationUpdates(() => {
90
+ environment.shift(-payload.delta);
91
+ });
92
+ result.then(promiseResult => {
93
+ if (promiseResult) {
94
+ // Navigation blocked.
95
+ // Already rewound to a previous location.
96
+ } else if (!status.cancelled) {
97
+ // Navigation not blocked.
98
+ // Rewind back to the new location.
99
+ ignoreEnvironmentLocationUpdates(() => {
100
+ environment.shift(payload.delta);
101
+ });
102
+ // Update the location.
103
+ next(action);
104
+ }
105
+ });
106
+ } else if (result) {
107
+ // Prevent the navigation: rewind to the previous location.
108
+ ignoreEnvironmentLocationUpdates(() => {
109
+ environment.shift(-payload.delta);
110
+ });
111
+ } else {
112
+ // Update the location.
113
+ return next(action);
114
+ }
115
+ // eslint-disable-next-line consistent-return
116
+ return;
117
+
118
+ // Remove any navigation blockers on `DISPOSE` event.
119
+ case _ActionTypes.default.DISPOSE:
120
+ (0, _navigationBlockers.removeAllNavigationBlockers)();
121
+ return next(action);
122
+ default:
123
+ return next(action);
124
+ }
125
+ };
126
+ };
127
+ }
128
+ module.exports = exports.default;