@dhis2/app-service-offline 2.10.0 → 2.12.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 +337 -0
- package/build/cjs/index.js +39 -1
- package/build/cjs/lib/__tests__/clear-sensitive-caches.test.js +131 -0
- package/build/cjs/lib/__tests__/offline-provider.test.js +127 -0
- package/build/cjs/lib/__tests__/use-cacheable-section.test.js +227 -0
- package/build/cjs/lib/cacheable-section-state.js +218 -0
- package/build/cjs/lib/cacheable-section.js +156 -0
- package/build/cjs/lib/clear-sensitive-caches.js +87 -0
- package/build/cjs/lib/global-state-service.js +95 -0
- package/build/cjs/lib/offline-interface.js +86 -0
- package/build/cjs/lib/offline-provider.js +53 -0
- package/build/cjs/types.js +0 -1
- package/build/cjs/utils/__tests__/render-counter.test.js +55 -0
- package/build/cjs/utils/render-counter.js +26 -0
- package/build/cjs/utils/test-mocks.js +40 -0
- package/build/es/__tests__/integration.test.js +327 -0
- package/build/es/index.js +5 -1
- package/build/es/lib/__tests__/clear-sensitive-caches.test.js +123 -0
- package/build/es/lib/__tests__/offline-provider.test.js +117 -0
- package/build/es/lib/__tests__/use-cacheable-section.test.js +218 -0
- package/build/es/lib/cacheable-section-state.js +199 -0
- package/build/es/lib/cacheable-section.js +137 -0
- package/build/es/lib/clear-sensitive-caches.js +78 -0
- package/build/es/lib/global-state-service.js +70 -0
- package/build/es/lib/offline-interface.js +65 -0
- package/build/es/lib/offline-provider.js +40 -0
- package/build/es/types.js +0 -1
- package/build/es/utils/__tests__/render-counter.test.js +40 -0
- package/build/es/utils/render-counter.js +11 -0
- package/build/es/utils/test-mocks.js +30 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/lib/cacheable-section-state.d.ts +66 -0
- package/build/types/lib/cacheable-section.d.ts +52 -0
- package/build/types/lib/clear-sensitive-caches.d.ts +16 -0
- package/build/types/lib/global-state-service.d.ts +16 -0
- package/build/types/lib/offline-interface.d.ts +26 -0
- package/build/types/lib/offline-provider.d.ts +19 -0
- package/build/types/types.d.ts +50 -0
- package/build/types/utils/render-counter.d.ts +10 -0
- package/build/types/utils/test-mocks.d.ts +11 -0
- package/package.json +2 -2
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useCacheableSection = useCacheableSection;
|
|
7
|
+
exports.CacheableSection = CacheableSection;
|
|
8
|
+
|
|
9
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
10
|
+
|
|
11
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
12
|
+
|
|
13
|
+
var _cacheableSectionState = require("./cacheable-section-state");
|
|
14
|
+
|
|
15
|
+
var _offlineInterface = require("./offline-interface");
|
|
16
|
+
|
|
17
|
+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
18
|
+
|
|
19
|
+
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; }
|
|
20
|
+
|
|
21
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
22
|
+
|
|
23
|
+
const recordingStates = {
|
|
24
|
+
default: 'default',
|
|
25
|
+
pending: 'pending',
|
|
26
|
+
recording: 'recording',
|
|
27
|
+
error: 'error'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the main controls for a cacheable section and manages recording
|
|
32
|
+
* state, which affects the render state of the CacheableSection component.
|
|
33
|
+
* Also returns the cached status of the section, which come straight from
|
|
34
|
+
* the `useCachedSection` hook.
|
|
35
|
+
*
|
|
36
|
+
* @param {String} id
|
|
37
|
+
* @returns {Object}
|
|
38
|
+
*/
|
|
39
|
+
function useCacheableSection(id) {
|
|
40
|
+
const offlineInterface = (0, _offlineInterface.useOfflineInterface)();
|
|
41
|
+
const {
|
|
42
|
+
isCached,
|
|
43
|
+
lastUpdated,
|
|
44
|
+
remove,
|
|
45
|
+
syncCachedSections
|
|
46
|
+
} = (0, _cacheableSectionState.useCachedSection)(id);
|
|
47
|
+
const {
|
|
48
|
+
recordingState,
|
|
49
|
+
setRecordingState,
|
|
50
|
+
removeRecordingState
|
|
51
|
+
} = (0, _cacheableSectionState.useRecordingState)(id);
|
|
52
|
+
(0, _react.useEffect)(() => {
|
|
53
|
+
// On mount, add recording state for this ID to context if needed
|
|
54
|
+
if (!recordingState) {
|
|
55
|
+
setRecordingState(recordingStates.default);
|
|
56
|
+
} // On unnmount, remove recording state if not recording
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
if (recordingState && recordingState !== recordingStates.recording && recordingState !== recordingStates.pending) {
|
|
61
|
+
removeRecordingState();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
65
|
+
|
|
66
|
+
function startRecording({
|
|
67
|
+
recordingTimeoutDelay = 1000,
|
|
68
|
+
onStarted,
|
|
69
|
+
onCompleted,
|
|
70
|
+
onError
|
|
71
|
+
} = {}) {
|
|
72
|
+
// This promise resolving means that the message to the service worker
|
|
73
|
+
// to start recording was successful. Waiting for resolution prevents
|
|
74
|
+
// unnecessarily rerendering the whole component in case of an error
|
|
75
|
+
return offlineInterface.startRecording({
|
|
76
|
+
sectionId: id,
|
|
77
|
+
recordingTimeoutDelay,
|
|
78
|
+
onStarted: () => {
|
|
79
|
+
onRecordingStarted();
|
|
80
|
+
onStarted && onStarted();
|
|
81
|
+
},
|
|
82
|
+
onCompleted: () => {
|
|
83
|
+
onRecordingCompleted();
|
|
84
|
+
onCompleted && onCompleted();
|
|
85
|
+
},
|
|
86
|
+
onError: error => {
|
|
87
|
+
onRecordingError(error);
|
|
88
|
+
onError && onError(error);
|
|
89
|
+
}
|
|
90
|
+
}).then(() => setRecordingState(recordingStates.pending));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function onRecordingStarted() {
|
|
94
|
+
setRecordingState(recordingStates.recording);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function onRecordingCompleted() {
|
|
98
|
+
setRecordingState(recordingStates.default);
|
|
99
|
+
syncCachedSections();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function onRecordingError(error) {
|
|
103
|
+
console.error('Error during recording:', error);
|
|
104
|
+
setRecordingState(recordingStates.error);
|
|
105
|
+
} // isCached, lastUpdated, remove: _could_ be accessed by useCachedSection,
|
|
106
|
+
// but provided through this hook for convenience
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
recordingState,
|
|
111
|
+
startRecording,
|
|
112
|
+
lastUpdated,
|
|
113
|
+
isCached,
|
|
114
|
+
remove
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Used to wrap the relevant component to be recorded and saved offline.
|
|
120
|
+
* Depending on the recording state of the section, this wrapper will
|
|
121
|
+
* render its children, not render its children while recording is pending,
|
|
122
|
+
* or RErerender the chilren to force data fetching to record by the service
|
|
123
|
+
* worker.
|
|
124
|
+
*
|
|
125
|
+
* During recording, a loading mask provided by props is also rendered that is
|
|
126
|
+
* intended to prevent other interaction with the app that might interfere
|
|
127
|
+
* with the recording process.
|
|
128
|
+
*/
|
|
129
|
+
function CacheableSection({
|
|
130
|
+
id,
|
|
131
|
+
loadingMask,
|
|
132
|
+
children
|
|
133
|
+
}) {
|
|
134
|
+
// Accesses recording state that useCacheableSection controls
|
|
135
|
+
const {
|
|
136
|
+
recordingState
|
|
137
|
+
} = (0, _cacheableSectionState.useRecordingState)(id); // The following causes the component to reload in the event of a recording
|
|
138
|
+
// error; the state will be cleared next time recording moves to pending.
|
|
139
|
+
// It fixes a component getting stuck while rendered without data after
|
|
140
|
+
// failing a recording while offline.
|
|
141
|
+
// Errors can be handled in the `onError` callback to `startRecording`.
|
|
142
|
+
|
|
143
|
+
if (recordingState === recordingStates.error) {
|
|
144
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, children);
|
|
145
|
+
} // Handling rendering with the following conditions prevents an unncessary
|
|
146
|
+
// rerender after successful recording
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, recordingState === recordingStates.recording && loadingMask, recordingState !== recordingStates.pending && children);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
CacheableSection.propTypes = {
|
|
153
|
+
id: _propTypes.default.string.isRequired,
|
|
154
|
+
children: _propTypes.default.node,
|
|
155
|
+
loadingMask: _propTypes.default.node
|
|
156
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.clearSensitiveCaches = clearSensitiveCaches;
|
|
7
|
+
exports.SECTIONS_STORE = exports.SECTIONS_DB = void 0;
|
|
8
|
+
// IndexedDB names; should be the same as in @dhis2/pwa
|
|
9
|
+
const SECTIONS_DB = 'sections-db';
|
|
10
|
+
exports.SECTIONS_DB = SECTIONS_DB;
|
|
11
|
+
const SECTIONS_STORE = 'sections-store'; // Non-sensitive caches that can be kept:
|
|
12
|
+
|
|
13
|
+
exports.SECTIONS_STORE = SECTIONS_STORE;
|
|
14
|
+
const KEEPABLE_CACHES = [/^workbox-precache/, // precached static assets
|
|
15
|
+
/^other-assets/ // static assets cached at runtime - shouldn't be sensitive
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Clears the 'sections-db' IndexedDB if it exists. Designed to avoid opening
|
|
20
|
+
* a new DB if it doesn't exist yet. Firefox can't check if 'sections-db'
|
|
21
|
+
* exists, in which circumstance the IndexedDB is unaffected. It's inelegant
|
|
22
|
+
* but acceptable because the IndexedDB has no sensitive data (only metadata
|
|
23
|
+
* of recorded sections), and the OfflineInterface handles discrepancies
|
|
24
|
+
* between CacheStorage and IndexedDB.
|
|
25
|
+
*/
|
|
26
|
+
const clearDB = async dbName => {
|
|
27
|
+
if (!('databases' in indexedDB)) {
|
|
28
|
+
// FF does not have indexedDB.databases. For that, just clear caches,
|
|
29
|
+
// and offline interface will handle discrepancies in PWA apps.
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const dbs = await window.indexedDB.databases();
|
|
34
|
+
|
|
35
|
+
if (!dbs.some(({
|
|
36
|
+
name
|
|
37
|
+
}) => name === dbName)) {
|
|
38
|
+
// Sections-db is not created; nothing to do here
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
// IndexedDB fun:
|
|
44
|
+
const openDBRequest = indexedDB.open(dbName);
|
|
45
|
+
|
|
46
|
+
openDBRequest.onsuccess = e => {
|
|
47
|
+
const db = e.target.result;
|
|
48
|
+
const tx = db.transaction(SECTIONS_STORE, 'readwrite'); // When the transaction completes is when the operation is done:
|
|
49
|
+
|
|
50
|
+
tx.oncomplete = () => resolve();
|
|
51
|
+
|
|
52
|
+
tx.onerror = e => reject(e.target.error);
|
|
53
|
+
|
|
54
|
+
const os = tx.objectStore(SECTIONS_STORE);
|
|
55
|
+
const clearReq = os.clear();
|
|
56
|
+
|
|
57
|
+
clearReq.onerror = e => reject(e.target.error);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
openDBRequest.onerror = e => {
|
|
61
|
+
reject(e.target.error);
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Used to clear caches and 'sections-db' IndexedDB when a user logs out or a
|
|
67
|
+
* different user logs in to prevent someone from accessing a different user's
|
|
68
|
+
* caches. Should be able to be used in a non-PWA app.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async function clearSensitiveCaches(dbName = SECTIONS_DB) {
|
|
73
|
+
console.debug('Clearing sensitive caches');
|
|
74
|
+
const cacheKeys = await caches.keys();
|
|
75
|
+
return Promise.all([clearDB(dbName), // remove caches if not in keepable list
|
|
76
|
+
...cacheKeys.map(key => {
|
|
77
|
+
if (!KEEPABLE_CACHES.some(pattern => pattern.test(key))) {
|
|
78
|
+
// .then() satisfies typescript
|
|
79
|
+
return caches.delete(key).then(() => undefined);
|
|
80
|
+
}
|
|
81
|
+
})]).then(responses => {
|
|
82
|
+
// Return true if any caches have been cleared
|
|
83
|
+
// (caches.delete() returns true if a cache is deleted successfully)
|
|
84
|
+
// PWA apps can reload to restore their app shell cache
|
|
85
|
+
return responses.some(response => response);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useGlobalStateMutation = useGlobalStateMutation;
|
|
7
|
+
exports.useGlobalState = exports.GlobalStateProvider = exports.createStore = void 0;
|
|
8
|
+
|
|
9
|
+
var _isEqual = _interopRequireDefault(require("lodash/isEqual"));
|
|
10
|
+
|
|
11
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
12
|
+
|
|
13
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
14
|
+
|
|
15
|
+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
16
|
+
|
|
17
|
+
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; }
|
|
18
|
+
|
|
19
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
20
|
+
|
|
21
|
+
// This file creates a redux-like state management service using React context
|
|
22
|
+
// that minimizes unnecessary rerenders that consume the context.
|
|
23
|
+
// See more at https://github.com/amcgee/state-service-poc
|
|
24
|
+
const identity = state => state;
|
|
25
|
+
|
|
26
|
+
const createStore = (initialState = {}) => {
|
|
27
|
+
const subscriptions = new Set();
|
|
28
|
+
let state = initialState;
|
|
29
|
+
return {
|
|
30
|
+
getState: () => state,
|
|
31
|
+
subscribe: callback => {
|
|
32
|
+
subscriptions.add(callback);
|
|
33
|
+
},
|
|
34
|
+
unsubscribe: callback => {
|
|
35
|
+
subscriptions.delete(callback);
|
|
36
|
+
},
|
|
37
|
+
mutate: mutation => {
|
|
38
|
+
state = mutation(state);
|
|
39
|
+
|
|
40
|
+
for (const callback of subscriptions) {
|
|
41
|
+
callback(state);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
exports.createStore = createStore;
|
|
48
|
+
|
|
49
|
+
const GlobalStateContext = /*#__PURE__*/_react.default.createContext(createStore());
|
|
50
|
+
|
|
51
|
+
const useGlobalStateStore = () => (0, _react.useContext)(GlobalStateContext);
|
|
52
|
+
|
|
53
|
+
const GlobalStateProvider = ({
|
|
54
|
+
store,
|
|
55
|
+
children
|
|
56
|
+
}) => /*#__PURE__*/_react.default.createElement(GlobalStateContext.Provider, {
|
|
57
|
+
value: store
|
|
58
|
+
}, children);
|
|
59
|
+
|
|
60
|
+
exports.GlobalStateProvider = GlobalStateProvider;
|
|
61
|
+
GlobalStateProvider.propTypes = {
|
|
62
|
+
children: _propTypes.default.node,
|
|
63
|
+
store: _propTypes.default.shape({})
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const useGlobalState = (selector = identity) => {
|
|
67
|
+
const store = useGlobalStateStore();
|
|
68
|
+
const [selectedState, setSelectedState] = (0, _react.useState)(selector(store.getState()));
|
|
69
|
+
(0, _react.useEffect)(() => {
|
|
70
|
+
// NEW: deep equality check before updating
|
|
71
|
+
const callback = state => {
|
|
72
|
+
const newSelectedState = selector(state); // Second condition handles case where a selected object gets
|
|
73
|
+
// deleted, but state does not update
|
|
74
|
+
|
|
75
|
+
if (!(0, _isEqual.default)(selectedState, newSelectedState) || selectedState === undefined) setSelectedState(newSelectedState);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
store.subscribe(callback); // Make sure to update state when selector changes
|
|
79
|
+
|
|
80
|
+
callback(store.getState());
|
|
81
|
+
return () => store.unsubscribe(callback);
|
|
82
|
+
}, [store, selector]);
|
|
83
|
+
/* eslint-disable-line react-hooks/exhaustive-deps */
|
|
84
|
+
|
|
85
|
+
return [selectedState, store.mutate];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
exports.useGlobalState = useGlobalState;
|
|
89
|
+
|
|
90
|
+
function useGlobalStateMutation(mutationCreator) {
|
|
91
|
+
const store = useGlobalStateStore();
|
|
92
|
+
return (0, _react.useCallback)((...args) => {
|
|
93
|
+
store.mutate(mutationCreator(...args));
|
|
94
|
+
}, [mutationCreator, store]);
|
|
95
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.OfflineInterfaceProvider = OfflineInterfaceProvider;
|
|
7
|
+
exports.useOfflineInterface = useOfflineInterface;
|
|
8
|
+
|
|
9
|
+
var _appServiceAlerts = require("@dhis2/app-service-alerts");
|
|
10
|
+
|
|
11
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
12
|
+
|
|
13
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
14
|
+
|
|
15
|
+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
16
|
+
|
|
17
|
+
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; }
|
|
18
|
+
|
|
19
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
20
|
+
|
|
21
|
+
// This is to prevent 'offlineInterface could be null' type-checking errors
|
|
22
|
+
const noopOfflineInterface = {
|
|
23
|
+
pwaEnabled: false,
|
|
24
|
+
init: () => () => null,
|
|
25
|
+
startRecording: async () => undefined,
|
|
26
|
+
getCachedSections: async () => [],
|
|
27
|
+
removeSection: async () => false
|
|
28
|
+
};
|
|
29
|
+
const OfflineInterfaceContext = /*#__PURE__*/(0, _react.createContext)(noopOfflineInterface);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Receives an OfflineInterface instance as a prop (presumably from the app
|
|
33
|
+
* adapter) and provides it as context for other offline tools.
|
|
34
|
+
*
|
|
35
|
+
* On mount, it initializes the offline interface, which (among other things)
|
|
36
|
+
* checks for service worker updates and, if updates are ready, prompts the
|
|
37
|
+
* user with an alert to skip waiting and reload the page to use new content.
|
|
38
|
+
*/
|
|
39
|
+
function OfflineInterfaceProvider({
|
|
40
|
+
offlineInterface,
|
|
41
|
+
children
|
|
42
|
+
}) {
|
|
43
|
+
const {
|
|
44
|
+
show
|
|
45
|
+
} = (0, _appServiceAlerts.useAlert)(({
|
|
46
|
+
message
|
|
47
|
+
}) => message, ({
|
|
48
|
+
action,
|
|
49
|
+
onConfirm
|
|
50
|
+
}) => ({
|
|
51
|
+
actions: [{
|
|
52
|
+
label: action,
|
|
53
|
+
onClick: onConfirm
|
|
54
|
+
}],
|
|
55
|
+
permanent: true
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
_react.default.useEffect(() => {
|
|
59
|
+
// Init returns a tear-down function
|
|
60
|
+
return offlineInterface.init({
|
|
61
|
+
promptUpdate: show
|
|
62
|
+
});
|
|
63
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
return /*#__PURE__*/_react.default.createElement(OfflineInterfaceContext.Provider, {
|
|
67
|
+
value: offlineInterface
|
|
68
|
+
}, children);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
OfflineInterfaceProvider.propTypes = {
|
|
72
|
+
children: _propTypes.default.node,
|
|
73
|
+
offlineInterface: _propTypes.default.shape({
|
|
74
|
+
init: _propTypes.default.func
|
|
75
|
+
})
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function useOfflineInterface() {
|
|
79
|
+
const offlineInterface = (0, _react.useContext)(OfflineInterfaceContext);
|
|
80
|
+
|
|
81
|
+
if (offlineInterface === undefined) {
|
|
82
|
+
throw new Error('Offline interface context not found. If this app is using the app platform, make sure `pwa: { enabled: true }` is in d2.config.js. If this is not a platform app, make sure your app is wrapped with an app-runtime <Provider> or an <OfflineProvider> from app-service-offline.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return offlineInterface;
|
|
86
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.OfflineProvider = OfflineProvider;
|
|
7
|
+
|
|
8
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
|
+
|
|
10
|
+
var _react = _interopRequireDefault(require("react"));
|
|
11
|
+
|
|
12
|
+
var _cacheableSectionState = require("./cacheable-section-state");
|
|
13
|
+
|
|
14
|
+
var _offlineInterface = require("./offline-interface");
|
|
15
|
+
|
|
16
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
+
|
|
18
|
+
/** A context provider for all the relevant offline contexts */
|
|
19
|
+
function OfflineProvider({
|
|
20
|
+
offlineInterface,
|
|
21
|
+
children
|
|
22
|
+
}) {
|
|
23
|
+
// If an offline interface is not provided, or if one is provided and PWA
|
|
24
|
+
// is not enabled, skip adding context providers
|
|
25
|
+
if (!offlineInterface) {
|
|
26
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, children);
|
|
27
|
+
} // If PWA is not enabled, just init interface to make sure new SW gets
|
|
28
|
+
// activated with code that unregisters SW. Not technically necessary if a
|
|
29
|
+
// killswitch SW takes over, but the killswitch may not always be in use.
|
|
30
|
+
// Then, skip adding any context
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if (!offlineInterface.pwaEnabled) {
|
|
34
|
+
offlineInterface.init({
|
|
35
|
+
promptUpdate: ({
|
|
36
|
+
onConfirm
|
|
37
|
+
}) => onConfirm()
|
|
38
|
+
});
|
|
39
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, children);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return /*#__PURE__*/_react.default.createElement(_offlineInterface.OfflineInterfaceProvider, {
|
|
43
|
+
offlineInterface: offlineInterface
|
|
44
|
+
}, /*#__PURE__*/_react.default.createElement(_cacheableSectionState.CacheableSectionProvider, null, children));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
OfflineProvider.propTypes = {
|
|
48
|
+
children: _propTypes.default.node,
|
|
49
|
+
offlineInterface: _propTypes.default.shape({
|
|
50
|
+
init: _propTypes.default.func,
|
|
51
|
+
pwaEnabled: _propTypes.default.bool
|
|
52
|
+
})
|
|
53
|
+
};
|