navigation-stack 0.3.1 → 0.5.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.
- package/CHANGELOG.md +12 -0
- package/README.md +611 -163
- package/data-storage/package.json +6 -0
- package/karma.conf.cjs +21 -4
- package/lib/cjs/NavigationStack.js +88 -0
- package/lib/cjs/data-storage/DataStorage.js +71 -0
- package/lib/cjs/data-storage/LocationDataStorage.js +29 -0
- package/lib/cjs/data-storage/index.js +9 -0
- package/lib/cjs/debug.js +12 -0
- package/lib/cjs/environment/InMemoryEnvironment.js +15 -0
- package/lib/cjs/environment/WebBrowserEnvironment.js +15 -0
- package/lib/cjs/environment/data-storage/InMemoryDataStorage.js +27 -0
- package/lib/cjs/environment/data-storage/WebBrowserDataStorage.js +21 -0
- package/lib/cjs/environment/scroll-position/InMemoryScrollPosition.js +44 -0
- package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +60 -0
- package/lib/cjs/getLocationFromInternalLocation.js +14 -0
- package/lib/cjs/index.js +20 -16
- package/lib/cjs/navigationBlockers.js +28 -23
- package/lib/cjs/{normalizeInputLocation.js → parseInputLocation.js} +25 -9
- package/lib/cjs/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/lib/cjs/redux/ActionTypesInternal.js +8 -0
- package/lib/cjs/{Actions.js → redux/Actions.js} +5 -4
- package/lib/cjs/redux/createMiddlewares.js +60 -0
- package/lib/cjs/redux/index.js +13 -0
- package/lib/cjs/redux/internalLocationReducer.js +14 -0
- package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +32 -0
- package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +113 -0
- package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
- package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +30 -0
- package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +73 -0
- package/lib/cjs/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +11 -8
- package/lib/cjs/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +6 -4
- package/lib/cjs/redux/middleware/updateLocationMiddleware.js +34 -0
- package/lib/cjs/scroll-position/PageScrollPositionSetter.js +97 -0
- package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +141 -0
- package/lib/cjs/scroll-position/ScrollPositionRestoration.js +407 -0
- package/lib/cjs/scroll-position/ScrollPositionSaver.js +87 -0
- package/lib/cjs/scroll-position/ScrollPositionSetter.js +16 -0
- package/lib/cjs/scroll-position/constants.js +5 -0
- package/lib/cjs/scroll-position/index.js +7 -0
- package/lib/cjs/scroll-position/scheduleNextTick.js +11 -0
- package/lib/cjs/session/InMemorySession.js +22 -0
- package/lib/cjs/session/ServerSideRenderSession.js +17 -0
- package/lib/cjs/session/Session.js +202 -0
- package/lib/cjs/session/WebBrowserSession.js +20 -0
- package/lib/cjs/session/key/createSessionKey.js +23 -0
- package/lib/cjs/session/lifecycle/InMemorySessionLifecycle.js +19 -0
- package/lib/cjs/session/lifecycle/WebBrowserSessionLifecycle.js +128 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycle.js +269 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +8 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +33 -0
- package/lib/cjs/session/navigation/InMemoryNavigation.js +104 -0
- package/lib/cjs/session/navigation/ServerSideNavigation.js +61 -0
- package/lib/cjs/session/navigation/WebBrowserNavigation.js +221 -0
- package/lib/cjs/session/navigation/error/NavigationOutOfBoundsError.js +12 -0
- package/lib/cjs/session/navigation/error/ServerSideNavigationError.js +21 -0
- package/lib/cjs/session/navigation/operation/operations.js +11 -0
- package/lib/cjs/session/subscription/Subscription.js +81 -0
- package/lib/data-storage/index.d.ts +35 -0
- package/lib/esm/NavigationStack.js +81 -0
- package/lib/esm/data-storage/DataStorage.js +65 -0
- package/lib/esm/data-storage/LocationDataStorage.js +22 -0
- package/lib/esm/data-storage/index.js +2 -0
- package/lib/esm/debug.js +7 -0
- package/lib/esm/environment/InMemoryEnvironment.js +8 -0
- package/lib/esm/environment/WebBrowserEnvironment.js +8 -0
- package/lib/esm/environment/data-storage/InMemoryDataStorage.js +21 -0
- package/lib/esm/environment/data-storage/WebBrowserDataStorage.js +15 -0
- package/lib/esm/environment/scroll-position/InMemoryScrollPosition.js +38 -0
- package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +54 -0
- package/lib/esm/getLocationFromInternalLocation.js +9 -0
- package/lib/esm/index.js +10 -8
- package/lib/esm/navigationBlockers.js +28 -23
- package/lib/esm/{normalizeInputLocation.js → parseInputLocation.js} +24 -8
- package/lib/esm/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/lib/esm/redux/ActionTypesInternal.js +3 -0
- package/lib/esm/{Actions.js → redux/Actions.js} +5 -4
- package/lib/esm/redux/createMiddlewares.js +54 -0
- package/lib/esm/redux/index.js +4 -0
- package/lib/esm/redux/internalLocationReducer.js +8 -0
- package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
- package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +108 -0
- package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +88 -0
- package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +25 -0
- package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +68 -0
- package/lib/esm/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -7
- package/lib/esm/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
- package/lib/esm/redux/middleware/updateLocationMiddleware.js +28 -0
- package/lib/esm/scroll-position/PageScrollPositionSetter.js +91 -0
- package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +134 -0
- package/lib/esm/scroll-position/ScrollPositionRestoration.js +400 -0
- package/lib/esm/scroll-position/ScrollPositionSaver.js +80 -0
- package/lib/esm/scroll-position/ScrollPositionSetter.js +10 -0
- package/lib/esm/scroll-position/constants.js +1 -0
- package/lib/esm/scroll-position/index.js +1 -0
- package/lib/esm/scroll-position/scheduleNextTick.js +6 -0
- package/lib/esm/session/InMemorySession.js +15 -0
- package/lib/esm/session/ServerSideRenderSession.js +11 -0
- package/lib/esm/session/Session.js +195 -0
- package/lib/esm/session/WebBrowserSession.js +13 -0
- package/lib/esm/session/key/createSessionKey.js +18 -0
- package/lib/esm/session/lifecycle/InMemorySessionLifecycle.js +13 -0
- package/lib/esm/session/lifecycle/WebBrowserSessionLifecycle.js +120 -0
- package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycle.js +263 -0
- package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +2 -0
- package/lib/esm/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +30 -0
- package/lib/esm/session/navigation/InMemoryNavigation.js +97 -0
- package/lib/esm/session/navigation/ServerSideNavigation.js +54 -0
- package/lib/esm/session/navigation/WebBrowserNavigation.js +213 -0
- package/lib/esm/session/navigation/error/NavigationOutOfBoundsError.js +6 -0
- package/lib/esm/session/navigation/error/ServerSideNavigationError.js +14 -0
- package/lib/esm/session/navigation/operation/operations.js +6 -0
- package/lib/esm/session/subscription/Subscription.js +75 -0
- package/lib/index.d.ts +179 -157
- package/lib/redux/index.d.ts +90 -0
- package/lib/scroll-position/index.d.ts +107 -0
- package/package.json +9 -5
- package/redux/package.json +6 -0
- package/scroll-position/package.json +6 -0
- package/src/NavigationStack.js +100 -0
- package/src/data-storage/DataStorage.js +69 -0
- package/src/data-storage/LocationDataStorage.js +23 -0
- package/src/data-storage/index.js +2 -0
- package/src/debug.js +8 -0
- package/src/environment/InMemoryEnvironment.js +9 -0
- package/src/environment/WebBrowserEnvironment.js +9 -0
- package/src/environment/data-storage/InMemoryDataStorage.js +23 -0
- package/src/environment/data-storage/WebBrowserDataStorage.js +17 -0
- package/src/environment/scroll-position/InMemoryScrollPosition.js +45 -0
- package/src/environment/scroll-position/WebBrowserScrollPosition.js +72 -0
- package/src/getLocationFromInternalLocation.js +7 -0
- package/src/index.js +10 -8
- package/src/navigationBlockers.js +31 -27
- package/src/{normalizeInputLocation.js → parseInputLocation.js} +23 -8
- package/src/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/src/redux/ActionTypesInternal.js +3 -0
- package/src/{Actions.js → redux/Actions.js} +4 -3
- package/src/redux/createMiddlewares.js +65 -0
- package/src/redux/index.js +4 -0
- package/src/redux/internalLocationReducer.js +9 -0
- package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
- package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +119 -0
- package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
- package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +26 -0
- package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +72 -0
- package/src/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -3
- package/src/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
- package/src/redux/middleware/updateLocationMiddleware.js +28 -0
- package/src/scroll-position/PageScrollPositionSetter.js +110 -0
- package/src/scroll-position/ScrollPositionAutoSaver.js +168 -0
- package/src/scroll-position/ScrollPositionRestoration.js +551 -0
- package/src/scroll-position/ScrollPositionSaver.js +120 -0
- package/src/scroll-position/ScrollPositionSetter.js +16 -0
- package/src/scroll-position/constants.js +1 -0
- package/src/scroll-position/index.js +1 -0
- package/src/scroll-position/scheduleNextTick.js +6 -0
- package/src/session/InMemorySession.js +13 -0
- package/src/session/ServerSideRenderSession.js +9 -0
- package/src/session/Session.js +238 -0
- package/src/session/WebBrowserSession.js +13 -0
- package/src/session/key/createSessionKey.js +18 -0
- package/src/session/lifecycle/InMemorySessionLifecycle.js +13 -0
- package/src/session/lifecycle/WebBrowserSessionLifecycle.js +126 -0
- package/src/session/lifecycle/page-lifecycle/PageLifecycle.js +291 -0
- package/src/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +3 -0
- package/src/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +32 -0
- package/src/session/navigation/InMemoryNavigation.js +78 -0
- package/src/session/navigation/ServerSideNavigation.js +43 -0
- package/src/session/navigation/WebBrowserNavigation.js +224 -0
- package/src/session/navigation/error/NavigationOutOfBoundsError.js +7 -0
- package/src/session/navigation/error/ServerSideNavigationError.js +18 -0
- package/src/session/navigation/operation/operations.js +6 -0
- package/src/session/subscription/Subscription.js +76 -0
- package/test/NavigationStack.test.js +296 -0
- package/test/{LocationDataStorage.test.js → data-storage/LocationDataStorage.test.js} +3 -3
- package/test/data-storage/index.test.js +8 -0
- package/test/index.js +12 -0
- package/test/index.test.js +8 -7
- package/test/{helpers.js → middlewareTestUtil.js} +9 -12
- package/test/{normalizeInputLocation.test.js → parseInputLocationMiddleware.test.js} +9 -9
- package/test/{Action.test.js → redux/Action.test.js} +7 -6
- package/test/{ActionTypes.test.js → redux/ActionTypes.test.js} +2 -2
- package/test/redux/createMiddlewares.test.js +96 -0
- package/test/redux/index.test.js +10 -0
- package/test/{locationReducer.test.js → redux/locationReducer.test.js} +4 -7
- package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +40 -0
- package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +264 -0
- package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +312 -0
- package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +51 -0
- package/test/{middleware/navigationActionMiddleware.test.js → redux/middleware/navigationOperationMiddleware.test.js} +16 -12
- package/test/{middleware/normalizeInputLocationMiddleware.test.js → redux/middleware/parseInputLocationMiddleware.test.js} +4 -4
- package/test/scroll-position/ScrollPositionRestoration.test.js +435 -0
- package/test/scroll-position/addScrollableContainer.js +39 -0
- package/test/scroll-position/addScrollableContainerWithAnchors.js +56 -0
- package/test/scroll-position/createApp.js +132 -0
- package/test/scroll-position/delay.js +9 -0
- package/test/scroll-position/mockPageLifecycle.js +17 -0
- package/test/scroll-position/runApp.js +24 -0
- package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +72 -0
- package/test/session/InMemorySession.test.js +348 -0
- package/test/session/ServerSession.test.js +17 -9
- package/test/session/WebBrowserSession.test.js +265 -0
- package/test/testUtil.js +3 -0
- package/types/data-storage/index.d.ts +35 -0
- package/types/index.d.ts +179 -157
- package/types/redux/index.d.ts +90 -0
- package/types/scroll-position/index.d.ts +107 -0
- package/types/tsconfig.json +1 -1
- package/lib/cjs/LocationDataStorage.js +0 -61
- package/lib/cjs/addBeforeLocationChangeListener.js +0 -7
- package/lib/cjs/beforeLocationChangeListeners.js +0 -51
- package/lib/cjs/createMiddlewares.js +0 -47
- package/lib/cjs/middleware/createBasePathMiddleware.js +0 -24
- package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -39
- package/lib/cjs/middleware/createLocationMiddleware.js +0 -56
- package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +0 -161
- package/lib/cjs/middleware/createTransformLocationMiddleware.js +0 -38
- package/lib/cjs/onlyAllowedOnClientSide.js +0 -10
- package/lib/cjs/session/BrowserSession.js +0 -235
- package/lib/cjs/session/MemorySession.js +0 -223
- package/lib/cjs/session/ServerSession.js +0 -65
- package/lib/esm/LocationDataStorage.js +0 -54
- package/lib/esm/addBeforeLocationChangeListener.js +0 -2
- package/lib/esm/beforeLocationChangeListeners.js +0 -44
- package/lib/esm/createMiddlewares.js +0 -41
- package/lib/esm/middleware/createBasePathMiddleware.js +0 -19
- package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -34
- package/lib/esm/middleware/createLocationMiddleware.js +0 -50
- package/lib/esm/middleware/createNavigationBlockerMiddleware.js +0 -156
- package/lib/esm/middleware/createTransformLocationMiddleware.js +0 -33
- package/lib/esm/onlyAllowedOnClientSide.js +0 -5
- package/lib/esm/session/BrowserSession.js +0 -229
- package/lib/esm/session/MemorySession.js +0 -217
- package/lib/esm/session/ServerSession.js +0 -58
- package/src/LocationDataStorage.js +0 -60
- package/src/addBeforeLocationChangeListener.js +0 -2
- package/src/beforeLocationChangeListeners.js +0 -54
- package/src/createMiddlewares.js +0 -45
- package/src/middleware/createBasePathMiddleware.js +0 -20
- package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -40
- package/src/middleware/createLocationMiddleware.js +0 -55
- package/src/middleware/createNavigationBlockerMiddleware.js +0 -168
- package/src/middleware/createTransformLocationMiddleware.js +0 -29
- package/src/onlyAllowedOnClientSide.js +0 -5
- package/src/session/BrowserSession.js +0 -235
- package/src/session/MemorySession.js +0 -219
- package/src/session/ServerSession.js +0 -67
- package/test/createMiddlewares.test.js +0 -62
- package/test/middleware/createBasePathMiddleware.test.js +0 -67
- package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +0 -141
- package/test/middleware/createNavigationBlockerMiddleware.test.js +0 -471
- package/test/middleware/createTransformLocationMiddleware.test.js +0 -44
- package/test/session/BrowserSession.test.js +0 -182
- package/test/session/MemorySession.test.js +0 -244
- /package/lib/cjs/{locationReducer.js → redux/locationReducer.js} +0 -0
- /package/lib/esm/{locationReducer.js → redux/locationReducer.js} +0 -0
- /package/src/{locationReducer.js → redux/locationReducer.js} +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Session from './Session';
|
|
2
|
+
import InMemoryEnvironment from '../environment/InMemoryEnvironment';
|
|
3
|
+
import InMemorySessionLifecycle from './lifecycle/InMemorySessionLifecycle';
|
|
4
|
+
import InMemoryNavigation from './navigation/InMemoryNavigation';
|
|
5
|
+
|
|
6
|
+
export default class InMemorySession extends Session {
|
|
7
|
+
constructor({ navigation = new InMemoryNavigation() } = {}) {
|
|
8
|
+
super({ navigation });
|
|
9
|
+
|
|
10
|
+
this.environment = new InMemoryEnvironment();
|
|
11
|
+
this.lifecycle = new InMemorySessionLifecycle();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import InMemorySession from './InMemorySession';
|
|
2
|
+
import ServerSideNavigation from './navigation/ServerSideNavigation';
|
|
3
|
+
|
|
4
|
+
// `ServerSideRenderSession` is just a `InMemorySession` that specifically prohibits any navigation.
|
|
5
|
+
export default class ServerSideRenderSession extends InMemorySession {
|
|
6
|
+
constructor() {
|
|
7
|
+
super({ navigation: new ServerSideNavigation() });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import debug from '../debug';
|
|
2
|
+
import parseInputLocation from '../parseInputLocation';
|
|
3
|
+
import createSessionKey from './key/createSessionKey';
|
|
4
|
+
import NavigationOutOfBoundsError from './navigation/error/NavigationOutOfBoundsError';
|
|
5
|
+
import NavigationOperations from './navigation/operation/operations';
|
|
6
|
+
import Subscription from './subscription/Subscription';
|
|
7
|
+
|
|
8
|
+
const INITIAL_KEY_INDEX = -1;
|
|
9
|
+
const INITIAL_INDEX = -1;
|
|
10
|
+
|
|
11
|
+
const INIT_LOCATION_DELTA = 0;
|
|
12
|
+
|
|
13
|
+
export default class Session {
|
|
14
|
+
constructor({ navigation }) {
|
|
15
|
+
// `key` is used in `WebBrowserSession` to uniquely identify a session
|
|
16
|
+
// when storing data in a `WebBrowserDataStorage` which uses `window.sessionStorage`
|
|
17
|
+
// under the hood, and `window.sessionStorage` is shared between different sessions.
|
|
18
|
+
this.key = createSessionKey();
|
|
19
|
+
|
|
20
|
+
// `this._locationKeyIndex` is incremented every time the current location changes.
|
|
21
|
+
this._locationKeyIndex = INITIAL_KEY_INDEX;
|
|
22
|
+
|
|
23
|
+
// `this._currentLocationIndex` is the index of the top element in the navigation stack.
|
|
24
|
+
// I.e. it's the index of the "current" location in the navigation stack.
|
|
25
|
+
this._currentLocationIndex = INITIAL_INDEX;
|
|
26
|
+
|
|
27
|
+
// The `index` of the terminal (rightmost) location in the navigation history.
|
|
28
|
+
// In other words, this is the last location index that it can `.shift()` to.
|
|
29
|
+
this._terminalLocationIndex = this._currentLocationIndex;
|
|
30
|
+
|
|
31
|
+
// Create `navigation`.
|
|
32
|
+
this._navigation = navigation;
|
|
33
|
+
|
|
34
|
+
// Manages subscriptions.
|
|
35
|
+
this._subscription = new Subscription({
|
|
36
|
+
activateSubscription: (listener) => {
|
|
37
|
+
return this._navigation.subscribe(listener);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Update current location index when a location change was not initiated
|
|
42
|
+
// by this session but rather by the user clicking "Back" or "Forward" button.
|
|
43
|
+
this._unsubscribe = this.subscribe((location) => {
|
|
44
|
+
// Update `this._currentLocationIndex` when the location change was not initiated
|
|
45
|
+
// by this session but rather by the user clicking "Back" or "Forward" button.
|
|
46
|
+
this._currentLocationIndex = location.index;
|
|
47
|
+
// Since `currentLocationIndex` has been updated, update `terminalLocationIndex`.
|
|
48
|
+
// It's not really currently possible to see a "PUSH" or a "REPLACE" operation here,
|
|
49
|
+
// but if it was possible, this call would be required. It would also be required
|
|
50
|
+
// by `navigation` to call `session.getNextKey()` function to increment `locationKeyIndex`.
|
|
51
|
+
this._updateTerminalLocationIndex(location);
|
|
52
|
+
|
|
53
|
+
debug(
|
|
54
|
+
'current location',
|
|
55
|
+
location.pathname,
|
|
56
|
+
'index',
|
|
57
|
+
this._currentLocationIndex,
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Subscribes to changes in location.
|
|
63
|
+
subscribe(listener) {
|
|
64
|
+
return this._subscription.subscribe((location) => {
|
|
65
|
+
if (
|
|
66
|
+
!this._isStarted() &&
|
|
67
|
+
location.operation !== NavigationOperations.INIT
|
|
68
|
+
) {
|
|
69
|
+
// eslint-disable-next-line no-console
|
|
70
|
+
console.error('Unexpected location change', location);
|
|
71
|
+
throw new Error('Not started');
|
|
72
|
+
} else {
|
|
73
|
+
// Call the listener.
|
|
74
|
+
listener(location);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
start(initialLocation) {
|
|
80
|
+
if (this._stopped) {
|
|
81
|
+
throw new Error('Can not be restarted');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Simplify "developer experience" by automatically calling
|
|
85
|
+
// `.init(initialLocation)` in case of using a `WebBrowserSession`.
|
|
86
|
+
//
|
|
87
|
+
// That's because `WebBrowserSession` environment already knows
|
|
88
|
+
// the initial location by the time javascript code starts execution.
|
|
89
|
+
//
|
|
90
|
+
if (!initialLocation) {
|
|
91
|
+
initialLocation = this._navigation.getInitialLocation();
|
|
92
|
+
if (initialLocation) {
|
|
93
|
+
initialLocation = parseInputLocation(initialLocation);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!initialLocation) {
|
|
98
|
+
throw new Error('`initialLocation` is required');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (this._currentLocationIndex !== INITIAL_INDEX) {
|
|
102
|
+
throw new Error('Already started');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
debug('▶ start session', initialLocation.pathname);
|
|
106
|
+
|
|
107
|
+
this._started = true;
|
|
108
|
+
|
|
109
|
+
const key = this._getNextLocationKey();
|
|
110
|
+
const index = INITIAL_INDEX + 1;
|
|
111
|
+
const delta = INIT_LOCATION_DELTA;
|
|
112
|
+
|
|
113
|
+
const locationResult = this._navigation.init(initialLocation, {
|
|
114
|
+
operation: NavigationOperations.INIT,
|
|
115
|
+
key,
|
|
116
|
+
index,
|
|
117
|
+
delta,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (locationResult) {
|
|
121
|
+
this._subscription.notifySubscribers(locationResult);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
stop() {
|
|
126
|
+
if (this._stopped) {
|
|
127
|
+
throw Error('Already stopped');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
debug('⏹ stop session');
|
|
131
|
+
|
|
132
|
+
// Once stopped, it won't be able to be restarted.
|
|
133
|
+
this._stopped = true;
|
|
134
|
+
|
|
135
|
+
// Remove location change subscription.
|
|
136
|
+
this._unsubscribe();
|
|
137
|
+
|
|
138
|
+
// Even if it calls `unsubscribe()` function above, any other subscriptions
|
|
139
|
+
// would still stay. For example, subscriptions created by the application code.
|
|
140
|
+
// To work around that, `.stop()` function removes all subscriptions.
|
|
141
|
+
this._subscription.stop();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
navigate(operation, location) {
|
|
145
|
+
if (!this._isStarted()) {
|
|
146
|
+
throw Error('Not started');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
operation !== NavigationOperations.PUSH &&
|
|
151
|
+
operation !== NavigationOperations.REPLACE
|
|
152
|
+
) {
|
|
153
|
+
throw Error(`Unknown navigation operation: ${operation}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const delta = operation === NavigationOperations.PUSH ? 1 : 0;
|
|
157
|
+
|
|
158
|
+
this._updateTerminalLocationIndex({ operation });
|
|
159
|
+
|
|
160
|
+
const key = this._getNextLocationKey();
|
|
161
|
+
const index = this._currentLocationIndex + delta;
|
|
162
|
+
|
|
163
|
+
debug(
|
|
164
|
+
operation === NavigationOperations.PUSH ? '↓' : '⇅',
|
|
165
|
+
operation,
|
|
166
|
+
location.pathname,
|
|
167
|
+
'index',
|
|
168
|
+
index,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Navigate to the location.
|
|
172
|
+
const locationResult = this._navigation.navigate(location, {
|
|
173
|
+
operation,
|
|
174
|
+
key,
|
|
175
|
+
index,
|
|
176
|
+
delta,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (locationResult) {
|
|
180
|
+
this._subscription.notifySubscribers(locationResult);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
shift(delta) {
|
|
185
|
+
if (!this._isStarted()) {
|
|
186
|
+
throw Error('Not started');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// If there'll be no navigation, return.
|
|
190
|
+
if (delta === 0) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const index = this._currentLocationIndex + delta;
|
|
195
|
+
|
|
196
|
+
debug(delta > 0 ? '→' : '←', 'shift', delta, 'index', index);
|
|
197
|
+
|
|
198
|
+
// Validate that the new `index` is not out of bounds.
|
|
199
|
+
if (index < 0 || index > this._terminalLocationIndex) {
|
|
200
|
+
throw new NavigationOutOfBoundsError(index);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Navigate to the location.
|
|
204
|
+
const locationResult = this._navigation.shift({
|
|
205
|
+
operation: NavigationOperations.SHIFT,
|
|
206
|
+
index,
|
|
207
|
+
delta,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (locationResult) {
|
|
211
|
+
this._subscription.notifySubscribers(locationResult);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// This function is used by navigation.
|
|
216
|
+
_getCurrentLocationIndex = () => {
|
|
217
|
+
return this._currentLocationIndex;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
_updateTerminalLocationIndex({ operation }) {
|
|
221
|
+
// A `PUSH` navigation sets a new terminal (rightmost) location.
|
|
222
|
+
if (
|
|
223
|
+
operation === NavigationOperations.PUSH ||
|
|
224
|
+
operation === NavigationOperations.INIT
|
|
225
|
+
) {
|
|
226
|
+
this._terminalLocationIndex = this._currentLocationIndex;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
_getNextLocationKey() {
|
|
231
|
+
this._locationKeyIndex++;
|
|
232
|
+
return this._locationKeyIndex.toString(36);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_isStarted() {
|
|
236
|
+
return !this._stopped && this._currentLocationIndex !== INITIAL_INDEX;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Session from './Session';
|
|
2
|
+
import WebBrowserEnvironment from '../environment/WebBrowserEnvironment';
|
|
3
|
+
import WebBrowserSessionLifecycle from './lifecycle/WebBrowserSessionLifecycle';
|
|
4
|
+
import WebBrowserNavigation from './navigation/WebBrowserNavigation';
|
|
5
|
+
|
|
6
|
+
export default class WebBrowserSession extends Session {
|
|
7
|
+
constructor() {
|
|
8
|
+
super({ navigation: new WebBrowserNavigation() });
|
|
9
|
+
|
|
10
|
+
this.environment = new WebBrowserEnvironment();
|
|
11
|
+
this.lifecycle = new WebBrowserSessionLifecycle();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// `session.key` exists to avoid `location.key` collision after a page refresh.
|
|
2
|
+
// After a page refresh, a different `session.key` is created
|
|
3
|
+
// while the previous navigation history still exists because
|
|
4
|
+
// web browser navigation history survives a page reload.
|
|
5
|
+
// So web browser navigation history after a refresh contains records from different sessions.
|
|
6
|
+
// This means that some of those history records end up having same `location.key`s
|
|
7
|
+
// because `location.key` always starts from `0` for each different session.
|
|
8
|
+
// So `location.key` alone can't be used to identify navigation history entries
|
|
9
|
+
// because it's not unique among them. In order to get a unique key for a navigation history entry,
|
|
10
|
+
// one should combine a unique `session.key` with a `location.key`.
|
|
11
|
+
// That's what `session.key` exists for.
|
|
12
|
+
// Supplementary features such as scroll position restoration
|
|
13
|
+
// use `window.sessionStorage` to store supplementary data for a given navigation history entry.
|
|
14
|
+
// Because `window.sessionStorage` is shared between all navigation history entries from different websites,
|
|
15
|
+
// the keys used for storing that supplementary data have to be unique.
|
|
16
|
+
export default function createSessionKey() {
|
|
17
|
+
return Date.now().toString(36);
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default class InMemorySessionLifecycle {
|
|
2
|
+
// Termination blockers of an "in-memory session" are currently ignored.
|
|
3
|
+
// eslint-disable-next-line no-unused-vars
|
|
4
|
+
addTerminationBlocker(blocker) {
|
|
5
|
+
return () => {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// An "in-memory session" execution status is always `running: true`.
|
|
9
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
|
+
addExecutionStatusListener(listener) {
|
|
11
|
+
return () => {};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// https://developers.google.com/web/updates/2018/07/page-lifecycle-api
|
|
2
|
+
// https://github.com/GoogleChromeLabs/page-lifecycle
|
|
3
|
+
import PageLifecycle from './page-lifecycle/PageLifecycleInstance';
|
|
4
|
+
|
|
5
|
+
export default class WebBrowserSessionLifecycle {
|
|
6
|
+
constructor() {
|
|
7
|
+
this._running = true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
addTerminationBlocker(terminationBlocker) {
|
|
11
|
+
const onBeforeUnload = (event) => {
|
|
12
|
+
if (terminationBlocker()) {
|
|
13
|
+
// Calling `event.preventDefault()` will cause a web browser
|
|
14
|
+
// to show a generic "Ok"/"Cancel" modal with some generic text:
|
|
15
|
+
// "Are you sure to leave the current page?".
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
window.addEventListener('beforeunload', onBeforeUnload);
|
|
21
|
+
return () => {
|
|
22
|
+
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
addExecutionStatusListener(listener) {
|
|
27
|
+
const pageLifecycleListener = (stateChange) => {
|
|
28
|
+
// (stateChange: PageLifecycleStateChange)
|
|
29
|
+
const { newState } = stateChange;
|
|
30
|
+
|
|
31
|
+
const running = !['terminated', 'frozen', 'discarded'].includes(
|
|
32
|
+
newState,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (this._running !== running) {
|
|
36
|
+
this._running = running;
|
|
37
|
+
listener({ running });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
PageLifecycle.addEventListener('statechange', pageLifecycleListener);
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
PageLifecycle.removeEventListener('statechange', pageLifecycleListener);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// interface PageLifecycleStateChange {
|
|
50
|
+
// newState: PageLifecycleState;
|
|
51
|
+
// oldState: PageLifecycleState;
|
|
52
|
+
// originalEvent: Event;
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// // Page Lifecycle API event types.
|
|
56
|
+
// // https://developer.chrome.com/docs/web-platform/page-lifecycle-api#states
|
|
57
|
+
// // https://wicg.github.io/page-lifecycle/spec.html
|
|
58
|
+
// //
|
|
59
|
+
// type PageLifecycleState =
|
|
60
|
+
// // The page is visible and is focused.
|
|
61
|
+
// | 'active'
|
|
62
|
+
// // The page is visible but is not focused.
|
|
63
|
+
// | 'passive'
|
|
64
|
+
// // The page is not visible (and has not been frozen, discarded, or terminated).
|
|
65
|
+
// | 'hidden'
|
|
66
|
+
// // If a page is hidden, a browser may choose to freeze it to reduce energy consumption.
|
|
67
|
+
// | 'frozen'
|
|
68
|
+
// // The process of terminating (destroying, closing) the page has started.
|
|
69
|
+
// | 'terminated'
|
|
70
|
+
// // The page is discarded by the web browser due to insufficient resources.
|
|
71
|
+
// // The page snapshot could still be visible to the user even though it's no longer running.
|
|
72
|
+
// | 'discarded';
|
|
73
|
+
|
|
74
|
+
// // Page Lifecycle API event types.
|
|
75
|
+
// // https://developer.chrome.com/docs/web-platform/page-lifecycle-api
|
|
76
|
+
// // https://wicg.github.io/page-lifecycle/spec.html
|
|
77
|
+
// //
|
|
78
|
+
// type PageLifecycleEvent =
|
|
79
|
+
// // When the web browser window with an opened page gets focus, a `focus` event is emitted.
|
|
80
|
+
// | 'focus'
|
|
81
|
+
//
|
|
82
|
+
// // When the web browser window with an opened page is no longer focused, a `blur` event is emitted.
|
|
83
|
+
// | 'blur'
|
|
84
|
+
//
|
|
85
|
+
// // `visibilitychange` event fires with `document.visibilityState` being "hidden"
|
|
86
|
+
// // when a user navigates to a new page, switches tabs, closes the tab, minimizes or closes the browser,
|
|
87
|
+
// // or, on mobile, switches from the browser to a different app.
|
|
88
|
+
// //
|
|
89
|
+
// // Transitioning to "hidden" is the last event that's reliably observable by the page,
|
|
90
|
+
// // so developers should treat it as the likely end of the user's session
|
|
91
|
+
// // (for example, for sending analytics data).
|
|
92
|
+
// //
|
|
93
|
+
// // The transition to "hidden" is also a good point at which pages can stop making UI updates
|
|
94
|
+
// // and stop any tasks that the user doesn't want to have running in the background.
|
|
95
|
+
// //
|
|
96
|
+
// | 'visibilitychange'
|
|
97
|
+
//
|
|
98
|
+
// // Sometimes browsers "freeze" hidden pages in order to reduce energy consumption on mobile devices.
|
|
99
|
+
// // In case of freezing an already-hidden page, a `freeze` event will be emitted, if supported by the browser.
|
|
100
|
+
// | 'freeze'
|
|
101
|
+
//
|
|
102
|
+
// // Sometimes browsers "freeze" hidden pages in order to reduce energy consumption on mobile devices.
|
|
103
|
+
// // In case of unfreezing an already-frozen page, a `resume` event will be emitted, if supported by the browser.
|
|
104
|
+
// | 'resume'
|
|
105
|
+
//
|
|
106
|
+
// // `pageshow` event is emitted when a new page gets shown.
|
|
107
|
+
// //
|
|
108
|
+
// // For example, `pageshow` event is emitted when visiting a web page
|
|
109
|
+
// // or after being navigated to a new page by clicking a hyperlink.
|
|
110
|
+
// //
|
|
111
|
+
// // `pageshow` event is also emitted when the user performs "Back" or "Forward" transition.
|
|
112
|
+
// //
|
|
113
|
+
// | 'pageshow'
|
|
114
|
+
//
|
|
115
|
+
// // `pagehide` event is emitted when the current page gets "destroyed".
|
|
116
|
+
// //
|
|
117
|
+
// // For example, `pagehide` event is emitted when the user performs "Back" or "Forward" transition.
|
|
118
|
+
// // In that case, `pagehide` event will be emitted for the current page before the transition.
|
|
119
|
+
// //
|
|
120
|
+
// // In any other cases of "destroying" The current page, `pagehide` event is not guaranteed to be emitted.
|
|
121
|
+
// // For example, it won't be emitted when closing the web browser app via a task manager.
|
|
122
|
+
// //
|
|
123
|
+
// // Hence, `pagehide` event is unreliable and it's adivised to use `visibilitychange` event instead.
|
|
124
|
+
// // Only if `visibilitychange` even is not supported by a web browser should one consider resorting to using `pagehide` event.
|
|
125
|
+
// //
|
|
126
|
+
// | 'pagehide';
|