1 // Memory optimization utilities for the JCRGuru application
2
3 // Memory leak prevention utilities
4 export class MemoryLeakPrevention {
5 private static intervals: Set<NodeJS.Timeout> = new Set();
6 private static timeouts: Set<NodeJS.Timeout> = new Set();
7 private static eventListeners: Map<Element, Array<{ event: string; handler: EventListener }>> = new Map();
8
9 // Tracked setTimeout to prevent leaks
10 static setTimeout(callback: () => void, delay: number): NodeJS.Timeout {
11 const timeout = setTimeout(() => {
12 callback();
13 this.timeouts.delete(timeout);
14 }, delay);
15 this.timeouts.add(timeout);
16 return timeout;
17 }
18
19 // Tracked setInterval to prevent leaks
20 static setInterval(callback: () => void, delay: number): NodeJS.Timeout {
21 const interval = setInterval(callback, delay);
22 this.intervals.add(interval);
23 return interval;
24 }
25
26 // Clear specific timeout
27 static clearTimeout(timeout: NodeJS.Timeout): void {
28 clearTimeout(timeout);
29 this.timeouts.delete(timeout);
30 }
31
32 // Clear specific interval
33 static clearInterval(interval: NodeJS.Timeout): void {
34 clearInterval(interval);
35 this.intervals.delete(interval);
36 }
37
38 // Add tracked event listener
39 static addEventListener(element: Element, event: string, handler: EventListener): void {
40 element.addEventListener(event, handler);
41
42 if (!this.eventListeners.has(element)) {
43 this.eventListeners.set(element, []);
44 }
45 this.eventListeners.get(element)!.push({ event, handler });
46 }
47
48 // Remove tracked event listener
49 static removeEventListener(element: Element, event: string, handler: EventListener): void {
50 element.removeEventListener(event, handler);
51
52 const listeners = this.eventListeners.get(element);
53 if (listeners) {
54 const index = listeners.findIndex(l => l.event === event && l.handler === handler);
55 if (index > -1) {
56 listeners.splice(index, 1);
57 }
58 if (listeners.length === 0) {
59 this.eventListeners.delete(element);
60 }
61 }
62 }
63
64 // Clean up all tracked resources
65 static cleanup(): void {
66 // Clear all intervals
67 this.intervals.forEach(interval => clearInterval(interval));
68 this.intervals.clear();
69
70 // Clear all timeouts
71 this.timeouts.forEach(timeout => clearTimeout(timeout));
72 this.timeouts.clear();
73
74 // Remove all event listeners
75 this.eventListeners.forEach((listeners, element) => {
76 listeners.forEach(({ event, handler }) => {
77 element.removeEventListener(event, handler);
78 });
79 });
80 this.eventListeners.clear();
81 }
82
83 // Get current resource count
84 static getResourceCount(): { intervals: number; timeouts: number; listeners: number } {
85 let listenerCount = 0;
86 this.eventListeners.forEach(listeners => {
87 listenerCount += listeners.length;
88 });
89
90 return {
91 intervals: this.intervals.size,
92 timeouts: this.timeouts.size,
93 listeners: listenerCount
94 };
95 }
96 }
97
98 // React component memory optimization hook
99 export function useMemoryOptimization() {
100 if (typeof window === 'undefined') return;
101
102 // Cleanup on page unload
103 const handleUnload = () => {
104 MemoryLeakPrevention.cleanup();
105 };
106
107 window.addEventListener('beforeunload', handleUnload);
108
109 return () => {
110 window.removeEventListener('beforeunload', handleUnload);
111 MemoryLeakPrevention.cleanup();
112 };
113 }
114
115 // Image compression utility
116 export function compressImage(file: Blob, maxWidth = 1920, maxHeight = 1080, quality = 0.8): Promise<Blob> {
117 return new Promise((resolve) => {
118 const canvas = document.createElement('canvas');
119 const ctx = canvas.getContext('2d');
120 const img = new Image();
121
122 img.onload = () => {
123 // Calculate new dimensions
124 let { width, height } = img;
125
126 if (width > maxWidth) {
127 height = (height * maxWidth) / width;
128 width = maxWidth;
129 }
130
131 if (height > maxHeight) {
132 width = (width * maxHeight) / height;
133 height = maxHeight;
134 }
135
136 canvas.width = width;
137 canvas.height = height;
138
139 // Draw and compress
140 ctx?.drawImage(img, 0, 0, width, height);
141
142 canvas.toBlob((blob) => {
143 resolve(blob || file);
144 }, 'image/jpeg', quality);
145 };
146
147 img.onerror = () => resolve(file);
148 img.src = URL.createObjectURL(file);
149 });
150 }
151
152 // Memory monitoring utilities
153 export class MemoryMonitor {
154 private static checkInterval: NodeJS.Timeout | null = null;
155
156 static startMonitoring(intervalMs = 30000): void {
157 if (this.checkInterval) return;
158
159 this.checkInterval = MemoryLeakPrevention.setInterval(() => {
160 this.checkMemoryUsage();
161 }, intervalMs);
162 }
163
164 static stopMonitoring(): void {
165 if (this.checkInterval) {
166 MemoryLeakPrevention.clearInterval(this.checkInterval);
167 this.checkInterval = null;
168 }
169 }
170
171 private static checkMemoryUsage(): void {
172 // Check performance memory if available
173 if ('memory' in performance) {
174 const memory = (performance as any).memory;
175 const used = Math.round(memory.usedJSHeapSize / 1048576);
176 const total = Math.round(memory.totalJSHeapSize / 1048576);
177 const limit = Math.round(memory.jsHeapSizeLimit / 1048576);
178
179 // Warn if memory usage is high
180 if (used > limit * 0.8) {
181 console.warn(`High memory usage detected: ${used}MB / ${limit}MB`);
182 this.suggestCleanup();
183 }
184 }
185
186 // Check localStorage usage
187 if (typeof localStorage !== 'undefined') {
188 let totalSize = 0;
189 for (let key in localStorage) {
190 if (localStorage.hasOwnProperty(key)) {
191 totalSize += localStorage[key].length;
192 }
193 }
194
195 const storageMB = totalSize / (1024 * 1024);
196 if (storageMB > 40) { // Warn at 40MB
197 console.warn(`High localStorage usage: ${storageMB.toFixed(1)}MB`);
198 }
199 }
200 }
201
202 private static suggestCleanup(): void {
203 console.log('Memory optimization suggestions:');
204 console.log('- Clear old cached images and videos');
205 console.log('- Reduce query cache size');
206 console.log('- Check for memory leaks in components');
207 }
208
209 static getMemoryInfo(): any {
210 if ('memory' in performance) {
211 const memory = (performance as any).memory;
212 return {
213 used: Math.round(memory.usedJSHeapSize / 1048576),
214 total: Math.round(memory.totalJSHeapSize / 1048576),
215 limit: Math.round(memory.jsHeapSizeLimit / 1048576),
216 percentage: Math.round((memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100)
217 };
218 }
219 return null;
220 }
221 }
222
223 // Efficient data structure helpers
224 export class MemoryEfficientMap<K, V> extends Map<K, V> {
225 private maxSize: number;
226
227 constructor(maxSize = 1000) {
228 super();
229 this.maxSize = maxSize;
230 }
231
232 set(key: K, value: V): this {
233 // Remove oldest entries if at capacity
234 if (this.size >= this.maxSize && !this.has(key)) {
235 const firstKey = this.keys().next().value;
236 this.delete(firstKey);
237 }
238
239 return super.set(key, value);
240 }
241 }
242
243 // Debounced cleanup for large lists
244 export function debouncedCleanup<T>(
245 items: T[],
246 cleanupFn: (item: T) => void,
247 delay = 1000
248 ): void {
249 const cleanup = () => {
250 items.forEach(cleanupFn);
251 };
252
253 clearTimeout((cleanup as any).timeoutId);
254 (cleanup as any).timeoutId = MemoryLeakPrevention.setTimeout(cleanup, delay);
255 }