@switchlabs/verify-ai-react-native 2.1.0 → 2.3.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.
- package/lib/client/index.js +31 -5
- package/lib/components/VerifyAIScanner.js +24 -9
- package/lib/types/index.d.ts +4 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +29 -5
- package/src/components/VerifyAIScanner.tsx +20 -3
- package/src/types/index.ts +4 -0
- package/src/version.ts +1 -1
package/lib/client/index.js
CHANGED
|
@@ -195,11 +195,37 @@ export class VerifyAIClient {
|
|
|
195
195
|
if (options?.idempotencyKey) {
|
|
196
196
|
headers['Idempotency-Key'] = options.idempotencyKey;
|
|
197
197
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
try {
|
|
199
|
+
return await this.executeRequest('/verify', {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers,
|
|
202
|
+
body: formData,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
// Blank 500 means the server crashed during multipart parsing (e.g.
|
|
207
|
+
// Vercel function crash). Retry as JSON/base64 with a fresh request
|
|
208
|
+
// — the crashed request may have partially recorded the idempotency key.
|
|
209
|
+
if (error instanceof VerifyAIRequestError && error.status >= 500 && error.isServerError) {
|
|
210
|
+
try {
|
|
211
|
+
const { Buffer } = await import('buffer');
|
|
212
|
+
const fileResponse = await fetch(request.imageUri);
|
|
213
|
+
const arrayBuffer = await fileResponse.arrayBuffer();
|
|
214
|
+
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
|
215
|
+
return this.verify({
|
|
216
|
+
image: base64,
|
|
217
|
+
policy: request.policy,
|
|
218
|
+
metadata: request.metadata,
|
|
219
|
+
provider: request.provider,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// If the retry itself fails, throw the original error
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
203
229
|
}
|
|
204
230
|
/**
|
|
205
231
|
* List past verifications with optional filters.
|
|
@@ -312,12 +312,12 @@ export function VerifyAIScanner({ onCapture, onResult, onError, overlay, style,
|
|
|
312
312
|
return (_jsxs(View, { style: [styles.container, styles.permissionContainer, style], children: [_jsx(Text, { style: styles.permissionText, children: "Camera access is required for photo verification" }), _jsx(TouchableOpacity, { style: styles.permissionButton, onPress: requestPermission, children: _jsx(Text, { style: styles.permissionButtonText, children: "Grant Camera Access" }) })] }));
|
|
313
313
|
}
|
|
314
314
|
const showBottomCard = status === 'success' || status === 'error';
|
|
315
|
-
return (_jsx(View, { style: [styles.container, style], children: _jsx(CameraView, { ref: cameraRef, style: styles.camera, facing: "back", enableTorch: !terminated && enableTorch, onCameraReady: onCameraReady, onMountError: onMountError, children: _jsxs(View, { style: styles.overlay, children: [overlay?.title && (_jsx(View, { style: styles.topBar, children: _jsx(Text, { style: styles.titleText, children: overlay.title }) })), overlay?.showGuideFrame && (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
315
|
+
return (_jsx(View, { style: [styles.container, style], children: _jsx(CameraView, { ref: cameraRef, style: styles.camera, facing: "back", enableTorch: !terminated && enableTorch, onCameraReady: onCameraReady, onMountError: onMountError, children: _jsxs(View, { style: styles.overlay, children: [overlay?.title && (_jsx(View, { style: styles.topBar, children: _jsx(Text, { style: styles.titleText, children: overlay.title }) })), overlay?.showGuideFrame && (_jsxs(View, { style: styles.guideContainer, children: [_jsxs(View, { style: [
|
|
316
|
+
styles.guideFrame,
|
|
317
|
+
overlay.guideFrameAspectRatio
|
|
318
|
+
? { aspectRatio: overlay.guideFrameAspectRatio }
|
|
319
|
+
: undefined,
|
|
320
|
+
], children: [overlay.guideOverlayContent && (_jsx(View, { style: [StyleSheet.absoluteFill, { opacity: overlay.guideOverlayOpacity ?? 0.3 }], children: overlay.guideOverlayContent })), _jsx(View, { style: [styles.corner, styles.cornerTopLeft] }), _jsx(View, { style: [styles.corner, styles.cornerTopRight] }), _jsx(View, { style: [styles.corner, styles.cornerBottomLeft] }), _jsx(View, { style: [styles.corner, styles.cornerBottomRight] })] }), overlay.guideCaption && (_jsx(Text, { style: styles.guideCaptionText, children: overlay.guideCaption }))] })), status === 'processing' && (_jsxs(View, { style: styles.processingOverlay, children: [_jsx(ActivityIndicator, { size: "large", color: "#fff" }), _jsx(Text, { style: styles.statusText, children: overlay?.processingMessage || 'Analyzing photo...' })] })), showBottomCard && _jsx(View, { style: styles.cardBackdrop }), _jsxs(View, { style: styles.bottomArea, children: [status === 'success' && result && (_jsxs(View, { style: styles.resultCard, children: [_jsxs(View, { style: styles.resultCardHeader, children: [_jsx(View, { style: [
|
|
321
321
|
styles.resultIconCircle,
|
|
322
322
|
result.is_compliant ? styles.resultIconPass : styles.resultIconFail,
|
|
323
323
|
], children: _jsx(Text, { style: styles.resultIcon, children: result.is_compliant ? '\u2713' : '\u2717' }) }), _jsx(Text, { style: [
|
|
@@ -334,9 +334,16 @@ export function VerifyAIScanner({ onCapture, onResult, onError, overlay, style,
|
|
|
334
334
|
}
|
|
335
335
|
else if (overlay?.maxAttempts != null && attemptCountRef.current < overlay.maxAttempts && result && !result.is_compliant) {
|
|
336
336
|
const remaining = overlay.maxAttempts - attemptCountRef.current;
|
|
337
|
-
errorTitle = 'Not Verified';
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
errorTitle = overlay?.failureMessage || 'Not Verified';
|
|
338
|
+
if (overlay?.retryMessage) {
|
|
339
|
+
errorMessage = overlay.retryMessage.replace('{remaining}', String(remaining));
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Show actual API feedback with retry count (matches Flutter)
|
|
343
|
+
const feedback = result.feedback?.trim();
|
|
344
|
+
const retryInfo = `${remaining} attempt${remaining === 1 ? '' : 's'} remaining.`;
|
|
345
|
+
errorMessage = feedback ? `${feedback}\n\n${retryInfo}` : `Please try again. ${retryInfo}`;
|
|
346
|
+
}
|
|
340
347
|
}
|
|
341
348
|
else {
|
|
342
349
|
const display = getErrorDisplay(lastError, overlay?.showTechnicalErrorDetails);
|
|
@@ -382,6 +389,14 @@ const styles = StyleSheet.create({
|
|
|
382
389
|
alignItems: 'center',
|
|
383
390
|
paddingHorizontal: 40,
|
|
384
391
|
},
|
|
392
|
+
guideCaptionText: {
|
|
393
|
+
color: 'rgba(255,255,255,0.8)',
|
|
394
|
+
fontSize: 13,
|
|
395
|
+
fontWeight: '500',
|
|
396
|
+
textAlign: 'center',
|
|
397
|
+
marginTop: 12,
|
|
398
|
+
lineHeight: 18,
|
|
399
|
+
},
|
|
385
400
|
guideFrame: {
|
|
386
401
|
width: '100%',
|
|
387
402
|
aspectRatio: 4 / 3,
|
package/lib/types/index.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface VerificationResult {
|
|
|
30
30
|
feedback: string;
|
|
31
31
|
metadata: Record<string, unknown>;
|
|
32
32
|
image_url: string | null;
|
|
33
|
+
/** Classification category (e.g. good_parking, no_vehicle, poor_photo). */
|
|
34
|
+
category?: string;
|
|
33
35
|
}
|
|
34
36
|
export interface VerificationListResponse {
|
|
35
37
|
data: VerificationResult[];
|
|
@@ -87,6 +89,8 @@ export interface ScannerOverlayConfig {
|
|
|
87
89
|
guideOverlayContent?: React.ReactNode;
|
|
88
90
|
/** Opacity of the guideOverlayContent (0–1). Default: 0.3. */
|
|
89
91
|
guideOverlayOpacity?: number;
|
|
92
|
+
/** Caption text shown directly below the guide frame. */
|
|
93
|
+
guideCaption?: string;
|
|
90
94
|
processingMessage?: string;
|
|
91
95
|
successMessage?: string;
|
|
92
96
|
failureMessage?: string;
|
package/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "2.
|
|
1
|
+
export declare const SDK_VERSION = "2.3.0";
|
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = '2.
|
|
1
|
+
export const SDK_VERSION = '2.3.0';
|
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -262,11 +262,35 @@ export class VerifyAIClient {
|
|
|
262
262
|
headers['Idempotency-Key'] = options.idempotencyKey;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
265
|
+
try {
|
|
266
|
+
return await this.executeRequest<VerificationResult>('/verify', {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
headers,
|
|
269
|
+
body: formData,
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
// Blank 500 means the server crashed during multipart parsing (e.g.
|
|
273
|
+
// Vercel function crash). Retry as JSON/base64 with a fresh request
|
|
274
|
+
// — the crashed request may have partially recorded the idempotency key.
|
|
275
|
+
if (error instanceof VerifyAIRequestError && error.status >= 500 && error.isServerError) {
|
|
276
|
+
try {
|
|
277
|
+
const { Buffer } = await import('buffer');
|
|
278
|
+
const fileResponse = await fetch(request.imageUri);
|
|
279
|
+
const arrayBuffer = await fileResponse.arrayBuffer();
|
|
280
|
+
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
|
281
|
+
return this.verify({
|
|
282
|
+
image: base64,
|
|
283
|
+
policy: request.policy,
|
|
284
|
+
metadata: request.metadata,
|
|
285
|
+
provider: request.provider,
|
|
286
|
+
});
|
|
287
|
+
} catch {
|
|
288
|
+
// If the retry itself fails, throw the original error
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
270
294
|
}
|
|
271
295
|
|
|
272
296
|
/**
|
|
@@ -450,6 +450,9 @@ export function VerifyAIScanner({
|
|
|
450
450
|
<View style={[styles.corner, styles.cornerBottomLeft]} />
|
|
451
451
|
<View style={[styles.corner, styles.cornerBottomRight]} />
|
|
452
452
|
</View>
|
|
453
|
+
{overlay.guideCaption && (
|
|
454
|
+
<Text style={styles.guideCaptionText}>{overlay.guideCaption}</Text>
|
|
455
|
+
)}
|
|
453
456
|
</View>
|
|
454
457
|
)}
|
|
455
458
|
|
|
@@ -505,9 +508,15 @@ export function VerifyAIScanner({
|
|
|
505
508
|
errorMessage = overlay?.exhaustedMessage || 'Maximum attempts reached.';
|
|
506
509
|
} else if (overlay?.maxAttempts != null && attemptCountRef.current < overlay.maxAttempts && result && !result.is_compliant) {
|
|
507
510
|
const remaining = overlay.maxAttempts - attemptCountRef.current;
|
|
508
|
-
errorTitle = 'Not Verified';
|
|
509
|
-
|
|
510
|
-
|
|
511
|
+
errorTitle = overlay?.failureMessage || 'Not Verified';
|
|
512
|
+
if (overlay?.retryMessage) {
|
|
513
|
+
errorMessage = overlay.retryMessage.replace('{remaining}', String(remaining));
|
|
514
|
+
} else {
|
|
515
|
+
// Show actual API feedback with retry count (matches Flutter)
|
|
516
|
+
const feedback = result.feedback?.trim();
|
|
517
|
+
const retryInfo = `${remaining} attempt${remaining === 1 ? '' : 's'} remaining.`;
|
|
518
|
+
errorMessage = feedback ? `${feedback}\n\n${retryInfo}` : `Please try again. ${retryInfo}`;
|
|
519
|
+
}
|
|
511
520
|
} else {
|
|
512
521
|
const display = getErrorDisplay(lastError, overlay?.showTechnicalErrorDetails);
|
|
513
522
|
errorTitle = display.title;
|
|
@@ -595,6 +604,14 @@ const styles = StyleSheet.create({
|
|
|
595
604
|
alignItems: 'center',
|
|
596
605
|
paddingHorizontal: 40,
|
|
597
606
|
},
|
|
607
|
+
guideCaptionText: {
|
|
608
|
+
color: 'rgba(255,255,255,0.8)',
|
|
609
|
+
fontSize: 13,
|
|
610
|
+
fontWeight: '500',
|
|
611
|
+
textAlign: 'center' as const,
|
|
612
|
+
marginTop: 12,
|
|
613
|
+
lineHeight: 18,
|
|
614
|
+
},
|
|
598
615
|
guideFrame: {
|
|
599
616
|
width: '100%',
|
|
600
617
|
aspectRatio: 4 / 3,
|
package/src/types/index.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface VerificationResult {
|
|
|
34
34
|
feedback: string;
|
|
35
35
|
metadata: Record<string, unknown>;
|
|
36
36
|
image_url: string | null;
|
|
37
|
+
/** Classification category (e.g. good_parking, no_vehicle, poor_photo). */
|
|
38
|
+
category?: string;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
export interface VerificationListResponse {
|
|
@@ -98,6 +100,8 @@ export interface ScannerOverlayConfig {
|
|
|
98
100
|
guideOverlayContent?: React.ReactNode;
|
|
99
101
|
/** Opacity of the guideOverlayContent (0–1). Default: 0.3. */
|
|
100
102
|
guideOverlayOpacity?: number;
|
|
103
|
+
/** Caption text shown directly below the guide frame. */
|
|
104
|
+
guideCaption?: string;
|
|
101
105
|
processingMessage?: string;
|
|
102
106
|
successMessage?: string;
|
|
103
107
|
failureMessage?: string;
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = '2.
|
|
1
|
+
export const SDK_VERSION = '2.3.0';
|