@dhis2/app-service-offline 3.11.2 → 3.12.0-alpha.1
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/build/cjs/__tests__/integration.test.js +51 -82
- package/build/cjs/index.js +0 -7
- package/build/cjs/lib/__tests__/cacheable-section-state.test.js +7 -14
- package/build/cjs/lib/__tests__/clear-sensitive-caches.test.js +17 -20
- package/build/cjs/lib/__tests__/network-status.test.js +135 -148
- package/build/cjs/lib/__tests__/offline-provider.test.js +12 -22
- package/build/cjs/lib/__tests__/use-cacheable-section.test.js +87 -98
- package/build/cjs/lib/__tests__/use-online-status-message.test.js +7 -14
- package/build/cjs/lib/cacheable-section-state.js +27 -38
- package/build/cjs/lib/cacheable-section.js +26 -27
- package/build/cjs/lib/clear-sensitive-caches.js +14 -24
- package/build/cjs/lib/dhis2-connection-status/dev-debug-log.js +1 -3
- package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.js +27 -58
- package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.test.js +287 -230
- package/build/cjs/lib/dhis2-connection-status/index.js +0 -1
- package/build/cjs/lib/dhis2-connection-status/is-ping-available.js +0 -6
- package/build/cjs/lib/dhis2-connection-status/is-ping-available.test.js +0 -1
- package/build/cjs/lib/dhis2-connection-status/smart-interval.js +35 -49
- package/build/cjs/lib/dhis2-connection-status/use-ping-query.js +4 -5
- package/build/cjs/lib/global-state-service.js +9 -27
- package/build/cjs/lib/network-status.js +10 -13
- package/build/cjs/lib/offline-interface.js +3 -14
- package/build/cjs/lib/offline-provider.js +1 -12
- package/build/cjs/lib/online-status-message.js +5 -17
- package/build/cjs/setupRTL.js +1 -1
- package/build/cjs/utils/__tests__/render-counter.test.js +3 -12
- package/build/cjs/utils/render-counter.js +2 -10
- package/build/cjs/utils/test-mocks.js +13 -18
- package/build/es/__tests__/integration.test.js +51 -74
- package/build/es/index.js +2 -2
- package/build/es/lib/__tests__/cacheable-section-state.test.js +2 -4
- package/build/es/lib/__tests__/clear-sensitive-caches.test.js +19 -16
- package/build/es/lib/__tests__/network-status.test.js +105 -114
- package/build/es/lib/__tests__/offline-provider.test.js +13 -15
- package/build/es/lib/__tests__/use-cacheable-section.test.js +69 -73
- package/build/es/lib/__tests__/use-online-status-message.test.js +2 -3
- package/build/es/lib/cacheable-section-state.js +25 -26
- package/build/es/lib/cacheable-section.js +23 -15
- package/build/es/lib/clear-sensitive-caches.js +13 -21
- package/build/es/lib/dhis2-connection-status/dev-debug-log.js +1 -3
- package/build/es/lib/dhis2-connection-status/dhis2-connection-status.js +26 -37
- package/build/es/lib/dhis2-connection-status/dhis2-connection-status.test.js +223 -159
- package/build/es/lib/dhis2-connection-status/is-ping-available.js +0 -5
- package/build/es/lib/dhis2-connection-status/smart-interval.js +34 -42
- package/build/es/lib/dhis2-connection-status/use-ping-query.js +6 -3
- package/build/es/lib/global-state-service.js +6 -12
- package/build/es/lib/network-status.js +10 -9
- package/build/es/lib/offline-interface.js +0 -3
- package/build/es/lib/offline-provider.js +0 -3
- package/build/es/lib/online-status-message.js +3 -2
- package/build/es/setupRTL.js +1 -1
- package/build/es/utils/__tests__/render-counter.test.js +2 -4
- package/build/es/utils/render-counter.js +1 -3
- package/build/es/utils/test-mocks.js +8 -9
- package/build/types/lib/cacheable-section.d.ts +1 -1
- package/build/types/lib/dhis2-connection-status/dhis2-connection-status.d.ts +1 -1
- package/build/types/lib/network-status.d.ts +1 -1
- package/build/types/lib/online-status-message.d.ts +1 -1
- package/build/types/types.d.ts +1 -1
- package/package.json +4 -4
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// IndexedDB names; should be the same as in @dhis2/pwa
|
|
2
2
|
export const SECTIONS_DB = 'sections-db';
|
|
3
|
-
export const SECTIONS_STORE = 'sections-store';
|
|
3
|
+
export const SECTIONS_STORE = 'sections-store';
|
|
4
4
|
|
|
5
|
+
// Non-sensitive caches that can be kept:
|
|
5
6
|
const KEEPABLE_CACHES = [/^workbox-precache/ // precached static assets
|
|
6
7
|
];
|
|
7
|
-
|
|
8
8
|
/*
|
|
9
9
|
* Clears the 'sections-db' IndexedDB if it exists. Designed to avoid opening
|
|
10
10
|
* a new DB if it doesn't exist yet. Firefox can't check if 'sections-db'
|
|
@@ -19,10 +19,8 @@ const clearDB = async dbName => {
|
|
|
19
19
|
// and offline interface will handle discrepancies in PWA apps.
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
22
|
const dbs = await window.indexedDB.databases();
|
|
24
|
-
|
|
25
|
-
if (!dbs.some((_ref) => {
|
|
23
|
+
if (!dbs.some(_ref => {
|
|
26
24
|
let {
|
|
27
25
|
name
|
|
28
26
|
} = _ref;
|
|
@@ -31,57 +29,51 @@ const clearDB = async dbName => {
|
|
|
31
29
|
// Sections-db is not created; nothing to do here
|
|
32
30
|
return;
|
|
33
31
|
}
|
|
34
|
-
|
|
35
32
|
return new Promise((resolve, reject) => {
|
|
36
33
|
// IndexedDB fun:
|
|
37
34
|
const openDBRequest = indexedDB.open(dbName);
|
|
38
|
-
|
|
39
35
|
openDBRequest.onsuccess = e => {
|
|
40
36
|
const db = e.target.result;
|
|
41
|
-
const tx = db.transaction(SECTIONS_STORE, 'readwrite');
|
|
42
|
-
|
|
37
|
+
const tx = db.transaction(SECTIONS_STORE, 'readwrite');
|
|
38
|
+
// When the transaction completes is when the operation is done:
|
|
43
39
|
tx.oncomplete = () => resolve();
|
|
44
|
-
|
|
45
40
|
tx.onerror = e => reject(e.target.error);
|
|
46
|
-
|
|
47
41
|
const os = tx.objectStore(SECTIONS_STORE);
|
|
48
42
|
const clearReq = os.clear();
|
|
49
|
-
|
|
50
43
|
clearReq.onerror = e => reject(e.target.error);
|
|
51
44
|
};
|
|
52
|
-
|
|
53
45
|
openDBRequest.onerror = e => {
|
|
54
46
|
reject(e.target.error);
|
|
55
47
|
};
|
|
56
48
|
});
|
|
57
49
|
};
|
|
50
|
+
|
|
58
51
|
/**
|
|
59
52
|
* Used to clear caches and 'sections-db' IndexedDB when a user logs out or a
|
|
60
53
|
* different user logs in to prevent someone from accessing a different user's
|
|
61
54
|
* caches. Should be able to be used in a non-PWA app.
|
|
62
55
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
56
|
export async function clearSensitiveCaches() {
|
|
66
57
|
let dbName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SECTIONS_DB;
|
|
67
58
|
console.debug('Clearing sensitive caches');
|
|
68
|
-
let cacheKeys;
|
|
69
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage
|
|
59
|
+
let cacheKeys;
|
|
70
60
|
|
|
61
|
+
// caches.keys can fail in insecure contexts, see:
|
|
62
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage
|
|
71
63
|
try {
|
|
72
64
|
cacheKeys = await caches.keys();
|
|
73
65
|
} catch (e) {
|
|
74
66
|
// Return false since no caches have been cleared
|
|
75
67
|
return false;
|
|
76
68
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
clearDB(dbName).then(() => false),
|
|
69
|
+
return Promise.all([
|
|
70
|
+
// (Resolves to 'false' because this can't detect if anything was deleted):
|
|
71
|
+
clearDB(dbName).then(() => false),
|
|
72
|
+
// Remove caches if not in keepable list
|
|
80
73
|
...cacheKeys.map(key => {
|
|
81
74
|
if (!KEEPABLE_CACHES.some(pattern => pattern.test(key))) {
|
|
82
75
|
return caches.delete(key);
|
|
83
76
|
}
|
|
84
|
-
|
|
85
77
|
return false;
|
|
86
78
|
})]).then(responses => {
|
|
87
79
|
// Return true if any caches have been cleared
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const shouldLog = localStorage.getItem('dhis2.debugConnectionStatus');
|
|
2
|
-
|
|
3
2
|
if (shouldLog) {
|
|
4
3
|
console.log('Logging for dhis2ConnectionStatus is enabled. Remove the `dhis2.debugConnectionStatus` item in localStorage to disable logging.');
|
|
5
4
|
}
|
|
5
|
+
|
|
6
6
|
/**
|
|
7
7
|
* This can be used to log info if the `dhis2.debugConnectionStatus` value
|
|
8
8
|
* in localStorage is set to a truthy value during development.
|
|
@@ -11,8 +11,6 @@ if (shouldLog) {
|
|
|
11
11
|
* The behavior of the connection status can be quite hard to inspect without
|
|
12
12
|
* logs, but the logs are quite chatty and should be omitted normally.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
14
|
export function devDebugLog() {
|
|
17
15
|
if (shouldLog) {
|
|
18
16
|
console.log(...arguments);
|
|
@@ -6,32 +6,30 @@ import { useOfflineInterface } from '../offline-interface';
|
|
|
6
6
|
import { devDebugLog } from './dev-debug-log';
|
|
7
7
|
import { isPingAvailable } from './is-ping-available';
|
|
8
8
|
import createSmartInterval from './smart-interval';
|
|
9
|
-
import { usePingQuery } from './use-ping-query';
|
|
9
|
+
import { usePingQuery } from './use-ping-query';
|
|
10
10
|
|
|
11
|
+
// Utils for saving 'last connected' datetime in local storage
|
|
11
12
|
const lastConnectedKey = 'dhis2.lastConnected';
|
|
12
13
|
export const getLastConnectedKey = appName => appName ? `${lastConnectedKey}.${appName}` : lastConnectedKey;
|
|
13
|
-
|
|
14
14
|
const updateLastConnected = appName => {
|
|
15
15
|
// use Date.now() because it's easier to mock for easier unit testing
|
|
16
16
|
const now = new Date(Date.now());
|
|
17
17
|
localStorage.setItem(getLastConnectedKey(appName), now.toUTCString());
|
|
18
18
|
return now;
|
|
19
19
|
};
|
|
20
|
-
|
|
21
20
|
const getLastConnected = appName => {
|
|
22
21
|
const lastConnected = localStorage.getItem(getLastConnectedKey(appName));
|
|
23
22
|
return lastConnected ? new Date(lastConnected) : null;
|
|
24
23
|
};
|
|
25
|
-
|
|
26
24
|
const clearLastConnected = appName => {
|
|
27
25
|
localStorage.removeItem(getLastConnectedKey(appName));
|
|
28
26
|
};
|
|
29
|
-
|
|
30
27
|
const Dhis2ConnectionStatusContext = /*#__PURE__*/React.createContext({
|
|
31
28
|
isConnected: true,
|
|
32
29
|
isDisconnected: false,
|
|
33
30
|
lastConnected: null
|
|
34
31
|
});
|
|
32
|
+
|
|
35
33
|
/**
|
|
36
34
|
* Provides a boolean indicating client's connection to the DHIS2 server,
|
|
37
35
|
* which is different from connection to the internet.
|
|
@@ -41,8 +39,7 @@ const Dhis2ConnectionStatusContext = /*#__PURE__*/React.createContext({
|
|
|
41
39
|
* and then will initiate periodic pings if there are no incidental requests in
|
|
42
40
|
* order to check the connection consistently
|
|
43
41
|
*/
|
|
44
|
-
|
|
45
|
-
export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
42
|
+
export const Dhis2ConnectionStatusProvider = _ref => {
|
|
46
43
|
let {
|
|
47
44
|
children
|
|
48
45
|
} = _ref;
|
|
@@ -50,21 +47,21 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
50
47
|
const {
|
|
51
48
|
appName,
|
|
52
49
|
serverVersion
|
|
53
|
-
} = useConfig();
|
|
50
|
+
} = useConfig();
|
|
51
|
+
// The offline interface persists the latest update from the SW so that
|
|
54
52
|
// this hook can initialize to an accurate value. The App Adapter in the
|
|
55
53
|
// platform waits for this value to be populated before rendering the
|
|
56
54
|
// the App Runtime provider (including this), but if that is not done,
|
|
57
55
|
// `latestIsConnected` may be `null` depending on the outcome of race
|
|
58
56
|
// conditions between the SW and the React component tree.
|
|
59
|
-
|
|
60
57
|
const [isConnected, setIsConnected] = useState(offlineInterface.latestIsConnected);
|
|
61
58
|
const ping = usePingQuery();
|
|
62
59
|
const smartIntervalRef = useRef(null);
|
|
60
|
+
|
|
63
61
|
/**
|
|
64
62
|
* Update state, reset ping backoff if changed, and update
|
|
65
63
|
* the lastConnected value in localStorage
|
|
66
64
|
*/
|
|
67
|
-
|
|
68
65
|
const updateConnectedState = useCallback(newIsConnected => {
|
|
69
66
|
// use 'set' with a function as param to get latest isConnected
|
|
70
67
|
// without needing it as a dependency for useCallback
|
|
@@ -73,13 +70,10 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
73
70
|
prevIsConnected,
|
|
74
71
|
newIsConnected
|
|
75
72
|
});
|
|
76
|
-
|
|
77
73
|
if (newIsConnected !== prevIsConnected) {
|
|
78
74
|
var _smartIntervalRef$cur;
|
|
79
|
-
|
|
80
75
|
// if value changed, reset ping interval to initial delay
|
|
81
76
|
(_smartIntervalRef$cur = smartIntervalRef.current) === null || _smartIntervalRef$cur === void 0 ? void 0 : _smartIntervalRef$cur.reset();
|
|
82
|
-
|
|
83
77
|
if (newIsConnected) {
|
|
84
78
|
// Need to clear this here so it doesn't affect another
|
|
85
79
|
// session that starts while offline
|
|
@@ -88,15 +82,15 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
88
82
|
updateLastConnected(appName);
|
|
89
83
|
}
|
|
90
84
|
}
|
|
91
|
-
|
|
92
85
|
return newIsConnected;
|
|
93
86
|
});
|
|
94
|
-
}, [appName]);
|
|
87
|
+
}, [appName]);
|
|
88
|
+
|
|
89
|
+
// Note that the SW is configured to not cache ping requests and won't
|
|
95
90
|
// trigger `handleChange` below to avoid redundant signals. This also
|
|
96
91
|
// helps to detect the connectivity status when the SW is not available
|
|
97
92
|
// for some reason (maybe private browsing, first installation, or
|
|
98
93
|
// insecure browser context)
|
|
99
|
-
|
|
100
94
|
const pingAndHandleStatus = useCallback(() => {
|
|
101
95
|
return ping().then(() => {
|
|
102
96
|
// Ping is successful; set 'connected'
|
|
@@ -106,16 +100,16 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
106
100
|
updateConnectedState(false);
|
|
107
101
|
});
|
|
108
102
|
}, [ping, updateConnectedState]);
|
|
109
|
-
/** Called when SW reports updates from incidental network traffic */
|
|
110
103
|
|
|
111
|
-
|
|
104
|
+
/** Called when SW reports updates from incidental network traffic */
|
|
105
|
+
const onUpdate = useCallback(_ref2 => {
|
|
112
106
|
var _smartIntervalRef$cur2;
|
|
113
|
-
|
|
114
107
|
let {
|
|
115
108
|
isConnected: newIsConnected
|
|
116
109
|
} = _ref2;
|
|
117
|
-
devDebugLog('[D2CS] handling update from sw');
|
|
110
|
+
devDebugLog('[D2CS] handling update from sw');
|
|
118
111
|
|
|
112
|
+
// Snooze ping timer to reduce pings since we know state from SW
|
|
119
113
|
(_smartIntervalRef$cur2 = smartIntervalRef.current) === null || _smartIntervalRef$cur2 === void 0 ? void 0 : _smartIntervalRef$cur2.snooze();
|
|
120
114
|
updateConnectedState(newIsConnected);
|
|
121
115
|
}, [updateConnectedState]);
|
|
@@ -124,26 +118,22 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
124
118
|
// pinging with the smart interval. Just use the service worker
|
|
125
119
|
if (!serverVersion || !isPingAvailable(serverVersion)) {
|
|
126
120
|
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
121
|
+
}
|
|
129
122
|
|
|
123
|
+
// Only create the smart interval once
|
|
130
124
|
const smartInterval = createSmartInterval({
|
|
131
125
|
// don't ping if window isn't focused or visible
|
|
132
126
|
initialPauseValue: !document.hasFocus() || document.visibilityState !== 'visible',
|
|
133
127
|
callback: pingAndHandleStatus
|
|
134
128
|
});
|
|
135
129
|
smartIntervalRef.current = smartInterval;
|
|
136
|
-
|
|
137
130
|
const handleBlur = () => smartInterval.pause();
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
const handleFocus = () => smartInterval.resume();
|
|
132
|
+
// Pinging when going offline should be low/no-cost in both online and
|
|
140
133
|
// local servers
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const handleOffline = () => smartInterval.invokeCallbackImmediately(); // Pinging when going online has a cost but improves responsiveness of
|
|
134
|
+
const handleOffline = () => smartInterval.invokeCallbackImmediately();
|
|
135
|
+
// Pinging when going online has a cost but improves responsiveness of
|
|
144
136
|
// the connection status -- only do it once every 15 seconds at most
|
|
145
|
-
|
|
146
|
-
|
|
147
137
|
const handleOnline = throttle(() => smartInterval.invokeCallbackImmediately(), 15000);
|
|
148
138
|
window.addEventListener('blur', handleBlur);
|
|
149
139
|
window.addEventListener('focus', handleFocus);
|
|
@@ -153,8 +143,9 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
153
143
|
window.removeEventListener('blur', handleBlur);
|
|
154
144
|
window.removeEventListener('focus', handleFocus);
|
|
155
145
|
window.removeEventListener('offline', handleOffline);
|
|
156
|
-
window.removeEventListener('online', handleOnline);
|
|
146
|
+
window.removeEventListener('online', handleOnline);
|
|
157
147
|
|
|
148
|
+
// clean up smart interval and throttled function
|
|
158
149
|
smartInterval.clear();
|
|
159
150
|
handleOnline.cancel();
|
|
160
151
|
};
|
|
@@ -162,22 +153,21 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
162
153
|
useEffect(() => {
|
|
163
154
|
if (!offlineInterface.subscribeToDhis2ConnectionStatus) {
|
|
164
155
|
var _smartIntervalRef$cur3;
|
|
165
|
-
|
|
166
156
|
// Missing this functionality from the offline interface --
|
|
167
157
|
// use a ping on startup to get the status
|
|
168
158
|
(_smartIntervalRef$cur3 = smartIntervalRef.current) === null || _smartIntervalRef$cur3 === void 0 ? void 0 : _smartIntervalRef$cur3.invokeCallbackImmediately();
|
|
169
159
|
console.warn('Please upgrade to @dhis2/cli-app-scripts@>10.3.8 for full connection status features');
|
|
170
160
|
return;
|
|
171
161
|
}
|
|
172
|
-
|
|
173
162
|
const unsubscribe = offlineInterface.subscribeToDhis2ConnectionStatus({
|
|
174
163
|
onUpdate
|
|
175
164
|
});
|
|
176
165
|
return () => {
|
|
177
166
|
unsubscribe();
|
|
178
167
|
};
|
|
179
|
-
}, [offlineInterface, onUpdate]);
|
|
168
|
+
}, [offlineInterface, onUpdate]);
|
|
180
169
|
|
|
170
|
+
// Memoize this value to prevent unnecessary rerenders of context provider
|
|
181
171
|
const contextValue = useMemo(() => {
|
|
182
172
|
// in the unlikely circumstance that offlineInterface.latestIsConnected
|
|
183
173
|
// is `null` or `undefined` when this initializes, fail safe by defaulting to
|
|
@@ -186,7 +176,8 @@ export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
|
186
176
|
return {
|
|
187
177
|
isConnected: validatedIsConnected,
|
|
188
178
|
isDisconnected: !validatedIsConnected,
|
|
189
|
-
lastConnected: validatedIsConnected ? null :
|
|
179
|
+
lastConnected: validatedIsConnected ? null :
|
|
180
|
+
// Only evaluate if disconnected, since local storage
|
|
190
181
|
// is synchronous and disk-based.
|
|
191
182
|
// If lastConnected is not set in localStorage though, set it.
|
|
192
183
|
// (relevant on startup)
|
|
@@ -202,10 +193,8 @@ Dhis2ConnectionStatusProvider.propTypes = {
|
|
|
202
193
|
};
|
|
203
194
|
export const useDhis2ConnectionStatus = () => {
|
|
204
195
|
const context = useContext(Dhis2ConnectionStatusContext);
|
|
205
|
-
|
|
206
196
|
if (!context) {
|
|
207
197
|
throw new Error('useDhis2ConnectionStatus must be used within a Dhis2ConnectionStatus provider');
|
|
208
198
|
}
|
|
209
|
-
|
|
210
199
|
return context;
|
|
211
200
|
};
|