File size: 4,476 Bytes
5da4770
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { FileCache } from '@/hooks/use-cached-file';

/**
 * Initialize cache maintenance routines
 * - Sets up interval to clean expired cache entries
 * - Adds event handlers for visibility and page unload
 */
export function initializeCacheSystem() {
  // Clean up expired cache entries every 5 minutes
  const CLEANUP_INTERVAL = 5 * 60 * 1000; // 5 minutes
  
  // Cache entry expiration
  const DEFAULT_EXPIRATION = 30 * 60 * 1000; // 30 minutes
  
  // Keep track of our interval
  let cleanupInterval: NodeJS.Timeout | null = null;
  
  // Clean up function to remove expired entries and release blob URLs
  const cleanupCache = () => {
    const now = Date.now();
    let blobUrlsToRevoke: string[] = [];
    
    // This is the implementation detail of how we access the Map inside the FileCache
    // We can't modify it directly, but we need to iterate through it for cleanup
    const cache = (FileCache as any).cache;
    
    if (cache && typeof cache.forEach === 'function') {
      const keysToDelete: string[] = [];
      
      cache.forEach((entry: any, key: string) => {
        // Check if the entry has expired
        if (now - entry.timestamp > DEFAULT_EXPIRATION) {
          keysToDelete.push(key);
          
          // If it's a blob URL, add it to our revocation list
          if (entry.type === 'url' && typeof entry.content === 'string' && entry.content.startsWith('blob:')) {
            blobUrlsToRevoke.push(entry.content);
          }
        }
      });
      
      // Delete expired keys
      keysToDelete.forEach(key => {
        FileCache.delete(key);
      });
      
      // Revoke blob URLs
      blobUrlsToRevoke.forEach(url => {
        try {
          URL.revokeObjectURL(url);
        } catch (err) {
          console.error(`Failed to revoke blob URL: ${url}`, err);
        }
      });
      
      if (keysToDelete.length > 0) {
        console.log(`Cache cleanup: removed ${keysToDelete.length} expired entries`);
      }
    }
  };
  
  // Set up visibility change handler to clean cache when page becomes visible again
  const handleVisibilityChange = () => {
    if (document.visibilityState === 'visible') {
      // User returned to the page, run a cleanup
      cleanupCache();
    }
  };
  
  // Clean all blob URLs before page unload to prevent memory leaks
  const handleBeforeUnload = () => {
    // This is more aggressive as we're about to unload anyway
    const cache = (FileCache as any).cache;
    
    if (cache && typeof cache.forEach === 'function') {
      cache.forEach((entry: any) => {
        if (entry.type === 'url' && typeof entry.content === 'string' && entry.content.startsWith('blob:')) {
          try {
            URL.revokeObjectURL(entry.content);
          } catch (err) {
            // Ignore errors during page unload
          }
        }
      });
    }
  };
  
  // Start the cleanup interval
  const startCleanupInterval = () => {
    // Clear any existing interval first
    if (cleanupInterval) {
      clearInterval(cleanupInterval);
    }
    
    // Set new interval
    cleanupInterval = setInterval(cleanupCache, CLEANUP_INTERVAL);
  };
  
  // Initialize event listeners
  const initEventListeners = () => {
    document.addEventListener('visibilitychange', handleVisibilityChange);
    window.addEventListener('beforeunload', handleBeforeUnload);
  };
  
  // Remove event listeners
  const removeEventListeners = () => {
    document.removeEventListener('visibilitychange', handleVisibilityChange);
    window.removeEventListener('beforeunload', handleBeforeUnload);
    
    if (cleanupInterval) {
      clearInterval(cleanupInterval);
      cleanupInterval = null;
    }
  };
  
  // Initialize the cache system
  startCleanupInterval();
  initEventListeners();
  
  // Return a cleanup function
  return {
    stopCacheSystem: removeEventListeners,
    clearCache: () => {
      // Revoke all blob URLs before clearing
      const cache = (FileCache as any).cache;
      if (cache && typeof cache.forEach === 'function') {
        cache.forEach((entry: any) => {
          if (entry.type === 'url' && typeof entry.content === 'string' && entry.content.startsWith('blob:')) {
            try {
              URL.revokeObjectURL(entry.content);
            } catch (err) {
              console.error('Failed to revoke URL during cache clear', err);
            }
          }
        });
      }
      
      // Clear the cache
      FileCache.clear();
    }
  };
}