# PostHog.js Edge Cases and Advanced Scenarios

Handling edge cases, race conditions, and advanced scenarios.

## Race Conditions

### Feature Flags Not Loaded Yet

```typescript { .api }
// ❌ Bad: Check flag before it's loaded
function Component() {
    const enabled = posthog.isFeatureEnabled('new-ui');  // May be undefined
    return enabled ? <NewUI /> : <OldUI />;
}

// ✅ Good: Wait for flags to load
function Component() {
    const [flagsLoaded, setFlagsLoaded] = useState(false);
    const [enabled, setEnabled] = useState(false);
    
    useEffect(() => {
        const unsubscribe = posthog.onFeatureFlags((flags) => {
            setEnabled(flags['new-ui'] || false);
            setFlagsLoaded(true);
        });
        return unsubscribe;
    }, []);
    
    if (!flagsLoaded) return <Loading />;
    return enabled ? <NewUI /> : <OldUI />;
}
```

### Async Initialization

```typescript { .api }
// Ensure PostHog is ready before use
async function initializeApp() {
    return new Promise<void>((resolve) => {
        posthog.init('token', {
            loaded: () => {
                console.log('PostHog ready');
                resolve();
            }
        });
    });
}

// Wait for initialization
await initializeApp();
posthog.capture('app_initialized');
```

## Network Failures

### Offline Handling

```typescript { .api }
// Handle offline scenarios gracefully
window.addEventListener('online', () => {
    console.log('Back online - PostHog will resume');
    posthog.capture('connection_restored');
});

window.addEventListener('offline', () => {
    console.log('Offline - PostHog will queue events');
    // Events are automatically queued and sent when connection returns
});

// Check connectivity before critical operations
if (navigator.onLine) {
    posthog.reloadFeatureFlags();
}
```

### API Host Unreachable

```typescript { .api }
// Fallback when API is unreachable
posthog.init('token', {
    api_host: 'https://us.i.posthog.com',
    on_request_error: (response) => {
        console.error('PostHog request failed:', response.statusCode);
        
        // Log to alternative service
        logToBackupService({
            error: 'PostHog unreachable',
            status: response.statusCode
        });
    }
});
```

## Browser Storage Issues

### LocalStorage Blocked

```typescript { .api }
// Handle blocked storage
try {
    posthog.init('token', {
        persistence: 'localStorage+cookie'
    });
} catch (error) {
    console.warn('Storage blocked, using memory persistence');
    
    // Fallback to memory-only
    posthog.init('token', {
        persistence: 'memory'
    });
}
```

### Cookie Restrictions

```typescript { .api }
// Handle third-party cookie restrictions
posthog.init('token', {
    persistence: 'localStorage',  // Avoid cookies
    cross_subdomain_cookie: false,
    secure_cookie: true
});
```

## Multiple Initialization

### Prevent Double Initialization

```typescript { .api }
// Singleton pattern for PostHog
let posthogInitialized = false;

export function initPostHog() {
    if (posthogInitialized) {
        console.warn('PostHog already initialized');
        return;
    }
    
    posthog.init('token', {
        loaded: () => {
            posthogInitialized = true;
        }
    });
}

// Safe to call multiple times
initPostHog();
initPostHog();  // Will warn and skip
```

## Session Transitions

### Handle Session ID Changes

```typescript { .api }
// Track session transitions
let currentSessionId = posthog.get_session_id();

posthog.onSessionId((newSessionId, windowId) => {
    console.log('Session changed:', {
        old: currentSessionId,
        new: newSessionId
    });
    
    // Track transition
    posthog.capture('session_transitioned', {
        previous_session: currentSessionId,
        new_session: newSessionId,
        window_id: windowId
    });
    
    currentSessionId = newSessionId;
});
```

## User Identity Edge Cases

### Anonymous to Identified Transition

```typescript { .api }
// Properly link anonymous and identified users
async function handleSignup(userData: SignupData) {
    const anonymousId = posthog.get_distinct_id();
    
    // Create user account
    const user = await createAccount(userData);
    
    // Link anonymous activity to new user
    posthog.alias(user.id);
    
    // Identify with properties
    posthog.identify(user.id, {
        email: user.email,
        name: user.name
    }, {
        signup_date: new Date().toISOString(),
        initial_referrer: document.referrer
    });
    
    // Track the conversion
    posthog.capture('user_signed_up', {
        previous_id: anonymousId,
        new_id: user.id
    });
}
```

### Multiple Device Tracking

```typescript { .api }
// Handle same user across devices
function identifyAcrossDevices(userId: string, deviceId: string) {
    posthog.identify(userId, {
        email: user.email
    });
    
    // Track device
    posthog.capture('device_identified', {
        device_id: deviceId,
        device_type: getDeviceType(),
        first_seen: isNewDevice(deviceId)
    });
    
    // Set device as super property
    posthog.register({
        device_id: deviceId
    });
}
```

## Configuration Conflicts

### Resolving Conflicting Settings

```typescript { .api }
// ❌ Bad: Conflicting configuration
posthog.init('token', {
    disable_persistence: true,
    persistence: 'localStorage'  // Ignored!
});

// ✅ Good: Consistent configuration
posthog.init('token', {
    disable_persistence: false,
    persistence: 'localStorage'
});

// ✅ Good: Validate configuration
function validateAndInit(config: Partial<PostHogConfig>) {
    if (config.disable_persistence && config.persistence !== 'memory') {
        console.warn('Conflict: disable_persistence overrides persistence setting');
        config.persistence = 'memory';
    }
    
    posthog.init('token', config);
}
```

## High-Traffic Scenarios

### Event Flooding Prevention

```typescript { .api }
// Prevent event flooding
class EventThrottler {
    private lastCapture = new Map<string, number>();
    private throttleMs = 1000;
    
    capture(event: string, properties: Properties = {}) {
        const now = Date.now();
        const last = this.lastCapture.get(event);
        
        if (last && (now - last) < this.throttleMs) {
            console.log('Throttling event:', event);
            return;
        }
        
        this.lastCapture.set(event, now);
        posthog.capture(event, properties);
    }
}

const throttler = new EventThrottler();

// Use for high-frequency events
window.addEventListener('scroll', () => {
    throttler.capture('scrolled', {
        position: window.scrollY
    });
});
```

## Memory Management

### Long-Running Sessions

```typescript { .api }
// Manage memory in long-running sessions
class SessionManager {
    private sessionStartTime = Date.now();
    private readonly MAX_SESSION_TIME = 4 * 60 * 60 * 1000;  // 4 hours
    
    initialize() {
        setInterval(() => {
            this.checkSessionAge();
        }, 60000);  // Check every minute
    }
    
    checkSessionAge() {
        const age = Date.now() - this.sessionStartTime;
        
        if (age > this.MAX_SESSION_TIME) {
            console.log('Session too old, resetting');
            
            // Capture session end
            posthog.capture('long_session_reset', {
                session_duration_ms: age
            });
            
            // Reset PostHog state
            posthog.reset();
            
            // Restart
            this.sessionStartTime = Date.now();
        }
    }
}
```

## Browser Compatibility

### Polyfill Requirements

```typescript { .api }
// Check for required features
function checkBrowserSupport(): boolean {
    const required = {
        localStorage: typeof window.localStorage !== 'undefined',
        fetch: typeof window.fetch !== 'undefined',
        Promise: typeof Promise !== 'undefined',
        JSON: typeof JSON !== 'undefined'
    };
    
    const unsupported = Object.entries(required)
        .filter(([key, supported]) => !supported)
        .map(([key]) => key);
    
    if (unsupported.length > 0) {
        console.error('Unsupported features:', unsupported);
        return false;
    }
    
    return true;
}

// Initialize only if supported
if (checkBrowserSupport()) {
    posthog.init('token');
} else {
    console.warn('Browser not supported, PostHog disabled');
}
```

## Performance Edge Cases

### Low-End Device Detection

```typescript { .api }
// Adapt to device capabilities
function getOptimalConfig(): Partial<PostHogConfig> {
    const isLowEnd = (
        navigator.deviceMemory && navigator.deviceMemory < 4 ||
        navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4
    );
    
    if (isLowEnd) {
        return {
            session_recording: {
                enabled: false  // Disable expensive features
            },
            capture_heatmaps: false,
            autocapture: {
                capture_clicks: true,
                rageclicks: false,
                dead_clicks: false
            }
        };
    }
    
    return {
        session_recording: {
            enabled: true,
            recordCanvas: true
        },
        capture_heatmaps: true
    };
}

posthog.init('token', getOptimalConfig());
```

## Testing Edge Cases

### Mock PostHog in Tests

```typescript { .api }
// Complete PostHog mock
const createPostHogMock = () => ({
    init: jest.fn(),
    capture: jest.fn(),
    identify: jest.fn(),
    alias: jest.fn(),
    reset: jest.fn(),
    isFeatureEnabled: jest.fn(() => false),
    getFeatureFlag: jest.fn(() => undefined),
    onFeatureFlags: jest.fn((cb) => {
        cb({}, {});
        return () => {};
    }),
    startSessionRecording: jest.fn(),
    stopSessionRecording: jest.fn(),
    get_distinct_id: jest.fn(() => 'test-user'),
    get_session_id: jest.fn(() => 'test-session'),
    featureFlags: {
        hasLoadedFlags: true,
        getFlags: jest.fn(() => [])
    }
});

// Use in tests
beforeEach(() => {
    (window as any).posthog = createPostHogMock();
});
```

## Security Edge Cases

### XSS Prevention

```typescript { .api }
// Sanitize user input before tracking
function sanitizeProperties(properties: Properties): Properties {
    const sanitized: Properties = {};
    
    for (const [key, value] of Object.entries(properties)) {
        if (typeof value === 'string') {
            // Remove potential XSS
            sanitized[key] = value
                .replace(/<script[^>]*>.*?<\/script>/gi, '')
                .replace(/<[^>]+>/g, '')
                .substring(0, 1000);  // Limit length
        } else {
            sanitized[key] = value;
        }
    }
    
    return sanitized;
}

// Use when tracking user-generated content
posthog.capture('comment_posted', sanitizeProperties({
    comment_text: userInput,
    user_id: userId
}));
```

## Migration Edge Cases

### Gradual Migration

```typescript { .api }
// Run PostHog alongside existing analytics
function dualTrack(event: string, properties: Properties) {
    // Track to both systems during migration
    posthog.capture(event, properties);
    legacyAnalytics.track(event, properties);
}

// Gradually phase out old system
const migrationPercent = 50;  // 50% on PostHog

function smartTrack(event: string, properties: Properties) {
    if (Math.random() * 100 < migrationPercent) {
        posthog.capture(event, properties);
    } else {
        legacyAnalytics.track(event, properties);
    }
}
```

## See Also

- **[Quick Start](../guides/quick-start.md)** - Basic setup
- **[Best Practices](../guides/best-practices.md)** - Production patterns
- **[Real-World Scenarios](./real-world-scenarios.md)** - Common use cases
- **[API Reference](../reference/initialization.md)** - Detailed documentation
