mirror of https://github.com/buster-so/buster.git
feat: Add PostHog ad blocker detection and shutdown
Co-authored-by: natemkelley <natemkelley@gmail.com>
This commit is contained in:
parent
b8ca3e9327
commit
614a31dfcb
|
@ -49,7 +49,12 @@ export const GlobalErrorCard: ErrorRouteComponent = ({ error }) => {
|
|||
const isPosthogLoaded = posthog.__loaded;
|
||||
|
||||
if (isPosthogLoaded) {
|
||||
try {
|
||||
posthog.captureException(error);
|
||||
} catch (captureError) {
|
||||
// Silently handle PostHog capture errors (likely due to ad blocker)
|
||||
console.warn('PostHog capture failed:', captureError);
|
||||
}
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
|
|
|
@ -16,6 +16,147 @@ const version = packageJson.version;
|
|||
const POSTHOG_KEY = env.VITE_PUBLIC_POSTHOG_KEY;
|
||||
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 }) => {
|
||||
if ((isDev && !DEBUG_POSTHOG) || !POSTHOG_KEY) {
|
||||
return <>{children}</>;
|
||||
|
@ -84,7 +225,7 @@ const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => {
|
|||
|
||||
// Initialize PostHog when modules are loaded and user data is available
|
||||
useEffect(() => {
|
||||
if (POSTHOG_KEY && !isServer && user && posthogModules?.posthog) {
|
||||
if (POSTHOG_KEY && !isServer && user && posthogModules?.posthog && !isPosthogBlocked) {
|
||||
const { posthog } = posthogModules;
|
||||
|
||||
if (posthog.__loaded) {
|
||||
|
@ -93,6 +234,9 @@ const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => {
|
|||
|
||||
posthog.init(POSTHOG_KEY, options);
|
||||
|
||||
// Set up failure detection after initialization
|
||||
setupPosthogFailureDetection(posthog);
|
||||
|
||||
const email = user.email;
|
||||
posthog.identify(email, {
|
||||
user,
|
||||
|
|
Loading…
Reference in New Issue