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