@networkpro/web 1.4.3 → 1.5.1

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 (32) hide show
  1. package/README.md +154 -62
  2. package/package.json +10 -9
  3. package/scripts/flattenHeaders.js +2 -2
  4. package/scripts/validateHeaders.js +1 -1
  5. package/src/app.html +43 -3
  6. package/src/global.d.ts +16 -7
  7. package/src/hooks.client.ts +16 -0
  8. package/src/lib/components/PWAInstallButton.svelte +13 -22
  9. package/src/lib/components/RedirectPage.svelte +62 -0
  10. package/src/lib/meta.js +1 -9
  11. package/src/lib/pages/TermsUseContent.svelte +5 -4
  12. package/src/lib/registerServiceWorker.js +7 -0
  13. package/src/lib/styles/css/brands.css +1 -1
  14. package/src/lib/styles/css/brands.min.css +1 -1
  15. package/src/lib/styles/css/solid.min.css +1 -1
  16. package/src/lib/types/metadata.js +17 -0
  17. package/src/lib/unregisterServiceWorker.js +18 -0
  18. package/src/lib/utils/utm.js +19 -0
  19. package/src/routes/+layout.js +8 -0
  20. package/src/routes/+layout.svelte +2 -28
  21. package/src/routes/consultation/+page.svelte +34 -0
  22. package/src/routes/contact/+page.svelte +34 -0
  23. package/src/routes/privacy-rights/+page.svelte +34 -0
  24. package/src/service-worker.js +15 -11
  25. package/tests/unit/unregisterServiceWorker.test.js +40 -0
  26. package/tests/unit/utm.test.js +48 -0
  27. package/src/routes/contact/+page.server.js +0 -24
  28. package/src/routes/privacy-rights/+page.server.js +0 -24
  29. /package/{src/lib/styles → static}/webfonts/fa-brands-400.ttf +0 -0
  30. /package/{src/lib/styles → static}/webfonts/fa-brands-400.woff2 +0 -0
  31. /package/{src/lib/styles → static}/webfonts/fa-solid-900.ttf +0 -0
  32. /package/{src/lib/styles → static}/webfonts/fa-solid-900.woff2 +0 -0
package/README.md CHANGED
@@ -1,116 +1,208 @@
1
- <!-- =========================================================================
2
- README.md
1
+ # 🌐 Network Pro — Web Presence
3
2
 
4
- SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
- This file is part of Network Pro.
6
- ========================================================================== -->
3
+ > **Locking Down Networks, Unlocking Confidence™**
4
+ > _Security, Networking, Privacy Network Pro™_
7
5
 
8
- <!--
9
- Copyright © 2025 Network Pro Strategies (Network Pro)
6
+ &nbsp;
10
7
 
