@networkpro/web 1.10.0 → 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 +5 -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/foss/FossItemContent.svelte +8 -6
- package/src/lib/components/layout/Footer.svelte +26 -46
- package/src/lib/index.js +27 -19
- package/src/lib/pages/AboutContent.svelte +17 -29
- package/src/lib/pages/FossContent.svelte +8 -13
- package/src/lib/pages/HomeContent.svelte +13 -25
- package/src/lib/pages/LicenseContent.svelte +69 -90
- package/src/lib/pages/PrivacyContent.svelte +102 -85
- package/src/lib/pages/PrivacyDashboard.svelte +79 -75
- package/src/lib/pages/TermsConditionsContent.svelte +13 -18
- package/src/lib/pages/TermsUseContent.svelte +17 -23
- 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
|
@@ -15,9 +15,9 @@ This file is part of Network Pro.
|
|
|
15
15
|
// Log the base path to verify its value
|
|
16
16
|
//console.log("Base path:", base);
|
|
17
17
|
|
|
18
|
-
console.log(CONSTANTS.APP_NAME);
|
|
18
|
+
console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
|
|
19
19
|
|
|
20
|
-
const {
|
|
20
|
+
const { COMPANY_INFO, PAGE, NAV } = CONSTANTS;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* URL to Terms and Conditions page, using the base path
|
|
@@ -62,15 +62,11 @@ This file is part of Network Pro.
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Constants for reusable content
|
|
65
|
-
* @type {{ effectiveDate: string, classSmall: string
|
|
65
|
+
* @type {{ effectiveDate: string, classSmall: string }}
|
|
66
66
|
*/
|
|
67
67
|
const constants = {
|
|
68
68
|
effectiveDate: "May 21, 2025",
|
|
69
69
|
classSmall: "small-text",
|
|
70
|
-
rel: "noopener noreferrer",
|
|
71
|
-
hrefTop: "#top",
|
|
72
|
-
targetBlank: "_blank",
|
|
73
|
-
targetSelf: "_self",
|
|
74
70
|
};
|
|
75
71
|
</script>
|
|
76
72
|
|
|
@@ -78,9 +74,9 @@ This file is part of Network Pro.
|
|
|
78
74
|
<section id="top">
|
|
79
75
|
<span class={constants.classSmall}>
|
|
80
76
|
<a
|
|
81
|
-
rel={
|
|
77
|
+
rel={PAGE.REL}
|
|
82
78
|
href="https://spdx.dev/learn/handling-license-info"
|
|
83
|
-
target={
|
|
79
|
+
target={PAGE.BLANK}>
|
|
84
80
|
SPDX License Identifier
|
|
85
81
|
</a>: <code>CC-BY-4.0 OR GPL-3.0-or-later</code>
|
|
86
82
|
</span>
|
|
@@ -89,7 +85,7 @@ This file is part of Network Pro.
|
|
|
89
85
|
<section id="page-title">
|
|
90
86
|
<h1>Website Terms of Use</h1>
|
|
91
87
|
<p>
|
|
92
|
-
<strong>{
|
|
88
|
+
<strong>{COMPANY_INFO.NAME}</strong><br />
|
|
93
89
|
<strong>Effective Date:</strong>
|
|
94
90
|
{constants.effectiveDate}
|
|
95
91
|
</p>
|
|
@@ -113,7 +109,7 @@ This file is part of Network Pro.
|
|
|
113
109
|
available via this website and its associated web properties. For provisions
|
|
114
110
|
governing our consulting and implementation services, please refer to the
|
|
115
111
|
applicable
|
|
116
|
-
<a href={tandcLink} target={
|
|
112
|
+
<a href={tandcLink} target={PAGE.SELF}>Terms and Conditions</a>.
|
|
117
113
|
</p>
|
|
118
114
|
|
|
119
115
|
<hr />
|
|
@@ -124,8 +120,7 @@ This file is part of Network Pro.
|
|
|
124
120
|
<strong>Formats Available:</strong> <span class="visited"
|
|
125
121
|
>HTML</span>
|
|
126
122
|
|
|
|
127
|
-
<a rel={
|
|
128
|
-
>Docs</a>
|
|
123
|
+
<a rel={PAGE.REL} href={termsLink} target={PAGE.SELF}>Docs</a>
|
|
129
124
|
</sup>
|
|
130
125
|
</p>
|
|
131
126
|
</section>
|
|
@@ -137,7 +132,7 @@ This file is part of Network Pro.
|
|
|
137
132
|
|
|
138
133
|
{#if link.id === "introduction"}
|
|
139
134
|
<p>
|
|
140
|
-
Welcome! By accessing or using any of the platforms operated by {
|
|
135
|
+
Welcome! By accessing or using any of the platforms operated by {COMPANY_INFO.NAME}
|
|
141
136
|
("Company," "we," "us," or "our"), you agree to be bound by these Terms of
|
|
142
137
|
Use ("Terms"). If you do not agree to these Terms, please refrain from using
|
|
143
138
|
our services.
|
|
@@ -202,10 +197,10 @@ This file is part of Network Pro.
|
|
|
202
197
|
</p>
|
|
203
198
|
{:else if link.id === "jurisdiction"}
|
|
204
199
|
<p>
|
|
205
|
-
{
|
|
206
|
-
arising from these Terms of Use shall be subject to the exclusive
|
|
207
|
-
of the state and federal courts located in Maricopa County,
|
|
208
|
-
Terms shall be governed by the
|
|
200
|
+
{COMPANY_INFO.NAME} is based in Maricopa County, Arizona. Any legal action
|
|
201
|
+
or dispute arising from these Terms of Use shall be subject to the exclusive
|
|
202
|
+
jurisdiction of the state and federal courts located in Maricopa County,
|
|
203
|
+
Arizona. These Terms shall be governed by the
|
|
209
204
|
<strong>Arizona Revised Statutes (A.R.S.)</strong> and applicable
|
|
210
205
|
provisions of the <strong>United States Code (U.S.C.)</strong>.
|
|
211
206
|
</p>
|
|
@@ -224,7 +219,7 @@ This file is part of Network Pro.
|
|
|
224
219
|
{/if}
|
|
225
220
|
|
|
226
221
|
<span class={constants.classSmall}
|
|
227
|
-
><a href={
|
|
222
|
+
><a href={NAV.HREFTOP}>{NAV.BACKTOP}</a></span>
|
|
228
223
|
</section>
|
|
229
224
|
{/each}
|
|
230
225
|
|
|
@@ -232,10 +227,9 @@ This file is part of Network Pro.
|
|
|
232
227
|
<p class="bquote">
|
|
233
228
|
<strong>Note:</strong> For more details regarding our privacy practices,
|
|
234
229
|
refer to our
|
|
235
|
-
<a rel={
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
<a rel={constants.rel} href={licenseLink} target={constants.targetSelf}
|
|
230
|
+
<a rel={PAGE.REL} href={privacyLink} target={PAGE.SELF}>Privacy Policy</a>.
|
|
231
|
+
For licensing terms and content usage rights, please visit our
|
|
232
|
+
<a rel={PAGE.REL} href={licenseLink} target={PAGE.SELF}
|
|
239
233
|
>Legal, Copyright, and Licensing</a>
|
|
240
234
|
page.
|
|
241
235
|
</p>
|
|
@@ -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}
|