@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
@@ -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
@@ -1537,7 +1678,13 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1537
1678
  }
1538
1679
  try {
1539
1680
  this.exitSession();
1540
- return { success: true };
1681
+ // Aggressive cleanup when stopping capture
1682
+ const cleanupResult = await this.aggressiveResourceCleanup();
1683
+ return {
1684
+ success: true,
1685
+ resourcesFreed: cleanupResult.freed,
1686
+ cleanupErrors: cleanupResult.errors
1687
+ };
1541
1688
  }
1542
1689
  catch (error) {
1543
1690
  return { success: false, error: error.message };
@@ -1762,6 +1909,29 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1762
1909
  // Always allow getting capture delay, even during initialization
1763
1910
  return this.captureDelay;
1764
1911
  }
1912
+ async getResourceReport() {
1913
+ // Public method to get resource usage report
1914
+ return this.getResourceReportInternal();
1915
+ }
1916
+ async forceResourceCleanup() {
1917
+ // Public method to force aggressive resource cleanup
1918
+ const readyCheck = this.isComponentReady();
1919
+ if (!readyCheck.ready) {
1920
+ return { success: false, error: readyCheck.message };
1921
+ }
1922
+ try {
1923
+ const cleanupResult = await this.aggressiveResourceCleanup();
1924
+ return {
1925
+ success: true,
1926
+ resourcesFreed: cleanupResult.freed,
1927
+ errors: cleanupResult.errors,
1928
+ timestamp: new Date().toISOString()
1929
+ };
1930
+ }
1931
+ catch (error) {
1932
+ return { success: false, error: error.message };
1933
+ }
1934
+ }
1765
1935
  // Memory and device capability detection
1766
1936
  checkDeviceCapabilities() {
1767
1937
  const deviceMemory = navigator.deviceMemory;
@@ -1785,13 +1955,18 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1785
1955
  memoryMB: null
1786
1956
  };
1787
1957
  }
1788
- // Safari tiene limitaciones conocidas con WASM
1958
+ // Safari moderno soporta WASM, solo bloquearlo en versiones muy antiguas
1789
1959
  if (isSafari) {
1790
- return {
1791
- canUseML: false,
1792
- reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
1793
- memoryMB: null
1794
- };
1960
+ const safariMatch = navigator.userAgent.match(/Version\/(\d+)/);
1961
+ const safariVersion = safariMatch ? parseInt(safariMatch[1]) : 0;
1962
+ // Solo bloquear Safari muy antiguo (< version 14)
1963
+ if (safariVersion > 0 && safariVersion < 14) {
1964
+ return {
1965
+ canUseML: false,
1966
+ reason: 'Safari antiguo detectado. Modo manual activado por compatibilidad.',
1967
+ memoryMB: null
1968
+ };
1969
+ }
1795
1970
  }
1796
1971
  return {
1797
1972
  canUseML: true,
@@ -2097,11 +2272,182 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2097
2272
  }
2098
2273
  // Clear canvas pool
2099
2274
  this.canvasPool = [];
2275
+ // Clean DOM references and memory leaks
2276
+ this.cleanupDOMReferences();
2100
2277
  this.detectionBoxes = [];
2101
2278
  this.alignmentStartTime = undefined;
2102
2279
  this.hasDocumentDetected = false;
2103
2280
  this.serviceContainer?.cleanup();
2104
2281
  }
