@dhis2/app-service-offline 3.8.0 → 3.9.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/build/cjs/__tests__/integration.test.js +37 -24
- package/build/cjs/index.js +18 -10
- package/build/cjs/lib/__tests__/clear-sensitive-caches.test.js +5 -1
- package/build/cjs/lib/__tests__/{online-status.test.js → network-status.test.js} +41 -20
- package/build/cjs/lib/__tests__/offline-provider.test.js +5 -1
- package/build/cjs/lib/__tests__/use-cacheable-section.test.js +77 -35
- package/build/cjs/lib/__tests__/{use-online-staus-message.test.js → use-online-status-message.test.js} +8 -5
- package/build/cjs/lib/cacheable-section-state.js +17 -13
- package/build/cjs/lib/cacheable-section.js +14 -12
- package/build/cjs/lib/clear-sensitive-caches.js +9 -5
- package/build/cjs/lib/dhis2-connection-status/dev-debug-log.js +26 -0
- package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.js +224 -0
- package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.test.js +841 -0
- package/build/cjs/lib/dhis2-connection-status/index.js +19 -0
- package/build/cjs/lib/dhis2-connection-status/is-ping-available.js +43 -0
- package/build/cjs/lib/dhis2-connection-status/is-ping-available.test.js +76 -0
- package/build/cjs/lib/dhis2-connection-status/smart-interval.js +211 -0
- package/build/cjs/lib/dhis2-connection-status/use-ping-query.js +21 -0
- package/build/cjs/lib/global-state-service.js +16 -11
- package/build/cjs/lib/{online-status.js → network-status.js} +8 -5
- package/build/cjs/lib/offline-interface.js +7 -4
- package/build/cjs/lib/offline-provider.js +9 -5
- package/build/cjs/lib/online-status-message.js +5 -4
- package/build/cjs/utils/render-counter.js +6 -4
- package/build/cjs/utils/test-mocks.js +17 -10
- package/build/es/__tests__/integration.test.js +37 -24
- package/build/es/index.js +5 -3
- package/build/es/lib/__tests__/clear-sensitive-caches.test.js +5 -1
- package/build/es/lib/__tests__/{online-status.test.js → network-status.test.js} +35 -14
- package/build/es/lib/__tests__/offline-provider.test.js +5 -1
- package/build/es/lib/__tests__/use-cacheable-section.test.js +77 -34
- package/build/es/lib/__tests__/{use-online-staus-message.test.js → use-online-status-message.test.js} +8 -5
- package/build/es/lib/cacheable-section-state.js +14 -10
- package/build/es/lib/cacheable-section.js +13 -11
- package/build/es/lib/clear-sensitive-caches.js +8 -4
- package/build/es/lib/dhis2-connection-status/dev-debug-log.js +20 -0
- package/build/es/lib/dhis2-connection-status/dhis2-connection-status.js +194 -0
- package/build/es/lib/dhis2-connection-status/dhis2-connection-status.test.js +831 -0
- package/build/es/lib/dhis2-connection-status/index.js +1 -0
- package/build/es/lib/dhis2-connection-status/is-ping-available.js +36 -0
- package/build/es/lib/dhis2-connection-status/is-ping-available.test.js +73 -0
- package/build/es/lib/dhis2-connection-status/smart-interval.js +199 -0
- package/build/es/lib/dhis2-connection-status/use-ping-query.js +12 -0
- package/build/es/lib/global-state-service.js +15 -10
- package/build/es/lib/{online-status.js → network-status.js} +7 -4
- package/build/es/lib/offline-interface.js +7 -4
- package/build/es/lib/offline-provider.js +8 -5
- package/build/es/lib/online-status-message.js +4 -3
- package/build/es/utils/render-counter.js +6 -4
- package/build/es/utils/test-mocks.js +16 -9
- package/build/types/index.d.ts +2 -1
- package/build/types/lib/dhis2-connection-status/dev-debug-log.d.ts +9 -0
- package/build/types/lib/dhis2-connection-status/dhis2-connection-status.d.ts +28 -0
- package/build/types/lib/dhis2-connection-status/index.d.ts +1 -0
- package/build/types/lib/dhis2-connection-status/is-ping-available.d.ts +14 -0
- package/build/types/lib/dhis2-connection-status/smart-interval.d.ts +18 -0
- package/build/types/lib/dhis2-connection-status/use-ping-query.d.ts +1 -0
- package/build/types/lib/{online-status.d.ts → network-status.d.ts} +3 -3
- package/build/types/types.d.ts +6 -0
- package/build/types/utils/test-mocks.d.ts +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { useConfig } from '@dhis2/app-service-config';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import React, { useCallback, useState, useRef, useMemo, useEffect, useContext } from 'react';
|
|
4
|
+
import { useOfflineInterface } from '../offline-interface';
|
|
5
|
+
import { devDebugLog } from './dev-debug-log';
|
|
6
|
+
import { isPingAvailable } from './is-ping-available';
|
|
7
|
+
import createSmartInterval from './smart-interval';
|
|
8
|
+
import { usePingQuery } from './use-ping-query'; // Utils for saving 'last connected' datetime in local storage
|
|
9
|
+
|
|
10
|
+
const lastConnectedKey = 'dhis2.lastConnected';
|
|
11
|
+
export const getLastConnectedKey = appName => appName ? `${lastConnectedKey}.${appName}` : lastConnectedKey;
|
|
12
|
+
|
|
13
|
+
const updateLastConnected = appName => {
|
|
14
|
+
// use Date.now() because it's easier to mock for easier unit testing
|
|
15
|
+
const now = new Date(Date.now());
|
|
16
|
+
localStorage.setItem(getLastConnectedKey(appName), now.toUTCString());
|
|
17
|
+
return now;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getLastConnected = appName => {
|
|
21
|
+
const lastConnected = localStorage.getItem(getLastConnectedKey(appName));
|
|
22
|
+
return lastConnected ? new Date(lastConnected) : null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const clearLastConnected = appName => {
|
|
26
|
+
localStorage.removeItem(getLastConnectedKey(appName));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const Dhis2ConnectionStatusContext = /*#__PURE__*/React.createContext({
|
|
30
|
+
isConnected: true,
|
|
31
|
+
isDisconnected: false,
|
|
32
|
+
lastConnected: null
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Provides a boolean indicating client's connection to the DHIS2 server,
|
|
36
|
+
* which is different from connection to the internet.
|
|
37
|
+
*
|
|
38
|
+
* The context provider subscribes to messages from the SW tracking successes
|
|
39
|
+
* and failures of requests to the DHIS2 server to determine connection status,
|
|
40
|
+
* and then will initiate periodic pings if there are no incidental requests in
|
|
41
|
+
* order to check the connection consistently
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
45
|
+
let {
|
|
46
|
+
children
|
|
47
|
+
} = _ref;
|
|
48
|
+
const offlineInterface = useOfflineInterface();
|
|
49
|
+
const {
|
|
50
|
+
appName,
|
|
51
|
+
serverVersion
|
|
52
|
+
} = useConfig(); // The offline interface persists the latest update from the SW so that
|
|
53
|
+
// this hook can initialize to an accurate value. The App Adapter in the
|
|
54
|
+
// platform waits for this value to be populated before rendering the
|
|
55
|
+
// the App Runtime provider (including this), but if that is not done,
|
|
56
|
+
// `latestIsConnected` may be `null` depending on the outcome of race
|
|
57
|
+
// conditions between the SW and the React component tree.
|
|
58
|
+
|
|
59
|
+
const [isConnected, setIsConnected] = useState(offlineInterface.latestIsConnected);
|
|
60
|
+
const ping = usePingQuery();
|
|
61
|
+
const smartIntervalRef = useRef(null);
|
|
62
|
+
/**
|
|
63
|
+
* Update state, reset ping backoff if changed, and update
|
|
64
|
+
* the lastConnected value in localStorage
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
const updateConnectedState = useCallback(newIsConnected => {
|
|
68
|
+
// use 'set' with a function as param to get latest isConnected
|
|
69
|
+
// without needing it as a dependency for useCallback
|
|
70
|
+
setIsConnected(prevIsConnected => {
|
|
71
|
+
devDebugLog('[D2CS] updating state:', {
|
|
72
|
+
prevIsConnected,
|
|
73
|
+
newIsConnected
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (newIsConnected !== prevIsConnected) {
|
|
77
|
+
var _smartIntervalRef$cur;
|
|
78
|
+
|
|
79
|
+
// if value changed, reset ping interval to initial delay
|
|
80
|
+
(_smartIntervalRef$cur = smartIntervalRef.current) === null || _smartIntervalRef$cur === void 0 ? void 0 : _smartIntervalRef$cur.reset();
|
|
81
|
+
|
|
82
|
+
if (newIsConnected) {
|
|
83
|
+
// Need to clear this here so it doesn't affect another
|
|
84
|
+
// session that starts while offline
|
|
85
|
+
clearLastConnected(appName);
|
|
86
|
+
} else {
|
|
87
|
+
updateLastConnected(appName);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return newIsConnected;
|
|
92
|
+
});
|
|
93
|
+
}, [appName]); // Note that the SW is configured to not cache ping requests and won't
|
|
94
|
+
// trigger `handleChange` below to avoid redundant signals. This also
|
|
95
|
+
// helps to detect the connectivity status when the SW is not available
|
|
96
|
+
// for some reason (maybe private browsing, first installation, or
|
|
97
|
+
// insecure browser context)
|
|
98
|
+
|
|
99
|
+
const pingAndHandleStatus = useCallback(() => {
|
|
100
|
+
return ping().then(() => {
|
|
101
|
+
// Ping is successful; set 'connected'
|
|
102
|
+
updateConnectedState(true);
|
|
103
|
+
}).catch(err => {
|
|
104
|
+
console.error('Ping failed:', err.message);
|
|
105
|
+
updateConnectedState(false);
|
|
106
|
+
});
|
|
107
|
+
}, [ping, updateConnectedState]);
|
|
108
|
+
/** Called when SW reports updates from incidental network traffic */
|
|
109
|
+
|
|
110
|
+
const onUpdate = useCallback((_ref2) => {
|
|
111
|
+
var _smartIntervalRef$cur2;
|
|
112
|
+
|
|
113
|
+
let {
|
|
114
|
+
isConnected: newIsConnected
|
|
115
|
+
} = _ref2;
|
|
116
|
+
devDebugLog('[D2CS] handling update from sw'); // Snooze ping timer to reduce pings since we know state from SW
|
|
117
|
+
|
|
118
|
+
(_smartIntervalRef$cur2 = smartIntervalRef.current) === null || _smartIntervalRef$cur2 === void 0 ? void 0 : _smartIntervalRef$cur2.snooze();
|
|
119
|
+
updateConnectedState(newIsConnected);
|
|
120
|
+
}, [updateConnectedState]);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
// If the /api/ping endpoint is not available on this instance, skip
|
|
123
|
+
// pinging with the smart interval. Just use the service worker
|
|
124
|
+
if (!serverVersion || !isPingAvailable(serverVersion)) {
|
|
125
|
+
return;
|
|
126
|
+
} // Only create the smart interval once
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
const smartInterval = createSmartInterval({
|
|
130
|
+
// don't ping if window isn't focused or visible
|
|
131
|
+
initialPauseValue: !document.hasFocus() || document.visibilityState !== 'visible',
|
|
132
|
+
callback: pingAndHandleStatus
|
|
133
|
+
});
|
|
134
|
+
smartIntervalRef.current = smartInterval;
|
|
135
|
+
|
|
136
|
+
const handleBlur = () => smartInterval.pause();
|
|
137
|
+
|
|
138
|
+
const handleFocus = () => smartInterval.resume(); // On offline event, ping immediately to test server connection.
|
|
139
|
+
// Only do this when going offline -- it's theoretically no-cost
|
|
140
|
+
// for both online and offline servers. Pinging when going online
|
|
141
|
+
// can be costly for clients connecting over the internet to online
|
|
142
|
+
// servers.
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
const handleOffline = () => smartInterval.invokeCallbackImmediately();
|
|
146
|
+
|
|
147
|
+
window.addEventListener('blur', handleBlur);
|
|
148
|
+
window.addEventListener('focus', handleFocus);
|
|
149
|
+
window.addEventListener('offline', handleOffline);
|
|
150
|
+
return () => {
|
|
151
|
+
window.removeEventListener('blur', handleBlur);
|
|
152
|
+
window.removeEventListener('focus', handleFocus);
|
|
153
|
+
window.removeEventListener('offline', handleOffline); // clean up smart interval
|
|
154
|
+
|
|
155
|
+
smartInterval.clear();
|
|
156
|
+
};
|
|
157
|
+
}, [pingAndHandleStatus, serverVersion]);
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const unsubscribe = offlineInterface.subscribeToDhis2ConnectionStatus({
|
|
160
|
+
onUpdate
|
|
161
|
+
});
|
|
162
|
+
return () => {
|
|
163
|
+
unsubscribe();
|
|
164
|
+
};
|
|
165
|
+
}, [offlineInterface, onUpdate]); // Memoize this value to prevent unnecessary rerenders of context provider
|
|
166
|
+
|
|
167
|
+
const contextValue = useMemo(() => ({
|
|
168
|
+
// in the unlikely circumstance that offlineInterface.latestIsConnected
|
|
169
|
+
// is `null` when this initializes, fail safe by defaulting to
|
|
170
|
+
// `isConnected: false`
|
|
171
|
+
isConnected: Boolean(isConnected),
|
|
172
|
+
isDisconnected: !isConnected,
|
|
173
|
+
lastConnected: isConnected ? null : // Only evaluate if disconnected, since local storage
|
|
174
|
+
// is synchronous and disk-based.
|
|
175
|
+
// If lastConnected is not set in localStorage though, set it.
|
|
176
|
+
// (relevant on startup)
|
|
177
|
+
getLastConnected(appName) || updateLastConnected(appName)
|
|
178
|
+
}), [isConnected, appName]);
|
|
179
|
+
return /*#__PURE__*/React.createElement(Dhis2ConnectionStatusContext.Provider, {
|
|
180
|
+
value: contextValue
|
|
181
|
+
}, children);
|
|
182
|
+
};
|
|
183
|
+
Dhis2ConnectionStatusProvider.propTypes = {
|
|
184
|
+
children: PropTypes.node
|
|
185
|
+
};
|
|
186
|
+
export const useDhis2ConnectionStatus = () => {
|
|
187
|
+
const context = useContext(Dhis2ConnectionStatusContext);
|
|
188
|
+
|
|
189
|
+
if (!context) {
|
|
190
|
+
throw new Error('useDhis2ConnectionStatus must be used within a Dhis2ConnectionStatus provider');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return context;
|
|
194
|
+
};
|