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;
|
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]);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue