navigation-stack 0.1.3 → 0.2.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 (77) hide show
  1. package/.github/workflows/main.yml +39 -39
  2. package/README.md +128 -27
  3. package/lib/cjs/{LocationStateStorage.js → LocationDataStorage.js} +5 -5
  4. package/lib/cjs/addBeforeLocationChangeListener.js +7 -0
  5. package/lib/cjs/beforeLocationChangeListeners.js +51 -0
  6. package/lib/cjs/createMiddlewares.js +21 -17
  7. package/lib/cjs/index.js +9 -9
  8. package/lib/cjs/middleware/createBasePathMiddleware.js +2 -2
  9. package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +39 -0
  10. package/lib/cjs/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  11. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +62 -29
  12. package/lib/cjs/middleware/createTransformLocationMiddleware.js +2 -2
  13. package/lib/cjs/navigationBlockers.js +55 -47
  14. package/lib/cjs/normalizeInputLocation.js +1 -0
  15. package/lib/cjs/parseLocationUrl.js +2 -0
  16. package/lib/cjs/parseQueryFromSearch.js +1 -1
  17. package/lib/cjs/session/BrowserSession.js +229 -0
  18. package/lib/cjs/session/MemorySession.js +223 -0
  19. package/lib/cjs/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -16
  20. package/lib/esm/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  21. package/lib/esm/addBeforeLocationChangeListener.js +2 -0
  22. package/lib/esm/beforeLocationChangeListeners.js +44 -0
  23. package/lib/esm/createMiddlewares.js +21 -17
  24. package/lib/esm/index.js +4 -4
  25. package/lib/esm/middleware/createBasePathMiddleware.js +2 -2
  26. package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +34 -0
  27. package/lib/esm/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +11 -13
  28. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +62 -29
  29. package/lib/esm/middleware/createTransformLocationMiddleware.js +2 -2
  30. package/lib/esm/navigationBlockers.js +55 -47
  31. package/lib/esm/normalizeInputLocation.js +1 -0
  32. package/lib/esm/parseLocationUrl.js +2 -0
  33. package/lib/esm/parseQueryFromSearch.js +1 -1
  34. package/lib/esm/session/BrowserSession.js +223 -0
  35. package/lib/esm/session/MemorySession.js +217 -0
  36. package/lib/esm/{environment/ServerEnvironment.js → session/ServerSession.js} +27 -15
  37. package/lib/index.d.ts +64 -59
  38. package/package.json +4 -4
  39. package/src/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  40. package/src/addBeforeLocationChangeListener.js +2 -0
  41. package/src/beforeLocationChangeListeners.js +54 -0
  42. package/src/createMiddlewares.js +21 -17
  43. package/src/index.js +4 -4
  44. package/src/middleware/createBasePathMiddleware.js +2 -2
  45. package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +40 -0
  46. package/src/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  47. package/src/middleware/createNavigationBlockerMiddleware.js +68 -28
  48. package/src/middleware/createTransformLocationMiddleware.js +2 -2
  49. package/src/navigationBlockers.js +68 -49
  50. package/src/normalizeInputLocation.js +1 -0
  51. package/src/parseLocationUrl.js +2 -0
  52. package/src/parseQueryFromSearch.js +1 -1
  53. package/src/session/BrowserSession.js +225 -0
  54. package/src/session/MemorySession.js +219 -0
  55. package/src/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -15
  56. package/test/{LocationStateStorage.test.js → LocationDataStorage.test.js} +6 -6
  57. package/test/createMiddlewares.test.js +2 -2
  58. package/test/helpers.js +1 -1
  59. package/test/index.test.js +3 -3
  60. package/test/middleware/createBasePathMiddleware.test.js +7 -7
  61. package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +141 -0
  62. package/test/middleware/createNavigationBlockerMiddleware.test.js +96 -97
  63. package/test/middleware/createTransformLocationMiddleware.test.js +1 -1
  64. package/test/normalizeInputLocation.test.js +3 -0
  65. package/test/parseLocationUrl.test.js +2 -0
  66. package/test/{environment/BrowserEnvironment.test.js → session/BrowserSession.test.js} +35 -18
  67. package/test/session/MemorySession.test.js +244 -0
  68. package/test/session/ServerSession.test.js +23 -0
  69. package/types/index.d.ts +64 -59
  70. package/lib/cjs/environment/BrowserEnvironment.js +0 -111
  71. package/lib/cjs/environment/MemoryEnvironment.js +0 -150
  72. package/lib/esm/environment/BrowserEnvironment.js +0 -104
  73. package/lib/esm/environment/MemoryEnvironment.js +0 -143
  74. package/src/environment/BrowserEnvironment.js +0 -109
  75. package/src/environment/MemoryEnvironment.js +0 -151
  76. package/test/environment/MemoryEnvironment.test.js +0 -218
  77. package/test/environment/ServerEnvironment.test.js +0 -23
@@ -0,0 +1,223 @@
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
+ /* eslint-disable max-classes-per-file */
8
+
9
+ // eslint-disable-next-line no-underscore-dangle
10
+ function _loadState(load, isValidLoadedData) {
11
+ try {
12
+ const data = JSON.parse(load());
13
+
14
+ // Check that the stack and index at least seem reasonable before using
15
+ // them as state. This isn't foolproof, but it might prevent mistakes.
16
+ // Also perform a basic validation of `state`.
17
+ if (isValidLoadedData(data)) {
18
+ return data;
19
+ }
20
+ } catch (error) {} // eslint-disable-line no-empty
21
+
22
+ return null;
23
+ }
24
+
25
+ // eslint-disable-next-line no-underscore-dangle
26
+ function _saveState(save, data) {
27
+ try {
28
+ save(JSON.stringify(data));
29
+ } catch (error) {} // eslint-disable-line no-empty
30
+ }
31
+ class MemoryNavigation {
32
+ constructor(initialLocation, {
33
+ save,
34
+ load
35
+ } = {}) {
36
+ this._save = save;
37
+ this._keyPrefix = Date.now().toString(36);
38
+ this._keyIndex = 0;
39
+ this._subscriptionListener = null;
40
+ const initialState = load ? _loadState(load, this._isValidLoadedData) : null;
41
+ if (initialState) {
42
+ this._stack = initialState.stack;
43
+ this._index = initialState.index;
44
+ } else {
45
+ this._stack = [Object.assign({}, (0, _normalizeInputLocation.default)(initialLocation), {
46
+ key: this._getNextKey()
47
+ })];
48
+ this._index = 0;
49
+ }
50
+ }
51
+ _isValidLoadedData({
52
+ stack,
53
+ index
54
+ }) {
55
+ // Check that the `stack` and `index` at least seem reasonable before using them.
56
+ // This isn't foolproof, but it might prevent mistakes.
57
+ return Array.isArray(stack) && typeof index === 'number' && stack[index];
58
+ }
59
+ init() {
60
+ return this._createLocationObject({
61
+ action: 'INIT',
62
+ delta: 0
63
+ });
64
+ }
65
+ subscribe(listener) {
66
+ this._subscriptionListener = listener;
67
+ return () => {
68
+ this._subscriptionListener = null;
69
+ };
70
+ }
71
+ navigate(location) {
72
+ const {
73
+ action,
74
+ pathname,
75
+ search,
76
+ query,
77
+ hash,
78
+ state
79
+ } = location;
80
+ if (action !== 'PUSH' && action !== 'REPLACE') {
81
+ throw Error(`Unrecognized browser session action: ${action}`);
82
+ }
83
+ const delta = action === 'PUSH' ? 1 : 0;
84
+ this._index += delta;
85
+ const key = this._getNextKey();
86
+ this._stack[this._index] = {
87
+ pathname,
88
+ search,
89
+ query,
90
+ hash,
91
+ state,
92
+ key
93
+ };
94
+ if (action === 'PUSH') {
95
+ this._stack.length = this._index + 1;
96
+ }
97
+ if (this._save) {
98
+ _saveState(this._save, {
99
+ stack: this._stack,
100
+ index: this._index
101
+ });
102
+ }
103
+ return Object.assign({}, location, {
104
+ key,
105
+ index: this._index,
106
+ delta
107
+ });
108
+ }
109
+ shift(delta) {
110
+ const prevIndex = this._index;
111
+ this._index = Math.min(Math.max(this._index + delta, 0), this._stack.length - 1);
112
+ if (this._index === prevIndex) {
113
+ return;
114
+ }
115
+ if (this._save) {
116
+ _saveState(this._save, {
117
+ stack: this._stack,
118
+ index: this._index
119
+ });
120
+ }
121
+ if (this._subscriptionListener) {
122
+ this._subscriptionListener(this._createLocationObject({
123
+ action: 'POP',
124
+ delta: this._index - prevIndex
125
+ }));
126
+ }
127
+ }
128
+ _getNextKey() {
129
+ const key = `${this._keyPrefix}.${this._keyIndex.toString(36)}`;
130
+ this._keyIndex++;
131
+ return key;
132
+ }
133
+ _createLocationObject({
134
+ action,
135
+ delta
136
+ }) {
137
+ return Object.assign({}, this._stack[this._index], {
138
+ action,
139
+ index: this._index,
140
+ delta
141
+ });
142
+ }
143
+ }
144
+ class MemoryDataStorage {
145
+ constructor({
146
+ load,
147
+ save
148
+ } = {}) {
149
+ this._save = save;
150
+ const initialState = load ? _loadState(load, this._isValidLoadedData) : null;
151
+ if (initialState) {
152
+ this._state = initialState.state;
153
+ } else {
154
+ this._state = {};
155
+ }
156
+ }
157
+
158
+ // Returns either a `string` value or `null` if the key doesn't exist.
159
+ get(key) {
160
+ if (key in this._state) {
161
+ return this._state[key];
162
+ }
163
+ return null;
164
+ }
165
+ remove(key) {
166
+ if (key in this._state) {
167
+ delete this._state[key];
168
+ }
169
+ if (this._save) {
170
+ _saveState(this._save, {
171
+ state: this._state
172
+ });
173
+ }
174
+ }
175
+ set(key, value) {
176
+ this._state[key] = value;
177
+ if (this._save) {
178
+ _saveState(this._save, {
179
+ state: this._state
180
+ });
181
+ }
182
+ }
183
+ _isValidLoadedData({
184
+ state
185
+ }) {
186
+ // Perform a basic validation of `state`.
187
+ return typeof state === 'object' && state !== null;
188
+ }
189
+ }
190
+ function createNestedStateSaveLoadFunctions({
191
+ save,
192
+ load
193
+ }, key) {
194
+ return {
195
+ save: save ? data => save(key, data) : undefined,
196
+ load: load ? () => load(key) : undefined
197
+ };
198
+ }
199
+ class MemorySession {
200
+ constructor(initialLocation, {
201
+ save,
202
+ load
203
+ } = {}) {
204
+ this.navigation = new MemoryNavigation(initialLocation, createNestedStateSaveLoadFunctions({
205
+ save,
206
+ load
207
+ }, 'navigation'));
208
+ this.dataStorage = new MemoryDataStorage(createNestedStateSaveLoadFunctions({
209
+ save,
210
+ load
211
+ }, 'dataStorage'));
212
+ }
213
+
214
+ // "Before destroy" listeners are currently ignored.
215
+ // If required, one could implement a `_destroy()` method
216
+ // and there check that the listeners actually do get called.
217
+ // eslint-disable-next-line no-unused-vars
218
+ addBeforeDestroyListener(listener) {
219
+ return () => {};
220
+ }
221
+ }
222
+ exports.default = MemorySession;
223
+ module.exports = exports.default;
@@ -4,50 +4,62 @@ exports.__esModule = true;
4
4
  exports.default = void 0;
