@jaak.ai/stamps 2.1.0-dev.7 → 2.1.0-dev.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  2. package/dist/cjs/jaak-stamps.cjs.entry.js +377 -17
  3. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  4. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/collection/components/my-component/my-component.js +265 -12
  7. package/dist/collection/components/my-component/my-component.js.map +1 -1
  8. package/dist/collection/services/DetectionService.js +148 -7
  9. package/dist/collection/services/DetectionService.js.map +1 -1
  10. package/dist/collection/services/interfaces/IDetectionService.js.map +1 -1
  11. package/dist/components/jaak-stamps.js +380 -18
  12. package/dist/components/jaak-stamps.js.map +1 -1
  13. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  14. package/dist/esm/jaak-stamps.entry.js +377 -17
  15. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  16. package/dist/esm/loader.js +1 -1
  17. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  18. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  19. package/dist/jaak-stamps-webcomponent/p-c115e01a.entry.js +2 -0
  20. package/dist/jaak-stamps-webcomponent/p-c115e01a.entry.js.map +1 -0
  21. package/dist/types/components/my-component/my-component.d.ts +27 -0
  22. package/dist/types/components.d.ts +3 -1
  23. package/dist/types/services/DetectionService.d.ts +9 -1
  24. package/dist/types/services/interfaces/IDetectionService.d.ts +6 -0
  25. package/package.json +1 -1
  26. package/dist/jaak-stamps-webcomponent/p-be6b6ca3.entry.js +0 -2
  27. package/dist/jaak-stamps-webcomponent/p-be6b6ca3.entry.js.map +0 -1
@@ -18,7 +18,7 @@ var patchBrowser = () => {
18
18
 
19
19
  patchBrowser().then(async (options) => {
20
20
  await index.globalScripts();
21
- return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"performanceDegradedMode":[32],"showPerformanceMessage":[32],"captureStateVersion":[32],"processingButton":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
21
+ return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"performanceDegradedMode":[32],"showPerformanceMessage":[32],"captureStateVersion":[32],"processingButton":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64],"getResourceReport":[64],"forceResourceCleanup":[64]}]]]], options);
22
22
  });
23
23
 
24
24
  exports.setNonce = index.setNonce;
@@ -650,6 +650,10 @@ class DetectionService {
650
650
  // Static canvas pool for reuse across instances
651
651
  static canvasPool = new Map();
652
652
  static MAX_POOL_SIZE = 5;
653
+ // Resource management for optimization
654
+ modelLoadController;
655
+ classificationLoadController;
656
+ modelBlobUrls = new Set();
653
657
  constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
654
658
  this.debug = debug;
655
659
  this.useDocumentClassification = useDocumentClassification;
@@ -667,11 +671,28 @@ class DetectionService {
667
671
  return;
668
672
  }
669
673
  try {
674
+ if (typeof window.ort === 'undefined') {
675
+ throw new Error('ONNX Runtime not available in window object');
676
+ }
677
+ // Create AbortController for this load operation
678
+ this.modelLoadController = new AbortController();
670
679
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
671
680
  try {
672
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
681
+ // Load model with abort signal support
682
+ const response = await fetch(this.MODEL_PATH, {
683
+ signal: this.modelLoadController.signal
684
+ });
685
+ if (!response.ok) {
686
+ throw new Error(`Failed to fetch model: ${response.status}`);
687
+ }
688
+ const modelBuffer = await response.arrayBuffer();
689
+ this.session = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
673
690
  }
674
691
  catch (error) {
692
+ // Handle abort signal
693
+ if (error.name === 'AbortError') {
694
+ return;
695
+ }
675
696
  // Múltiples tipos de errores de memoria que pueden ocurrir
676
697
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
677
698
  error.message.includes('Out of memory') ||
@@ -693,7 +714,15 @@ class DetectionService {
693
714
  sessionLogVerbosityLevel: 0,
694
715
  };
695
716
  try {
696
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
717
+ // Retry with fallback options and abort signal
718
+ const fallbackResponse = await fetch(this.MODEL_PATH, {
719
+ signal: this.modelLoadController.signal
720
+ });
721
+ if (!fallbackResponse.ok) {
722
+ throw new Error(`Failed to fetch model with fallback: ${fallbackResponse.status}`);
723
+ }
724
+ const fallbackBuffer = await fallbackResponse.arrayBuffer();
725
+ this.session = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
697
726
  }
698
727
  catch (fallbackError) {
699
728
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -714,9 +743,13 @@ class DetectionService {
714
743
  return;
715
744
  }
716
745
  try {
746
+ // Create AbortController for classification model loading
747
+ this.classificationLoadController = new AbortController();
717
748
  // Try to load class map (optional - SqueezeNet may not need it for basic classification)
718
749
  try {
719
- const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
750
+ const classResponse = await fetch(this.MOBILENET_CLASSES_PATH, {
751
+ signal: this.classificationLoadController.signal
752
+ });
720
753
  if (classResponse.ok) {
721
754
  this.mobileNetClassMap = await classResponse.json();
722
755
  }
@@ -734,9 +767,22 @@ class DetectionService {
734
767
  // Load model
735
768
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
736
769
  try {
737
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
770
+ // Load classification model with abort signal support
771
+ const response = await fetch(this.MOBILENET_MODEL_PATH, {
772
+ signal: this.classificationLoadController.signal
773
+ });
774
+ if (!response.ok) {
775
+ throw new Error(`Failed to fetch classification model: ${response.status}`);
776
+ }
777
+ const modelBuffer = await response.arrayBuffer();
778
+ this.mobileNetSession = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
738
779
  }
739
780
  catch (error) {
781
+ // Handle abort signal
782
+ if (error.name === 'AbortError') {
783
+ console.log('Classification model loading was cancelled');
784
+ return;
785
+ }
740
786
  // Múltiples tipos de errores de memoria que pueden ocurrir
741
787
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
742
788
  error.message.includes('Out of memory') ||
@@ -758,7 +804,15 @@ class DetectionService {
758
804
  sessionLogVerbosityLevel: 0,
759
805
  };
760
806
  try {
761
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
807
+ // Retry with fallback options and abort signal
808
+ const fallbackResponse = await fetch(this.MOBILENET_MODEL_PATH, {
809
+ signal: this.classificationLoadController.signal
810
+ });
811
+ if (!fallbackResponse.ok) {
812
+ throw new Error(`Failed to fetch classification model with fallback: ${fallbackResponse.status}`);
813
+ }
814
+ const fallbackBuffer = await fallbackResponse.arrayBuffer();
815
+ this.mobileNetSession = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
762
816
  }
763
817
  catch (fallbackError) {
764
818
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -948,6 +1002,9 @@ class DetectionService {
948
1002
  return alignment.top && alignment.right && alignment.bottom && alignment.left;
949
1003
  }
950
1004
  cleanup() {
1005
+ // Cancel any ongoing requests
1006
+ this.abortPendingRequests();
1007
+ // Cleanup ONNX resources
951
1008
  this.cleanupCanvasPool();
952
1009
  if (this.session) {
953
1010
  this.session.release?.();
@@ -959,6 +1016,8 @@ class DetectionService {
959
1016
  }
960
1017
  this.mobileNetClassMap = undefined;
961
1018
  this.modelLoaded = false;
1019
+ // Cleanup blob URLs
1020
+ this.revokeModelBlobUrls();
962
1021
  }
963
1022
  initializeCanvasPool() {
964
1023
  this.preprocessCanvas = document.createElement('canvas');
@@ -1056,11 +1115,93 @@ class DetectionService {
1056
1115
  const pool = DetectionService.canvasPool.get(key);
1057
1116
  // Only return to pool if not at max capacity
1058
1117
  if (pool.length < DetectionService.MAX_POOL_SIZE) {
1118
+ const ctx = canvas.getContext('2d');
1119
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1059
1120
  pool.push(canvas);
1060
1121
  }
1122
+ // If pool is full, let the canvas be garbage collected
1123
+ }
1124
+ // Resource management methods for cache and browser optimization
1125
+ async clearModelCache() {
1126
+ try {
1127
+ // Clear cache specific to models
1128
+ const cacheNames = await caches.keys();
1129
+ const modelCaches = cacheNames.filter(name => name.includes('onnx') ||
1130
+ name.includes('jaak-static') ||
1131
+ name.includes('squeezenet') ||
1132
+ name.includes('ddmyp'));
1133
+ await Promise.all(modelCaches.map(cacheName => caches.delete(cacheName)));
1134
+ if (this.debug) {
1135
+ console.log(`🧹 Cleared ${modelCaches.length} model caches`);
1136
+ }
1137
+ }
1138
+ catch (error) {
1139
+ console.warn('Error clearing model cache:', error);
1140
+ }
1141
+ }
1142
+ abortPendingRequests() {
1143
+ // Cancel any fetch in progress
1144
+ if (this.modelLoadController) {
1145
+ this.modelLoadController.abort();
1146
+ this.modelLoadController = undefined;
1147
+ }
1148
+ if (this.classificationLoadController) {
1149
+ this.classificationLoadController.abort();
1150
+ this.classificationLoadController = undefined;
1151
+ }
1152
+ }
1153
+ revokeModelBlobUrls() {
1154
+ // Revoke any blob URLs created for models
1155
+ this.modelBlobUrls.forEach(url => URL.revokeObjectURL(url));
1156
+ this.modelBlobUrls.clear();
1157
+ }
1158
+ // Progressive resource release for optimization
1159
+ async releaseMobileNetResources() {
1160
+ if (this.mobileNetSession) {
1161
+ this.mobileNetSession.release?.();
1162
+ this.mobileNetSession = undefined;
1163
+ }
1164
+ this.mobileNetClassMap = undefined;
1165
+ if (this.classificationLoadController) {
1166
+ this.classificationLoadController.abort();
1167
+ this.classificationLoadController = undefined;
1168
+ }
1169
+ if (this.debug) {
1170
+ console.log('🧹 Released MobileNet classification resources');
1171
+ }
1061
1172
  }
1062
- // Static method to clear all canvas pools (useful for memory management)
1063
- static clearCanvasPools() {
1173
+ // Optimize canvas pool by reducing to specific size
1174
+ optimizeCanvasPool(maxSize) {
1175
+ DetectionService.canvasPool.forEach((pool, key) => {
1176
+ while (pool.length > maxSize) {
1177
+ const canvas = pool.pop();
1178
+ // Explicitly clear and nullify for garbage collection
1179
+ if (canvas) {
1180
+ const ctx = canvas.getContext('2d');
1181
+ if (ctx) {
1182
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1183
+ }
1184
+ }
1185
+ }
1186
+ if (pool.length === 0) {
1187
+ DetectionService.canvasPool.delete(key);
1188
+ }
1189
+ });
1190
+ if (this.debug) {
1191
+ console.log(`🧹 Optimized canvas pool to max size: ${maxSize}`);
1192
+ }
1193
+ }
1194
+ // Clear all static canvas pools
1195
+ static clearAllCanvasPools() {
1196
+ DetectionService.canvasPool.forEach((pool) => {
1197
+ pool.forEach(canvas => {
1198
+ const ctx = canvas.getContext('2d');
1199
+ if (ctx) {
1200
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1201
+ }
1202
+ });
1203
+ pool.length = 0;
1204
+ });
1064
1205
  DetectionService.canvasPool.clear();
1065
1206
  }
1066
1207
  // Method to get pool statistics for debugging
@@ -1537,7 +1678,13 @@ const JaakStamps = class {
1537
1678
  }
1538
1679
  try {
1539
1680
  this.exitSession();
1540
- return { success: true };
1681
+ // Aggressive cleanup when stopping capture
1682
+ const cleanupResult = await this.aggressiveResourceCleanup();
1683
+ return {
1684
+ success: true,
1685
+ resourcesFreed: cleanupResult.freed,
1686
+ cleanupErrors: cleanupResult.errors
1687
+ };
1541
1688
  }
1542
1689
  catch (error) {
1543
1690
  return { success: false, error: error.message };
@@ -1762,6 +1909,29 @@ const JaakStamps = class {
1762
1909
  // Always allow getting capture delay, even during initialization
1763
1910
  return this.captureDelay;
1764
1911
  }
1912
+ async getResourceReport() {
1913
+ // Public method to get resource usage report
1914
+ return this.getResourceReportInternal();
1915
+ }
1916
+ async forceResourceCleanup() {
1917
+ // Public method to force aggressive resource cleanup
1918
+ const readyCheck = this.isComponentReady();
1919
+ if (!readyCheck.ready) {
1920
+ return { success: false, error: readyCheck.message };
1921
+ }
1922
+ try {
1923
+ const cleanupResult = await this.aggressiveResourceCleanup();
1924
+ return {
1925
+ success: true,
1926
+ resourcesFreed: cleanupResult.freed,
1927
+ errors: cleanupResult.errors,
1928
+ timestamp: new Date().toISOString()
1929
+ };
1930
+ }
1931
+ catch (error) {
1932
+ return { success: false, error: error.message };
1933
+ }
1934
+ }
1765
1935
  // Memory and device capability detection
1766
1936
  checkDeviceCapabilities() {
1767
1937
  const deviceMemory = navigator.deviceMemory;
@@ -1785,13 +1955,18 @@ const JaakStamps = class {
1785
1955
  memoryMB: null
1786
1956
  };
1787
1957
  }
1788
- // Safari tiene limitaciones conocidas con WASM
1958
+ // Safari moderno soporta WASM, solo bloquearlo en versiones muy antiguas
1789
1959
  if (isSafari) {
1790
- return {
1791
- canUseML: false,
1792
- reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
1793
- memoryMB: null
1794
- };
1960
+ const safariMatch = navigator.userAgent.match(/Version\/(\d+)/);
1961
+ const safariVersion = safariMatch ? parseInt(safariMatch[1]) : 0;
1962
+ // Solo bloquear Safari muy antiguo (< version 14)
1963
+ if (safariVersion > 0 && safariVersion < 14) {
1964
+ return {
1965
+ canUseML: false,
1966
+ reason: 'Safari antiguo detectado. Modo manual activado por compatibilidad.',
1967
+ memoryMB: null
1968
+ };
1969
+ }
1795
1970
  }
1796
1971
  return {
1797
1972
  canUseML: true,
@@ -2097,11 +2272,182 @@ const JaakStamps = class {
2097
2272
  }
2098
2273
  // Clear canvas pool
2099
2274
  this.canvasPool = [];
2275
+ // Clean DOM references and memory leaks
2276
+ this.cleanupDOMReferences();
2100
2277
  this.detectionBoxes = [];
2101
2278
  this.alignmentStartTime = undefined;
2102
2279
  this.hasDocumentDetected = false;
2103
2280
  this.serviceContainer?.cleanup();
2104
2281
  }
2282
+ // AGGRESSIVE RESOURCE CLEANUP METHODS
2283
+ // Clean DOM references and memory leaks
2284
+ cleanupDOMReferences() {
2285
+ // Remove event listeners that could maintain references
2286
+ if (this.videoRef) {
2287
+ // Remove common event listeners (if any were added)
2288
+ this.videoRef.srcObject = null;
2289
+ this.videoRef = undefined;
2290
+ }
2291
+ // Clean detection container
2292
+ if (this.detectionContainer) {
2293
+ this.detectionContainer = undefined;
2294
+ }
2295
+ // Clean references to images in the DOM and revoke blob URLs
2296
+ const images = this.el.shadowRoot?.querySelectorAll('img');
2297
+ images?.forEach(img => {
2298
+ if (img.src.startsWith('blob:')) {
2299
+ URL.revokeObjectURL(img.src);
2300
+ }
2301
+ img.src = '';
2302
+ });
2303
+ }
2304
+ // Clear browser cache and storage
2305
+ async clearBrowserCache() {
2306
+ try {
2307
+ // 1. Clear Service Worker cache if exists
2308
+ if ('serviceWorker' in navigator) {
2309
+ const registrations = await navigator.serviceWorker.getRegistrations();
2310
+ await Promise.all(registrations.map(registration => registration.unregister()));
2311
+ }
2312
+ // 2. Clear Cache API specific to the component
2313
+ const cacheNames = await caches.keys();
2314
+ const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
2315
+ await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
2316
+ // 3. Force garbage collection if available (only in dev)
2317
+ if (this.debug && window.gc) {
2318
+ window.gc();
2319
+ }
2320
+ }
2321
+ catch (error) {
2322
+ console.warn('Error clearing browser cache:', error);
2323
+ }
2324
+ }
2325
+ // Clear local storage specific to the component
2326
+ async clearLocalStorage() {
2327
+ try {
2328
+ // Clear localStorage specific to the component
2329
+ const keysToRemove = [];
2330
+ for (let i = 0; i < localStorage.length; i++) {
2331
+ const key = localStorage.key(i);
2332
+ if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
2333
+ keysToRemove.push(key);
2334
+ }
2335
+ }
2336
+ keysToRemove.forEach(key => localStorage.removeItem(key));
2337
+ // Clear sessionStorage
2338
+ const sessionKeysToRemove = [];
2339
+ for (let i = 0; i < sessionStorage.length; i++) {
2340
+ const key = sessionStorage.key(i);
2341
+ if (key && (key.includes('jaak') || key.includes('onnx'))) {
2342
+ sessionKeysToRemove.push(key);
2343
+ }
2344
+ }
2345
+ sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
2346
+ }
2347
+ catch (error) {
2348
+ console.warn('Error clearing local storage:', error);
2349
+ }
2350
+ }
2351
+ // Aggressive resource cleanup combining all strategies
2352
+ async aggressiveResourceCleanup() {
2353
+ const freed = [];
2354
+ const errors = [];
2355
+ try {
2356
+ // 1. Release ONNX resources
2357
+ this.releaseOnnxResources();
2358
+ freed.push('ONNX models');
2359
+ // 2. Clear model cache in detection service
2360
+ if (this.detectionService) {
2361
+ await this.detectionService.clearModelCache();
2362
+ freed.push('Model cache');
2363
+ }
2364
+ // 3. Clear browser cache
2365
+ await this.clearBrowserCache();
2366
+ freed.push('Browser cache');
2367
+ // 4. Clear local storage
2368
+ await this.clearLocalStorage();
2369
+ freed.push('Local storage');
2370
+ // 5. Clean DOM references
2371
+ this.cleanupDOMReferences();
2372
+ freed.push('DOM references');
2373
+ // 6. Optimize canvas pools
2374
+ if (this.detectionService) {
2375
+ this.detectionService.optimizeCanvasPool(0);
2376
+ freed.push('Canvas pools');
2377
+ }
2378
+ // 7. Clear component canvas pool
2379
+ this.canvasPool = [];
2380
+ freed.push('Component canvas pool');
2381
+ // 8. Force garbage collection (if available)
2382
+ if (this.debug && window.gc) {
2383
+ window.gc();
2384
+ freed.push('Garbage collection');
2385
+ }
2386
+ }
2387
+ catch (error) {
2388
+ errors.push(error.message);
2389
+ }
2390
+ return { freed, errors };
2391
+ }
2392
+ // Progressive resource release after front capture
2393
+ async onFrontCaptureComplete() {
2394
+ try {
2395
+ // Release classification model if not needed for back
2396
+ if (this.detectionService && !this.useDocumentClassification) {
2397
+ await this.detectionService.releaseMobileNetResources();
2398
+ }
2399
+ // Optimize canvas pool to minimum needed for back capture
2400
+ if (this.detectionService) {
2401
+ this.detectionService.optimizeCanvasPool(1);
2402
+ }
2403
+ // Reduce component canvas pool
2404
+ while (this.canvasPool.length > 1) {
2405
+ const canvas = this.canvasPool.pop();
2406
+ if (canvas) {
2407
+ const ctx = canvas.getContext('2d');
2408
+ if (ctx) {
2409
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2410
+ }
2411
+ }
2412
+ }
2413
+ if (this.debug) {
2414
+ console.log('🧹 Progressive cleanup after front capture completed');
2415
+ }
2416
+ }
2417
+ catch (error) {
2418
+ console.warn('Error in progressive cleanup:', error);
2419
+ }
2420
+ }
2421
+ // Resource reporting for debugging
2422
+ getResourceReportInternal() {
2423
+ const report = {
2424
+ timestamp: new Date().toISOString(),
2425
+ component: {
2426
+ hasVideoStream: !!this.videoStream,
2427
+ hasAnimationFrame: !!this.animationId,
2428
+ canvasPoolSize: this.canvasPool.length,
2429
+ detectionBoxesCount: this.detectionBoxes.length,
2430
+ performanceMonitorActive: !!this.performanceUpdateInterval
2431
+ }
2432
+ };
2433
+ // Add detection service stats if available
2434
+ if (this.detectionService) {
2435
+ report.detectionService = {
2436
+ isModelLoaded: this.detectionService.isModelLoaded(),
2437
+ canvasPoolStats: this.detectionService.getPoolStats()
2438
+ };
2439
+ }
2440
+ // Add memory info if available
2441
+ if ('memory' in performance) {
2442
+ const memInfo = performance.memory;
2443
+ report.memory = {
2444
+ usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
2445
+ totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
2446
+ jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
2447
+ };
2448
+ }
2449
+ return report;
2450
+ }
2105
2451
  render() {
2106
2452
  const captureState = this.stateManager?.getCaptureState() || {
2107
2453
  isVideoActive: false,
@@ -2111,7 +2457,7 @@ const JaakStamps = class {
2111
2457
  isCapturing: false
2112
2458
  };
2113
2459
  const cameraInfo = this.cameraInfoWithAutofocus;
2114
- return (index.h("div", { key: '51cc6d0f873691852f938940b131732252b970c1', class: "detector-container" }, index.h("div", { key: '009480499c0a2833bf9f6bd28003142ca3dd3750', class: "video-container" }, index.h("video", { key: '88a99a1a90c8d817e40549a255324b2da342797f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '7ede15ccd6f67b3081d907777de7b0d1a1b4cfd6', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
2460
+ return (index.h("div", { key: 'd32941c10b3b7193b2113691dae712a80315b430', class: "detector-container" }, index.h("div", { key: '7704c993cb9e6e3d29edf6b0adfe341dc1cc8e04', class: "video-container" }, index.h("video", { key: '2d0d46f1973c86e91f9fe9ede0aa938326c6071f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '9d62ea5221271a18938a9f80535ec7d960745ddb', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
2115
2461
  position: 'absolute',
2116
2462
  left: `${box.x}px`,
2117
2463
  top: `${box.y}px`,
@@ -2120,9 +2466,9 @@ const JaakStamps = class {
2120
2466
  border: '2px solid #32406C',
2121
2467
  pointerEvents: 'none',
2122
2468
  boxSizing: 'border-box'
2123
- } })))), this.isMaskReady && (index.h("div", { key: '63beb4330ab171a5e63237b1d83dcace61422018', class: "overlay-mask" }, index.h("div", { key: '93cfac6898de6ece6c7dd8f02ba2b79ce6591755', class: "card-outline" }, index.h("div", { key: '3087d3c51f42ea8d720cea4170dcb68e8bb385e1', class: "side side-top" }), index.h("div", { key: '992e311b895da161d3b5c49dde346ae378139641', class: "side side-right" }), index.h("div", { key: '5fb3d7b704f08177b2b9c6e4fbaf685f251a3e53', class: "side side-bottom" }), index.h("div", { key: '76f6e2cb856632c58809ccdabdf3f7d9a588c9da', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '142dfc187e07100278e16b19018e4b5fb96d964a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'bba078ce4c9585631b6a8154f878464b1f779c8b', class: "back-capture-section" }, index.h("div", { key: 'ec94c9ba2cb373a539f0e6cc3eb356294cebf3ea', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (index.h("button", { key: '30561e1fc88090c6bb4d0cd7909a33a9525b0ac2', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: '4b3e3356507c7f654705425e201d0303d727db72', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: '1abfada41312658eab4966d8fcdb711a6c4762e6', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: '3cab3ce751cb38174b6d061170a66ac654a90a38', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2469
+ } })))), this.isMaskReady && (index.h("div", { key: '38ebb5be5c55a184644411f06fe20221bdbdf693', class: "overlay-mask" }, index.h("div", { key: '6200c59e7a1212267e7335e1495cf9d9db2bca62', class: "card-outline" }, index.h("div", { key: 'c5321d2b5c09734cbca69fc8bb7849eef465a0ce', class: "side side-top" }), index.h("div", { key: 'd73b5afcb6781307460b8827c491673ebbfc40ef', class: "side side-right" }), index.h("div", { key: '787dfa0792c5a00f8a83feaa423ca22092e0a8bf', class: "side side-bottom" }), index.h("div", { key: '50736a6b9c7c8847d1dabaa22464e26f284da11e', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'ec8cc90cd589e4f713becb57b6787ce4f57318d4', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'd9d391c02855ac754607cbf77c119153d1f6e56f', class: "back-capture-section" }, index.h("div", { key: 'a3ebe694f74958b7910fc355a66cbf1394fb5a2a', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (index.h("button", { key: 'bc6baf4e00199dfdd50bbfbf8abbcede0fb719e9', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: '3a56f9132fce454c792818925c8bed5557e69eed', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: '2fe7e44bc91b73581d6333b7c404ec018a390372', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: 'ebb2d40cbcb646c9bd70ebe278b0643f9335779b', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2124
2470
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
2125
- : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'b773fe579cc039fd90ff5e61976752bcf631d16e', class: "camera-controls" }, index.h("button", { key: '8535463b13cc02e0030d14c1d57390e560572b4f', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: 'c4b159cf28c24ba2ec0a658bf6f1296c6aba47e3', class: "camera-selector-dropdown" }, index.h("div", { key: '033273241b77cedc414dee00b70926c3a659910a', class: "camera-selector-header" }, index.h("span", { key: '83fb4c7ae2387b85134a3ca0c5a156beaf73e481' }, "Seleccionar C\u00E1mara"), index.h("button", { key: 'f65b6e994603e616af8d69df198c3d8471125695', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: 'fd90d4c4db1e910c5d05b9ce19a252967c71ad1f', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '49faa944b3c0c052e18f48e9dda2758cd2c0ae6b', class: "device-info" }, index.h("small", { key: '15e95207aef60a78b2912a6c00a1b61ab760b72d' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'a67a3795837adcd307fa3666c338376cecae16d0', class: "manual-capture-section" }, index.h("button", { key: '7f21ccd9b622d97bfce80962038de8be7cd6e33d', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: 'a4b5e865f373cc0e7787098eedd2d4df413d310f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: '0b02e81963136dfda62a8980bc5a1f5662509fb8', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '5eb16e12a7ce16720ca7bdd11aefd902ba772e24', class: "flip-animation" }, index.h("div", { key: '4c6f3ed33c5080e9e18d17c4594cf394250c3517', class: "id-card-icon" }), index.h("div", { key: 'd848d7a98540988a8f491bcf8f6f6ad9c5dccfd4', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '94863e6d0c404188d548db428af918274d882652', class: "success-animation" }, index.h("div", { key: 'a90b777e7b7eb533fabec2c15f79a4bd18778f0b', class: "check-icon" }), index.h("div", { key: '2152e5a3756f58f996c520b58cb1b6cca1763e94', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: 'ae18e06bf291429a74d1b6c8a0c4d37d473f7b30', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: 'b6b03ed285868d7d3350a2a73e59cb1d65a47401', class: "status-spinner" })), index.h("div", { key: 'dc85b49dd1cfcddf7300fb3f327d2e1de8ab9bff', class: "status-content" }, index.h("div", { key: '82461ed0c6d70a0c95ae6b7144ff1d8b8371c6fc', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: '24aa77ec92ca5a48d1bed4cf98ae591bdbff74f0', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '3b56a0b305ca07407c09a73624a4040cf6625b56', class: "performance-monitor" }, index.h("div", { key: '52f975da1502f6b08a0b2c828bfd0be8d10e36f8', class: "performance-expanded" }, index.h("div", { key: 'ea2f444255add0415c7f88cdea2733e1244b17bd', class: "metrics-row" }, index.h("div", { key: 'e3c2370f1a900594940cb321d5b1661690018025', class: "metric-compact" }, index.h("span", { key: '25c2f402c9b69f21736c494420e7a396673e9893', class: "metric-label" }, "FPS"), index.h("span", { key: '505de593f7927544cd874332127e84c72131da72', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '5cb1ff4bc1ac33988404e59dac3d01d5a55893b9', class: "metric-compact" }, index.h("span", { key: 'ce701538abbfe797b288520ab4c277bf7a1d6a05', class: "metric-label" }, "MEM"), index.h("span", { key: 'efc383e993ad72c20cbfc630e20b2bf9a617a02d', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'b6b6b832740c44d4f78cddf99ba1d139dc3d4739', class: "metrics-row" }, index.h("div", { key: '3a8903a05aed8adb1626f93bee6ba258fbea9a7d', class: "metric-compact" }, index.h("span", { key: 'aabc56057156217b3be771ab3b1e92590fa886c5', class: "metric-label" }, "INF"), index.h("span", { key: '1080df34860cc891cda94c5aacc754cbde18a138', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: 'ac152093d4c0cc25862aac638fc4693dd0c51a69', class: "metric-compact" }, index.h("span", { key: 'b23712dbc0b9b8e9cb6e2c659e5af3844ec0ebe7', class: "metric-label" }, "FRAME"), index.h("span", { key: '461f88519b339fbad6444f009312f72efbc6e450', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '8f99afe2c94f37fd3a6feb082cc9aa96f54fc5e1', class: "metrics-row" }, index.h("div", { key: 'da7ac0fd842f38bbdcac01550764f9fe1bd666a2', class: "metric-compact" }, index.h("span", { key: 'a5dcd9c8753ba42038f869efa50ae9602cdf6256', class: "metric-label" }, "DET"), index.h("span", { key: 'e98b91ea98a41637baea1fcfda0bb590cf5d24a0', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '3ac54161a712d305a098eae0a3b5d21a731c6019', class: "metric-compact" }, index.h("span", { key: '3c8cc465433be3b490887457a6c3967dbbda6a7d', class: "metric-label" }, "RATE"), index.h("span", { key: 'f2e083118fe107c7dc2a675e3f661466cea6d479', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '10b6c32b0460e194d680c63b30a078c51a26e9c9', class: "watermark" }, index.h("img", { key: 'f5954a0ba3f90a642e0d5c6c14d5b549cd193018', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2471
+ : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: '4373e4f9ab73d93ce32ae52fbdc14f7bcb69e68e', class: "camera-controls" }, index.h("button", { key: '5d2afe5b71b02d379fe2ec82bd57a86f614fbea6', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '4fe5e10260a1299a0dc8517646bed8134fe38d9a', class: "camera-selector-dropdown" }, index.h("div", { key: 'b041acd0b0292864d20ad256301e7be714854424', class: "camera-selector-header" }, index.h("span", { key: '2769170dc9ce382b420a719cad6f9bb1f5e4420f' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '0c54f73bb29041ba88e1838c5b1c8a54eafdc67d', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: 'd72ae015c5e89cc3396c14406591308eda4d4c4a', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '5933bb5c8345debf2eb78fc2cd3900cb09a29335', class: "device-info" }, index.h("small", { key: 'a126d886feaede4f9657abee9b45d1eff1910e25' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '62ab98c6afa629583ff13f1792422ef1c5012883', class: "manual-capture-section" }, index.h("button", { key: '93a42f7b0626a63ab795652eb9eb81657f4fec13', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: '77b5aaf62edab4fe8eb68f37083121d9f8493797', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: '399242238fd4a2a4985af0f30faed504d73a0adc', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '06add02bc244511dc0dc82bf206f41d9ce3cdcd3', class: "flip-animation" }, index.h("div", { key: 'e3949d8e41bc496bf503d1dcd34df53d2bf876aa', class: "id-card-icon" }), index.h("div", { key: 'e8ebdd9d29e0b33dcfc506b40e67e0d4af8cb0bb', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: 'f72f6fc4d42c7b10d98c8aff7541596afe131883', class: "success-animation" }, index.h("div", { key: '8d0d422b37caa97f695f62c0094c52c3c12c7bda', class: "check-icon" }), index.h("div", { key: 'd9d673dbfd2af4a1b8ca1a775e5b318326d70446', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: 'fdfa1239ddea40bbbabf032542a7160fae3f3c96', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '34bb9d5b83e4383c4f640ccf11189484514e4efd', class: "status-spinner" })), index.h("div", { key: '131bcd252fc7e1f6eb27332d64fd249b5889aa9d', class: "status-content" }, index.h("div", { key: '911b7324456e84842fa3c14c9a6fd3ba475804cf', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'e938b9294ee3c6125b133bf737caa4a33a016872', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: 'cab385a12f6ccf8579d2d073c1ed987bf815efeb', class: "performance-monitor" }, index.h("div", { key: '903f8a7005a98c4b99eb7d4c91662911cee8beae', class: "performance-expanded" }, index.h("div", { key: 'f1ba17573d53442a23212e2c8fdd132bd3ddf4f0', class: "metrics-row" }, index.h("div", { key: '71357107dcc19749e5af2d5668df535db65cb986', class: "metric-compact" }, index.h("span", { key: '9ad542fb9445340c669c3d18fc293e2364fc5dcc', class: "metric-label" }, "FPS"), index.h("span", { key: 'ab0fb401df5dc792c0a641a574851aec48093862', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: 'a650ab2a1af27829d4a63a264f00907dfba363c6', class: "metric-compact" }, index.h("span", { key: '56e195b0fd468f2a457a66b2342ed6a80fbef0a0', class: "metric-label" }, "MEM"), index.h("span", { key: '265445131d4492c6e2f7571d367f52c93e00b711', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '56023a82a1978c7c2f8f08edac729f39efcafa63', class: "metrics-row" }, index.h("div", { key: '6f1ceb668fc246ec11bfeee3f14c745f21d4986e', class: "metric-compact" }, index.h("span", { key: '92efba3ee31c5b9a382f1e205584950217482722', class: "metric-label" }, "INF"), index.h("span", { key: 'fc2baec34fd2f0696a216c487753b94b8bacaf7e', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '683eeb875e96c94cf36c403385c2764239fb5a4d', class: "metric-compact" }, index.h("span", { key: '33ba3f357868f7feaaeefab35e3ecc35d3b5b726', class: "metric-label" }, "FRAME"), index.h("span", { key: 'f496be674b0328556d27a9a9b2f24ffa50ff76ea', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '521f275481d35c2132dbcf138e4bbda84a0b97b2', class: "metrics-row" }, index.h("div", { key: '17acc0238696816077b80b66aee317ae9ab20e75', class: "metric-compact" }, index.h("span", { key: 'cac1a4ee661de14739623a1a6f0149ba72fa918c', class: "metric-label" }, "DET"), index.h("span", { key: '70071c404c5e50001d813f1f3e0d1002776ed887', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '26a00ad1c95e6c2b8feaafc36103fb7fb869039f', class: "metric-compact" }, index.h("span", { key: 'a1c7fb80b6795f10111c1ed403de933aadcbe450', class: "metric-label" }, "RATE"), index.h("span", { key: '49471b7bfa2d6ca6357a39bf611738fd6344764a', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '3870b7399d2d5068e7f59177225476b00bef7fc7', class: "watermark" }, index.h("img", { key: '5030e9f14eebd4546207ced129d7d00d85abb189', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2126
2472
  }
2127
2473
  // Utility methods
2128
2474
  updateDetectionBoxes(boxes) {
@@ -2346,6 +2692,8 @@ const JaakStamps = class {
2346
2692
  showFlipAnimation: true
2347
2693
  });
2348
2694
  this.triggerRerender();
2695
+ // Progressive resource cleanup after front capture
2696
+ await this.onFrontCaptureComplete();
2349
2697
  setTimeout(() => {
2350
2698
  this.stateManager.updateCaptureState({
2351
2699
  showFlipAnimation: false,
@@ -2421,6 +2769,8 @@ const JaakStamps = class {
2421
2769
  isDetectionPaused: true,
2422
2770
  showFlipAnimation: true
2423
2771
  });
2772
+ // Progressive resource cleanup after front capture
2773
+ await this.onFrontCaptureComplete();
2424
2774
  setTimeout(() => {
2425
2775
  this.stateManager.updateCaptureState({
2426
2776
  showFlipAnimation: false,
@@ -2598,6 +2948,16 @@ const JaakStamps = class {
2598
2948
  setTimeout(() => {
2599
2949
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2600
2950
  }, 3000);
2951
+ // Aggressive cleanup after process completion with delay
2952
+ setTimeout(async () => {
2953
+ const cleanupResult = await this.aggressiveResourceCleanup();
2954
+ if (this.debug) {
2955
+ console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
2956
+ if (cleanupResult.errors.length > 0) {
2957
+ console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
2958
+ }
2959
+ }
2960
+ }, 1000); // Wait 1 second after completion
2601
2961
  }
2602
2962
  stopDetection() {
2603
2963
  if (this.animationId) {