feat: Add PostHog ad blocker detection and shutdown

Co-authored-by: natemkelley <natemkelley@gmail.com>
This commit is contained in:
Cursor Agent 2025-09-16 23:35:24 +00:00
parent b8ca3e9327
commit 614a31dfcb
2 changed files with 151 additions and 2 deletions

View File

@ -49,7 +49,12 @@ export const GlobalErrorCard: ErrorRouteComponent = ({ error }) => {
const isPosthogLoaded = posthog.__loaded; const isPosthogLoaded = posthog.__loaded;
if (isPosthogLoaded) { if (isPosthogLoaded) {
posthog.captureException(error); try {
posthog.captureException(error);
} catch (captureError) {
// Silently handle PostHog capture errors (likely due to ad blocker)
console.warn('PostHog capture failed:', captureError);
}
} }
}, [error]); }, [error]);

View File

@ -16,6 +16,147 @@ const version = packageJson.version;
const POSTHOG_KEY = env.VITE_PUBLIC_POSTHOG_KEY; const POSTHOG_KEY = env.VITE_PUBLIC_POSTHOG_KEY;
const DEBUG_POSTHOG = false; const DEBUG_POSTHOG = false;
// PostHog failure detection constants
const MAX_CONSECUTIVE_FAILURES = 3;
const FAILURE_TIMEOUT = 5000; // 5 seconds
// Global state for failure tracking
let consecutiveFailures = 0;
let isPosthogBlocked = false;
let failureTimeouts: NodeJS.Timeout[] = [];
/**
* Monitors PostHog network requests for failures and shuts down PostHog
* if too many consecutive failures are detected (likely due to ad blockers)
*/
const setupPosthogFailureDetection = (posthog: typeof import('posthog-js').default) => {
if (isPosthogBlocked) {
return;
}
// Store original methods
const originalCapture = posthog.capture;
const originalIdentify = posthog.identify;
const originalGroup = posthog.group;
const handleNetworkFailure = () => {
consecutiveFailures++;
console.warn(
`PostHog network failure detected (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`
);
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
console.warn('PostHog blocked - shutting down to prevent network bloat');
shutdownPosthog(posthog);
}
};
const handleNetworkSuccess = () => {
if (consecutiveFailures > 0) {
console.info('PostHog network request succeeded - resetting failure count');
consecutiveFailures = 0;
// Clear any pending failure timeouts
failureTimeouts.forEach(clearTimeout);
failureTimeouts = [];
}
};
const wrapWithFailureDetection = (
originalMethod: (...args: unknown[]) => unknown,
methodName: string
) => {
return function (this: unknown, ...args: unknown[]) {
if (isPosthogBlocked) {
return;
}
try {
// Set a timeout to detect if the request hangs or fails silently
const timeoutId = setTimeout(() => {
console.warn(`PostHog ${methodName} request timed out`);
handleNetworkFailure();
}, FAILURE_TIMEOUT);
failureTimeouts.push(timeoutId);
// Call original method
const result = originalMethod.apply(this, args);
// If the method returns a promise, handle success/failure
if (result && typeof result.then === 'function') {
result
.then(() => {
clearTimeout(timeoutId);
const index = failureTimeouts.indexOf(timeoutId);
if (index > -1) failureTimeouts.splice(index, 1);
handleNetworkSuccess();
})
.catch((error: unknown) => {
clearTimeout(timeoutId);
const index = failureTimeouts.indexOf(timeoutId);
if (index > -1) failureTimeouts.splice(index, 1);
console.warn(`PostHog ${methodName} failed:`, error);
handleNetworkFailure();
});
} else {
// For synchronous methods, assume success and clear timeout
clearTimeout(timeoutId);
const index = failureTimeouts.indexOf(timeoutId);
if (index > -1) failureTimeouts.splice(index, 1);
handleNetworkSuccess();
}
return result;
} catch (error) {
console.warn(`PostHog ${methodName} error:`, error);
handleNetworkFailure();
throw error;
}
};
};
// Override PostHog methods with failure detection
posthog.capture = wrapWithFailureDetection(originalCapture, 'capture');
posthog.identify = wrapWithFailureDetection(originalIdentify, 'identify');
posthog.group = wrapWithFailureDetection(originalGroup, 'group');
};
const shutdownPosthog = (posthog: typeof import('posthog-js').default) => {
isPosthogBlocked = true;
try {
// Clear any pending timeouts
failureTimeouts.forEach(clearTimeout);
failureTimeouts = [];
// Stop PostHog from making further requests
if (posthog?.reset) {
posthog.reset();
}
// Override all methods to be no-ops
const noOp = () => undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
posthog.capture = noOp as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
posthog.identify = noOp as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
posthog.group = noOp as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
posthog.alias = noOp as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
posthog.reset = noOp as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
posthog.register = noOp as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
posthog.unregister = noOp as any;
console.info('PostHog has been shut down due to network failures (likely ad blocker)');
} catch (error) {
console.error('Error shutting down PostHog:', error);
}
};
export const BusterPosthogProvider: React.FC<PropsWithChildren> = ({ children }) => { export const BusterPosthogProvider: React.FC<PropsWithChildren> = ({ children }) => {
if ((isDev && !DEBUG_POSTHOG) || !POSTHOG_KEY) { if ((isDev && !DEBUG_POSTHOG) || !POSTHOG_KEY) {
return <>{children}</>; return <>{children}</>;
@ -84,7 +225,7 @@ const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => {
// Initialize PostHog when modules are loaded and user data is available // Initialize PostHog when modules are loaded and user data is available
useEffect(() => { useEffect(() => {
if (POSTHOG_KEY && !isServer && user && posthogModules?.posthog) { if (POSTHOG_KEY && !isServer && user && posthogModules?.posthog && !isPosthogBlocked) {
const { posthog } = posthogModules; const { posthog } = posthogModules;
if (posthog.__loaded) { if (posthog.__loaded) {
@ -93,6 +234,9 @@ const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => {
posthog.init(POSTHOG_KEY, options); posthog.init(POSTHOG_KEY, options);
// Set up failure detection after initialization
setupPosthogFailureDetection(posthog);
const email = user.email; const email = user.email;
posthog.identify(email, { posthog.identify(email, {
user, user,