gpt_chrome_autodrafter_v3/thread_id_strategies.md
2025-07-01 15:46:34 -07:00

8.4 KiB

Bulletproof Gmail Thread ID Extraction Strategies

Core Principles

1. Layered Fallback System

  • Never rely on a single method
  • Each method should be independent and handle different failure scenarios
  • Graceful degradation when methods fail

2. Caching & Performance

  • Cache successful extractions to avoid repeated work
  • Invalidate cache on navigation or DOM changes
  • Use session storage for persistence across page reloads

3. Validation & Verification

  • Always validate thread ID format (16-char hex for Gmail)
  • Cross-verify using multiple methods when possible
  • Implement confidence scoring for extraction methods

Advanced Techniques

4. Gmail API Integration

// Use Gmail API as authoritative source
async function verifyThreadIdWithAPI(threadId, token) {
  try {
    const response = await fetch(
      `https://gmail.googleapis.com/gmail/v1/users/me/threads/${threadId}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    );
    return response.ok;
  } catch {
    return false;
  }
}

5. Proactive Thread Tracking

// Track thread IDs as user navigates
class ThreadTracker {
  constructor() {
    this.threadHistory = new Map();
    this.setupNavigationTracking();
  }
  
  setupNavigationTracking() {
    // Track URL changes
    let lastUrl = location.href;
    new MutationObserver(() => {
      if (location.href !== lastUrl) {
        this.onNavigationChange(lastUrl, location.href);
        lastUrl = location.href;
      }
    }).observe(document, { subtree: true, childList: true });
  }
  
  onNavigationChange(oldUrl, newUrl) {
    const threadId = this.extractFromUrl(newUrl);
    if (threadId) {
      this.threadHistory.set(newUrl, {
        threadId,
        timestamp: Date.now(),
        confidence: 'high'
      });
    }
  }
}

6. DOM Mutation Monitoring

// Watch for thread ID attributes being added
function setupThreadIdWatcher() {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.type === 'attributes') {
        const threadId = mutation.target.getAttribute('data-legacy-thread-id');
        if (threadId && validateThreadId(threadId)) {
          // Store for immediate use
          window.currentThreadId = threadId;
        }
      }
    });
  });
  
  observer.observe(document.body, {
    attributes: true,
    attributeFilter: ['data-legacy-thread-id', 'data-thread-id'],
    subtree: true
  });
}

7. Cross-Frame Communication

// Handle Gmail's iframe structure
function extractFromAllFrames() {
  const frames = [window, ...Array.from(document.querySelectorAll('iframe'))
    .map(f => f.contentWindow)
    .filter(w => w && w.location.hostname.includes('google'))];
  
  for (const frame of frames) {
    try {
      const threadId = extractThreadIdFromFrame(frame);
      if (threadId) return threadId;
    } catch (e) {
      // Cross-origin restrictions
      continue;
    }
  }
  return null;
}

8. Intelligent Retry Logic

async function extractWithRetry(maxAttempts = 5, delay = 1000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const threadId = await threadExtractor.extractThreadId();
    
    if (threadId) {
      return threadId;
    }
    
    if (attempt < maxAttempts) {
      // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, delay * attempt));
      
      // Try to trigger Gmail state updates
      await triggerGmailRefresh();
    }
  }
  
  throw new Error('Failed to extract thread ID after all attempts');
}

async function triggerGmailRefresh() {
  // Simulate user interactions that might refresh Gmail's state
  document.dispatchEvent(new KeyboardEvent('keydown', { key: 'r' }));
  await new Promise(resolve => setTimeout(resolve, 500));
}

9. Context-Aware Extraction

function getExtractionContext() {
  return {
    isInboxView: location.hash.includes('#inbox'),
    isConversationView: !!document.querySelector('div[role="listitem"]'),
    isComposeView: !!document.querySelector('div[aria-label="Message Body"]'),
    gmailVersion: detectGmailVersion(),
    userAgent: navigator.userAgent
  };
}

function extractBasedOnContext(context) {
  if (context.isConversationView) {
    return extractFromConversationView();
  } else if (context.isInboxView) {
    return extractFromInboxView();
  } else {
    return extractGeneric();
  }
}

10. Error Recovery & Logging

class ThreadExtractionLogger {
  constructor() {
    this.attempts = [];
    this.successes = new Map();
    this.failures = new Map();
  }
  
  logAttempt(method, success, threadId, error) {
    const attempt = {
      method,
      success,
      threadId,
      error: error?.message,
      timestamp: Date.now(),
      url: location.href,
      context: getExtractionContext()
    };
    
    this.attempts.push(attempt);
    
    if (success) {
      this.successes.set(method, (this.successes.get(method) || 0) + 1);
    } else {
      this.failures.set(method, (this.failures.get(method) || 0) + 1);
    }
  }
  
  getReliabilityStats() {
    const stats = {};
    for (const [method, successes] of this.successes) {
      const failures = this.failures.get(method) || 0;
      stats[method] = {
        successRate: successes / (successes + failures),
        totalAttempts: successes + failures
      };
    }
    return stats;
  }
}

Implementation Best Practices

11. Performance Optimization

  • Use requestIdleCallback for non-critical extractions
  • Debounce rapid extraction requests
  • Implement method prioritization based on success rates

12. User Experience

  • Show loading states during extraction
  • Provide fallback UI when thread ID cannot be found
  • Allow manual thread ID input as last resort

13. Testing & Validation

// Comprehensive testing suite
const testCases = [
  { url: '#inbox/1234567890abcdef', expected: '1234567890abcdef' },
  { url: '#search/test/1234567890abcdef', expected: '1234567890abcdef' },
  // Add more test cases for different Gmail states
];

function runExtractionTests() {
  for (const testCase of testCases) {
    // Simulate different Gmail states and verify extraction
    history.pushState(null, '', testCase.url);
    const extracted = threadExtractor.extractThreadId();
    console.assert(extracted === testCase.expected, 
      `Failed for ${testCase.url}: got ${extracted}, expected ${testCase.expected}`);
  }
}

14. Graceful Degradation

// When all else fails, provide alternative functionality
async function handleExtractionFailure() {
  // Option 1: Use Gmail API to list recent threads
  const recentThreads = await fetchRecentThreads();
  
  // Option 2: Show thread selection UI
  showThreadSelectionDialog(recentThreads);
  
  // Option 3: Generate draft without thread context
  return generateStandaloneDraft();
}

Monitoring & Maintenance

15. Success Rate Tracking

  • Monitor extraction success rates across different Gmail versions
  • Track which methods work best in different contexts
  • Automatically adjust method priority based on performance

16. Gmail Update Detection

// Detect when Gmail updates might break extraction
function detectGmailChanges() {
  const currentSelectors = document.querySelectorAll('[data-legacy-thread-id]').length;
  const storedCount = localStorage.getItem('gmail_thread_elements');
  
  if (storedCount && Math.abs(currentSelectors - parseInt(storedCount)) > 10) {
    console.warn('Significant change in Gmail DOM structure detected');
    // Trigger extraction method re-evaluation
    reevaluateExtractionMethods();
  }
  
  localStorage.setItem('gmail_thread_elements', currentSelectors.toString());
}

This comprehensive approach ensures that your extension can reliably extract Gmail thread IDs regardless of:

  • Gmail interface updates
  • Different Gmail views (inbox, conversation, search)
  • Network issues affecting API calls
  • DOM structure changes
  • User navigation patterns

The key is having multiple independent methods that can work together, with intelligent fallbacks and continuous monitoring to adapt to changes in Gmail's structure.