@docusaurus/plugin-pwa 2.0.0-beta.21 → 2.0.0-beta.22
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/lib/registerSw.js +99 -142
- package/lib/renderReloadPopup.d.ts +1 -1
- package/lib/renderReloadPopup.js +5 -4
- package/package.json +15 -14
- package/src/deps.d.ts +15 -0
- package/src/registerSw.ts +117 -196
- package/src/renderReloadPopup.tsx +5 -4
package/lib/registerSw.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
+
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
|
7
8
|
import { createStorageSlot } from '@docusaurus/theme-common';
|
|
8
9
|
// First: read the env variables (provided by Webpack)
|
|
9
10
|
/* eslint-disable prefer-destructuring */
|
|
@@ -12,23 +13,25 @@ const PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES = process.env
|
|
|
12
13
|
.PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES;
|
|
13
14
|
const PWA_DEBUG = process.env.PWA_DEBUG;
|
|
14
15
|
/* eslint-enable prefer-destructuring */
|
|
15
|
-
const
|
|
16
|
-
const MAX_MOBILE_WIDTH = 940;
|
|
16
|
+
const MAX_MOBILE_WIDTH = 996;
|
|
17
17
|
const AppInstalledEventFiredStorage = createStorageSlot('docusaurus.pwa.event.appInstalled.fired');
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (debug) {
|
|
26
|
-
console.log(`[Docusaurus-PWA][registerSw]: unregister() service worker registration`, registrations, result);
|
|
18
|
+
function debugLog(msg, obj) {
|
|
19
|
+
if (PWA_DEBUG) {
|
|
20
|
+
if (typeof obj === 'undefined') {
|
|
21
|
+
console.log(`[Docusaurus-PWA][registerSw]: ${msg}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log(`[Docusaurus-PWA][registerSw]: ${msg}`, obj);
|
|
27
25
|
}
|
|
28
|
-
}));
|
|
29
|
-
if (debug) {
|
|
30
|
-
console.log(`[Docusaurus-PWA][registerSw]: unregistered all service worker registrations`, registrations);
|
|
31
26
|
}
|
|
27
|
+
}
|
|
28
|
+
async function clearRegistrations() {
|
|
29
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
30
|
+
debugLog('will unregister all service workers', { registrations });
|
|
31
|
+
await Promise.all(registrations.map((registration) => registration
|
|
32
|
+
.unregister()
|
|
33
|
+
.then((result) => debugLog('unregister service worker', { registration, result }))));
|
|
34
|
+
debugLog('unregistered all service workers', { registrations });
|
|
32
35
|
window.location.reload();
|
|
33
36
|
}
|
|
34
37
|
/*
|
|
@@ -40,15 +43,15 @@ https://stackoverflow.com/questions/51735869/check-if-user-has-already-installed
|
|
|
40
43
|
- getInstalledRelatedApps() is only supported in recent Chrome and does not seem to reliable either https://github.com/WICG/get-installed-related-apps
|
|
41
44
|
- display-mode: standalone is not exactly the same concept, but looks like a decent fallback https://petelepage.com/blog/2019/07/is-my-pwa-installed/
|
|
42
45
|
*/
|
|
43
|
-
|
|
46
|
+
function getIsAppInstalledEventFired() {
|
|
44
47
|
return AppInstalledEventFiredStorage.get() === 'true';
|
|
45
48
|
}
|
|
46
|
-
async function
|
|
47
|
-
if ('getInstalledRelatedApps' in window.navigator) {
|
|
48
|
-
|
|
49
|
-
return relatedApps.some((app) => app.platform === 'webapp');
|
|
49
|
+
async function getIsAppInstalledRelatedApps() {
|
|
50
|
+
if (!('getInstalledRelatedApps' in window.navigator)) {
|
|
51
|
+
return false;
|
|
50
52
|
}
|
|
51
|
-
|
|
53
|
+
const relatedApps = await navigator.getInstalledRelatedApps();
|
|
54
|
+
return relatedApps.some((app) => app.platform === 'webapp');
|
|
52
55
|
}
|
|
53
56
|
function isStandaloneDisplayMode() {
|
|
54
57
|
return window.matchMedia('(display-mode: standalone)').matches;
|
|
@@ -57,169 +60,123 @@ const OfflineModeActivationStrategiesImplementations = {
|
|
|
57
60
|
always: () => true,
|
|
58
61
|
mobile: () => window.innerWidth <= MAX_MOBILE_WIDTH,
|
|
59
62
|
saveData: () => !!navigator.connection?.saveData,
|
|
60
|
-
appInstalled:
|
|
61
|
-
const installedEventFired = await isAppInstalledEventFired();
|
|
62
|
-
const installedRelatedApps = await isAppInstalledRelatedApps();
|
|
63
|
-
return installedEventFired || installedRelatedApps;
|
|
64
|
-
},
|
|
63
|
+
appInstalled: () => getIsAppInstalledEventFired() || getIsAppInstalledRelatedApps(),
|
|
65
64
|
standalone: () => isStandaloneDisplayMode(),
|
|
66
65
|
queryString: () => new URLSearchParams(window.location.search).get('offlineMode') === 'true',
|
|
67
66
|
};
|
|
68
|
-
async function isStrategyActive(strategyName) {
|
|
69
|
-
return OfflineModeActivationStrategiesImplementations[strategyName]();
|
|
70
|
-
}
|
|
71
67
|
async function getActiveStrategies() {
|
|
72
|
-
const activeStrategies = await Promise.all(PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES.map(
|
|
73
|
-
const isActive = await isStrategyActive(strategyName);
|
|
74
|
-
return isActive ? strategyName : undefined;
|
|
75
|
-
}));
|
|
68
|
+
const activeStrategies = await Promise.all(PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES.map((strategyName) => Promise.resolve(OfflineModeActivationStrategiesImplementations[strategyName]()).then((isActive) => (isActive ? strategyName : undefined))));
|
|
76
69
|
return activeStrategies.filter(Boolean);
|
|
77
70
|
}
|
|
78
|
-
async function
|
|
71
|
+
async function getIsOfflineModeEnabled() {
|
|
79
72
|
const activeStrategies = await getActiveStrategies();
|
|
80
73
|
const enabled = activeStrategies.length > 0;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.log('[Docusaurus-PWA][registerSw]: offline mode enabled, because of activation strategies', logObject);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
console.log('[Docusaurus-PWA][registerSw]: offline mode disabled, because none of the offlineModeActivationStrategies could be used', logObject);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
74
|
+
debugLog(enabled
|
|
75
|
+
? 'offline mode enabled, because of activation strategies'
|
|
76
|
+
: 'offline mode disabled, because none of the offlineModeActivationStrategies could be used', {
|
|
77
|
+
activeStrategies,
|
|
78
|
+
availableStrategies: PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES,
|
|
79
|
+
});
|
|
93
80
|
return enabled;
|
|
94
81
|
}
|
|
95
82
|
function createServiceWorkerUrl(params) {
|
|
96
83
|
const paramsQueryString = JSON.stringify(params);
|
|
97
84
|
const url = `${PWA_SERVICE_WORKER_URL}?params=${encodeURIComponent(paramsQueryString)}`;
|
|
98
|
-
|
|
99
|
-
console.log(`[Docusaurus-PWA][registerSw]: service worker url`, {
|
|
100
|
-
url,
|
|
101
|
-
params,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
85
|
+
debugLog('service worker url', { url, params });
|
|
104
86
|
return url;
|
|
105
87
|
}
|
|
106
88
|
async function registerSW() {
|
|
107
|
-
const { Workbox } = await
|
|
108
|
-
|
|
109
|
-
|
|
89
|
+
const [{ Workbox }, offlineMode] = await Promise.all([
|
|
90
|
+
import('workbox-window'),
|
|
91
|
+
getIsOfflineModeEnabled(),
|
|
92
|
+
]);
|
|
93
|
+
const url = createServiceWorkerUrl({ offlineMode, debug: PWA_DEBUG });
|
|
110
94
|
const wb = new Workbox(url);
|
|
111
|
-
const registration = await wb.register();
|
|
112
95
|
const sendSkipWaiting = () => wb.messageSW({ type: 'SKIP_WAITING' });
|
|
113
|
-
const handleServiceWorkerWaiting =
|
|
114
|
-
|
|
115
|
-
console.log('[Docusaurus-PWA][registerSw]: handleServiceWorkerWaiting');
|
|
116
|
-
}
|
|
96
|
+
const handleServiceWorkerWaiting = () => {
|
|
97
|
+
debugLog('handleServiceWorkerWaiting');
|
|
117
98
|
// Immediately load new service worker when files aren't cached
|
|
118
99
|
if (!offlineMode) {
|
|
119
|
-
sendSkipWaiting();
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
const renderReloadPopup = (await import('./renderReloadPopup')).default;
|
|
123
|
-
await renderReloadPopup({
|
|
124
|
-
onReload() {
|
|
125
|
-
wb.addEventListener('controlling', () => {
|
|
126
|
-
window.location.reload();
|
|
127
|
-
});
|
|
128
|
-
sendSkipWaiting();
|
|
129
|
-
},
|
|
130
|
-
});
|
|
100
|
+
return sendSkipWaiting();
|
|
131
101
|
}
|
|
102
|
+
return import('./renderReloadPopup.js').then(({ renderReloadPopup }) => renderReloadPopup({
|
|
103
|
+
onReload() {
|
|
104
|
+
wb.addEventListener('controlling', () => {
|
|
105
|
+
window.location.reload();
|
|
106
|
+
});
|
|
107
|
+
sendSkipWaiting();
|
|
108
|
+
},
|
|
109
|
+
}));
|
|
132
110
|
};
|
|
133
|
-
if (debug && registration) {
|
|
134
|
-
if (registration.active) {
|
|
135
|
-
console.log('[Docusaurus-PWA][registerSw]: registration.active', registration);
|
|
136
|
-
}
|
|
137
|
-
if (registration.installing) {
|
|
138
|
-
console.log('[Docusaurus-PWA][registerSw]: registration.installing', registration);
|
|
139
|
-
}
|
|
140
|
-
if (registration.waiting) {
|
|
141
|
-
console.log('[Docusaurus-PWA][registerSw]: registration.waiting', registration);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
111
|
// Update the current service worker when the next one has finished
|
|
145
112
|
// installing and transitions to waiting state.
|
|
146
113
|
wb.addEventListener('waiting', (event) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
handleServiceWorkerWaiting();
|
|
114
|
+
debugLog('event waiting', { event });
|
|
115
|
+
void handleServiceWorkerWaiting();
|
|
151
116
|
});
|
|
152
117
|
// Update current service worker if the next one finishes installing and
|
|
153
118
|
// moves to waiting state in another tab.
|
|
154
119
|
// @ts-expect-error: not present in the API typings anymore
|
|
155
120
|
wb.addEventListener('externalwaiting', (event) => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
handleServiceWorkerWaiting();
|
|
121
|
+
debugLog('event externalwaiting', { event });
|
|
122
|
+
void handleServiceWorkerWaiting();
|
|
160
123
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
124
|
+
const registration = await wb.register();
|
|
125
|
+
if (registration) {
|
|
126
|
+
if (registration.active) {
|
|
127
|
+
debugLog('registration.active', { registration });
|
|
128
|
+
}
|
|
129
|
+
if (registration.installing) {
|
|
130
|
+
debugLog('registration.installing', { registration });
|
|
131
|
+
}
|
|
132
|
+
if (registration.waiting) {
|
|
133
|
+
debugLog('registration.waiting', { registration });
|
|
134
|
+
// Update service worker if the next one is already in the waiting
|
|
135
|
+
// state. This happens when the user doesn't click on `reload` in
|
|
136
|
+
// the popup.
|
|
137
|
+
await handleServiceWorkerWaiting();
|
|
138
|
+
}
|
|
165
139
|
}
|
|
166
140
|
}
|
|
167
141
|
// TODO these events still works in chrome but have been removed from the spec
|
|
168
142
|
// in 2019! See https://github.com/w3c/manifest/pull/836
|
|
169
143
|
function addLegacyAppInstalledEventsListeners() {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
if (debug) {
|
|
198
|
-
console.log('[Docusaurus-PWA][registerSw]: AppInstalledEventFiredStorage.get()', AppInstalledEventFiredStorage.get());
|
|
199
|
-
}
|
|
200
|
-
if (AppInstalledEventFiredStorage.get()) {
|
|
201
|
-
AppInstalledEventFiredStorage.del();
|
|
202
|
-
if (debug) {
|
|
203
|
-
console.log('[Docusaurus-PWA][registerSw]: AppInstalledEventFiredStorage.del()');
|
|
204
|
-
}
|
|
205
|
-
// After uninstalling the app, if the user doesn't clear all data, then
|
|
206
|
-
// the previous service worker will continue serving cached files. We
|
|
207
|
-
// need to clear registrations and reload, otherwise the popup shows.
|
|
208
|
-
await clearRegistrations();
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
if (debug) {
|
|
212
|
-
console.log('[Docusaurus-PWA][registerSw]: legacy appinstalled and beforeinstallprompt event listeners installed');
|
|
144
|
+
debugLog('addLegacyAppInstalledEventsListeners');
|
|
145
|
+
window.addEventListener('appinstalled', (event) => {
|
|
146
|
+
debugLog('event appinstalled', { event });
|
|
147
|
+
AppInstalledEventFiredStorage.set('true');
|
|
148
|
+
debugLog("AppInstalledEventFiredStorage.set('true')");
|
|
149
|
+
// After the app is installed, we register a service worker with the path
|
|
150
|
+
// `/sw?enabled`. Since the previous service worker was `/sw`, it'll be
|
|
151
|
+
// treated as a new one. The previous registration will need to be
|
|
152
|
+
// cleared, otherwise the reload popup will show.
|
|
153
|
+
void clearRegistrations();
|
|
154
|
+
});
|
|
155
|
+
// TODO this event still works in chrome but has been removed from the spec
|
|
156
|
+
// in 2019!!!
|
|
157
|
+
window.addEventListener('beforeinstallprompt', (event) => {
|
|
158
|
+
debugLog('event beforeinstallprompt', { event });
|
|
159
|
+
// TODO instead of default browser install UI, show custom docusaurus
|
|
160
|
+
// prompt?
|
|
161
|
+
// event.preventDefault();
|
|
162
|
+
const appInstalledEventFired = AppInstalledEventFiredStorage.get();
|
|
163
|
+
debugLog('AppInstalledEventFiredStorage.get()', { appInstalledEventFired });
|
|
164
|
+
if (appInstalledEventFired) {
|
|
165
|
+
AppInstalledEventFiredStorage.del();
|
|
166
|
+
debugLog('AppInstalledEventFiredStorage.del()');
|
|
167
|
+
// After uninstalling the app, if the user doesn't clear all data, then
|
|
168
|
+
// the previous service worker will continue serving cached files. We
|
|
169
|
+
// need to clear registrations and reload, otherwise the popup shows.
|
|
170
|
+
void clearRegistrations();
|
|
213
171
|
}
|
|
214
|
-
}
|
|
172
|
+
});
|
|
173
|
+
debugLog('legacy appinstalled and beforeinstallprompt event listeners installed');
|
|
215
174
|
}
|
|
216
175
|
/*
|
|
217
176
|
Init code to run on the client!
|
|
218
177
|
*/
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
console.log('[Docusaurus-PWA][registerSw]: debug mode enabled');
|
|
222
|
-
}
|
|
178
|
+
if (ExecutionEnvironment.canUseDOM) {
|
|
179
|
+
debugLog('debug mode enabled');
|
|
223
180
|
if ('serviceWorker' in navigator) {
|
|
224
181
|
// First: add the listeners asap/synchronously
|
|
225
182
|
addLegacyAppInstalledEventsListeners();
|
package/lib/renderReloadPopup.js
CHANGED
|
@@ -14,8 +14,9 @@ const createContainer = () => {
|
|
|
14
14
|
document.body.appendChild(container);
|
|
15
15
|
return container;
|
|
16
16
|
};
|
|
17
|
-
export
|
|
18
|
-
const container = getContainer()
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
export function renderReloadPopup(props) {
|
|
18
|
+
const container = getContainer() ?? createContainer();
|
|
19
|
+
return import('@theme/PwaReloadPopup').then(({ default: ReloadPopup }) => {
|
|
20
|
+
ReactDOM.render(<ReloadPopup {...props}/>, container);
|
|
21
|
+
});
|
|
21
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-pwa",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.22",
|
|
4
4
|
"description": "Docusaurus Plugin to add PWA support.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-pwa.d.ts",
|
|
@@ -20,26 +20,27 @@
|
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@babel/core": "^7.18.
|
|
24
|
-
"@babel/preset-env": "^7.18.
|
|
25
|
-
"@docusaurus/core": "2.0.0-beta.
|
|
26
|
-
"@docusaurus/theme-common": "2.0.0-beta.
|
|
27
|
-
"@docusaurus/theme-translations": "2.0.0-beta.
|
|
28
|
-
"@docusaurus/
|
|
29
|
-
"@docusaurus/utils
|
|
23
|
+
"@babel/core": "^7.18.6",
|
|
24
|
+
"@babel/preset-env": "^7.18.6",
|
|
25
|
+
"@docusaurus/core": "2.0.0-beta.22",
|
|
26
|
+
"@docusaurus/theme-common": "2.0.0-beta.22",
|
|
27
|
+
"@docusaurus/theme-translations": "2.0.0-beta.22",
|
|
28
|
+
"@docusaurus/types": "2.0.0-beta.22",
|
|
29
|
+
"@docusaurus/utils": "2.0.0-beta.22",
|
|
30
|
+
"@docusaurus/utils-validation": "2.0.0-beta.22",
|
|
30
31
|
"babel-loader": "^8.2.5",
|
|
31
|
-
"clsx": "^1.
|
|
32
|
-
"core-js": "^3.
|
|
33
|
-
"terser-webpack-plugin": "^5.3.
|
|
32
|
+
"clsx": "^1.2.0",
|
|
33
|
+
"core-js": "^3.23.3",
|
|
34
|
+
"terser-webpack-plugin": "^5.3.3",
|
|
34
35
|
"tslib": "^2.4.0",
|
|
35
|
-
"webpack": "^5.
|
|
36
|
+
"webpack": "^5.73.0",
|
|
36
37
|
"webpack-merge": "^5.8.0",
|
|
37
38
|
"workbox-build": "^6.5.3",
|
|
38
39
|
"workbox-precaching": "^6.5.3",
|
|
39
40
|
"workbox-window": "^6.5.3"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@docusaurus/module-type-aliases": "2.0.0-beta.
|
|
43
|
+
"@docusaurus/module-type-aliases": "2.0.0-beta.22",
|
|
43
44
|
"fs-extra": "^10.1.0"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
@@ -49,5 +50,5 @@
|
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=16.14"
|
|
51
52
|
},
|
|
52
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "daf9e462c4eebb7ac26a940932311f987e768f87"
|
|
53
54
|
}
|
package/src/deps.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// TODO incompatible declaration file: https://github.com/unjs/webpackbar/pull/108
|
|
9
|
+
declare module 'webpackbar' {
|
|
10
|
+
import webpack from 'webpack';
|
|
11
|
+
|
|
12
|
+
export default class WebpackBarPlugin extends webpack.ProgressPlugin {
|
|
13
|
+
constructor(options: {name: string; color?: string});
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/registerSw.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
|
8
9
|
import {createStorageSlot} from '@docusaurus/theme-common';
|
|
9
10
|
|
|
10
11
|
// First: read the env variables (provided by Webpack)
|
|
@@ -15,40 +16,42 @@ const PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES = process.env
|
|
|
15
16
|
const PWA_DEBUG = process.env.PWA_DEBUG;
|
|
16
17
|
/* eslint-enable prefer-destructuring */
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const MAX_MOBILE_WIDTH = 940;
|
|
19
|
+
const MAX_MOBILE_WIDTH = 996;
|
|
21
20
|
|
|
22
21
|
const AppInstalledEventFiredStorage = createStorageSlot(
|
|
23
22
|
'docusaurus.pwa.event.appInstalled.fired',
|
|
24
23
|
);
|
|
25
24
|
|
|
25
|
+
declare global {
|
|
26
|
+
interface Navigator {
|
|
27
|
+
getInstalledRelatedApps: () => Promise<{platform: string}[]>;
|
|
28
|
+
connection?: {effectiveType: string; saveData: boolean};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function debugLog(msg: string, obj?: unknown) {
|
|
33
|
+
if (PWA_DEBUG) {
|
|
34
|
+
if (typeof obj === 'undefined') {
|
|
35
|
+
console.log(`[Docusaurus-PWA][registerSw]: ${msg}`);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`[Docusaurus-PWA][registerSw]: ${msg}`, obj);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
async function clearRegistrations() {
|
|
27
43
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
28
|
-
|
|
29
|
-
console.log(
|
|
30
|
-
`[Docusaurus-PWA][registerSw]: will unregister all service worker registrations`,
|
|
31
|
-
registrations,
|
|
32
|
-
);
|
|
33
|
-
}
|
|
44
|
+
debugLog('will unregister all service workers', {registrations});
|
|
34
45
|
await Promise.all(
|
|
35
|
-
registrations.map(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
}),
|
|
46
|
+
registrations.map((registration) =>
|
|
47
|
+
registration
|
|
48
|
+
.unregister()
|
|
49
|
+
.then((result) =>
|
|
50
|
+
debugLog('unregister service worker', {registration, result}),
|
|
51
|
+
),
|
|
52
|
+
),
|
|
45
53
|
);
|
|
46
|
-
|
|
47
|
-
console.log(
|
|
48
|
-
`[Docusaurus-PWA][registerSw]: unregistered all service worker registrations`,
|
|
49
|
-
registrations,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
54
|
+
debugLog('unregistered all service workers', {registrations});
|
|
52
55
|
window.location.reload();
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -61,23 +64,16 @@ https://stackoverflow.com/questions/51735869/check-if-user-has-already-installed
|
|
|
61
64
|
- getInstalledRelatedApps() is only supported in recent Chrome and does not seem to reliable either https://github.com/WICG/get-installed-related-apps
|
|
62
65
|
- display-mode: standalone is not exactly the same concept, but looks like a decent fallback https://petelepage.com/blog/2019/07/is-my-pwa-installed/
|
|
63
66
|
*/
|
|
64
|
-
|
|
67
|
+
function getIsAppInstalledEventFired() {
|
|
65
68
|
return AppInstalledEventFiredStorage.get() === 'true';
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
connection?: {effectiveType: string; saveData: boolean};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function isAppInstalledRelatedApps() {
|
|
76
|
-
if ('getInstalledRelatedApps' in window.navigator) {
|
|
77
|
-
const relatedApps = await navigator.getInstalledRelatedApps();
|
|
78
|
-
return relatedApps.some((app) => app.platform === 'webapp');
|
|
71
|
+
async function getIsAppInstalledRelatedApps() {
|
|
72
|
+
if (!('getInstalledRelatedApps' in window.navigator)) {
|
|
73
|
+
return false;
|
|
79
74
|
}
|
|
80
|
-
|
|
75
|
+
const relatedApps = await navigator.getInstalledRelatedApps();
|
|
76
|
+
return relatedApps.some((app) => app.platform === 'webapp');
|
|
81
77
|
}
|
|
82
78
|
function isStandaloneDisplayMode() {
|
|
83
79
|
return window.matchMedia('(display-mode: standalone)').matches;
|
|
@@ -87,52 +83,36 @@ const OfflineModeActivationStrategiesImplementations = {
|
|
|
87
83
|
always: () => true,
|
|
88
84
|
mobile: () => window.innerWidth <= MAX_MOBILE_WIDTH,
|
|
89
85
|
saveData: () => !!navigator.connection?.saveData,
|
|
90
|
-
appInstalled:
|
|
91
|
-
|
|
92
|
-
const installedRelatedApps = await isAppInstalledRelatedApps();
|
|
93
|
-
return installedEventFired || installedRelatedApps;
|
|
94
|
-
},
|
|
86
|
+
appInstalled: () =>
|
|
87
|
+
getIsAppInstalledEventFired() || getIsAppInstalledRelatedApps(),
|
|
95
88
|
standalone: () => isStandaloneDisplayMode(),
|
|
96
89
|
queryString: () =>
|
|
97
90
|
new URLSearchParams(window.location.search).get('offlineMode') === 'true',
|
|
98
91
|
};
|
|
99
92
|
|
|
100
|
-
async function isStrategyActive(
|
|
101
|
-
strategyName: keyof typeof OfflineModeActivationStrategiesImplementations,
|
|
102
|
-
) {
|
|
103
|
-
return OfflineModeActivationStrategiesImplementations[strategyName]();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
93
|
async function getActiveStrategies() {
|
|
107
94
|
const activeStrategies = await Promise.all(
|
|
108
|
-
PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES.map(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
95
|
+
PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES.map((strategyName) =>
|
|
96
|
+
Promise.resolve(
|
|
97
|
+
OfflineModeActivationStrategiesImplementations[strategyName](),
|
|
98
|
+
).then((isActive) => (isActive ? strategyName : undefined)),
|
|
99
|
+
),
|
|
112
100
|
);
|
|
113
101
|
return activeStrategies.filter(Boolean);
|
|
114
102
|
}
|
|
115
103
|
|
|
116
|
-
async function
|
|
104
|
+
async function getIsOfflineModeEnabled() {
|
|
117
105
|
const activeStrategies = await getActiveStrategies();
|
|
118
106
|
const enabled = activeStrategies.length > 0;
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
debugLog(
|
|
108
|
+
enabled
|
|
109
|
+
? 'offline mode enabled, because of activation strategies'
|
|
110
|
+
: 'offline mode disabled, because none of the offlineModeActivationStrategies could be used',
|
|
111
|
+
{
|
|
121
112
|
activeStrategies,
|
|
122
113
|
availableStrategies: PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES,
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log(
|
|
126
|
-
'[Docusaurus-PWA][registerSw]: offline mode enabled, because of activation strategies',
|
|
127
|
-
logObject,
|
|
128
|
-
);
|
|
129
|
-
} else {
|
|
130
|
-
console.log(
|
|
131
|
-
'[Docusaurus-PWA][registerSw]: offline mode disabled, because none of the offlineModeActivationStrategies could be used',
|
|
132
|
-
logObject,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
114
|
+
},
|
|
115
|
+
);
|
|
136
116
|
return enabled;
|
|
137
117
|
}
|
|
138
118
|
|
|
@@ -141,170 +121,111 @@ function createServiceWorkerUrl(params: object) {
|
|
|
141
121
|
const url = `${PWA_SERVICE_WORKER_URL}?params=${encodeURIComponent(
|
|
142
122
|
paramsQueryString,
|
|
143
123
|
)}`;
|
|
144
|
-
|
|
145
|
-
console.log(`[Docusaurus-PWA][registerSw]: service worker url`, {
|
|
146
|
-
url,
|
|
147
|
-
params,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
124
|
+
debugLog('service worker url', {url, params});
|
|
150
125
|
return url;
|
|
151
126
|
}
|
|
152
127
|
|
|
153
128
|
async function registerSW() {
|
|
154
|
-
const {Workbox} = await
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const url = createServiceWorkerUrl({offlineMode, debug});
|
|
129
|
+
const [{Workbox}, offlineMode] = await Promise.all([
|
|
130
|
+
import('workbox-window'),
|
|
131
|
+
getIsOfflineModeEnabled(),
|
|
132
|
+
]);
|
|
133
|
+
const url = createServiceWorkerUrl({offlineMode, debug: PWA_DEBUG});
|
|
159
134
|
const wb = new Workbox(url);
|
|
160
|
-
|
|
161
|
-
const registration = await wb.register();
|
|
162
|
-
|
|
163
135
|
const sendSkipWaiting = () => wb.messageSW({type: 'SKIP_WAITING'});
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (debug) {
|
|
167
|
-
console.log('[Docusaurus-PWA][registerSw]: handleServiceWorkerWaiting');
|
|
168
|
-
}
|
|
136
|
+
const handleServiceWorkerWaiting = () => {
|
|
137
|
+
debugLog('handleServiceWorkerWaiting');
|
|
169
138
|
// Immediately load new service worker when files aren't cached
|
|
170
139
|
if (!offlineMode) {
|
|
171
|
-
sendSkipWaiting();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
140
|
+
return sendSkipWaiting();
|
|
141
|
+
}
|
|
142
|
+
return import('./renderReloadPopup.js').then(({renderReloadPopup}) =>
|
|
143
|
+
renderReloadPopup({
|
|
175
144
|
onReload() {
|
|
176
145
|
wb.addEventListener('controlling', () => {
|
|
177
146
|
window.location.reload();
|
|
178
147
|
});
|
|
179
148
|
sendSkipWaiting();
|
|
180
149
|
},
|
|
181
|
-
})
|
|
182
|
-
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
183
152
|
};
|
|
184
|
-
|
|
185
|
-
if (debug && registration) {
|
|
186
|
-
if (registration.active) {
|
|
187
|
-
console.log(
|
|
188
|
-
'[Docusaurus-PWA][registerSw]: registration.active',
|
|
189
|
-
registration,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
if (registration.installing) {
|
|
193
|
-
console.log(
|
|
194
|
-
'[Docusaurus-PWA][registerSw]: registration.installing',
|
|
195
|
-
registration,
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
if (registration.waiting) {
|
|
199
|
-
console.log(
|
|
200
|
-
'[Docusaurus-PWA][registerSw]: registration.waiting',
|
|
201
|
-
registration,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
153
|
// Update the current service worker when the next one has finished
|
|
207
154
|
// installing and transitions to waiting state.
|
|
208
155
|
wb.addEventListener('waiting', (event) => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
handleServiceWorkerWaiting();
|
|
156
|
+
debugLog('event waiting', {event});
|
|
157
|
+
void handleServiceWorkerWaiting();
|
|
213
158
|
});
|
|
214
|
-
|
|
215
159
|
// Update current service worker if the next one finishes installing and
|
|
216
160
|
// moves to waiting state in another tab.
|
|
217
161
|
// @ts-expect-error: not present in the API typings anymore
|
|
218
162
|
wb.addEventListener('externalwaiting', (event) => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
handleServiceWorkerWaiting();
|
|
163
|
+
debugLog('event externalwaiting', {event});
|
|
164
|
+
void handleServiceWorkerWaiting();
|
|
223
165
|
});
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
166
|
+
const registration = await wb.register();
|
|
167
|
+
if (registration) {
|
|
168
|
+
if (registration.active) {
|
|
169
|
+
debugLog('registration.active', {registration});
|
|
170
|
+
}
|
|
171
|
+
if (registration.installing) {
|
|
172
|
+
debugLog('registration.installing', {registration});
|
|
173
|
+
}
|
|
174
|
+
if (registration.waiting) {
|
|
175
|
+
debugLog('registration.waiting', {registration});
|
|
176
|
+
// Update service worker if the next one is already in the waiting
|
|
177
|
+
// state. This happens when the user doesn't click on `reload` in
|
|
178
|
+
// the popup.
|
|
179
|
+
await handleServiceWorkerWaiting();
|
|
180
|
+
}
|
|
229
181
|
}
|
|
230
182
|
}
|
|
231
183
|
|
|
232
184
|
// TODO these events still works in chrome but have been removed from the spec
|
|
233
185
|
// in 2019! See https://github.com/w3c/manifest/pull/836
|
|
234
186
|
function addLegacyAppInstalledEventsListeners() {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
AppInstalledEventFiredStorage.set('true');
|
|
248
|
-
if (debug) {
|
|
249
|
-
console.log(
|
|
250
|
-
"[Docusaurus-PWA][registerSw]: AppInstalledEventFiredStorage.set('true')",
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// After the app is installed, we register a service worker with the path
|
|
255
|
-
// `/sw?enabled`. Since the previous service worker was `/sw`, it'll be
|
|
256
|
-
// treated as a new one. The previous registration will need to be
|
|
257
|
-
// cleared, otherwise the reload popup will show.
|
|
258
|
-
await clearRegistrations();
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// TODO this event still works in chrome but has been removed from the spec
|
|
262
|
-
// in 2019!!!
|
|
263
|
-
window.addEventListener('beforeinstallprompt', async (event) => {
|
|
264
|
-
if (debug) {
|
|
265
|
-
console.log(
|
|
266
|
-
'[Docusaurus-PWA][registerSw]: event beforeinstallprompt',
|
|
267
|
-
event,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
// TODO instead of default browser install UI, show custom docusaurus
|
|
271
|
-
// prompt?
|
|
272
|
-
// event.preventDefault();
|
|
273
|
-
if (debug) {
|
|
274
|
-
console.log(
|
|
275
|
-
'[Docusaurus-PWA][registerSw]: AppInstalledEventFiredStorage.get()',
|
|
276
|
-
AppInstalledEventFiredStorage.get(),
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
if (AppInstalledEventFiredStorage.get()) {
|
|
280
|
-
AppInstalledEventFiredStorage.del();
|
|
281
|
-
if (debug) {
|
|
282
|
-
console.log(
|
|
283
|
-
'[Docusaurus-PWA][registerSw]: AppInstalledEventFiredStorage.del()',
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
// After uninstalling the app, if the user doesn't clear all data, then
|
|
287
|
-
// the previous service worker will continue serving cached files. We
|
|
288
|
-
// need to clear registrations and reload, otherwise the popup shows.
|
|
289
|
-
await clearRegistrations();
|
|
290
|
-
}
|
|
291
|
-
});
|
|
187
|
+
debugLog('addLegacyAppInstalledEventsListeners');
|
|
188
|
+
|
|
189
|
+
window.addEventListener('appinstalled', (event) => {
|
|
190
|
+
debugLog('event appinstalled', {event});
|
|
191
|
+
AppInstalledEventFiredStorage.set('true');
|
|
192
|
+
debugLog("AppInstalledEventFiredStorage.set('true')");
|
|
193
|
+
// After the app is installed, we register a service worker with the path
|
|
194
|
+
// `/sw?enabled`. Since the previous service worker was `/sw`, it'll be
|
|
195
|
+
// treated as a new one. The previous registration will need to be
|
|
196
|
+
// cleared, otherwise the reload popup will show.
|
|
197
|
+
void clearRegistrations();
|
|
198
|
+
});
|
|
292
199
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
200
|
+
// TODO this event still works in chrome but has been removed from the spec
|
|
201
|
+
// in 2019!!!
|
|
202
|
+
window.addEventListener('beforeinstallprompt', (event) => {
|
|
203
|
+
debugLog('event beforeinstallprompt', {event});
|
|
204
|
+
// TODO instead of default browser install UI, show custom docusaurus
|
|
205
|
+
// prompt?
|
|
206
|
+
// event.preventDefault();
|
|
207
|
+
const appInstalledEventFired = AppInstalledEventFiredStorage.get();
|
|
208
|
+
debugLog('AppInstalledEventFiredStorage.get()', {appInstalledEventFired});
|
|
209
|
+
if (appInstalledEventFired) {
|
|
210
|
+
AppInstalledEventFiredStorage.del();
|
|
211
|
+
debugLog('AppInstalledEventFiredStorage.del()');
|
|
212
|
+
// After uninstalling the app, if the user doesn't clear all data, then
|
|
213
|
+
// the previous service worker will continue serving cached files. We
|
|
214
|
+
// need to clear registrations and reload, otherwise the popup shows.
|
|
215
|
+
void clearRegistrations();
|
|
297
216
|
}
|
|
298
|
-
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
debugLog(
|
|
220
|
+
'legacy appinstalled and beforeinstallprompt event listeners installed',
|
|
221
|
+
);
|
|
299
222
|
}
|
|
300
223
|
|
|
301
224
|
/*
|
|
302
225
|
Init code to run on the client!
|
|
303
226
|
*/
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
console.log('[Docusaurus-PWA][registerSw]: debug mode enabled');
|
|
307
|
-
}
|
|
227
|
+
if (ExecutionEnvironment.canUseDOM) {
|
|
228
|
+
debugLog('debug mode enabled');
|
|
308
229
|
|
|
309
230
|
if ('serviceWorker' in navigator) {
|
|
310
231
|
// First: add the listeners asap/synchronously
|
|
@@ -20,8 +20,9 @@ const createContainer = () => {
|
|
|
20
20
|
return container;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export
|
|
24
|
-
const container = getContainer()
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
export function renderReloadPopup(props: Props): Promise<void> {
|
|
24
|
+
const container = getContainer() ?? createContainer();
|
|
25
|
+
return import('@theme/PwaReloadPopup').then(({default: ReloadPopup}) => {
|
|
26
|
+
ReactDOM.render(<ReloadPopup {...props} />, container);
|
|
27
|
+
});
|
|
27
28
|
}
|