@networkpro/web 1.1.3 → 1.4.2

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/_redirects ADDED
@@ -0,0 +1 @@
1
+ https://www.netwk.pro/* https://netwk.pro/:splat 301
package/eslint.config.mjs CHANGED
@@ -75,6 +75,17 @@ export default [
75
75
  "jsdoc/check-types": "warn", // Checks if types in JSDoc are defined correctly
76
76
  "jsdoc/require-param": "warn", // Requires @param in JSDoc
77
77
  "jsdoc/require-returns": "warn", // Requires @returns in JSDoc
78
+ "jsdoc/require-jsdoc": [
79
+ "warn",
80
+ {
81
+ publicOnly: true,
82
+ require: {
83
+ FunctionDeclaration: true,
84
+ MethodDefinition: true,
85
+ ClassDeclaration: true,
86
+ },
87
+ },
88
+ ],
78
89
  },
79
90
  },
80
91
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@networkpro/web",
3
3
  "private": false,
4
4
  "sideEffects": false,
5
- "version": "1.1.3",
5
+ "version": "1.4.2",
6
6
  "description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy — Network Pro Strategies",
7
7
  "keywords": [
8
8
  "security",
@@ -30,37 +30,61 @@
30
30
  },
31
31
  "type": "module",
32
32
  "engines": {
33
- "node": ">=22"
33
+ "node": ">=22.0.0 <25",
34
+ "npm": ">=11.0.0 <12"
34
35
  },
35
- "main": "src/app.html",
36
36
  "style": "src/lib/styles/global.min.css",