2282
+ // AGGRESSIVE RESOURCE CLEANUP METHODS
2283
+ // Clean DOM references and memory leaks
2284
+ cleanupDOMReferences() {
2285
+ // Remove event listeners that could maintain references
2286
+ if (this.videoRef) {
2287
+ // Remove common event listeners (if any were added)
2288
+ this.videoRef.srcObject = null;
2289
+ this.videoRef = undefined;
2290
+ }
2291
+ // Clean detection container
2292
+ if (this.detectionContainer) {
2293
+ this.detectionContainer = undefined;
2294
+ }
2295
+ // Clean references to images in the DOM and revoke blob URLs
2296
+ const images = this.el.shadowRoot?.querySelectorAll('img');
2297
+ images?.forEach(img => {
2298
+ if (img.src.startsWith('blob:')) {
2299
+ URL.revokeObjectURL(img.src);
2300
+ }
2301
+ img.src = '';
2302
+ });
2303
+ }
2304
+ // Clear browser cache and storage
2305
+ async clearBrowserCache() {
2306
+ try {
2307
+ // 1. Clear Service Worker cache if exists
2308
+ if ('serviceWorker' in navigator) {
2309
+ const registrations = await navigator.serviceWorker.getRegistrations();
2310
+ await Promise.all(registrations.map(registration => registration.unregister()));
2311
+ }
2312
+ // 2. Clear Cache API specific to the component
2313
+ const cacheNames = await caches.keys();
2314
+ const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
2315
+ await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
2316
+ // 3. Force garbage collection if available (only in dev)
2317
+ if (this.debug && window.gc) {
2318
+ window.gc();
2319
+ }
2320
+ }
2321
+ catch (error) {
2322
+ console.warn('Error clearing browser cache:', error);
2323
+ }
2324
+ }
2325
+ // Clear local storage specific to the component
2326
+ async clearLocalStorage() {
2327
+ try {
2328
+ // Clear localStorage specific to the component
2329
+ const keysToRemove = [];
2330
+ for (let i = 0; i < localStorage.length; i++) {
2331
+ const key = localStorage.key(i);
2332
+ if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
2333
+ keysToRemove.push(key);
2334
+ }
2335
+ }
2336
+ keysToRemove.forEach(key => localStorage.removeItem(key));
2337
+ // Clear sessionStorage
2338
+ const sessionKeysToRemove = [];
2339
+ for (let i = 0; i < sessionStorage.length; i++) {
2340
+ const key = sessionStorage.key(i);
2341
+ if (key && (key.includes('jaak') || key.includes('onnx'))) {
2342
+ sessionKeysToRemove.push(key);
2343
+ }
2344
+ }
2345
+ sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
2346
+ }
2347
+ catch (error) {
2348
+ console.warn('Error clearing local storage:', error);
2349
+ }
2350
+ }
2351
+ // Aggressive resource cleanup combining all strategies
2352
+ async aggressiveResourceCleanup() {
2353
+ const freed = [];
2354
+ const errors = [];
2355
+ try {
2356
+ // 1. Release ONNX resources
2357
+ this.releaseOnnxResources();
2358
+ freed.push('ONNX models');
2359
+ // 2. Clear model cache in detection service
2360
+ if (this.detectionService) {
2361
+ await this.detectionService.clearModelCache();
2362
+ freed.push('Model cache');
2363
+ }
2364
+ // 3. Clear browser cache
2365
+ await this.clearBrowserCache();
2366
+ freed.push('Browser cache');
2367
+ // 4. Clear local storage
2368
+ await this.clearLocalStorage();
2369
+ freed.push('Local storage');
2370
+ // 5. Clean DOM references
2371
+ this.cleanupDOMReferences();
2372
+ freed.push('DOM references');
2373
+ // 6. Optimize canvas pools
2374
+ if (this.detectionService) {
2375
+ this.detectionService.optimizeCanvasPool(0);
2376
+ freed.push('Canvas pools');
2377
+ }
2378
+ // 7. Clear component canvas pool
2379
+ this.canvasPool = [];
2380
+ freed.push('Component canvas pool');
2381
+ // 8. Force garbage collection (if available)
2382
+ if (this.debug && window.gc) {
2383
+ window.gc();
2384
+ freed.push('Garbage collection');
2385
+ }
2386
+ }
2387
+ catch (error) {
2388
+ errors.push(error.message);
2389
+ }
2390
+ return { freed, errors };
2391
+ }
2392
+ // Progressive resource release after front capture
2393
+ async onFrontCaptureComplete() {
2394
+ try {
2395
+ // Release classification model if not needed for back
2396
+ if (this.detectionService && !this.useDocumentClassification) {
2397
+ await this.detectionService.releaseMobileNetResources();
2398
+ }
2399
+ // Optimize canvas pool to minimum needed for back capture
2400
+ if (this.detectionService) {
2401
+ this.detectionService.optimizeCanvasPool(1);
2402
+ }
2403
+ // Reduce component canvas pool
2404
+ while (this.canvasPool.length > 1) {
2405
+ const canvas = this.canvasPool.pop();
2406
+ if (canvas) {
2407
+ const ctx = canvas.getContext('2d');
2408
+ if (ctx) {
2409
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2410
+ }
2411
+ }
2412
+ }
2413
+ if (this.debug) {
2414
+ console.log('🧹 Progressive cleanup after front capture completed');
2415
+ }
2416
+ }
2417
+ catch (error) {
2418
+ console.warn('Error in progressive cleanup:', error);
2419
+ }
2420
+ }
2421
+ // Resource reporting for debugging
2422
+ getResourceReportInternal() {
2423
+ const report = {
2424
+ timestamp: new Date().toISOString(),
2425
+ component: {
2426
+ hasVideoStream: !!this.videoStream,
2427
+ hasAnimationFrame: !!this.animationId,
2428
+ canvasPoolSize: this.canvasPool.length,
2429
+ detectionBoxesCount: this.detectionBoxes.length,
2430
+ performanceMonitorActive: !!this.performanceUpdateInterval
2431
+ }
2432
+ };
2433
+ // Add detection service stats if available
2434
+ if (this.detectionService) {
2435
+ report.detectionService = {
2436
+ isModelLoaded: this.detectionService.isModelLoaded(),
2437
+ canvasPoolStats: this.detectionService.getPoolStats()
2438
+ };
2439
+ }
2440
+ // Add memory info if available
2441
+ if ('memory' in performance) {
2442
+ const memInfo = performance.memory;
2443
+ report.memory = {
2444
+ usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
2445
+ totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
2446
+ jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
2447
+ };
2448
+ }
2449
+ return report;
2450
+ }
2105
2451
  render() {
2106
2452
  const captureState = this.stateManager?.getCaptureState() || {
2107
2453
  isVideoActive: false,
@@ -2111,7 +2457,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2111
2457
  isCapturing: false
2112
2458
  };
2113
2459
  const cameraInfo = this.cameraInfoWithAutofocus;
2114
- 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: {
2460
+ 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: {
2115
2461
  position: 'absolute',
2116
2462
  left: `${box.x}px`,
2117
2463
  top: `${box.y}px`,
@@ -2120,9 +2466,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2120
2466
  border: '2px solid #32406C',
2121
2467
  pointerEvents: 'none',
2122
2468
  boxSizing: 'border-box'
2123
- } })))), 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
2469
+ } })))), 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
2124
2470
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
2125
- : '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" })))));
2471
+ : '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" })))));
2126
2472
  }
