@networkpro/web 1.4.2 → 1.5.0

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/netlify.toml CHANGED
@@ -1,3 +1,15 @@
1
1
  [build]
2
2
  command = "npm run build"
3
3
  publish = "build"
4
+
5
+ [[plugins]]
6
+ package = "netlify-plugin-submit-sitemap"
7
+
8
+ [plugins.inputs]
9
+ baseUrl = "https://netwk.pro"
10
+ sitemapPath = "/sitemap.xml"
11
+ ignorePeriod = 0
12
+ providers = [
13
+ "google",
14
+ "yandex"
15
+ ]
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@networkpro/web",
3
3
  "private": false,
4
4
  "sideEffects": false,
5
- "version": "1.4.2",
5
+ "version": "1.5.0",
6
6
  "description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy — Network Pro Strategies",
7
7
  "keywords": [
8
8
  "security",
package/src/app.html CHANGED
@@ -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
  }
@@ -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,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
+ }
@@ -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 -->
@@ -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
+ });