@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.
- 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 @@
|
|
|
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 = (
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 = (
|
|
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((
|
|
70
|
-
store.mutate(mutationCreator(...
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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,7 +1,9 @@
|
|
|
1
|
-
export const successfulRecordingMock = jest.fn().mockImplementation(async ({
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
};
|
package/build/types/index.d.ts
CHANGED
|
@@ -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/
|
|
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
|
|
2
|
+
interface NetworkStatusOptions {
|
|
3
3
|
debounceDelay?: milliseconds;
|
|
4
4
|
}
|
|
5
|
-
interface
|
|
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
|
|
24
|
+
export declare function useNetworkStatus(options?: NetworkStatusOptions): NetworkStatus;
|
|
25
25
|
export {};
|
package/build/types/types.d.ts
CHANGED
|
@@ -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
|
};
|