@dhis2/app-service-offline 3.7.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
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.useCacheableSection = useCacheableSection;
|
|
7
6
|
exports.CacheableSection = CacheableSection;
|
|
7
|
+
exports.useCacheableSection = useCacheableSection;
|
|
8
8
|
|
|
9
9
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
10
10
|
|
|
@@ -63,12 +63,13 @@ function useCacheableSection(id) {
|
|
|
63
63
|
};
|
|
64
64
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
65
65
|
|
|
66
|
-
function startRecording({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
function startRecording() {
|
|
67
|
+
let {
|
|
68
|
+
recordingTimeoutDelay = 1000,
|
|
69
|
+
onStarted,
|
|
70
|
+
onCompleted,
|
|
71
|
+
onError
|
|
72
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
72
73
|
// This promise resolving means that the message to the service worker
|
|
73
74
|
// to start recording was successful. Waiting for resolution prevents
|
|
74
75
|
// unnecessarily rerendering the whole component in case of an error
|
|
@@ -126,11 +127,12 @@ function useCacheableSection(id) {
|
|
|
126
127
|
* intended to prevent other interaction with the app that might interfere
|
|
127
128
|
* with the recording process.
|
|
128
129
|
*/
|
|
129
|
-
function CacheableSection({
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
function CacheableSection(_ref) {
|
|
131
|
+
let {
|
|
132
|
+
id,
|
|
133
|
+
loadingMask,
|
|
134
|
+
children
|
|
135
|
+
} = _ref;
|
|
134
136
|
// Accesses recording state that useCacheableSection controls
|
|
135
137
|
const {
|
|
136
138
|
recordingState
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.clearSensitiveCaches = clearSensitiveCaches;
|
|
7
6
|
exports.SECTIONS_STORE = exports.SECTIONS_DB = void 0;
|
|
7
|
+
exports.clearSensitiveCaches = clearSensitiveCaches;
|
|
8
8
|
// IndexedDB names; should be the same as in @dhis2/pwa
|
|
9
9
|
const SECTIONS_DB = 'sections-db';
|
|
10
10
|
exports.SECTIONS_DB = SECTIONS_DB;
|
|
@@ -31,9 +31,12 @@ const clearDB = async dbName => {
|
|
|
31
31
|
|
|
32
32
|
const dbs = await window.indexedDB.databases();
|
|
33
33
|
|
|
34
|
-
if (!dbs.some(({
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if (!dbs.some((_ref) => {
|
|
35
|
+
let {
|
|
36
|
+
name
|
|
37
|
+
} = _ref;
|
|
38
|
+
return name === dbName;
|
|
39
|
+
})) {
|
|
37
40
|
// Sections-db is not created; nothing to do here
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
@@ -68,7 +71,8 @@ const clearDB = async dbName => {
|
|
|
68
71
|
*/
|
|
69
72
|
|
|
70
73
|
|
|
71
|
-
async function clearSensitiveCaches(
|
|
74
|
+
async function clearSensitiveCaches() {
|
|
75
|
+
let dbName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SECTIONS_DB;
|
|
72
76
|
console.debug('Clearing sensitive caches');
|
|
73
77
|
let cacheKeys; // caches.keys can fail in insecure contexts, see:
|
|
74
78
|
// https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.devDebugLog = devDebugLog;
|
|
7
|
+
const shouldLog = localStorage.getItem('dhis2.debugConnectionStatus');
|
|
8
|
+
|
|
9
|
+
if (shouldLog) {
|
|
10
|
+
console.log('Logging for dhis2ConnectionStatus is enabled. Remove the `dhis2.debugConnectionStatus` item in localStorage to disable logging.');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* This can be used to log info if the `dhis2.debugConnectionStatus` value
|
|
14
|
+
* in localStorage is set to a truthy value during development.
|
|
15
|
+
* Set the value manually and refresh the page to see the logs.
|
|
16
|
+
*
|
|
17
|
+
* The behavior of the connection status can be quite hard to inspect without
|
|
18
|
+
* logs, but the logs are quite chatty and should be omitted normally.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function devDebugLog() {
|
|
23
|
+
if (shouldLog) {
|
|
24
|
+
console.log(...arguments);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useDhis2ConnectionStatus = exports.getLastConnectedKey = exports.Dhis2ConnectionStatusProvider = void 0;
|
|
7
|
+
|
|
8
|
+
var _appServiceConfig = require("@dhis2/app-service-config");
|
|
9
|
+
|
|
10
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
11
|
+
|
|
12
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
13
|
+
|
|
14
|
+
var _offlineInterface = require("../offline-interface");
|
|
15
|
+
|
|
16
|
+
var _devDebugLog = require("./dev-debug-log");
|
|
17
|
+
|
|
18
|
+
var _isPingAvailable = require("./is-ping-available");
|
|
19
|
+
|
|
20
|
+
var _smartInterval = _interopRequireDefault(require("./smart-interval"));
|
|
21
|
+
|
|
22
|
+
var _usePingQuery = require("./use-ping-query");
|
|
23
|
+
|
|
24
|
+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
25
|
+
|
|
26
|
+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
27
|
+
|
|
28
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
+
|
|
30
|
+
// Utils for saving 'last connected' datetime in local storage
|
|
31
|
+
const lastConnectedKey = 'dhis2.lastConnected';
|
|
32
|
+
|
|
33
|
+
const getLastConnectedKey = appName => appName ? `${lastConnectedKey}.${appName}` : lastConnectedKey;
|
|
34
|
+
|
|
35
|
+
exports.getLastConnectedKey = getLastConnectedKey;
|
|
36
|
+
|
|
37
|
+
const updateLastConnected = appName => {
|
|
38
|
+
// use Date.now() because it's easier to mock for easier unit testing
|
|
39
|
+
const now = new Date(Date.now());
|
|
40
|
+
localStorage.setItem(getLastConnectedKey(appName), now.toUTCString());
|
|
41
|
+
return now;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getLastConnected = appName => {
|
|
45
|
+
const lastConnected = localStorage.getItem(getLastConnectedKey(appName));
|
|
46
|
+
return lastConnected ? new Date(lastConnected) : null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const clearLastConnected = appName => {
|
|
50
|
+
localStorage.removeItem(getLastConnectedKey(appName));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const Dhis2ConnectionStatusContext = /*#__PURE__*/_react.default.createContext({
|
|
54
|
+
isConnected: true,
|
|
55
|
+
isDisconnected: false,
|
|
56
|
+
lastConnected: null
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Provides a boolean indicating client's connection to the DHIS2 server,
|
|
60
|
+
* which is different from connection to the internet.
|
|
61
|
+
*
|
|
62
|
+
* The context provider subscribes to messages from the SW tracking successes
|
|
63
|
+
* and failures of requests to the DHIS2 server to determine connection status,
|
|
64
|
+
* and then will initiate periodic pings if there are no incidental requests in
|
|
65
|
+
* order to check the connection consistently
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const Dhis2ConnectionStatusProvider = (_ref) => {
|
|
70
|
+
let {
|
|
71
|
+
children
|
|
72
|
+
} = _ref;
|
|
73
|
+
const offlineInterface = (0, _offlineInterface.useOfflineInterface)();
|
|
74
|
+
const {
|
|
75
|
+
appName,
|
|
76
|
+
serverVersion
|
|
77
|
+
} = (0, _appServiceConfig.useConfig)(); // The offline interface persists the latest update from the SW so that
|
|
78
|
+
// this hook can initialize to an accurate value. The App Adapter in the
|
|
79
|
+
// platform waits for this value to be populated before rendering the
|
|
80
|
+
// the App Runtime provider (including this), but if that is not done,
|
|
81
|
+
// `latestIsConnected` may be `null` depending on the outcome of race
|
|
82
|
+
// conditions between the SW and the React component tree.
|
|
83
|
+
|
|
84
|
+
const [isConnected, setIsConnected] = (0, _react.useState)(offlineInterface.latestIsConnected);
|
|
85
|
+
const ping = (0, _usePingQuery.usePingQuery)();
|
|
86
|
+
const smartIntervalRef = (0, _react.useRef)(null);
|
|
87
|
+
/**
|
|
88
|
+
* Update state, reset ping backoff if changed, and update
|
|
89
|
+
* the lastConnected value in localStorage
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
const updateConnectedState = (0, _react.useCallback)(newIsConnected => {
|
|
93
|
+
// use 'set' with a function as param to get latest isConnected
|
|
94
|
+
// without needing it as a dependency for useCallback
|
|
95
|
+
setIsConnected(prevIsConnected => {
|
|
96
|
+
(0, _devDebugLog.devDebugLog)('[D2CS] updating state:', {
|
|
97
|
+
prevIsConnected,
|
|
98
|
+
newIsConnected
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (newIsConnected !== prevIsConnected) {
|
|
102
|
+
var _smartIntervalRef$cur;
|
|
103
|
+
|
|
104
|
+
// if value changed, reset ping interval to initial delay
|
|
105
|
+
(_smartIntervalRef$cur = smartIntervalRef.current) === null || _smartIntervalRef$cur === void 0 ? void 0 : _smartIntervalRef$cur.reset();
|
|
106
|
+
|
|
107
|
+
if (newIsConnected) {
|
|
108
|
+
// Need to clear this here so it doesn't affect another
|
|
109
|
+
// session that starts while offline
|
|
110
|
+
clearLastConnected(appName);
|
|
111
|
+
} else {
|
|
112
|
+
updateLastConnected(appName);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return newIsConnected;
|
|
117
|
+
});
|
|
118
|
+
}, [appName]); // Note that the SW is configured to not cache ping requests and won't
|
|
119
|
+
// trigger `handleChange` below to avoid redundant signals. This also
|
|
120
|
+
// helps to detect the connectivity status when the SW is not available
|
|
121
|
+
// for some reason (maybe private browsing, first installation, or
|
|
122
|
+
// insecure browser context)
|
|
123
|
+
|
|
124
|
+
const pingAndHandleStatus = (0, _react.useCallback)(() => {
|
|
125
|
+
return ping().then(() => {
|
|
126
|
+
// Ping is successful; set 'connected'
|
|
127
|
+
updateConnectedState(true);
|
|
128
|
+
}).catch(err => {
|
|
129
|
+
console.error('Ping failed:', err.message);
|
|
130
|
+
updateConnectedState(false);
|
|
131
|
+
});
|
|
132
|
+
}, [ping, updateConnectedState]);
|
|
133
|
+
/** Called when SW reports updates from incidental network traffic */
|
|
134
|
+
|
|
135
|
+
const onUpdate = (0, _react.useCallback)((_ref2) => {
|
|
136
|
+
var _smartIntervalRef$cur2;
|
|
137
|
+
|
|
138
|
+
let {
|
|
139
|
+
isConnected: newIsConnected
|
|
140
|
+
} = _ref2;
|
|
141
|
+
(0, _devDebugLog.devDebugLog)('[D2CS] handling update from sw'); // Snooze ping timer to reduce pings since we know state from SW
|
|
142
|
+
|
|
143
|
+
(_smartIntervalRef$cur2 = smartIntervalRef.current) === null || _smartIntervalRef$cur2 === void 0 ? void 0 : _smartIntervalRef$cur2.snooze();
|
|
144
|
+
updateConnectedState(newIsConnected);
|
|
145
|
+
}, [updateConnectedState]);
|
|
146
|
+
(0, _react.useEffect)(() => {
|
|
147
|
+
// If the /api/ping endpoint is not available on this instance, skip
|
|
148
|
+
// pinging with the smart interval. Just use the service worker
|
|
149
|
+
if (!serverVersion || !(0, _isPingAvailable.isPingAvailable)(serverVersion)) {
|
|
150
|
+
return;
|
|
151
|
+
} // Only create the smart interval once
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
const smartInterval = (0, _smartInterval.default)({
|
|
155
|
+
// don't ping if window isn't focused or visible
|
|
156
|
+
initialPauseValue: !document.hasFocus() || document.visibilityState !== 'visible',
|
|
157
|
+
callback: pingAndHandleStatus
|
|
158
|
+
});
|
|
159
|
+
smartIntervalRef.current = smartInterval;
|
|
160
|
+
|
|
161
|
+
const handleBlur = () => smartInterval.pause();
|
|
162
|
+
|
|
163
|
+
const handleFocus = () => smartInterval.resume(); // On offline event, ping immediately to test server connection.
|
|
164
|
+
// Only do this when going offline -- it's theoretically no-cost
|
|
165
|
+
// for both online and offline servers. Pinging when going online
|
|
166
|
+
// can be costly for clients connecting over the internet to online
|
|
167
|
+
// servers.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
const handleOffline = () => smartInterval.invokeCallbackImmediately();
|
|
171
|
+
|
|
172
|
+
window.addEventListener('blur', handleBlur);
|
|
173
|
+
window.addEventListener('focus', handleFocus);
|
|
174
|
+
window.addEventListener('offline', handleOffline);
|
|
175
|
+
return () => {
|
|
176
|
+
window.removeEventListener('blur', handleBlur);
|
|
177
|
+
window.removeEventListener('focus', handleFocus);
|
|
178
|
+
window.removeEventListener('offline', handleOffline); // clean up smart interval
|
|
179
|
+
|
|
180
|
+
smartInterval.clear();
|
|
181
|
+
};
|
|
182
|
+
}, [pingAndHandleStatus, serverVersion]);
|
|
183
|
+
(0, _react.useEffect)(() => {
|
|
184
|
+
const unsubscribe = offlineInterface.subscribeToDhis2ConnectionStatus({
|
|
185
|
+
onUpdate
|
|
186
|
+
});
|
|
187
|
+
return () => {
|
|
188
|
+
unsubscribe();
|
|
189
|
+
};
|
|
190
|
+
}, [offlineInterface, onUpdate]); // Memoize this value to prevent unnecessary rerenders of context provider
|
|
191
|
+
|
|
192
|
+
const contextValue = (0, _react.useMemo)(() => ({
|
|
193
|
+
// in the unlikely circumstance that offlineInterface.latestIsConnected
|
|
194
|
+
// is `null` when this initializes, fail safe by defaulting to
|
|
195
|
+
// `isConnected: false`
|
|
196
|
+
isConnected: Boolean(isConnected),
|
|
197
|
+
isDisconnected: !isConnected,
|
|
198
|
+
lastConnected: isConnected ? null : // Only evaluate if disconnected, since local storage
|
|
199
|
+
// is synchronous and disk-based.
|
|
200
|
+
// If lastConnected is not set in localStorage though, set it.
|
|
201
|
+
// (relevant on startup)
|
|
202
|
+
getLastConnected(appName) || updateLastConnected(appName)
|
|
203
|
+
}), [isConnected, appName]);
|
|
204
|
+
return /*#__PURE__*/_react.default.createElement(Dhis2ConnectionStatusContext.Provider, {
|
|
205
|
+
value: contextValue
|
|
206
|
+
}, children);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
exports.Dhis2ConnectionStatusProvider = Dhis2ConnectionStatusProvider;
|
|
210
|
+
Dhis2ConnectionStatusProvider.propTypes = {
|
|
211
|
+
children: _propTypes.default.node
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const useDhis2ConnectionStatus = () => {
|
|
215
|
+
const context = (0, _react.useContext)(Dhis2ConnectionStatusContext);
|
|
216
|
+
|
|
217
|
+
if (!context) {
|
|
218
|
+
throw new Error('useDhis2ConnectionStatus must be used within a Dhis2ConnectionStatus provider');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return context;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
exports.useDhis2ConnectionStatus = useDhis2ConnectionStatus;
|