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.
Files changed (81) hide show
  1. package/.github/workflows/npmpkg.ci.yml +1 -0
  2. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  3. package/.github/workflows/release.cd.yml +1 -0
  4. package/.vscode/settings.json +10 -5
  5. package/CHANGELOG.md +122 -1
  6. package/CLI-HELP.md +22 -7
  7. package/README.md +37 -8
  8. package/bin/build.js +26 -9
  9. package/bin/deploy.js +20 -21
  10. package/bin/file.js +31 -13
  11. package/bin/index.js +2 -1
  12. package/bin/vs.js +1 -1
  13. package/bump.config.js +26 -0
  14. package/conf.js +20 -4
  15. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  16. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  17. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  18. package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
  19. package/manifests/kind-config-dev.yaml +8 -0
  20. package/manifests/mongodb/pv-pvc.yaml +44 -8
  21. package/manifests/mongodb/statefulset.yaml +55 -68
  22. package/package.json +27 -12
  23. package/scripts/k3s-node-setup.sh +28 -9
  24. package/src/api/core/core.router.js +19 -14
  25. package/src/api/core/core.service.js +5 -5
  26. package/src/api/default/default.router.js +22 -18
  27. package/src/api/default/default.service.js +5 -5
  28. package/src/api/document/document.router.js +28 -23
  29. package/src/api/document/document.service.js +100 -23
  30. package/src/api/file/file.router.js +19 -13
  31. package/src/api/file/file.service.js +9 -7
  32. package/src/api/test/test.router.js +17 -12
  33. package/src/api/types.js +24 -0
  34. package/src/api/user/guest.service.js +5 -4
  35. package/src/api/user/user.router.js +297 -288
  36. package/src/api/user/user.service.js +100 -35
  37. package/src/cli/baremetal.js +20 -11
  38. package/src/cli/cluster.js +196 -55
  39. package/src/cli/db.js +59 -60
  40. package/src/cli/deploy.js +273 -159
  41. package/src/cli/fs.js +3 -1
  42. package/src/cli/index.js +16 -9
  43. package/src/cli/ipfs.js +4 -6
  44. package/src/cli/kubectl.js +4 -1
  45. package/src/cli/lxd.js +217 -135
  46. package/src/cli/release.js +289 -131
  47. package/src/cli/repository.js +58 -7
  48. package/src/cli/run.js +152 -25
  49. package/src/cli/test.js +9 -3
  50. package/src/client/Default.index.js +9 -3
  51. package/src/client/components/core/Auth.js +4 -0
  52. package/src/client/components/core/PanelForm.js +56 -52
  53. package/src/client/components/core/Worker.js +162 -363
  54. package/src/client/sw/core.sw.js +174 -112
  55. package/src/db/DataBaseProvider.js +120 -20
  56. package/src/db/mongo/MongoBootstrap.js +587 -0
  57. package/src/db/mongo/MongooseDB.js +126 -22
  58. package/src/index.js +1 -1
  59. package/src/runtime/express/Express.js +2 -2
  60. package/src/runtime/wp/Wp.js +8 -5
  61. package/src/server/auth.js +2 -2
  62. package/src/server/client-build-docs.js +1 -1
  63. package/src/server/client-build.js +94 -129
  64. package/src/server/conf.js +20 -65
  65. package/src/server/process.js +180 -19
  66. package/src/server/runtime.js +1 -1
  67. package/src/server/start.js +12 -4
  68. package/src/ws/IoInterface.js +16 -16
  69. package/src/ws/core/channels/core.ws.chat.js +11 -11
  70. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  71. package/src/ws/core/channels/core.ws.stream.js +19 -19
  72. package/src/ws/core/core.ws.connection.js +8 -8
  73. package/src/ws/core/core.ws.server.js +6 -5
  74. package/src/ws/default/channels/default.ws.main.js +10 -10
  75. package/src/ws/default/default.ws.connection.js +4 -4
  76. package/src/ws/default/default.ws.server.js +4 -3
  77. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  78. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  79. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  80. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  81. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -1,65 +1,44 @@