2127
2473
  // Utility methods
2128
2474
  updateDetectionBoxes(boxes) {
@@ -2346,6 +2692,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2346
2692
  showFlipAnimation: true
2347
2693
  });
2348
2694
  this.triggerRerender();
2695
+ // Progressive resource cleanup after front capture
2696
+ await this.onFrontCaptureComplete();
2349
2697
  setTimeout(() => {
2350
2698
  this.stateManager.updateCaptureState({
2351
2699
  showFlipAnimation: false,
@@ -2421,6 +2769,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2421
2769
  isDetectionPaused: true,
2422
2770
  showFlipAnimation: true
2423
2771
  });
2772
+ // Progressive resource cleanup after front capture
2773
+ await this.onFrontCaptureComplete();
2424
2774
  setTimeout(() => {
2425
2775
  this.stateManager.updateCaptureState({
2426
2776
  showFlipAnimation: false,
@@ -2598,6 +2948,16 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2598
2948
  setTimeout(() => {
2599
2949
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2600
2950
  }, 3000);
2951
+ // Aggressive cleanup after process completion with delay
2952
+ setTimeout(async () => {
2953
+ const cleanupResult = await this.aggressiveResourceCleanup();
2954
+ if (this.debug) {
2955
+ console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
2956
+ if (cleanupResult.errors.length > 0) {
2957
+ console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
2958
+ }
2959
+ }
2960
+ }, 1000); // Wait 1 second after completion
2601
2961
  }
2602
2962
  stopDetection() {
2603
2963
  if (this.animationId) {
@@ -2889,7 +3249,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2889
3249
  "getCameraInfo": [64],
2890
3250
  "setPreferredCamera": [64],
2891
3251
  "setCaptureDelay": [64],
2892
- "getCaptureDelay": [64]
3252
+ "getCaptureDelay": [64],
3253
+ "getResourceReport": [64],
3254
+ "forceResourceCleanup": [64]
2893
3255
  }]);
2894
3256
  function defineCustomElement$1() {
2895
3257
  if (typeof customElements === "undefined") {