@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,200 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
import type { VerificationRequest, VerificationResult, QueueItem } from '../types';
|
|
3
|
+
import { VerifyAIClient } from '../client';
|
|
4
|
+
|
|
5
|
+
const MANIFEST_KEY = '@verifyai/queue_manifest';
|
|
6
|
+
const ITEM_PREFIX = '@verifyai/queue_item_';
|
|
7
|
+
const LEGACY_KEY = '@verifyai/offline_queue';
|
|
8
|
+
|
|
9
|
+
export class OfflineQueue {
|
|
10
|
+
private client: VerifyAIClient;
|
|
11
|
+
private processing = false;
|
|
12
|
+
private migrated = false;
|
|
13
|
+
|
|
14
|
+
constructor(client: VerifyAIClient) {
|
|
15
|
+
this.client = client;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Migrate from legacy single-key storage to per-item keys.
|
|
20
|
+
* Runs once per instance, no-ops if already migrated or no legacy data.
|
|
21
|
+
*/
|
|
22
|
+
private async migrateIfNeeded(): Promise<void> {
|
|
23
|
+
if (this.migrated) return;
|
|
24
|
+
this.migrated = true;
|
|
25
|
+
|
|
26
|
+
const legacy = await AsyncStorage.getItem(LEGACY_KEY);
|
|
27
|
+
if (!legacy) return;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const items: QueueItem[] = JSON.parse(legacy);
|
|
31
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
32
|
+
await AsyncStorage.removeItem(LEGACY_KEY);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ids: string[] = [];
|
|
37
|
+
const pairs: [string, string][] = [];
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
ids.push(item.id);
|
|
40
|
+
pairs.push([`${ITEM_PREFIX}${item.id}`, JSON.stringify(item)]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await AsyncStorage.multiSet([
|
|
44
|
+
[MANIFEST_KEY, JSON.stringify(ids)],
|
|
45
|
+
...pairs,
|
|
46
|
+
]);
|
|
47
|
+
await AsyncStorage.removeItem(LEGACY_KEY);
|
|
48
|
+
} catch {
|
|
49
|
+
// If migration fails, remove corrupt legacy data
|
|
50
|
+
await AsyncStorage.removeItem(LEGACY_KEY);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async getManifest(): Promise<string[]> {
|
|
55
|
+
await this.migrateIfNeeded();
|
|
56
|
+
const raw = await AsyncStorage.getItem(MANIFEST_KEY);
|
|
57
|
+
return raw ? JSON.parse(raw) : [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async setManifest(ids: string[]): Promise<void> {
|
|
61
|
+
await AsyncStorage.setItem(MANIFEST_KEY, JSON.stringify(ids));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Add a verification request to the offline queue.
|
|
66
|
+
* Returns a temporary ID that can be used to track the item.
|
|
67
|
+
*/
|
|
68
|
+
async enqueue(request: VerificationRequest): Promise<string> {
|
|
69
|
+
const item: QueueItem = {
|
|
70
|
+
id: `offline_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
71
|
+
request,
|
|
72
|
+
createdAt: Date.now(),
|
|
73
|
+
retryCount: 0,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const ids = await this.getManifest();
|
|
77
|
+
ids.push(item.id);
|
|
78
|
+
|
|
79
|
+
await AsyncStorage.setItem(`${ITEM_PREFIX}${item.id}`, JSON.stringify(item));
|
|
80
|
+
await this.setManifest(ids);
|
|
81
|
+
return item.id;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get all items currently in the offline queue.
|
|
86
|
+
*/
|
|
87
|
+
async getQueue(): Promise<QueueItem[]> {
|
|
88
|
+
const ids = await this.getManifest();
|
|
89
|
+
if (ids.length === 0) return [];
|
|
90
|
+
|
|
91
|
+
const keys = ids.map((id) => `${ITEM_PREFIX}${id}`);
|
|
92
|
+
const pairs = await AsyncStorage.multiGet(keys);
|
|
93
|
+
|
|
94
|
+
const items: QueueItem[] = [];
|
|
95
|
+
for (const [, value] of pairs) {
|
|
96
|
+
if (value) {
|
|
97
|
+
try {
|
|
98
|
+
items.push(JSON.parse(value));
|
|
99
|
+
} catch {
|
|
100
|
+
// Skip corrupt items
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return items;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the number of items waiting in the queue.
|
|
109
|
+
*/
|
|
110
|
+
async getQueueSize(): Promise<number> {
|
|
111
|
+
const ids = await this.getManifest();
|
|
112
|
+
return ids.length;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Remove an item from the queue by ID.
|
|
117
|
+
*/
|
|
118
|
+
async remove(id: string): Promise<void> {
|
|
119
|
+
const ids = await this.getManifest();
|
|
120
|
+
const filtered = ids.filter((i) => i !== id);
|
|
121
|
+
await this.setManifest(filtered);
|
|
122
|
+
await AsyncStorage.removeItem(`${ITEM_PREFIX}${id}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Clear all items from the queue.
|
|
127
|
+
*/
|
|
128
|
+
async clear(): Promise<void> {
|
|
129
|
+
const ids = await this.getManifest();
|
|
130
|
+
if (ids.length > 0) {
|
|
131
|
+
const keys = ids.map((id) => `${ITEM_PREFIX}${id}`);
|
|
132
|
+
await AsyncStorage.multiRemove([MANIFEST_KEY, ...keys]);
|
|
133
|
+
} else {
|
|
134
|
+
await AsyncStorage.removeItem(MANIFEST_KEY);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Process all queued items, sending them to the API.
|
|
140
|
+
* Returns results for successfully processed items.
|
|
141
|
+
*
|
|
142
|
+
* Call this when the device comes back online or on app foreground.
|
|
143
|
+
*
|
|
144
|
+
* @param onResult - Optional callback fired for each successful verification
|
|
145
|
+
* @param maxRetries - Maximum retry attempts before dropping an item (default: 3)
|
|
146
|
+
*/
|
|
147
|
+
async processQueue(
|
|
148
|
+
onResult?: (queueId: string, result: VerificationResult) => void,
|
|
149
|
+
maxRetries = 3
|
|
150
|
+
): Promise<{ processed: number; failed: number; remaining: number }> {
|
|
151
|
+
if (this.processing) {
|
|
152
|
+
const size = await this.getQueueSize();
|
|
153
|
+
return { processed: 0, failed: 0, remaining: size };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.processing = true;
|
|
157
|
+
let processed = 0;
|
|
158
|
+
let failed = 0;
|
|
159
|
+
const remainingIds: string[] = [];
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const ids = await this.getManifest();
|
|
163
|
+
|
|
164
|
+
for (const id of ids) {
|
|
165
|
+
const raw = await AsyncStorage.getItem(`${ITEM_PREFIX}${id}`);
|
|
166
|
+
if (!raw) continue;
|
|
167
|
+
|
|
168
|
+
let item: QueueItem;
|
|
169
|
+
try {
|
|
170
|
+
item = JSON.parse(raw);
|
|
171
|
+
} catch {
|
|
172
|
+
// Remove corrupt item
|
|
173
|
+
await AsyncStorage.removeItem(`${ITEM_PREFIX}${id}`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const result = await this.client.verify(item.request);
|
|
179
|
+
processed++;
|
|
180
|
+
await AsyncStorage.removeItem(`${ITEM_PREFIX}${id}`);
|
|
181
|
+
onResult?.(item.id, result);
|
|
182
|
+
} catch {
|
|
183
|
+
item.retryCount++;
|
|
184
|
+
if (item.retryCount < maxRetries) {
|
|
185
|
+
await AsyncStorage.setItem(`${ITEM_PREFIX}${id}`, JSON.stringify(item));
|
|
186
|
+
remainingIds.push(id);
|
|
187
|
+
} else {
|
|
188
|
+
failed++;
|
|
189
|
+
await AsyncStorage.removeItem(`${ITEM_PREFIX}${id}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await this.setManifest(remainingIds);
|
|
195
|
+
return { processed, failed, remaining: remainingIds.length };
|
|
196
|
+
} finally {
|
|
197
|
+
this.processing = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface VerifyAIConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
offlineMode?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface VerificationRequest {
|
|
9
|
+
image: string; // base64 encoded image data
|
|
10
|
+
policy: string;
|
|
11
|
+
metadata?: Record<string, unknown>;
|
|
12
|
+
provider?: 'openai' | 'anthropic' | 'gemini';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface VerificationResult {
|
|
16
|
+
id: string;
|
|
17
|
+
created_at: string;
|
|
18
|
+
status: 'success' | 'error';
|
|
19
|
+
is_compliant: boolean;
|
|
20
|
+
confidence: number;
|
|
21
|
+
policy: string;
|
|
22
|
+
violation_reasons: string[];
|
|
23
|
+
feedback: string;
|
|
24
|
+
metadata: Record<string, unknown>;
|
|
25
|
+
image_url: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface VerificationListResponse {
|
|
29
|
+
data: VerificationResult[];
|
|
30
|
+
has_more: boolean;
|
|
31
|
+
next_cursor: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface VerificationListParams {
|
|
35
|
+
limit?: number;
|
|
36
|
+
cursor?: string;
|
|
37
|
+
policy?: string;
|
|
38
|
+
status?: string;
|
|
39
|
+
is_compliant?: boolean;
|
|
40
|
+
start_date?: string;
|
|
41
|
+
end_date?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface QueueItem {
|
|
45
|
+
id: string;
|
|
46
|
+
request: VerificationRequest;
|
|
47
|
+
createdAt: number;
|
|
48
|
+
retryCount: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface VerifyAIError {
|
|
52
|
+
error: string;
|
|
53
|
+
status: number;
|
|
54
|
+
current_usage?: number;
|
|
55
|
+
limit?: number;
|
|
56
|
+
upgrade_url?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ScannerStatus = 'idle' | 'capturing' | 'processing' | 'success' | 'error';
|
|
60
|
+
|
|
61
|
+
export interface ScannerOverlayConfig {
|
|
62
|
+
title?: string;
|
|
63
|
+
instructions?: string;
|
|
64
|
+
showGuideFrame?: boolean;
|
|
65
|
+
guideFrameAspectRatio?: number;
|
|
66
|
+
}
|