11
- ---
8
+ ## 🚀 Project Overview
9
+
10
+ This is the official web presence for **[Network Pro Strategies](https://netwk.pro/about)**, a privacy-forward consultancy specializing in network engineering, information security, and public advocacy focused on cybersecurity and digital privacy.
11
+
12
+ Built with [SvelteKit](https://kit.svelte.dev/) and deployed via [Netlify](https://www.netlify.com/).
13
+ Blog and documentation subsites built with [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) and deployed via [GitHub Pages](https://pages.github.com/).
14
+ All infrastructure and data flows are designed with **maximum transparency, self-hosting, and user privacy** in mind.
15
+
16
+ ### 📁 Repository Structure
12
17
 
13
- I. Creative Commons Attribution 4.0 International
18
+ ```bash
19
+ .
20
+ ├── src/
21
+ │ ├── lib/ # Reusable components, styles, utilities
22
+ │ ├── routes/ # SvelteKit routes (+page.svelte, +page.server.js)
23
+ │ ├── hooks.client.ts # Client-only lifecycle hooks (e.g., SW control)
24
+ │ └── app.html # SvelteKit entry HTML with CSP/meta/bootstrap
25
+ ├── tests/ # Vitest test suites
26
+ ├── public/ # Static assets served at root
27
+ ├── netlify.toml # Netlify configuration
28
+ ├── .github/ # CI workflows and automation
29
+ └── ...
30
+ ```
14
31
 
15
- Network Pro (the "Licensed Material") is licensed under Creative Commons Attribution 4.0 International ("CC BY 4.0"). To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/.
32
+ &nbsp;
33
+
34
+ ## 🛠 Getting Started
35
+
36
+ ### 📦 Environment Setup
16
37
 
17
- Per the terms of the License, you are free to distribute, remix, adapt, and build upon the Licensed Material for any purpose, even commercially. You must give appropriate credit, provide a link to the License, and indicate if changes were made.
38
+ ```bash
39
+ git clone https://github.com/netwk-pro/netwk-pro.github.io.git
40
+ cd netwk-pro.github.io
41
+ cp .env.template .env
42
+ npm install
43
+ ```
18
44
 
19
- The Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable.
45
+ Edit .env to configure your environment mode:
20
46
 
21
- Permissions beyond the scope of this License—or instead of those permitted by this License—may be available as further defined within this document.
47
+ ```env
48
+ ENV_MODE=dev # Options: dev, test, ci, preview, prod
49
+ ```
22
50
 
23
- SPDX Reference: https://spdx.org/licenses/CC-BY-4.0.html
24
- Canonical URL: https://creativecommons.org/licenses/by/4.0/
51
+ > ENV*MODE is used for tooling and workflows — not by SvelteKit itself.
52
+ > Use VITE*-prefixed env variables for runtime values.
53
+
54
+ &nbsp;
55
+
56
+ ### 📐 Node.js Version Management
57
+
58
+ This repo uses .nvmrc and .node-version for version consistency with tools like:
59
+
60
+ - nvm
61
+ - asdf
62
+ - Volta
63
+ - GitHub Actions
64
+
65
+ ```bash
66
+ node -v # Should match "engines" in package.json
67
+ npm -v
68
+ ```
69
+
70
+ &nbsp;
25
71
 
26
72
  ---
27
73
 
28
- II. GNU General Public License
74
+ &nbsp;
29
75
 
30
- Network Pro is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License ("GNU GPL") as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
76
+ ## 📜 Available Scripts
31
77
 
32
- This material is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
33
- FITNESS FOR A PARTICULAR PURPOSE.
78
+ The following CLI commands are available via `npm run <script>` or `pnpm run <script>`.
34
79
 
35
- See the GNU General Public License for more details.
80
+ ### 🔄 Development
36
81
 
37
- SPDX Reference: https://spdx.org/licenses/GPL-3.0-or-later.html
38
- Canonical URL: https://www.gnu.org/licenses/gpl-3.0.html
82
+ | Script | Description |
83
+ | --------------- | ---------------------------------- |
84
+ | `dev` | Start development server with Vite |
85
+ | `preview` | Preview production build locally |
86
+ | `build` | Build the project with Vite |
87
+ | `build:netlify` | Build using Netlify CLI |
88
+ | `css:bundle` | Bundle and minify CSS |
39
89
 
40
90
  ---
41
91
 
42
- Author: Scott Lopez
43
- Email: <contact@neteng.pro>
44
- Web: <https://bio.neteng.pro>
45
- -->
92
+ ### Pre-check / Sync
46
93
 
47
- <section id="top">
94
+ | Script | Description |
95
+ | ------------- | ------------------------------------------------------------ |
96
+ | `prepare` | Run SvelteKit sync |
97
+ | `check` | Run SvelteKit sync and type check with `svelte-check` |
98
+ | `check:watch` | Watch mode for type checks |
99
+ | `check:node` | Validate Node & NPM versions match package.json `engines` |
100
+ | `checkout` | Full local validation: check versions, test, lint, typecheck |
101
+ | `verify` | Alias for `checkout` |
48
102
 
49
- <sup>[SPDX-License-Identifier](https://spdx.dev/learn/handling-license-info/): `CC-BY-4.0 OR GPL-3.0-or-later`</sup>
103
+ ---
50
104
 
51
- # Network Pro Strategies
105
+ ### 🧹 Cleanup & Maintenance
52
106
 
53
- </section>
107
+ | Script | Description |
108
+ | --------- | ----------------------------------------------- |
109
+ | `delete` | Remove build artifacts and `node_modules` |
110
+ | `clean` | Fully reset environment and reinstall |
111
+ | `upgrade` | Update all dependencies via `npm-check-updates` |
54
112
 
55
- [![Netlify Status](https://api.netlify.com/api/v1/badges/93910633-3fdb-4bb3-a9bf-5d91ccfeebf9/deploy-status)](https://app.netlify.com/projects/networkpro-web/deploys) [![NPM Version](https://img.shields.io/npm/v/%40networkpro%2Fweb?registry_uri=https%3A%2F%2Fregistry.npmjs.com&style=flat&logo=npm&logoSize=auto&color=%23CB3837)](https://www.npmjs.com/package/@networkpro/web) [![Build and Publish to Registries](https://github.com/netwk-pro/netwk-pro.github.io/actions/workflows/build-and-publish.yml/badge.svg)](https://github.com/netwk-pro/netwk-pro.github.io/actions/workflows/build-and-publish.yml)
56
- [![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat)](https://github.com/prettier/prettier) [![stylelint](https://img.shields.io/badge/stylelint-%23747474?style=flat&logo=stylelint&logoSize=auto&labelColor=%23263238)](https://stylelint.io/)
57
- [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/netwk-pro/netwk-pro.github.io/blob/master/CODE_OF_CONDUCT.md)
113
+ ---
58
114
 
59
- ## Security That Respects You
115
+ ### 🧪 Testing
60
116
 
61
- At **Network Pro Strategies (Network Pro&trade;)**, we deliver network security, cybersecurity, and digital privacy consulting with clarity, credibility, and care. We believe that real security doesn’t have to come at the cost of user autonomy, and that privacy-minded solutions can be both practical and powerful.
117
+ | Script | Description |
118
+ | --------------- | -------------------------------------------- |
119
+ | `test` | Alias for `test:all` |
120
+ | `test:all` | Run both client and server test suites |
121
+ | `test:client` | Run client tests with Vitest |
122
+ | `test:server` | Run server-side tests with Vitest |
123
+ | `test:watch` | Watch mode for client tests |
124
+ | `test:coverage` | Collect coverage from both client and server |
62
125
 
63
- Our approach is built on a simple principle: **the best security is the one that fits.** That means we don’t push ideologies—we apply what works. We advocate for and implement **free and open source solutions** where they offer competitive functionality, control, and visibility. When proprietary platforms are better suited, we deploy them responsibly and securely—ensuring every recommendation is grounded in **technical merit**, **scalability**, and **client goals**.
126
+ ---
64
127
 
65
- ### **What We Do**
128
+ ### 🧼 Linting & Formatting
66
129
 
67
- As a remote-first consultancy, we support clients across industries and geographies with a focus on:
130
+ | Script | Description |
131
+ | ------------ | --------------------------------------- |
132
+ | `lint` | Run ESLint on JS, MJS, and Svelte files |
133
+ | `lint:fix` | Auto-fix ESLint issues |
134
+ | `lint:jsdoc` | Check JSDoc annotations |
135
+ | `lint:css` | Run Stylelint on CSS and Svelte styles |
136
+ | `lint:md` | Lint Markdown content |
137
+ | `lint:all` | Run all linters and formatting checks |
138
+ | `format` | Run Prettier formatting check |
139
+ | `format:fix` | Auto-format code using Prettier |
68
140
 
69
- - **Network Hardening & Perimeter Defense**
70
- - **Firewall Architecture & Policy Optimization**
71
- - **Cloud Security & Zero Trust Implementation**
72
- - **Secure Infrastructure Design & Implementation**
73
- - **Risk Reduction & Security Posture Assessment**
141
+ ---
74
142
 
75
- Our consulting engagements range from tactical one-off solutions to strategic, long-term partnerships. Whether it’s helping a business segment its internal network, lock down its cloud footprint, or plan scalable defenses—we deliver clear value, with zero fluff.
143
+ ### 💡 Lighthouse / Performance
76
144
 
77
- We also believe education is a core pillar of real-world security. That’s why we invest in raising awareness—across both technical and general audiences—on best practices in digital privacy, secure design, and threat mitigation.
145
+ | Script | Description |
146
+ | ------------------ | ----------------------------------------------- |
147
+ | `lhci` | Alias for Lighthouse CI |
148
+ | `lighthouse` | Run local Lighthouse test and launch viewer |
149
+ | `lighthouse:local` | Build site, preview, and run Lighthouse locally |
150
+ | `lhci:run` | Run Lighthouse CI autorun |
78
151
 
79
- **Network Pro&trade; exists to bring strong, thoughtful security to organizations that value integrity—without sacrificing agility or trust.** We don’t just secure infrastructure. We secure confidence.
152
+ ---
80
153
 
81
- &nbsp;
154
+ ### 📋 Audits / Validation
82
155
 
83
- 🔹 [Let’s connect](https://netwk.pro/contact) to discuss how we can help secure and strengthen your business today.
156
+ | Script | Description |
157
+ | --------------- | -------------------------------------------- |
158
+ | `audit:scripts` | Check for untested utility scripts |
159
+ | `head:flatten` | Flatten headers for Netlify |
160
+ | `head:validate` | Validate headers file against project config |
84
161
 
85
162
  ---
86
163
 
87
- You can find our PGP keys and a vCard containing our contact information for your convenience below.
164
+ ### 🔄 Lifecycle Hooks
88
165
 
89
- | <img decoding="sync" loading="eager" src="https://netwk.pro/img/qr/pgp-support.png" width="125px" height="125px" alt="PGP Key - support@neteng.pro"> | **[support@neteng.pro](https://keys.openpgp.org/search?q=support%40neteng.pro)**<br />**PGP Key (ed25519)**<br />&nbsp;<br /><a href="https://raw.githubusercontent.com/netwk-pro/netwk-pro.github.io/refs/heads/master/assets/bin/support@neteng.pro.aexpk" download type="application/pgp-keys">**aexpk**</a>&nbsp; **&#47;** &nbsp;<a href="https://raw.githubusercontent.com/netwk-pro/netwk-pro.github.io/refs/heads/master/assets/bin/support@neteng.pro.asc" download type="application/pgp-keys">**asc**</a><br />&nbsp;<br />6590b992e2e3eff12738<br />7bce2af093e9dec61ba0 |
90
- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
- | **[contact@s.neteng.pro](https://keys.openpgp.org/search?q=contact%40s.neteng.pro)**<br />**PGP Key (ed25519)**<br />&nbsp;<br /><a href="https://raw.githubusercontent.com/netwk-pro/netwk-pro.github.io/refs/heads/master/assets/bin/contact@s.neteng.pro.aexpk" download type="application/pgp-keys">**aexpk**</a>&nbsp; **&#47;** &nbsp;<a href="https://raw.githubusercontent.com/netwk-pro/netwk-pro.github.io/refs/heads/master/assets/bin/contact@s.neteng.pro.asc" download type="application/pgp-keys">**asc**</a><br />&nbsp;<br />**df118baa6c2d9dcdebdc**<br />**2ddcf99373499495f957** | <img decoding="async" loading="lazy" src="https://netwk.pro/img/qr/pgp-contact.png" width="125px" height="125px" alt="PGP Key - contact@s.neteng.pro"> |
92
- | <img decoding="async" loading="lazy" src="https://netwk.pro/img/qr/vcard.png" width="125px" height="125px" alt="vCard"> | **vCard**<br />&nbsp;<br /><a href="https://raw.githubusercontent.com/netwk-pro/netwk-pro.github.io/refs/heads/master/assets/bin/contact.vcf" download type="text/vcard">**vcf**</a> |
166
+ | Script | Description |
167
+ | ------------- | ----------------------------------- |
168
+ | `postinstall` | Ensures version check after install |
93
169
 
94
- <sub>[Back to top](#top)</sub>
170
+ &nbsp;
95
171
 
96
172
  ---
97
173
 
98
- <div style="font-size: 12px; font-weight: bold; text-align: center;">
174
+ &nbsp;
99
175
 
100
- [Home](https://netwk.pro) &nbsp; | &nbsp; [Terms of Use](https://netwk.pro/terms-of-use)
101
- [Privacy Policy](https://netwk.pro/privacy-policy) &nbsp; | &nbsp; [Legal](https://netwk.pro/license)
176
+ ## 🧾 License
102
177
 
103
- </div>
178
+ This project is licensed under:
179
+
180
+ - [Creative Commons BY 4.0](https://netwk.pro/license#cc-by)
181
+
182
+ - Or optionally, [GNU GPL v3 or later](https://netwk.pro/license#gnu-gpl)
183
+
184
+ Source code, branding, and visual assets are subject to reuse and distribution terms specified on our [Legal, Copyright, and Licensing page](https://netwk.pro/license).
185
+
186
+ &nbsp;
187
+
188
+ ## 🙋‍♂️ Questions?
189
+
190
+ Reach out via [netwk.pro/contact](https://netwk.pro/contact), open an issue on this repo, or email us directly at `contact (at) s.neteng.pro`.
104
191
 
105
192
  &nbsp;
106
193
 
107
- <span style="font-size: 12px; text-align: center;">
194
+ _Designed for professionals. Hardened for privacy. Built with intent._
195
+ — **Network Pro Strategies**
196
+
197
+ ---
198
+
199
+ <div style="font-size: 12px; text-align: center;">
108
200
 
109
201
  Copyright &copy; 2025
110
- **[Network Pro Strategies](https://netwk.pro/)** (Network Pro&trade;)
202
+ **[Network Pro Strategies](https://netwk.pro) (Network Pro&trade;)**
111
203
 
112
204
  Network Pro&trade;, the shield logo, and the "Locking Down Networks&trade;" slogan are [trademarks](https://netwk.pro/license#trademark) of Network Pro Strategies.
113
205
 
114
- Licensed under **[CC BY 4.0](https://netwk.pro/license#cc-by)** and the **[GNU GPL](https://netwk.pro/license#gnu-gpl)**, as published by the [Free Software Foundation](https://fsf.org), either version 3 of the License, or (at your option) any later version.
206
+ Licensed under **[CC BY 4.0](https://netwk.pro/license#cc-by)** and the **[GNU GPL](https://netwk.pro/license#gnu-gpl)**, as published by the [Free Software Foundation](https://www.fsf.org), either version 3 of the License, or (at your option) any later version.
115
207
 
116
- </span>
208
+ </div>
package/package.json CHANGED
@@ -2,17 +2,18 @@
2
2
  "name": "@networkpro/web",
3
3
  "private": false,
4
4
  "sideEffects": false,
5
- "version": "1.4.3",
5
+ "version": "1.5.1",
6
6
  "description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy — Network Pro Strategies",
7
7
  "keywords": [
8
- "security",
9
- "networking",
10
- "privacy",
11
- "cybersecurity",
12
8
  "advisory",
13
9
  "consulting",
14
- "android",
15
- "linux"
10
+ "cybersecurity",
11
+ "networking",
12
+ "privacy",
13
+ "pwa",
14
+ "security",
15
+ "svelte",
16
+ "sveltekit"
16
17
  ],
17
18
  "homepage": "https://netwk.pro",
18
19
  "bugs": {
@@ -84,7 +85,7 @@
84
85
  "postinstall": "npm run check:node"
85
86
  },
86
87
  "dependencies": {
87
- "svelte": "5.31.1"
88
+ "svelte": "5.32.1"
88
89
  },
89
90
  "devDependencies": {
90
91
  "@eslint/compat": "^1.2.9",
@@ -102,7 +103,7 @@
102
103
  "eslint": "^9.27.0",
103
104
  "eslint-config-prettier": "^10.1.5",
104
105
  "eslint-plugin-jsdoc": "^50.6.17",
105
- "eslint-plugin-svelte": "^3.8.2",
106
+ "eslint-plugin-svelte": "^3.9.0",
106
107
  "globals": "^16.1.0",
107
108
  "jsdom": "^26.1.0",
108
109
  "lightningcss": "^1.30.1",
@@ -16,8 +16,8 @@ This file is part of Network Pro.
16
16
 
17
17
  import fs from "fs";
18
18
 
19
- const HEADERS_PATH = "./static/_headers"; // update if needed
20
- const OUTPUT_PATH = "./static/_headers.flattened";
19
+ const HEADERS_PATH = "./.headers_new"; // update if needed
20
+ const OUTPUT_PATH = "./_headers.flattened";
21
21
 
22
22
  const lines = fs.readFileSync(HEADERS_PATH, "utf-8").split("\n");
23
23
  const output = [];
@@ -17,7 +17,7 @@ This file is part of Network Pro.
17
17
  // validate-headers.js
18
18
  import fs from "fs";
19
19
 
20
- const file = "./static/_headers";
20
+ const file = "./_headers";
21
21
  const lines = fs.readFileSync(file, "utf-8").split("\n");
22
22
 
23
23
  let currentPath = null;
package/src/app.html CHANGED
@@ -16,7 +16,7 @@
16
16
  content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
17
17
  <meta name="author" content="Network Pro Strategies" />
18
18
 
19
- <!-- Optional CSP for Dev -->
19
+ <!-- CSP for Dev -->
20
20
  <meta
21
21
  http-equiv="Content-Security-Policy"
22
22
  content="default-src 'self'; script-src 'self' 'unsafe-inline' https://snap.licdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://px.ads.linkedin.com; connect-src 'self' https://px.ads.linkedin.com;" />
@@ -32,12 +32,52 @@
32
32
  <link rel="preconnect" href="https://snap.licdn.com" crossorigin />
33
33
  <link rel="preconnect" href="https://px.ads.linkedin.com" crossorigin />
34
34
 
35
- <!-- Manifest + theme color -->
36
- <link rel="manifest" href="manifest.json" />
35
+ <!-- Preload FontAwesome webfonts -->
36
+ <link
37
+ rel="preload"
38
+ href="/webfonts/fa-solid-900.woff2"
39
+ as="font"
40
+ type="font/woff2"
41
+ crossorigin="anonymous" />
42
+ <link
43
+ rel="preload"
44
+ href="/webfonts/fa-brands-400.woff2"
45
+ as="font"
46
+ type="font/woff2"
47
+ crossorigin="anonymous" />
48
+
49
+ <!-- PWA -->
37
50
  <meta name="theme-color" content="#ffc627" />
51
+ <link rel="manifest" href="/manifest.json" />
52
+ <meta name="mobile-web-app-capable" content="yes" />
53
+ <meta name="apple-mobile-web-app-capable" content="yes" />
54
+ <meta
55
+ name="apple-mobile-web-app-status-bar-style"
56
+ content="black-translucent" />
57
+
58
+ <meta
59
+ name="facebook-domain-verification"
60
+ content="bx4ham0zkpvzztzu213bhpt76m9siq" />
38
61
 
39
62
  <meta name="generator" content="SvelteKit 2.21.1" />
40
63
 
64
+ <script>
65
+ if (location.search.includes("nosw")) {
66
+ window.__DISABLE_SW__ = true;
67
+ console.warn("🧪 Service worker disabled via ?nosw flag in URL.");
68
+
69
+ if ("serviceWorker" in navigator) {
70
+ navigator.serviceWorker.getRegistrations().then((registrations) => {
71
+ for (const registration of registrations) {
72
+ registration.unregister().then((success) => {
73
+ console.log("🧹 SW unregistered:", success);
74
+ });
75
+ }
76
+ });
77
+ }
78
+ }
79
+ </script>
80
+
41
81
  %sveltekit.head%
42
82
  </head>
43
83
  <body>
package/src/global.d.ts CHANGED
@@ -5,11 +5,20 @@ 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
- interface BeforeInstallPromptEvent extends Event {
9
- readonly platforms: string[];
10
- readonly userChoice: Promise<{
11
- outcome: 'accepted' | 'dismissed';
12
- platform: string;
13
- }>;
14
- prompt(): Promise<void>;
8
+ declare global {
9
+ interface BeforeInstallPromptEvent extends Event {
10
+ readonly platforms: string[];
11
+ readonly userChoice: Promise<{
12
+ outcome: 'accepted' | 'dismissed';
13
+ platform: string;
14
+ }>;
15
+ prompt(): Promise<void>;
16
+ }
17
+
18
+ interface Window {
19
+ __DISABLE_SW__?: boolean;
20
+ }
15
21
  }
22
+
23
+ export { };
24
+
@@ -0,0 +1,16 @@
1
+ /* ==========================================================================
2
+ src/hooks.client.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
+ window.addEventListener("beforeinstallprompt", (e) => {
9
+ // Prevent default install prompt
10
+ e.preventDefault();
11
+
12
+ // Re-dispatch as a custom event so your component can respond
13
+ window.dispatchEvent(new CustomEvent("pwa-install-available", { detail: e }));
14
+ });
15
+
16
+ export {};
@@ -14,40 +14,31 @@ This file is part of Network Pro.
14
14
  /** @type {BeforeInstallPromptEvent | null} */
15
15
  let deferredPrompt = null;
16
16
 
17
- /**
18
- * @typedef {CustomEvent<BeforeInstallPromptEvent>} PWAInstallAvailableEvent
19
- */
20
-
21
17
  onMount(() => {
22
18
  /**
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.
19
+ * @param {Event} e
28
20
  */
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
- );
21
+ function handleInstallPrompt(e) {
22
+ /** @type {CustomEvent<BeforeInstallPromptEvent>} */
23
+ const customEvent = /** @type {CustomEvent} */ (e);
24
+ deferredPrompt = customEvent.detail;
25
+ show = true;
26
+ }
27
+
28
+ window.addEventListener("pwa-install-available", handleInstallPrompt);
29
+
30
+ return () => {
31
+ window.removeEventListener("pwa-install-available", handleInstallPrompt);
32
+ };
37
33
  });
38
34
 
39
- /**
40
- * Trigger the native install prompt and handle user response
41
- */
42
35
  async function promptInstall() {
43
36
  if (!deferredPrompt) return;
44
37
 
45
38
  deferredPrompt.prompt();
46
-
47
39
  const { outcome } = await deferredPrompt.userChoice;
48
40
  console.log(`User response to PWA install prompt: ${outcome}`);
49
41
 
50
- // Always hide the button after interaction
51
42
  show = false;
52
43
  deferredPrompt = null;
53
44
  }
@@ -0,0 +1,62 @@
1
+ <!-- ==========================================================================
2
+ src/lib/components/RedirectPage.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
+
11
+ export let to;
12
+ export let delay = 3;
13
+
14
+ onMount(() => {
15
+ if (!to) {
16
+ console.warn("⛔ No redirect target provided");
17
+ return;
18
+ }
19
+
20
+ console.log("✅ Starting redirect to:", to);
21
+
22
+ setTimeout(() => {
23
+ window.location.href = to;
24
+ }, delay * 1000);
25
+ });
26
+ </script>
27
+
28
+ <svelte:head>
29
+ <title>Redirecting…</title>
30
+ <meta name="robots" content="noindex, nofollow" />
31
+ </svelte:head>
32
+
33
+ <div class="container">
34
+ <h1>Redirecting…</h1>
35
+ <p>You'll be taken to the destination in just a moment.</p>
36
+ <div class="loading-spinner" aria-hidden="true"></div>
37
+ <p>If nothing happens, <a href={to}>click here</a>.</p>
38
+ </div>
39
+
40
+ <style>
41
+ .loading-spinner {
42
+ width: 48px;
43
+ height: 48px;
44
+ margin: 2rem auto;
45
+ border: 4px solid #ddd;
46
+ animation: spin 1s linear infinite;
47
+ border-radius: 50%;
48
+ border-top: 4px solid #ffc627;
49
+ }
50
+
51
+ @keyframes spin {
52
+ to {
53
+ transform: rotate(360deg);
54
+ }
55
+ }
56
+
57
+ .container {
58
+ text-align: center;
59
+ font-family: system-ui, sans-serif;
60
+ margin-top: 5rem;
61
+ }
62
+ </style>
package/src/lib/meta.js CHANGED
@@ -50,15 +50,7 @@ const meta = {
50
50
  description:
51
51
  "FOSS Spotlight | Security, Networking, Privacy — Network Pro™",
52
52
  },
53
- "/contact": {
54
- title: "Contact Form — Network Pro™",
55
- description: "Contact Form | Security, Networking, Privacy — Network Pro™",
56
- },
57
- "/privacy-rights": {
58
- title: "Privacy Rights Request Form — Network Pro™",
59
- description:
60
- "Privacy Rights Request Form | Security, Networking, Privacy — Network Pro™",
61
- },
53
+ // Excludes redirect-only routes like /contact, /consultation, /privacy-rights
62
54
  };
63
55
 
64
56
  /** @type {MetaData} */
@@ -58,7 +58,7 @@ This file is part of Network Pro.
58
58
  */
59
59
  const constants = {
60
60
  company: "Network Pro Strategies",
61
- effectiveDate: "May 8, 2025",
61
+ effectiveDate: "May 21, 2025",
62
62
  classSmall: "small-text",
63
63
  rel: "noopener noreferrer",
64
64
  hrefTop: "#top",
@@ -141,9 +141,10 @@ This file is part of Network Pro.
141
141
  These Terms of Use apply to all platforms associated with the Company,
142
142
  including but not limited to:
143
143
  <strong>
144
- GitHub, our main website (hosted via GitHub Pages), communications on
145
- Discord and/or Slack, Stack Overflow Teams, Nextcloud, and our social
146
- media presence (e.g., Facebook, Instagram, X, and similar platforms).
144
+ GitHub, our main website (hosted via Netlify and GitHub Pages), Stack
145
+ Overflow Teams, Nextcloud, communications on Discord and/or Slack, and
146
+ our social media presence (e.g., Facebook, Instagram, X, and similar
147
+ platforms).
147
148
  </strong>
148
149
  </p>
149
150
  {:else if link.id === "acceptable-use"}
@@ -10,6 +10,13 @@ This file is part of Network Pro.
10
10
  * browser/environment compatibility checks. This supports offline usage and PWA behavior.
11
11
  */
12
12
  export function registerServiceWorker() {
13
+ const disableSW = window?.__DISABLE_SW__ || false;
14
+
15
+ if (disableSW) {
16
+ console.warn("⚠️ Service Worker registration disabled via diagnostic mode.");
17
+ return;
18
+ }
19
+
13
20
  if ('serviceWorker' in navigator) {
14
21
  // Skip registration in Firefox during development
15
22
  const isFirefox = navigator.userAgent.includes('Firefox');
@@ -12,7 +12,7 @@
12
12
  font-style: normal;
13
13
  font-weight: 400;
14
14
  font-display: block;
15
- src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
15
+ src: url("/webfonts/fa-brands-400.woff2") format("woff2"), url("/webfonts/fa-brands-400.ttf") format("truetype"); }
16
16
 
17
17
  .fab,
18
18
  .fa-brands {
@@ -3,4 +3,4 @@
3
3
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4
4
  * Copyright 2024 Fonticons, Inc.
5
5
  */
6
- :root,:host{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2)format("woff2"),url(../webfonts/fa-brands-400.ttf)format("truetype")}.fab,.fa-brands{font-weight:400}.fa-square-instagram,.fa-instagram-square{--fa:""}.fa-threads{--fa:""}.fa-linkedin-in{--fa:""}.fa-square-twitter,.fa-twitter-square{--fa:""}.fa-500px{--fa:""}.fa-mastodon{--fa:""}.fa-square-github,.fa-github-square{--fa:""}.fa-x-twitter{--fa:""}.fa-square-facebook,.fa-facebook-square{--fa:""}.fa-linkedin{--fa:""}.fa-instagram{--fa:""}.fa-facebook{--fa:""}.fa-github{--fa:""}.fa-twitter{--fa:""}.fa-square-x-twitter{--fa:""}
6
+ :root,:host{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(/webfonts/fa-brands-400.woff2)format("woff2"),url(/webfonts/fa-brands-400.ttf)format("truetype")}.fab,.fa-brands{font-weight:400}.fa-square-instagram,.fa-instagram-square{--fa:""}.fa-threads{--fa:""}.fa-linkedin-in{--fa:""}.fa-square-twitter,.fa-twitter-square{--fa:""}.fa-500px{--fa:""}.fa-mastodon{--fa:""}.fa-square-github,.fa-github-square{--fa:""}.fa-x-twitter{--fa:""}.fa-square-facebook,.fa-facebook-square{--fa:""}.fa-linkedin{--fa:""}.fa-instagram{--fa:""}.fa-facebook{--fa:""}.fa-github{--fa:""}.fa-twitter{--fa:""}.fa-square-x-twitter{--fa:""}
@@ -3,4 +3,4 @@
3
3
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4
4
  * Copyright 2024 Fonticons, Inc.
5
5
  */
6
- :host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}
6
+ :host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(/webfonts/fa-solid-900.woff2) format("woff2"),url(/webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}
@@ -0,0 +1,17 @@
1
+ /* ==========================================================================
2
+ src/lib/types/metadata.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
+ * @typedef {object} MetaData
10
+ * @property {string} title - Page title
11
+ * @property {string} description - Meta description for SEO/social
12
+ * @property {string} [image] - Optional OG image URL
13
+ * @property {string} [url] - Canonical URL
14
+ */
15
+
16
+ // Export types only for JSDoc
17
+ export /** @typedef {import('./metadata.js').MetaData} MetaData */ {};
@@ -0,0 +1,18 @@
1
+ /* ==========================================================================
2
+ src/lib/unregisterServiceWorker.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
+ * Allows for manual toggling of the service worker
10
+ */
11
+ export function unregisterServiceWorker() {
12
+ if ('serviceWorker' in navigator) {
13
+ navigator.serviceWorker.getRegistrations().then((registrations) => {
14
+ registrations.forEach((reg) => reg.unregister());
15
+ console.log("🧹 All service workers unregistered.");
16
+ });
17
+ }
18
+ }
@@ -0,0 +1,19 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/utm.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
+ * Append UTM parameter from window.location to a given URL.
10
+ * Returns `null` if not in a browser context.
11
+ * @param {string} url - The base URL to append to
12
+ * @returns {string | null}
13
+ */
14
+ export function appendUTM(url) {
15
+ if (typeof window === "undefined") return null;
16
+
17
+ const utm = new URLSearchParams(window.location.search).get("utm_source");
18
+ return utm ? `${url}?utm_source=${encodeURIComponent(utm)}` : url;
19
+ }
@@ -21,6 +21,9 @@ const fallbackMeta = {
21
21
  "Locking Down Networks, Unlocking Confidence™ | Security, Networking, Privacy — Network Pro™",
22
22
  };
23
23
 
24
+ import { browser } from "$app/environment";
25
+ import { registerServiceWorker } from "$lib/registerServiceWorker.js";
26
+
24
27
  export const prerender = "auto";
25
28
  export const trailingSlash = "never";
26
29
 
@@ -31,6 +34,11 @@ export const trailingSlash = "never";
31
34
  export function load({ url }) {
32
35
  const normalizedPathname = url.pathname.replace(/\/+$/, "") || "/";
33
36
 
37
+ if (browser) {
38
+ // Move service worker registration here
39
+ registerServiceWorker();
40
+ }
41
+
34
42
  return {
35
43
  pathname: normalizedPathname,
36
44
  meta: fallbackMeta, // Required to ensure meta always exists for typing
@@ -14,7 +14,6 @@ This file is part of Network Pro.
14
14
  import HeaderHome from "$lib/components/layout/HeaderHome.svelte";
15
15
  import PWAInstallButton from "$lib/components/PWAInstallButton.svelte";
16
16
  import { browser } from "$app/environment";
17
- import { registerServiceWorker } from "$lib/registerServiceWorker.js";
18
17
  import "$lib/styles";
19
18
 
20
19
  // Import favicon images
@@ -23,18 +22,6 @@ This file is part of Network Pro.
23
22
  import faviconSvg from "$lib/img/favicon.svg";
24
23
  import appleTouchIcon from "$lib/img/icon-180x180.png";
25
24
 
26
- /**
27
- * @type {string}
28
- * Style class for the mobile-web-app-capable meta tag.
29
- * OpenGraph URL for the website.
30
- * Company name for the website.
31
- * Twitter account for the website.
32
- */
33
- const webApp = "mobile-web-app-capable";
34
- const ogUrl = "https://netwk.pro";
35
- const companyName = "Network Pro Strategies";
36
- const twitterAct = "@NetEng_Pro";
37
-
38
25
  if (browser) {
39
26
  // Preload logo images
40
27
  [logoPng, logoWbp].forEach((src) => {
@@ -46,9 +33,6 @@ This file is part of Network Pro.
46
33
  const touchImg = new Image();
47
34
  // Preload Apple Touch icon
48
35
  touchImg.src = appleTouchIcon;
49
-
50
- // Register the service worker
51
- registerServiceWorker();
52
36
  }
53
37
 
54
38
  // fallback values if data.meta not set
@@ -62,7 +46,7 @@ This file is part of Network Pro.
62
46
  </script>
63
47
 
64
48
  <svelte:head>
65
- <!-- Static only, dynamic content moved to $lib/components/MetaTags.svelte -->
49
+ <!-- Dynamic preloads only, meta moved to $lib/components/MetaTags.svelte -->
66
50
  <link rel="preload" href={logoWbp} as="image" type="image/webp" />
67
51
  <link rel="preload" href={logoPng} as="image" type="image/png" />
68
52
  <link rel="preload" href={faviconSvg} as="image" type="image/svg+xml" />
@@ -71,18 +55,8 @@ This file is part of Network Pro.
71
55
  <link rel="icon" href={faviconSvg} type="image/svg+xml" />
72
56
  <link rel="apple-touch-icon" href={appleTouchIcon} />
73
57
 
74
- <!-- PWA -->
75
- <link rel="manifest" href="/manifest.json" />
76
- <meta name={webApp} content="yes" />
77
- <meta name={"apple-" + webApp} content="yes" />
78
- <meta
79
- name="apple-mobile-web-app-status-bar-style"
80
- content="black-translucent" />
58
+ <!-- Static moved to app.html 2025-05-21 -->
81
59
  <meta name="theme-color" content="#ffc627" />
82
-
83
- <meta
84
- name="facebook-domain-verification"
85
- content="bx4ham0zkpvzztzu213bhpt76m9siq" />
86
60
  </svelte:head>
87
61
 
88
62
  <!-- BEGIN HEADER -->
@@ -0,0 +1,34 @@
1
+ <!-- ==========================================================================
2
+ src/routes/consultation/+page.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 RedirectPage from "$lib/components/RedirectPage.svelte";
10
+ import { appendUTM } from "$lib/utils/utm.js";
11
+ import { onMount } from "svelte";
12
+ import { browser } from "$app/environment";
13
+
14
+ /** @type {string | null} */
15
+ let target = null;
16
+
17
+ /** @type {boolean} */
18
+ let show = false;
19
+
20
+ onMount(() => {
21
+ if (!browser) return;
22
+
23
+ target = appendUTM(
24
+ "https://cloud.neteng.pro/index.php/apps/appointments/pub/8clCqQrt3AtGbNrr/form",
25
+ );
26
+ show = true;
27
+ });
28
+ </script>
29
+
30
+ {#if show && target}
31
+ <RedirectPage to={target} />
32
+ {:else}
33
+ <p style="text-align: center; margin-top: 4rem;">Preparing to redirect…</p>
34
+ {/if}
@@ -0,0 +1,34 @@
1
+ <!-- ==========================================================================
2
+ src/routes/contact/+page.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 RedirectPage from "$lib/components/RedirectPage.svelte";
10
+ import { appendUTM } from "$lib/utils/utm.js";
11
+ import { onMount } from "svelte";
12
+ import { browser } from "$app/environment";
13
+
14
+ /** @type {string | null} */
15
+ let target = null;
16
+
17
+ /** @type {boolean} */
18
+ let show = false;
19
+
20
+ onMount(() => {
21
+ if (!browser) return;
22
+
23
+ target = appendUTM(
24
+ "https://cloud.neteng.pro/index.php/apps/forms/s/nyWEq9fdE7kWAjqMtMySLqJc",
25
+ );
26
+ show = true;
27
+ });
28
+ </script>
29
+
30
+ {#if show && target}
31
+ <RedirectPage to={target} />
32
+ {:else}
33
+ <p style="text-align: center; margin-top: 4rem;">Preparing to redirect…</p>
34
+ {/if}
@@ -0,0 +1,34 @@
1
+ <!-- ==========================================================================
2
+ src/routes/privacy-rights/+page.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 RedirectPage from "$lib/components/RedirectPage.svelte";
10
+ import { appendUTM } from "$lib/utils/utm.js";
11
+ import { onMount } from "svelte";
12
+ import { browser } from "$app/environment";
13
+
14
+ /** @type {string | null} */
15
+ let target = null;
16
+
17
+ /** @type {boolean} */
18
+ let show = false;
19
+
20
+ onMount(() => {
21
+ if (!browser) return;
22
+
23
+ target = appendUTM(
24
+ "https://cloud.neteng.pro/index.php/apps/forms/s/6HpZKZCaLwb6TXYL99nLQM8t",
25
+ );
26
+ show = true;
27
+ });
28
+ </script>
29
+
30
+ {#if show && target}
31
+ <RedirectPage to={target} />
32
+ {:else}
33
+ <p style="text-align: center; margin-top: 4rem;">Preparing to redirect…</p>
34
+ {/if}
@@ -26,22 +26,26 @@ const ASSETS = Array.from(
26
26
  const url = new URL(path, location.origin);
27
27
  const hostname = url.hostname;
28
28
 
29
+ const IGNORE_PATHS = new Set([
30
+ "/img/banner-1280x640.png",
31
+ "/img/banner-og-1200x630.png",
32
+ "/img/logo-transparent.png",
33
+ "/img/logo.png",
34
+ "/img/svelte.png",
35
+ "/webfonts/fa-brands-400.ttf",
36
+ "/webfonts/fa-solid-900.ttf",
37
+ "/robots.txt",
38
+ "/screenshots/desktop-foss.png",
39
+ "/sitemap.xml",
40
+ "/CNAME",
41
+ ]);
42
+
29
43
  const shouldExclude =
30
44
  path.startsWith("http") ||
31
45
  disallowedHosts.some(
32
46
  (host) => hostname === host || hostname.endsWith(`.${host}`),
33
47
  ) ||
34
- [
35
- "/img/banner-1280x640.png",
36
- "/img/banner-og-1200x630.png",
37
- "/img/logo-transparent.png",
38
- "/img/logo.png",
39
- "/img/svelte.png",
40
- "/robots.txt",
41
- "/screenshots/desktop-foss.png",
42
- "/sitemap.xml",
43
- "/CNAME",
44
- ].includes(path);
48
+ IGNORE_PATHS.has(path);
45
49
 
46
50
  if (shouldExclude) excludedAssets.push(path);
47
51
  return !shouldExclude;
@@ -0,0 +1,40 @@
1
+ /* ==========================================================================
2
+ tests/unit/unregisterServiceWorker.test.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
+ import { beforeEach, describe, expect, it, vi } from "vitest";
9
+ import { unregisterServiceWorker } from "../../src/lib/unregisterServiceWorker.js";
10
+
11
+ describe("unregisterServiceWorker()", () => {
12
+ beforeEach(() => {
13
+ // Clean up any mocks from previous runs
14
+ vi.restoreAllMocks();
15
+ });
16
+
17
+ it("should call unregister on all registered service workers", async () => {
18
+ const mockUnregister1 = vi.fn();
19
+ const mockUnregister2 = vi.fn();
20
+
21
+ // Minimal mock objects
22
+ const mockRegistration1 = { unregister: mockUnregister1 };
23
+ const mockRegistration2 = { unregister: mockUnregister2 };
24
+
25
+ // Stub getRegistrations to return mock service workers
26
+ Object.defineProperty(navigator, "serviceWorker", {
27
+ configurable: true,
28
+ value: {
29
+ getRegistrations: vi
30
+ .fn()
31
+ .mockResolvedValue([mockRegistration1, mockRegistration2]),
32
+ },
33
+ });
34
+
35
+ await unregisterServiceWorker();
36
+
37
+ expect(mockUnregister1).toHaveBeenCalled();
38
+ expect(mockUnregister2).toHaveBeenCalled();
39
+ });
40
+ });
@@ -0,0 +1,48 @@
1
+ /* ==========================================================================
2
+ tests/unit/utm.test.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
+ import { appendUTM } from "$lib/utils/utm.js";
9
+ import { afterEach, describe, expect, it } from "vitest";
10
+
11
+ describe("appendUTM", () => {
12
+ const originalWindow = globalThis.window;
13
+
14
+ afterEach(() => {
15
+ globalThis.window = originalWindow;
16
+ });
17
+
18
+ it("should return null when not in a browser environment", () => {
19
+ // @ts-expect-error – simulating SSR
20
+ delete globalThis.window;
21
+
22
+ const url = "https://example.com";
23
+ const result = appendUTM(url);
24
+ expect(result).toBe(null);
25
+ });
26
+
27
+ it("should return URL with utm_source appended", () => {
28
+ globalThis.window = {
29
+ // @ts-expect-error – mock minimal window for test
30
+ location: { search: "?utm_source=linkedin" },
31
+ };
32
+
33
+ const url = "https://example.com";
34
+ const result = appendUTM(url);
35
+ expect(result).toBe("https://example.com?utm_source=linkedin");
36
+ });
37
+
38
+ it("should return original URL if no utm_source is present", () => {
39
+ globalThis.window = {
40
+ // @ts-expect-error – mock minimal window for test
41
+ location: { search: "" },
42
+ };
43
+
44
+ const url = "https://example.com";
45
+ const result = appendUTM(url);
46
+ expect(result).toBe("https://example.com");
47
+ });
48
+ });
@@ -1,24 +0,0 @@
1
- /* ==========================================================================
2
- src/routes/contact/+page.server.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
- import { redirect } from "@sveltejs/kit";
9
-
10
- export const prerender = false;
11
-
12
- /** @type {import('./$types').PageServerLoad} */
13
- export function load({ url }) {
14
- const utmSource = url.searchParams.get("utm_source");
15
-
16
- // Set target to your actual contact page path
17
- const target =
18
- "https://cloud.neteng.pro/index.php/apps/forms/s/nyWEq9fdE7kWAjqMtMySLqJc";
19
-
20
- // Preserve the tracking parameter
21
- const redirectTo = utmSource ? `${target}?utm_source=${utmSource}` : target;
22
-
23
- throw redirect(302, redirectTo);
24
- }
@@ -1,24 +0,0 @@
1
- /* ==========================================================================
2
- src/routes/privacy-rights/+page.server.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
- import { redirect } from "@sveltejs/kit";
9
-
10
- export const prerender = false;
11
-
12
- /** @type {import('./$types').PageServerLoad} */
13
- export function load({ url }) {
14
- const utmSource = url.searchParams.get("utm_source");
15
-
16
- // Set target to your actual privacy rights form path
17
- const target =
18
- "https://cloud.neteng.pro/index.php/apps/forms/s/6HpZKZCaLwb6TXYL99nLQM8t";
19
-
20
- // Preserve the tracking parameter
21
- const redirectTo = utmSource ? `${target}?utm_source=${utmSource}` : target;
22
-
23
- throw redirect(302, redirectTo);
24
- }