37
- "scripts": {
38
- "upgrade": "npx npm-check-updates -u",
39
- "dev": "vite dev",
40
- "build": "vite build",
41
- "build:netlify": "netlify build",
42
- "preview": "vite preview",
43
- "prepare": "svelte-kit sync || echo ''",
44
- "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
45
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
46
- "clean": "rm -rf build/* node_modules package-lock.json && npm install",
47
- "format": "prettier --check .",
48
- "format:fix": "prettier --write .",
49
- "lint": "eslint --ext .mjs,.js,.svelte --config eslint.config.mjs .",
50
- "lint:fix": "eslint --ext .mjs,.js,.svelte --config eslint.config.mjs . --fix",
51
- "lint:md": "npx markdownlint-cli2 \"**/*.{md,markdown}\" \"#node_modules/**\" \"#build/**\"",
52
- "lint:css": "stylelint \"**/*.{css,svelte}\" --ignore-path .stylelintignore",
53
- "lint:all": "npm run lint && npm run lint:md && npm run lint:css && npm run format",
54
- "test": "npm run test:all",
55
- "test:all": "npm run test:client -- --run && npm run test:server -- --run",
56
- "test:client": "vitest --config vitest.config.client.js",
57
- "test:server": "vitest --config vitest.config.server.js",
58
- "test:watch": "vitest --config vitest.config.client.js --watch",
59
- "test:coverage": "npm run test:client -- --run --coverage && npm run test:server -- --run --coverage",
60
- "css:bundle": "node scripts/bundleCss.js",
61
- "head:flatten": "node scripts/flattenHeaders.js",
62
- "head:validate": "node scripts/validateHeaders.js",
63
- "checkout": "npm run test:all && npm run lint:all && npm run check"
37
+ "scripts": {
38
+ "dev": "vite dev",
39
+ "preview": "vite preview",
40
+ "start": "npm run dev",
41
+
42
+ "build": "vite build",
43
+ "build:netlify": "netlify build",
44
+ "css:bundle": "node scripts/bundleCss.js",
45
+
46
+ "prepare": "svelte-kit sync || echo ''",
47
+ "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
48
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
49
+ "check:env": "node scripts/checkEnv.js",
50
+ "check:node": "node scripts/checkNode.js",
51
+
52
+ "checkout": "npm run check:node && npm run test:all && npm run lint:all && npm run check && npm run audit:scripts",
53
+ "verify": "npm run checkout",
54
+
55
+ "delete": "rm -rf build .svelte-kit node_modules package-lock.json",
56
+ "clean": "npm run delete && npm cache clean --force && npm install",
57
+ "upgrade": "npx npm-check-updates -u",
58
+
59
+ "test": "npm run test:all",
60
+ "test:all": "npm run test:client -- --run && npm run test:server -- --run",
61
+ "test:client": "vitest --config vitest.config.client.js",
62
+ "test:server": "vitest --config vitest.config.server.js",
63
+ "test:watch": "vitest --config vitest.config.client.js --watch",
64
+ "test:coverage": "npm run test:client -- --run --coverage && npm run test:server -- --run --coverage",
65
+
66
+ "lint": "eslint . --ext .mjs,.js,.svelte",
67
+ "lint:fix": "eslint . --ext .mjs,.js,.svelte --fix",
68
+ "lint:jsdoc": "eslint . --ext .js,.mjs,.svelte --max-warnings=0",
69
+ "lint:css": "stylelint \"**/*.{css,svelte}\" --ignore-path .stylelintignore",
70
+ "lint:md": "npx markdownlint-cli2 \"**/*.{md,markdown}\" \"#node_modules/**\" \"#build/**\"",
71
+ "lint:all": "npm run lint && npm run lint:md && npm run lint:css && npm run format",
72
+ "format": "prettier --check .",
73
+ "format:fix": "prettier --write .",
74
+
75
+ "lhci": "lhci",
76
+ "lighthouse": "npm run lighthouse:local",
77
+ "lighthouse:local": "npm run build && npm run preview & wait-on http://localhost:4173 && npm run lhci:run",
78
+ "lhci:run": "lhci autorun --config=.lighthouserc.cjs",
79
+
80
+ "audit:scripts": "node scripts/auditScripts.js",
81
+ "head:flatten": "node scripts/flattenHeaders.js",
82
+ "head:validate": "node scripts/validateHeaders.js",
83
+
84
+ "postinstall": "npm run check:node"
85
+ },
86
+ "dependencies": {
87
+ "svelte": "5.31.1"
64
88
  },
65
89
  "devDependencies": {
66
90
  "@eslint/compat": "^1.2.9",
@@ -68,21 +92,20 @@
68
92
  "@netlify/plugin-sitemap": "^0.8.1",
69
93
  "@playwright/test": "^1.52.0",
70
94
  "@sveltejs/adapter-netlify": "^5.0.2",
71
- "@sveltejs/kit": "^2.21.1",
72
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
95
+ "@sveltejs/kit": "2.21.1",
96
+ "@sveltejs/vite-plugin-svelte": "5.0.3",
73
97
  "@testing-library/jest-dom": "^6.6.3",
74
- "@testing-library/svelte": "^5.2.7",
75
- "@vitest/coverage-v8": "^3.1.3",
98
+ "@testing-library/svelte": "^5.2.8",
99
+ "@vitest/coverage-v8": "^3.1.4",
76
100
  "autoprefixer": "^10.4.21",
77
101
  "browserslist": "^4.24.5",
78
102
  "eslint": "^9.27.0",
79
103
  "eslint-config-prettier": "^10.1.5",
80
104
  "eslint-plugin-jsdoc": "^50.6.17",
81
- "eslint-plugin-svelte": "^3.8.1",
105
+ "eslint-plugin-svelte": "^3.8.2",
82
106
  "globals": "^16.1.0",
83
107
  "jsdom": "^26.1.0",
84
108
  "lightningcss": "^1.30.1",
85
- "lightningcss-cli": "^1.30.1",
86
109
  "markdownlint": "^0.38.0",
87
110
  "markdownlint-cli2": "^0.18.1",
88
111
  "mdsvex": "^0.12.6",
@@ -91,18 +114,18 @@
91
114
  "postcss-html": "^1.8.0",
92
115
  "prettier": "^3.5.3",
93
116
  "prettier-plugin-svelte": "^3.4.0",
117
+ "semver": "^7.7.2",
94
118
  "stylelint": "^16.19.1",
95
119
  "stylelint-config-html": "^1.1.0",
96
120
  "stylelint-config-recommended": "^16.0.0",
97
121
  "stylelint-order": "^7.0.0",
98
- "svelte": "^5.30.2",
99
122
  "svelte-check": "^4.2.1",
100
123
  "svelte-eslint-parser": "^1.2.0",
101
124
  "svelte-preprocess": "^6.0.3",
102
125
  "typescript": "^5.8.3",
103
126
  "vite": "^6.3.5",
104
127
  "vite-plugin-lightningcss": "^0.0.5",
105
- "vitest": "^3.1.3"
128
+ "vitest": "^3.1.4"
106
129
  },
107
130
  "overrides": {
108
131
  "@sveltejs/kit": {
@@ -1,3 +1,10 @@
1
+ /* ==========================================================================
2
+ playwright.config.js
3
+
4
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
+ This file is part of Network Pro.
6
+ ========================================================================== */
7
+
1
8
  // @ts-check
2
9
  import { defineConfig, devices } from "@playwright/test";
3
10
 
@@ -5,7 +12,8 @@ import { defineConfig, devices } from "@playwright/test";
5
12
  * @see https://playwright.dev/docs/test-configuration
6
13
  */
7
14
  export default defineConfig({
8
- testDir: "./tests",
15
+ testDir: "./tests/e2e",
16
+ testMatch: "*.spec.js",
9
17
 
10
18
  /* Run tests in files in parallel */
11
19
  fullyParallel: true,
@@ -0,0 +1,94 @@
1
+ /* ==========================================================================
2
+ scripts/auditScripts.js
3
+
4
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
+ This file is part of Network Pro.
6
+ ========================================================================== */
7
+
8
+ /**
9
+ * Audit for untested script modules in scripts/
10
+ * Looks for any .js/.mjs files without a matching test in tests/
11
+ *
12
+ * @module scripts/
13
+ * @author SunDevil311
14
+ * @updated 2025-05-21
15
+ */
16
+
17
+ import fs from "fs";
18
+ import path from "path";
19
+
20
+ const scriptsDir = path.resolve("./scripts");
21
+ const testsDir = path.resolve("./tests");
22
+
23
+ // Scripts intentionally excluded from test coverage
24
+ const allowList = new Set([
25
+ "checkNode.js",
26
+ "auditScripts.js", // itself
27
+ ]);
28
+
29
+ // Recursively gather all test files
30
+ function getAllTestFiles(dir) {
31
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
32
+ const files = [];
33
+
34
+ for (const entry of entries) {
35
+ const fullPath = path.join(dir, entry.name);
36
+ if (entry.isDirectory()) {
37
+ files.push(...getAllTestFiles(fullPath));
38
+ } else if (
39
+ entry.isFile() &&
40
+ (entry.name.endsWith(".test.js") ||
41
+ entry.name.endsWith(".spec.js") ||
42
+ entry.name.endsWith(".test.mjs") ||
43
+ entry.name.endsWith(".spec.mjs"))
44
+ ) {
45
+ files.push(fullPath);
46
+ }
47
+ }
48
+
49
+ return files;
50
+ }
51
+
52
+ // Get base names of tested modules (no extension or test specifier)
53
+ const testFiles = getAllTestFiles(testsDir);
54
+ const testedModules = new Set(
55
+ testFiles.map((filePath) =>
56
+ path
57
+ .basename(filePath)
58
+ .replace(/\.test\.js$|\.spec\.js$|\.test\.mjs$|\.spec\.mjs$/, ""),
59
+ ),
60
+ );
61
+
62
+ // Gather all scripts (.js and .mjs)
63
+ const scriptFiles = fs
64
+ .readdirSync(scriptsDir)
65
+ .filter((file) => file.endsWith(".js") || file.endsWith(".mjs"));
66
+
67
+ const untested = scriptFiles.filter((file) => {
68
+ const base = file.replace(/\.(js|mjs)$/, "");
69
+ return !allowList.has(file) && !testedModules.has(base);
70
+ });
71
+
72
+ const pathRelative = (file) =>
73
+ path.relative(process.cwd(), path.join(scriptsDir, file));
74
+
75
+ // Output results
76
+ if (untested.length) {
77
+ console.warn("\n⚠ Untested script files detected:\n");
78
+
79
+ untested.forEach((file) => {
80
+ const filePath = pathRelative(file);
81
+ console.warn(
82
+ `::warning file=${filePath},line=1,col=1::Missing test file for ${file}`,
83
+ );
84
+ });
85
+
86
+ console.warn(
87
+ `\nAdd a corresponding test file in /tests (e.g., ${untested[0].replace(
88
+ /\.(js|mjs)$/,
89
+ ".test.js",
90
+ )})`,
91
+ );
92
+ } else {
93
+ console.log("✅ All script files have corresponding tests.");
94
+ }
@@ -0,0 +1,66 @@
1
+ /* ==========================================================================
2
+ scripts/checkEnv.js
3
+
4
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
+ This file is part of Network Pro.
6
+ ========================================================================== */
7
+
8
+ /**
9
+ * Utility to validate execution environment
10
+ * Ensures `ENV_MODE` is defined and matches allowed environments
11
+ *
12
+ * @module scripts/
13
+ * @author SunDevil311
14
+ * @updated 2025-05-21
15
+ */
16
+
17
+ import { basename } from "path";
18
+ import { fileURLToPath } from "url";
19
+
20
+ const validEnvs = new Set(["dev", "test", "ci", "prod", "preview"]);
21
+
22
+ /**
23
+ * Checks and returns validation for ENV_MODE
24
+ * @returns {{
25
+ * mode: string,
26
+ * valid: boolean,
27
+ * wasDefaulted: boolean,
28
+ * allowed: string[]
29
+ * }}
30
+ */
31
+ export function checkEnv() {
32
+ const current = process.env.ENV_MODE;
33
+ let mode = current;
34
+ let valid = false;
35
+ let wasDefaulted = false;
36
+
37
+ if (!mode) {
38
+ mode = "dev";
39
+ process.env.ENV_MODE = mode;
40
+ wasDefaulted = true;
41
+ console.warn("⚠️ ENV_MODE not set. Defaulting to 'dev'.");
42
+ }
43
+
44
+ valid = validEnvs.has(mode);
45
+
46
+ if (valid) {
47
+ const tag = wasDefaulted ? "[info]" : "[ok]";
48
+ console.log(`${tag} ENV_MODE is set to: "${mode}"`);
49
+ } else {
50
+ console.error(
51
+ `❌ Invalid ENV_MODE "${mode}". Must be one of: ${[...validEnvs].join(", ")}`,
52
+ );
53
+ }
54
+
55
+ return {
56
+ mode,
57
+ valid,
58
+ wasDefaulted,
59
+ allowed: [...validEnvs].sort(),
60
+ };
61
+ }
62
+
63
+ // ✅ Run only if called directly via CLI
64
+ if (basename(fileURLToPath(import.meta.url)) === basename(process.argv[1])) {
65
+ checkEnv();
66
+ }
@@ -0,0 +1,54 @@
1
+ /* ==========================================================================
2
+ scripts/checkNode.js
3
+
4
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
+ This file is part of Network Pro.
6
+ ========================================================================== */
7
+
8
+ /**
9
+ * Utility to check Node.js and NPM version
10
+ * Ensures the current environment matches the required versions from package.json
11
+ *
12
+ * @module scripts/
13
+ * @author SunDevil311
14
+ * @updated 2025-05-20
15
+ */
16
+
17
+ import { execSync } from "child_process";
18
+ import fs from "fs";
19
+ import path from "path";
20
+ import semver from "semver";
21
+
22
+ // Load engines from package.json
23
+ const pkg = JSON.parse(fs.readFileSync(path.resolve("./package.json"), "utf8"));
24
+ const { node: nodeRange, npm: npmRange } = pkg.engines;
25
+
26
+ // Check Node version
27
+ if (!semver.satisfies(process.version, nodeRange)) {
28
+ console.error(
29
+ `⚠️ Node version ${process.version} does not match required range: ${nodeRange}`,
30
+ );
31
+ process.exit(1);
32
+ }
33
+
34
+ // Check NPM version
35
+ let npmVersion;
36
+
37
+ try {
38
+ npmVersion = execSync("npm --version").toString().trim();
39
+ if (!semver.satisfies(npmVersion, npmRange)) {
40
+ console.error(
41
+ `⚠️ NPM version ${npmVersion} does not match required range: ${npmRange}`,
42
+ );
43
+ process.exit(1);
44
+ }
45
+ } catch (err) {
46
+ console.error("❌ Failed to check NPM version:", err.message);
47
+ process.exit(1);
48
+ }
49
+
50
+ if (!process.env.CI || process.env.VERBOSE === "true") {
51
+ console.log(
52
+ `✅ Node (${process.version}) matches ${nodeRange}, and NPM (${npmVersion}) matches ${npmRange}`,
53
+ );
54
+ }
@@ -0,0 +1,58 @@
1
+ /* ==========================================================================
2
+ scripts/checkVersions.js
3
+
4
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
+ This file is part of Network Pro.
6
+ ========================================================================== */
7
+
8
+ /**
9
+ * Utility to check Node.js and NPM version (non-blocking)
10
+ * Returns version info, can be unit tested, and doesn't kill process during
11
+ * test runs
12
+ *
13
+ * @module scripts/
14
+ * @author SunDevil311
15
+ * @updated 2025-05-20
16
+ */
17
+
18
+ import { execSync } from "child_process";
19
+ import fs from "fs";
20
+ import path from "path";
21
+ import semver from "semver";
22
+
23
+ /**
24
+ * @typedef {object} VersionCheckResult
25
+ * @property {string} nodeVersion
26
+ * @property {string} npmVersion
27
+ * @property {string} nodeRange
28
+ * @property {string} npmRange
29
+ * @property {boolean} nodeValid
30
+ * @property {boolean} npmValid
31
+ */
32
+
33
+ /**
34
+ * Returns an object with validation results for Node and NPM versions.
35
+ *
36
+ * @returns {VersionCheckResult}
37
+ */
38
+ export function checkVersions() {
39
+ const pkg = JSON.parse(
40
+ fs.readFileSync(path.resolve("./package.json"), "utf8"),
41
+ );
42
+ const { node: nodeRange, npm: npmRange } = pkg.engines;
43
+
44
+ const nodeVersion = process.version;
45
+ const npmVersion = execSync("npm --version").toString().trim();
46
+
47
+ const nodeValid = semver.satisfies(nodeVersion, nodeRange);
48
+ const npmValid = semver.satisfies(npmVersion, npmRange);
49
+
50
+ return {
51
+ nodeVersion,
52
+ npmVersion,
53
+ nodeRange,
54
+ npmRange,
55
+ nodeValid,
56
+ npmValid,
57
+ };
58
+ }
@@ -0,0 +1,15 @@
1
+ /* ==========================================================================
2
+ src/global.d.ts
3
+
4
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
+ This file is part of Network Pro.
6
+ ========================================================================== */
7
+
8
+ interface BeforeInstallPromptEvent extends Event {
9
+ readonly platforms: string[];
10
+ readonly userChoice: Promise<{
11
+ outcome: 'accepted' | 'dismissed';
12
+ platform: string;
13
+ }>;
14
+ prompt(): Promise<void>;
15
+ }
@@ -0,0 +1,93 @@
1
+ <!-- ==========================================================================
2
+ src/lib/components/PWAInstallButton.svelte
3
+
4
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
+ This file is part of Network Pro.
6
+ ========================================================================== -->
7
+
8
+ <script>
9
+ import { onMount } from "svelte";
10
+ import { fade } from "svelte/transition";
11
+
12
+ let show = false;
13
+
14
+ /** @type {BeforeInstallPromptEvent | null} */
15
+ let deferredPrompt = null;
16
+
17
+ /**
18
+ * @typedef {CustomEvent<BeforeInstallPromptEvent>} PWAInstallAvailableEvent
19
+ */
20
+
21
+ onMount(() => {
22
+ /**
23
+ * Listen for the custom event fired by registerServiceWorker.js
24
+ * to enable a custom install experience.
25
+ *
26
+ * TypeScript / svelte-check does not recognize custom events by default,
27
+ * so we cast the base Event to CustomEvent manually.
28
+ */
29
+ window.addEventListener(
30
+ "pwa-install-available",
31
+ (/** @type {Event} */ e) => {
32
+ const customEvent = /** @type {PWAInstallAvailableEvent} */ (e);
33
+ deferredPrompt = customEvent.detail;
34
+ show = true;
35
+ },
36
+ );
37
+ });
38
+
39
+ /**
40
+ * Trigger the native install prompt and handle user response
41
+ */
42
+ async function promptInstall() {
43
+ if (!deferredPrompt) return;
44
+
45
+ deferredPrompt.prompt();
46
+
47
+ const { outcome } = await deferredPrompt.userChoice;
48
+ console.log(`User response to PWA install prompt: ${outcome}`);
49
+
50
+ // Always hide the button after interaction
51
+ show = false;
52
+ deferredPrompt = null;
53
+ }
54
+ </script>
55
+
56
+ {#if show}
57
+ <button
58
+ id="pwa-install"
59
+ class="install-button"
60
+ on:click={promptInstall}
61
+ transition:fade={{ duration: 600 }}>
62
+ Install App
63
+ </button>
64
+ {/if}
65
+
66
+ <style>
67
+ .install-button {
68
+ display: block;
69
+ padding: 0.5rem 1rem;
70
+ border: none;
71
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
72
+ font-size: 1rem;
73
+ font-weight: bold;
74
+ color: #000;
75
+ background-color: #ffc627;
76
+ transition: background-color 0.2s ease-in-out;
77
+ border-radius: 6px;
78
+ cursor: pointer;
79
+ font-family: inherit;
80
+ margin-left: auto;
81
+ margin-right: auto;
82
+ margin-top: 1rem;
83
+ }
84
+
85
+ .install-button:hover {
86
+ background-color: #e6b300;
87
+ }
88
+
89
+ .install-button:focus {
90
+ outline: 2px solid #000;
91
+ outline-offset: 2px;
92
+ }
93
+ </style>
@@ -6,6 +6,8 @@ This file is part of Network Pro.
6
6
  ========================================================================== -->
7
7
 
8
8
  <script>
9
+ /* eslint-disable svelte/no-at-html-tags */
10
+
9
11
  import FossFeatures from "$lib/components/foss/FossFeatures.svelte";
10
12
  // Import directly from $lib by way of image utility
11
13
  import { obtainiumPng, obtainiumWbp } from "$lib";
@@ -85,14 +87,17 @@ This file is part of Network Pro.
85
87
 
86
88
  <h3>{fossItem.headline}</h3>
87
89
 
88
- {@html fossItem.headlineDescription}
90
+ <!-- Trusted input, from internal CMS -->
91
+ {@html fossItem.detailsDescription}
89
92
 
90
93
  <FossFeatures features={fossItem.features} />
91
94
 
95
+ <!-- Trusted input, from internal CMS -->
92
96
  {@html fossItem.detailsDescription}
93
97
 
94
98
  {#each fossItem.notes as note}
95
99
  <blockquote class="bquote">
100
+ <!-- Trusted input, from internal CMS -->
96
101
  {@html note}
97
102
  </blockquote>
98
103
  {/each}
@@ -5,25 +5,31 @@ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
5
  This file is part of Network Pro.
6
6
  ========================================================================== */
7
7
 
8
+ /**
9
+ * Registers the service worker and handles update lifecycle, install prompt, and
10
+ * browser/environment compatibility checks. This supports offline usage and PWA behavior.
11
+ */
8
12
  export function registerServiceWorker() {
9
13
  if ('serviceWorker' in navigator) {
10
14
  // Skip registration in Firefox during development
11
- const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
12
- const isDevelopment = window.location.hostname === 'localhost' ||
13
- window.location.hostname === '127.0.0.1';
15
+ const isFirefox = navigator.userAgent.includes('Firefox');
16
+ const isDevelopment =
17
+ window.location.hostname === 'localhost' ||
18
+ window.location.hostname === '127.0.0.1';
14
19
 
15
20
  if (isFirefox && isDevelopment) {
16
21
  console.log('Service Worker registration skipped in Firefox development mode');
17
22
  return;
18
23
  }
19
24
 
20
- // Wait until after the page fully loads for better performance
25
+ // Wait until after full page load for performance optimization
21
26
  window.addEventListener('load', () => {
22
- navigator.serviceWorker.register('service-worker.js')
27
+ navigator.serviceWorker
28
+ .register('service-worker.js')
23
29
  .then((registration) => {
24
30
  console.log('Service Worker registered with scope:', registration.scope);
25
31
 
26
- // Track installation of new service worker
32
+ // Track installation of a new service worker
27
33
  registration.addEventListener('updatefound', () => {
28
34
  const newWorker = registration.installing;
29
35
  console.log('New service worker installing...');
@@ -40,7 +46,7 @@ export function registerServiceWorker() {
40
46
  ) {
41
47
  updatePrompted = true;
42
48
 
43
- // Custom prompt: reload to update
49
+ // Custom prompt: ask user to reload for latest content
44
50
  if (confirm('New content is available. Reload to update?')) {
45
51
  window.location.reload();
46
52
  }
@@ -52,7 +58,7 @@ export function registerServiceWorker() {
52
58
  console.error('Service Worker registration failed:', error);
53
59
  });
54
60
 
55
- // Ensure page reloads when new service worker takes control
61
+ // Ensure the page reloads automatically when the new service worker takes control
56
62
  let refreshing = false;
57
63
  navigator.serviceWorker.addEventListener('controllerchange', () => {
58
64
  if (!refreshing) {
@@ -60,6 +66,14 @@ export function registerServiceWorker() {
60
66
  window.location.reload();
61
67
  }
62
68
  });
69
+
70
+ // Optional PWA install prompt logic
71
+ window.addEventListener('beforeinstallprompt', (e) => {
72
+ e.preventDefault();
73
+ window.dispatchEvent(new CustomEvent('pwa-install-available', {
74
+ detail: /** @type {BeforeInstallPromptEvent} */ (e)
75
+ }));
76
+ });
63
77
  });
64
78
  }
65
79
  }