underpost 3.2.9 → 3.2.10
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/.github/workflows/npmpkg.ci.yml +1 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/settings.json +10 -5
- package/CHANGELOG.md +122 -1
- package/CLI-HELP.md +22 -7
- package/README.md +37 -8
- package/bin/build.js +26 -9
- package/bin/deploy.js +20 -21
- package/bin/file.js +31 -13
- package/bin/index.js +2 -1
- package/bin/vs.js +1 -1
- package/bump.config.js +26 -0
- package/conf.js +20 -4
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/package.json +27 -12
- package/scripts/k3s-node-setup.sh +28 -9
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +20 -11
- package/src/cli/cluster.js +196 -55
- package/src/cli/db.js +59 -60
- package/src/cli/deploy.js +273 -159
- package/src/cli/fs.js +3 -1
- package/src/cli/index.js +16 -9
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +217 -135
- package/src/cli/release.js +289 -131
- package/src/cli/repository.js +58 -7
- package/src/cli/run.js +152 -25
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +4 -0
- package/src/client/components/core/PanelForm.js +56 -52
- package/src/client/components/core/Worker.js +162 -363
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +120 -20
- package/src/db/mongo/MongoBootstrap.js +587 -0
- package/src/db/mongo/MongooseDB.js +126 -22
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +20 -65
- package/src/server/process.js +180 -19
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +12 -4
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /package/src/client/ssr/{pages → views}/Test.js +0 -0
|
@@ -1,65 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Progressive Web App (PWA) worker integration: service worker lifecycle,
|
|
3
|
+
* cache management, and the bootstrap path that wires the app's router,
|
|
4
|
+
* shell, translation, sockets, and session components together.
|
|
5
|
+
*
|
|
6
|
+
* The public surface is a single `PwaWorker` class with one shared instance,
|
|
7
|
+
* re-exported as `Worker` for backward compatibility.
|
|
8
|
+
*
|
|
5
9
|
* @module src/client/components/core/Worker.js
|
|
6
10
|
* @namespace PwaWorker
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
13
|
import { BtnIcon } from './BtnIcon.js';
|
|
10
|
-
import { s4 } from './CommonJs.js';
|
|
11
14
|
import { EventsUI } from './EventsUI.js';
|
|
12
15
|
import { LoadingAnimation } from './LoadingAnimation.js';
|
|
13
16
|
import { loggerFactory } from './Logger.js';
|
|
14
|
-
import { LoadRouter } from './Router.js';
|
|
15
|
-
import {
|
|
16
|
-
import { Translate } from './Translate.js';
|
|
17
|
+
import { LoadRouter, registerRoutes, getProxyPath } from './Router.js';
|
|
18
|
+
import { Translate, TranslateCore } from './Translate.js';
|
|
17
19
|
import { s } from './VanillaJs.js';
|
|
18
|
-
import { getProxyPath } from './Router.js';
|
|
19
20
|
import { Css } from './Css.js';
|
|
20
|
-
import { TranslateCore } from './Translate.js';
|
|
21
21
|
import { Responsive } from './Responsive.js';
|
|
22
22
|
import { SocketIo } from './SocketIo.js';
|
|
23
23
|
import { Keyboard } from './Keyboard.js';
|
|
24
|
+
|
|
24
25
|
const logger = loggerFactory(import.meta);
|
|
25
26
|
|
|
27
|
+
const SW_URL = () => `${getProxyPath()}sw.js`;
|
|
28
|
+
|
|
26
29
|
/**
|
|
27
|
-
* Manages the PWA lifecycle, service workers, and related client-side events.
|
|
28
30
|
* @memberof PwaWorker
|
|
29
31
|
*/
|
|
30
32
|
class PwaWorker {
|
|
31
|
-
/**
|
|
32
|
-
* The application title, usually from the <title> tag content.
|
|
33
|
-
* @type {string}
|
|
34
|
-
*/
|
|
33
|
+
/** App title sourced from <title>. @type {string} */
|
|
35
34
|
title = '';
|
|
36
35
|
|
|
37
|
-
/**
|
|
38
|
-
* Tracks if notification permission has been granted and is active.
|
|
39
|
-
* @type {boolean}
|
|
40
|
-
*/
|
|
41
|
-
notificationActive = false;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* A function reference to the service worker's update method (registration.update()),
|
|
45
|
-
* dynamically set upon successful registration status check.
|
|
46
|
-
* @type {function(): Promise<void>}
|
|
47
|
-
*/
|
|
48
|
-
updateServiceWorker = async () => {};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Router instance reference, initialized during the `instance` call.
|
|
52
|
-
* @type {object | null}
|
|
53
|
-
*/
|
|
36
|
+
/** Router instance reference, set during bootstrap. @type {object | null} */
|
|
54
37
|
RouterInstance = null;
|
|
55
38
|
|
|
56
|
-
/**
|
|
57
|
-
* Creates an instance of PwaWorker and initializes the application title.
|
|
58
|
-
* @memberof PwaWorker
|
|
59
|
-
*/
|
|
60
39
|
constructor() {
|
|
61
40
|
this.title = `${s('title').textContent}`;
|
|
62
|
-
if (!window.renderPayload
|
|
41
|
+
if (!window.renderPayload?.dev) {
|
|
63
42
|
console.log = () => null;
|
|
64
43
|
console.error = () => null;
|
|
65
44
|
console.info = () => null;
|
|
@@ -67,58 +46,47 @@ class PwaWorker {
|
|
|
67
46
|
}
|
|
68
47
|
}
|
|
69
48
|
|
|
70
|
-
/**
|
|
71
|
-
* Checks if the application is running in development mode (localhost or 127.0.0.1).
|
|
72
|
-
* @method devMode
|
|
73
|
-
* @memberof PwaWorker
|
|
74
|
-
* @returns {boolean} True if in development mode.
|
|
75
|
-
*/
|
|
49
|
+
/** @returns {boolean} True when running on localhost or with renderPayload.dev. */
|
|
76
50
|
devMode() {
|
|
77
|
-
return
|
|
51
|
+
return Boolean(
|
|
52
|
+
window.renderPayload?.dev || location.origin.match('localhost') || location.origin.match('127.0.0.1'),
|
|
53
|
+
);
|
|
78
54
|
}
|
|
79
55
|
|
|
80
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Awaits a component's `instance()` method, a plain function, or an array of either.
|
|
58
|
+
* Used by `instance()` to keep the bootstrap declarative.
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
async #initComponent(component, options) {
|
|
81
62
|
if (!component) return;
|
|
82
63
|
if (Array.isArray(component)) {
|
|
83
|
-
for (const item of component) await this
|
|
64
|
+
for (const item of component) await this.#initComponent(item, options);
|
|
84
65
|
return;
|
|
85
66
|
}
|
|
86
|
-
if (typeof component.instance === 'function')
|
|
87
|
-
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (typeof component === 'function') {
|
|
91
|
-
await component(options);
|
|
92
|
-
}
|
|
67
|
+
if (typeof component.instance === 'function') return void (await component.instance(options));
|
|
68
|
+
if (typeof component === 'function') await component(options);
|
|
93
69
|
}
|
|
94
70
|
|
|
95
71
|
/**
|
|
96
|
-
* Bootstraps the app with a declarative options object.
|
|
97
|
-
*
|
|
98
|
-
*
|
|
72
|
+
* Bootstraps the app with a declarative options object. Shared core inits
|
|
73
|
+
* (Css, TranslateCore, Responsive, SocketIo, Keyboard) run in the correct
|
|
74
|
+
* order so per-app entrypoints only declare what's app-specific.
|
|
99
75
|
*
|
|
100
76
|
* @param {object} options
|
|
101
|
-
* @param {function
|
|
102
|
-
* @param {function(): Promise<string>} [options.template]
|
|
103
|
-
* @param {Array} [options.themes]
|
|
104
|
-
* @param {object|Array} [options.translate] -
|
|
105
|
-
* @param {object} [options.render]
|
|
106
|
-
* @param {string} [options.socketPath]
|
|
107
|
-
* @param {object} [options.appStore]
|
|
108
|
-
* @param {object} [options.session]
|
|
109
|
-
* @param {function(): Promise<void>} [options.render] - Legacy raw render callback (backward-compat).
|
|
77
|
+
* @param {function|object} options.router Router class (with static `instance()`) or factory.
|
|
78
|
+
* @param {function(): Promise<string>} [options.template] Async function returning the landing HTML body.
|
|
79
|
+
* @param {Array} [options.themes] CSS theme list passed to `Css.loadThemes()`.
|
|
80
|
+
* @param {object|Array} [options.translate] App-level translation class(es).
|
|
81
|
+
* @param {object} [options.render] AppShell class with `instance({ htmlMainBody })`.
|
|
82
|
+
* @param {string} [options.socketPath] Socket.IO path override.
|
|
83
|
+
* @param {object} [options.appStore] AppStore whose `.Data` supplies socket channels.
|
|
84
|
+
* @param {object} [options.session] `{ socket, login, signout|logout, signup, account }`.
|
|
110
85
|
* @returns {Promise<void>}
|
|
111
86
|
*/
|
|
112
87
|
async instance({ router, template, themes, translate, render, socketPath, appStore, session }) {
|
|
113
|
-
window.ononline =
|
|
114
|
-
|
|
115
|
-
};
|
|
116
|
-
window.onoffline = async () => {
|
|
117
|
-
logger.warn('onoffline');
|
|
118
|
-
};
|
|
119
|
-
setTimeout(() => {
|
|
120
|
-
if ('onLine' in navigator && navigator.onLine) window.ononline();
|
|
121
|
-
});
|
|
88
|
+
window.ononline = () => logger.warn('ononline');
|
|
89
|
+
window.onoffline = () => logger.warn('onoffline');
|
|
122
90
|
|
|
123
91
|
if ('serviceWorker' in navigator) {
|
|
124
92
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
@@ -126,123 +94,115 @@ class PwaWorker {
|
|
|
126
94
|
});
|
|
127
95
|
navigator.serviceWorker.ready.then((worker) => {
|
|
128
96
|
logger.info('Service Worker Ready', worker);
|
|
129
|
-
|
|
130
|
-
// event message listener
|
|
131
97
|
navigator.serviceWorker.addEventListener('message', (event) => {
|
|
132
98
|
logger.info('Received event message', event.data);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
switch (status) {
|
|
136
|
-
case 'loader':
|
|
137
|
-
{
|
|
138
|
-
LoadingAnimation.RenderCurrentSrcLoad(event);
|
|
139
|
-
}
|
|
140
|
-
break;
|
|
141
|
-
|
|
142
|
-
default:
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
99
|
+
if (event.data?.status === 'loader') LoadingAnimation.RenderCurrentSrcLoad(event);
|
|
145
100
|
});
|
|
146
|
-
|
|
147
|
-
// if (navigator.serviceWorker.controller)
|
|
148
|
-
// navigator.serviceWorker.controller.postMessage({
|
|
149
|
-
// title: 'Hello from Client event message',
|
|
150
|
-
// });
|
|
151
|
-
|
|
152
|
-
// broadcast message
|
|
153
|
-
// const channel = new BroadcastChannel('sw-messages');
|
|
154
|
-
// channel.addEventListener('message', (event) => {
|
|
155
|
-
// logger.info('Received broadcast message', event.data);
|
|
156
|
-
// });
|
|
157
|
-
// channel.postMessage({ title: 'Hello from Client broadcast message' });
|
|
158
|
-
// channel.close();
|
|
159
101
|
});
|
|
160
102
|
}
|
|
161
103
|
|
|
162
104
|
this.RouterInstance = typeof router?.instance === 'function' ? router.instance() : router();
|
|
163
105
|
if (this.RouterInstance?.Routes) registerRoutes(this.RouterInstance.Routes);
|
|
164
|
-
// Defer SW registration entirely out of the render critical path.
|
|
165
|
-
// Firefox IPC for SW registration is notably slower than Chromium;
|
|
166
|
-
// scheduling after 'load' means the first meaningful paint is not blocked.
|
|
167
|
-
this.registerServiceWorkerDeferred();
|
|
168
106
|
|
|
169
|
-
//
|
|
170
|
-
//
|
|
107
|
+
// Defer SW registration out of the render critical path. Firefox in
|
|
108
|
+
// particular pays a non-trivial IPC cost for SW registration; running it
|
|
109
|
+
// after `load` keeps first paint unblocked across all engines.
|
|
110
|
+
this.#registerDeferred();
|
|
111
|
+
|
|
112
|
+
// Shared core inits — keep order: theme → translation → responsive → shell.
|
|
171
113
|
if (themes) await Css.loadThemes(themes);
|
|
172
|
-
await this
|
|
173
|
-
await this
|
|
174
|
-
await this
|
|
175
|
-
|
|
114
|
+
await this.#initComponent(TranslateCore);
|
|
115
|
+
await this.#initComponent(translate);
|
|
116
|
+
await this.#initComponent(Responsive);
|
|
117
|
+
|
|
176
118
|
if (render && typeof render.instance === 'function') {
|
|
177
119
|
const htmlMainBody = typeof template === 'function' ? template : undefined;
|
|
178
|
-
await this
|
|
120
|
+
await this.#initComponent(render, htmlMainBody ? { htmlMainBody } : undefined);
|
|
179
121
|
}
|
|
180
|
-
|
|
181
|
-
const channels = appStore
|
|
182
|
-
await this
|
|
122
|
+
|
|
123
|
+
const channels = appStore?.Data ?? session?.socket?.Data;
|
|
124
|
+
await this.#initComponent(SocketIo, { channels, path: socketPath });
|
|
125
|
+
|
|
183
126
|
if (session) {
|
|
184
|
-
await this
|
|
185
|
-
await this
|
|
186
|
-
await this
|
|
187
|
-
await this
|
|
188
|
-
await this
|
|
127
|
+
await this.#initComponent(session.socket);
|
|
128
|
+
await this.#initComponent(session.login);
|
|
129
|
+
await this.#initComponent(session.logout || session.signout);
|
|
130
|
+
await this.#initComponent(session.signup);
|
|
131
|
+
await this.#initComponent(session.account);
|
|
189
132
|
}
|
|
190
|
-
|
|
191
|
-
|
|
133
|
+
|
|
134
|
+
await this.#initComponent(Keyboard);
|
|
192
135
|
await LoadRouter(this.RouterInstance);
|
|
193
136
|
LoadingAnimation.removeSplashScreen();
|
|
194
|
-
if (this.devMode()) {
|
|
195
|
-
// const delayLiveReload = 1250;
|
|
196
|
-
// window.addEventListener('visibilitychange', () => {
|
|
197
|
-
// if (document.visibilityState === 'visible') {
|
|
198
|
-
// this.reload(delayLiveReload);
|
|
199
|
-
// }
|
|
200
|
-
// });
|
|
201
|
-
// window.addEventListener('focus', () => {
|
|
202
|
-
// this.reload(delayLiveReload);
|
|
203
|
-
// });
|
|
204
|
-
}
|
|
205
137
|
window.serviceWorkerReady = true;
|
|
206
138
|
}
|
|
207
139
|
|
|
208
140
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
141
|
+
* Schedules SW registration to run after page `load` (or immediately if
|
|
142
|
+
* the page is already loaded). Idempotent: existing registrations are
|
|
143
|
+
* left alone; missing ones are registered.
|
|
144
|
+
* @private
|
|
212
145
|
*/
|
|
213
|
-
|
|
214
|
-
|
|
146
|
+
#registerDeferred() {
|
|
147
|
+
if (!('serviceWorker' in navigator)) {
|
|
148
|
+
logger.warn('Service Worker disabled in browser');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const run = async () => {
|
|
152
|
+
const registration = await this.getRegistration();
|
|
153
|
+
if (!registration) await this.register();
|
|
154
|
+
};
|
|
155
|
+
if (document.readyState === 'complete') run();
|
|
156
|
+
else window.addEventListener('load', run, { once: true });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Returns the current service worker registration, if any. */
|
|
160
|
+
getRegistration() {
|
|
161
|
+
return 'serviceWorker' in navigator ? navigator.serviceWorker.getRegistration() : Promise.resolve(undefined);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Registers `sw.js` with the browser. Resolves with the registration (or `null` on failure). */
|
|
165
|
+
async register() {
|
|
166
|
+
if (!('serviceWorker' in navigator)) {
|
|
167
|
+
logger.warn('Service Worker disabled in browser');
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const registration = await navigator.serviceWorker.register(SW_URL());
|
|
172
|
+
logger.warn('Service Worker registered', registration);
|
|
173
|
+
return registration;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
logger.error('Error registering service worker:', error);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
215
178
|
}
|
|
216
179
|
|
|
217
180
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* @
|
|
221
|
-
* @param {number} [timeOut=3000] - Delay in milliseconds before reloading the page.
|
|
222
|
-
* @returns {Promise<void>} A promise that resolves after the page is reloaded.
|
|
181
|
+
* Reloads the page, asking the active SW to skip waiting first so the
|
|
182
|
+
* new version takes effect on the next load.
|
|
183
|
+
* @param {number} [delay=3000] Milliseconds before reload.
|
|
223
184
|
*/
|
|
224
|
-
async reload(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
navigator.serviceWorker.controller.postMessage({
|
|
228
|
-
status: 'skipWaiting',
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
setTimeout(() => resolve(location.reload()), timeOut);
|
|
232
|
-
});
|
|
185
|
+
async reload(delay = 3000) {
|
|
186
|
+
navigator.serviceWorker?.controller?.postMessage({ status: 'skipWaiting' });
|
|
187
|
+
return new Promise((resolve) => setTimeout(() => resolve(location.reload()), delay));
|
|
233
188
|
}
|
|
234
189
|
|
|
190
|
+
/** Deletes every cache visible to the SW. Returns the number of caches removed. */
|
|
235
191
|
async clearAllCaches() {
|
|
236
|
-
const
|
|
237
|
-
await Promise.all(
|
|
238
|
-
return
|
|
192
|
+
const names = await caches.keys();
|
|
193
|
+
await Promise.all(names.map((name) => caches.delete(name)));
|
|
194
|
+
return names.length;
|
|
239
195
|
}
|
|
240
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Deletes Workbox/background-sync IndexedDB databases that may pin stale state
|
|
199
|
+
* across reloads. Safe no-op in browsers without `indexedDB.databases`.
|
|
200
|
+
*/
|
|
241
201
|
async clearWorkboxIndexedDb() {
|
|
242
202
|
if (!('indexedDB' in window) || typeof indexedDB.databases !== 'function') return 0;
|
|
243
203
|
const databases = await indexedDB.databases();
|
|
244
|
-
const
|
|
245
|
-
.map((
|
|
204
|
+
const targets = databases
|
|
205
|
+
.map((db) => db.name)
|
|
246
206
|
.filter(
|
|
247
207
|
(name) =>
|
|
248
208
|
typeof name === 'string' &&
|
|
@@ -252,263 +212,102 @@ class PwaWorker {
|
|
|
252
212
|
);
|
|
253
213
|
|
|
254
214
|
await Promise.all(
|
|
255
|
-
|
|
215
|
+
targets.map(
|
|
256
216
|
(name) =>
|
|
257
217
|
new Promise((resolve) => {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
218
|
+
const req = indexedDB.deleteDatabase(name);
|
|
219
|
+
req.onsuccess = () => resolve(true);
|
|
220
|
+
req.onerror = () => resolve(false);
|
|
221
|
+
req.onblocked = () => resolve(false);
|
|
262
222
|
}),
|
|
263
223
|
),
|
|
264
224
|
);
|
|
265
|
-
|
|
266
|
-
return workboxDatabases.length;
|
|
225
|
+
return targets.length;
|
|
267
226
|
}
|
|
268
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Asks the active SW to drop its caches and confirm. Resolves `false`
|
|
230
|
+
* after 1500 ms if the SW does not reply.
|
|
231
|
+
*/
|
|
269
232
|
async requestWorkboxReset() {
|
|
270
|
-
if (!
|
|
271
|
-
|
|
233
|
+
if (!navigator.serviceWorker?.controller) return false;
|
|
272
234
|
return new Promise((resolve) => {
|
|
273
235
|
const channel = new MessageChannel();
|
|
274
236
|
const timeoutId = setTimeout(() => resolve(false), 1500);
|
|
275
|
-
|
|
276
237
|
channel.port1.onmessage = (event) => {
|
|
277
238
|
clearTimeout(timeoutId);
|
|
278
239
|
resolve(event.data?.status === 'workbox-reset-done');
|
|
279
240
|
};
|
|
280
|
-
|
|
281
241
|
navigator.serviceWorker.controller.postMessage({ status: 'workbox-reset' }, [channel.port2]);
|
|
282
242
|
});
|
|
283
243
|
}
|
|
284
244
|
|
|
285
|
-
async resetWorkboxAndRestart() {
|
|
286
|
-
localStorage.clear();
|
|
287
|
-
sessionStorage.clear();
|
|
288
|
-
|
|
289
|
-
await this.requestWorkboxReset();
|
|
290
|
-
await this.uninstall();
|
|
291
|
-
await this.clearAllCaches();
|
|
292
|
-
await this.clearWorkboxIndexedDb();
|
|
293
|
-
await this.install();
|
|
294
|
-
await this.reload(600);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
245
|
/**
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
* @returns {Promise<void>}
|
|
246
|
+
* Targeted cache invalidation: removes app-shell entries for components,
|
|
247
|
+
* services, and index bundles, then forces the SW registration to update.
|
|
248
|
+
* Cheaper than a full reset; used as the default "Update" action.
|
|
302
249
|
*/
|
|
303
250
|
async update() {
|
|
304
|
-
const
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
251
|
+
const registration = await this.getRegistration();
|
|
252
|
+
if (!registration) return;
|
|
253
|
+
const names = await caches.keys();
|
|
254
|
+
for (const name of names) {
|
|
255
|
+
if (name.match('components/') || name.match('services/') || name.match('.index.js')) {
|
|
256
|
+
await caches.delete(name);
|
|
311
257
|
}
|
|
312
|
-
await this.updateServiceWorker();
|
|
313
258
|
}
|
|
259
|
+
await registration.update();
|
|
314
260
|
}
|
|
315
261
|
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
* if the page is already loaded). This keeps SW work off the render critical path,
|
|
319
|
-
* which is the primary cause of Firefox being slower than Chromium on first load.
|
|
320
|
-
* @memberof PwaWorker
|
|
321
|
-
* @returns {void}
|
|
322
|
-
*/
|
|
323
|
-
registerServiceWorkerDeferred() {
|
|
262
|
+
/** Unregisters all SWs and drops every cache. */
|
|
263
|
+
async unregister() {
|
|
324
264
|
if (!('serviceWorker' in navigator)) {
|
|
325
|
-
logger.warn('Service Worker
|
|
265
|
+
logger.warn('Service Worker disabled in browser');
|
|
326
266
|
return;
|
|
327
267
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
register();
|
|
335
|
-
} else {
|
|
336
|
-
window.addEventListener('load', register, { once: true });
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Checks the current status of all service worker registrations and sets the
|
|
342
|
-
* `updateServiceWorker` function reference if an active worker is found.
|
|
343
|
-
* @memberof PwaWorker
|
|
344
|
-
* @returns {Promise<boolean>} True if at least one service worker is registered.
|
|
345
|
-
*/
|
|
346
|
-
status() {
|
|
347
|
-
let status = false;
|
|
348
|
-
return new Promise((resolve) => {
|
|
349
|
-
if ('serviceWorker' in navigator) {
|
|
350
|
-
navigator.serviceWorker
|
|
351
|
-
.getRegistrations()
|
|
352
|
-
.then((registrations) => {
|
|
353
|
-
for (const registration of registrations) {
|
|
354
|
-
if (registration.installing) logger.info('installing', registration);
|
|
355
|
-
else if (registration.waiting) logger.info('waiting', registration);
|
|
356
|
-
else if (registration.active) {
|
|
357
|
-
logger.info('active', registration);
|
|
358
|
-
// Dynamically set the update function
|
|
359
|
-
this.updateServiceWorker = async () => await registration.update();
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (registrations.length > 0) status = true;
|
|
363
|
-
resolve(status);
|
|
364
|
-
})
|
|
365
|
-
.catch((...args) => {
|
|
366
|
-
logger.error('Error getting service worker registrations:', ...args);
|
|
367
|
-
resolve(false);
|
|
368
|
-
});
|
|
369
|
-
} else {
|
|
370
|
-
logger.warn('Service Worker Disabled in browser');
|
|
371
|
-
resolve(false);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Registers the service worker (`sw.js`) with the browser.
|
|
378
|
-
* @memberof PwaWorker
|
|
379
|
-
* @returns {Promise<Array<any>>} A promise that resolves with the registration arguments.
|
|
380
|
-
*/
|
|
381
|
-
install() {
|
|
382
|
-
return new Promise((resolve) => {
|
|
383
|
-
if ('serviceWorker' in navigator) {
|
|
384
|
-
navigator.serviceWorker
|
|
385
|
-
.register(`${getProxyPath()}sw.js`)
|
|
386
|
-
.then((...args) => {
|
|
387
|
-
logger.warn('Service Worker Registered', args);
|
|
388
|
-
resolve(args);
|
|
389
|
-
})
|
|
390
|
-
.catch((...args) => {
|
|
391
|
-
logger.error('Error registering service worker:', ...args);
|
|
392
|
-
resolve(args);
|
|
393
|
-
});
|
|
394
|
-
} else {
|
|
395
|
-
logger.warn('Service Worker Disabled in browser');
|
|
396
|
-
resolve([]);
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Unregisters all service workers and deletes all application caches.
|
|
403
|
-
* @memberof PwaWorker
|
|
404
|
-
* @returns {Promise<Array<any>>} A promise that resolves after uninstallation.
|
|
405
|
-
*/
|
|
406
|
-
uninstall() {
|
|
407
|
-
return new Promise(async (resolve) => {
|
|
408
|
-
if ('serviceWorker' in navigator) {
|
|
409
|
-
try {
|
|
410
|
-
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
411
|
-
const cacheNames = await caches.keys();
|
|
412
|
-
for (const cacheName of cacheNames) await caches.delete(cacheName);
|
|
413
|
-
for (const registration of registrations) {
|
|
414
|
-
logger.info('Removing service worker registration', registration);
|
|
415
|
-
registration.unregister();
|
|
416
|
-
}
|
|
417
|
-
resolve([]);
|
|
418
|
-
} catch (error) {
|
|
419
|
-
logger.error('Error during service worker uninstallation:', error);
|
|
420
|
-
resolve([error]);
|
|
421
|
-
}
|
|
422
|
-
} else {
|
|
423
|
-
logger.warn('Service Worker Disabled in browser');
|
|
424
|
-
resolve([]);
|
|
268
|
+
try {
|
|
269
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
270
|
+
await this.clearAllCaches();
|
|
271
|
+
for (const registration of registrations) {
|
|
272
|
+
logger.info('Removing service worker registration', registration);
|
|
273
|
+
registration.unregister();
|
|
425
274
|
}
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Requests permission from the user to display notifications.
|
|
431
|
-
* Sets the internal `notificationActive` state.
|
|
432
|
-
* @memberof PwaWorker
|
|
433
|
-
* @returns {Promise<boolean>} True if permission is granted, false otherwise.
|
|
434
|
-
*/
|
|
435
|
-
notificationRequestPermission() {
|
|
436
|
-
return new Promise((resolve) =>
|
|
437
|
-
Notification.requestPermission().then((result) => {
|
|
438
|
-
if (result === 'granted') {
|
|
439
|
-
this.notificationActive = true;
|
|
440
|
-
resolve(true);
|
|
441
|
-
} else {
|
|
442
|
-
this.notificationActive = false;
|
|
443
|
-
resolve(false);
|
|
444
|
-
}
|
|
445
|
-
}),
|
|
446
|
-
);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
logger.error('Error during service worker unregistration:', error);
|
|
277
|
+
}
|
|
447
278
|
}
|
|
448
279
|
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
body: 'Buzz! Buzz!',
|
|
460
|
-
icon: '../images/touch/chrome-touch-icon.png',
|
|
461
|
-
vibrate: [200, 100, 200, 100, 200, 100, 200],
|
|
462
|
-
tag: 'vibration-sample',
|
|
463
|
-
requireInteraction: true,
|
|
464
|
-
});
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
});
|
|
280
|
+
/** Full reset: workbox runtime + SW + client storage. Use when caches are visibly stale. */
|
|
281
|
+
async resetAndRestart() {
|
|
282
|
+
localStorage.clear();
|
|
283
|
+
sessionStorage.clear();
|
|
284
|
+
await this.requestWorkboxReset();
|
|
285
|
+
await this.unregister();
|
|
286
|
+
await this.clearAllCaches();
|
|
287
|
+
await this.clearWorkboxIndexedDb();
|
|
288
|
+
await this.register();
|
|
289
|
+
await this.reload(600);
|
|
468
290
|
}
|
|
469
291
|
|
|
470
|
-
/**
|
|
471
|
-
* Renders the UI for PWA settings, including buttons for cleaning cache and worker management.
|
|
472
|
-
* It also attaches the click handler for the 'clean-cache' button.
|
|
473
|
-
* @memberof PwaWorker
|
|
474
|
-
* @returns {Promise<string>} The HTML string for the settings section.
|
|
475
|
-
*/
|
|
292
|
+
/** Settings panel UI: a single "clean cache" action wired to `resetAndRestart`. */
|
|
476
293
|
async RenderSetting() {
|
|
477
294
|
setTimeout(() => {
|
|
478
|
-
// Event listener for the clean cache button
|
|
479
295
|
EventsUI.onClick(`.btn-clean-cache`, async (e) => {
|
|
480
296
|
e.preventDefault();
|
|
481
|
-
|
|
482
|
-
await this.resetWorkboxAndRestart();
|
|
297
|
+
await this.resetAndRestart();
|
|
483
298
|
});
|
|
484
299
|
});
|
|
485
300
|
return html` <div class="in">
|
|
486
|
-
${await BtnIcon.instance({
|
|
487
|
-
class: 'inl section-mp btn-custom btn-install-service-controller hide',
|
|
488
|
-
label: html`<i class="fas fa-download"></i> ${Translate.instance('Install control service')}`,
|
|
489
|
-
})}
|
|
490
|
-
${await BtnIcon.instance({
|
|
491
|
-
class: 'inl section-mp btn-custom btn-uninstall-service-controller hide',
|
|
492
|
-
label: html`<i class="far fa-trash-alt"></i> ${Translate.instance('Uninstall control service')}`,
|
|
493
|
-
})}
|
|
494
301
|
${await BtnIcon.instance({
|
|
495
302
|
class: 'inl section-mp btn-custom btn-clean-cache',
|
|
496
303
|
label: html`<i class="fa-solid fa-broom"></i> ${Translate.instance('clean-cache')}`,
|
|
497
304
|
})}
|
|
498
|
-
${await BtnIcon.instance({
|
|
499
|
-
class: 'inl section-mp btn-custom btn-reload hide',
|
|
500
|
-
label: html`<i class="fas fa-sync-alt"></i> ${Translate.instance('Reload')}`,
|
|
501
|
-
})}
|
|
502
305
|
</div>`;
|
|
503
306
|
}
|
|
504
307
|
}
|
|
505
308
|
|
|
506
|
-
// Create the singleton instance
|
|
507
309
|
const PwaWorkerInstance = new PwaWorker();
|
|
508
310
|
|
|
509
|
-
// Export the new class name for modern usage
|
|
510
311
|
export { PwaWorker };
|
|
511
|
-
|
|
512
|
-
// Export the instance with the old name (`Worker`) for backward compatibility,
|
|
513
|
-
// ensuring existing code consuming the module continues to work.
|
|
312
|
+
// Backward-compat alias — older code imports the singleton as `Worker`.
|
|
514
313
|
export { PwaWorkerInstance as Worker };
|