1
1
  /**
2
- * Utility class for managing Progressive Web App (PWA) worker functionalities,
3
- * including service worker registration, caching, and notification management.
4
- * This class is designed to be used as a singleton instance (exported as 'Worker').
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 { registerRoutes } from './Router.js';
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.dev) {
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 window.renderPayload.dev || location.origin.match('localhost') || location.origin.match('127.0.0.1');
51
+ return Boolean(
52
+ window.renderPayload?.dev || location.origin.match('localhost') || location.origin.match('127.0.0.1'),
53
+ );
78
54
  }
79
55
 
80
- async runComponentInit(component, options) {
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.runComponentInit(item, options);
64
+ for (const item of component) await this.#initComponent(item, options);
84
65
  return;
85
66
  }
86
- if (typeof component.instance === 'function') {
87
- await component.instance(options);
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
- * Shared core inits (Css, TranslateCore, Responsive, SocketIo, Keyboard) run
98
- * internally in the correct order so index entrypoints only list app-specific components.
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(): object} options.router - Function returning the router instance.
102
- * @param {function(): Promise<string>} [options.template] - Async function returning the landing HTML body.
103
- * @param {Array} [options.themes] - CSS theme array passed to Css.loadThemes().
104
- * @param {object|Array} [options.translate] - App translate class(es) with static instance().
105
- * @param {object} [options.render] - AppShell class with static instance().
106
- * @param {string} [options.socketPath] - Socket.IO path override.
107
- * @param {object} [options.appStore] - AppStore whose .Data is used for socket channels.
108
- * @param {object} [options.session] - Session components: { socket, login, signout, signup }.
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 = async () => {
114
- logger.warn('ononline');
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
- const { status } = event.data;
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
- // ── declarative bootstrap path ──────────────────────────────────────────
170
- // shared core inits
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.runComponentInit(TranslateCore);
173
- await this.runComponentInit(translate);
174
- await this.runComponentInit(Responsive);
175
- // app shell render
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.runComponentInit(render, htmlMainBody ? { htmlMainBody } : undefined);
120
+ await this.#initComponent(render, htmlMainBody ? { htmlMainBody } : undefined);
179
121
  }
180
- // socket init
181
- const channels = appStore ? appStore.Data : (session && session.socket && session.socket.Data) || undefined;
182
- await this.runComponentInit(SocketIo, { channels, path: socketPath });
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.runComponentInit(session.socket);
185
- await this.runComponentInit(session.login);
186
- await this.runComponentInit(session.logout || session.signout);
187
- await this.runComponentInit(session.signup);
188
- await this.runComponentInit(session.account);
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
- await this.runComponentInit(Keyboard);
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
- * Gets the current service worker registration.
210
- * @memberof PwaWorker
211
- * @returns {Promise<ServiceWorkerRegistration | undefined>} The service worker registration object, or undefined.
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
- async getRegistration() {
214
- return navigator.serviceWorker.getRegistration();
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
- * Forces the current service worker to skip waiting and reloads the page
219
- * to apply the new service worker immediately.
220
- * @memberof PwaWorker
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(timeOut = 3000) {
225
- return new Promise((resolve) => {
226
- if (navigator.serviceWorker && navigator.serviceWorker.controller) {
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 cacheNames = await caches.keys();
237
- await Promise.all(cacheNames.map((cacheName) => caches.delete(cacheName)));
238
- return cacheNames.length;
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 workboxDatabases = databases
245
- .map((database) => database.name)
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
- workboxDatabases.map(
215
+ targets.map(
256
216
  (name) =>
257
217
  new Promise((resolve) => {
258
- const request = indexedDB.deleteDatabase(name);
259
- request.onsuccess = () => resolve(true);
260
- request.onerror = () => resolve(false);
261
- request.onblocked = () => resolve(false);
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 (!(navigator.serviceWorker && navigator.serviceWorker.controller)) return false;
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
- * Updates the application by clearing specific caches and running the service worker update logic.
299
- * Cache names matching 'components/', 'services/', or '.index.js' are deleted.
300
- * @memberof PwaWorker
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 isInstall = await this.status();
305
- if (isInstall) {
306
- const cacheNames = await caches.keys();
307
- for (const cacheName of cacheNames) {
308
- if (cacheName.match('components/') || cacheName.match('services/') || cacheName.match('.index.js')) {
309
- await caches.delete(cacheName);
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
- * Schedules SW registration to run after the page 'load' event (or immediately
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 Disabled in browser');
265
+ logger.warn('Service Worker disabled in browser');
326
266
  return;
327
267
  }
328
- const register = () => {
329
- this.status().then((isInstall) => {
330
- if (!isInstall) this.install();
331
- });
332
- };
333
- if (document.readyState === 'complete') {
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
- * Shows a sample notification if permission is granted.
451
- * @memberof PwaWorker
452
- * @returns {void}
453
- */
454
- notificationShow() {
455
- Notification.requestPermission().then((result) => {
456
- if (result === 'granted') {
457
- navigator.serviceWorker.ready.then((registration) => {
458
- registration.showNotification('Vibration Sample', {
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
- // Full reset: workbox runtime state + SW registrations + client storage.
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 };