@switchlabs/verify-ai-react-native 0.1.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.d.ts +62 -0
- package/lib/client/index.js +124 -0
- package/lib/components/VerifyAIScanner.d.ts +41 -0
- package/lib/components/VerifyAIScanner.js +222 -0
- package/lib/hooks/useVerifyAI.d.ts +48 -0
- package/lib/hooks/useVerifyAI.js +97 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +8 -0
- package/lib/storage/offlineQueue.d.ts +50 -0
- package/lib/storage/offlineQueue.js +179 -0
- package/lib/types/index.d.ts +58 -0
- package/lib/types/index.js +1 -0
- package/package.json +51 -0
- package/src/client/index.ts +158 -0
- package/src/components/VerifyAIScanner.tsx +346 -0
- package/src/hooks/useVerifyAI.ts +150 -0
- package/src/index.ts +26 -0
- package/src/storage/offlineQueue.ts +200 -0
- package/src/types/index.ts +66 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import React, { useRef, useState, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
ActivityIndicator,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
CameraView,
|
|
12
|
+
useCameraPermissions,
|
|
13
|
+
type CameraCapturedPicture,
|
|
14
|
+
} from 'expo-camera';
|
|
15
|
+
import type {
|
|
16
|
+
VerificationResult,
|
|
17
|
+
ScannerStatus,
|
|
18
|
+
ScannerOverlayConfig,
|
|
19
|
+
} from '../types';
|
|
20
|
+
|
|
21
|
+
export interface VerifyAIScannerProps {
|
|
22
|
+
/** Called with the base64 image data when the user captures a photo. */
|
|
23
|
+
onCapture: (base64Image: string) => Promise<VerificationResult | null>;
|
|
24
|
+
/** Called when verification completes successfully. */
|
|
25
|
+
onResult?: (result: VerificationResult) => void;
|
|
26
|
+
/** Called when an error occurs. */
|
|
27
|
+
onError?: (error: Error) => void;
|
|
28
|
+
/** Overlay configuration for the camera view. */
|
|
29
|
+
overlay?: ScannerOverlayConfig;
|
|
30
|
+
/** Custom style for the container. */
|
|
31
|
+
style?: ViewStyle;
|
|
32
|
+
/** Whether to show the default capture button. Set false to use your own. */
|
|
33
|
+
showCaptureButton?: boolean;
|
|
34
|
+
/** Ref to imperatively trigger capture from parent. */
|
|
35
|
+
captureRef?: React.MutableRefObject<(() => void) | null>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Camera scanner component for capturing verification photos.
|
|
40
|
+
* Uses expo-camera for the camera view and provides a simple capture UI.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const { verify } = useVerifyAI({ apiKey: 'vai_...' });
|
|
45
|
+
*
|
|
46
|
+
* <VerifyAIScanner
|
|
47
|
+
* onCapture={(base64) => verify({ image: base64, policy: 'scooter_parking' })}
|
|
48
|
+
* onResult={(result) => {
|
|
49
|
+
* if (result.is_compliant) navigation.navigate('Success');
|
|
50
|
+
* }}
|
|
51
|
+
* overlay={{
|
|
52
|
+
* title: 'Photo Verification',
|
|
53
|
+
* instructions: 'Center the scooter in the frame',
|
|
54
|
+
* showGuideFrame: true,
|
|
55
|
+
* }}
|
|
56
|
+
* />
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function VerifyAIScanner({
|
|
60
|
+
onCapture,
|
|
61
|
+
onResult,
|
|
62
|
+
onError,
|
|
63
|
+
overlay,
|
|
64
|
+
style,
|
|
65
|
+
showCaptureButton = true,
|
|
66
|
+
captureRef,
|
|
67
|
+
}: VerifyAIScannerProps) {
|
|
68
|
+
const cameraRef = useRef<CameraView>(null);
|
|
69
|
+
const [status, setStatus] = useState<ScannerStatus>('idle');
|
|
70
|
+
const [result, setResult] = useState<VerificationResult | null>(null);
|
|
71
|
+
const [permission, requestPermission] = useCameraPermissions();
|
|
72
|
+
|
|
73
|
+
const handleCapture = useCallback(async () => {
|
|
74
|
+
if (!cameraRef.current || status === 'capturing' || status === 'processing') return;
|
|
75
|
+
|
|
76
|
+
setStatus('capturing');
|
|
77
|
+
setResult(null);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const photo = await cameraRef.current.takePictureAsync({
|
|
81
|
+
base64: true,
|
|
82
|
+
quality: 0.8,
|
|
83
|
+
exif: false,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!photo?.base64) {
|
|
87
|
+
throw new Error('Failed to capture photo');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
setStatus('processing');
|
|
91
|
+
const verificationResult = await onCapture(photo.base64);
|
|
92
|
+
|
|
93
|
+
if (verificationResult) {
|
|
94
|
+
setResult(verificationResult);
|
|
95
|
+
setStatus('success');
|
|
96
|
+
onResult?.(verificationResult);
|
|
97
|
+
} else {
|
|
98
|
+
// null result means queued for offline
|
|
99
|
+
setStatus('idle');
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
103
|
+
setStatus('error');
|
|
104
|
+
onError?.(error);
|
|
105
|
+
// Reset after a brief pause
|
|
106
|
+
setTimeout(() => setStatus('idle'), 2000);
|
|
107
|
+
}
|
|
108
|
+
}, [status, onCapture, onResult, onError]);
|
|
109
|
+
|
|
110
|
+
// Expose capture to parent via ref
|
|
111
|
+
if (captureRef) {
|
|
112
|
+
captureRef.current = handleCapture;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!permission) {
|
|
116
|
+
return <View style={[styles.container, style]} />;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!permission.granted) {
|
|
120
|
+
return (
|
|
121
|
+
<View style={[styles.container, styles.permissionContainer, style]}>
|
|
122
|
+
<Text style={styles.permissionText}>Camera access is required for photo verification</Text>
|
|
123
|
+
<TouchableOpacity style={styles.permissionButton} onPress={requestPermission}>
|
|
124
|
+
<Text style={styles.permissionButtonText}>Grant Camera Access</Text>
|
|
125
|
+
</TouchableOpacity>
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<View style={[styles.container, style]}>
|
|
132
|
+
<CameraView ref={cameraRef} style={styles.camera} facing="back">
|
|
133
|
+
{/* Overlay */}
|
|
134
|
+
<View style={styles.overlay}>
|
|
135
|
+
{overlay?.title && (
|
|
136
|
+
<View style={styles.topBar}>
|
|
137
|
+
<Text style={styles.titleText}>{overlay.title}</Text>
|
|
138
|
+
</View>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
{overlay?.showGuideFrame && (
|
|
142
|
+
<View style={styles.guideContainer}>
|
|
143
|
+
<View
|
|
144
|
+
style={[
|
|
145
|
+
styles.guideFrame,
|
|
146
|
+
overlay.guideFrameAspectRatio
|
|
147
|
+
? { aspectRatio: overlay.guideFrameAspectRatio }
|
|
148
|
+
: undefined,
|
|
149
|
+
]}
|
|
150
|
+
/>
|
|
151
|
+
</View>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{overlay?.instructions && status === 'idle' && (
|
|
155
|
+
<View style={styles.instructionsContainer}>
|
|
156
|
+
<Text style={styles.instructionsText}>{overlay.instructions}</Text>
|
|
157
|
+
</View>
|
|
158
|
+
)}
|
|
159
|
+
|
|
160
|
+
{/* Status indicators */}
|
|
161
|
+
{status === 'processing' && (
|
|
162
|
+
<View style={styles.statusOverlay}>
|
|
163
|
+
<ActivityIndicator size="large" color="#fff" />
|
|
164
|
+
<Text style={styles.statusText}>Analyzing photo...</Text>
|
|
165
|
+
</View>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
{status === 'success' && result && (
|
|
169
|
+
<View style={styles.statusOverlay}>
|
|
170
|
+
<View
|
|
171
|
+
style={[
|
|
172
|
+
styles.resultBadge,
|
|
173
|
+
result.is_compliant ? styles.resultPass : styles.resultFail,
|
|
174
|
+
]}
|
|
175
|
+
>
|
|
176
|
+
<Text style={styles.resultText}>
|
|
177
|
+
{result.is_compliant ? 'PASS' : 'FAIL'}
|
|
178
|
+
</Text>
|
|
179
|
+
</View>
|
|
180
|
+
<Text style={styles.feedbackText}>{result.feedback}</Text>
|
|
181
|
+
</View>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{status === 'error' && (
|
|
185
|
+
<View style={styles.statusOverlay}>
|
|
186
|
+
<Text style={styles.errorText}>Verification failed. Try again.</Text>
|
|
187
|
+
</View>
|
|
188
|
+
)}
|
|
189
|
+
</View>
|
|
190
|
+
|
|
191
|
+
{/* Capture button */}
|
|
192
|
+
{showCaptureButton && (
|
|
193
|
+
<View style={styles.bottomBar}>
|
|
194
|
+
<TouchableOpacity
|
|
195
|
+
style={[
|
|
196
|
+
styles.captureButton,
|
|
197
|
+
(status === 'capturing' || status === 'processing') && styles.captureButtonDisabled,
|
|
198
|
+
]}
|
|
199
|
+
onPress={handleCapture}
|
|
200
|
+
disabled={status === 'capturing' || status === 'processing'}
|
|
201
|
+
activeOpacity={0.7}
|
|
202
|
+
>
|
|
203
|
+
<View style={styles.captureButtonInner} />
|
|
204
|
+
</TouchableOpacity>
|
|
205
|
+
</View>
|
|
206
|
+
)}
|
|
207
|
+
</CameraView>
|
|
208
|
+
</View>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const styles = StyleSheet.create({
|
|
213
|
+
container: {
|
|
214
|
+
flex: 1,
|
|
215
|
+
backgroundColor: '#000',
|
|
216
|
+
},
|
|
217
|
+
camera: {
|
|
218
|
+
flex: 1,
|
|
219
|
+
},
|
|
220
|
+
overlay: {
|
|
221
|
+
...StyleSheet.absoluteFillObject,
|
|
222
|
+
justifyContent: 'space-between',
|
|
223
|
+
},
|
|
224
|
+
topBar: {
|
|
225
|
+
paddingTop: 60,
|
|
226
|
+
paddingHorizontal: 20,
|
|
227
|
+
alignItems: 'center',
|
|
228
|
+
},
|
|
229
|
+
titleText: {
|
|
230
|
+
color: '#fff',
|
|
231
|
+
fontSize: 18,
|
|
232
|
+
fontWeight: '600',
|
|
233
|
+
},
|
|
234
|
+
guideContainer: {
|
|
235
|
+
flex: 1,
|
|
236
|
+
justifyContent: 'center',
|
|
237
|
+
alignItems: 'center',
|
|
238
|
+
paddingHorizontal: 40,
|
|
239
|
+
},
|
|
240
|
+
guideFrame: {
|
|
241
|
+
width: '100%',
|
|
242
|
+
aspectRatio: 4 / 3,
|
|
243
|
+
borderWidth: 2,
|
|
244
|
+
borderColor: 'rgba(255, 255, 255, 0.5)',
|
|
245
|
+
borderRadius: 12,
|
|
246
|
+
borderStyle: 'dashed',
|
|
247
|
+
},
|
|
248
|
+
instructionsContainer: {
|
|
249
|
+
paddingHorizontal: 20,
|
|
250
|
+
paddingBottom: 20,
|
|
251
|
+
alignItems: 'center',
|
|
252
|
+
},
|
|
253
|
+
instructionsText: {
|
|
254
|
+
color: 'rgba(255, 255, 255, 0.8)',
|
|
255
|
+
fontSize: 14,
|
|
256
|
+
textAlign: 'center',
|
|
257
|
+
},
|
|
258
|
+
statusOverlay: {
|
|
259
|
+
...StyleSheet.absoluteFillObject,
|
|
260
|
+
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
261
|
+
justifyContent: 'center',
|
|
262
|
+
alignItems: 'center',
|
|
263
|
+
gap: 16,
|
|
264
|
+
},
|
|
265
|
+
statusText: {
|
|
266
|
+
color: '#fff',
|
|
267
|
+
fontSize: 16,
|
|
268
|
+
fontWeight: '500',
|
|
269
|
+
},
|
|
270
|
+
resultBadge: {
|
|
271
|
+
paddingHorizontal: 24,
|
|
272
|
+
paddingVertical: 12,
|
|
273
|
+
borderRadius: 8,
|
|
274
|
+
},
|
|
275
|
+
resultPass: {
|
|
276
|
+
backgroundColor: 'rgba(34, 197, 94, 0.9)',
|
|
277
|
+
},
|
|
278
|
+
resultFail: {
|
|
279
|
+
backgroundColor: 'rgba(239, 68, 68, 0.9)',
|
|
280
|
+
},
|
|
281
|
+
resultText: {
|
|
282
|
+
color: '#fff',
|
|
283
|
+
fontSize: 24,
|
|
284
|
+
fontWeight: '700',
|
|
285
|
+
},
|
|
286
|
+
feedbackText: {
|
|
287
|
+
color: '#fff',
|
|
288
|
+
fontSize: 14,
|
|
289
|
+
textAlign: 'center',
|
|
290
|
+
paddingHorizontal: 40,
|
|
291
|
+
lineHeight: 20,
|
|
292
|
+
},
|
|
293
|
+
errorText: {
|
|
294
|
+
color: '#ef4444',
|
|
295
|
+
fontSize: 16,
|
|
296
|
+
fontWeight: '500',
|
|
297
|
+
},
|
|
298
|
+
bottomBar: {
|
|
299
|
+
position: 'absolute',
|
|
300
|
+
bottom: 40,
|
|
301
|
+
left: 0,
|
|
302
|
+
right: 0,
|
|
303
|
+
alignItems: 'center',
|
|
304
|
+
},
|
|
305
|
+
captureButton: {
|
|
306
|
+
width: 72,
|
|
307
|
+
height: 72,
|
|
308
|
+
borderRadius: 36,
|
|
309
|
+
borderWidth: 4,
|
|
310
|
+
borderColor: '#fff',
|
|
311
|
+
justifyContent: 'center',
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
},
|
|
314
|
+
captureButtonDisabled: {
|
|
315
|
+
opacity: 0.4,
|
|
316
|
+
},
|
|
317
|
+
captureButtonInner: {
|
|
318
|
+
width: 58,
|
|
319
|
+
height: 58,
|
|
320
|
+
borderRadius: 29,
|
|
321
|
+
backgroundColor: '#fff',
|
|
322
|
+
},
|
|
323
|
+
permissionContainer: {
|
|
324
|
+
justifyContent: 'center',
|
|
325
|
+
alignItems: 'center',
|
|
326
|
+
paddingHorizontal: 40,
|
|
327
|
+
gap: 20,
|
|
328
|
+
},
|
|
329
|
+
permissionText: {
|
|
330
|
+
color: '#fff',
|
|
331
|
+
fontSize: 16,
|
|
332
|
+
textAlign: 'center',
|
|
333
|
+
lineHeight: 24,
|
|
334
|
+
},
|
|
335
|
+
permissionButton: {
|
|
336
|
+
backgroundColor: '#3b82f6',
|
|
337
|
+
paddingHorizontal: 24,
|
|
338
|
+
paddingVertical: 12,
|
|
339
|
+
borderRadius: 8,
|
|
340
|
+
},
|
|
341
|
+
permissionButtonText: {
|
|
342
|
+
color: '#fff',
|
|
343
|
+
fontSize: 16,
|
|
344
|
+
fontWeight: '600',
|
|
345
|
+
},
|
|
346
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { useRef, useMemo, useCallback, useState, useEffect } from 'react';
|
|
2
|
+
import { AppState, type AppStateStatus } from 'react-native';
|
|
3
|
+
import { VerifyAIClient } from '../client';
|
|
4
|
+
import { OfflineQueue } from '../storage/offlineQueue';
|
|
5
|
+
import type {
|
|
6
|
+
VerifyAIConfig,
|
|
7
|
+
VerificationRequest,
|
|
8
|
+
VerificationResult,
|
|
9
|
+
VerificationListParams,
|
|
10
|
+
VerificationListResponse,
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
13
|
+
export interface UseVerifyAIReturn {
|
|
14
|
+
/** Submit a verification. Uses offline queue if offlineMode is enabled and request fails. */
|
|
15
|
+
verify: (request: VerificationRequest) => Promise<VerificationResult | null>;
|
|
16
|
+
/** List past verifications. */
|
|
17
|
+
listVerifications: (params?: VerificationListParams) => Promise<VerificationListResponse>;
|
|
18
|
+
/** Get a single verification by ID. */
|
|
19
|
+
getVerification: (id: string) => Promise<VerificationResult>;
|
|
20
|
+
/** Whether a verification is currently in progress. */
|
|
21
|
+
loading: boolean;
|
|
22
|
+
/** The most recent error, if any. */
|
|
23
|
+
error: Error | null;
|
|
24
|
+
/** The most recent verification result. */
|
|
25
|
+
lastResult: VerificationResult | null;
|
|
26
|
+
/** Number of items in the offline queue. */
|
|
27
|
+
queueSize: number;
|
|
28
|
+
/** Manually process the offline queue. */
|
|
29
|
+
processQueue: () => Promise<void>;
|
|
30
|
+
/** The underlying client instance. */
|
|
31
|
+
client: VerifyAIClient;
|
|
32
|
+
/** The offline queue instance (null if offlineMode is disabled). */
|
|
33
|
+
offlineQueue: OfflineQueue | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* React hook for Verify AI. Provides verification methods,
|
|
38
|
+
* loading/error state, and optional offline queue management.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* const { verify, loading, lastResult, error } = useVerifyAI({
|
|
43
|
+
* apiKey: 'vai_your_api_key',
|
|
44
|
+
* offlineMode: true,
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* const handleCapture = async (base64Image: string) => {
|
|
48
|
+
* const result = await verify({
|
|
49
|
+
* image: base64Image,
|
|
50
|
+
* policy: 'scooter_parking',
|
|
51
|
+
* });
|
|
52
|
+
* if (result?.is_compliant) {
|
|
53
|
+
* // Success
|
|
54
|
+
* }
|
|
55
|
+
* };
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function useVerifyAI(config: VerifyAIConfig): UseVerifyAIReturn {
|
|
59
|
+
const client = useMemo(() => new VerifyAIClient(config), [config.apiKey, config.baseUrl, config.timeout]);
|
|
60
|
+
const offlineQueue = useMemo(
|
|
61
|
+
() => (config.offlineMode ? new OfflineQueue(client) : null),
|
|
62
|
+
[client, config.offlineMode]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const [loading, setLoading] = useState(false);
|
|
66
|
+
const [error, setError] = useState<Error | null>(null);
|
|
67
|
+
const [lastResult, setLastResult] = useState<VerificationResult | null>(null);
|
|
68
|
+
const [queueSize, setQueueSize] = useState(0);
|
|
69
|
+
|
|
70
|
+
// Refresh queue size
|
|
71
|
+
const refreshQueueSize = useCallback(async () => {
|
|
72
|
+
if (offlineQueue) {
|
|
73
|
+
const size = await offlineQueue.getQueueSize();
|
|
74
|
+
setQueueSize(size);
|
|
75
|
+
}
|
|
76
|
+
}, [offlineQueue]);
|
|
77
|
+
|
|
78
|
+
// Process queue on app foreground
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!offlineQueue) return;
|
|
81
|
+
|
|
82
|
+
refreshQueueSize();
|
|
83
|
+
|
|
84
|
+
const handleAppState = (state: AppStateStatus) => {
|
|
85
|
+
if (state === 'active') {
|
|
86
|
+
offlineQueue.processQueue().then(() => refreshQueueSize());
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const subscription = AppState.addEventListener('change', handleAppState);
|
|
91
|
+
return () => subscription.remove();
|
|
92
|
+
}, [offlineQueue, refreshQueueSize]);
|
|
93
|
+
|
|
94
|
+
const verify = useCallback(
|
|
95
|
+
async (request: VerificationRequest): Promise<VerificationResult | null> => {
|
|
96
|
+
setLoading(true);
|
|
97
|
+
setError(null);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const result = await client.verify(request);
|
|
101
|
+
setLastResult(result);
|
|
102
|
+
return result;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
105
|
+
setError(error);
|
|
106
|
+
|
|
107
|
+
// If offline mode, queue the request
|
|
108
|
+
if (offlineQueue) {
|
|
109
|
+
await offlineQueue.enqueue(request);
|
|
110
|
+
await refreshQueueSize();
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw error;
|
|
115
|
+
} finally {
|
|
116
|
+
setLoading(false);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
[client, offlineQueue, refreshQueueSize]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const listVerifications = useCallback(
|
|
123
|
+
(params?: VerificationListParams) => client.listVerifications(params),
|
|
124
|
+
[client]
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const getVerification = useCallback(
|
|
128
|
+
(id: string) => client.getVerification(id),
|
|
129
|
+
[client]
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const processQueue = useCallback(async () => {
|
|
133
|
+
if (!offlineQueue) return;
|
|
134
|
+
await offlineQueue.processQueue((_, result) => setLastResult(result));
|
|
135
|
+
await refreshQueueSize();
|
|
136
|
+
}, [offlineQueue, refreshQueueSize]);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
verify,
|
|
140
|
+
listVerifications,
|
|
141
|
+
getVerification,
|
|
142
|
+
loading,
|
|
143
|
+
error,
|
|
144
|
+
lastResult,
|
|
145
|
+
queueSize,
|
|
146
|
+
processQueue,
|
|
147
|
+
client,
|
|
148
|
+
offlineQueue,
|
|
149
|
+
};
|
|
150
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Client
|
|
2
|
+
export { VerifyAIClient, VerifyAIRequestError } from './client';
|
|
3
|
+
|
|
4
|
+
// Hooks
|
|
5
|
+
export { useVerifyAI } from './hooks/useVerifyAI';
|
|
6
|
+
export type { UseVerifyAIReturn } from './hooks/useVerifyAI';
|
|
7
|
+
|
|
8
|
+
// Components
|
|
9
|
+
export { VerifyAIScanner } from './components/VerifyAIScanner';
|
|
10
|
+
export type { VerifyAIScannerProps } from './components/VerifyAIScanner';
|
|
11
|
+
|
|
12
|
+
// Offline Queue
|
|
13
|
+
export { OfflineQueue } from './storage/offlineQueue';
|
|
14
|
+
|
|
15
|
+
// Types
|
|
16
|
+
export type {
|
|
17
|
+
VerifyAIConfig,
|
|
18
|
+
VerificationRequest,
|
|
19
|
+
VerificationResult,
|
|
20
|
+
VerificationListResponse,
|
|
21
|
+
VerificationListParams,
|
|
22
|
+
QueueItem,
|
|
23
|
+
VerifyAIError,
|
|
24
|
+
ScannerStatus,
|
|
25
|
+
ScannerOverlayConfig,
|
|
26
|
+
} from './types';
|