From cecca8ffe5519b5af9213995acebca6736afb81a Mon Sep 17 00:00:00 2001 From: ssbg04 Date: Sat, 24 Oct 2026 11:03:29 +0800 Subject: [PATCH] fix(compliance): align privacy policy, dynamic adsense loading, and csp enforcement --- index.html | 2 -- public/_headers | 2 +- src/components/AdSlot.tsx | 19 +++++++++--- src/components/PrivacyModal.tsx | 53 +++++++++++++++++++++++++-------- vercel.json | 4 +-- 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/index.html b/index.html index 4335371..435f6a6 100644 --- a/index.html +++ b/index.html @@ -11,8 +11,6 @@ - diff --git a/public/_headers b/public/_headers index 4743534..f8af745 100644 --- a/public/_headers +++ b/public/_headers @@ -3,4 +3,4 @@ X-Frame-Options: DENY Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() - Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net https://ep2.adtrafficquality.google blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data: blob: https:; media-src 'self' data: blob: https:; connect-src 'self' https: blob: data:; frame-src 'self' https://googleads.g.doubleclick.net https://tpc.googlesyndication.com https://www.google.com https://ep2.adtrafficquality.google; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; + Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net https://ep2.adtrafficquality.google https://va.vercel-scripts.com blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data: blob: https:; media-src 'self' data: blob: https:; connect-src 'self' https: blob: data:; frame-src 'self' https://googleads.g.doubleclick.net https://tpc.googlesyndication.com https://www.google.com https://ep2.adtrafficquality.google; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; diff --git a/src/components/AdSlot.tsx b/src/components/AdSlot.tsx index f8520c2..515e9ed 100644 --- a/src/components/AdSlot.tsx +++ b/src/components/AdSlot.tsx @@ -26,6 +26,18 @@ export const AdSlot: React.FC = ({ placement, className = '' }) => useEffect(() => { if (!canRenderAd || !adRef.current || typeof window === 'undefined') return; + + const SCRIPT_ID = 'google-adsense-script'; + let script = document.getElementById(SCRIPT_ID) as HTMLScriptElement | null; + if (!script) { + script = document.createElement('script'); + script.id = SCRIPT_ID; + script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${AD_CONFIG.googleClient}`; + script.async = true; + script.crossOrigin = 'anonymous'; + document.head.appendChild(script); + } + const pushAd = () => { try { (window.adsbygoogle = window.adsbygoogle || []).push({}); @@ -34,12 +46,11 @@ export const AdSlot: React.FC = ({ placement, className = '' }) => } }; - const scriptExists = !!document.querySelector('script[src*="adsbygoogle.js"]'); - if (scriptExists) { + if (window.adsbygoogle) { pushAd(); } else { - // Wait a short moment for the AdSense script to load, then try again - window.setTimeout(pushAd, 250); + script.addEventListener('load', pushAd, { once: true }); + window.setTimeout(pushAd, 300); } }, [canRenderAd]); diff --git a/src/components/PrivacyModal.tsx b/src/components/PrivacyModal.tsx index cb85f8f..109df21 100644 --- a/src/components/PrivacyModal.tsx +++ b/src/components/PrivacyModal.tsx @@ -114,9 +114,9 @@ export const PrivacyModal: React.FC = ({ isOpen, onClose }) = All conversions—including image manipulation, PDF generation, JSON/YAML parsing, Base64 encoding, and UUID generation—run entirely inside your browser's local sandbox.

    -
  • Your files and text inputs are never uploaded to our servers or third-party cloud storage.
  • +
  • Your files and text inputs are processed locally and are never uploaded to our servers or third-party cloud storage.
  • Calculations utilize HTML5 Canvas, crypto.randomUUID(), and in-memory Web APIs.
  • -
  • Data is automatically discarded from browser memory as soon as you clear the input or refresh the page.
  • +
  • Active file memory is automatically cleared when you clear your inputs or leave the tool.
@@ -124,23 +124,41 @@ export const PrivacyModal: React.FC = ({ isOpen, onClose }) =
-

2. Privacy-Respecting Advertising (Google AdSense)

+

2. Advertising & Third-Party Partners (Google AdSense)

- To keep all conversion tools free and support the platform, we partner with Google AdSense: + To keep all conversion tools and Philippine calculators 100% free, we partner with Google AdSense to display advertisements:

-
+
- No Tracking Cookies: Google AdSense does not place third‑party cookies for tracking across the web. + Advertising Cookies: Google and its ad partners use cookies or unique device identifiers to serve and personalize ads based on a user's prior visits to this and other websites.
- Contextual Only: Ads are selected based on page content and coarse geographic region, not personal behavior. + User Choice & Opt-Out: You can opt out of personalized advertising at any time by visiting{' '} + + Google Ads Settings + {' '} + or{' '} + + aboutads.info + . +
- No Invasive Scripts: Google AdSense respects "Do Not Track" signals and complies with GDPR/CCPA requirements. + Non-Intrusive Layout: Ad banners are configured in designated non-blocking slots and will not interfere with tool execution.
@@ -149,10 +167,13 @@ export const PrivacyModal: React.FC = ({ isOpen, onClose }) =
-

3. Hosting & Server Infrastructure

+

3. Infrastructure & Analytics

- This site is hosted globally via Vercel's Edge Network. When your browser requests static assets (HTML, CSS, JS), temporary standard server access logs (such as IP address, user-agent, and timestamp) are processed exclusively for security, rate-limiting, and DDoS defense. No analytics tracking pixels (such as Google Analytics or Facebook Pixel) are present. + This site is hosted globally via Vercel's Edge Network. Standard temporary access logs (IP address, user-agent, timestamp) are recorded for security, DDoS protection, and rate-limiting. +

+

+ We also use Vercel Web Analytics to monitor aggregated performance and page views. Vercel Web Analytics is privacy-friendly, does not use tracking cookies, does not track individual user identities across the web, and does not sell your data. We do not use third-party tracking pixels (such as Meta Pixel).

@@ -160,10 +181,18 @@ export const PrivacyModal: React.FC = ({ isOpen, onClose }) =
-

4. Local Storage Usage

+

4. Local Storage Usage & Data Control

- We only use your browser's localStorage to remember your interface preferences (such as dark mode vs light mode). This information never leaves your device. + We use your browser's localStorage exclusively on your device to enhance your experience: +

+
    +
  • Interface Preferences: Preserving your theme choice (dark or light mode) and category layout.
  • +
  • Local Conversion History: Storing up to 20 recent transformations locally so you can review or restore previous results without recalculating.
  • +
  • Unit Pairs: Remembering recently used unit pairs for faster conversion.
  • +
+

+ How to clear: This data never leaves your device and is never uploaded to any server. You can clear your conversion history at any time by clicking "Clear History" in the Activity History drawer, or by clearing your browser's site data/cookies.

diff --git a/vercel.json b/vercel.json index 1ff9bed..f32d34e 100644 --- a/vercel.json +++ b/vercel.json @@ -20,8 +20,8 @@ "value": "camera=(), microphone=(), geolocation=()" }, { - "key": "Content-Security-Policy-Report-Only", - "value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net https://ep2.adtrafficquality.google blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data: blob: https:; media-src 'self' data: blob: https:; connect-src 'self' https: blob: data:; frame-src 'self' https://googleads.g.doubleclick.net https://tpc.googlesyndication.com https://www.google.com; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self';" + "key": "Content-Security-Policy", + "value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net https://ep2.adtrafficquality.google https://va.vercel-scripts.com blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data: blob: https:; media-src 'self' data: blob: https:; connect-src 'self' https: blob: data:; frame-src 'self' https://googleads.g.doubleclick.net https://tpc.googlesyndication.com https://www.google.com https://ep2.adtrafficquality.google; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self';" } ] }