From 32c0fc0f1cdd35036b4ced09bcc1abe180ba9906 Mon Sep 17 00:00:00 2001 From: Connor Yoh Date: Wed, 15 Oct 2025 15:36:59 +0100 Subject: [PATCH] Cookie changes --- .../common/model/ApplicationProperties.java | 14 ++ .../common/service/PostHogService.java | 12 +- .../controller/api/SettingsController.java | 4 +- .../controller/api/misc/ConfigController.java | 2 + .../src/main/resources/settings.yml.template | 4 +- .../public/locales/en-GB/translation.json | 27 ++++ frontend/src/App.tsx | 4 + frontend/src/components/layout/Workbench.tsx | 4 +- .../shared/AdminAnalyticsChoiceModal.tsx | 127 ++++++++++++++++++ frontend/src/hooks/useAppConfig.ts | 11 +- frontend/src/hooks/useCookieConsent.ts | 49 +++++-- frontend/src/hooks/useScarfTracking.ts | 45 +++++++ frontend/src/index.tsx | 23 ++-- frontend/src/pages/HomePage.tsx | 12 ++ frontend/src/styles/zIndex.ts | 3 +- frontend/src/utils/scarfTracking.ts | 75 ++++++++++- 16 files changed, 382 insertions(+), 34 deletions(-) create mode 100644 frontend/src/components/shared/AdminAnalyticsChoiceModal.tsx create mode 100644 frontend/src/hooks/useScarfTracking.ts diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 14704d825..d13eb0fbc 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -320,6 +320,8 @@ public class ApplicationProperties { private String tessdataDir; private Boolean enableAlphaFunctionality; private Boolean enableAnalytics; + private Boolean enablePosthog; + private Boolean enableScarf; private Datasource datasource; private Boolean disableSanitize; private int maxDPI; @@ -332,6 +334,18 @@ public class ApplicationProperties { public boolean isAnalyticsEnabled() { return this.getEnableAnalytics() != null && this.getEnableAnalytics(); } + + public boolean isPosthogEnabled() { + // Treat null as enabled when analytics is enabled + return this.isAnalyticsEnabled() + && (this.getEnablePosthog() == null || this.getEnablePosthog()); + } + + public boolean isScarfEnabled() { + // Treat null as enabled when analytics is enabled + return this.isAnalyticsEnabled() + && (this.getEnableScarf() == null || this.getEnableScarf()); + } } @Data diff --git a/app/common/src/main/java/stirling/software/common/service/PostHogService.java b/app/common/src/main/java/stirling/software/common/service/PostHogService.java index 2bc219832..0d6353b50 100644 --- a/app/common/src/main/java/stirling/software/common/service/PostHogService.java +++ b/app/common/src/main/java/stirling/software/common/service/PostHogService.java @@ -56,7 +56,7 @@ public class PostHogService { } private void captureSystemInfo() { - if (!applicationProperties.getSystem().isAnalyticsEnabled()) { + if (!applicationProperties.getSystem().isPosthogEnabled()) { return; } try { @@ -67,7 +67,7 @@ public class PostHogService { } public void captureEvent(String eventName, Map properties) { - if (!applicationProperties.getSystem().isAnalyticsEnabled()) { + if (!applicationProperties.getSystem().isPosthogEnabled()) { return; } @@ -325,6 +325,14 @@ public class PostHogService { properties, "system_enableAnalytics", applicationProperties.getSystem().isAnalyticsEnabled()); + addIfNotEmpty( + properties, + "system_enablePosthog", + applicationProperties.getSystem().isPosthogEnabled()); + addIfNotEmpty( + properties, + "system_enableScarf", + applicationProperties.getSystem().isScarfEnabled()); // Capture UI properties addIfNotEmpty(properties, "ui_appName", applicationProperties.getUi().getAppName()); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java index 3c2e6f33a..48793a98b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java @@ -6,7 +6,7 @@ import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import io.swagger.v3.oas.annotations.Hidden; @@ -29,7 +29,7 @@ public class SettingsController { @AutoJobPostMapping("/update-enable-analytics") @Hidden - public ResponseEntity updateApiKey(@RequestBody Boolean enabled) throws IOException { + public ResponseEntity updateApiKey(@RequestParam Boolean enabled) throws IOException { if (applicationProperties.getSystem().getEnableAnalytics() != null) { return ResponseEntity.status(HttpStatus.ALREADY_REPORTED) .body( diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java index 072471e5c..9d94c3ad4 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java @@ -65,6 +65,8 @@ public class ConfigController { applicationProperties.getSystem().getEnableAlphaFunctionality()); configData.put( "enableAnalytics", applicationProperties.getSystem().getEnableAnalytics()); + configData.put("enablePosthog", applicationProperties.getSystem().getEnablePosthog()); + configData.put("enableScarf", applicationProperties.getSystem().getEnableScarf()); // Premium/Enterprise settings configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled()); diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 8143ba4c2..03348ae2e 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -105,7 +105,9 @@ system: showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true' customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored. - enableAnalytics: null # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true + enableAnalytics: null # Master toggle for analytics: set to 'true' to enable all analytics, 'false' to disable all analytics, or leave as 'null' to prompt admin on first launch + enablePosthog: null # Enable PostHog analytics (open-source product analytics): set to 'true' to enable, 'false' to disable, or 'null' to enable by default when analytics is enabled + enableScarf: null # Enable Scarf tracking pixel: set to 'true' to enable, 'false' to disable, or 'null' to enable by default when analytics is enabled enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML) maxDPI: 500 # Maximum allowed DPI for PDF to image conversion diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 8294d34f5..d74418a8c 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -3092,6 +3092,10 @@ "title": "Analytics", "description": "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with." } + }, + "services": { + "posthog": "PostHog Analytics", + "scarf": "Scarf Pixel" } }, "removeMetadata": { @@ -3617,6 +3621,29 @@ "title": "Attachment Results" } }, + "analytics": { + "modal": { + "title": "Configure Analytics", + "description": "Choose whether to enable analytics for Stirling PDF. If enabled, users can control individual services (PostHog and Scarf) through the cookie preferences.", + "whatWeCollect": "What we collect:", + "collect": { + "system": "Operating system and Java version", + "config": "CPU/memory configuration and deployment type", + "features": "Aggregate feature usage counts", + "pages": "Page visits (via tracking pixel)" + }, + "whatWeDoNotCollect": "What we do NOT collect:", + "notCollect": { + "documents": "Document content or file data", + "pii": "Personally identifiable information (PII)", + "ip": "IP addresses" + }, + "privacy": "All analytics data is hosted on EU servers and respects your privacy.", + "enable": "Enable Analytics", + "disable": "Disable Analytics", + "note": "This choice can be changed later by editing the settings.yml file." + } + }, "termsAndConditions": "Terms & Conditions", "logOut": "Log out" } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 88c19649f..cea3da359 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,6 +9,7 @@ import { SidebarProvider } from "./contexts/SidebarContext"; import { PreferencesProvider } from "./contexts/PreferencesContext"; import ErrorBoundary from "./components/shared/ErrorBoundary"; import HomePage from "./pages/HomePage"; +import { useScarfTracking } from "./hooks/useScarfTracking"; // Import global styles import "./styles/tailwind.css"; @@ -38,6 +39,9 @@ const LoadingFallback = () => ( ); export default function App() { + // Initialize scarf tracking (mounts once at app startup) + useScarfTracking(); + return ( }> diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index 62fc48667..f2ea13d21 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -6,6 +6,7 @@ import { useFileHandler } from '../../hooks/useFileHandler'; import { useFileState } from '../../contexts/FileContext'; import { useNavigationState, useNavigationActions } from '../../contexts/NavigationContext'; import { useViewer } from '../../contexts/ViewerContext'; +import { useAppConfig } from '../../hooks/useAppConfig'; import './Workbench.css'; import TopControls from '../shared/TopControls'; @@ -20,6 +21,7 @@ import DismissAllErrorsButton from '../shared/DismissAllErrorsButton'; // No props needed - component uses contexts directly export default function Workbench() { const { isRainbowMode } = useRainbowThemeContext(); + const { config } = useAppConfig(); // Use context-based hooks to eliminate all prop drilling const { selectors } = useFileState(); @@ -180,7 +182,7 @@ export default function Workbench() { {renderMainContent()} -