@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.
Files changed (61) hide show
  1. package/build/cjs/__tests__/integration.test.js +37 -24
  2. package/build/cjs/index.js +18 -10
  3. package/build/cjs/lib/__tests__/clear-sensitive-caches.test.js +5 -1
  4. package/build/cjs/lib/__tests__/{online-status.test.js → network-status.test.js} +41 -20
  5. package/build/cjs/lib/__tests__/offline-provider.test.js +5 -1
  6. package/build/cjs/lib/__tests__/use-cacheable-section.test.js +77 -35
  7. package/build/cjs/lib/__tests__/{use-online-staus-message.test.js → use-online-status-message.test.js} +8 -5
  8. package/build/cjs/lib/cacheable-section-state.js +17 -13
  9. package/build/cjs/lib/cacheable-section.js +14 -12
  10. package/build/cjs/lib/clear-sensitive-caches.js +9 -5
  11. package/build/cjs/lib/dhis2-connection-status/dev-debug-log.js +26 -0
  12. package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.js +224 -0
  13. package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.test.js +841 -0
  14. package/build/cjs/lib/dhis2-connection-status/index.js +19 -0
  15. package/build/cjs/lib/dhis2-connection-status/is-ping-available.js +43 -0
  16. package/build/cjs/lib/dhis2-connection-status/is-ping-available.test.js +76 -0
  17. package/build/cjs/lib/dhis2-connection-status/smart-interval.js +211 -0
  18. package/build/cjs/lib/dhis2-connection-status/use-ping-query.js +21 -0
  19. package/build/cjs/lib/global-state-service.js +16 -11
  20. package/build/cjs/lib/{online-status.js → network-status.js} +8 -5
  21. package/build/cjs/lib/offline-interface.js +7 -4
  22. package/build/cjs/lib/offline-provider.js +9 -5
  23. package/build/cjs/lib/online-status-message.js +5 -4
  24. package/build/cjs/utils/render-counter.js +6 -4
  25. package/build/cjs/utils/test-mocks.js +17 -10
  26. package/build/es/__tests__/integration.test.js +37 -24
  27. package/build/es/index.js +5 -3
  28. package/build/es/lib/__tests__/clear-sensitive-caches.test.js +5 -1
  29. package/build/es/lib/__tests__/{online-status.test.js → network-status.test.js} +35 -14
  30. package/build/es/lib/__tests__/offline-provider.test.js +5 -1
  31. package/build/es/lib/__tests__/use-cacheable-section.test.js +77 -34
  32. package/build/es/lib/__tests__/{use-online-staus-message.test.js → use-online-status-message.test.js} +8 -5
  33. package/build/es/lib/cacheable-section-state.js +14 -10
  34. package/build/es/lib/cacheable-section.js +13 -11
  35. package/build/es/lib/clear-sensitive-caches.js +8 -4
  36. package/build/es/lib/dhis2-connection-status/dev-debug-log.js +20 -0
  37. package/build/es/lib/dhis2-connection-status/dhis2-connection-status.js +194 -0
  38. package/build/es/lib/dhis2-connection-status/dhis2-connection-status.test.js +831 -0
  39. package/build/es/lib/dhis2-connection-status/index.js +1 -0
  40. package/build/es/lib/dhis2-connection-status/is-ping-available.js +36 -0
  41. package/build/es/lib/dhis2-connection-status/is-ping-available.test.js +73 -0
  42. package/build/es/lib/dhis2-connection-status/smart-interval.js +199 -0
  43. package/build/es/lib/dhis2-connection-status/use-ping-query.js +12 -0
  44. package/build/es/lib/global-state-service.js +15 -10
  45. package/build/es/lib/{online-status.js → network-status.js} +7 -4
  46. package/build/es/lib/offline-interface.js +7 -4
  47. package/build/es/lib/offline-provider.js +8 -5
  48. package/build/es/lib/online-status-message.js +4 -3
  49. package/build/es/utils/render-counter.js +6 -4
  50. package/build/es/utils/test-mocks.js +16 -9
  51. package/build/types/index.d.ts +2 -1
  52. package/build/types/lib/dhis2-connection-status/dev-debug-log.d.ts +9 -0
  53. package/build/types/lib/dhis2-connection-status/dhis2-connection-status.d.ts +28 -0
  54. package/build/types/lib/dhis2-connection-status/index.d.ts +1 -0
  55. package/build/types/lib/dhis2-connection-status/is-ping-available.d.ts +14 -0
  56. package/build/types/lib/dhis2-connection-status/smart-interval.d.ts +18 -0
  57. package/build/types/lib/dhis2-connection-status/use-ping-query.d.ts +1 -0
  58. package/build/types/lib/{online-status.d.ts → network-status.d.ts} +3 -3
  59. package/build/types/types.d.ts +6 -0
  60. package/build/types/utils/test-mocks.d.ts +2 -0
  61. 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
- recordingTimeoutDelay = 1000,
68
- onStarted,
69
- onCompleted,
70
- onError
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
- id,
131
- loadingMask,
132
- children
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
- name
36
- }) => name === dbName)) {
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(dbName = SECTIONS_DB) {
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;