5
5
  var _normalizeInputLocation = _interopRequireDefault(require("../normalizeInputLocation"));
6
6
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ /* eslint-disable max-classes-per-file */
8
+
7
9
  function noop() {}
8
10
  function serverSideNavigationNotPossible() {
9
11
  throw new Error('Server-side navigation is not possible');
10
12
  }
11
- class ServerEnvironment {
13
+ class ServerNavigation {
12
14
  constructor(initialLocation) {
13
15
  this._location = (0, _normalizeInputLocation.default)(initialLocation);
14
16
  }
15
17
  init() {
16
18
  return Object.assign({
17
- action: 'POP'
18
- }, this._location);
19
+ action: 'INIT'
20
+ }, this._location, {
21
+ index: 0,
22
+ key: '0'
23
+ });
19
24
  }
20
25
  subscribe() {
21
- // Server environment emits no location events.
26
+ // Server-side environment emits no location subscription events.
22
27
  return noop;
23
28
  }
24
29
 
25
- // Navigation methods are not implemented, because `ServerPEnvironment` instances
30
+ // Navigation methods are not implemented, because `ServerSession` instances
26
31
  // cannot navigate.
27
32
  navigate() {
28
33
  serverSideNavigationNotPossible();
29
34
  }
30
35
 
31
- // Navigation methods are not implemented, because `ServerEnvironment` instances
36
+ // Navigation methods are not implemented, because `ServerSession` instances
32
37
  // cannot navigate.
