-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathweb-storage.ts
132 lines (122 loc) · 4.12 KB
/
web-storage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { Store } from 'redux'
import { filterState, IndexedObject } from '../state/filter-state'
const ROOT_KEY = 'redux-iframe-state'
/**
* Storage type
*/
export enum StorageType {
/**
* Use session storage (cleared if window closes)
*/
SESSION,
/**
* Use use local storage (persistent)
*/
LOCAL
}
/**
* Storage options
*/
type StorageOptions = {
/**
* Name of the top-level key of the global storage
*/
rootKey?: string
/**
* Storage type to use, either LOCAL (persistent) or SESSION (cleared if window closes)
*/
storageType?: StorageType
}
const DEFAULT_STORAGE_OPTIONS : StorageOptions = {
rootKey: ROOT_KEY,
storageType: StorageType.SESSION
}
/**
* Reads the Redux store content from local or session storage. Returns undefined if no state exists.
*
* @param keys array of Redux top-level keys to load
* @param options storage options (which default to rootKey: 'state' and storage: SESSION)
*/
export const getStoredState = (keys: Array<string>, options: StorageOptions = DEFAULT_STORAGE_OPTIONS): Object | undefined => {
const storage = getStorage(options.storageType || StorageType.SESSION)
if (storage) {
try {
const serializedState = storage.getItem(options.rootKey || ROOT_KEY)
if (serializedState === null) {
// If no state was stored previously, tell the store constructor that no initial state exists
return undefined
}
const filteredState = filterState(JSON.parse(serializedState), keys)
// TODO: Remove or configure log output
console.log('Loaded state from storage:', filteredState)
return filteredState
} catch (err) {
console.warn('Cannot read from storage:', err)
}
}
}
/**
* Subscribes to the given Redux store and copies all substates matching one of the keys to local or session storage.
*
* @param store a Redux store
* @param keys array of Redux top-level keys to save
* @param options storage options (which default to rootKey: 'state' and storage: SESSION)
*/
export const installStorageWriter = (store: Store, keys: Array<string>, options: StorageOptions = DEFAULT_STORAGE_OPTIONS) => {
let memoizedState: IndexedObject | undefined
store.subscribe(() => {
const state = filterState(store.getState(), keys)
if (state && hasChanged(memoizedState, state)) {
if (saveState(state, options)) {
memoizedState = state
}
}
})
}
const getStorage = (storageType: StorageType) => {
switch (storageType) {
case StorageType.LOCAL:
if (!window.localStorage) {
console.warn('Local storage is not supported')
return undefined
}
return window.localStorage
case StorageType.SESSION:
if (!window.sessionStorage) {
console.warn('Session storage is not supported')
return undefined
}
return window.sessionStorage
}
}
/**
* Saves the state of the Redux store to local or session storage.
*
* @param state the Redux state object
* @param options storage options (which default to rootKey: 'state' and storage: SESSION)
* @return true if storing succeeded, false otherwise
*/
const saveState = (state: Object, options: StorageOptions): boolean => {
const storage = getStorage(options.storageType || StorageType.SESSION)
if (storage) {
try {
if (state) {
// TODO: Remove or configure log output
console.log('Saved state to storage:', state)
storage.setItem(options.rootKey || ROOT_KEY, JSON.stringify(state))
return true
}
} catch (err) {
console.warn('Cannot write to storage:', err)
}
}
return false
}
const hasChanged = (memoizedState: IndexedObject | undefined, state: IndexedObject): boolean => {
for (const key in state) {
if (!memoizedState || memoizedState[key] !== state[key]) {
return true
}
}
return false
}