@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.
@@ -0,0 +1,62 @@
1
+ import type { VerifyAIConfig, VerificationRequest, VerificationResult, VerificationListResponse, VerificationListParams, VerifyAIError } from '../types';
2
+ export declare class VerifyAIClient {
3
+ private apiKey;
4
+ private baseUrl;
5
+ private timeout;
6
+ constructor(config: VerifyAIConfig);
7
+ private request;
8
+ /**
9
+ * Submit a photo for AI verification.
10
+ *
11
+ * @param request - The verification request with base64 image and policy
12
+ * @returns The verification result with compliance status and feedback
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const result = await client.verify({
17
+ * image: base64ImageData,
18
+ * policy: 'scooter_parking',
19
+ * metadata: { device_id: 'dev_123', gps: { lat: 37.77, lng: -122.42 } },
20
+ * });
21
+ *
22
+ * if (result.is_compliant) {
23
+ * console.log('Parking approved!');
24
+ * } else {
25
+ * console.log('Issues:', result.violation_reasons);
26
+ * console.log('Feedback:', result.feedback);
27
+ * }
28
+ * ```
29
+ */
30
+ verify(request: VerificationRequest): Promise<VerificationResult>;
31
+ /**
32
+ * List past verifications with optional filters.
33
+ *
34
+ * @param params - Pagination and filter parameters
35
+ * @returns Paginated list of verification results
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const { data, has_more, next_cursor } = await client.listVerifications({
40
+ * limit: 20,
41
+ * policy: 'scooter_parking',
42
+ * is_compliant: false,
43
+ * });
44
+ * ```
45
+ */
46
+ listVerifications(params?: VerificationListParams): Promise<VerificationListResponse>;
47
+ /**
48
+ * Get a single verification by ID.
49
+ *
50
+ * @param id - The verification ID (e.g., "ver_8x92m4k9")
51
+ * @returns The full verification result with a fresh signed image URL
52
+ */
53
+ getVerification(id: string): Promise<VerificationResult>;
54
+ }
55
+ export declare class VerifyAIRequestError extends Error {
56
+ status: number;
57
+ body: VerifyAIError;
58
+ constructor(message: string, status: number, body: VerifyAIError);
59
+ get isRateLimited(): boolean;
60
+ get isUnauthorized(): boolean;
61
+ get upgradeUrl(): string | undefined;
62
+ }
@@ -0,0 +1,124 @@
1
+ const DEFAULT_BASE_URL = 'https://verify.switchlabs.dev/api/v1';
2
+ const DEFAULT_TIMEOUT = 30000;
3
+ export class VerifyAIClient {
4
+ constructor(config) {
5
+ if (!config.apiKey) {
6
+ throw new Error('VerifyAI: apiKey is required');
7
+ }
8
+ this.apiKey = config.apiKey;
9
+ this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
10
+ this.timeout = config.timeout || DEFAULT_TIMEOUT;
11
+ }
12
+ async request(path, options = {}) {
13
+ const controller = new AbortController();
14
+ const timer = setTimeout(() => controller.abort(), this.timeout);
15
+ try {
16
+ const response = await fetch(`${this.baseUrl}${path}`, {
17
+ ...options,
18
+ signal: controller.signal,
19
+ headers: {
20
+ 'X-API-Key': this.apiKey,
21
+ ...options.headers,
22
+ },
23
+ });
24
+ const body = await response.json();
25
+ if (!response.ok) {
26
+ const error = body;
27
+ throw new VerifyAIRequestError(error.error || `Request failed with status ${response.status}`, response.status, error);
28
+ }
29
+ return body;
30
+ }
31
+ finally {
32
+ clearTimeout(timer);
33
+ }
34
+ }
35
+ /**
36
+ * Submit a photo for AI verification.
37
+ *
38
+ * @param request - The verification request with base64 image and policy
39
+ * @returns The verification result with compliance status and feedback
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const result = await client.verify({
44
+ * image: base64ImageData,
45
+ * policy: 'scooter_parking',
46
+ * metadata: { device_id: 'dev_123', gps: { lat: 37.77, lng: -122.42 } },
47
+ * });
48
+ *
49
+ * if (result.is_compliant) {
50
+ * console.log('Parking approved!');
51
+ * } else {
52
+ * console.log('Issues:', result.violation_reasons);
53
+ * console.log('Feedback:', result.feedback);
54
+ * }
55
+ * ```
56
+ */
57
+ async verify(request) {
58
+ return this.request('/verify', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify(request),
62
+ });
63
+ }
64
+ /**
65
+ * List past verifications with optional filters.
66
+ *
67
+ * @param params - Pagination and filter parameters
68
+ * @returns Paginated list of verification results
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const { data, has_more, next_cursor } = await client.listVerifications({
73
+ * limit: 20,
74
+ * policy: 'scooter_parking',
75
+ * is_compliant: false,
76
+ * });
77
+ * ```
78
+ */
79
+ async listVerifications(params = {}) {
80
+ const searchParams = new URLSearchParams();
81
+ if (params.limit)
82
+ searchParams.set('limit', String(params.limit));
83
+ if (params.cursor)
84
+ searchParams.set('cursor', params.cursor);
85
+ if (params.policy)
86
+ searchParams.set('policy', params.policy);
87
+ if (params.status)
88
+ searchParams.set('status', params.status);
89
+ if (params.is_compliant !== undefined)
90
+ searchParams.set('is_compliant', String(params.is_compliant));
91
+ if (params.start_date)
92
+ searchParams.set('start_date', params.start_date);
93
+ if (params.end_date)
94
+ searchParams.set('end_date', params.end_date);
95
+ const qs = searchParams.toString();
96
+ return this.request(`/verifications${qs ? `?${qs}` : ''}`);
97
+ }
98
+ /**
99
+ * Get a single verification by ID.
100
+ *
101
+ * @param id - The verification ID (e.g., "ver_8x92m4k9")
102
+ * @returns The full verification result with a fresh signed image URL
103
+ */
104
+ async getVerification(id) {
105
+ return this.request(`/verifications/${id}`);
106
+ }
107
+ }
108
+ export class VerifyAIRequestError extends Error {
109
+ constructor(message, status, body) {
110
+ super(message);
111
+ this.name = 'VerifyAIRequestError';
112
+ this.status = status;
113
+ this.body = body;
114
+ }
115
+ get isRateLimited() {
116
+ return this.status === 429;
117
+ }
118
+ get isUnauthorized() {
119
+ return this.status === 401;
120
+ }
121
+ get upgradeUrl() {
122
+ return this.body.upgrade_url;
123
+ }
124
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { type ViewStyle } from 'react-native';
3
+ import type { VerificationResult, ScannerOverlayConfig } from '../types';
4
+ export interface VerifyAIScannerProps {
5
+ /** Called with the base64 image data when the user captures a photo. */
6
+ onCapture: (base64Image: string) => Promise<VerificationResult | null>;
7
+ /** Called when verification completes successfully. */
8
+ onResult?: (result: VerificationResult) => void;
9
+ /** Called when an error occurs. */
10
+ onError?: (error: Error) => void;
11
+ /** Overlay configuration for the camera view. */
12
+ overlay?: ScannerOverlayConfig;
13
+ /** Custom style for the container. */
14
+ style?: ViewStyle;
15
+ /** Whether to show the default capture button. Set false to use your own. */
16
+ showCaptureButton?: boolean;
17
+ /** Ref to imperatively trigger capture from parent. */
18
+ captureRef?: React.MutableRefObject<(() => void) | null>;
19
+ }
20
+ /**
21
+ * Camera scanner component for capturing verification photos.
22
+ * Uses expo-camera for the camera view and provides a simple capture UI.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * const { verify } = useVerifyAI({ apiKey: 'vai_...' });
27
+ *
28
+ * <VerifyAIScanner
29
+ * onCapture={(base64) => verify({ image: base64, policy: 'scooter_parking' })}
30
+ * onResult={(result) => {
31
+ * if (result.is_compliant) navigation.navigate('Success');
32
+ * }}
33
+ * overlay={{
34
+ * title: 'Photo Verification',
35
+ * instructions: 'Center the scooter in the frame',
36
+ * showGuideFrame: true,
37
+ * }}
38
+ * />
39
+ * ```
40
+ */
41
+ export declare function VerifyAIScanner({ onCapture, onResult, onError, overlay, style, showCaptureButton, captureRef, }: VerifyAIScannerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,222 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState, useCallback } from 'react';
3
+ import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, } from 'react-native';
4
+ import { CameraView, useCameraPermissions, } from 'expo-camera';
5
+ /**
6
+ * Camera scanner component for capturing verification photos.
7
+ * Uses expo-camera for the camera view and provides a simple capture UI.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const { verify } = useVerifyAI({ apiKey: 'vai_...' });
12
+ *
13
+ * <VerifyAIScanner
14
+ * onCapture={(base64) => verify({ image: base64, policy: 'scooter_parking' })}
15
+ * onResult={(result) => {
16
+ * if (result.is_compliant) navigation.navigate('Success');
17
+ * }}
18
+ * overlay={{
19
+ * title: 'Photo Verification',
20
+ * instructions: 'Center the scooter in the frame',
21
+ * showGuideFrame: true,
22
+ * }}
23
+ * />
24
+ * ```
25
+ */
26
+ export function VerifyAIScanner({ onCapture, onResult, onError, overlay, style, showCaptureButton = true, captureRef, }) {
27
+ const cameraRef = useRef(null);
28
+ const [status, setStatus] = useState('idle');
29
+ const [result, setResult] = useState(null);
30
+ const [permission, requestPermission] = useCameraPermissions();
31
+ const handleCapture = useCallback(async () => {
32
+ if (!cameraRef.current || status === 'capturing' || status === 'processing')
33
+ return;
34
+ setStatus('capturing');
35
+ setResult(null);
36
+ try {
37
+ const photo = await cameraRef.current.takePictureAsync({
38
+ base64: true,
39
+ quality: 0.8,
40
+ exif: false,
41
+ });
42
+ if (!photo?.base64) {
43
+ throw new Error('Failed to capture photo');
44
+ }
45
+ setStatus('processing');
46
+ const verificationResult = await onCapture(photo.base64);
47
+ if (verificationResult) {
48
+ setResult(verificationResult);
49
+ setStatus('success');
50
+ onResult?.(verificationResult);
51
+ }
52
+ else {
53
+ // null result means queued for offline
54
+ setStatus('idle');
55
+ }
56
+ }
57
+ catch (err) {
58
+ const error = err instanceof Error ? err : new Error(String(err));
59
+ setStatus('error');
60
+ onError?.(error);
61
+ // Reset after a brief pause
62
+ setTimeout(() => setStatus('idle'), 2000);
63
+ }
64
+ }, [status, onCapture, onResult, onError]);
65
+ // Expose capture to parent via ref
66
+ if (captureRef) {
67
+ captureRef.current = handleCapture;
68
+ }
69
+ if (!permission) {
70
+ return _jsx(View, { style: [styles.container, style] });
71
+ }
72
+ if (!permission.granted) {
73
+ 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" }) })] }));
74
+ }
75
+ return (_jsx(View, { style: [styles.container, style], children: _jsxs(CameraView, { ref: cameraRef, style: styles.camera, facing: "back", 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 && (_jsx(View, { style: styles.guideContainer, children: _jsx(View, { style: [
76
+ styles.guideFrame,
77
+ overlay.guideFrameAspectRatio
78
+ ? { aspectRatio: overlay.guideFrameAspectRatio }
79
+ : undefined,
80
+ ] }) })), overlay?.instructions && status === 'idle' && (_jsx(View, { style: styles.instructionsContainer, children: _jsx(Text, { style: styles.instructionsText, children: overlay.instructions }) })), status === 'processing' && (_jsxs(View, { style: styles.statusOverlay, children: [_jsx(ActivityIndicator, { size: "large", color: "#fff" }), _jsx(Text, { style: styles.statusText, children: "Analyzing photo..." })] })), status === 'success' && result && (_jsxs(View, { style: styles.statusOverlay, children: [_jsx(View, { style: [
81
+ styles.resultBadge,
82
+ result.is_compliant ? styles.resultPass : styles.resultFail,
83
+ ], children: _jsx(Text, { style: styles.resultText, children: result.is_compliant ? 'PASS' : 'FAIL' }) }), _jsx(Text, { style: styles.feedbackText, children: result.feedback })] })), status === 'error' && (_jsx(View, { style: styles.statusOverlay, children: _jsx(Text, { style: styles.errorText, children: "Verification failed. Try again." }) }))] }), showCaptureButton && (_jsx(View, { style: styles.bottomBar, children: _jsx(TouchableOpacity, { style: [
84
+ styles.captureButton,
85
+ (status === 'capturing' || status === 'processing') && styles.captureButtonDisabled,
86
+ ], onPress: handleCapture, disabled: status === 'capturing' || status === 'processing', activeOpacity: 0.7, children: _jsx(View, { style: styles.captureButtonInner }) }) }))] }) }));
87
+ }
88
+ const styles = StyleSheet.create({
89
+ container: {
90
+ flex: 1,
91
+ backgroundColor: '#000',
92
+ },
93
+ camera: {
94
+ flex: 1,
95
+ },
96
+ overlay: {
97
+ ...StyleSheet.absoluteFillObject,
98
+ justifyContent: 'space-between',
99
+ },
100
+ topBar: {
101
+ paddingTop: 60,
102
+ paddingHorizontal: 20,
103
+ alignItems: 'center',
104
+ },
105
+ titleText: {
106
+ color: '#fff',
107
+ fontSize: 18,
108
+ fontWeight: '600',
109
+ },
110
+ guideContainer: {
111
+ flex: 1,
112
+ justifyContent: 'center',
113
+ alignItems: 'center',
114
+ paddingHorizontal: 40,
115
+ },
116
+ guideFrame: {
117
+ width: '100%',
118
+ aspectRatio: 4 / 3,
119
+ borderWidth: 2,
120
+ borderColor: 'rgba(255, 255, 255, 0.5)',
121
+ borderRadius: 12,
122
+ borderStyle: 'dashed',
123
+ },
124
+ instructionsContainer: {
125
+ paddingHorizontal: 20,
126
+ paddingBottom: 20,
127
+ alignItems: 'center',
128
+ },
129
+ instructionsText: {
130
+ color: 'rgba(255, 255, 255, 0.8)',
131
+ fontSize: 14,
132
+ textAlign: 'center',
133
+ },
134
+ statusOverlay: {
135
+ ...StyleSheet.absoluteFillObject,
136
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
137
+ justifyContent: 'center',
138
+ alignItems: 'center',
139
+ gap: 16,
140
+ },
141
+ statusText: {
142
+ color: '#fff',
143
+ fontSize: 16,
144
+ fontWeight: '500',
145
+ },
146
+ resultBadge: {
147
+ paddingHorizontal: 24,
148
+ paddingVertical: 12,
149
+ borderRadius: 8,
150
+ },
151
+ resultPass: {
152
+ backgroundColor: 'rgba(34, 197, 94, 0.9)',
153
+ },
154
+ resultFail: {
155
+ backgroundColor: 'rgba(239, 68, 68, 0.9)',
156
+ },
157
+ resultText: {
158
+ color: '#fff',
159
+ fontSize: 24,
160
+ fontWeight: '700',
161
+ },
162
+ feedbackText: {
163
+ color: '#fff',
164
+ fontSize: 14,
165
+ textAlign: 'center',
166
+ paddingHorizontal: 40,
167
+ lineHeight: 20,
168
+ },
169
+ errorText: {
170
+ color: '#ef4444',
171
+ fontSize: 16,
172
+ fontWeight: '500',
173
+ },
174
+ bottomBar: {
175
+ position: 'absolute',
176
+ bottom: 40,
177
+ left: 0,
178
+ right: 0,
179
+ alignItems: 'center',
180
+ },
181
+ captureButton: {
182
+ width: 72,
183
+ height: 72,
184
+ borderRadius: 36,
185
+ borderWidth: 4,
186
+ borderColor: '#fff',
187
+ justifyContent: 'center',
188
+ alignItems: 'center',
189
+ },
190
+ captureButtonDisabled: {
191
+ opacity: 0.4,
192
+ },
193
+ captureButtonInner: {
194
+ width: 58,
195
+ height: 58,
196
+ borderRadius: 29,
197
+ backgroundColor: '#fff',
198
+ },
199
+ permissionContainer: {
200
+ justifyContent: 'center',
201
+ alignItems: 'center',
202
+ paddingHorizontal: 40,
203
+ gap: 20,
204
+ },
205
+ permissionText: {
206
+ color: '#fff',
207
+ fontSize: 16,
208
+ textAlign: 'center',
209
+ lineHeight: 24,
210
+ },
211
+ permissionButton: {
212
+ backgroundColor: '#3b82f6',
213
+ paddingHorizontal: 24,
214
+ paddingVertical: 12,
215
+ borderRadius: 8,
216
+ },
217
+ permissionButtonText: {
218
+ color: '#fff',
219
+ fontSize: 16,
220
+ fontWeight: '600',
221
+ },
222
+ });
@@ -0,0 +1,48 @@
1
+ import { VerifyAIClient } from '../client';
2
+ import { OfflineQueue } from '../storage/offlineQueue';
3
+ import type { VerifyAIConfig, VerificationRequest, VerificationResult, VerificationListParams, VerificationListResponse } from '../types';
4
+ export interface UseVerifyAIReturn {
5
+ /** Submit a verification. Uses offline queue if offlineMode is enabled and request fails. */
6
+ verify: (request: VerificationRequest) => Promise<VerificationResult | null>;
7
+ /** List past verifications. */
8
+ listVerifications: (params?: VerificationListParams) => Promise<VerificationListResponse>;
9
+ /** Get a single verification by ID. */
10
+ getVerification: (id: string) => Promise<VerificationResult>;
11
+ /** Whether a verification is currently in progress. */
12
+ loading: boolean;
13
+ /** The most recent error, if any. */
14
+ error: Error | null;
15
+ /** The most recent verification result. */
16
+ lastResult: VerificationResult | null;
17
+ /** Number of items in the offline queue. */
18
+ queueSize: number;
19
+ /** Manually process the offline queue. */
20
+ processQueue: () => Promise<void>;
21
+ /** The underlying client instance. */
22
+ client: VerifyAIClient;
23
+ /** The offline queue instance (null if offlineMode is disabled). */
24
+ offlineQueue: OfflineQueue | null;
25
+ }
26
+ /**
27
+ * React hook for Verify AI. Provides verification methods,
28
+ * loading/error state, and optional offline queue management.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const { verify, loading, lastResult, error } = useVerifyAI({
33
+ * apiKey: 'vai_your_api_key',
34
+ * offlineMode: true,
35
+ * });
36
+ *
37
+ * const handleCapture = async (base64Image: string) => {
38
+ * const result = await verify({
39
+ * image: base64Image,
40
+ * policy: 'scooter_parking',
41
+ * });
42
+ * if (result?.is_compliant) {
43
+ * // Success
44
+ * }
45
+ * };
46
+ * ```
47
+ */
48
+ export declare function useVerifyAI(config: VerifyAIConfig): UseVerifyAIReturn;
@@ -0,0 +1,97 @@
1
+ import { useMemo, useCallback, useState, useEffect } from 'react';
2
+ import { AppState } from 'react-native';
3
+ import { VerifyAIClient } from '../client';
4
+ import { OfflineQueue } from '../storage/offlineQueue';
5
+ /**
6
+ * React hook for Verify AI. Provides verification methods,
7
+ * loading/error state, and optional offline queue management.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const { verify, loading, lastResult, error } = useVerifyAI({
12
+ * apiKey: 'vai_your_api_key',
13
+ * offlineMode: true,
14
+ * });
15
+ *
16
+ * const handleCapture = async (base64Image: string) => {
17
+ * const result = await verify({
18
+ * image: base64Image,
19
+ * policy: 'scooter_parking',
20
+ * });
21
+ * if (result?.is_compliant) {
22
+ * // Success
23
+ * }
24
+ * };
25
+ * ```
26
+ */
27
+ export function useVerifyAI(config) {
28
+ const client = useMemo(() => new VerifyAIClient(config), [config.apiKey, config.baseUrl, config.timeout]);
29
+ const offlineQueue = useMemo(() => (config.offlineMode ? new OfflineQueue(client) : null), [client, config.offlineMode]);
30
+ const [loading, setLoading] = useState(false);
31
+ const [error, setError] = useState(null);
32
+ const [lastResult, setLastResult] = useState(null);
33
+ const [queueSize, setQueueSize] = useState(0);
34
+ // Refresh queue size
35
+ const refreshQueueSize = useCallback(async () => {
36
+ if (offlineQueue) {
37
+ const size = await offlineQueue.getQueueSize();
38
+ setQueueSize(size);
39
+ }
40
+ }, [offlineQueue]);
41
+ // Process queue on app foreground
42
+ useEffect(() => {
43
+ if (!offlineQueue)
44
+ return;
45
+ refreshQueueSize();
46
+ const handleAppState = (state) => {
47
+ if (state === 'active') {
48
+ offlineQueue.processQueue().then(() => refreshQueueSize());
49
+ }
50
+ };
51
+ const subscription = AppState.addEventListener('change', handleAppState);
52
+ return () => subscription.remove();
53
+ }, [offlineQueue, refreshQueueSize]);
54
+ const verify = useCallback(async (request) => {
55
+ setLoading(true);
56
+ setError(null);
57
+ try {
58
+ const result = await client.verify(request);
59
+ setLastResult(result);
60
+ return result;
61
+ }
62
+ catch (err) {
63
+ const error = err instanceof Error ? err : new Error(String(err));
64
+ setError(error);
65
+ // If offline mode, queue the request
66
+ if (offlineQueue) {
67
+ await offlineQueue.enqueue(request);
68
+ await refreshQueueSize();
69
+ return null;
70
+ }
71
+ throw error;
72
+ }
73
+ finally {
74
+ setLoading(false);
75
+ }
76
+ }, [client, offlineQueue, refreshQueueSize]);
77
+ const listVerifications = useCallback((params) => client.listVerifications(params), [client]);
78
+ const getVerification = useCallback((id) => client.getVerification(id), [client]);
79
+ const processQueue = useCallback(async () => {
80
+ if (!offlineQueue)
81
+ return;
82
+ await offlineQueue.processQueue((_, result) => setLastResult(result));
83
+ await refreshQueueSize();
84
+ }, [offlineQueue, refreshQueueSize]);
85
+ return {
86
+ verify,
87
+ listVerifications,
88
+ getVerification,
89
+ loading,
90
+ error,
91
+ lastResult,
92
+ queueSize,
93
+ processQueue,
94
+ client,
95
+ offlineQueue,
96
+ };
97
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { VerifyAIClient, VerifyAIRequestError } from './client';
2
+ export { useVerifyAI } from './hooks/useVerifyAI';
3
+ export type { UseVerifyAIReturn } from './hooks/useVerifyAI';
4
+ export { VerifyAIScanner } from './components/VerifyAIScanner';
5
+ export type { VerifyAIScannerProps } from './components/VerifyAIScanner';
6
+ export { OfflineQueue } from './storage/offlineQueue';
7
+ export type { VerifyAIConfig, VerificationRequest, VerificationResult, VerificationListResponse, VerificationListParams, QueueItem, VerifyAIError, ScannerStatus, ScannerOverlayConfig, } from './types';
package/lib/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Client
2
+ export { VerifyAIClient, VerifyAIRequestError } from './client';
3
+ // Hooks
4
+ export { useVerifyAI } from './hooks/useVerifyAI';
5
+ // Components
6
+ export { VerifyAIScanner } from './components/VerifyAIScanner';
7
+ // Offline Queue
8
+ export { OfflineQueue } from './storage/offlineQueue';
@@ -0,0 +1,50 @@
1
+ import type { VerificationRequest, VerificationResult, QueueItem } from '../types';
2
+ import { VerifyAIClient } from '../client';
3
+ export declare class OfflineQueue {
4
+ private client;
5
+ private processing;
6
+ private migrated;
7
+ constructor(client: VerifyAIClient);
8
+ /**
9
+ * Migrate from legacy single-key storage to per-item keys.
10
+ * Runs once per instance, no-ops if already migrated or no legacy data.
11
+ */
12
+ private migrateIfNeeded;
13
+ private getManifest;
14
+ private setManifest;
15
+ /**
16
+ * Add a verification request to the offline queue.
17
+ * Returns a temporary ID that can be used to track the item.
18
+ */
19
+ enqueue(request: VerificationRequest): Promise<string>;
20
+ /**
21
+ * Get all items currently in the offline queue.
22
+ */
23
+ getQueue(): Promise<QueueItem[]>;
24
+ /**
25
+ * Get the number of items waiting in the queue.
26
+ */
27
+ getQueueSize(): Promise<number>;
28
+ /**
29
+ * Remove an item from the queue by ID.
30
+ */
31
+ remove(id: string): Promise<void>;
32
+ /**
33
+ * Clear all items from the queue.
34
+ */
35
+ clear(): Promise<void>;
36
+ /**
37
+ * Process all queued items, sending them to the API.
38
+ * Returns results for successfully processed items.
39
+ *
40
+ * Call this when the device comes back online or on app foreground.
41
+ *
42
+ * @param onResult - Optional callback fired for each successful verification
43
+ * @param maxRetries - Maximum retry attempts before dropping an item (default: 3)
44
+ */
45
+ processQueue(onResult?: (queueId: string, result: VerificationResult) => void, maxRetries?: number): Promise<{
46
+ processed: number;
47
+ failed: number;
48
+ remaining: number;
49
+ }>;
50
+ }