underpost 3.2.8 → 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 +223 -2
- package/CLI-HELP.md +36 -7
- package/README.md +38 -9
- package/bin/build.js +27 -11
- package/bin/deploy.js +20 -21
- package/bin/file.js +32 -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 +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
- 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 +40 -25
- package/scripts/k3s-node-setup.sh +30 -11
- package/scripts/nat-iptables.sh +103 -18
- 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 +243 -55
- package/src/cli/db.js +106 -62
- package/src/cli/deploy.js +297 -154
- package/src/cli/fs.js +19 -3
- package/src/cli/index.js +37 -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 +91 -34
- package/src/cli/run.js +297 -56
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +19 -5
- package/src/client/components/core/Docs.js +6 -34
- package/src/client/components/core/FileExplorer.js +6 -6
- package/src/client/components/core/Modal.js +65 -2
- package/src/client/components/core/PanelForm.js +56 -52
- package/src/client/components/core/Recover.js +4 -4
- package/src/client/components/core/Worker.js +170 -350
- package/src/client/services/default/default.management.js +20 -25
- package/src/client/services/user/guest.service.js +10 -3
- 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/data-query.js +32 -20
- package/src/server/dns.js +22 -0
- package/src/server/process.js +180 -19
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +26 -7
- package/src/server/valkey.js +9 -2
- 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/typedoc.json +10 -1
- 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,126 +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
|
-
const isInstall = await this.status();
|
|
165
|
-
if (!isInstall) await this.install();
|
|
166
106
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
await this.runComponentInit(SocketIo, { channels, path: socketPath });
|
|
182
|
-
if (session) {
|
|
183
|
-
await this.runComponentInit(session.socket);
|
|
184
|
-
await this.runComponentInit(session.login);
|
|
185
|
-
await this.runComponentInit(session.logout || session.signout);
|
|
186
|
-
await this.runComponentInit(session.signup);
|
|
187
|
-
await this.runComponentInit(session.account);
|
|
188
|
-
}
|
|
189
|
-
await this.runComponentInit(Keyboard);
|
|
190
|
-
} else {
|
|
191
|
-
// ── legacy raw render callback (backward-compat) ─────────────────────
|
|
192
|
-
await render();
|
|
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.
|
|
113
|
+
if (themes) await Css.loadThemes(themes);
|
|
114
|
+
await this.#initComponent(TranslateCore);
|
|
115
|
+
await this.#initComponent(translate);
|
|
116
|
+
await this.#initComponent(Responsive);
|
|
117
|
+
|
|
118
|
+
if (render && typeof render.instance === 'function') {
|
|
119
|
+
const htmlMainBody = typeof template === 'function' ? template : undefined;
|
|
120
|
+
await this.#initComponent(render, htmlMainBody ? { htmlMainBody } : undefined);
|
|
193
121
|
}
|
|
194
|
-
|
|
122
|
+
|
|
123
|
+
const channels = appStore?.Data ?? session?.socket?.Data;
|
|
124
|
+
await this.#initComponent(SocketIo, { channels, path: socketPath });
|
|
125
|
+
|
|
126
|
+
if (session) {
|
|
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);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await this.#initComponent(Keyboard);
|
|
195
135
|
await LoadRouter(this.RouterInstance);
|
|
196
136
|
LoadingAnimation.removeSplashScreen();
|
|
197
|
-
if (this.devMode()) {
|
|
198
|
-
// const delayLiveReload = 1250;
|
|
199
|
-
// window.addEventListener('visibilitychange', () => {
|
|
200
|
-
// if (document.visibilityState === 'visible') {
|
|
201
|
-
// this.reload(delayLiveReload);
|
|
202
|
-
// }
|
|
203
|
-
// });
|
|
204
|
-
// window.addEventListener('focus', () => {
|
|
205
|
-
// this.reload(delayLiveReload);
|
|
206
|
-
// });
|
|
207
|
-
}
|
|
208
137
|
window.serviceWorkerReady = true;
|
|
209
138
|
}
|
|
210
139
|
|
|
211
140
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
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
|
|
215
145
|
*/
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
}
|
|
218
178
|
}
|
|
219
179
|
|
|
220
180
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* @
|
|
224
|
-
* @param {number} [timeOut=3000] - Delay in milliseconds before reloading the page.
|
|
225
|
-
* @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.
|
|
226
184
|
*/
|
|
227
|
-
async reload(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
navigator.serviceWorker.controller.postMessage({
|
|
231
|
-
status: 'skipWaiting',
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
setTimeout(() => resolve(location.reload()), timeOut);
|
|
235
|
-
});
|
|
185
|
+
async reload(delay = 3000) {
|
|
186
|
+
navigator.serviceWorker?.controller?.postMessage({ status: 'skipWaiting' });
|
|
187
|
+
return new Promise((resolve) => setTimeout(() => resolve(location.reload()), delay));
|
|
236
188
|
}
|
|
237
189
|
|
|
190
|
+
/** Deletes every cache visible to the SW. Returns the number of caches removed. */
|
|
238
191
|
async clearAllCaches() {
|
|
239
|
-
const
|
|
240
|
-
await Promise.all(
|
|
241
|
-
return
|
|
192
|
+
const names = await caches.keys();
|
|
193
|
+
await Promise.all(names.map((name) => caches.delete(name)));
|
|
194
|
+
return names.length;
|
|
242
195
|
}
|
|
243
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
|
+
*/
|
|
244
201
|
async clearWorkboxIndexedDb() {
|
|
245
202
|
if (!('indexedDB' in window) || typeof indexedDB.databases !== 'function') return 0;
|
|
246
203
|
const databases = await indexedDB.databases();
|
|
247
|
-
const
|
|
248
|
-
.map((
|
|
204
|
+
const targets = databases
|
|
205
|
+
.map((db) => db.name)
|
|
249
206
|
.filter(
|
|
250
207
|
(name) =>
|
|
251
208
|
typeof name === 'string' &&
|
|
@@ -255,239 +212,102 @@ class PwaWorker {
|
|
|
255
212
|
);
|
|
256
213
|
|
|
257
214
|
await Promise.all(
|
|
258
|
-
|
|
215
|
+
targets.map(
|
|
259
216
|
(name) =>
|
|
260
217
|
new Promise((resolve) => {
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
218
|
+
const req = indexedDB.deleteDatabase(name);
|
|
219
|
+
req.onsuccess = () => resolve(true);
|
|
220
|
+
req.onerror = () => resolve(false);
|
|
221
|
+
req.onblocked = () => resolve(false);
|
|
265
222
|
}),
|
|
266
223
|
),
|
|
267
224
|
);
|
|
268
|
-
|
|
269
|
-
return workboxDatabases.length;
|
|
225
|
+
return targets.length;
|
|
270
226
|
}
|
|
271
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
|
+
*/
|
|
272
232
|
async requestWorkboxReset() {
|
|
273
|
-
if (!
|
|
274
|
-
|
|
233
|
+
if (!navigator.serviceWorker?.controller) return false;
|
|
275
234
|
return new Promise((resolve) => {
|
|
276
235
|
const channel = new MessageChannel();
|
|
277
236
|
const timeoutId = setTimeout(() => resolve(false), 1500);
|
|
278
|
-
|
|
279
237
|
channel.port1.onmessage = (event) => {
|
|
280
238
|
clearTimeout(timeoutId);
|
|
281
239
|
resolve(event.data?.status === 'workbox-reset-done');
|
|
282
240
|
};
|
|
283
|
-
|
|
284
241
|
navigator.serviceWorker.controller.postMessage({ status: 'workbox-reset' }, [channel.port2]);
|
|
285
242
|
});
|
|
286
243
|
}
|
|
287
244
|
|
|
288
|
-
async resetWorkboxAndRestart() {
|
|
289
|
-
localStorage.clear();
|
|
290
|
-
sessionStorage.clear();
|
|
291
|
-
|
|
292
|
-
await this.requestWorkboxReset();
|
|
293
|
-
await this.uninstall();
|
|
294
|
-
await this.clearAllCaches();
|
|
295
|
-
await this.clearWorkboxIndexedDb();
|
|
296
|
-
await this.install();
|
|
297
|
-
await this.reload(600);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
245
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* @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.
|
|
305
249
|
*/
|
|
306
250
|
async update() {
|
|
307
|
-
const
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
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);
|
|
314
257
|
}
|
|
315
|
-
await this.updateServiceWorker();
|
|
316
258
|
}
|
|
259
|
+
await registration.update();
|
|
317
260
|
}
|
|
318
261
|
|
|
319
|
-
/**
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
.then((registrations) => {
|
|
332
|
-
for (const registration of registrations) {
|
|
333
|
-
if (registration.installing) logger.info('installing', registration);
|
|
334
|
-
else if (registration.waiting) logger.info('waiting', registration);
|
|
335
|
-
else if (registration.active) {
|
|
336
|
-
logger.info('active', registration);
|
|
337
|
-
// Dynamically set the update function
|
|
338
|
-
this.updateServiceWorker = async () => await registration.update();
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (registrations.length > 0) status = true;
|
|
342
|
-
resolve(status);
|
|
343
|
-
})
|
|
344
|
-
.catch((...args) => {
|
|
345
|
-
logger.error('Error getting service worker registrations:', ...args);
|
|
346
|
-
resolve(false);
|
|
347
|
-
});
|
|
348
|
-
} else {
|
|
349
|
-
logger.warn('Service Worker Disabled in browser');
|
|
350
|
-
resolve(false);
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Registers the service worker (`sw.js`) with the browser.
|
|
357
|
-
* @memberof PwaWorker
|
|
358
|
-
* @returns {Promise<Array<any>>} A promise that resolves with the registration arguments.
|
|
359
|
-
*/
|
|
360
|
-
install() {
|
|
361
|
-
return new Promise((resolve) => {
|
|
362
|
-
if ('serviceWorker' in navigator) {
|
|
363
|
-
navigator.serviceWorker
|
|
364
|
-
.register(`${getProxyPath()}sw.js`)
|
|
365
|
-
.then((...args) => {
|
|
366
|
-
logger.warn('Service Worker Registered', args);
|
|
367
|
-
resolve(args);
|
|
368
|
-
})
|
|
369
|
-
.catch((...args) => {
|
|
370
|
-
logger.error('Error registering service worker:', ...args);
|
|
371
|
-
resolve(args);
|
|
372
|
-
});
|
|
373
|
-
} else {
|
|
374
|
-
logger.warn('Service Worker Disabled in browser');
|
|
375
|
-
resolve([]);
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Unregisters all service workers and deletes all application caches.
|
|
382
|
-
* @memberof PwaWorker
|
|
383
|
-
* @returns {Promise<Array<any>>} A promise that resolves after uninstallation.
|
|
384
|
-
*/
|
|
385
|
-
uninstall() {
|
|
386
|
-
return new Promise(async (resolve) => {
|
|
387
|
-
if ('serviceWorker' in navigator) {
|
|
388
|
-
try {
|
|
389
|
-
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
390
|
-
const cacheNames = await caches.keys();
|
|
391
|
-
for (const cacheName of cacheNames) await caches.delete(cacheName);
|
|
392
|
-
for (const registration of registrations) {
|
|
393
|
-
logger.info('Removing service worker registration', registration);
|
|
394
|
-
registration.unregister();
|
|
395
|
-
}
|
|
396
|
-
resolve([]);
|
|
397
|
-
} catch (error) {
|
|
398
|
-
logger.error('Error during service worker uninstallation:', error);
|
|
399
|
-
resolve([error]);
|
|
400
|
-
}
|
|
401
|
-
} else {
|
|
402
|
-
logger.warn('Service Worker Disabled in browser');
|
|
403
|
-
resolve([]);
|
|
262
|
+
/** Unregisters all SWs and drops every cache. */
|
|
263
|
+
async unregister() {
|
|
264
|
+
if (!('serviceWorker' in navigator)) {
|
|
265
|
+
logger.warn('Service Worker disabled in browser');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
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();
|
|
404
274
|
}
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Requests permission from the user to display notifications.
|
|
410
|
-
* Sets the internal `notificationActive` state.
|
|
411
|
-
* @memberof PwaWorker
|
|
412
|
-
* @returns {Promise<boolean>} True if permission is granted, false otherwise.
|
|
413
|
-
*/
|
|
414
|
-
notificationRequestPermission() {
|
|
415
|
-
return new Promise((resolve) =>
|
|
416
|
-
Notification.requestPermission().then((result) => {
|
|
417
|
-
if (result === 'granted') {
|
|
418
|
-
this.notificationActive = true;
|
|
419
|
-
resolve(true);
|
|
420
|
-
} else {
|
|
421
|
-
this.notificationActive = false;
|
|
422
|
-
resolve(false);
|
|
423
|
-
}
|
|
424
|
-
}),
|
|
425
|
-
);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
logger.error('Error during service worker unregistration:', error);
|
|
277
|
+
}
|
|
426
278
|
}
|
|
427
279
|
|
|
428
|
-
/**
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
body: 'Buzz! Buzz!',
|
|
439
|
-
icon: '../images/touch/chrome-touch-icon.png',
|
|
440
|
-
vibrate: [200, 100, 200, 100, 200, 100, 200],
|
|
441
|
-
tag: 'vibration-sample',
|
|
442
|
-
requireInteraction: true,
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
});
|
|
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);
|
|
447
290
|
}
|
|
448
291
|
|
|
449
|
-
/**
|
|
450
|
-
* Renders the UI for PWA settings, including buttons for cleaning cache and worker management.
|
|
451
|
-
* It also attaches the click handler for the 'clean-cache' button.
|
|
452
|
-
* @memberof PwaWorker
|
|
453
|
-
* @returns {Promise<string>} The HTML string for the settings section.
|
|
454
|
-
*/
|
|
292
|
+
/** Settings panel UI: a single "clean cache" action wired to `resetAndRestart`. */
|
|
455
293
|
async RenderSetting() {
|
|
456
294
|
setTimeout(() => {
|
|
457
|
-
// Event listener for the clean cache button
|
|
458
295
|
EventsUI.onClick(`.btn-clean-cache`, async (e) => {
|
|
459
296
|
e.preventDefault();
|
|
460
|
-
|
|
461
|
-
await this.resetWorkboxAndRestart();
|
|
297
|
+
await this.resetAndRestart();
|
|
462
298
|
});
|
|
463
299
|
});
|
|
464
300
|
return html` <div class="in">
|
|
465
|
-
${await BtnIcon.instance({
|
|
466
|
-
class: 'inl section-mp btn-custom btn-install-service-controller hide',
|
|
467
|
-
label: html`<i class="fas fa-download"></i> ${Translate.instance('Install control service')}`,
|
|
468
|
-
})}
|
|
469
|
-
${await BtnIcon.instance({
|
|
470
|
-
class: 'inl section-mp btn-custom btn-uninstall-service-controller hide',
|
|
471
|
-
label: html`<i class="far fa-trash-alt"></i> ${Translate.instance('Uninstall control service')}`,
|
|
472
|
-
})}
|
|
473
301
|
${await BtnIcon.instance({
|
|
474
302
|
class: 'inl section-mp btn-custom btn-clean-cache',
|
|
475
303
|
label: html`<i class="fa-solid fa-broom"></i> ${Translate.instance('clean-cache')}`,
|
|
476
304
|
})}
|
|
477
|
-
${await BtnIcon.instance({
|
|
478
|
-
class: 'inl section-mp btn-custom btn-reload hide',
|
|
479
|
-
label: html`<i class="fas fa-sync-alt"></i> ${Translate.instance('Reload')}`,
|
|
480
|
-
})}
|
|
481
305
|
</div>`;
|
|
482
306
|
}
|
|
483
307
|
}
|
|
484
308
|
|
|
485
|
-
// Create the singleton instance
|
|
486
309
|
const PwaWorkerInstance = new PwaWorker();
|
|
487
310
|
|
|
488
|
-
// Export the new class name for modern usage
|
|
489
311
|
export { PwaWorker };
|
|
490
|
-
|
|
491
|
-
// Export the instance with the old name (`Worker`) for backward compatibility,
|
|
492
|
-
// ensuring existing code consuming the module continues to work.
|
|
312
|
+
// Backward-compat alias — older code imports the singleton as `Worker`.
|
|
493
313
|
export { PwaWorkerInstance as Worker };
|