@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.
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +377 -17
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/my-component/my-component.js +265 -12
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/DetectionService.js +148 -7
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/collection/services/interfaces/IDetectionService.js.map +1 -1
- package/dist/components/jaak-stamps.js +380 -18
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps-webcomponent.js +1 -1
- package/dist/esm/jaak-stamps.entry.js +377 -17
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-c115e01a.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-c115e01a.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +27 -0
- package/dist/types/components.d.ts +3 -1
- package/dist/types/services/DetectionService.d.ts +9 -1
- package/dist/types/services/interfaces/IDetectionService.d.ts +6 -0
- package/package.json +1 -1
- package/dist/jaak-stamps-webcomponent/p-be6b6ca3.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-be6b6ca3.entry.js.map +0 -1
|
@@ -16,7 +16,7 @@ var patchBrowser = () => {
|
|
|
16
16
|
|
|
17
17
|
patchBrowser().then(async (options) => {
|
|
18
18
|
await globalScripts();
|
|
19
|
-
return bootstrapLazy([["jaak-stamps",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"performanceDegradedMode":[32],"showPerformanceMessage":[32],"captureStateVersion":[32],"processingButton":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
|
|
19
|
+
return bootstrapLazy([["jaak-stamps",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"performanceDegradedMode":[32],"showPerformanceMessage":[32],"captureStateVersion":[32],"processingButton":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64],"getResourceReport":[64],"forceResourceCleanup":[64]}]]]], options);
|
|
20
20
|
});
|
|
21
21
|
//# sourceMappingURL=jaak-stamps-webcomponent.js.map
|
|
22
22
|
|
|
@@ -648,6 +648,10 @@ class DetectionService {
|
|
|
648
648
|
// Static canvas pool for reuse across instances
|
|
649
649
|
static canvasPool = new Map();
|
|
650
650
|
static MAX_POOL_SIZE = 5;
|
|
651
|
+
// Resource management for optimization
|
|
652
|
+
modelLoadController;
|
|
653
|
+
classificationLoadController;
|
|
654
|
+
modelBlobUrls = new Set();
|
|
651
655
|
constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
|
|
652
656
|
this.debug = debug;
|
|
653
657
|
this.useDocumentClassification = useDocumentClassification;
|
|
@@ -665,11 +669,28 @@ class DetectionService {
|
|
|
665
669
|
return;
|
|
666
670
|
}
|
|
667
671
|
try {
|
|
672
|
+
if (typeof window.ort === 'undefined') {
|
|
673
|
+
throw new Error('ONNX Runtime not available in window object');
|
|
674
|
+
}
|
|
675
|
+
// Create AbortController for this load operation
|
|
676
|
+
this.modelLoadController = new AbortController();
|
|
668
677
|
const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
|
|
669
678
|
try {
|
|
670
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1061
|
-
|
|
1171
|
+
// Optimize canvas pool by reducing to specific size
|
|
1172
|
+
optimizeCanvasPool(maxSize) {
|
|
1173
|
+
DetectionService.canvasPool.forEach((pool, key) => {
|
|
1174
|
+
while (pool.length > maxSize) {
|
|
1175
|
+
const canvas = pool.pop();
|
|
1176
|
+
// Explicitly clear and nullify for garbage collection
|
|
1177
|
+
if (canvas) {
|
|
1178
|
+
const ctx = canvas.getContext('2d');
|
|
1179
|
+
if (ctx) {
|
|
1180
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (pool.length === 0) {
|
|
1185
|
+
DetectionService.canvasPool.delete(key);
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
if (this.debug) {
|
|
1189
|
+
console.log(`🧹 Optimized canvas pool to max size: ${maxSize}`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
// Clear all static canvas pools
|
|
1193
|
+
static clearAllCanvasPools() {
|
|
1194
|
+
DetectionService.canvasPool.forEach((pool) => {
|
|
1195
|
+
pool.forEach(canvas => {
|
|
1196
|
+
const ctx = canvas.getContext('2d');
|
|
1197
|
+
if (ctx) {
|
|
1198
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
pool.length = 0;
|
|
1202
|
+
});
|
|
1062
1203
|
DetectionService.canvasPool.clear();
|
|
1063
1204
|
}
|
|
1064
1205
|
// Method to get pool statistics for debugging
|
|
@@ -1535,7 +1676,13 @@ const JaakStamps = class {
|
|
|
1535
1676
|
}
|
|
1536
1677
|
try {
|
|
1537
1678
|
this.exitSession();
|
|
1538
|
-
|
|
1679
|
+
// Aggressive cleanup when stopping capture
|
|
1680
|
+
const cleanupResult = await this.aggressiveResourceCleanup();
|
|
1681
|
+
return {
|
|
1682
|
+
success: true,
|
|
1683
|
+
resourcesFreed: cleanupResult.freed,
|
|
1684
|
+
cleanupErrors: cleanupResult.errors
|
|
1685
|
+
};
|
|
1539
1686
|
}
|
|
1540
1687
|
catch (error) {
|
|
1541
1688
|
return { success: false, error: error.message };
|
|
@@ -1760,6 +1907,29 @@ const JaakStamps = class {
|
|
|
1760
1907
|
// Always allow getting capture delay, even during initialization
|
|
1761
1908
|
return this.captureDelay;
|
|
1762
1909
|
}
|
|
1910
|
+
async getResourceReport() {
|
|
1911
|
+
// Public method to get resource usage report
|
|
1912
|
+
return this.getResourceReportInternal();
|
|
1913
|
+
}
|
|
1914
|
+
async forceResourceCleanup() {
|
|
1915
|
+
// Public method to force aggressive resource cleanup
|
|
1916
|
+
const readyCheck = this.isComponentReady();
|
|
1917
|
+
if (!readyCheck.ready) {
|
|
1918
|
+
return { success: false, error: readyCheck.message };
|
|
1919
|
+
}
|
|
1920
|
+
try {
|
|
1921
|
+
const cleanupResult = await this.aggressiveResourceCleanup();
|
|
1922
|
+
return {
|
|
1923
|
+
success: true,
|
|
1924
|
+
resourcesFreed: cleanupResult.freed,
|
|
1925
|
+
errors: cleanupResult.errors,
|
|
1926
|
+
timestamp: new Date().toISOString()
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
catch (error) {
|
|
1930
|
+
return { success: false, error: error.message };
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1763
1933
|
// Memory and device capability detection
|
|
1764
1934
|
checkDeviceCapabilities() {
|
|
1765
1935
|
const deviceMemory = navigator.deviceMemory;
|
|
@@ -1783,13 +1953,18 @@ const JaakStamps = class {
|
|
|
1783
1953
|
memoryMB: null
|
|
1784
1954
|
};
|
|
1785
1955
|
}
|
|
1786
|
-
// Safari
|
|
1956
|
+
// Safari moderno soporta WASM, solo bloquearlo en versiones muy antiguas
|
|
1787
1957
|
if (isSafari) {
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1958
|
+
const safariMatch = navigator.userAgent.match(/Version\/(\d+)/);
|
|
1959
|
+
const safariVersion = safariMatch ? parseInt(safariMatch[1]) : 0;
|
|
1960
|
+
// Solo bloquear Safari muy antiguo (< version 14)
|
|
1961
|
+
if (safariVersion > 0 && safariVersion < 14) {
|
|
1962
|
+
return {
|
|
1963
|
+
canUseML: false,
|
|
1964
|
+
reason: 'Safari antiguo detectado. Modo manual activado por compatibilidad.',
|
|
1965
|
+
memoryMB: null
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1793
1968
|
}
|
|
1794
1969
|
return {
|
|
1795
1970
|
canUseML: true,
|
|
@@ -2095,11 +2270,182 @@ const JaakStamps = class {
|
|
|
2095
2270
|
}
|
|
2096
2271
|
// Clear canvas pool
|
|
2097
2272
|
this.canvasPool = [];
|
|
2273
|
+
// Clean DOM references and memory leaks
|
|
2274
|
+
this.cleanupDOMReferences();
|
|
2098
2275
|
this.detectionBoxes = [];
|
|
2099
2276
|
this.alignmentStartTime = undefined;
|
|
2100
2277
|
this.hasDocumentDetected = false;
|
|
2101
2278
|
this.serviceContainer?.cleanup();
|
|
2102
2279
|
}
|
|
2280
|
+
// AGGRESSIVE RESOURCE CLEANUP METHODS
|
|
2281
|
+
// Clean DOM references and memory leaks
|
|
2282
|
+
cleanupDOMReferences() {
|
|
2283
|
+
// Remove event listeners that could maintain references
|
|
2284
|
+
if (this.videoRef) {
|
|
2285
|
+
// Remove common event listeners (if any were added)
|
|
2286
|
+
this.videoRef.srcObject = null;
|
|
2287
|
+
this.videoRef = undefined;
|
|
2288
|
+
}
|
|
2289
|
+
// Clean detection container
|
|
2290
|
+
if (this.detectionContainer) {
|
|
2291
|
+
this.detectionContainer = undefined;
|
|
2292
|
+
}
|
|
2293
|
+
// Clean references to images in the DOM and revoke blob URLs
|
|
2294
|
+
const images = this.el.shadowRoot?.querySelectorAll('img');
|
|
2295
|
+
images?.forEach(img => {
|
|
2296
|
+
if (img.src.startsWith('blob:')) {
|
|
2297
|
+
URL.revokeObjectURL(img.src);
|
|
2298
|
+
}
|
|
2299
|
+
img.src = '';
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
// Clear browser cache and storage
|
|
2303
|
+
async clearBrowserCache() {
|
|
2304
|
+
try {
|
|
2305
|
+
// 1. Clear Service Worker cache if exists
|
|
2306
|
+
if ('serviceWorker' in navigator) {
|
|
2307
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
2308
|
+
await Promise.all(registrations.map(registration => registration.unregister()));
|
|
2309
|
+
}
|
|
2310
|
+
// 2. Clear Cache API specific to the component
|
|
2311
|
+
const cacheNames = await caches.keys();
|
|
2312
|
+
const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
|
|
2313
|
+
await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
|
|
2314
|
+
// 3. Force garbage collection if available (only in dev)
|
|
2315
|
+
if (this.debug && window.gc) {
|
|
2316
|
+
window.gc();
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
catch (error) {
|
|
2320
|
+
console.warn('Error clearing browser cache:', error);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// Clear local storage specific to the component
|
|
2324
|
+
async clearLocalStorage() {
|
|
2325
|
+
try {
|
|
2326
|
+
// Clear localStorage specific to the component
|
|
2327
|
+
const keysToRemove = [];
|
|
2328
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2329
|
+
const key = localStorage.key(i);
|
|
2330
|
+
if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
|
|
2331
|
+
keysToRemove.push(key);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
2335
|
+
// Clear sessionStorage
|
|
2336
|
+
const sessionKeysToRemove = [];
|
|
2337
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2338
|
+
const key = sessionStorage.key(i);
|
|
2339
|
+
if (key && (key.includes('jaak') || key.includes('onnx'))) {
|
|
2340
|
+
sessionKeysToRemove.push(key);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
2344
|
+
}
|
|
2345
|
+
catch (error) {
|
|
2346
|
+
console.warn('Error clearing local storage:', error);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
// Aggressive resource cleanup combining all strategies
|
|
2350
|
+
async aggressiveResourceCleanup() {
|
|
2351
|
+
const freed = [];
|
|
2352
|
+
const errors = [];
|
|
2353
|
+
try {
|
|
2354
|
+
// 1. Release ONNX resources
|
|
2355
|
+
this.releaseOnnxResources();
|
|
2356
|
+
freed.push('ONNX models');
|
|
2357
|
+
// 2. Clear model cache in detection service
|
|
2358
|
+
if (this.detectionService) {
|
|
2359
|
+
await this.detectionService.clearModelCache();
|
|
2360
|
+
freed.push('Model cache');
|
|
2361
|
+
}
|
|
2362
|
+
// 3. Clear browser cache
|
|
2363
|
+
await this.clearBrowserCache();
|
|
2364
|
+
freed.push('Browser cache');
|
|
2365
|
+
// 4. Clear local storage
|
|
2366
|
+
await this.clearLocalStorage();
|
|
2367
|
+
freed.push('Local storage');
|
|
2368
|
+
// 5. Clean DOM references
|
|
2369
|
+
this.cleanupDOMReferences();
|
|
2370
|
+
freed.push('DOM references');
|
|
2371
|
+
// 6. Optimize canvas pools
|
|
2372
|
+
if (this.detectionService) {
|
|
2373
|
+
this.detectionService.optimizeCanvasPool(0);
|
|
2374
|
+
freed.push('Canvas pools');
|
|
2375
|
+
}
|
|
2376
|
+
// 7. Clear component canvas pool
|
|
2377
|
+
this.canvasPool = [];
|
|
2378
|
+
freed.push('Component canvas pool');
|
|
2379
|
+
// 8. Force garbage collection (if available)
|
|
2380
|
+
if (this.debug && window.gc) {
|
|
2381
|
+
window.gc();
|
|
2382
|
+
freed.push('Garbage collection');
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
catch (error) {
|
|
2386
|
+
errors.push(error.message);
|
|
2387
|
+
}
|
|
2388
|
+
return { freed, errors };
|
|
2389
|
+
}
|
|
2390
|
+
// Progressive resource release after front capture
|
|
2391
|
+
async onFrontCaptureComplete() {
|
|
2392
|
+
try {
|
|
2393
|
+
// Release classification model if not needed for back
|
|
2394
|
+
if (this.detectionService && !this.useDocumentClassification) {
|
|
2395
|
+
await this.detectionService.releaseMobileNetResources();
|
|
2396
|
+
}
|
|
2397
|
+
// Optimize canvas pool to minimum needed for back capture
|
|
2398
|
+
if (this.detectionService) {
|
|
2399
|
+
this.detectionService.optimizeCanvasPool(1);
|
|
2400
|
+
}
|
|
2401
|
+
// Reduce component canvas pool
|
|
2402
|
+
while (this.canvasPool.length > 1) {
|
|
2403
|
+
const canvas = this.canvasPool.pop();
|
|
2404
|
+
if (canvas) {
|
|
2405
|
+
const ctx = canvas.getContext('2d');
|
|
2406
|
+
if (ctx) {
|
|
2407
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
if (this.debug) {
|
|
2412
|
+
console.log('🧹 Progressive cleanup after front capture completed');
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
catch (error) {
|
|
2416
|
+
console.warn('Error in progressive cleanup:', error);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
// Resource reporting for debugging
|
|
2420
|
+
getResourceReportInternal() {
|
|
2421
|
+
const report = {
|
|
2422
|
+
timestamp: new Date().toISOString(),
|
|
2423
|
+
component: {
|
|
2424
|
+
hasVideoStream: !!this.videoStream,
|
|
2425
|
+
hasAnimationFrame: !!this.animationId,
|
|
2426
|
+
canvasPoolSize: this.canvasPool.length,
|
|
2427
|
+
detectionBoxesCount: this.detectionBoxes.length,
|
|
2428
|
+
performanceMonitorActive: !!this.performanceUpdateInterval
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
// Add detection service stats if available
|
|
2432
|
+
if (this.detectionService) {
|
|
2433
|
+
report.detectionService = {
|
|
2434
|
+
isModelLoaded: this.detectionService.isModelLoaded(),
|
|
2435
|
+
canvasPoolStats: this.detectionService.getPoolStats()
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
// Add memory info if available
|
|
2439
|
+
if ('memory' in performance) {
|
|
2440
|
+
const memInfo = performance.memory;
|
|
2441
|
+
report.memory = {
|
|
2442
|
+
usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
|
|
2443
|
+
totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
|
|
2444
|
+
jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
return report;
|
|
2448
|
+
}
|
|
2103
2449
|
render() {
|
|
2104
2450
|
const captureState = this.stateManager?.getCaptureState() || {
|
|
2105
2451
|
isVideoActive: false,
|
|
@@ -2109,7 +2455,7 @@ const JaakStamps = class {
|
|
|
2109
2455
|
isCapturing: false
|
|
2110
2456
|
};
|
|
2111
2457
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
2112
|
-
return (h("div", { key: '
|
|
2458
|
+
return (h("div", { key: 'd32941c10b3b7193b2113691dae712a80315b430', class: "detector-container" }, h("div", { key: '7704c993cb9e6e3d29edf6b0adfe341dc1cc8e04', class: "video-container" }, h("video", { key: '2d0d46f1973c86e91f9fe9ede0aa938326c6071f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '9d62ea5221271a18938a9f80535ec7d960745ddb', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
|
|
2113
2459
|
position: 'absolute',
|
|
2114
2460
|
left: `${box.x}px`,
|
|
2115
2461
|
top: `${box.y}px`,
|
|
@@ -2118,9 +2464,9 @@ const JaakStamps = class {
|
|
|
2118
2464
|
border: '2px solid #32406C',
|
|
2119
2465
|
pointerEvents: 'none',
|
|
2120
2466
|
boxSizing: 'border-box'
|
|
2121
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
2467
|
+
} })))), this.isMaskReady && (h("div", { key: '38ebb5be5c55a184644411f06fe20221bdbdf693', class: "overlay-mask" }, h("div", { key: '6200c59e7a1212267e7335e1495cf9d9db2bca62', class: "card-outline" }, h("div", { key: 'c5321d2b5c09734cbca69fc8bb7849eef465a0ce', class: "side side-top" }), h("div", { key: 'd73b5afcb6781307460b8827c491673ebbfc40ef', class: "side side-right" }), h("div", { key: '787dfa0792c5a00f8a83feaa423ca22092e0a8bf', class: "side side-bottom" }), h("div", { key: '50736a6b9c7c8847d1dabaa22464e26f284da11e', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'ec8cc90cd589e4f713becb57b6787ce4f57318d4', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'd9d391c02855ac754607cbf77c119153d1f6e56f', class: "back-capture-section" }, h("div", { key: 'a3ebe694f74958b7910fc355a66cbf1394fb5a2a', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: 'bc6baf4e00199dfdd50bbfbf8abbcede0fb719e9', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '3a56f9132fce454c792818925c8bed5557e69eed', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: '2fe7e44bc91b73581d6333b7c404ec018a390372', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: 'ebb2d40cbcb646c9bd70ebe278b0643f9335779b', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
2122
2468
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
2123
|
-
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '
|
|
2469
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '4373e4f9ab73d93ce32ae52fbdc14f7bcb69e68e', class: "camera-controls" }, h("button", { key: '5d2afe5b71b02d379fe2ec82bd57a86f614fbea6', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '4fe5e10260a1299a0dc8517646bed8134fe38d9a', class: "camera-selector-dropdown" }, h("div", { key: 'b041acd0b0292864d20ad256301e7be714854424', class: "camera-selector-header" }, h("span", { key: '2769170dc9ce382b420a719cad6f9bb1f5e4420f' }, "Seleccionar C\u00E1mara"), h("button", { key: '0c54f73bb29041ba88e1838c5b1c8a54eafdc67d', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'd72ae015c5e89cc3396c14406591308eda4d4c4a', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '5933bb5c8345debf2eb78fc2cd3900cb09a29335', class: "device-info" }, h("small", { key: 'a126d886feaede4f9657abee9b45d1eff1910e25' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '62ab98c6afa629583ff13f1792422ef1c5012883', class: "manual-capture-section" }, h("button", { key: '93a42f7b0626a63ab795652eb9eb81657f4fec13', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '77b5aaf62edab4fe8eb68f37083121d9f8493797', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '399242238fd4a2a4985af0f30faed504d73a0adc', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '06add02bc244511dc0dc82bf206f41d9ce3cdcd3', class: "flip-animation" }, h("div", { key: 'e3949d8e41bc496bf503d1dcd34df53d2bf876aa', class: "id-card-icon" }), h("div", { key: 'e8ebdd9d29e0b33dcfc506b40e67e0d4af8cb0bb', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'f72f6fc4d42c7b10d98c8aff7541596afe131883', class: "success-animation" }, h("div", { key: '8d0d422b37caa97f695f62c0094c52c3c12c7bda', class: "check-icon" }), h("div", { key: 'd9d673dbfd2af4a1b8ca1a775e5b318326d70446', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: 'fdfa1239ddea40bbbabf032542a7160fae3f3c96', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '34bb9d5b83e4383c4f640ccf11189484514e4efd', class: "status-spinner" })), h("div", { key: '131bcd252fc7e1f6eb27332d64fd249b5889aa9d', class: "status-content" }, h("div", { key: '911b7324456e84842fa3c14c9a6fd3ba475804cf', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'e938b9294ee3c6125b133bf737caa4a33a016872', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'cab385a12f6ccf8579d2d073c1ed987bf815efeb', class: "performance-monitor" }, h("div", { key: '903f8a7005a98c4b99eb7d4c91662911cee8beae', class: "performance-expanded" }, h("div", { key: 'f1ba17573d53442a23212e2c8fdd132bd3ddf4f0', class: "metrics-row" }, h("div", { key: '71357107dcc19749e5af2d5668df535db65cb986', class: "metric-compact" }, h("span", { key: '9ad542fb9445340c669c3d18fc293e2364fc5dcc', class: "metric-label" }, "FPS"), h("span", { key: 'ab0fb401df5dc792c0a641a574851aec48093862', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'a650ab2a1af27829d4a63a264f00907dfba363c6', class: "metric-compact" }, h("span", { key: '56e195b0fd468f2a457a66b2342ed6a80fbef0a0', class: "metric-label" }, "MEM"), h("span", { key: '265445131d4492c6e2f7571d367f52c93e00b711', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '56023a82a1978c7c2f8f08edac729f39efcafa63', class: "metrics-row" }, h("div", { key: '6f1ceb668fc246ec11bfeee3f14c745f21d4986e', class: "metric-compact" }, h("span", { key: '92efba3ee31c5b9a382f1e205584950217482722', class: "metric-label" }, "INF"), h("span", { key: 'fc2baec34fd2f0696a216c487753b94b8bacaf7e', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '683eeb875e96c94cf36c403385c2764239fb5a4d', class: "metric-compact" }, h("span", { key: '33ba3f357868f7feaaeefab35e3ecc35d3b5b726', class: "metric-label" }, "FRAME"), h("span", { key: 'f496be674b0328556d27a9a9b2f24ffa50ff76ea', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '521f275481d35c2132dbcf138e4bbda84a0b97b2', class: "metrics-row" }, h("div", { key: '17acc0238696816077b80b66aee317ae9ab20e75', class: "metric-compact" }, h("span", { key: 'cac1a4ee661de14739623a1a6f0149ba72fa918c', class: "metric-label" }, "DET"), h("span", { key: '70071c404c5e50001d813f1f3e0d1002776ed887', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '26a00ad1c95e6c2b8feaafc36103fb7fb869039f', class: "metric-compact" }, h("span", { key: 'a1c7fb80b6795f10111c1ed403de933aadcbe450', class: "metric-label" }, "RATE"), h("span", { key: '49471b7bfa2d6ca6357a39bf611738fd6344764a', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '3870b7399d2d5068e7f59177225476b00bef7fc7', class: "watermark" }, h("img", { key: '5030e9f14eebd4546207ced129d7d00d85abb189', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
2124
2470
|
}
|
|
2125
2471
|
// Utility methods
|
|
2126
2472
|
updateDetectionBoxes(boxes) {
|
|
@@ -2344,6 +2690,8 @@ const JaakStamps = class {
|
|
|
2344
2690
|
showFlipAnimation: true
|
|
2345
2691
|
});
|
|
2346
2692
|
this.triggerRerender();
|
|
2693
|
+
// Progressive resource cleanup after front capture
|
|
2694
|
+
await this.onFrontCaptureComplete();
|
|
2347
2695
|
setTimeout(() => {
|
|
2348
2696
|
this.stateManager.updateCaptureState({
|
|
2349
2697
|
showFlipAnimation: false,
|
|
@@ -2419,6 +2767,8 @@ const JaakStamps = class {
|
|
|
2419
2767
|
isDetectionPaused: true,
|
|
2420
2768
|
showFlipAnimation: true
|
|
2421
2769
|
});
|
|
2770
|
+
// Progressive resource cleanup after front capture
|
|
2771
|
+
await this.onFrontCaptureComplete();
|
|
2422
2772
|
setTimeout(() => {
|
|
2423
2773
|
this.stateManager.updateCaptureState({
|
|
2424
2774
|
showFlipAnimation: false,
|
|
@@ -2596,6 +2946,16 @@ const JaakStamps = class {
|
|
|
2596
2946
|
setTimeout(() => {
|
|
2597
2947
|
this.stateManager.updateCaptureState({ showSuccessAnimation: false });
|
|
2598
2948
|
}, 3000);
|
|
2949
|
+
// Aggressive cleanup after process completion with delay
|
|
2950
|
+
setTimeout(async () => {
|
|
2951
|
+
const cleanupResult = await this.aggressiveResourceCleanup();
|
|
2952
|
+
if (this.debug) {
|
|
2953
|
+
console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
|
|
2954
|
+
if (cleanupResult.errors.length > 0) {
|
|
2955
|
+
console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
}, 1000); // Wait 1 second after completion
|
|
2599
2959
|
}
|
|
2600
2960
|
stopDetection() {
|
|
2601
2961
|
if (this.animationId) {
|