@dhis2/app-service-offline 3.8.0 → 3.9.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.
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
@@ -0,0 +1 @@
1
+ export { useDhis2ConnectionStatus, Dhis2ConnectionStatusProvider } from './dhis2-connection-status';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Checks the server version to see if the /api/ping endpoint is available for
3
+ * this version.
4
+ *
5
+ * The endpoint was released for versions 2.37.10, 2.38.3, 2.39.2, and 2.40.0
6
+ * (see DHIS2-14531). All versions below that are considered unsupported
7
+ *
8
+ * If the patchVersion is undefined, it's assumed to be a dev server that's
9
+ * newer than the fix versions listed above
10
+ *
11
+ * Major versions above 2 aren't supported 🤷‍♂️
12
+ */
13
+ export function isPingAvailable(serverVersion) {
14
+ if (!serverVersion) {
15
+ return false;
16
+ }
17
+
18
+ const {
19
+ minor,
20
+ patch
21
+ } = serverVersion;
22
+
23
+ switch (minor) {
24
+ case 39:
25
+ return patch === undefined || patch >= 2;
26
+
27
+ case 38:
28
+ return patch === undefined || patch >= 3;
29
+
30
+ case 37:
31
+ return patch === undefined || patch >= 10;
32
+
33
+ default:
34
+ return minor >= 40;
35
+ }
36
+ }
@@ -0,0 +1,73 @@
1
+ import { isPingAvailable } from './is-ping-available';
2
+ const fixedVersions = [{
3
+ full: 'unimportant',
4
+ major: 2,
5
+ minor: 40,
6
+ patch: 0
7
+ }, {
8
+ full: 'unimportant',
9
+ major: 2,
10
+ minor: 39,
11
+ patch: 2
12
+ }, {
13
+ full: 'unimportant',
14
+ major: 2,
15
+ minor: 38,
16
+ patch: 3
17
+ }, {
18
+ full: 'unimportant',
19
+ major: 2,
20
+ minor: 37,
21
+ patch: 10
22
+ }, {
23
+ full: 'unimportant',
24
+ major: 2,
25
+ minor: 40,
26
+ tag: 'SNAPSHOT'
27
+ }, {
28
+ full: 'unimportant',
29
+ major: 2,
30
+ minor: 3291,
31
+ patch: 0
32
+ }];
33
+ const unsupportedVersions = [{
34
+ full: 'unimportant',
35
+ major: 2,
36
+ minor: 39,
37
+ patch: 1
38
+ }, {
39
+ full: 'unimportant',
40
+ major: 2,
41
+ minor: 38,
42
+ patch: 2
43
+ }, {
44
+ full: 'unimportant',
45
+ major: 2,
46
+ minor: 37,
47
+ patch: 9
48
+ }, {
49
+ full: 'unimportant',
50
+ major: 2,
51
+ minor: 36,
52
+ patch: 12
53
+ }, {
54
+ full: 'unimportant',
55
+ major: 2,
56
+ minor: 35,
57
+ patch: 0
58
+ }, {
59
+ full: 'unimportant',
60
+ major: 2,
61
+ minor: 0,
62
+ patch: 0
63
+ }];
64
+ test('supported server versions pass', () => {
65
+ fixedVersions.forEach(version => {
66
+ expect(isPingAvailable(version)).toBe(true);
67
+ });
68
+ });
69
+ test('unsupported server versions fail', () => {
70
+ unsupportedVersions.forEach(version => {
71
+ expect(isPingAvailable(version)).toBe(false);
72
+ });
73
+ });
@@ -0,0 +1,199 @@
1
+ import { devDebugLog } from './dev-debug-log'; // Exported for tests
2
+
3
+ export const DEFAULT_INITIAL_DELAY_MS = 1000 * 30; // 30 sec
4
+
5
+ export const DEFAULT_MAX_DELAY_MS = 1000 * 60 * 5; // 5 min
6
+
7
+ export const DEFAULT_INCREMENT_FACTOR = 1.5;
8
+
9
+ const throwErrorIfNoCallbackIsProvided = () => {
10
+ throw new Error('Provide a callback');
11
+ };
12
+
13
+ export default function createSmartInterval() {
14
+ let {
15
+ initialDelay = DEFAULT_INITIAL_DELAY_MS,
16
+ maxDelay = DEFAULT_MAX_DELAY_MS,
17
+ delayIncrementFactor = DEFAULT_INCREMENT_FACTOR,
18
+ initialPauseValue = false,
19
+ callback = throwErrorIfNoCallbackIsProvided
20
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
21
+ const state = {
22
+ paused: initialPauseValue,
23
+ delay: initialDelay,
24
+ // Timeout types are weird; this dummy timeout helps fix them:
25
+ // (named to help debugging in tests)
26
+ timeout: setTimeout(function dummyTimeout() {
27
+ return;
28
+ }, 0),
29
+ standbyCallback: null
30
+ };
31
+ /** Increment delay by the increment factor, up to a max value */
32
+
33
+ function incrementDelay() {
34
+ const newDelay = Math.min(state.delay * delayIncrementFactor, maxDelay);
35
+ devDebugLog('[SI] incrementing delay', {
36
+ prev: state.delay,
37
+ new: newDelay
38
+ });
39
+ state.delay = newDelay;
40
+ }
41
+
42
+ function invokeCallbackAndHandleDelay() {
43
+ // Increment delay before calling callback, so callback can potentially
44
+ // reset the delay to initial before starting the next timeout
45
+ incrementDelay();
46
+ callback();
47
+ }
48
+
49
+ function clearTimeoutAndStart() {
50
+ devDebugLog('[SI] clearing and starting timeout', {
51
+ delay: state.delay
52
+ }); // Prevent parallel timeouts from occuring
53
+ // (weird note: `if (this.timeout) { clearTimeout(this.timeout) }`
54
+ // does NOT work for some reason)
55
+
56
+ clearTimeout(state.timeout); // A timeout is used instead of an interval for handling slow execution
57
+ // https://developer.mozilla.org/en-US/docs/Web/API/setInterval#ensure_that_execution_duration_is_shorter_than_interval_frequency
58
+
59
+ state.timeout = setTimeout(function callbackAndRestart() {
60
+ if (state.paused) {
61
+ devDebugLog('[SI] entering regular standby'); // If paused, prepare a 'standby callback' to be invoked when
62
+ // `resume()` is called (see its definition below).
63
+ // The timer will not be started again until the standbyCallback
64
+ // is invoked.
65
+
66
+ state.standbyCallback = () => {
67
+ invokeCallbackAndHandleDelay();
68
+ clearTimeoutAndStart();
69
+ };
70
+
71
+ return;
72
+ } // Otherwise, invoke callback
73
+
74
+
75
+ invokeCallbackAndHandleDelay(); // and start process over again
76
+
77
+ clearTimeoutAndStart();
78
+ }, state.delay);
79
+ }
80
+ /** Stop the interval. Used for cleaning up */
81
+
82
+
83
+ function clear() {
84
+ clearTimeout(state.timeout);
85
+ }
86
+ /**
87
+ * Invoke the provided callback immediately and start the timer over.
88
+ * The timeout to the next invocation will not be increased
89
+ * (unless the timer fully elapses while this interval is paused).
90
+ *
91
+ * If the interval is 'paused', it will not invoke the callback immediately,
92
+ * but enter a 'partial standby', which will invoke the callback upon
93
+ * resuming, but without incrementing the delay. If the regular timeout
94
+ * elapses while paused, the regular standby is entered, overwriting this
95
+ * partial standby.
96
+ */
97
+
98
+
99
+ function invokeCallbackImmediately() {
100
+ if (state.paused) {
101
+ if (state.standbyCallback === null) {
102
+ // If there is not an existing standbyCallback,
103
+ // set one to be called upon `resume()`
104
+ // (but don't overwrite a previous callback).
105
+ // See setTimeout call above too.
106
+ // The timed out function set in `clearTimeoutAndStart` may
107
+ // overwrite this callback if the timer elapses, so that the
108
+ // timeout delay gets incremented appropriately.
109
+ devDebugLog('[SI] entering standby without timer increment');
110
+
111
+ state.standbyCallback = () => {
112
+ // Invoke callback and start timer without incrementing
113
+ callback();
114
+ clearTimeoutAndStart();
115
+ };
116
+ } // Skip rest of execution while paused
117
+
118
+
119
+ return;
120
+ } // Invoke callback and start timer without incrementing
121
+
122
+
123
+ callback();
124
+ clearTimeoutAndStart();
125
+ }
126
+ /**
127
+ * Sets a 'paused' flag (doesn't yet stop the timer):
128
+ *
129
+ * If the main timer elapses or `invokeCallbackImmediately` is called
130
+ * while the interval is paused, the timer will not be started again.
131
+ * Instead, a callback function will be saved that will be called when
132
+ * `resume()` is called (see its definition below)
133
+ *
134
+ * This decreases execution activity while 'paused'
135
+ */
136
+
137
+
138
+ function pause() {
139
+ devDebugLog('[SI] pausing');
140
+ state.paused = true;
141
+ }
142
+ /**
143
+ * Removes 'paused' state
144
+ *
145
+ * If the interval is in 'standby', trigger the saved 'standbyCallback',
146
+ * which should start the interval timer again
147
+ */
148
+
149
+
150
+ function resume() {
151
+ devDebugLog('[SI] resuming', {
152
+ standbyCb: state.standbyCallback
153
+ }); // Clear paused state
154
+
155
+ state.paused = false; // If in standby, invoke the saved callback
156
+ // (invokeCallbackImmediately and clearTimeoutAndStart can set a
157
+ // standby callback)
158
+
159
+ if (state.standbyCallback !== null) {
160
+ state.standbyCallback(); // Remove existing standbyCallback
161
+
162
+ state.standbyCallback = null;
163
+ }
164
+ }
165
+ /**
166
+ * Restart the timer to the next callback invocation, using the current
167
+ * delay
168
+ *
169
+ * Expected to be called to delay a ping in response to incidental network
170
+ * traffic, for example
171
+ */
172
+
173
+
174
+ function snooze() {
175
+ devDebugLog('[SI] snoozing timeout');
176
+ clearTimeoutAndStart();
177
+ }
178
+ /**
179
+ * Cancels the current timeout and starts a new one with the initial delay
180
+ */
181
+
182
+
183
+ function reset() {
184
+ devDebugLog('[SI] resetting interval from beginning');
185
+ state.delay = initialDelay;
186
+ clearTimeoutAndStart();
187
+ } // Start the timer!
188
+
189
+
190
+ clearTimeoutAndStart();
191
+ return {
192
+ clear,
193
+ pause,
194
+ resume,
195
+ invokeCallbackImmediately,
196
+ snooze,
197
+ reset
198
+ };
199
+ }
@@ -0,0 +1,12 @@
1
+ import { useConfig } from '@dhis2/app-service-config';
2
+ import { useCallback } from 'react'; // This function has a separate file for easier mocking
3
+
4
+ export function usePingQuery() {
5
+ const {
6
+ baseUrl
7
+ } = useConfig(); // This endpoint doesn't need any extra headers or handling since it's
8
+ // public. It doesn't extend the user session. See DHIS2-14531
9
+
10
+ const ping = useCallback(() => fetch(baseUrl + '/api/ping'), [baseUrl]);
11
+ return ping;
12
+ }
@@ -7,7 +7,8 @@ import React, { useEffect, useCallback, useContext, useState } from 'react';
7
7
  // See more at https://github.com/amcgee/state-service-poc
8
8
  const identity = state => state;
9
9
 
10
- export const createStore = (initialState = {}) => {
10
+ export const createStore = function () {
11
+ let initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
11
12
  const subscriptions = new Set();
12
13
  let state = initialState;
13
14
  return {
@@ -31,17 +32,21 @@ const GlobalStateContext = /*#__PURE__*/React.createContext(createStore());
31
32
 
32
33
  const useGlobalStateStore = () => useContext(GlobalStateContext);
33
34
 
34
- export const GlobalStateProvider = ({
35
- store,
36
- children
37
- }) => /*#__PURE__*/React.createElement(GlobalStateContext.Provider, {
38
- value: store
39
- }, children);
35
+ export const GlobalStateProvider = (_ref) => {
36
+ let {
37
+ store,
38
+ children
39
+ } = _ref;
40
+ return /*#__PURE__*/React.createElement(GlobalStateContext.Provider, {
41
+ value: store
42
+ }, children);
43
+ };
40
44
  GlobalStateProvider.propTypes = {
41
45
  children: PropTypes.node,
42
46
  store: PropTypes.shape({})
43
47
  };
44
- export const useGlobalState = (selector = identity) => {
48
+ export const useGlobalState = function () {
49
+ let selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : identity;
45
50
  const store = useGlobalStateStore();
46
51
  const [selectedState, setSelectedState] = useState(selector(store.getState()));
47
52
  useEffect(() => {
@@ -66,7 +71,7 @@ export const useGlobalState = (selector = identity) => {
66
71
  };
67
72
  export function useGlobalStateMutation(mutationCreator) {
68
73
  const store = useGlobalStateStore();
69
- return useCallback((...args) => {
70
- store.mutate(mutationCreator(...args));
74
+ return useCallback(function () {
75
+ store.mutate(mutationCreator(...arguments));
71
76
  }, [mutationCreator, store]);
72
77
  }
@@ -18,15 +18,18 @@ const lastOnlineKey = 'dhis2.lastOnline'; // TODO: Add option to periodically pi
18
18
  * @returns {Object} `{ online: boolean, offline: boolean, lastOnline: Date | null }`
19
19
  */
20
20
 
21
- export function useOnlineStatus(options = {}) {
21
+ export function useNetworkStatus() {
22
22
  var _options$debounceDela;
23
23
 
24
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
24
25
  // initialize state to `navigator.onLine` value
25
26
  const [online, setOnline] = useState(navigator.onLine); // eslint-disable-next-line react-hooks/exhaustive-deps
26
27
 
27
- const updateState = useCallback(debounce(({
28
- type
29
- }) => {
28
+ const updateState = useCallback(debounce((_ref) => {
29
+ let {
30
+ type
31
+ } = _ref;
32
+
30
33
  if (type === 'online') {
31
34
  setOnline(true);
32
35
  } else if (type === 'offline') {
@@ -3,6 +3,8 @@ import React, { createContext, useContext } from 'react';
3
3
  // This is to prevent 'offlineInterface could be null' type-checking errors
4
4
  const noopOfflineInterface = {
5
5
  pwaEnabled: false,
6
+ latestIsConnected: false,
7
+ subscribeToDhis2ConnectionStatus: () => () => undefined,
6
8
  startRecording: async () => undefined,
7
9
  getCachedSections: async () => [],
8
10
  removeSection: async () => false
@@ -17,10 +19,11 @@ const OfflineInterfaceContext = /*#__PURE__*/createContext(noopOfflineInterface)
17
19
  * checks for service worker updates and, if updates are ready, prompts the
18
20
  * user with an alert to skip waiting and reload the page to use new content.
19
21
  */
20
- export function OfflineInterfaceProvider({
21
- offlineInterface,
22
- children
23
- }) {
22
+ export function OfflineInterfaceProvider(_ref) {
23
+ let {
24
+ offlineInterface,
25
+ children
26
+ } = _ref;
24
27
  return /*#__PURE__*/React.createElement(OfflineInterfaceContext.Provider, {
25
28
  value: offlineInterface
26
29
  }, children);
@@ -1,14 +1,17 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
3
  import { CacheableSectionProvider } from './cacheable-section-state';
4
+ import { Dhis2ConnectionStatusProvider } from './dhis2-connection-status';
4
5
  import { OfflineInterfaceProvider } from './offline-interface';
5
6
  import { OnlineStatusMessageProvider } from './online-status-message';
6
7
 
7
8
  /** A context provider for all the relevant offline contexts */
8
- export function OfflineProvider({
9
- offlineInterface,
10
- children
11
- }) {
9
+ export function OfflineProvider(_ref) {
10
+ let {
11
+ offlineInterface,
12
+ children
13
+ } = _ref;
14
+
12
15
  // If an offline interface is not provided, or if one is provided and PWA
13
16
  // is not enabled, skip adding context providers
14
17
  if (!offlineInterface || !offlineInterface.pwaEnabled) {
@@ -17,7 +20,7 @@ export function OfflineProvider({
17
20
 
18
21
  return /*#__PURE__*/React.createElement(OfflineInterfaceProvider, {
19
22
  offlineInterface: offlineInterface
20
- }, /*#__PURE__*/React.createElement(CacheableSectionProvider, null, /*#__PURE__*/React.createElement(OnlineStatusMessageProvider, null, children)));
23
+ }, /*#__PURE__*/React.createElement(Dhis2ConnectionStatusProvider, null, /*#__PURE__*/React.createElement(CacheableSectionProvider, null, /*#__PURE__*/React.createElement(OnlineStatusMessageProvider, null, children))));
21
24
  }
22
25
  OfflineProvider.propTypes = {
23
26
  children: PropTypes.node,
@@ -14,9 +14,10 @@ export const useOnlineStatusMessage = () => {
14
14
  setOnlineStatusMessage
15
15
  };
16
16
  };
17
- export const OnlineStatusMessageProvider = ({
18
- children
19
- }) => {
17
+ export const OnlineStatusMessageProvider = (_ref) => {
18
+ let {
19
+ children
20
+ } = _ref;
20
21
  const [onlineStatusMessage, setOnlineStatusMessage] = useState();
21
22
  return /*#__PURE__*/React.createElement(OnlineStatusMessageContext.Provider, {
22
23
  value: {
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
- export const RenderCounter = ({
3
- id,
4
- countsObj
5
- }) => {
2
+ export const RenderCounter = (_ref) => {
3
+ let {
4
+ id,
5
+ countsObj
6
+ } = _ref;
7
+
6
8
  if (!(id in countsObj)) {
7
9
  countsObj[id] = 0;
8
10
  }
@@ -1,7 +1,9 @@
1
- export const successfulRecordingMock = jest.fn().mockImplementation(async ({
2
- onStarted,
3
- onCompleted
4
- } = {}) => {
1
+ export const successfulRecordingMock = jest.fn().mockImplementation(async function () {
2
+ let {
3
+ onStarted,
4
+ onCompleted
5
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
6
+
5
7
  // in 100ms, call 'onStarted' callback (allows 'pending' state)
6
8
  if (onStarted) {
7
9
  setTimeout(onStarted, 100);
@@ -15,10 +17,12 @@ export const successfulRecordingMock = jest.fn().mockImplementation(async ({
15
17
 
16
18
  return Promise.resolve();
17
19
  });
18
- export const errorRecordingMock = jest.fn().mockImplementation(({
19
- onStarted,
20
- onError
21
- } = {}) => {
20
+ export const errorRecordingMock = jest.fn().mockImplementation(function () {
21
+ let {
22
+ onStarted,
23
+ onError
24
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25
+
22
26
  // in 100ms, call 'onStarted' callback (allows 'pending' state)
23
27
  if (onStarted) {
24
28
  setTimeout(onStarted, 100);
@@ -32,7 +36,10 @@ export const errorRecordingMock = jest.fn().mockImplementation(({
32
36
  export const failedMessageRecordingMock = jest.fn().mockRejectedValue(new Error('Failed message'));
33
37
  export const mockOfflineInterface = {
34
38
  pwaEnabled: true,
39
+ latestIsConnected: true,
35
40
  startRecording: successfulRecordingMock,
36
41
  getCachedSections: jest.fn().mockResolvedValue([]),
37
- removeSection: jest.fn().mockResolvedValue(true)
42
+ removeSection: jest.fn().mockResolvedValue(true),
43
+ // returns an unsubscribe function
44
+ subscribeToDhis2ConnectionStatus: jest.fn().mockReturnValue(() => undefined)
38
45
  };
@@ -1,6 +1,7 @@
1
1
  export { OfflineProvider } from './lib/offline-provider';
2
2
  export { CacheableSection, useCacheableSection } from './lib/cacheable-section';
3
3
  export { useCachedSections } from './lib/cacheable-section-state';
4
- export { useOnlineStatus } from './lib/online-status';
4
+ export { useNetworkStatus as useOnlineStatus } from './lib/network-status';
5
5
  export { useOnlineStatusMessage } from './lib/online-status-message';
6
6
  export { clearSensitiveCaches } from './lib/clear-sensitive-caches';
7
+ export { useDhis2ConnectionStatus } from './lib/dhis2-connection-status';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This can be used to log info if the `dhis2.debugConnectionStatus` value
3
+ * in localStorage is set to a truthy value during development.
4
+ * Set the value manually and refresh the page to see the logs.
5
+ *
6
+ * The behavior of the connection status can be quite hard to inspect without
7
+ * logs, but the logs are quite chatty and should be omitted normally.
8
+ */
9
+ export declare function devDebugLog(...args: any[]): void;
@@ -0,0 +1,28 @@
1
+ import PropTypes from 'prop-types';
2
+ import React from 'react';
3
+ declare type AppName = string | undefined;
4
+ export declare const getLastConnectedKey: (appName?: AppName) => string;
5
+ export interface Dhis2ConnectionStatus {
6
+ isConnected: boolean;
7
+ isDisconnected: boolean;
8
+ lastConnected: Date | null;
9
+ }
10
+ /**
11
+ * Provides a boolean indicating client's connection to the DHIS2 server,
12
+ * which is different from connection to the internet.
13
+ *
14
+ * The context provider subscribes to messages from the SW tracking successes
15
+ * and failures of requests to the DHIS2 server to determine connection status,
16
+ * and then will initiate periodic pings if there are no incidental requests in
17
+ * order to check the connection consistently
18
+ */
19
+ export declare const Dhis2ConnectionStatusProvider: {
20
+ ({ children, }: {
21
+ children: React.ReactNode;
22
+ }): JSX.Element;
23
+ propTypes: {
24
+ children: PropTypes.Requireable<PropTypes.ReactNodeLike>;
25
+ };
26
+ };
27
+ export declare const useDhis2ConnectionStatus: () => Dhis2ConnectionStatus;
28
+ export {};
@@ -0,0 +1 @@
1
+ export { useDhis2ConnectionStatus, Dhis2ConnectionStatusProvider, } from './dhis2-connection-status';
@@ -0,0 +1,14 @@
1
+ import { Version } from '@dhis2/app-service-config';
2
+ /**
3
+ * Checks the server version to see if the /api/ping endpoint is available for
4
+ * this version.
5
+ *
6
+ * The endpoint was released for versions 2.37.10, 2.38.3, 2.39.2, and 2.40.0
7
+ * (see DHIS2-14531). All versions below that are considered unsupported
8
+ *
9
+ * If the patchVersion is undefined, it's assumed to be a dev server that's
10
+ * newer than the fix versions listed above
11
+ *
12
+ * Major versions above 2 aren't supported 🤷‍♂️
13
+ */
14
+ export declare function isPingAvailable(serverVersion: Version): boolean;
@@ -0,0 +1,18 @@
1
+ export declare const DEFAULT_INITIAL_DELAY_MS: number;
2
+ export declare const DEFAULT_MAX_DELAY_MS: number;
3
+ export declare const DEFAULT_INCREMENT_FACTOR = 1.5;
4
+ export interface SmartInterval {
5
+ clear: () => void;
6
+ pause: () => void;
7
+ resume: () => void;
8
+ invokeCallbackImmediately: () => void;
9
+ snooze: () => void;
10
+ reset: () => void;
11
+ }
12
+ export default function createSmartInterval({ initialDelay, maxDelay, delayIncrementFactor, initialPauseValue, callback, }?: {
13
+ initialDelay?: number | undefined;
14
+ maxDelay?: number | undefined;
15
+ delayIncrementFactor?: number | undefined;
16
+ initialPauseValue?: boolean | undefined;
17
+ callback?: (() => void) | undefined;
18
+ }): SmartInterval;
@@ -0,0 +1 @@
1
+ export declare function usePingQuery(): () => Promise<any>;
@@ -1,8 +1,8 @@
1
1
  declare type milliseconds = number;
2
- interface OnlineStatusOptions {
2
+ interface NetworkStatusOptions {
3
3
  debounceDelay?: milliseconds;
4
4
  }
5
- interface OnlineStatus {
5
+ export interface NetworkStatus {
6
6
  online: boolean;
7
7
  offline: boolean;
8
8
  lastOnline: Date | null;
@@ -21,5 +21,5 @@ interface OnlineStatus {
21
21
  * @param {Number} [options.debounceDelay] - Timeout delay to debounce updates, in ms
22
22
  * @returns {Object} `{ online: boolean, offline: boolean, lastOnline: Date | null }`
23
23
  */
24
- export declare function useOnlineStatus(options?: OnlineStatusOptions): OnlineStatus;
24
+ export declare function useNetworkStatus(options?: NetworkStatusOptions): NetworkStatus;
25
25
  export {};
@@ -34,6 +34,12 @@ export interface IndexedDBCachedSection {
34
34
  }
35
35
  export interface OfflineInterface {
36
36
  readonly pwaEnabled: boolean;
37
+ readonly latestIsConnected: boolean | null;
38
+ subscribeToDhis2ConnectionStatus: ({ onUpdate, }: {
39
+ onUpdate: ({ isConnected }: {
40
+ isConnected: boolean;
41
+ }) => void;
42
+ }) => () => void;
37
43
  startRecording: StartRecording;
38
44
  getCachedSections: () => Promise<IndexedDBCachedSection[]>;
39
45
  removeSection: (id: string) => Promise<boolean>;
@@ -4,7 +4,9 @@ export declare const errorRecordingMock: jest.Mock<any, any>;
4
4
  export declare const failedMessageRecordingMock: jest.Mock<any, any>;
5
5
  export declare const mockOfflineInterface: {
6
6
  pwaEnabled: boolean;
7
+ latestIsConnected: boolean;
7
8
  startRecording: jest.Mock<any, any>;
8
9
  getCachedSections: jest.Mock<any, any>;
9
10
  removeSection: jest.Mock<any, any>;
11
+ subscribeToDhis2ConnectionStatus: jest.Mock<any, any>;
10
12
  };