react-native-rectangle-doc-scanner 3.60.0 → 3.62.0

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.
@@ -88,6 +88,13 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
88
88
  return Math.min(100, Math.max(0, quality));
89
89
  }, [quality]);
90
90
  const handlePictureTaken = (0, react_1.useCallback)((event) => {
91
+ console.log('[DocScanner] handlePictureTaken called with event:', {
92
+ hasInitialImage: !!event.initialImage,
93
+ hasCroppedImage: !!event.croppedImage,
94
+ hasRectangleCoordinates: !!event.rectangleCoordinates,
95
+ width: event.width,
96
+ height: event.height,
97
+ });
91
98
  setIsAutoCapturing(false);
92
99
  const normalizedRectangle = normalizeRectangle(event.rectangleCoordinates ?? null) ?? lastRectangleRef.current;
93
100
  const quad = normalizedRectangle ? (0, coordinate_1.rectangleToQuad)(normalizedRectangle) : null;
@@ -96,7 +103,15 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
96
103
  const initialPath = event.initialImage ?? null;
97
104
  const croppedPath = event.croppedImage ?? null;
98
105
  const editablePath = initialPath ?? croppedPath;
106
+ console.log('[DocScanner] Processing captured image:', {
107
+ origin,
108
+ initialPath,
109
+ croppedPath,
110
+ editablePath,
111
+ hasQuad: !!quad,
112
+ });
99
113
  if (editablePath) {
114
+ console.log('[DocScanner] Calling onCapture callback');
100
115
  onCapture?.({
101
116
  path: editablePath,
102
117
  initialPath,
@@ -108,8 +123,12 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
108
123
  origin,
109
124
  });
110
125
  }
126
+ else {
127
+ console.warn('[DocScanner] No editable path available, skipping onCapture');
128
+ }
111
129
  setDetectedRectangle(null);
112
130
  if (captureResolvers.current) {
131
+ console.log('[DocScanner] Resolving capture promise');
113
132
  captureResolvers.current.resolve(event);
114
133
  captureResolvers.current = null;
115
134
  }
@@ -121,42 +140,57 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
121
140
  }
122
141
  }, []);
