@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
@@ -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,26 @@ class DetectionService {
667
671
  return;
668
672
  }
669
673
  try {
674
+ // Create AbortController for this load operation
675
+ this.modelLoadController = new AbortController();
670
676
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
671
677
  try {
672
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
678
+ // Load model with abort signal support
679
+ const response = await fetch(this.MODEL_PATH, {
680
+ signal: this.modelLoadController.signal
681
+ });
682
+ if (!response.ok) {
683
+ throw new Error(`Failed to fetch model: ${response.status}`);
684
+ }
685
+ const modelBuffer = await response.arrayBuffer();
686
+ this.session = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
673
687
  }
674
688
  catch (error) {
689
+ // Handle abort signal
690
+ if (error.name === 'AbortError') {
691
+ console.log('Model loading was cancelled');
692
+ return;
693
+ }
675
694
  // Múltiples tipos de errores de memoria que pueden ocurrir
676
695
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
677
696
  error.message.includes('Out of memory') ||
@@ -693,7 +712,15 @@ class DetectionService {
693
712
  sessionLogVerbosityLevel: 0,
694
713
  };
695
714
  try {
696
- 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);
697
724
  }
698
725
  catch (fallbackError) {
699
726
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -714,9 +741,13 @@ class DetectionService {
714
741
  return;
715
742
  }
716
743
  try {
744
+ // Create AbortController for classification model loading
745
+ this.classificationLoadController = new AbortController();
717
746
  // Try to load class map (optional - SqueezeNet may not need it for basic classification)
718
747
  try {
719
- const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
748
+ const classResponse = await fetch(this.MOBILENET_CLASSES_PATH, {
749
+ signal: this.classificationLoadController.signal
750
+ });
720
751
  if (classResponse.ok) {
721
752
  this.mobileNetClassMap = await classResponse.json();
722
753
  }
@@ -734,9 +765,22 @@ class DetectionService {
734
765
  // Load model
735
766
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
736
767
  try {
737
- 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);
738
777
  }
739
778
  catch (error) {
779
+ // Handle abort signal
780
+ if (error.name === 'AbortError') {
781
+ console.log('Classification model loading was cancelled');
782
+ return;
783
+ }
740
784
  // Múltiples tipos de errores de memoria que pueden ocurrir
741
785
  const isMemoryError = error.message.includes('failed to allocate a buffer') ||
742
786
  error.message.includes('Out of memory') ||
@@ -758,7 +802,15 @@ class DetectionService {
758
802
  sessionLogVerbosityLevel: 0,
759
803
  };
760
804
  try {
761
- 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);
762
814
  }
763
815
  catch (fallbackError) {
764
816
  throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
@@ -948,6 +1000,9 @@ class DetectionService {
948
1000
  return alignment.top && alignment.right && alignment.bottom && alignment.left;
949
1001
  }
950
1002
  cleanup() {
1003
+ // Cancel any ongoing requests
1004
+ this.abortPendingRequests();
1005
+ // Cleanup ONNX resources
951
1006
  this.cleanupCanvasPool();
952
1007
  if (this.session) {
953
1008
  this.session.release?.();
@@ -959,6 +1014,8 @@ class DetectionService {
959
1014
  }
960
1015
  this.mobileNetClassMap = undefined;
961
1016
  this.modelLoaded = false;
1017
+ // Cleanup blob URLs
1018
+ this.revokeModelBlobUrls();
962
1019
  }
963
1020
  initializeCanvasPool() {
964
1021
  this.preprocessCanvas = document.createElement('canvas');
@@ -1056,11 +1113,93 @@ class DetectionService {
1056
1113
  const pool = DetectionService.canvasPool.get(key);
1057
1114
  // Only return to pool if not at max capacity
1058
1115
  if (pool.length < DetectionService.MAX_POOL_SIZE) {
1116
+ const ctx = canvas.getContext('2d');
1117
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1059
1118
  pool.push(canvas);
1060
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
+ }
1061
1170
  }
1062
- // Static method to clear all canvas pools (useful for memory management)
1063
- 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
+ });
1064
1203
  DetectionService.canvasPool.clear();
1065
1204
  }
1066
1205
  // Method to get pool statistics for debugging
@@ -1537,7 +1676,13 @@ const JaakStamps = class {
1537
1676
  }
1538
1677
  try {
1539
1678
  this.exitSession();
1540
- 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
+ };
1541
1686
  }
1542
1687
  catch (error) {
1543
1688
  return { success: false, error: error.message };
@@ -1762,6 +1907,29 @@ const JaakStamps = class {
1762
1907
  // Always allow getting capture delay, even during initialization
1763
1908
  return this.captureDelay;
1764
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
+ }
1765
1933
  // Memory and device capability detection
1766
1934
  checkDeviceCapabilities() {
1767
1935
  const deviceMemory = navigator.deviceMemory;
@@ -2097,11 +2265,182 @@ const JaakStamps = class {
2097
2265
  }
2098
2266
  // Clear canvas pool
2099
2267
  this.canvasPool = [];
2268
+ // Clean DOM references and memory leaks
2269
+ this.cleanupDOMReferences();
2100
2270
  this.detectionBoxes = [];
2101
2271
  this.alignmentStartTime = undefined;
2102
2272
  this.hasDocumentDetected = false;
2103
2273
  this.serviceContainer?.cleanup();
2104
2274
  }
2275
+ // AGGRESSIVE RESOURCE CLEANUP METHODS
2276
+ // Clean DOM references and memory leaks
2277
+ cleanupDOMReferences() {
2278
+ // Remove event listeners that could maintain references
2279
+ if (this.videoRef) {
2280
+ // Remove common event listeners (if any were added)
2281
+ this.videoRef.srcObject = null;
2282
+ this.videoRef = undefined;
2283
+ }
2284
+ // Clean detection container
2285
+ if (this.detectionContainer) {
2286
+ this.detectionContainer = undefined;
2287
+ }
2288
+ // Clean references to images in the DOM and revoke blob URLs
2289
+ const images = this.el.shadowRoot?.querySelectorAll('img');
2290
+ images?.forEach(img => {
2291
+ if (img.src.startsWith('blob:')) {
2292
+ URL.revokeObjectURL(img.src);
2293
+ }
2294
+ img.src = '';
2295
+ });
2296
+ }
2297
+ // Clear browser cache and storage
2298
+ async clearBrowserCache() {
2299
+ try {
2300
+ // 1. Clear Service Worker cache if exists
2301
+ if ('serviceWorker' in navigator) {
2302
+ const registrations = await navigator.serviceWorker.getRegistrations();
2303
+ await Promise.all(registrations.map(registration => registration.unregister()));
2304
+ }
2305
+ // 2. Clear Cache API specific to the component
2306
+ const cacheNames = await caches.keys();
2307
+ const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
2308
+ await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
2309
+ // 3. Force garbage collection if available (only in dev)
2310
+ if (this.debug && window.gc) {
2311
+ window.gc();
2312
+ }
2313
+ }
2314
+ catch (error) {
2315
+ console.warn('Error clearing browser cache:', error);
2316
+ }
2317
+ }
2318
+ // Clear local storage specific to the component
2319
+ async clearLocalStorage() {
2320
+ try {
2321
+ // Clear localStorage specific to the component
2322
+ const keysToRemove = [];
2323
+ for (let i = 0; i < localStorage.length; i++) {
2324
+ const key = localStorage.key(i);
2325
+ if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
2326
+ keysToRemove.push(key);
2327
+ }
2328
+ }
2329
+ keysToRemove.forEach(key => localStorage.removeItem(key));
2330
+ // Clear sessionStorage
2331
+ const sessionKeysToRemove = [];
2332
+ for (let i = 0; i < sessionStorage.length; i++) {
2333
+ const key = sessionStorage.key(i);
2334
+ if (key && (key.includes('jaak') || key.includes('onnx'))) {
2335
+ sessionKeysToRemove.push(key);
2336
+ }
2337
+ }
2338
+ sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
2339
+ }
2340
+ catch (error) {
2341
+ console.warn('Error clearing local storage:', error);
2342
+ }
2343
+ }
2344
+ // Aggressive resource cleanup combining all strategies
2345
+ async aggressiveResourceCleanup() {
2346
+ const freed = [];
2347
+ const errors = [];
2348
+ try {
2349
+ // 1. Release ONNX resources
2350
+ this.releaseOnnxResources();
2351
+ freed.push('ONNX models');
2352
+ // 2. Clear model cache in detection service
2353
+ if (this.detectionService) {
2354
+ await this.detectionService.clearModelCache();
2355
+ freed.push('Model cache');
2356
+ }
2357
+ // 3. Clear browser cache
2358
+ await this.clearBrowserCache();
2359
+ freed.push('Browser cache');
2360
+ // 4. Clear local storage
2361
+ await this.clearLocalStorage();
2362
+ freed.push('Local storage');
2363
+ // 5. Clean DOM references
2364
+ this.cleanupDOMReferences();
2365
+ freed.push('DOM references');
2366
+ // 6. Optimize canvas pools
2367
+ if (this.detectionService) {
2368
+ this.detectionService.optimizeCanvasPool(0);
2369
+ freed.push('Canvas pools');
2370
+ }
2371
+ // 7. Clear component canvas pool
2372
+ this.canvasPool = [];
2373
+ freed.push('Component canvas pool');
2374
+ // 8. Force garbage collection (if available)
2375
+ if (this.debug && window.gc) {
2376
+ window.gc();
2377
+ freed.push('Garbage collection');
2378
+ }
2379
+ }
2380
+ catch (error) {
2381
+ errors.push(error.message);
2382
+ }
2383
+ return { freed, errors };
2384
+ }
2385
+ // Progressive resource release after front capture
2386
+ async onFrontCaptureComplete() {
2387
+ try {
2388
+ // Release classification model if not needed for back
2389
+ if (this.detectionService && !this.useDocumentClassification) {
2390
+ await this.detectionService.releaseMobileNetResources();
2391
+ }
2392
+ // Optimize canvas pool to minimum needed for back capture
2393
+ if (this.detectionService) {
2394
+ this.detectionService.optimizeCanvasPool(1);
2395
+ }
2396
+ // Reduce component canvas pool
2397
+ while (this.canvasPool.length > 1) {
2398
+ const canvas = this.canvasPool.pop();
2399
+ if (canvas) {
2400
+ const ctx = canvas.getContext('2d');
2401
+ if (ctx) {
2402
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2403
+ }
2404
+ }
2405
+ }
2406
+ if (this.debug) {
2407
+ console.log('🧹 Progressive cleanup after front capture completed');
2408
+ }
2409
+ }
2410
+ catch (error) {
2411
+ console.warn('Error in progressive cleanup:', error);
2412
+ }
2413
+ }
2414
+ // Resource reporting for debugging
2415
+ getResourceReportInternal() {
2416
+ const report = {
2417
+ timestamp: new Date().toISOString(),
2418
+ component: {
2419
+ hasVideoStream: !!this.videoStream,
2420
+ hasAnimationFrame: !!this.animationId,
2421
+ canvasPoolSize: this.canvasPool.length,
2422
+ detectionBoxesCount: this.detectionBoxes.length,
2423
+ performanceMonitorActive: !!this.performanceUpdateInterval
2424
+ }
2425
+ };
2426
+ // Add detection service stats if available
2427
+ if (this.detectionService) {
2428
+ report.detectionService = {
2429
+ isModelLoaded: this.detectionService.isModelLoaded(),
2430
+ canvasPoolStats: this.detectionService.getPoolStats()
2431
+ };
2432
+ }
2433
+ // Add memory info if available
2434
+ if ('memory' in performance) {
2435
+ const memInfo = performance.memory;
2436
+ report.memory = {
2437
+ usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
2438
+ totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
2439
+ jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
2440
+ };
2441
+ }
2442
+ return report;
2443
+ }
2105
2444
  render() {
2106
2445
  const captureState = this.stateManager?.getCaptureState() || {
2107
2446
  isVideoActive: false,
@@ -2111,7 +2450,7 @@ const JaakStamps = class {
2111
2450
  isCapturing: false
2112
2451
  };
2113
2452
  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: {
2453
+ return (index.h("div", { key: 'ca44b9eacbde9f688f2c41aa21ad35a2b882002b', class: "detector-container" }, index.h("div", { key: '86c7b5d31ed69276b62fa43e01c92b0c0b9f298d', class: "video-container" }, index.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' } }), index.h("div", { key: '078a79a0c6386ca6c040e629acbf4f52f12d8e29', 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
2454
  position: 'absolute',
2116
2455
  left: `${box.x}px`,
2117
2456
  top: `${box.y}px`,
@@ -2120,9 +2459,9 @@ const JaakStamps = class {
2120
2459
  border: '2px solid #32406C',
2121
2460
  pointerEvents: 'none',
2122
2461
  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
2462
+ } })))), this.isMaskReady && (index.h("div", { key: '990015ff5b2ded89405dd2b891c027aad8788f3e', class: "overlay-mask" }, index.h("div", { key: 'dd9145140987addb1b6e4cc2119b8e9f89003726', class: "card-outline" }, index.h("div", { key: 'e8f4ec692a8b80c7a98075e4f708b79a272db67b', class: "side side-top" }), index.h("div", { key: '2577253b40425ecdcb3dd51b2f4a43c15c83ca79', class: "side side-right" }), index.h("div", { key: '30f4454020c00cf4fb92308af5ecf1843ec24005', class: "side side-bottom" }), index.h("div", { key: '3459f634987b8396106fff23fa1c99b295ee7d0a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '84f4d70947e211a9c6b296531d53420223d2ca34', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '7c28e86e8de6c33bf7d7baab3083c5f6a0ab060f', class: "back-capture-section" }, index.h("div", { key: '27bdcf9cfe9790c4bbad484423f6f4c353ccf0b3', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (index.h("button", { key: 'ed0320b1df412bf53cbe4e4119f6d769926eb2c5', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: '0bba706c8ebda59c33faca8e97b7536146462d21', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: 'b072c46b25d16a04c5e0a8f3975d8aa8d24576d5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: 'efd0827d0f19c9072d49d1b3c0046ef6c95a842c', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2124
2463
  ? `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" })))));
2464
+ : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'bd0303e8ce85211e4c68fc3cd79545db7c74288b', class: "camera-controls" }, index.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 ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '35da16baf4c62466c2a66650209a1daf842c0db2', class: "camera-selector-dropdown" }, index.h("div", { key: '7035dd0e22f614cf239a015c6d59531b68fa74a9', class: "camera-selector-header" }, index.h("span", { key: 'ae026f31931f52ea0daf72391a9de4ff9e4b88f1' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '40f2ba0770bafea71fedbf849938010e743be776', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '6f26927094a724a35ebb07ef701caf14f296a837', 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: '082236981c369f8fd487f036439b16e6560514be', class: "device-info" }, index.h("small", { key: 'ccbbfa0bca96395f574bda71103338ae9226f709' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '6baa487409503777e90453b39fd30649941cbbe4', class: "manual-capture-section" }, index.h("button", { key: '893f4715ca3315613f779603f178459955f686f0', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: '7c30b680fe84f4c8227beba45d8416f1edb8348f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: 'd8ef052cf67d7bda4b3534febf96efa2cdcc6bf7', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '3dae7634e770dda2655b1042ec8fb997b3cb2220', class: "flip-animation" }, index.h("div", { key: 'a977fc5e889369121bacc06cf5213dfee39d858d', class: "id-card-icon" }), index.h("div", { key: '4f052cdc8c7b493cddce90e108e976325c9bab8e', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '5117cd6e63e1e0763c1503f6f60f3c7ef60dd665', class: "success-animation" }, index.h("div", { key: 'd11128c45c7118915cc0dd9c25845bb55b4b3eb2', class: "check-icon" }), index.h("div", { key: '693e197bf8a4c91ac7ddedb457d15aa3aa46a5d5', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '89caaa9d78eb522e0fadfd1c0e844e05d29b8286', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '3f41d40b05b71e13e7e1d0c0e980b48308cba9d6', class: "status-spinner" })), index.h("div", { key: '8674480928f4f4a77099c0600ed84f0e27903379', class: "status-content" }, index.h("div", { key: 'e88f4044ab8115a6a5cbb78cc4030975eef9149a', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'ad581e42c30c304b837711549148c11b801dee5e', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '1b371c1b47f3ba699885db00fef52eb448f9e245', class: "performance-monitor" }, index.h("div", { key: '8b9ac60449a15e9fb966c329b2e6c9285c0228c0', class: "performance-expanded" }, index.h("div", { key: '760c11a3ed28e248a144d53a92fd8adee6822ff2', class: "metrics-row" }, index.h("div", { key: '858ab8b106f1d12210e4e15c423ab3bab11bdb60', class: "metric-compact" }, index.h("span", { key: '65fe2479c3822d99452e7e5faa93c25a80765024', class: "metric-label" }, "FPS"), index.h("span", { key: '9faf7763d1284a6f3c1e15fb0f3c99f1d45bb4dc', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: 'c95ed8fa8101f42bfba5cc8ca7debbad028caf42', class: "metric-compact" }, index.h("span", { key: '3555b3778cb71e959d132d6aa563f58f757915d8', class: "metric-label" }, "MEM"), index.h("span", { key: 'd72b0491147a6f80dcf33f5d72dd8dae5898f44a', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'cd4dc81f28f9e59a36a0295dde7bfee89f50d3e2', class: "metrics-row" }, index.h("div", { key: 'c43d8624c5da256ce3e97f67b022bdd98fb04e07', class: "metric-compact" }, index.h("span", { key: 'adb402d83809a90580b81544f31b5331bdff59dd', class: "metric-label" }, "INF"), index.h("span", { key: 'e48c9814c08acbe321460a29e42cb49b0eab38ff', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '71517f9279e9a6aa0d5b02e2169db868ca3fb676', class: "metric-compact" }, index.h("span", { key: '3ddbcc56b137aea702a40f900178e8e16efd146e', class: "metric-label" }, "FRAME"), index.h("span", { key: 'd3a70afb511d3f72b485b3a1cb922876faa62b11', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: 'a3b62cdbe830b1024ab96f7fb726825df24fecdc', class: "metrics-row" }, index.h("div", { key: '95471e5cdde748d1ce6bdee3540740dd3aa66865', class: "metric-compact" }, index.h("span", { key: 'c93e01c0bdc67621c3a666357d2477b7a5275d52', class: "metric-label" }, "DET"), index.h("span", { key: 'ef33c86a83101b0989b14c7f1ab0ce0b331b397f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: 'e14d56128bf96337e4c3859f8e4003e001e8e953', class: "metric-compact" }, index.h("span", { key: '8aa828632f0a977a27ed377f103bb8d5e7e0ed1a', class: "metric-label" }, "RATE"), index.h("span", { key: '59035c473b5a747a27f774e2554322a084b6d19f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '4d0c2de00954416faac30ab622fb21f36c932aca', class: "watermark" }, index.h("img", { key: 'd8f775428ebb3ba86a943ca73b074db2180d0c29', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2126
2465
  }
2127
2466
  // Utility methods
2128
2467
  updateDetectionBoxes(boxes) {
@@ -2346,6 +2685,8 @@ const JaakStamps = class {
2346
2685
  showFlipAnimation: true
2347
2686
  });
2348
2687
  this.triggerRerender();
2688
+ // Progressive resource cleanup after front capture
2689
+ await this.onFrontCaptureComplete();
2349
2690
  setTimeout(() => {
2350
2691
  this.stateManager.updateCaptureState({
2351
2692
  showFlipAnimation: false,
@@ -2421,6 +2762,8 @@ const JaakStamps = class {
2421
2762
  isDetectionPaused: true,
2422
2763
  showFlipAnimation: true
2423
2764
  });
2765
+ // Progressive resource cleanup after front capture
2766
+ await this.onFrontCaptureComplete();
2424
2767
  setTimeout(() => {
2425
2768
  this.stateManager.updateCaptureState({
2426
2769
  showFlipAnimation: false,
@@ -2598,6 +2941,16 @@ const JaakStamps = class {
2598
2941
  setTimeout(() => {
2599
2942
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2600
2943
  }, 3000);
2944
+ // Aggressive cleanup after process completion with delay
2945
+ setTimeout(async () => {
2946
+ const cleanupResult = await this.aggressiveResourceCleanup();
2947
+ if (this.debug) {
2948
+ console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
2949
+ if (cleanupResult.errors.length > 0) {
2950
+ console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
2951
+ }
2952
+ }
2953
+ }, 1000); // Wait 1 second after completion
2601
2954
  }
2602
2955
  stopDetection() {
2603
2956
  if (this.animationId) {