@networkpro/web 1.10.1 → 1.11.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/cspell.json +3 -0
- package/jsconfig.json +1 -15
- package/jsconfig.template.jsonc +32 -0
- package/netlify.toml +0 -1
- package/package.json +9 -9
- package/src/app.html +1 -1
- package/src/hooks.server.js +13 -0
- package/src/lib/components/Badges.svelte +6 -2
- package/src/lib/components/LegalNav.svelte +6 -1
- package/src/lib/components/MetaTags.svelte +8 -2
- package/src/lib/components/layout/Footer.svelte +3 -4
- package/src/lib/index.js +7 -33
- package/src/lib/pages/PrivacyContent.svelte +68 -35
- package/src/lib/pages/PrivacyDashboard.svelte +58 -27
- package/src/lib/stores/posthog.js +115 -0
- package/src/lib/stores/trackingStatus.js +30 -0
- package/src/lib/types/appConstants.js +60 -0
- package/src/lib/types/fossTypes.js +17 -0
- package/src/lib/utils/privacy.js +47 -4
- package/src/lib/utils/trackingCookies.js +40 -10
- package/src/lib/utils/trackingStatus.js +18 -3
- package/src/routes/+layout.js +0 -2
- package/src/routes/+layout.svelte +36 -40
- package/static/offline.min.css +2 -3
- package/svelte.config.js +5 -2
- package/vite.config.js +2 -4
- package/src/lib/components/PostHog.svelte +0 -36
package/cspell.json
CHANGED
package/jsconfig.json
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/* =========================================================================
|
|
2
|
-
jsconfig.json
|
|
3
|
-
|
|
4
|
-
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
5
|
-
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
6
|
-
This file is part of Network Pro.
|
|
7
|
-
========================================================================= */
|
|
8
|
-
|
|
9
1
|
{
|
|
10
2
|
"extends": "./.svelte-kit/tsconfig.json",
|
|
11
3
|
"compilerOptions": {
|
|
@@ -19,12 +11,6 @@ This file is part of Network Pro.
|
|
|
19
11
|
"strict": true,
|
|
20
12
|
"moduleResolution": "bundler"
|
|
21
13
|
},
|
|
22
|
-
"exclude": ["vite.config.js"],
|
|
14
|
+
"exclude": ["vite.config.js"],
|
|
23
15
|
"include": ["src", "src/global.d.ts", "src/service-worker.js"]
|
|
24
|
-
|
|
25
|
-
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
|
26
|
-
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
|
27
|
-
|
|
28
|
-
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
|
29
|
-
// from the referenced tsconfig.json - TypeScript does not merge them in
|
|
30
16
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* =========================================================================
|
|
2
|
+
jsconfig.template.jsonc
|
|
3
|
+
|
|
4
|
+
NOTE: This file is for reference only and is not actively used by SvelteKit or tooling. SvelteKit uses the jsconfig.json file without comments for actual configuration.
|
|
5
|
+
|
|
6
|
+
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
7
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
8
|
+
This file is part of Network Pro.
|
|
9
|
+
========================================================================= */
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
"extends": "./.svelte-kit/tsconfig.json",
|
|
13
|
+
"compilerOptions": {
|
|
14
|
+
"allowJs": true,
|
|
15
|
+
"checkJs": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"resolveJsonModule": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"sourceMap": true,
|
|
21
|
+
"strict": true,
|
|
22
|
+
"moduleResolution": "bundler"
|
|
23
|
+
},
|
|
24
|
+
"exclude": ["vite.config.js"], // Exclude the config file if needed
|
|
25
|
+
"include": ["src", "src/global.d.ts", "src/service-worker.js"]
|
|
26
|
+
|
|
27
|
+
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
|
28
|
+
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
|
29
|
+
|
|
30
|
+
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
|
31
|
+
// from the referenced tsconfig.json - TypeScript does not merge them in
|
|
32
|
+
}
|
package/netlify.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@networkpro/web",
|
|
3
3
|
"private": false,
|
|
4
|
-
"
|
|
5
|
-
"./.netlify/shims.js"
|
|
6
|
-
],
|
|
7
|
-
"version": "1.10.1",
|
|
4
|
+
"version": "1.11.0",
|
|
8
5
|
"description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy — Network Pro Strategies",
|
|
9
6
|
"keywords": [
|
|
10
7
|
"advisory",
|
|
@@ -47,6 +44,8 @@
|
|
|
47
44
|
"prepare": "svelte-kit sync || echo ''",
|
|
48
45
|
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
49
46
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
|
47
|
+
"type-check": "svelte-check --tsconfig ./jsconfig.json",
|
|
48
|
+
"lint:types": "npm run type-check",
|
|
50
49
|
"check:env": "node scripts/checkEnv.js",
|
|
51
50
|
"check:node": "node scripts/checkNode.js",
|
|
52
51
|
"checkout": "npm run check:node && npm run test:all && npm run lint:all && npm run check",
|
|
@@ -78,7 +77,7 @@
|
|
|
78
77
|
},
|
|
79
78
|
"dependencies": {
|
|
80
79
|
"dompurify": "^3.2.6",
|
|
81
|
-
"posthog-js": "^1.249.
|
|
80
|
+
"posthog-js": "^1.249.2",
|
|
82
81
|
"semver": "^7.7.2",
|
|
83
82
|
"svelte": "5.33.14"
|
|
84
83
|
},
|
|
@@ -88,11 +87,11 @@
|
|
|
88
87
|
"@lhci/cli": "^0.14.0",
|
|
89
88
|
"@playwright/test": "^1.52.0",
|
|
90
89
|
"@sveltejs/adapter-netlify": "^5.0.2",
|
|
91
|
-
"@sveltejs/kit": "2.21.
|
|
92
|
-
"@sveltejs/vite-plugin-svelte": "5.0
|
|
90
|
+
"@sveltejs/kit": "2.21.2",
|
|
91
|
+
"@sveltejs/vite-plugin-svelte": "5.1.0",
|
|
93
92
|
"@testing-library/jest-dom": "^6.6.3",
|
|
94
93
|
"@testing-library/svelte": "^5.2.8",
|
|
95
|
-
"@vitest/coverage-v8": "^3.2.
|
|
94
|
+
"@vitest/coverage-v8": "^3.2.1",
|
|
96
95
|
"autoprefixer": "^10.4.21",
|
|
97
96
|
"browserslist": "^4.25.0",
|
|
98
97
|
"eslint": "^9.28.0",
|
|
@@ -119,7 +118,8 @@
|
|
|
119
118
|
"typescript": "^5.8.3",
|
|
120
119
|
"vite": "^6.3.5",
|
|
121
120
|
"vite-plugin-lightningcss": "^0.0.5",
|
|
122
|
-
"
|
|
121
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
122
|
+
"vitest": "^3.2.1"
|
|
123
123
|
},
|
|
124
124
|
"overrides": {
|
|
125
125
|
"@sveltejs/kit": {
|
package/src/app.html
CHANGED
package/src/hooks.server.js
CHANGED
|
@@ -91,3 +91,16 @@ export async function handle({ event, resolve }) {
|
|
|
91
91
|
|
|
92
92
|
return response;
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* SvelteKit server-side error handler to log SSR errors.
|
|
97
|
+
* @type {import('@sveltejs/kit').HandleServerError}
|
|
98
|
+
*/
|
|
99
|
+
export function handleError({ error, event }) {
|
|
100
|
+
console.error("🔴 SSR Error in route:", event.url.pathname);
|
|
101
|
+
console.error(error);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
message: "A server-side error occurred",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -10,10 +10,15 @@ This file is part of Network Pro.
|
|
|
10
10
|
import { base } from "$app/paths";
|
|
11
11
|
// Import badges for licenses
|
|
12
12
|
import { ccBadge, gplBadge } from "$lib";
|
|
13
|
+
import { CONSTANTS } from "$lib";
|
|
13
14
|
|
|
14
15
|
// Log the base path to verify its value
|
|
15
16
|
//console.log("Base path:", base);
|
|
16
17
|
|
|
18
|
+
console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
|
|
19
|
+
|
|
20
|
+
const { PAGE } = CONSTANTS;
|
|
21
|
+
|
|
17
22
|
const ccbyLink = `${base}/license#cc-by`;
|
|
18
23
|
const gplLink = `${base}/license#gnu-gpl`;
|
|
19
24
|
|
|
@@ -51,14 +56,13 @@ This file is part of Network Pro.
|
|
|
51
56
|
</script>
|
|
52
57
|
|
|
53
58
|
<!-- BEGIN BADGES -->
|
|
54
|
-
<!-- Updated 2025-05-15 02:12:35 by SunDevil311 -->
|
|
55
59
|
<div class="bnav2-wrap">
|
|
56
60
|
<table class="bnav2">
|
|
57
61
|
<tbody>
|
|
58
62
|
<tr>
|
|
59
63
|
{#each badges as badge}
|
|
60
64
|
<td class="bnav2-nav">
|
|
61
|
-
<a href={badge.href} target=
|
|
65
|
+
<a href={badge.href} target={PAGE.SELF}>
|
|
62
66
|
<img
|
|
63
67
|
decoding="async"
|
|
64
68
|
loading="lazy"
|
|
@@ -8,10 +8,15 @@ This file is part of Network Pro.
|
|
|
8
8
|
|
|
9
9
|
<script>
|
|
10
10
|
import { base } from "$app/paths";
|
|
11
|
+
import { CONSTANTS } from "$lib";
|
|
11
12
|
|
|
12
13
|
// Log the base path to verify its value
|
|
13
14
|
//console.log("Base path:", base);
|
|
14
15
|
|
|
16
|
+
console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
|
|
17
|
+
|
|
18
|
+
const { PAGE } = CONSTANTS;
|
|
19
|
+
|
|
15
20
|
const termsLink = `${base}/terms-of-use`;
|
|
16
21
|
const privacyLink = `${base}/privacy`;
|
|
17
22
|
const licenseLink = `${base}/license`;
|
|
@@ -62,7 +67,7 @@ This file is part of Network Pro.
|
|
|
62
67
|
<tr>
|
|
63
68
|
{#each row as link}
|
|
64
69
|
<td class="bnav-cell" colspan={link.colspan || 1}>
|
|
65
|
-
<a href={link.href} target=
|
|
70
|
+
<a href={link.href} target={PAGE.SELF}>
|
|
66
71
|
{link.text}
|
|
67
72
|
</a>
|
|
68
73
|
</td>
|
|
@@ -10,10 +10,16 @@ This file is part of Network Pro.
|
|
|
10
10
|
export let title;
|
|
11
11
|
export let description;
|
|
12
12
|
|
|
13
|
+
import { CONSTANTS } from "$lib";
|
|
14
|
+
|
|
15
|
+
console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
|
|
16
|
+
|
|
17
|
+
const { COMPANY_INFO, LINKS } = CONSTANTS;
|
|
18
|
+
|
|
13
19
|
// Static shared values
|
|
14
|
-
const ogUrl =
|
|
20
|
+
const ogUrl = LINKS.HOME;
|
|
15
21
|
const ogImg = "/img/banner-og-1200x630.png";
|
|
16
|
-
const companyName =
|
|
22
|
+
const companyName = COMPANY_INFO.NAME;
|
|
17
23
|
const twitterAct = "@NetEng_Pro";
|
|
18
24
|
</script>
|
|
19
25
|
|
|
@@ -149,14 +149,13 @@ This file is part of Network Pro.
|
|
|
149
149
|
{:else}
|
|
150
150
|
<a href={license.url} target={PAGE.SELF} rel={relLicense}>
|
|
151
151
|
<strong>{license.type}</strong>
|
|
152
|
-
</a
|
|
153
|
-
, as published by the
|
|
152
|
+
</a>, as published by the
|
|
154
153
|
<a rel={PAGE.REL} href={license.externalUrl} target={PAGE.BLANK}
|
|
155
154
|
>{license.description}</a
|
|
156
|
-
>, either version 3 of the License
|
|
155
|
+
>, either version 3 of the License or (at your option) any later
|
|
157
156
|
version.
|
|
158
157
|
{/if}
|
|
159
|
-
{index < licenses.length - 1 ? "
|
|
158
|
+
{index < licenses.length - 1 ? " and the " : ""}
|
|
160
159
|
{/each}
|
|
161
160
|
</p>
|
|
162
161
|
</div>
|
package/src/lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@ This file is part of Network Pro.
|
|
|
13
13
|
* @description Main export point for library components, utilities, and assets
|
|
14
14
|
* @module src/lib
|
|
15
15
|
* @author SunDevil311
|
|
16
|
-
* @updated 2025-06-
|
|
16
|
+
* @updated 2025-06-03
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
// Re-export images from dedicated images.js file
|
|
@@ -32,38 +32,8 @@ export * from "./images.js";
|
|
|
32
32
|
// export { default as Button } from './components/Button.svelte';
|
|
33
33
|
// export { default as Card } from './components/Card.svelte';
|
|
34
34
|
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
* NAME: string,
|
|
38
|
-
* APP_NAME: string,
|
|
39
|
-
* YEAR: string
|
|
40
|
-
* }} CompanyInfo
|
|
41
|
-
*
|
|
42
|
-
* @typedef {{
|
|
43
|
-
* EMAIL: string,
|
|
44
|
-
* SECURE: string,
|
|
45
|
-
* PRIVACY: string,
|
|
46
|
-
* PHONE: string
|
|
47
|
-
* }} ContactInfo
|
|
48
|
-
*
|
|
49
|
-
* @typedef {{
|
|
50
|
-
* BLANK: string,
|
|
51
|
-
* SELF: string,
|
|
52
|
-
* REL: string
|
|
53
|
-
* }} PageTargets
|
|
54
|
-
*
|
|
55
|
-
* @typedef {{
|
|
56
|
-
* BACKTOP: string,
|
|
57
|
-
* HREFTOP: string
|
|
58
|
-
* }} NavigationLabels
|
|
59
|
-
*
|
|
60
|
-
* @typedef {{
|
|
61
|
-
* COMPANY_INFO: CompanyInfo,
|
|
62
|
-
* CONTACT: ContactInfo,
|
|
63
|
-
* PAGE: PageTargets,
|
|
64
|
-
* NAV: NavigationLabels
|
|
65
|
-
* }} AppConstants
|
|
66
|
-
*/
|
|
35
|
+
/** @typedef {import('./types/appConstants.js').AppConstants} AppConstants */
|
|
36
|
+
|
|
67
37
|
|
|
68
38
|
/** @type {AppConstants} */
|
|
69
39
|
export const CONSTANTS = {
|
|
@@ -87,4 +57,8 @@ export const CONSTANTS = {
|
|
|
87
57
|
BACKTOP: "Back to top",
|
|
88
58
|
HREFTOP: "#top",
|
|
89
59
|
},
|
|
60
|
+
LINKS: {
|
|
61
|
+
HOME: "https://netwk.pro",
|
|
62
|
+
BLOG: "https://blog.netwk.pro",
|
|
63
|
+
},
|
|
90
64
|
};
|
|
@@ -9,43 +9,45 @@ This file is part of Network Pro.
|
|
|
9
9
|
<script>
|
|
10
10
|
import { base } from "$app/paths";
|
|
11
11
|
import { onMount } from "svelte";
|
|
12
|
-
import {
|
|
13
|
-
/** @type {(type: 'enable' | 'disable') => void} */
|
|
12
|
+
import { trackingStatus } from "$lib/stores/trackingStatus.js";
|
|
14
13
|
import {
|
|
14
|
+
/** @type {(type: 'enable' | 'disable') => void} */
|
|
15
15
|
setTrackingPreference,
|
|
16
|
+
/** @type {() => void} */
|
|
16
17
|
clearTrackingPreferences,
|
|
17
18
|
} from "$lib/utils/trackingCookies.js";
|
|
18
19
|
import { CONSTANTS } from "$lib";
|
|
19
20
|
|
|
20
|
-
// Log the base path to verify its value
|
|
21
|
-
//console.log("Base path:", base);
|
|
22
|
-
|
|
23
21
|
console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
/** @type {typeof CONSTANTS.COMPANY_INFO} */
|
|
24
|
+
const COMPANY_INFO = CONSTANTS.COMPANY_INFO;
|
|
26
25
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
/** @type {typeof CONSTANTS.CONTACT} */
|
|
27
|
+
const CONTACT = CONSTANTS.CONTACT;
|
|
28
|
+
|
|
29
|
+
/** @type {typeof CONSTANTS.PAGE} */
|
|
30
|
+
const PAGE = CONSTANTS.PAGE;
|
|
31
|
+
|
|
32
|
+
/** @type {typeof CONSTANTS.NAV} */
|
|
33
|
+
const NAV = CONSTANTS.NAV;
|
|
34
|
+
|
|
35
|
+
/** @type {string} */
|
|
33
36
|
const prightsLink = `${base}/privacy-rights`;
|
|
37
|
+
|
|
38
|
+
/** @type {string} */
|
|
34
39
|
const contactLink = `${base}/contact`;
|
|
40
|
+
|
|
41
|
+
/** @type {string} */
|
|
35
42
|
const pdashLink = `${base}/privacy-dashboard`;
|
|
36
43
|
|
|
37
|
-
/**
|
|
38
|
-
* URL to the privacy policy in Markdown format
|
|
39
|
-
* External URL to the GPC website
|
|
40
|
-
* @type {string}
|
|
41
|
-
*/
|
|
44
|
+
/** @type {string} */
|
|
42
45
|
const privacyLink = "https://docs.netwk.pro/privacy";
|
|
46
|
+
|
|
47
|
+
/** @type {string} */
|
|
43
48
|
const gpcLink = "https://globalprivacycontrol.org/";
|
|
44
49
|
|
|
45
|
-
/**
|
|
46
|
-
* Table of Contents Links
|
|
47
|
-
* @type {{ id: string, text: string }[]}
|
|
48
|
-
*/
|
|
50
|
+
/** @type {{ id: string, text: string }[]} */
|
|
49
51
|
const tocLinks = [
|
|
50
52
|
{ id: "intro", text: "Introduction" },
|
|
51
53
|
{ id: "collect", text: "Information We Collect" },
|
|
@@ -67,21 +69,41 @@ This file is part of Network Pro.
|
|
|
67
69
|
/** @type {string} */
|
|
68
70
|
const classSmall = "small-text";
|
|
69
71
|
|
|
72
|
+
/** @type {boolean} */
|
|
70
73
|
let optedOut = false;
|
|
74
|
+
|
|
75
|
+
/** @type {boolean} */
|
|
71
76
|
let optedIn = false;
|
|
72
|
-
let trackingStatus = "";
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Refreshes tracking preferences state and updates the reactive store.
|
|
80
|
+
* This function uses dynamic import to avoid SSR evaluation of browser-only code.
|
|
81
|
+
*
|
|
82
|
+
* @returns {Promise<void>}
|
|
83
|
+
*/
|
|
84
|
+
async function refreshTrackingStatus() {
|
|
85
|
+
/** @type {typeof import("$lib/utils/trackingStatus.js")} */
|
|
86
|
+
const tracking = await import("$lib/utils/trackingStatus.js");
|
|
87
|
+
|
|
88
|
+
const prefs = tracking.getTrackingPreferences();
|
|
76
89
|
optedOut = prefs.optedOut;
|
|
77
90
|
optedIn = prefs.optedIn;
|
|
78
|
-
trackingStatus
|
|
79
|
-
|
|
91
|
+
trackingStatus.set(prefs.status);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Runs tracking preference detection on client mount.
|
|
96
|
+
*/
|
|
97
|
+
onMount(() => {
|
|
98
|
+
refreshTrackingStatus();
|
|
99
|
+
console.log("[Tracking] Status:", $trackingStatus);
|
|
80
100
|
});
|
|
81
101
|
|
|
82
102
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
103
|
+
* Toggles user tracking opt-out setting and updates cookies + store.
|
|
104
|
+
*
|
|
105
|
+
* @param {boolean} value - Whether the user is opting out
|
|
106
|
+
* @returns {void}
|
|
85
107
|
*/
|
|
86
108
|
function toggleTracking(value) {
|
|
87
109
|
optedOut = value;
|
|
@@ -92,11 +114,14 @@ This file is part of Network Pro.
|
|
|
92
114
|
console.log("[Tracking] User cleared opt-out");
|
|
93
115
|
clearTrackingPreferences();
|
|
94
116
|
}
|
|
117
|
+
refreshTrackingStatus();
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
121
|
+
* Toggles user tracking opt-in setting and updates cookies + store.
|
|
122
|
+
*
|
|
123
|
+
* @param {boolean} value - Whether the user is opting in
|
|
124
|
+
* @returns {void}
|
|
100
125
|
*/
|
|
101
126
|
function toggleOptIn(value) {
|
|
102
127
|
optedIn = value;
|
|
@@ -107,6 +132,7 @@ This file is part of Network Pro.
|
|
|
107
132
|
console.log("[Tracking] User cleared opt-in");
|
|
108
133
|
clearTrackingPreferences();
|
|
109
134
|
}
|
|
135
|
+
refreshTrackingStatus();
|
|
110
136
|
}
|
|
111
137
|
</script>
|
|
112
138
|
|
|
@@ -241,10 +267,17 @@ This file is part of Network Pro.
|
|
|
241
267
|
<div class="spacer"></div>
|
|
242
268
|
|
|
243
269
|
<h3>Tracking Preferences</h3>
|
|
244
|
-
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
|
|
270
|
+
{#if $trackingStatus !== "⏳ Checking tracking preferences..."}
|
|
271
|
+
<p id="tracking-status" aria-live="polite">
|
|
272
|
+
<strong>Tracking Status:</strong>
|
|
273
|
+
{$trackingStatus}
|
|
274
|
+
</p>
|
|
275
|
+
{:else}
|
|
276
|
+
<p id="tracking-status" aria-live="polite">
|
|
277
|
+
<strong>Tracking Status:</strong>
|
|
278
|
+
<em>Loading…</em>
|
|
279
|
+
</p>
|
|
280
|
+
{/if}
|
|
248
281
|
|
|
249
282
|
<!-- Opt-out checkbox -->
|
|
250
283
|
<label>
|
|
@@ -405,7 +438,7 @@ This file is part of Network Pro.
|
|
|
405
438
|
For questions, please utilize our <a
|
|
406
439
|
rel={PAGE.REL}
|
|
407
440
|
href={contactLink}
|
|
408
|
-
target={PAGE.
|
|
441
|
+
target={PAGE.BLANK}>Contact Form</a> or contact us directly:
|
|
409
442
|
</p>
|
|
410
443
|
<p>
|
|
411
444
|
<strong>{COMPANY_INFO.NAME}</strong><br />
|
|
@@ -9,52 +9,72 @@ This file is part of Network Pro.
|
|
|
9
9
|
<script>
|
|
10
10
|
import { base } from "$app/paths";
|
|
11
11
|
import { onMount } from "svelte";
|
|
12
|
-
import {
|
|
13
|
-
/** @type {(type: 'enable' | 'disable') => void} */
|
|
12
|
+
import { trackingStatus } from "$lib/stores/trackingStatus.js";
|
|
14
13
|
import {
|
|
14
|
+
/** @type {(type: 'enable' | 'disable') => void} */
|
|
15
15
|
setTrackingPreference,
|
|
16
|
+
/** @type {() => void} */
|
|
16
17
|
clearTrackingPreferences,
|
|
17
18
|
} from "$lib/utils/trackingCookies.js";
|
|
18
19
|
import { CONSTANTS } from "$lib";
|
|
19
20
|
|
|
20
|
-
// Log the base path to verify its value
|
|
21
|
-
//console.log("Base path:", base);
|
|
22
|
-
|
|
23
21
|
console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
/** @type {typeof CONSTANTS.CONTACT} */
|
|
24
|
+
const CONTACT = CONSTANTS.CONTACT;
|
|
26
25
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
/** @type {typeof CONSTANTS.PAGE} */
|
|
27
|
+
const PAGE = CONSTANTS.PAGE;
|
|
28
|
+
|
|
29
|
+
/** @type {typeof CONSTANTS.NAV} */
|
|
30
|
+
const NAV = CONSTANTS.NAV;
|
|
31
|
+
|
|
32
|
+
/** @type {string} */
|
|
31
33
|
const spaceStyle = "spacer";
|
|
32
34
|
|
|
33
|
-
/**
|
|
34
|
-
* URL to the full Privacy Policy using the base path
|
|
35
|
-
* @type {string}
|
|
36
|
-
*/
|
|
35
|
+
/** @type {string} */
|
|
37
36
|
const privacyPolicy = `${base}/privacy`;
|
|
37
|
+
|
|
38
|
+
/** @type {string} */
|
|
38
39
|
const prightsLink = `${base}/privacy-rights`;
|
|
39
40
|
|
|
40
41
|
/** @type {string} */
|
|
41
42
|
const classSmall = "small-text";
|
|
42
43
|
|
|
44
|
+
/** @type {boolean} */
|
|
43
45
|
let optedOut = false;
|
|
46
|
+
|
|
47
|
+
/** @type {boolean} */
|
|
44
48
|
let optedIn = false;
|
|
45
|
-
let trackingStatus = "";
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Refreshes tracking preferences state and updates the reactive store.
|
|
52
|
+
* Uses dynamic import to prevent SSR from loading browser-only dependencies.
|
|
53
|
+
*
|
|
54
|
+
* @returns {Promise<void>}
|
|
55
|
+
*/
|
|
56
|
+
async function refreshTrackingStatus() {
|
|
57
|
+
/** @type {typeof import("$lib/utils/trackingStatus.js")} */
|
|
58
|
+
const tracking = await import("$lib/utils/trackingStatus.js");
|
|
59
|
+
|
|
60
|
+
const prefs = tracking.getTrackingPreferences();
|
|
49
61
|
optedOut = prefs.optedOut;
|
|
50
62
|
optedIn = prefs.optedIn;
|
|
51
|
-
trackingStatus
|
|
52
|
-
|
|
63
|
+
trackingStatus.set(prefs.status);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Initializes tracking state on component mount (runs only in browser).
|
|
68
|
+
*/
|
|
69
|
+
onMount(() => {
|
|
70
|
+
refreshTrackingStatus();
|
|
53
71
|
});
|
|
54
72
|
|
|
55
73
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
74
|
+
* Toggles opt-out tracking state, updates cookie and tracking store.
|
|
75
|
+
*
|
|
76
|
+
* @param {boolean} value - Whether the user is opting out
|
|
77
|
+
* @returns {void}
|
|
58
78
|
*/
|
|
59
79
|
function toggleTracking(value) {
|
|
60
80
|
optedOut = value;
|
|
@@ -65,11 +85,14 @@ This file is part of Network Pro.
|
|
|
65
85
|
console.log("[Tracking] User cleared opt-out");
|
|
66
86
|
clearTrackingPreferences();
|
|
67
87
|
}
|
|
88
|
+
refreshTrackingStatus();
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
92
|
+
* Toggles opt-in tracking state, updates cookie and tracking store.
|
|
93
|
+
*
|
|
94
|
+
* @param {boolean} value - Whether the user is opting in
|
|
95
|
+
* @returns {void}
|
|
73
96
|
*/
|
|
74
97
|
function toggleOptIn(value) {
|
|
75
98
|
optedIn = value;
|
|
@@ -80,6 +103,7 @@ This file is part of Network Pro.
|
|
|
80
103
|
console.log("[Tracking] User cleared opt-in");
|
|
81
104
|
clearTrackingPreferences();
|
|
82
105
|
}
|
|
106
|
+
refreshTrackingStatus();
|
|
83
107
|
}
|
|
84
108
|
</script>
|
|
85
109
|
|
|
@@ -143,10 +167,17 @@ This file is part of Network Pro.
|
|
|
143
167
|
|
|
144
168
|
|
|
145
169
|
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
170
|
+
{#if $trackingStatus !== "⏳ Checking tracking preferences..."}
|
|
171
|
+
<p id="tracking-status" aria-live="polite">
|
|
172
|
+
<strong>Tracking Status:</strong>
|
|
173
|
+
{$trackingStatus}
|
|
174
|
+
</p>
|
|
175
|
+
{:else}
|
|
176
|
+
<p id="tracking-status" aria-live="polite">
|
|
177
|
+
<strong>Tracking Status:</strong>
|
|
178
|
+
<em>Loading…</em>
|
|
179
|
+
</p>
|
|
180
|
+
{/if}
|
|
150
181
|
|
|
151
182
|
<!-- Opt-out checkbox -->
|
|
152
183
|
<label>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
src/lib/stores/posthog.js
|
|
3
|
+
|
|
4
|
+
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
5
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @file posthog.js
|
|
10
|
+
* @description Privacy-aware PostHog tracking store with reactive state and safe API surface.
|
|
11
|
+
* @module src/lib/stores
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
shouldRemindUserToReconsent,
|
|
16
|
+
shouldTrackUser,
|
|
17
|
+
} from "$lib/utils/privacy.js";
|
|
18
|
+
import { get, writable } from "svelte/store";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tracks whether tracking is allowed based on cookies, DNT/GPC, and user preference.
|
|
22
|
+
* @type {import("svelte/store").Writable<boolean>}
|
|
23
|
+
*/
|
|
24
|
+
export const trackingEnabled = writable(false);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Determines if the user should be reminded to re-consent (after 6 months).
|
|
28
|
+
* @type {import("svelte/store").Writable<boolean>}
|
|
29
|
+
*/
|
|
30
|
+
export const showReminder = writable(false);
|
|
31
|
+
|
|
32
|
+
/** @type {boolean} Internal one-time init guard */
|
|
33
|
+
let initialized = false;
|
|
34
|
+
|
|
35
|
+
/** @type {import("posthog-js").PostHog | null} Loaded PostHog instance */
|
|
36
|
+
let ph = null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initializes the PostHog analytics client if tracking is permitted.
|
|
40
|
+
* Uses dynamic import to avoid SSR failures.
|
|
41
|
+
* @returns {Promise<void>}
|
|
42
|
+
*/
|
|
43
|
+
export async function initPostHog() {
|
|
44
|
+
if (initialized || typeof window === "undefined") return;
|
|
45
|
+
initialized = true;
|
|
46
|
+
|
|
47
|
+
const allowTracking = shouldTrackUser();
|
|
48
|
+
trackingEnabled.set(allowTracking);
|
|
49
|
+
showReminder.set(shouldRemindUserToReconsent());
|
|
50
|
+
|
|
51
|
+
if (!allowTracking) {
|
|
52
|
+
console.log("[PostHog] Tracking is disabled — skipping init.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const posthogModule = await import("posthog-js");
|
|
57
|
+
ph = posthogModule.default;
|
|
58
|
+
|
|
59
|
+
// cspell:disable-next-line
|
|
60
|
+
ph.init("phc_Qshfo6AXzh4pS7aPigfqyeo4qj1qlyh7gDuHDeVMSR0", {
|
|
61
|
+
api_host: "https://us.i.posthog.com",
|
|
62
|
+
autocapture: true,
|
|
63
|
+
capture_pageview: false,
|
|
64
|
+
person_profiles: "identified_only",
|
|
65
|
+
loaded: (phInstance) => {
|
|
66
|
+
if (!allowTracking) {
|
|
67
|
+
console.log(
|
|
68
|
+
"[PostHog] ⛔ User opted out — calling opt_out_capturing()",
|
|
69
|
+
);
|
|
70
|
+
phInstance.opt_out_capturing();
|
|
71
|
+
} else {
|
|
72
|
+
console.log("[PostHog] ✅ Tracking enabled");
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
ph.capture("$pageview");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Conditionally captures an event if tracking is enabled.
|
|
82
|
+
* @param {string} event - The event name to track
|
|
83
|
+
* @param {Record<string, any>} [properties={}] - Optional event properties
|
|
84
|
+
*/
|
|
85
|
+
export function capture(event, properties = {}) {
|
|
86
|
+
if (ph !== null && get(trackingEnabled)) {
|
|
87
|
+
ph.capture(event, properties);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Conditionally identifies a user if tracking is enabled.
|
|
93
|
+
* @param {string} id - Unique user identifier
|
|
94
|
+
* @param {Record<string, any>} [properties={}] - Optional user traits
|
|
95
|
+
*/
|
|
96
|
+
export function identify(id, properties = {}) {
|
|
97
|
+
if (ph !== null && get(trackingEnabled)) {
|
|
98
|
+
ph.identify(id, properties);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* For test cleanup only — resets internal state.
|
|
104
|
+
* No-op in production.
|
|
105
|
+
* @returns {void}
|
|
106
|
+
*/
|
|
107
|
+
export function _resetPostHog() {
|
|
108
|
+
if (import.meta.env.MODE === "production") {
|
|
109
|
+
console.warn("[PostHog] _resetPostHog() called in production — no-op");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
initialized = false;
|
|
114
|
+
ph = null;
|
|
115
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
src/lib/stores/trackingStatus.js
|
|
3
|
+
|
|
4
|
+
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
5
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @file trackingStatus.js
|
|
10
|
+
* @description Tracks state of PostHog tracking status for instant updates
|
|
11
|
+
* in Privacy Policy and Privacy Dashboard.
|
|
12
|
+
* @module src/lib/stores
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { writable } from "svelte/store";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Writable tracking status store.
|
|
19
|
+
* Initialized with fallback value and updated in browser context.
|
|
20
|
+
* @type {import("svelte/store").Writable<string>}
|
|
21
|
+
*/
|
|
22
|
+
export const trackingStatus = writable("⏳ Checking tracking preferences...");
|
|
23
|
+
|
|
24
|
+
// Dynamically import browser-only logic after mount
|
|
25
|
+
if (typeof window !== "undefined") {
|
|
26
|
+
import("$lib/utils/trackingStatus.js").then(({ getTrackingPreferences }) => {
|
|
27
|
+
const prefs = getTrackingPreferences();
|
|
28
|
+
trackingStatus.set(prefs.status);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
src/lib/types/appConstants.js
|
|
3
|
+
|
|
4
|
+
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
5
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
6
|
+
This file is part of Network Pro.
|
|
7
|
+
========================================================================== */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @file appConstants.js
|
|
11
|
+
* @description Type definitions for app constants in src/lib/index.js
|
|
12
|
+
* @module src/lib/types
|
|
13
|
+
* @author SunDevil311
|
|
14
|
+
* @updated 2025-06-03
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} CompanyInfo
|
|
19
|
+
* @property {string} NAME - Full company name
|
|
20
|
+
* @property {string} APP_NAME - Application name
|
|
21
|
+
* @property {string} YEAR - Current copyright year
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} ContactInfo
|
|
26
|
+
* @property {string} EMAIL - Primary contact email
|
|
27
|
+
* @property {string} SECURE - Secure contact email
|
|
28
|
+
* @property {string} PRIVACY - Privacy policy email
|
|
29
|
+
* @property {string} PHONE - Support phone number
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {object} PageTargets
|
|
34
|
+
* @property {string} BLANK - Value for `target="_blank"`
|
|
35
|
+
* @property {string} SELF - Value for `target="_self"`
|
|
36
|
+
* @property {string} REL - Value for `rel="noopener noreferrer"`
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} NavigationLabels
|
|
41
|
+
* @property {string} BACKTOP
|
|
42
|
+
* @property {string} HREFTOP
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} Links
|
|
47
|
+
* @property {string} HOME - Main website URL
|
|
48
|
+
* @property {string} BLOG - External blog URL
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {object} AppConstants
|
|
53
|
+
* @property {CompanyInfo} COMPANY_INFO
|
|
54
|
+
* @property {ContactInfo} CONTACT
|
|
55
|
+
* @property {PageTargets} PAGE
|
|
56
|
+
* @property {NavigationLabels} NAV
|
|
57
|
+
* @property {Links} LINKS
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
export {};
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
src/lib/types/fossTypes.js
|
|
3
|
+
|
|
4
|
+
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
5
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
6
|
+
This file is part of Network Pro.
|
|
7
|
+
========================================================================== */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @file fossTypes.js
|
|
11
|
+
* @description Type definitions for `fossItem` in
|
|
12
|
+
* src/lib/components/foss/FossItemContent.svelte
|
|
13
|
+
* @module src/lib/types
|
|
14
|
+
* @author SunDevil311
|
|
15
|
+
* @updated 2025-06-03
|
|
16
|
+
*/
|
|
17
|
+
|
|
1
18
|
/**
|
|
2
19
|
* @typedef {object} FossLink
|
|
3
20
|
* @property {string} [label]
|
package/src/lib/utils/privacy.js
CHANGED
|
@@ -11,28 +11,71 @@ This file is part of Network Pro.
|
|
|
11
11
|
* @description Determines whether the user allows tracking based on DNT, GPC, or manual opt-out.
|
|
12
12
|
* @module src/lib/utils/
|
|
13
13
|
* @author SunDevil311
|
|
14
|
-
* @updated 2025-
|
|
14
|
+
* @updated 2025-06-03
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
+
* Check if user has manually set tracking preference.
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
export function hasUserManuallySetTrackingPreference() {
|
|
22
|
+
if (typeof document === "undefined") return false;
|
|
23
|
+
|
|
24
|
+
const cookies = document.cookie;
|
|
25
|
+
return (
|
|
26
|
+
cookies.includes("enable_tracking=true") ||
|
|
27
|
+
cookies.includes("disable_tracking=true")
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Determine if the user allows tracking based on cookies, DNT, and GPC.
|
|
18
33
|
* @returns {boolean}
|
|
19
34
|
*/
|
|
20
35
|
export function shouldTrackUser() {
|
|
36
|
+
if (
|
|
37
|
+
typeof window === "undefined" ||
|
|
38
|
+
typeof navigator === "undefined" ||
|
|
39
|
+
typeof document === "undefined"
|
|
40
|
+
) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cookies = document.cookie;
|
|
21
45
|
const windowDNT = /** @type {any} */ (window).doNotTrack;
|
|
22
46
|
const navigatorGPC = /** @type {any} */ (navigator).globalPrivacyControl;
|
|
23
47
|
|
|
24
48
|
const dnt = navigator.doNotTrack === "1" || windowDNT === "1";
|
|
25
49
|
const gpc = navigatorGPC === true;
|
|
26
50
|
|
|
27
|
-
const manualOptOut =
|
|
28
|
-
const manualOptIn =
|
|
51
|
+
const manualOptOut = cookies.includes("disable_tracking=true");
|
|
52
|
+
const manualOptIn = cookies.includes("enable_tracking=true");
|
|
29
53
|
|
|
30
54
|
console.log("[Privacy] Opt-in cookie present:", manualOptIn);
|
|
31
55
|
console.log("[Privacy] Opt-out cookie present:", manualOptOut);
|
|
32
56
|
|
|
33
|
-
// Opt-in overrides everything; opt-out disables regardless of DNT/GPC
|
|
34
57
|
if (manualOptIn) return true;
|
|
35
58
|
if (manualOptOut) return false;
|
|
36
59
|
|
|
37
60
|
return !dnt && !gpc;
|
|
38
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Determines if user should be reminded to reconsent (after 6 months).
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
export function shouldRemindUserToReconsent() {
|
|
68
|
+
if (typeof document === "undefined") return false;
|
|
69
|
+
|
|
70
|
+
if (!hasUserManuallySetTrackingPreference()) return false;
|
|
71
|
+
|
|
72
|
+
const match = document.cookie.match(/tracking_consent_timestamp=(\d+)/);
|
|
73
|
+
if (!match) return true;
|
|
74
|
+
|
|
75
|
+
const timestamp = Number(match[1]);
|
|
76
|
+
if (isNaN(timestamp)) return true;
|
|
77
|
+
|
|
78
|
+
const age = Date.now() - timestamp;
|
|
79
|
+
|
|
80
|
+
return age > 1000 * 60 * 60 * 24 * 180; // 6 months
|
|
81
|
+
}
|
|
@@ -11,30 +11,60 @@ This file is part of Network Pro.
|
|
|
11
11
|
* @description Handles setting, clearing, and toggling tracking preference cookies.
|
|
12
12
|
* @module src/lib/utils/
|
|
13
13
|
* @author SunDevil311
|
|
14
|
-
* @updated 2025-
|
|
14
|
+
* @updated 2025-06-04
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
// 6 months (in seconds). Will be centralized later.
|
|
18
|
+
const DEFAULT_COOKIE_MAX_AGE = 60 * 60 * 24 * 180;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds a standard cookie string for use in all tracking cookies.
|
|
22
|
+
* @param {number} maxAge
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
function buildCookieSettings(maxAge) {
|
|
26
|
+
return `path=/; max-age=${maxAge}; expires=${new Date(Date.now() + maxAge * 1000).toUTCString()}; SameSite=Lax; Secure`;
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
/**
|
|
18
|
-
*
|
|
30
|
+
* Sets tracking preference cookies based on type.
|
|
19
31
|
* @param {"enable" | "disable"} type
|
|
32
|
+
* @param {number} [maxAge=DEFAULT_COOKIE_MAX_AGE]
|
|
20
33
|
*/
|
|
21
|
-
export function setTrackingPreference(type) {
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
export function setTrackingPreference(type, maxAge = DEFAULT_COOKIE_MAX_AGE) {
|
|
35
|
+
if (typeof document === "undefined") return; // SSR guard
|
|
36
|
+
|
|
37
|
+
const cookieSettings = buildCookieSettings(maxAge);
|
|
38
|
+
const now = Date.now();
|
|
24
39
|
|
|
25
40
|
if (type === "enable") {
|
|
26
41
|
document.cookie = `enable_tracking=true; ${cookieSettings}`;
|
|
27
|
-
document.cookie = `
|
|
42
|
+
document.cookie = `tracking_consent_timestamp=${now}; ${cookieSettings}`;
|
|
43
|
+
clearCookie("disable_tracking");
|
|
28
44
|
} else if (type === "disable") {
|
|
29
45
|
document.cookie = `disable_tracking=true; ${cookieSettings}`;
|
|
30
|
-
document.cookie = `
|
|
46
|
+
document.cookie = `tracking_consent_timestamp=${now}; ${cookieSettings}`;
|
|
47
|
+
clearCookie("enable_tracking");
|
|
31
48
|
}
|
|
32
49
|
}
|
|
33
50
|
|
|
34
51
|
/**
|
|
35
|
-
*
|
|
52
|
+
* Clears all tracking-related cookies.
|
|
36
53
|
*/
|
|
37
54
|
export function clearTrackingPreferences() {
|
|
38
|
-
document
|
|
39
|
-
|
|
55
|
+
if (typeof document === "undefined") return; // SSR guard
|
|
56
|
+
|
|
57
|
+
clearCookie("enable_tracking");
|
|
58
|
+
clearCookie("disable_tracking");
|
|
59
|
+
clearCookie("tracking_consent_timestamp");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clears an individual cookie.
|
|
64
|
+
* @param {string} name
|
|
65
|
+
*/
|
|
66
|
+
function clearCookie(name) {
|
|
67
|
+
if (typeof document === "undefined") return; // SSR guard
|
|
68
|
+
|
|
69
|
+
document.cookie = `${name}=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax; Secure`;
|
|
40
70
|
}
|
|
@@ -9,12 +9,16 @@ This file is part of Network Pro.
|
|
|
9
9
|
/**
|
|
10
10
|
* @file trackingStatus.js
|
|
11
11
|
* @description Get tracking preferences based on cookies and browser privacy signals.
|
|
12
|
-
* @module src/lib/utils
|
|
12
|
+
* @module src/lib/utils
|
|
13
13
|
* @author SunDevil311
|
|
14
14
|
* @updated 2025-05-28
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { browser } from "$app/environment";
|
|
18
|
+
|
|
17
19
|
/**
|
|
20
|
+
* Gets the current tracking preferences based on browser cookies and signals.
|
|
21
|
+
*
|
|
18
22
|
* @returns {{
|
|
19
23
|
* optedOut: boolean,
|
|
20
24
|
* optedIn: boolean,
|
|
@@ -24,12 +28,23 @@ This file is part of Network Pro.
|
|
|
24
28
|
* }}
|
|
25
29
|
*/
|
|
26
30
|
export function getTrackingPreferences() {
|
|
31
|
+
// Prevent errors during SSR (no document or navigator)
|
|
32
|
+
if (!browser) {
|
|
33
|
+
return {
|
|
34
|
+
optedOut: false,
|
|
35
|
+
optedIn: false,
|
|
36
|
+
dnt: false,
|
|
37
|
+
gpc: false,
|
|
38
|
+
status: "⏳ Checking tracking preferences...",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
const cookies = document.cookie;
|
|
28
43
|
const optedOut = cookies.includes("disable_tracking=true");
|
|
29
44
|
const optedIn = cookies.includes("enable_tracking=true");
|
|
30
|
-
const dnt = navigator.doNotTrack === "1";
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
const dnt = navigator.doNotTrack === "1";
|
|
47
|
+
// @ts-expect-error: 'globalPrivacyControl' is non-standard
|
|
33
48
|
const gpc = navigator.globalPrivacyControl === true;
|
|
34
49
|
|
|
35
50
|
let status = "⚙️ Using default settings (tracking enabled)";
|
package/src/routes/+layout.js
CHANGED
|
@@ -9,61 +9,61 @@ This file is part of Network Pro.
|
|
|
9
9
|
<script>
|
|
10
10
|
export let data;
|
|
11
11
|
|
|
12
|
+
import { onMount } from "svelte";
|
|
13
|
+
import { afterNavigate } from "$app/navigation";
|
|
14
|
+
import { initPostHog, showReminder, capture } from "$lib/stores/posthog";
|
|
15
|
+
import { registerServiceWorker } from "$lib/registerServiceWorker.js";
|
|
16
|
+
import { browser } from "$app/environment";
|
|
17
|
+
import { shouldTrackUser } from "$lib/utils/privacy.js";
|
|
18
|
+
|
|
12
19
|
import ContainerSection from "$lib/components/ContainerSection.svelte";
|
|
13
20
|
import Footer from "$lib/components/layout/Footer.svelte";
|
|
14
21
|
import HeaderDefault from "$lib/components/layout/HeaderDefault.svelte";
|
|
15
22
|
import HeaderHome from "$lib/components/layout/HeaderHome.svelte";
|
|
16
23
|
import PWAInstallButton from "$lib/components/PWAInstallButton.svelte";
|
|
17
|
-
|
|
18
|
-
import { onMount } from "svelte";
|
|
19
|
-
import { registerServiceWorker } from "$lib/registerServiceWorker.js";
|
|
20
|
-
import { browser } from "$app/environment";
|
|
24
|
+
|
|
21
25
|
import "$lib/styles/global.min.css";
|
|
22
26
|
import "$lib/styles/fa-global.css";
|
|
23
27
|
|
|
24
|
-
// Import favicon images
|
|
25
28
|
import logoPng from "$lib/img/logo-web.png";
|
|
26
29
|
import logoWbp from "$lib/img/logo-web.webp";
|
|
27
30
|
import faviconSvg from "$lib/img/favicon.svg";
|
|
28
31
|
import appleTouchIcon from "$lib/img/icon-180x180.png";
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
/** @type {typeof import('$lib/components/PostHog.svelte').default | null} */
|
|
32
|
-
let PostHog = null;
|
|
33
|
+
$: shouldShowReminder = $showReminder;
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
[logoPng, logoWbp, appleTouchIcon].forEach((src) => {
|
|
37
|
-
const img = new Image();
|
|
38
|
-
img.src = src;
|
|
39
|
-
});
|
|
35
|
+
onMount(() => {
|
|
36
|
+
console.log("[APP] onMount triggered in +layout.svelte");
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
registerServiceWorker();
|
|
39
|
+
initPostHog();
|
|
40
|
+
|
|
41
|
+
// Register navigation tracking only on client
|
|
42
|
+
afterNavigate(() => {
|
|
43
|
+
capture("$pageview");
|
|
44
|
+
});
|
|
45
45
|
|
|
46
|
+
if (browser) {
|
|
46
47
|
const isDev = import.meta.env.MODE === "development";
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (isDev ||
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log("[Dev] ✅ PostHog component loaded (tracking enabled)");
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
} else {
|
|
61
|
-
console.log(
|
|
62
|
-
"[Privacy] ⛔ Skipping PostHog component due to DNT or GPC signal.",
|
|
63
|
-
);
|
|
49
|
+
// Check for ?debug=true in URL (no persistence)
|
|
50
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
51
|
+
const debug = urlParams.get("debug") === "true";
|
|
52
|
+
|
|
53
|
+
if (isDev || debug) {
|
|
54
|
+
console.log("ENV MODE =", import.meta.env.MODE);
|
|
55
|
+
console.log("isDev =", isDev);
|
|
56
|
+
console.log("debug param =", debug);
|
|
57
|
+
console.log("shouldTrackUser =", shouldTrackUser()); // Now called statically
|
|
64
58
|
}
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
|
|
60
|
+
// Preload logo assets
|
|
61
|
+
[logoPng, logoWbp, appleTouchIcon].forEach((src) => {
|
|
62
|
+
const img = new Image();
|
|
63
|
+
img.src = src;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
67
|
|
|
68
68
|
// fallback values if data.meta not set
|
|
69
69
|
const metaTitle =
|
|
@@ -103,10 +103,6 @@ This file is part of Network Pro.
|
|
|
103
103
|
</header>
|
|
104
104
|
<!-- END HEADER -->
|
|
105
105
|
|
|
106
|
-
{#if PostHog}
|
|
107
|
-
<PostHog /> <!-- Add PostHog component when it's loaded -->
|
|
108
|
-
{/if}
|
|
109
|
-
|
|
110
106
|
<main>
|
|
111
107
|
<slot />
|
|
112
108
|
</main>
|
package/static/offline.min.css
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/*! ==========================================================================
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
4
3
|
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
4
|
This file is part of Network Pro.
|
|
6
5
|
=========================================================================== */
|
|
7
|
-
html,body{height:100%;margin:0;padding:0}body{color:#fafafa;text-align:center;background-color:#191919;flex-direction:column;justify-content:center;align-items:center;margin:10px;padding:0 1rem;font-family:Arial,Helvetica,sans-serif;display:flex}.container{max-width:600px;margin:0 auto}h1{color:#fafafa;margin-bottom:1rem;font-size:2rem}p{margin-bottom:1.5rem;font-size:1.1rem;line-height:1.5}.icon{color:#ff5252;margin-bottom:1rem;font-size:4rem}.retry-button{color:#fafafa;cursor:pointer;background-color:#2e7d32;border:none;border-radius:4px;margin-top:1rem;padding:12px 24px;font-family:Arial,Helvetica,sans-serif;font-size:1rem;transition:background-color .2s}.retry-button:hover{background-color:#388e3c}.status{color:#bdbdbd;background-color:#ffffff0d;border-radius:4px;margin:1rem 0;padding:1rem;font-size:.9rem}a{text-decoration:none}a:link{color:#ffc627}a:hover,a:active{color:#ffc627;text-decoration:underline}a:focus{color:#191919;background-color:#ffc627}a:visited,a:visited:hover{color:#7f6227}.help-text{color:#bdbdbd;margin-top:2rem;font-size:.9rem}
|
|
6
|
+
html,body{height:100%;margin:0;padding:0}body{color:#fafafa;text-align:center;background-color:#191919;flex-direction:column;justify-content:center;align-items:center;margin:10px;padding:0 1rem;font-family:Arial,Helvetica,sans-serif;display:flex}.container{max-width:600px;margin:0 auto}h1{color:#fafafa;margin-bottom:1rem;font-size:2rem}p{margin-bottom:1.5rem;font-size:1.1rem;line-height:1.5}.icon{color:#ff5252;margin-bottom:1rem;font-size:4rem}.retry-button{color:#fafafa;cursor:pointer;background-color:#2e7d32;border:none;border-radius:4px;margin-top:1rem;padding:12px 24px;font-family:Arial,Helvetica,sans-serif;font-size:1rem;transition:background-color .2s}.retry-button:hover{background-color:#388e3c}.status{color:#bdbdbd;background-color:#ffffff0d;border-radius:4px;margin:1rem 0;padding:1rem;font-size:.9rem}a{text-decoration:none}a:link{color:#ffc627}a:hover,a:active{color:#ffc627;text-decoration:underline}a:focus{color:#191919;background-color:#ffc627}a:visited,a:visited:hover{color:#7f6227}.help-text{color:#bdbdbd;margin-top:2rem;font-size:.9rem}
|
package/svelte.config.js
CHANGED
|
@@ -23,13 +23,15 @@ const config = {
|
|
|
23
23
|
kit: {
|
|
24
24
|
// Netlify adapter configuration
|
|
25
25
|
adapter: adapter({
|
|
26
|
-
edge: false, // Disable edge functions
|
|
27
|
-
split: false, // Disable splitting function files (optional, enable if needed)
|
|
26
|
+
edge: false, // Disable edge functions for site
|
|
27
|
+
split: false, // Disable splitting function files (optional, enable if needed)
|
|
28
28
|
}),
|
|
29
|
+
|
|
29
30
|
// Paths configuration for deployment
|
|
30
31
|
paths: {
|
|
31
32
|
base: "", // Always deploy to the root of the domain
|
|
32
33
|
},
|
|
34
|
+
|
|
33
35
|
prerender: {
|
|
34
36
|
// Handle HTTP errors during prerendering
|
|
35
37
|
handleHttpError: ({ path, _referrer, message }) => {
|
|
@@ -46,6 +48,7 @@ const config = {
|
|
|
46
48
|
},
|
|
47
49
|
},
|
|
48
50
|
},
|
|
51
|
+
|
|
49
52
|
// File extensions for Svelte and mdsvex
|
|
50
53
|
extensions: [".svelte", ".svx", ".md"], // Added .md for Markdown support
|
|
51
54
|
};
|
package/vite.config.js
CHANGED
|
@@ -9,19 +9,17 @@ This file is part of Network Pro.
|
|
|
9
9
|
import { sveltekit } from "@sveltejs/kit/vite";
|
|
10
10
|
import { defineConfig } from "vite";
|
|
11
11
|
import lightningcssPlugin from "vite-plugin-lightningcss";
|
|
12
|
+
import tsconfigPaths from "vite-tsconfig-paths"; // NEW: tsconfig/jsconfig alias support
|
|
12
13
|
|
|
13
14
|
export default defineConfig({
|
|
14
15
|
plugins: [
|
|
16
|
+
tsconfigPaths(), // Insert before sveltekit()
|
|
15
17
|
sveltekit(),
|
|
16
18
|
lightningcssPlugin({
|
|
17
19
|
minify: process.env.NODE_ENV === "production",
|
|
18
20
|
pruneUnusedFontFaceRules: true,
|
|
19
21
|
pruneUnusedKeyframes: true,
|
|
20
22
|
removeUnusedFontFaces: true,
|
|
21
|
-
// Enables nesting support in Lightning CSS
|
|
22
|
-
//drafts: {
|
|
23
|
-
// nesting: true
|
|
24
|
-
//}
|
|
25
23
|
}),
|
|
26
24
|
],
|
|
27
25
|
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<!-- ==========================================================================
|
|
2
|
-
src/lib/components/PostHog.svelte
|
|
3
|
-
|
|
4
|
-
Copyright © 2025 Network Pro Strategies (Network Pro™)
|
|
5
|
-
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
6
|
-
This file is part of Network Pro.
|
|
7
|
-
========================================================================== -->
|
|
8
|
-
|
|
9
|
-
<script>
|
|
10
|
-
import posthog from "posthog-js";
|
|
11
|
-
import { onMount } from "svelte";
|
|
12
|
-
import { browser } from "$app/environment";
|
|
13
|
-
import { shouldTrackUser } from "$lib/utils/privacy.js";
|
|
14
|
-
|
|
15
|
-
onMount(() => {
|
|
16
|
-
if (!browser) return;
|
|
17
|
-
|
|
18
|
-
const allowTracking = shouldTrackUser();
|
|
19
|
-
|
|
20
|
-
// cspell:disable-next-line
|
|
21
|
-
posthog.init("phc_Qshfo6AXzh4pS7aPigfqyeo4qj1qlyh7gDuHDeVMSR0", {
|
|
22
|
-
api_host: "https://us.i.posthog.com",
|
|
23
|
-
loaded: (ph) => {
|
|
24
|
-
if (!allowTracking) {
|
|
25
|
-
console.log("[PostHog] ⛔ User opted out — disabling tracking");
|
|
26
|
-
ph.opt_out_capturing(); // Fully disable any tracking
|
|
27
|
-
} else {
|
|
28
|
-
console.log("[PostHog] ✅ Tracking enabled");
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
autocapture: allowTracking, // Optional: Disable autocapture
|
|
32
|
-
capture_pageview: allowTracking, // Optional: Disable initial pageview
|
|
33
|
-
person_profiles: "identified_only",
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
</script>
|