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

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 +364 -11
  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 +254 -6
  7. package/dist/collection/components/my-component/my-component.js.map +1 -1
  8. package/dist/collection/services/DetectionService.js +146 -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 +367 -12
  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 +364 -11
  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-8e25497e.entry.js +2 -0
  20. package/dist/jaak-stamps-webcomponent/p-8e25497e.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,26 @@ class DetectionService {
665
669
  return;
666
670
  }
667
671
  try {
672
+ // Create AbortController for this load operation
673
+ this.modelLoadController = new AbortController();
668
674
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
669
675
  try {
670
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
676
+ // Load model with abort signal support
677
+ const response = await fetch(this.MODEL_PATH, {
678
+ signal: this.modelLoadController.signal
679
+ });
680
+ if (!response.ok) {
681
+ throw new Error(`Failed to fetch model: ${response.status}`);
682
+ }
683
+ const modelBuffer = await response.arrayBuffer();
684
+ this.session = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
671
685
  }
672
686
  catch (error) {
687
+ // Handle abort signal
688
+ if (error.name === 'AbortError') {
689
+ console.log('Model loading was cancelled');
690
+ return;
691
+ }
673
692
  // Múltiples tipos de errores de memoria que pueden ocurrir
674
693
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
675
694
  error.message.includes('Out of memory') ||
@@ -691,7 +710,15 @@ class DetectionService {
691
710
  sessionLogVerbosityLevel: 0,
692
711
  };
693
712
  try {
694
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
713
+ // Retry with fallback options and abort signal
714
+ const fallbackResponse = await fetch(this.MODEL_PATH, {
715
+ signal: this.modelLoadController.signal
716
+ });
717
+ if (!fallbackResponse.ok) {
718
+ throw new Error(`Failed to fetch model with fallback: ${fallbackResponse.status}`);
719
+ }
720
+ const fallbackBuffer = await fallbackResponse.arrayBuffer();
721
+ this.session = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
695
722
  }
696
723
  catch (fallbackError) {
697
724
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -712,9 +739,13 @@ class DetectionService {
712
739
  return;
713
740
  }
714
741
  try {
742
+ // Create AbortController for classification model loading
743
+ this.classificationLoadController = new AbortController();
715
744
  // Try to load class map (optional - SqueezeNet may not need it for basic classification)
716
745
  try {
717
- const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
746
+ const classResponse = await fetch(this.MOBILENET_CLASSES_PATH, {
747
+ signal: this.classificationLoadController.signal
748
+ });
718
749
  if (classResponse.ok) {
719
750
  this.mobileNetClassMap = await classResponse.json();
720
751
  }
@@ -732,9 +763,22 @@ class DetectionService {
732
763
  // Load model
733
764
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
734
765
  try {
735
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
766
+ // Load classification model with abort signal support
767
+ const response = await fetch(this.MOBILENET_MODEL_PATH, {
768
+ signal: this.classificationLoadController.signal
769
+ });
770
+ if (!response.ok) {
771
+ throw new Error(`Failed to fetch classification model: ${response.status}`);
772
+ }
773
+ const modelBuffer = await response.arrayBuffer();
774
+ this.mobileNetSession = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
736
775
  }
737
776
  catch (error) {
777
+ // Handle abort signal
778
+ if (error.name === 'AbortError') {
779
+ console.log('Classification model loading was cancelled');
780
+ return;
781
+ }
738
782
  // Múltiples tipos de errores de memoria que pueden ocurrir
739
783
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
740
784
  error.message.includes('Out of memory') ||
@@ -756,7 +800,15 @@ class DetectionService {
756
800
  sessionLogVerbosityLevel: 0,
757
801
  };
758
802
  try {
759
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
803
+ // Retry with fallback options and abort signal
804
+ const fallbackResponse = await fetch(this.MOBILENET_MODEL_PATH, {
805
+ signal: this.classificationLoadController.signal
806
+ });
807
+ if (!fallbackResponse.ok) {
808
+ throw new Error(`Failed to fetch classification model with fallback: ${fallbackResponse.status}`);
809
+ }
810
+ const fallbackBuffer = await fallbackResponse.arrayBuffer();
811
+ this.mobileNetSession = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
760
812
  }
761
813
  catch (fallbackError) {
762
814
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -946,6 +998,9 @@ class DetectionService {
946
998
  return alignment.top && alignment.right && alignment.bottom && alignment.left;
947
999
  }
948
1000
  cleanup() {
1001
+ // Cancel any ongoing requests
1002
+ this.abortPendingRequests();
1003
+ // Cleanup ONNX resources
949
1004
  this.cleanupCanvasPool();
950
1005
  if (this.session) {
951
1006
  this.session.release?.();
@@ -957,6 +1012,8 @@ class DetectionService {
957
1012
  }
958
1013
  this.mobileNetClassMap = undefined;
959
1014
  this.modelLoaded = false;
1015
+ // Cleanup blob URLs
1016
+ this.revokeModelBlobUrls();
960
1017
  }
961
1018
  initializeCanvasPool() {
962
1019
  this.preprocessCanvas = document.createElement('canvas');
@@ -1054,11 +1111,93 @@ class DetectionService {
1054
1111
  const pool = DetectionService.canvasPool.get(key);
1055
1112
  // Only return to pool if not at max capacity
1056
1113
  if (pool.length < DetectionService.MAX_POOL_SIZE) {
1114
+ const ctx = canvas.getContext('2d');
1115
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1057
1116
  pool.push(canvas);
1058
1117
  }
1118
+ // If pool is full, let the canvas be garbage collected
1119
+ }
1120
+ // Resource management methods for cache and browser optimization
1121
+ async clearModelCache() {
1122
+ try {
1123
+ // Clear cache specific to models
1124
+ const cacheNames = await caches.keys();
1125
+ const modelCaches = cacheNames.filter(name => name.includes('onnx') ||
1126
+ name.includes('jaak-static') ||
1127
+ name.includes('squeezenet') ||
1128
+ name.includes('ddmyp'));
1129
+ await Promise.all(modelCaches.map(cacheName => caches.delete(cacheName)));
1130
+ if (this.debug) {
1131
+ console.log(`🧹 Cleared ${modelCaches.length} model caches`);
1132
+ }
1133
+ }
1134
+ catch (error) {
1135
+ console.warn('Error clearing model cache:', error);
1136
+ }
1137
+ }
1138
+ abortPendingRequests() {
1139
+ // Cancel any fetch in progress
1140
+ if (this.modelLoadController) {
1141
+ this.modelLoadController.abort();
1142
+ this.modelLoadController = undefined;
1143
+ }
1144
+ if (this.classificationLoadController) {
1145
+ this.classificationLoadController.abort();
1146
+ this.classificationLoadController = undefined;
1147
+ }
1148
+ }
1149
+ revokeModelBlobUrls() {
1150
+ // Revoke any blob URLs created for models
1151
+ this.modelBlobUrls.forEach(url => URL.revokeObjectURL(url));
1152
+ this.modelBlobUrls.clear();
1153
+ }
1154
+ // Progressive resource release for optimization
1155
+ async releaseMobileNetResources() {
1156
+ if (this.mobileNetSession) {
1157
+ this.mobileNetSession.release?.();
1158
+ this.mobileNetSession = undefined;
1159
+ }
1160
+ this.mobileNetClassMap = undefined;
1161
+ if (this.classificationLoadController) {
1162
+ this.classificationLoadController.abort();
1163
+ this.classificationLoadController = undefined;
1164
+ }
1165
+ if (this.debug) {
1166
+ console.log('🧹 Released MobileNet classification resources');
1167
+ }
1059
1168
  }
1060
- // Static method to clear all canvas pools (useful for memory management)
1061
- static clearCanvasPools() {
1169
+ // Optimize canvas pool by reducing to specific size
1170
+ optimizeCanvasPool(maxSize) {
1171
+ DetectionService.canvasPool.forEach((pool, key) => {
1172
+ while (pool.length > maxSize) {
1173
+ const canvas = pool.pop();
1174
+ // Explicitly clear and nullify for garbage collection
1175
+ if (canvas) {
1176
+ const ctx = canvas.getContext('2d');
1177
+ if (ctx) {
1178
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1179
+ }
1180
+ }
1181
+ }
1182
+ if (pool.length === 0) {
1183
+ DetectionService.canvasPool.delete(key);
1184
+ }
1185
+ });
1186
+ if (this.debug) {
1187
+ console.log(`🧹 Optimized canvas pool to max size: ${maxSize}`);
1188
+ }
1189
+ }
1190
+ // Clear all static canvas pools
1191
+ static clearAllCanvasPools() {
1192
+ DetectionService.canvasPool.forEach((pool) => {
1193
+ pool.forEach(canvas => {
1194
+ const ctx = canvas.getContext('2d');
1195
+ if (ctx) {
1196
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1197
+ }
1198
+ });
1199
+ pool.length = 0;
1200
+ });
1062
1201
  DetectionService.canvasPool.clear();
1063
1202
  }
1064
1203
  // Method to get pool statistics for debugging
@@ -1535,7 +1674,13 @@ const JaakStamps = class {
1535
1674
  }
1536
1675
  try {
1537
1676
  this.exitSession();
1538
- return { success: true };
1677
+ // Aggressive cleanup when stopping capture
1678
+ const cleanupResult = await this.aggressiveResourceCleanup();
1679
+ return {
1680
+ success: true,
1681
+ resourcesFreed: cleanupResult.freed,
1682
+ cleanupErrors: cleanupResult.errors
1683
+ };
1539
1684
  }
1540
1685
  catch (error) {
1541
1686
  return { success: false, error: error.message };
@@ -1760,6 +1905,29 @@ const JaakStamps = class {
1760
1905
  // Always allow getting capture delay, even during initialization
1761
1906
  return this.captureDelay;
1762
1907
  }
1908
+ async getResourceReport() {
1909
+ // Public method to get resource usage report
1910
+ return this.getResourceReportInternal();
1911
+ }
1912
+ async forceResourceCleanup() {
1913
+ // Public method to force aggressive resource cleanup
1914
+ const readyCheck = this.isComponentReady();
1915
+ if (!readyCheck.ready) {
1916
+ return { success: false, error: readyCheck.message };
1917
+ }
1918
+ try {
1919
+ const cleanupResult = await this.aggressiveResourceCleanup();
1920
+ return {
1921
+ success: true,
1922
+ resourcesFreed: cleanupResult.freed,
1923
+ errors: cleanupResult.errors,
1924
+ timestamp: new Date().toISOString()
1925
+ };
1926
+ }
1927
+ catch (error) {
1928
+ return { success: false, error: error.message };
1929
+ }
1930
+ }
1763
1931
  // Memory and device capability detection
1764
1932
  checkDeviceCapabilities() {
1765
1933
  const deviceMemory = navigator.deviceMemory;
@@ -2095,11 +2263,182 @@ const JaakStamps = class {
2095
2263
  }
2096
2264
  // Clear canvas pool
2097
2265
  this.canvasPool = [];
2266
+ // Clean DOM references and memory leaks
2267
+ this.cleanupDOMReferences();
2098
2268
  this.detectionBoxes = [];
2099
2269
  this.alignmentStartTime = undefined;
2100
2270
  this.hasDocumentDetected = false;
2101
2271
  this.serviceContainer?.cleanup();
2102
2272
  }
2273
+ // AGGRESSIVE RESOURCE CLEANUP METHODS
2274
+ // Clean DOM references and memory leaks
2275
+ cleanupDOMReferences() {
2276
+ // Remove event listeners that could maintain references
2277
+ if (this.videoRef) {
2278
+ // Remove common event listeners (if any were added)
2279
+ this.videoRef.srcObject = null;
2280
+ this.videoRef = undefined;
2281
+ }
2282
+ // Clean detection container
2283
+ if (this.detectionContainer) {
2284
+ this.detectionContainer = undefined;
2285
+ }
2286
+ // Clean references to images in the DOM and revoke blob URLs
2287
+ const images = this.el.shadowRoot?.querySelectorAll('img');
2288
+ images?.forEach(img => {
2289
+ if (img.src.startsWith('blob:')) {
2290
+ URL.revokeObjectURL(img.src);
2291
+ }
2292
+ img.src = '';
2293
+ });
2294
+ }
2295
+ // Clear browser cache and storage
2296
+ async clearBrowserCache() {
2297
+ try {
2298
+ // 1. Clear Service Worker cache if exists
2299
+ if ('serviceWorker' in navigator) {
2300
+ const registrations = await navigator.serviceWorker.getRegistrations();
2301
+ await Promise.all(registrations.map(registration => registration.unregister()));
2302
+ }
2303
+ // 2. Clear Cache API specific to the component
2304
+ const cacheNames = await caches.keys();
2305
+ const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
2306
+ await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
2307
+ // 3. Force garbage collection if available (only in dev)
2308
+ if (this.debug && window.gc) {
2309
+ window.gc();
2310
+ }
2311
+ }
2312
+ catch (error) {
2313
+ console.warn('Error clearing browser cache:', error);
2314
+ }
2315
+ }
2316
+ // Clear local storage specific to the component
2317
+ async clearLocalStorage() {
2318
+ try {
2319
+ // Clear localStorage specific to the component
2320
+ const keysToRemove = [];
2321
+ for (let i = 0; i < localStorage.length; i++) {
2322
+ const key = localStorage.key(i);
2323
+ if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
2324
+ keysToRemove.push(key);
2325
+ }
2326
+ }
2327
+ keysToRemove.forEach(key => localStorage.removeItem(key));
2328
+ // Clear sessionStorage
2329
+ const sessionKeysToRemove = [];
2330
+ for (let i = 0; i < sessionStorage.length; i++) {
2331
+ const key = sessionStorage.key(i);
2332
+ if (key && (key.includes('jaak') || key.includes('onnx'))) {
2333
+ sessionKeysToRemove.push(key);
2334
+ }
2335
+ }
2336
+ sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
2337
+ }
2338
+ catch (error) {
2339
+ console.warn('Error clearing local storage:', error);
2340
+ }
2341
+ }
2342
+ // Aggressive resource cleanup combining all strategies
2343
+ async aggressiveResourceCleanup() {
2344
+ const freed = [];
2345
+ const errors = [];
2346
+ try {
2347
+ // 1. Release ONNX resources
2348
+ this.releaseOnnxResources();
2349
+ freed.push('ONNX models');
2350
+ // 2. Clear model cache in detection service
2351
+ if (this.detectionService) {
2352
+ await this.detectionService.clearModelCache();
2353
+ freed.push('Model cache');
2354
+ }
2355
+ // 3. Clear browser cache
2356
+ await this.clearBrowserCache();
2357
+ freed.push('Browser cache');
2358
+ // 4. Clear local storage
2359
+ await this.clearLocalStorage();
2360
+ freed.push('Local storage');
2361
+ // 5. Clean DOM references
2362
+ this.cleanupDOMReferences();
2363
+ freed.push('DOM references');
2364
+ // 6. Optimize canvas pools
2365
+ if (this.detectionService) {
2366
+ this.detectionService.optimizeCanvasPool(0);
2367
+ freed.push('Canvas pools');
2368
+ }
2369
+ // 7. Clear component canvas pool
2370
+ this.canvasPool = [];
2371
+ freed.push('Component canvas pool');
2372
+ // 8. Force garbage collection (if available)
2373
+ if (this.debug && window.gc) {
2374
+ window.gc();
2375
+ freed.push('Garbage collection');
2376
+ }
2377
+ }
2378
+ catch (error) {
2379
+ errors.push(error.message);
2380
+ }
2381
+ return { freed, errors };
2382
+ }
2383
+ // Progressive resource release after front capture
2384
+ async onFrontCaptureComplete() {
2385
+ try {
2386
+ // Release classification model if not needed for back
2387
+ if (this.detectionService && !this.useDocumentClassification) {
2388
+ await this.detectionService.releaseMobileNetResources();
2389
+ }
2390
+ // Optimize canvas pool to minimum needed for back capture
2391
+ if (this.detectionService) {
2392
+ this.detectionService.optimizeCanvasPool(1);
2393
+ }
2394
+ // Reduce component canvas pool
2395
+ while (this.canvasPool.length > 1) {
2396
+ const canvas = this.canvasPool.pop();
2397
+ if (canvas) {
2398
+ const ctx = canvas.getContext('2d');
2399
+ if (ctx) {
2400
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2401
+ }
2402
+ }
2403
+ }
2404
+ if (this.debug) {
2405
+ console.log('🧹 Progressive cleanup after front capture completed');
2406
+ }
2407
+ }
2408
+ catch (error) {
2409
+ console.warn('Error in progressive cleanup:', error);
2410
+ }
2411
+ }
2412
+ // Resource reporting for debugging
2413
+ getResourceReportInternal() {
2414
+ const report = {
2415
+ timestamp: new Date().toISOString(),
2416
+ component: {
2417
+ hasVideoStream: !!this.videoStream,
2418
+ hasAnimationFrame: !!this.animationId,
2419
+ canvasPoolSize: this.canvasPool.length,
2420
+ detectionBoxesCount: this.detectionBoxes.length,
2421
+ performanceMonitorActive: !!this.performanceUpdateInterval
2422
+ }
2423
+ };
2424
+ // Add detection service stats if available
2425
+ if (this.detectionService) {
2426
+ report.detectionService = {
2427
+ isModelLoaded: this.detectionService.isModelLoaded(),
2428
+ canvasPoolStats: this.detectionService.getPoolStats()
2429
+ };
2430
+ }
2431
+ // Add memory info if available
2432
+ if ('memory' in performance) {
2433
+ const memInfo = performance.memory;
2434
+ report.memory = {
2435
+ usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
2436
+ totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
2437
+ jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
2438
+ };
2439
+ }
2440
+ return report;
2441
+ }
2103
2442
  render() {
2104
2443
  const captureState = this.stateManager?.getCaptureState() || {
2105
2444
  isVideoActive: false,
@@ -2109,7 +2448,7 @@ const JaakStamps = class {
2109
2448
  isCapturing: false
2110
2449
  };
2111
2450
  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: {
2451
+ return (h("div", { key: 'ca44b9eacbde9f688f2c41aa21ad35a2b882002b', class: "detector-container" }, h("div", { key: '86c7b5d31ed69276b62fa43e01c92b0c0b9f298d', class: "video-container" }, h("video", { key: 'bb310544c17ae734d8b42ed2617748c2f69743ff', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '078a79a0c6386ca6c040e629acbf4f52f12d8e29', 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
2452
  position: 'absolute',
2114
2453
  left: `${box.x}px`,
2115
2454
  top: `${box.y}px`,
@@ -2118,9 +2457,9 @@ const JaakStamps = class {
2118
2457
  border: '2px solid #32406C',
2119
2458
  pointerEvents: 'none',
2120
2459
  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
2460
+ } })))), this.isMaskReady && (h("div", { key: '990015ff5b2ded89405dd2b891c027aad8788f3e', class: "overlay-mask" }, h("div", { key: 'dd9145140987addb1b6e4cc2119b8e9f89003726', class: "card-outline" }, h("div", { key: 'e8f4ec692a8b80c7a98075e4f708b79a272db67b', class: "side side-top" }), h("div", { key: '2577253b40425ecdcb3dd51b2f4a43c15c83ca79', class: "side side-right" }), h("div", { key: '30f4454020c00cf4fb92308af5ecf1843ec24005', class: "side side-bottom" }), h("div", { key: '3459f634987b8396106fff23fa1c99b295ee7d0a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '84f4d70947e211a9c6b296531d53420223d2ca34', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '7c28e86e8de6c33bf7d7baab3083c5f6a0ab060f', class: "back-capture-section" }, h("div", { key: '27bdcf9cfe9790c4bbad484423f6f4c353ccf0b3', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: 'ed0320b1df412bf53cbe4e4119f6d769926eb2c5', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '0bba706c8ebda59c33faca8e97b7536146462d21', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'b072c46b25d16a04c5e0a8f3975d8aa8d24576d5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: 'efd0827d0f19c9072d49d1b3c0046ef6c95a842c', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2122
2461
  ? `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" })))));
2462
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'bd0303e8ce85211e4c68fc3cd79545db7c74288b', class: "camera-controls" }, h("button", { key: 'e4327d260b988b67e0de3654c72f24455cd3bbbd', 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: '35da16baf4c62466c2a66650209a1daf842c0db2', class: "camera-selector-dropdown" }, h("div", { key: '7035dd0e22f614cf239a015c6d59531b68fa74a9', class: "camera-selector-header" }, h("span", { key: 'ae026f31931f52ea0daf72391a9de4ff9e4b88f1' }, "Seleccionar C\u00E1mara"), h("button", { key: '40f2ba0770bafea71fedbf849938010e743be776', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '6f26927094a724a35ebb07ef701caf14f296a837', 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: '082236981c369f8fd487f036439b16e6560514be', class: "device-info" }, h("small", { key: 'ccbbfa0bca96395f574bda71103338ae9226f709' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '6baa487409503777e90453b39fd30649941cbbe4', class: "manual-capture-section" }, h("button", { key: '893f4715ca3315613f779603f178459955f686f0', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '7c30b680fe84f4c8227beba45d8416f1edb8348f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: 'd8ef052cf67d7bda4b3534febf96efa2cdcc6bf7', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '3dae7634e770dda2655b1042ec8fb997b3cb2220', class: "flip-animation" }, h("div", { key: 'a977fc5e889369121bacc06cf5213dfee39d858d', class: "id-card-icon" }), h("div", { key: '4f052cdc8c7b493cddce90e108e976325c9bab8e', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '5117cd6e63e1e0763c1503f6f60f3c7ef60dd665', class: "success-animation" }, h("div", { key: 'd11128c45c7118915cc0dd9c25845bb55b4b3eb2', class: "check-icon" }), h("div", { key: '693e197bf8a4c91ac7ddedb457d15aa3aa46a5d5', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '89caaa9d78eb522e0fadfd1c0e844e05d29b8286', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '3f41d40b05b71e13e7e1d0c0e980b48308cba9d6', class: "status-spinner" })), h("div", { key: '8674480928f4f4a77099c0600ed84f0e27903379', class: "status-content" }, h("div", { key: 'e88f4044ab8115a6a5cbb78cc4030975eef9149a', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'ad581e42c30c304b837711549148c11b801dee5e', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '1b371c1b47f3ba699885db00fef52eb448f9e245', class: "performance-monitor" }, h("div", { key: '8b9ac60449a15e9fb966c329b2e6c9285c0228c0', class: "performance-expanded" }, h("div", { key: '760c11a3ed28e248a144d53a92fd8adee6822ff2', class: "metrics-row" }, h("div", { key: '858ab8b106f1d12210e4e15c423ab3bab11bdb60', class: "metric-compact" }, h("span", { key: '65fe2479c3822d99452e7e5faa93c25a80765024', class: "metric-label" }, "FPS"), h("span", { key: '9faf7763d1284a6f3c1e15fb0f3c99f1d45bb4dc', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'c95ed8fa8101f42bfba5cc8ca7debbad028caf42', class: "metric-compact" }, h("span", { key: '3555b3778cb71e959d132d6aa563f58f757915d8', class: "metric-label" }, "MEM"), h("span", { key: 'd72b0491147a6f80dcf33f5d72dd8dae5898f44a', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'cd4dc81f28f9e59a36a0295dde7bfee89f50d3e2', class: "metrics-row" }, h("div", { key: 'c43d8624c5da256ce3e97f67b022bdd98fb04e07', class: "metric-compact" }, h("span", { key: 'adb402d83809a90580b81544f31b5331bdff59dd', class: "metric-label" }, "INF"), h("span", { key: 'e48c9814c08acbe321460a29e42cb49b0eab38ff', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '71517f9279e9a6aa0d5b02e2169db868ca3fb676', class: "metric-compact" }, h("span", { key: '3ddbcc56b137aea702a40f900178e8e16efd146e', class: "metric-label" }, "FRAME"), h("span", { key: 'd3a70afb511d3f72b485b3a1cb922876faa62b11', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: 'a3b62cdbe830b1024ab96f7fb726825df24fecdc', class: "metrics-row" }, h("div", { key: '95471e5cdde748d1ce6bdee3540740dd3aa66865', class: "metric-compact" }, h("span", { key: 'c93e01c0bdc67621c3a666357d2477b7a5275d52', class: "metric-label" }, "DET"), h("span", { key: 'ef33c86a83101b0989b14c7f1ab0ce0b331b397f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'e14d56128bf96337e4c3859f8e4003e001e8e953', class: "metric-compact" }, h("span", { key: '8aa828632f0a977a27ed377f103bb8d5e7e0ed1a', class: "metric-label" }, "RATE"), h("span", { key: '59035c473b5a747a27f774e2554322a084b6d19f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '4d0c2de00954416faac30ab622fb21f36c932aca', class: "watermark" }, h("img", { key: 'd8f775428ebb3ba86a943ca73b074db2180d0c29', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2124
2463
  }
2125
2464
  // Utility methods
2126
2465
  updateDetectionBoxes(boxes) {
@@ -2344,6 +2683,8 @@ const JaakStamps = class {
2344
2683
  showFlipAnimation: true
2345
2684
  });
2346
2685
  this.triggerRerender();
2686
+ // Progressive resource cleanup after front capture
2687
+ await this.onFrontCaptureComplete();
2347
2688
  setTimeout(() => {
2348
2689
  this.stateManager.updateCaptureState({
2349
2690
  showFlipAnimation: false,
@@ -2419,6 +2760,8 @@ const JaakStamps = class {
2419
2760
  isDetectionPaused: true,
2420
2761
  showFlipAnimation: true
2421
2762
  });
2763
+ // Progressive resource cleanup after front capture
2764
+ await this.onFrontCaptureComplete();
2422
2765
  setTimeout(() => {
2423
2766
  this.stateManager.updateCaptureState({
2424
2767
  showFlipAnimation: false,
@@ -2596,6 +2939,16 @@ const JaakStamps = class {
2596
2939
  setTimeout(() => {
2597
2940
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2598
2941
  }, 3000);
2942
+ // Aggressive cleanup after process completion with delay
2943
+ setTimeout(async () => {
2944
+ const cleanupResult = await this.aggressiveResourceCleanup();
2945
+ if (this.debug) {
2946
+ console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
2947
+ if (cleanupResult.errors.length > 0) {
2948
+ console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
2949
+ }
2950
+ }
2951
+ }, 1000); // Wait 1 second after completion
2599
2952
  }
2600
2953
  stopDetection() {
2601
2954
  if (this.animationId) {