33
38
  shift() {
34
39
  serverSideNavigationNotPossible();
35
40
  }
41
+ }
42
+ class ServerDataStorage {
43
+ // It doesn't seem to make any sense to store anything on server side.
44
+ // Hence, state management methods are "no op" stubs.
45
+ get() {
46
+ return null;
47
+ }
48
+ remove() {}
49
+ set() {}
50
+ }
51
+ class ServerSession {
52
+ constructor(initialLocation) {
53
+ this.navigation = new ServerNavigation(initialLocation);
54
+ this.dataStorage = new ServerDataStorage();
55
+ }
36
56
 
37
57
  // "Before destroy" listeners are currently ignored.
38
58
  // If required, one could implement a `_destroy()` method
39
59
  // and there check that the listeners actually do get called.
40
60
  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;
61
+ return noop;
48
62
  }
49
- removeState() {}
50
- setState() {}
51
63
  }
52
- exports.default = ServerEnvironment;
64
+ exports.default = ServerSession;
53
65
  module.exports = exports.default;
@@ -1,5 +1,5 @@
1
1
  import getLocationUrl from './getLocationUrl';
2
- export default class LocationStateStorage {
2
+ export default class LocationDataStorage {
3
3
  constructor(environment, {
4
4
  namespace
5
5
  } = {}) {
@@ -10,7 +10,7 @@ export default class LocationStateStorage {
10
10
  get(location, key) {
11
11
  const stateKey = this._getStateKey(location, key);
12
12
  try {
13
- const value = this._environment.getState(stateKey);
13
+ const value = this._environment.dataStorage.get(stateKey);
14
14
  // === null is probably sufficient.
15
15
  if (value === null) {
16
16
  return undefined;
@@ -28,7 +28,7 @@ export default class LocationStateStorage {
28
28
  const stateKey = this._getStateKey(location, key);
29
29
  if (value === undefined) {
30
30
  try {
31
- this._environment.removeState(stateKey);
31
+ this._environment.dataStorage.remove(stateKey);
32
32
  } catch (error) {
33
33
  // No need to handle errors here.
34
34
  }
@@ -39,7 +39,7 @@ export default class LocationStateStorage {
39
39
  // value here is provided by the caller of this method.
40
40
  const valueString = JSON.stringify(value);
41
41
  try {
42
- this._environment.setState(stateKey, valueString);
42
+ this._environment.dataStorage.set(stateKey, valueString);
43
43
  } catch (error) {
44
44
  // No need to handle errors here either. If it didn't work, it didn't
45
45
  // work. We make no guarantees about actually saving the value.
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line no-restricted-exports
2
+ export { addBeforeLocationChangeListener as default } from './beforeLocationChangeListeners';
@@ -0,0 +1,44 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+
3
+ export function getBeforeLocationChangeListeners(session) {
4
+ return session._beforeLocationChangeListenersList || [];
5
+ }
6
+ function addBeforeLocationChangeListenerToTheList(listener, session) {
7
+ if (!session._beforeLocationChangeListenersList) {
8
+ session._beforeLocationChangeListenersList = [];
9
+ }
10
+ session._beforeLocationChangeListenersList.push(listener);
11
+ }
12
+ function removeBeforeLocationChangeListenerFromTheList(listener, session) {
13
+ if (session._beforeLocationChangeListenersList) {
14
+ session._beforeLocationChangeListenersList = session._beforeLocationChangeListenersList.filter(_ => _ !== listener);
15
+ }
16
+ }
17
+ export function removeAllBeforeLocationChangeListeners(session) {
18
+ session._beforeLocationChangeListenersList = [];
19
+ }
20
+
21
+ // Runs the `listener` while ignoring any errors that might be thrown by it.
22
+ function runBeforeLocationChangeListener(listener, location) {
23
+ try {
24
+ listener(location);
25
+ } catch (error) {
26
+ // eslint-disable-next-line no-console
27
+ console.warn(`Ignoring before location change listener \`${listener.name}\` that failed with \`${error}\`.`);
28
+ // eslint-disable-next-line no-console
29
+ console.error(error);
30
+ }
31
+ }
32
+
33
+ // Runs all listeners in order.
34
+ export function runBeforeLocationChangeListeners(navigationListeners, toLocation) {
35
+ for (const listener of navigationListeners) {
36
+ runBeforeLocationChangeListener(listener, toLocation);
37
+ }
38
+ }
39
+ export function addBeforeLocationChangeListener(session, listener) {
40
+ addBeforeLocationChangeListenerToTheList(listener, session);
41
+ return () => {
42
+ removeBeforeLocationChangeListenerFromTheList(listener, session);
43
+ };
44
+ }
@@ -1,15 +1,16 @@
1
1
  import createBasePathMiddleware from './middleware/createBasePathMiddleware';
2
- import createEnvironmentMiddleware from './middleware/createEnvironmentMiddleware';
2
+ import createBeforeLocationChangeListenerMiddleware from './middleware/createBeforeLocationChangeListenerMiddleware';
3
+ import createLocationMiddleware from './middleware/createLocationMiddleware';
3
4
  import createNavigationBlockerMiddleware from './middleware/createNavigationBlockerMiddleware';
4
5
  import navigationActionMiddleware from './middleware/navigationActionMiddleware';
5
6
  import normalizeInputLocationMiddleware from './middleware/normalizeInputLocationMiddleware';
6
- export default function createMiddlewares(environment, options) {
7
- // Allows temporarily ignoring certain environment location updates.
8
- let shouldIgnoreEnvironmentLocationUpdates = false;
9
- const ignoreEnvironmentLocationUpdates = func => {
10
- shouldIgnoreEnvironmentLocationUpdates = true;
7
+ export default function createMiddlewares(session, options) {
8
+ // Allows temporarily ignoring location update events.
9
+ let shouldIgnoreLocationSubscriptionEvents = false;
10
+ const ignoreLocationSubscriptionEvents = func => {
11
+ shouldIgnoreLocationSubscriptionEvents = true;
11
12
  func();
12
- shouldIgnoreEnvironmentLocationUpdates = false;
13
+ shouldIgnoreLocationSubscriptionEvents = false;
13
14
  };
14
15
  return [
15
16
  // Validates that the action "payload" (input location) is a proper `NormalizedInputLocation`.
@@ -21,17 +22,20 @@ export default function createMiddlewares(environment, options) {
21
22
  createBasePathMiddleware(options && options.basePath),
22
23
  // Allows blocking navigation.
23
24
  // Handles `NAVIGATE` actions dispatched by the application itself.
24
- createNavigationBlockerMiddleware(environment, {
25
- ignoreEnvironmentLocationUpdates
25
+ createNavigationBlockerMiddleware(session, {
26
+ ignoreLocationSubscriptionEvents
26
27
  }),
27
- // This "middleware" performs the actual navigation according to the `environment` being used.
28
- // For example, when `BrowserEnvironment` is used, it calls methods of the `history` object.
29
- createEnvironmentMiddleware(environment, {
30
- shouldIgnoreEnvironmentLocationUpdates: () => shouldIgnoreEnvironmentLocationUpdates
28
+ // This "middleware" performs the actual navigation according to the `session` being used.
29
+ // For example, when `BrowserSession` is used, it calls methods of the `history` object.
30
+ createLocationMiddleware(session, {
31
+ shouldIgnoreLocationSubscriptionEvents: () => shouldIgnoreLocationSubscriptionEvents
31
32
  }),
32
33
  // Allows blocking navigation.
33
- // Handles location `UPDATE` actions dispatched by the environment.
34
- createNavigationBlockerMiddleware(environment, {
35
- ignoreEnvironmentLocationUpdates
36
- })];
34
+ // Handles location `UPDATE` actions dispatched in response to location update events.
35
+ createNavigationBlockerMiddleware(session, {
36
+ ignoreLocationSubscriptionEvents
37
+ }),
38
+ // Allows subscribing to upcoming location changes
39
+ // before those changes are applied in the `location` object in the state.
40
+ createBeforeLocationChangeListenerMiddleware(session)];
37
41
  }
package/lib/esm/index.js CHANGED
@@ -6,7 +6,7 @@ export { default as getLocationUrl } from './getLocationUrl';
6
6
  export { default as parseLocationUrl } from './parseLocationUrl';
7
7
  export { default as createMiddlewares } from './createMiddlewares';
8
8
  export { default as locationReducer } from './locationReducer';
9
- export { default as LocationStateStorage } from './LocationStateStorage';
10
- export { default as BrowserEnvironment } from './environment/BrowserEnvironment';
11
- export { default as MemoryEnvironment } from './environment/MemoryEnvironment';
12
- export { default as ServerEnvironment } from './environment/ServerEnvironment';
9
+ export { default as LocationDataStorage } from './LocationDataStorage';
10
+ export { default as BrowserSession } from './session/BrowserSession';
11
+ export { default as MemorySession } from './session/MemorySession';
12
+ export { default as ServerSession } from './session/ServerSession';
@@ -10,9 +10,9 @@ export default function createBasePathMiddleware(basePath) {
10
10
  transformInputLocation: location => {
11
11
  return addBasePath(location, basePath);
12
12
  },
13
- // Transforms environment `Location` object:
13
+ // Transforms subscription `Location` object:
14
14
  // removes `basePath` from the URL.
15
- transformEnvironmentLocation: location => {
15
+ transformSubscriptionLocation: location => {
16
16
  return removeBasePath(location, basePath);
17
17
  }
18
18
  });
@@ -0,0 +1,34 @@
1
+ import ActionTypes from '../ActionTypes';
2
+ import { getBeforeLocationChangeListeners, removeAllBeforeLocationChangeListeners, runBeforeLocationChangeListeners } from '../beforeLocationChangeListeners';
3
+
4
+ // Creates a "middleware" that calls upcoming navigation listeners.
5
+ export default function createBeforeLocationChangeListenerMiddleware(session) {
6
+ return function navigationListenerMiddleware() {
7
+ return next => action => {
8
+ const {
9
+ type,
10
+ payload
11
+ } = action;
12
+ switch (type) {
13
+ // Trigger navigation listeners before the `location` has been updated in Redux state.
14
+ // It doesn't matter that the new location URL has already been updated in the web browser's
15
+ // address bar, or that the web browser's history has already switched to the new locaiton.
16
+ // From the application's point of view, all of that doesn't matter and even doesn't exist.
17
+ // All that exists from the application's point of view is the `location` object in the Redux state.
18
+ // Until the `location` object in the Redux state is updated, the old page is still rendered.
19
+ // The appliation is only concerned with the updates of the `location` object in the Redux state
20
+ // and completely ignores any updates to the URL in the web browser's address bar.
21
+ case ActionTypes.UPDATE:
22
+ runBeforeLocationChangeListeners(getBeforeLocationChangeListeners(session), payload);
23
+ return next(action);
24
+
25
+ // Remove any navigation listeners on `DISPOSE` event.
26
+ case ActionTypes.DISPOSE:
27
+ removeAllBeforeLocationChangeListeners(session);
28
+ return next(action);
29
+ default:
30
+ return next(action);
31
+ }
32
+ };
33
+ };
34
+ }
@@ -6,19 +6,17 @@ function updateLocation(location) {
6
6
  };
7
7
  }
8
8
 
9
- // Creates a "middleware" that performs the actual navigation according to the `environment` being used.
10
- // For example, when `BrowserProtocol` is used, it calls methods of the `history` object.
11
- // A better name for this function could be something like `createProtocolMiddleware(environment)`.
12
- // A better name for "environment" could be something like "environment".
13
- export default function createEnvironmentMiddleware(environment, {
14
- shouldIgnoreEnvironmentLocationUpdates
9
+ // Creates a "middleware" that performs the actual navigation according to the `session` being used.
10
+ // For example, when `BrowserSession` is used, it calls methods of the `window.history` object.
11
+ export default function createLocationMiddleware(session, {
12
+ shouldIgnoreLocationSubscriptionEvents
15
13
  }) {
16
- return function environmentMiddleware() {
14
+ return function locationMiddleware() {
17
15
  return next => {
18
16
  // Whenever browser location changes,
19
17
  // perform the same changes with the internal `location` object.
20
- const unsubscribe = environment.subscribe(location => {
21
- if (!shouldIgnoreEnvironmentLocationUpdates()) {
18
+ const unsubscribe = session.navigation.subscribe(location => {
19
+ if (!shouldIgnoreLocationSubscriptionEvents()) {
22
20
  next(updateLocation(location));
23
21
  }
24
22
  });
@@ -29,14 +27,14 @@ export default function createEnvironmentMiddleware(environment, {
29
27
  } = action;
30
28
  switch (type) {
31
29
  case ActionTypes.INIT:
32
- return next(updateLocation(environment.init()));
30
+ return next(updateLocation(session.navigation.init()));
33
31
  case ActionTypes.NAVIGATE:
34
- // `environment.navigate()` doesn't trigger the `subscribe()` listener.
35
- return next(updateLocation(environment.navigate(payload)));
32
+ // `session.navigate()` doesn't trigger the `subscribe()` listener.
33
+ return next(updateLocation(session.navigation.navigate(payload)));
36
34
  case ActionTypes.SHIFT:
37
35
  // `shift()` will trigger the `subscribe()` listener,
38
36
  // which will call `updateLocation()`.
39
- environment.shift(payload);
37
+ session.navigation.shift(payload);
40
38
  // eslint-disable-next-line consistent-return
41
39
  return;
42
40
  case ActionTypes.DISPOSE: