---
description: JavaScript implementation patterns for Expo compatibility and React Native Web
globs: ["javascript/**/*.js", "**/*main.js", "**/*core.js"]
alwaysApply: false
---

# JavaScript Implementation Patterns

## Factory Function Pattern (REQUIRED)
ALL JavaScript modules MUST use factory functions for dependency injection:

```javascript
// Correct: Factory function with injected dependencies
export const MixpanelCore = (storage) => {
  const mixpanelPersistent = MixpanelPersistent.getInstance(storage);
  const mixpanelConfig = MixpanelConfig.getInstance();
  
  const initialize = async (token) => {
    // Implementation...
  };
  
  return {
    initialize,
    track,
    flush,
    // Export only what's needed
  };
};

// Wrong: Direct imports and dependencies
import storage from './storage'; // Breaks testability
```

## Async/Await Pattern (MANDATORY)
ALL JavaScript operations MUST use async/await for consistency:

```javascript
// Correct: Async/await pattern
export default class MixpanelMain {
  async track(token, eventName, properties) {
    if (this.mixpanelPersistent.getOptedOut(token)) {
      MixpanelLogger.log(token, `User has opted out, skipping track`);
      return;
    }

    const enrichedEvent = await this.buildEventData(token, eventName, properties);
    await this.addToMixpanelQueue(token, MixpanelType.EVENTS, enrichedEvent);
  }
}

// Wrong: Callback or Promise.then patterns
// Use async/await consistently throughout
```

## Queue Management Pattern (CRITICAL)
ALL data operations MUST go through queue system:

```javascript
// ALWAYS use queue for event processing
const addToMixpanelQueue = async (token, type, data) => {
  // Add to in-memory queue
  await MixpanelQueueManager.enqueue(token, type, data);
  
  // Check if batch processing should trigger
  const queueSize = MixpanelQueueManager.getQueueSize(token, type);
  const batchSize = this.mixpanelConfig.getFlushBatchSize(token);
  
  if (queueSize >= batchSize) {
    this.processBatch(token, type);
  }
};
```

## Event Enrichment Pattern
ALWAYS enrich events with metadata before queuing:

```javascript
const buildEventData = async (token, eventName, properties) => {
  const identityProps = this.mixpanelPersistent.getIdentityProps(token);
  const superProperties = this.mixpanelPersistent.getSuperProperties(token);
  
  return Object.freeze({
    event: eventName,
    properties: {
      token,
      time: Date.now(),
      ...this.getMetaData(),           // Device info, lib version
      ...superProperties,              // User-defined global properties
      ...properties,                   // Event-specific properties
      ...identityProps,               // distinct_id, device_id
      $mp_metadata: {
        $mp_session_id: this.sessionId,
        $mp_event_id: this.generateEventId(),
      },
    },
  });
};
```

## Batch Processing Pattern
Implement efficient queue processing with error handling:

```javascript
const processBatch = async (token, type) => {
  const config = this.mixpanelConfig;
  const batchSize = config.getFlushBatchSize(token);
  const queue = MixpanelQueueManager.getQueue(token, type);
  
  if (queue.length === 0) return;
  
  const batch = queue.slice(0, batchSize);
  
  try {
    await MixpanelNetwork.sendRequest({
      token,
      endpoint: MixpanelType.getEndpoint(type),
      data: batch,
      serverURL: config.getServerURL(token),
      useIPAddressForGeoLocation: config.getUseIpAddressForGeoLocation(token),
    });
    
    // Remove successful items from queue
    MixpanelQueueManager.spliceQueue(token, type, 0, batch.length);
    
    // Process remaining items
    if (queue.length > batchSize) {
      setTimeout(() => this.processBatch(token, type), 0);
    }
  } catch (error) {
    this.handleBatchError(token, error, type, this.processBatch);
  }
};
```

## Identity Management Pattern
Handle identity consistently across all operations:

```javascript
const identify = async (token, distinctId) => {
  // Update persistent storage
  this.mixpanelPersistent.setDistinctId(token, distinctId);
  this.mixpanelPersistent.setUserId(token, distinctId);
  
  // ⚠️ Evolution [Updated: 2025-05-30]: Now delegates to core module
  // Update existing USER queue records for consistency  
  await this.core.identifyUserQueue(token);
  
  MixpanelLogger.log(token, `Identified user: ${distinctId}`);
};

// ⚠️ Note: identifyUserQueue is now handled by MixpanelCore
// The core module exposes this method:
const MixpanelCore = (storage) => {
  // ... other methods
  
  const identifyUserQueue = async (token) => {
    await MixpanelQueueManager.identifyUserQueue(token);
  };
  
  return {
    initialize,
    startProcessingQueue,
    addToMixpanelQueue,
    flush,
    identifyUserQueue, // Newly exposed method
  };
};
```

## Configuration Management Pattern
Use per-token configuration consistently:

```javascript
// Access configuration through config singleton
const config = this.mixpanelConfig;

// All settings are token-scoped
const flushInterval = config.getFlushInterval(token);
const batchSize = config.getFlushBatchSize(token);
const serverURL = config.getServerURL(token);

// Update configuration
config.setFlushInterval(token, newInterval);
config.setServerURL(token, newURL);
```

## Timer Management Pattern
Handle automatic flushing with proper cleanup:

```javascript
const startProcessingQueue = (token) => {
  const config = this.mixpanelConfig;
  const flushInterval = config.getFlushInterval(token);
  
  // Clear existing timer if any
  if (this._timers[token]) {
    clearInterval(this._timers[token]);
  }
  
  // Start new processing timer
  this._timers[token] = setInterval(() => {
    this.flush(token);
  }, flushInterval);
};

const stopProcessingQueue = (token) => {
  if (this._timers[token]) {
    clearInterval(this._timers[token]);
    delete this._timers[token];
  }
};
```

## Session Management Pattern
Track sessions consistently across events:

```javascript
class MixpanelMain {
  constructor(token, trackAutomaticEvents, storage) {
    this.sessionId = this.generateSessionId();
    this.sessionStartTime = Date.now();
  }
  
  generateSessionId() {
    return `session_${Date.now()}_${Math.random().toString(36).substring(2)}`;
  }
  
  generateEventId() {
    return `event_${Date.now()}_${Math.random().toString(36).substring(2)}`;
  }
}
```

## Error Recovery Pattern
Implement automatic recovery from queue corruption:

```javascript
const handleBatchError = async (token, error, type, processBatch) => {
  if (error.message?.includes('400') || error.message?.includes('Bad Request')) {
    // Likely corrupted data - remove first item and retry
    MixpanelLogger.error(token, `Removing corrupted item from ${type} queue`);
    MixpanelQueueManager.spliceQueue(token, type, 0, 1);
    
    // Retry processing
    setTimeout(() => processBatch(token, type), 1000);
  } else {
    // Network error - will retry via sendRequest retry logic
    MixpanelLogger.error(token, `Batch processing failed: ${error.message}`);
  }
};
```

## Performance Optimization Patterns

### Minimize Object Creation
```javascript
// Good: Reuse objects and minimize copies
const updateSuperProperties = (token, properties) => {
  // Only update if there are actual changes
  const current = this._superProperties[token];
  if (!this.hasChanges(current, properties)) {
    return;
  }
  
  // Shallow copy only when necessary
  this._superProperties[token] = { ...current, ...properties };
};
```

### Efficient Queue Operations
```javascript
// Use splice for efficient array operations
MixpanelQueueManager.spliceQueue(token, type, 0, batchSize);

// Avoid recreating arrays
const queue = this.getQueue(token, type);
queue.push(item); // Mutate in place for performance
```

## Testing JavaScript Implementation
```javascript
describe('JavaScript Mode', () => {
  beforeEach(() => {
    // Force JavaScript mode
    mixpanel = new Mixpanel('test-token', true, false);
  });

  it('should queue events for batch processing', async () => {
    mixpanel.track('Event', {prop: 'value'});
    
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      expect.objectContaining({
        event: 'Event',
        properties: expect.objectContaining({prop: 'value'})
      })
    );
  });
});
```