From 68c3c45da37d3d31e05bfe37613235d6b86977cd Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Sun, 25 May 2025 13:48:57 +0800 Subject: [PATCH] feat: add an option to opt-out telemetry (#1236) --- apps/readest-app/package.json | 2 +- .../public/locales/ar/translation.json | 4 +- .../public/locales/de/translation.json | 4 +- .../public/locales/el/translation.json | 4 +- .../public/locales/es/translation.json | 4 +- .../public/locales/fr/translation.json | 4 +- .../public/locales/hi/translation.json | 4 +- .../public/locales/id/translation.json | 4 +- .../public/locales/it/translation.json | 4 +- .../public/locales/ja/translation.json | 4 +- .../public/locales/ko/translation.json | 4 +- .../public/locales/nl/translation.json | 4 +- .../public/locales/pl/translation.json | 4 +- .../public/locales/pt/translation.json | 4 +- .../public/locales/ru/translation.json | 4 +- .../public/locales/tr/translation.json | 4 +- .../public/locales/uk/translation.json | 4 +- .../public/locales/vi/translation.json | 4 +- .../public/locales/zh-CN/translation.json | 4 +- .../public/locales/zh-TW/translation.json | 4 +- .../app/library/components/SettingsMenu.tsx | 20 ++++++ apps/readest-app/src/components/MenuItem.tsx | 62 ++++++++++++------- apps/readest-app/src/context/PHContext.tsx | 11 +++- apps/readest-app/src/services/constants.ts | 1 + apps/readest-app/src/types/settings.ts | 1 + apps/readest-app/src/utils/telemetry.ts | 22 +++++++ pnpm-lock.yaml | 18 ++++-- 27 files changed, 163 insertions(+), 50 deletions(-) create mode 100644 apps/readest-app/src/utils/telemetry.ts diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index efd5dce7..f0d11ab1 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -68,7 +68,7 @@ "js-md5": "^0.8.3", "jwt-decode": "^4.0.0", "next": "15.2.4", - "posthog-js": "^1.205.0", + "posthog-js": "^1.246.0", "react": "19.0.0", "react-color": "^2.19.3", "react-dom": "19.0.0", diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index ebc07a1c..bde3129e 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -325,5 +325,7 @@ "DeepL": "ديب إل", "Google Translate": "ترجمة جوجل", "Azure Translator": "مترجم أزور", - "Invert Image In Dark Mode": "عكس الصورة في الوضع المظلم" + "Invert Image In Dark Mode": "عكس الصورة في الوضع المظلم", + "Help improve Readest": "ساعد في تحسين ريديست", + "Sharing anonymized statistics": "مشاركة إحصائيات مجهولة الهوية" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 146ae678..d290db2b 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -321,5 +321,7 @@ "DeepL": "DeepL", "Google Translate": "Google Übersetzer", "Azure Translator": "Azure Übersetzer", - "Invert Image In Dark Mode": "Invertiere Bild im Dunkeln" + "Invert Image In Dark Mode": "Invertiere Bild im Dunkeln", + "Help improve Readest": "Readest verbessern helfen", + "Sharing anonymized statistics": "Anonymisierte Statistiken teilen" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index de7ebbb0..4c1f1bcb 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -321,5 +321,7 @@ "DeepL": "DeepL", "Google Translate": "Μετάφραση Google", "Azure Translator": "Μεταφραστής Azure", - "Invert Image In Dark Mode": "Αντιστροφή σε σκούρο" + "Invert Image In Dark Mode": "Αντιστροφή σε σκούρο", + "Help improve Readest": "Βοηθήστε στη βελτίωση του Readest", + "Sharing anonymized statistics": "Μοιράζομαι ανώνυμες στατιστικές" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 5d7e93fd..2e287ad3 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -322,5 +322,7 @@ "DeepL": "DeepL", "Google Translate": "Google Translate", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "Invertir imagen en oscuro" + "Invert Image In Dark Mode": "Invertir imagen en oscuro", + "Help improve Readest": "Ayuda a mejorar Readest", + "Sharing anonymized statistics": "Compartiendo estadísticas anónimas" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index fd1be275..25b9f3d2 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -322,5 +322,7 @@ "DeepL": "DeepL", "Google Translate": "Google Traduction", "Azure Translator": "Traducteur Azure", - "Invert Image In Dark Mode": "Inverser l'image en mode sombre" + "Invert Image In Dark Mode": "Inverser l'image en mode sombre", + "Help improve Readest": "Contribuer à améliorer Readest", + "Sharing anonymized statistics": "Partage de statistiques anonymisées" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index acc71269..c7646df9 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -321,5 +321,7 @@ "DeepL": "डीपएल", "Google Translate": "गूगल अनुवाद", "Azure Translator": "एज़्योर अनुवादक", - "Invert Image In Dark Mode": "अंधेरे में छवि को उलटें" + "Invert Image In Dark Mode": "अंधेरे में छवि को उलटें", + "Help improve Readest": "Readest में सुधार करने में मदद करें", + "Sharing anonymized statistics": "सांख्यिकी साझा करना" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index f5b01d94..608126c3 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -320,5 +320,7 @@ "DeepL": "DeepL", "Google Translate": "Google Translate", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "Inversi Gambar di Gelap" + "Invert Image In Dark Mode": "Inversi Gambar di Gelap", + "Help improve Readest": "Bantu tingkatkan Readest", + "Sharing anonymized statistics": "Berbagi statistik anonim" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 1f1905e4..ab88bc62 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -322,5 +322,7 @@ "DeepL": "DeepL", "Google Translate": "Google Translate", "Azure Translator": "Traduttore Azure", - "Invert Image In Dark Mode": "Inverti immagine in scuro" + "Invert Image In Dark Mode": "Inverti immagine in scuro", + "Help improve Readest": "Aiuta a migliorare Readest", + "Sharing anonymized statistics": "Condivisione di statistiche anonime" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 7f15352f..a23121aa 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -320,5 +320,7 @@ "DeepL": "DeepL", "Google Translate": "Google翻訳", "Azure Translator": "Azure翻訳", - "Invert Image In Dark Mode": "ダークモードで画像を反転" + "Invert Image In Dark Mode": "ダークモードで画像を反転", + "Help improve Readest": "Readestを改善する手助け", + "Sharing anonymized statistics": "匿名化された統計情報を共有" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index df82cadd..ec4a96d4 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -320,5 +320,7 @@ "DeepL": "DeepL", "Google Translate": "구글 번역", "Azure Translator": "Azure 번역기", - "Invert Image In Dark Mode": "어두운 곳에서 이미지 반전" + "Invert Image In Dark Mode": "어두운 곳에서 이미지 반전", + "Help improve Readest": "Readest 개선에 도움을 주세요", + "Sharing anonymized statistics": "익명화된 통계 공유" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index f423fb8a..d4be7fb7 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -321,5 +321,7 @@ "DeepL": "DeepL", "Google Translate": "Google Vertalen", "Azure Translator": "Azure Vertaler", - "Invert Image In Dark Mode": "Inverteer afbeelding in het donker" + "Invert Image In Dark Mode": "Inverteer afbeelding in het donker", + "Help improve Readest": "Help Readest verbeteren", + "Sharing anonymized statistics": "Anonieme statistieken delen" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 4b62e34d..aa6612d0 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -323,5 +323,7 @@ "DeepL": "DeepL", "Google Translate": "Tłumacz Google", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "Odwróć obraz w ciemności" + "Invert Image In Dark Mode": "Odwróć obraz w ciemności", + "Help improve Readest": "Pomóż ulepszyć Readest", + "Sharing anonymized statistics": "Udostępnianie zanonimizowanych statystyk" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index bc9257f8..d29e0d64 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -322,5 +322,7 @@ "DeepL": "DeepL", "Google Translate": "Google Tradutor", "Azure Translator": "Tradutor Azure", - "Invert Image In Dark Mode": "Inverter Imagem no Escuro" + "Invert Image In Dark Mode": "Inverter Imagem no Escuro", + "Help improve Readest": "Ajude a melhorar o Readest", + "Sharing anonymized statistics": "Compartilhando estatísticas anonimizadas" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 64428a96..eb66e37c 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -323,5 +323,7 @@ "DeepL": "DeepL", "Google Translate": "Google Translate", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "Инвертировать в тёмной теме" + "Invert Image In Dark Mode": "Инвертировать в тёмной теме", + "Help improve Readest": "Помогите улучшить Readest", + "Sharing anonymized statistics": "Отправка анонимной статистики" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 3a9b2cb6..87728f9f 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -321,5 +321,7 @@ "DeepL": "DeepL", "Google Translate": "Google Çeviri", "Azure Translator": "Azure Çevirmen", - "Invert Image In Dark Mode": "Karartılmış Görüntüyü Ters Çevir" + "Invert Image In Dark Mode": "Karartılmış Görüntüyü Ters Çevir", + "Help improve Readest": "Readest’i geliştirin", + "Sharing anonymized statistics": "Anonim verileri paylaş" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index cebcc139..9f37fce0 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -323,5 +323,7 @@ "DeepL": "DeepL", "Google Translate": "Google Translate", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "Інвертувати зображення в темряві" + "Invert Image In Dark Mode": "Інвертувати зображення в темряві", + "Help improve Readest": "Допоможіть покращити Readest", + "Sharing anonymized statistics": "Поділ анонімною статистикою" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 43dd6f54..046bbb53 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -320,5 +320,7 @@ "DeepL": "DeepL", "Google Translate": "Google Dịch", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "Đảo ảnh chế độ tối" + "Invert Image In Dark Mode": "Đảo ảnh chế độ tối", + "Help improve Readest": "Giúp cải thiện Readest", + "Sharing anonymized statistics": "Chia sẻ thống kê ẩn danh" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index cf47eed4..09a48a8a 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -320,5 +320,7 @@ "DeepL": "DeepL", "Google Translate": "Google Translate", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "深色主题下图片反色" + "Invert Image In Dark Mode": "深色主题下图片反色", + "Help improve Readest": "帮助改进 Readest", + "Sharing anonymized statistics": "分享匿名统计数据" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index db7563e0..8c95d39f 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -320,5 +320,7 @@ "DeepL": "DeepL", "Google Translate": "Google Translate", "Azure Translator": "Azure Translator", - "Invert Image In Dark Mode": "深色主題下圖片反相" + "Invert Image In Dark Mode": "深色主題下圖片反相", + "Help improve Readest": "幫助改進 Readest", + "Sharing anonymized statistics": "分享匿名統計數據" } diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index 5959cf20..0c4aeaf5 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -16,6 +16,7 @@ import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { getStoragePlanData } from '@/utils/access'; import { navigateToLogin, navigateToProfile } from '@/utils/nav'; import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen } from '@/utils/window'; +import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry'; import { QuotaType } from '@/types/user'; import UserAvatar from '@/components/UserAvatar'; import MenuItem from '@/components/MenuItem'; @@ -40,6 +41,7 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { const [isAutoImportBooksOnOpen, setIsAutoImportBooksOnOpen] = useState( settings.autoImportBooksOnOpen, ); + const [isTelemetryEnabled, setIsTelemetryEnabled] = useState(settings.telemetryEnabled); const iconSize = useResponsiveSize(16); const showAboutReadest = () => { @@ -119,6 +121,18 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { setIsOpenLastBooks(settings.openLastBooks); }; + const toggleTelemetry = () => { + settings.telemetryEnabled = !settings.telemetryEnabled; + if (settings.telemetryEnabled) { + optInTelemetry(); + } else { + optOutTelemetry(); + } + setSettings(settings); + saveSettings(envConfig, settings); + setIsTelemetryEnabled(settings.telemetryEnabled); + }; + useEffect(() => { if (!user || !token) return; const storagPlan = getStoragePlanData(token); @@ -215,6 +229,12 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => {
{isWebAppPlatform() && } + ); }; diff --git a/apps/readest-app/src/components/MenuItem.tsx b/apps/readest-app/src/components/MenuItem.tsx index 0887ead4..2906ea27 100644 --- a/apps/readest-app/src/components/MenuItem.tsx +++ b/apps/readest-app/src/components/MenuItem.tsx @@ -5,6 +5,7 @@ import { useResponsiveSize } from '@/hooks/useResponsiveSize'; interface MenuItemProps { label: string; + description?: string; buttonClass?: string; labelClass?: string; shortcut?: string; @@ -17,6 +18,7 @@ interface MenuItemProps { const MenuItem: React.FC = ({ label, + description, buttonClass, labelClass, shortcut, @@ -30,40 +32,52 @@ const MenuItem: React.FC = ({ const menuButton = ( ); diff --git a/apps/readest-app/src/context/PHContext.tsx b/apps/readest-app/src/context/PHContext.tsx index eb2f583b..0d098b73 100644 --- a/apps/readest-app/src/context/PHContext.tsx +++ b/apps/readest-app/src/context/PHContext.tsx @@ -1,7 +1,13 @@ 'use client'; -import { ReactNode } from 'react'; import posthog from 'posthog-js'; +import { ReactNode } from 'react'; import { PostHogProvider } from 'posthog-js/react'; +import { TELEMETRY_OPT_OUT_KEY } from '@/utils/telemetry'; + +const shouldDisablePostHog = () => { + if (typeof window === 'undefined') return false; + return localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === 'true'; +}; if ( typeof window !== 'undefined' && @@ -12,6 +18,9 @@ if ( api_host: process.env['NEXT_PUBLIC_POSTHOG_HOST'], person_profiles: 'always', }); + if (shouldDisablePostHog()) { + posthog.opt_out_capturing(); + } } export const CSPostHogProvider = ({ children }: { children: ReactNode }) => { return {children}; diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 64b4001d..9b4eab14 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -41,6 +41,7 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial = { openLastBooks: false, lastOpenBooks: [], autoImportBooksOnOpen: false, + telemetryEnabled: true, libraryViewMode: 'grid', librarySortBy: 'updated', librarySortAscending: false, diff --git a/apps/readest-app/src/types/settings.ts b/apps/readest-app/src/types/settings.ts index 537fcf68..0b2a520e 100644 --- a/apps/readest-app/src/types/settings.ts +++ b/apps/readest-app/src/types/settings.ts @@ -31,6 +31,7 @@ export interface SystemSettings { openLastBooks: boolean; lastOpenBooks: string[]; autoImportBooksOnOpen: boolean; + telemetryEnabled: boolean; libraryViewMode: LibraryViewModeType; librarySortBy: LibrarySortByType; librarySortAscending: boolean; diff --git a/apps/readest-app/src/utils/telemetry.ts b/apps/readest-app/src/utils/telemetry.ts new file mode 100644 index 00000000..08813406 --- /dev/null +++ b/apps/readest-app/src/utils/telemetry.ts @@ -0,0 +1,22 @@ +import posthog from 'posthog-js'; + +export const TELEMETRY_OPT_OUT_KEY = 'readest-telemetry-opt-out'; + +export const hasOptedOutTelemetry = () => { + return localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === 'true'; +}; + +export const captureEvent = (event: string, properties?: Record) => { + if (!hasOptedOutTelemetry()) { + posthog.capture(event, properties); + } +}; + +export const optInTelemetry = () => { + localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'false'); + posthog.opt_in_capturing(); +}; +export const optOutTelemetry = () => { + localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'true'); + posthog.opt_out_capturing(); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc2009e9..0532056d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,8 +135,8 @@ importers: specifier: 15.2.4 version: 15.2.4(@babel/core@7.26.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) posthog-js: - specifier: ^1.205.0 - version: 1.205.0 + specifier: ^1.246.0 + version: 1.246.0 react: specifier: 19.0.0 version: 19.0.0 @@ -4877,8 +4877,16 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - posthog-js@1.205.0: - resolution: {integrity: sha512-zP4SQ9Dg9JwqkEteoAOviAAAMdT/nJ4vk1jqfE6fVudziEa3szkQWd7czk5ehlEdrKFUE85MonCKW4L/uwtybA==} + posthog-js@1.246.0: + resolution: {integrity: sha512-5lN/UMqDfxsLeSnT3LsY4P+eD1H+P9qxgN/iUk473LmhCM7IV8TAfdjJj23nnXk/euGNQtvyINYoD2DG+d4eEw==} + peerDependencies: + '@rrweb/types': 2.0.0-alpha.17 + rrweb-snapshot: 2.0.0-alpha.17 + peerDependenciesMeta: + '@rrweb/types': + optional: true + rrweb-snapshot: + optional: true preact@10.25.0: resolution: {integrity: sha512-6bYnzlLxXV3OSpUxLdaxBmE7PMOu0aR3pG6lryK/0jmvcDFPlcXGQAt5DpK3RITWiDrfYZRI0druyaK/S9kYLg==} @@ -11851,7 +11859,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - posthog-js@1.205.0: + posthog-js@1.246.0: dependencies: core-js: 3.39.0 fflate: 0.4.8