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.
Files changed (92) 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 +223 -2
  6. package/CLI-HELP.md +36 -7
  7. package/README.md +38 -9
  8. package/bin/build.js +27 -11
  9. package/bin/deploy.js +20 -21
  10. package/bin/file.js +32 -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 +2 -2
  16. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
  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 +40 -25
  23. package/scripts/k3s-node-setup.sh +30 -11
  24. package/scripts/nat-iptables.sh +103 -18
  25. package/src/api/core/core.router.js +19 -14
  26. package/src/api/core/core.service.js +5 -5
  27. package/src/api/default/default.router.js +22 -18
  28. package/src/api/default/default.service.js +5 -5
  29. package/src/api/document/document.router.js +28 -23
  30. package/src/api/document/document.service.js +100 -23
  31. package/src/api/file/file.router.js +19 -13
  32. package/src/api/file/file.service.js +9 -7
  33. package/src/api/test/test.router.js +17 -12
  34. package/src/api/types.js +24 -0
  35. package/src/api/user/guest.service.js +5 -4
  36. package/src/api/user/user.router.js +297 -288
  37. package/src/api/user/user.service.js +100 -35
  38. package/src/cli/baremetal.js +20 -11
  39. package/src/cli/cluster.js +243 -55
  40. package/src/cli/db.js +106 -62
  41. package/src/cli/deploy.js +297 -154
  42. package/src/cli/fs.js +19 -3
  43. package/src/cli/index.js +37 -9
  44. package/src/cli/ipfs.js +4 -6
  45. package/src/cli/kubectl.js +4 -1
  46. package/src/cli/lxd.js +217 -135
  47. package/src/cli/release.js +289 -131
  48. package/src/cli/repository.js +91 -34
  49. package/src/cli/run.js +297 -56
  50. package/src/cli/test.js +9 -3
  51. package/src/client/Default.index.js +9 -3
  52. package/src/client/components/core/Auth.js +19 -5
  53. package/src/client/components/core/Docs.js +6 -34
  54. package/src/client/components/core/FileExplorer.js +6 -6
  55. package/src/client/components/core/Modal.js +65 -2
  56. package/src/client/components/core/PanelForm.js +56 -52
  57. package/src/client/components/core/Recover.js +4 -4
  58. package/src/client/components/core/Worker.js +170 -350
  59. package/src/client/services/default/default.management.js +20 -25
  60. package/src/client/services/user/guest.service.js +10 -3
  61. package/src/client/sw/core.sw.js +174 -112
  62. package/src/db/DataBaseProvider.js +120 -20
  63. package/src/db/mongo/MongoBootstrap.js +587 -0
  64. package/src/db/mongo/MongooseDB.js +126 -22
  65. package/src/index.js +1 -1
  66. package/src/runtime/express/Express.js +2 -2
  67. package/src/runtime/wp/Wp.js +8 -5
  68. package/src/server/auth.js +2 -2
  69. package/src/server/client-build-docs.js +1 -1
  70. package/src/server/client-build.js +94 -129
  71. package/src/server/conf.js +20 -65
  72. package/src/server/data-query.js +32 -20
  73. package/src/server/dns.js +22 -0
  74. package/src/server/process.js +180 -19
  75. package/src/server/runtime.js +1 -1
  76. package/src/server/start.js +26 -7
  77. package/src/server/valkey.js +9 -2
  78. package/src/ws/IoInterface.js +16 -16
  79. package/src/ws/core/channels/core.ws.chat.js +11 -11
  80. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  81. package/src/ws/core/channels/core.ws.stream.js +19 -19
  82. package/src/ws/core/core.ws.connection.js +8 -8
  83. package/src/ws/core/core.ws.server.js +6 -5
  84. package/src/ws/default/channels/default.ws.main.js +10 -10
  85. package/src/ws/default/default.ws.connection.js +4 -4
  86. package/src/ws/default/default.ws.server.js +4 -3
  87. package/typedoc.json +10 -1
  88. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  89. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  90. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  91. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  92. /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,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
- 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
- const isInstall = await this.status();
165
- if (!isInstall) await this.install();
166
106
 
167
- // ── declarative bootstrap path ──────────────────────────────────────────
168
- if (typeof render !== 'function' || render.instance) {
169
- // shared core inits
170
- if (themes) await Css.loadThemes(themes);
171
- await this.runComponentInit(TranslateCore);
172
- await this.runComponentInit(translate);
173
- await this.runComponentInit(Responsive);
174
- // app shell render
175
- if (render && typeof render.instance === 'function') {
176
- const htmlMainBody = typeof template === 'function' ? template : undefined;
177
- await this.runComponentInit(render, htmlMainBody ? { htmlMainBody } : undefined);
178
- }
179
- // socket init
180
- const channels = appStore ? appStore.Data : (session && session.socket && session.socket.Data) || undefined;
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
- * Gets the current service worker registration.
213
- * @memberof PwaWorker
214
- * @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
215
145
  */
216
- async getRegistration() {
217
- 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
+ }
218
178
  }
219
179
 
220
180
  /**
221
- * Forces the current service worker to skip waiting and reloads the page
222
- * to apply the new service worker immediately.
223
- * @memberof PwaWorker
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(timeOut = 3000) {
228
- return new Promise((resolve) => {
229
- if (navigator.serviceWorker && navigator.serviceWorker.controller) {
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 cacheNames = await caches.keys();
240
- await Promise.all(cacheNames.map((cacheName) => caches.delete(cacheName)));
241
- return cacheNames.length;
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 workboxDatabases = databases
248
- .map((database) => database.name)
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
- workboxDatabases.map(
215
+ targets.map(
259
216
  (name) =>
260
217
  new Promise((resolve) => {
261
- const request = indexedDB.deleteDatabase(name);
262
- request.onsuccess = () => resolve(true);
263
- request.onerror = () => resolve(false);
264
- 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);
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 (!(navigator.serviceWorker && navigator.serviceWorker.controller)) return false;
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
- * Updates the application by clearing specific caches and running the service worker update logic.
302
- * Cache names matching 'components/', 'services/', or '.index.js' are deleted.
303
- * @memberof PwaWorker
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 isInstall = await this.status();
308
- if (isInstall) {
309
- const cacheNames = await caches.keys();
310
- for (const cacheName of cacheNames) {
311
- if (cacheName.match('components/') || cacheName.match('services/') || cacheName.match('.index.js')) {
312
- await caches.delete(cacheName);
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
- * Checks the current status of all service worker registrations and sets the
321
- * `updateServiceWorker` function reference if an active worker is found.
322
- * @memberof PwaWorker
323
- * @returns {Promise<boolean>} True if at least one service worker is registered.
324
- */
325
- status() {
326
- let status = false;
327
- return new Promise((resolve) => {
328
- if ('serviceWorker' in navigator) {
329
- navigator.serviceWorker
330
- .getRegistrations()
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
- * Shows a sample notification if permission is granted.
430
- * @memberof PwaWorker
431
- * @returns {void}
432
- */
433
- notificationShow() {
434
- Notification.requestPermission().then((result) => {
435
- if (result === 'granted') {
436
- navigator.serviceWorker.ready.then((registration) => {
437
- registration.showNotification('Vibration Sample', {
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
- // Full reset: workbox runtime state + SW registrations + client storage.
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 };