From d876b17f26c22fc3552f1d58a53f46f269f7a44b Mon Sep 17 00:00:00 2001 From: redshift <213178690+sh1ftred@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:43:32 +0000 Subject: [PATCH] Detect Tor Browser and route to .onion URLs (even on clearnet frontend) - Updated utils/torUtils.ts to detect Tor Browser via navigator.userAgent - Added manual Tor mode preference with localStorage (getTorModePreference/setTorModePreference) - Modified isTorContext() to check in order: 1. Manual override from localStorage 2. .onion hostname check (existing behavior) 3. Tor Browser detection via user agent (new) Benefits: - Users accessing clearnet URL via Tor Browser now get routed to .onion provider endpoints - Users can manually override Tor mode via localStorage if needed - Maintains backward compatibility with existing .onion hostname detection Technical Details: - Tor Browser user agent contains 'tor' string - Added localStorage key 'routstr_tor_mode' for manual override - Auto-detects Tor Browser when no manual override is set Fixes #159 --- utils/torUtils.ts | 65 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/utils/torUtils.ts b/utils/torUtils.ts index 2063fbfe..ec00130c 100644 --- a/utils/torUtils.ts +++ b/utils/torUtils.ts @@ -7,11 +7,74 @@ export type ProviderDirectoryEntry = { }; const TOR_ONION_SUFFIX = ".onion"; +const TOR_MODE_STORAGE_KEY = "routstr_tor_mode"; +/** + * Detect if the current browser is Tor Browser by checking the user agent + */ +const isTorBrowser = (): boolean => { + if (typeof window === "undefined") return false; + const userAgent = navigator.userAgent.toLowerCase(); + return userAgent.includes("tor"); +}; + +/** + * Get manual Tor mode preference from localStorage + * Returns: true (force Tor), false (force non-Tor), or null (auto-detect) + */ +export const getTorModePreference = (): boolean | null => { + if (typeof window === "undefined") return null; + try { + const stored = localStorage.getItem(TOR_MODE_STORAGE_KEY); + if (stored === "true") return true; + if (stored === "false") return false; + return null; + } catch { + return null; + } +}; + +/** + * Set manual Tor mode preference in localStorage + * Pass null to clear the preference and enable auto-detection + */ +export const setTorModePreference = (value: boolean | null): void => { + if (typeof window === "undefined") return; + try { + if (value === null) { + localStorage.removeItem(TOR_MODE_STORAGE_KEY); + } else { + localStorage.setItem(TOR_MODE_STORAGE_KEY, String(value)); + } + } catch { + // Silently fail if localStorage is not available + } +}; + +/** + * Determine if we're in a Tor context + * Checks in order: + * 1. Manual override from localStorage + * 2. .onion hostname check + * 3. Tor Browser detection via user agent + */ export const isTorContext = (): boolean => { if (typeof window === "undefined") return false; + + // Check manual override first + const manualOverride = getTorModePreference(); + if (manualOverride !== null) { + return manualOverride; + } + + // Check if accessed via .onion address const hostname = window.location.hostname.toLowerCase(); - return hostname.endsWith(TOR_ONION_SUFFIX); + if (hostname.endsWith(TOR_ONION_SUFFIX)) { + return true; + } + + // Auto-detect Tor Browser + return isTorBrowser(); }; export const isOnionUrl = (url: string): boolean => {