123
142
  const capture = (0, react_1.useCallback)(() => {
143
+ console.log('[DocScanner] capture() called');
124
144
  captureOriginRef.current = 'manual';
125
145
  const instance = scannerRef.current;
126
146
  if (!instance || typeof instance.capture !== 'function') {
147
+ console.error('[DocScanner] Native instance not ready:', {
148
+ hasInstance: !!instance,
149
+ hasCaptureMethod: instance ? typeof instance.capture : 'no instance',
150
+ });
127
151
  captureOriginRef.current = 'auto';
128
152
  return Promise.reject(new Error('DocumentScanner native instance is not ready'));
129
153
  }
130
154
  if (captureResolvers.current) {
155
+ console.warn('[DocScanner] Capture already in progress');
131
156
  captureOriginRef.current = 'auto';
132
157
  return Promise.reject(new Error('capture_in_progress'));
133
158
  }
159
+ console.log('[DocScanner] Calling native capture method...');
134
160
  let result;
135
161
  try {
136
162
  result = instance.capture();
163
+ console.log('[DocScanner] Native capture method called, result type:', typeof result, 'isPromise:', !!(result && typeof result.then === 'function'));
137
164
  }
138
165
  catch (error) {
166
+ console.error('[DocScanner] Native capture threw error:', error);
139
167
  captureOriginRef.current = 'auto';
140
168
  return Promise.reject(error);
141
169
  }
142
170
  if (result && typeof result.then === 'function') {
171
+ console.log('[DocScanner] Native returned a promise, waiting for resolution...');
143
172
  return result
144
173
  .then((payload) => {
174
+ console.log('[DocScanner] Native promise resolved with payload:', payload);
145
175
  handlePictureTaken(payload);
146
176
  return payload;
147
177
  })
148
178
  .catch((error) => {
179
+ console.error('[DocScanner] Native promise rejected:', error);
149
180
  captureOriginRef.current = 'auto';
150
181
  throw error;
151
182
  });
152
183
  }
184
+ console.log('[DocScanner] Native did not return a promise, using callback-based approach');
153
185
  return new Promise((resolve, reject) => {
154
186
  captureResolvers.current = {
155
187
  resolve: (value) => {
188
+ console.log('[DocScanner] Callback resolver called with:', value);
156
189
  captureOriginRef.current = 'auto';
157
190
  resolve(value);
158
191
  },
159
192
  reject: (reason) => {
193
+ console.error('[DocScanner] Callback rejector called with:', reason);
160
194
  captureOriginRef.current = 'auto';
161
195
  reject(reason);
162
196
  },
@@ -83,12 +83,15 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
83
83
  try {
84
84
  console.log('[FullDocScanner] openCropper called with path:', imagePath);
85
85
  setProcessing(true);
86
- // Normalize path - ensure file:// prefix for iOS
87
- const normalizedPath = imagePath.startsWith('file://') ? imagePath : `file://${imagePath}`;
88
- console.log('[FullDocScanner] Normalized path:', normalizedPath);
89
- // Add timeout to prevent hanging
90
- const cropperPromise = react_native_image_crop_picker_1.default.openCropper({
91
- path: normalizedPath,
86
+ // Clean path - remove file:// prefix for react-native-image-crop-picker
87
+ // The library handles the prefix internally and double prefixing causes issues
88
+ let cleanPath = imagePath;
89
+ if (cleanPath.startsWith('file://')) {
90
+ cleanPath = cleanPath.replace('file://', '');
91
+ }
92
+ console.log('[FullDocScanner] Clean path for cropper:', cleanPath);
93
+ const croppedImage = await react_native_image_crop_picker_1.default.openCropper({
94
+ path: cleanPath,
92
95
  mediaType: 'photo',
93
96
  width: cropWidth,
94
97
  height: cropHeight,
@@ -98,10 +101,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
98
101
  includeBase64: true,
99
102
  compressImageQuality: 0.9,
100
103
  });
101
- const timeoutPromise = new Promise((_, reject) => {
102
- setTimeout(() => reject(new Error('Cropper timeout after 30 seconds')), 30000);
103
- });
104
- const croppedImage = await Promise.race([cropperPromise, timeoutPromise]);
105
104
  console.log('[FullDocScanner] Cropper returned:', {
106
105
  path: croppedImage.path,
107
106
  hasBase64: !!croppedImage.data,
@@ -134,14 +133,16 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
134
133
  croppedPath: document.croppedPath,
135
134
  initialPath: document.initialPath,
136
135
  captureMode: captureModeRef.current,
136
+ captureInProgress: captureInProgressRef.current,
137
137
  });
138
- captureInProgressRef.current = false;
139
138
  const captureMode = captureModeRef.current;
139
+ // Reset capture state
140
+ captureInProgressRef.current = false;
141
+ captureModeRef.current = null;
140
142
  if (!captureMode) {
141
143
  console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
142
144
  return;
143
145
  }
144
- captureModeRef.current = null;
145
146
  const normalizedDoc = normalizeCapturedDocument(document);
146
147
  if (captureMode === 'no-grid') {
147
148
  console.log('[FullDocScanner] No grid at capture button press: opening cropper for manual selection');
@@ -184,12 +185,27 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
184
185
  const captureMode = rectangleDetected ? 'grid' : 'no-grid';
185
186
  captureModeRef.current = captureMode;
186
187
  captureInProgressRef.current = true;
188
+ // Add timeout to reset state if capture hangs
189
+ const captureTimeout = setTimeout(() => {
190
+ if (captureInProgressRef.current) {
191
+ console.error('[FullDocScanner] Capture timeout - resetting state');
192
+ captureModeRef.current = null;
193
+ captureInProgressRef.current = false;
194
+ emitError(new Error('Capture timeout'), 'Capture timed out. Please try again.');
195
+ }
196
+ }, 10000);
187
197
  scannerInstance.capture()
188
198
  .then((result) => {
189
- console.log('[FullDocScanner] Manual capture success:', result);
190
- captureInProgressRef.current = false;
199
+ clearTimeout(captureTimeout);
200
+ console.log('[FullDocScanner] Manual capture promise resolved:', {
201
+ hasResult: !!result,
202
+ croppedImage: result?.croppedImage,
203
+ initialImage: result?.initialImage,
204
+ });
205
+ // Note: captureInProgressRef is reset in handleCapture
191
206
  })
192
207
  .catch((error) => {
208
+ clearTimeout(captureTimeout);
193
209
  const errorMessage = error instanceof Error ? error.message : String(error);
194
210
  console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
195
211
  captureModeRef.current = null;
@@ -265,29 +281,33 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
265
281
  const stableCounter = event.stableCounter ?? 0;
266
282
  const hasRectangle = Boolean(event.rectangleOnScreen ?? event.rectangleCoordinates);
267
283
  const isGoodRectangle = hasRectangle && event.lastDetectionType === 0;
268
- if (hasRectangle) {
284
+ // Clear timeout immediately when rectangle is lost
285
+ if (!hasRectangle || !isGoodRectangle) {
286
+ if (rectangleTimeoutRef.current) {
287
+ clearTimeout(rectangleTimeoutRef.current);
288
+ rectangleTimeoutRef.current = null;
289
+ }
290
+ setRectangleDetected(false);
291
+ }
292
+ else {
293
+ // Rectangle detected - clear any existing timeout
269
294
  if (rectangleTimeoutRef.current) {
270
295
  clearTimeout(rectangleTimeoutRef.current);
271
296
  }
297
+ setRectangleDetected(true);
298
+ // Set timeout to clear rectangle after brief period of no updates
272
299
  rectangleTimeoutRef.current = setTimeout(() => {
273
300
  rectangleTimeoutRef.current = null;
274
301
  setRectangleDetected(false);
302
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
275
303
  }, 300);
276
304
  }
277
- else if (rectangleTimeoutRef.current) {
278
- clearTimeout(rectangleTimeoutRef.current);
279
- rectangleTimeoutRef.current = null;
280
- }
281
- setRectangleDetected((prev) => {
282
- if (prev !== isGoodRectangle) {
283
- console.log('[FullDocScanner] Rectangle detection update', {
284
- lastDetectionType: event.lastDetectionType,
285
- stableCounter,
286
- hasRectangle,
287
- isGoodRectangle,
288
- });
289
- }
290
- return isGoodRectangle;
305
+ console.log('[FullDocScanner] Rectangle detection update', {
306
+ lastDetectionType: event.lastDetectionType,
307
+ stableCounter,
308
+ hasRectangle,
309
+ isGoodRectangle,
310
+ rectangleDetected: isGoodRectangle,
291
311
  });
292
312
  }, []);
293
313
  (0, react_1.useEffect)(() => () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.60.0",
3
+ "version": "3.62.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -150,6 +150,14 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
150
150
 
151
151
  const handlePictureTaken = useCallback(
152
152
  (event: PictureEvent) => {
153
+ console.log('[DocScanner] handlePictureTaken called with event:', {
154
+ hasInitialImage: !!event.initialImage,
155
+ hasCroppedImage: !!event.croppedImage,
156
+ hasRectangleCoordinates: !!event.rectangleCoordinates,
157
+ width: event.width,
158
+ height: event.height,
159
+ });
160
+
153
161
  setIsAutoCapturing(false);
154
162
 
155
163
  const normalizedRectangle =
@@ -162,7 +170,16 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
162
170
  const croppedPath = event.croppedImage ?? null;
163
171
  const editablePath = initialPath ?? croppedPath;
164
172
 
173
+ console.log('[DocScanner] Processing captured image:', {
174
+ origin,
175
+ initialPath,
176
+ croppedPath,
177
+ editablePath,
178
+ hasQuad: !!quad,
179
+ });
180
+
165
181
  if (editablePath) {
182
+ console.log('[DocScanner] Calling onCapture callback');
166
183
  onCapture?.({
167
184
  path: editablePath,
168
185
  initialPath,
@@ -173,11 +190,14 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
173
190
  height: event.height ?? 0,
174
191
  origin,
175
192
  });
193
+ } else {
194
+ console.warn('[DocScanner] No editable path available, skipping onCapture');
176
195
  }
177
196
 
178
197
  setDetectedRectangle(null);
179
198
 
180
199
  if (captureResolvers.current) {
200
+ console.log('[DocScanner] Resolving capture promise');
181
201
  captureResolvers.current.resolve(event);
182
202
  captureResolvers.current = null;
183
203
  }
@@ -193,43 +213,61 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
193
213
  }, []);
194
214
 
195
215
  const capture = useCallback((): Promise<PictureEvent> => {
216
+ console.log('[DocScanner] capture() called');
196
217
  captureOriginRef.current = 'manual';
197
218
  const instance = scannerRef.current;
219
+
198
220
  if (!instance || typeof instance.capture !== 'function') {
221
+ console.error('[DocScanner] Native instance not ready:', {
222
+ hasInstance: !!instance,
223
+ hasCaptureMethod: instance ? typeof instance.capture : 'no instance',
224
+ });
199
225
  captureOriginRef.current = 'auto';
200
226
  return Promise.reject(new Error('DocumentScanner native instance is not ready'));
201
227
  }
228
+
202
229
  if (captureResolvers.current) {
230
+ console.warn('[DocScanner] Capture already in progress');
203
231
  captureOriginRef.current = 'auto';
204
232
  return Promise.reject(new Error('capture_in_progress'));
205
233
  }
206
234
 
235
+ console.log('[DocScanner] Calling native capture method...');
207
236
  let result: any;
208
237
  try {
209
238
  result = instance.capture();
239
+ console.log('[DocScanner] Native capture method called, result type:', typeof result, 'isPromise:', !!(result && typeof result.then === 'function'));
210
240
  } catch (error) {
241
+ console.error('[DocScanner] Native capture threw error:', error);
211
242
  captureOriginRef.current = 'auto';
212
243
  return Promise.reject(error);
213
244
  }
245
+
214
246
  if (result && typeof result.then === 'function') {
247
+ console.log('[DocScanner] Native returned a promise, waiting for resolution...');
215
248
  return result
216
249
  .then((payload: PictureEvent) => {
250
+ console.log('[DocScanner] Native promise resolved with payload:', payload);
217
251
  handlePictureTaken(payload);
218
252
  return payload;
219
253
  })
220
254
  .catch((error: unknown) => {
255
+ console.error('[DocScanner] Native promise rejected:', error);
221
256
  captureOriginRef.current = 'auto';
222
257
  throw error;
223
258
  });
224
259
  }
225
260
 
261
+ console.log('[DocScanner] Native did not return a promise, using callback-based approach');
226
262
  return new Promise<PictureEvent>((resolve, reject) => {
227
263
  captureResolvers.current = {
228
264
  resolve: (value) => {
265
+ console.log('[DocScanner] Callback resolver called with:', value);
229
266
  captureOriginRef.current = 'auto';
230
267
  resolve(value);
231
268
  },
232
269
  reject: (reason) => {
270
+ console.error('[DocScanner] Callback rejector called with:', reason);
233
271
  captureOriginRef.current = 'auto';
234
272
  reject(reason);
235
273
  },
@@ -122,13 +122,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
122
122
  console.log('[FullDocScanner] openCropper called with path:', imagePath);
123
123
  setProcessing(true);
124
124
 
125
- // Normalize path - ensure file:// prefix for iOS
126
- const normalizedPath = imagePath.startsWith('file://') ? imagePath : `file://${imagePath}`;
127
- console.log('[FullDocScanner] Normalized path:', normalizedPath);
125
+ // Clean path - remove file:// prefix for react-native-image-crop-picker
126
+ // The library handles the prefix internally and double prefixing causes issues
127
+ let cleanPath = imagePath;
128
+ if (cleanPath.startsWith('file://')) {
129
+ cleanPath = cleanPath.replace('file://', '');
130
+ }
131
+ console.log('[FullDocScanner] Clean path for cropper:', cleanPath);
128
132
 
129
- // Add timeout to prevent hanging
130
- const cropperPromise = ImageCropPicker.openCropper({
131
- path: normalizedPath,
133
+ const croppedImage = await ImageCropPicker.openCropper({
134
+ path: cleanPath,
132
135
  mediaType: 'photo',
133
136
  width: cropWidth,
134
137
  height: cropHeight,
@@ -139,12 +142,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
139
142
  compressImageQuality: 0.9,
140
143
  });
141
144
 
142
- const timeoutPromise = new Promise((_, reject) => {
143
- setTimeout(() => reject(new Error('Cropper timeout after 30 seconds')), 30000);
144
- });
145
-
146
- const croppedImage = await Promise.race([cropperPromise, timeoutPromise]) as any;
147
-
148
145
  console.log('[FullDocScanner] Cropper returned:', {
149
146
  path: croppedImage.path,
150
147
  hasBase64: !!croppedImage.data,
@@ -186,19 +183,20 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
186
183
  croppedPath: document.croppedPath,
187
184
  initialPath: document.initialPath,
188
185
  captureMode: captureModeRef.current,
186
+ captureInProgress: captureInProgressRef.current,
189
187
  });
190
188
 
191
- captureInProgressRef.current = false;
192
-
193
189
  const captureMode = captureModeRef.current;
194
190
 
191
+ // Reset capture state
192
+ captureInProgressRef.current = false;
193
+ captureModeRef.current = null;
194
+
195
195
  if (!captureMode) {
196
196
  console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
197
197
  return;
198
198
  }
199
199
 
200
- captureModeRef.current = null;
201
-
202
200
  const normalizedDoc = normalizeCapturedDocument(document);
203
201
 
204
202
  if (captureMode === 'no-grid') {
@@ -253,12 +251,31 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
253
251
  captureModeRef.current = captureMode;
254
252
  captureInProgressRef.current = true;
255
253
 
254
+ // Add timeout to reset state if capture hangs
255
+ const captureTimeout = setTimeout(() => {
256
+ if (captureInProgressRef.current) {
257
+ console.error('[FullDocScanner] Capture timeout - resetting state');
258
+ captureModeRef.current = null;
259
+ captureInProgressRef.current = false;
260
+ emitError(
261
+ new Error('Capture timeout'),
262
+ 'Capture timed out. Please try again.',
263
+ );
264
+ }
265
+ }, 10000);
266
+
256
267
  scannerInstance.capture()
257
268
  .then((result) => {
258
- console.log('[FullDocScanner] Manual capture success:', result);
259
- captureInProgressRef.current = false;
269
+ clearTimeout(captureTimeout);
270
+ console.log('[FullDocScanner] Manual capture promise resolved:', {
271
+ hasResult: !!result,
272
+ croppedImage: result?.croppedImage,
273
+ initialImage: result?.initialImage,
274
+ });
275
+ // Note: captureInProgressRef is reset in handleCapture
260
276
  })
261
277
  .catch((error: unknown) => {
278
+ clearTimeout(captureTimeout);
262
279
  const errorMessage = error instanceof Error ? error.message : String(error);
263
280
  console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
264
281
  captureModeRef.current = null;
@@ -353,29 +370,34 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
353
370
  const hasRectangle = Boolean(event.rectangleOnScreen ?? event.rectangleCoordinates);
354
371
  const isGoodRectangle = hasRectangle && event.lastDetectionType === 0;
355
372
 
356
- if (hasRectangle) {
373
+ // Clear timeout immediately when rectangle is lost
374
+ if (!hasRectangle || !isGoodRectangle) {
357
375
  if (rectangleTimeoutRef.current) {
358
376
  clearTimeout(rectangleTimeoutRef.current);
377
+ rectangleTimeoutRef.current = null;
359
378
  }
379
+ setRectangleDetected(false);
380
+ } else {
381
+ // Rectangle detected - clear any existing timeout
382
+ if (rectangleTimeoutRef.current) {
383
+ clearTimeout(rectangleTimeoutRef.current);
384
+ }
385
+ setRectangleDetected(true);
386
+
387
+ // Set timeout to clear rectangle after brief period of no updates
360
388
  rectangleTimeoutRef.current = setTimeout(() => {
361
389
  rectangleTimeoutRef.current = null;
362
390
  setRectangleDetected(false);
391
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
363
392
  }, 300);
364
- } else if (rectangleTimeoutRef.current) {
365
- clearTimeout(rectangleTimeoutRef.current);
366
- rectangleTimeoutRef.current = null;
367
393
  }
368
394
 
369
- setRectangleDetected((prev) => {
370
- if (prev !== isGoodRectangle) {
371
- console.log('[FullDocScanner] Rectangle detection update', {
372
- lastDetectionType: event.lastDetectionType,
373
- stableCounter,
374
- hasRectangle,
375
- isGoodRectangle,
376
- });
377
- }
378
- return isGoodRectangle;
395
+ console.log('[FullDocScanner] Rectangle detection update', {
396
+ lastDetectionType: event.lastDetectionType,
397
+ stableCounter,
398
+ hasRectangle,
399
+ isGoodRectangle,
400
+ rectangleDetected: isGoodRectangle,
379
401
  });
380
402
  }, []);
381
403