@switchlabs/verify-ai-react-native 1.1.2 → 2.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/README.md +15 -15
- package/lib/client/index.d.ts +0 -3
- package/lib/client/index.js +21 -7
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/scanner.d.ts +2 -0
- package/lib/scanner.js +1 -0
- package/lib/storage/offlineQueue.js +26 -18
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +11 -1
- package/src/client/index.ts +23 -7
- package/src/index.ts +0 -4
- package/src/scanner.ts +2 -0
- package/src/storage/offlineQueue.ts +27 -18
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -4,18 +4,20 @@ React Native SDK for Verify AI photo verification.
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Core SDK (client, hooks, types):
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
npm install @switchlabs/verify-ai-react-native
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
With built-in camera scanner:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install @switchlabs/verify-ai-react-native expo-camera expo-image-manipulator
|
|
16
|
+
npm install @switchlabs/verify-ai-react-native expo-camera expo-image-manipulator
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
For offline queue support, also install `@react-native-async-storage/async-storage`.
|
|
20
|
+
|
|
19
21
|
If you want on-device inference, also install `expo-file-system`,
|
|
20
22
|
`react-native-fast-tflite`, and configure `react-native-fast-tflite` for the
|
|
21
23
|
delegates you plan to use.
|
|
@@ -44,18 +46,18 @@ function ParkingScreen() {
|
|
|
44
46
|
|
|
45
47
|
## Scanner Component
|
|
46
48
|
|
|
49
|
+
The scanner is exported from a separate subpath to avoid pulling in `expo-camera` for consumers that only need the client.
|
|
50
|
+
|
|
47
51
|
```tsx
|
|
48
|
-
import { useVerifyAI
|
|
52
|
+
import { useVerifyAI } from '@switchlabs/verify-ai-react-native';
|
|
53
|
+
import { VerifyAIScanner } from '@switchlabs/verify-ai-react-native/scanner';
|
|
49
54
|
|
|
50
55
|
function ScannerScreen() {
|
|
51
|
-
const {
|
|
52
|
-
apiKey: 'vai_your_api_key',
|
|
53
|
-
enableOnDeviceML: true,
|
|
54
|
-
});
|
|
56
|
+
const { verify } = useVerifyAI({ apiKey: 'vai_your_api_key' });
|
|
55
57
|
return (
|
|
56
58
|
<VerifyAIScanner
|
|
57
|
-
onCapture={(
|
|
58
|
-
|
|
59
|
+
onCapture={(base64) =>
|
|
60
|
+
verify({ image: base64, policy: 'scooter_parking' })
|
|
59
61
|
}
|
|
60
62
|
onResult={(result) => console.log(result.is_compliant ? 'PASS' : 'FAIL')}
|
|
61
63
|
/>
|
|
@@ -63,12 +65,10 @@ function ScannerScreen() {
|
|
|
63
65
|
}
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
`verifyMultipart()` is the recommended path for live camera captures. The
|
|
67
|
-
built-in offline queue currently replays base64 `verify()` requests only; raw
|
|
68
|
-
multipart uploads are not persisted automatically.
|
|
69
|
-
|
|
70
68
|
## Offline Mode
|
|
71
69
|
|
|
70
|
+
Requires `@react-native-async-storage/async-storage` to be installed.
|
|
71
|
+
|
|
72
72
|
Set `offlineMode: true` to queue transient failures (network, timeout, 429, 5xx) and retry later.
|
|
73
73
|
|
|
74
74
|
```tsx
|
package/lib/client/index.d.ts
CHANGED
|
@@ -38,9 +38,6 @@ export declare class VerifyAIClient {
|
|
|
38
38
|
* Submit a photo for AI verification using multipart/form-data.
|
|
39
39
|
* Streams the image directly from disk — avoids base64 encoding overhead.
|
|
40
40
|
*
|
|
41
|
-
* **Breaking change:** `onCapture` prop on `VerifyAIScanner` now receives
|
|
42
|
-
* an image URI instead of a base64 string.
|
|
43
|
-
*
|
|
44
41
|
* @param request - Multipart request with file URI and policy
|
|
45
42
|
* @param options - Optional verify options (e.g. idempotency key)
|
|
46
43
|
* @returns The verification result with compliance status and feedback
|
package/lib/client/index.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { TelemetryReporter } from '../telemetry/TelemetryReporter';
|
|
2
|
+
import { SDK_VERSION } from '../version';
|
|
2
3
|
const DEFAULT_BASE_URL = 'https://verify.switchlabs.dev/api/v1';
|
|
3
4
|
const DEFAULT_TIMEOUT = 30000;
|
|
5
|
+
const VEHICLE_TYPE_LABELS = {
|
|
6
|
+
scooter: 'Scooter',
|
|
7
|
+
'e-bike': 'E-Bike',
|
|
8
|
+
ebike: 'E-Bike',
|
|
9
|
+
bike: 'Bike',
|
|
10
|
+
moped: 'Moped',
|
|
11
|
+
car: 'Car',
|
|
12
|
+
};
|
|
13
|
+
/** Auto-inject sdkVersion and vehicleTypeLabel into metadata. */
|
|
14
|
+
function enrichMetadata(metadata) {
|
|
15
|
+
const enriched = { ...metadata, sdkVersion: SDK_VERSION };
|
|
16
|
+
const vehicleType = enriched.vehicleType;
|
|
17
|
+
if (typeof vehicleType === 'string' && !enriched.vehicleTypeLabel) {
|
|
18
|
+
enriched.vehicleTypeLabel = VEHICLE_TYPE_LABELS[vehicleType.toLowerCase()] || vehicleType;
|
|
19
|
+
}
|
|
20
|
+
return enriched;
|
|
21
|
+
}
|
|
4
22
|
export class VerifyAIClient {
|
|
5
23
|
constructor(config) {
|
|
6
24
|
if (!config.apiKey) {
|
|
@@ -142,19 +160,17 @@ export class VerifyAIClient {
|
|
|
142
160
|
if (options?.idempotencyKey) {
|
|
143
161
|
headers['Idempotency-Key'] = options.idempotencyKey;
|
|
144
162
|
}
|
|
163
|
+
const enrichedRequest = { ...request, metadata: enrichMetadata(request.metadata) };
|
|
145
164
|
return this.request('/verify', {
|
|
146
165
|
method: 'POST',
|
|
147
166
|
headers,
|
|
148
|
-
body: JSON.stringify(
|
|
167
|
+
body: JSON.stringify(enrichedRequest),
|
|
149
168
|
});
|
|
150
169
|
}
|
|
151
170
|
/**
|
|
152
171
|
* Submit a photo for AI verification using multipart/form-data.
|
|
153
172
|
* Streams the image directly from disk — avoids base64 encoding overhead.
|
|
154
173
|
*
|
|
155
|
-
* **Breaking change:** `onCapture` prop on `VerifyAIScanner` now receives
|
|
156
|
-
* an image URI instead of a base64 string.
|
|
157
|
-
*
|
|
158
174
|
* @param request - Multipart request with file URI and policy
|
|
159
175
|
* @param options - Optional verify options (e.g. idempotency key)
|
|
160
176
|
* @returns The verification result with compliance status and feedback
|
|
@@ -168,9 +184,7 @@ export class VerifyAIClient {
|
|
|
168
184
|
name: 'photo.jpg',
|
|
169
185
|
});
|
|
170
186
|
formData.append('policy', request.policy);
|
|
171
|
-
|
|
172
|
-
formData.append('metadata', JSON.stringify(request.metadata));
|
|
173
|
-
}
|
|
187
|
+
formData.append('metadata', JSON.stringify(enrichMetadata(request.metadata)));
|
|
174
188
|
if (request.provider) {
|
|
175
189
|
formData.append('provider', request.provider);
|
|
176
190
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { VerifyAIClient, VerifyAIRequestError } from './client';
|
|
2
2
|
export { useVerifyAI } from './hooks/useVerifyAI';
|
|
3
3
|
export type { UseVerifyAIReturn, UseVerifyAIConfig } from './hooks/useVerifyAI';
|
|
4
|
-
export { VerifyAIScanner } from './components/VerifyAIScanner';
|
|
5
|
-
export type { VerifyAIScannerProps } from './components/VerifyAIScanner';
|
|
6
4
|
export { TelemetryReporter } from './telemetry/TelemetryReporter';
|
|
7
5
|
export { TelemetryContext } from './telemetry/TelemetryContext';
|
|
8
6
|
export { OfflineQueue } from './storage/offlineQueue';
|
package/lib/index.js
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
export { VerifyAIClient, VerifyAIRequestError } from './client';
|
|
3
3
|
// Hooks
|
|
4
4
|
export { useVerifyAI } from './hooks/useVerifyAI';
|
|
5
|
-
// Components
|
|
6
|
-
export { VerifyAIScanner } from './components/VerifyAIScanner';
|
|
7
5
|
// Telemetry
|
|
8
6
|
export { TelemetryReporter } from './telemetry/TelemetryReporter';
|
|
9
7
|
export { TelemetryContext } from './telemetry/TelemetryContext';
|
package/lib/scanner.d.ts
ADDED
package/lib/scanner.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { VerifyAIScanner } from './components/VerifyAIScanner';
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
1
|
import { VerifyAIRequestError } from '../client';
|
|
2
|
+
let _storage = null;
|
|
3
|
+
async function getStorage() {
|
|
4
|
+
if (!_storage) {
|
|
5
|
+
const mod = await import('@react-native-async-storage/async-storage');
|
|
6
|
+
_storage = mod.default;
|
|
7
|
+
}
|
|
8
|
+
return _storage;
|
|
9
|
+
}
|
|
3
10
|
const MANIFEST_KEY = '@verifyai/queue_manifest';
|
|
4
11
|
const ITEM_PREFIX = '@verifyai/queue_item_';
|
|
5
12
|
const LEGACY_KEY = '@verifyai/offline_queue';
|
|
@@ -17,13 +24,13 @@ export class OfflineQueue {
|
|
|
17
24
|
if (this.migrated)
|
|
18
25
|
return;
|
|
19
26
|
this.migrated = true;
|
|
20
|
-
const legacy = await
|
|
27
|
+
const legacy = await (await getStorage()).getItem(LEGACY_KEY);
|
|
21
28
|
if (!legacy)
|
|
22
29
|
return;
|
|
23
30
|
try {
|
|
24
31
|
const items = JSON.parse(legacy);
|
|
25
32
|
if (!Array.isArray(items) || items.length === 0) {
|
|
26
|
-
await
|
|
33
|
+
await (await getStorage()).removeItem(LEGACY_KEY);
|
|
27
34
|
return;
|
|
28
35
|
}
|
|
29
36
|
const ids = [];
|
|
@@ -32,24 +39,25 @@ export class OfflineQueue {
|
|
|
32
39
|
ids.push(item.id);
|
|
33
40
|
pairs.push([`${ITEM_PREFIX}${item.id}`, JSON.stringify(item)]);
|
|
34
41
|
}
|
|
35
|
-
await
|
|
42
|
+
const storage = await getStorage();
|
|
43
|
+
await storage.multiSet([
|
|
36
44
|
[MANIFEST_KEY, JSON.stringify(ids)],
|
|
37
45
|
...pairs,
|
|
38
46
|
]);
|
|
39
|
-
await
|
|
47
|
+
await storage.removeItem(LEGACY_KEY);
|
|
40
48
|
}
|
|
41
49
|
catch {
|
|
42
50
|
// If migration fails, remove corrupt legacy data
|
|
43
|
-
await
|
|
51
|
+
await (await getStorage()).removeItem(LEGACY_KEY);
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
async getManifest() {
|
|
47
55
|
await this.migrateIfNeeded();
|
|
48
|
-
const raw = await
|
|
56
|
+
const raw = await (await getStorage()).getItem(MANIFEST_KEY);
|
|
49
57
|
return raw ? JSON.parse(raw) : [];
|
|
50
58
|
}
|
|
51
59
|
async setManifest(ids) {
|
|
52
|
-
await
|
|
60
|
+
await (await getStorage()).setItem(MANIFEST_KEY, JSON.stringify(ids));
|
|
53
61
|
}
|
|
54
62
|
/**
|
|
55
63
|
* Add a verification request to the offline queue.
|
|
@@ -64,7 +72,7 @@ export class OfflineQueue {
|
|
|
64
72
|
};
|
|
65
73
|
const ids = await this.getManifest();
|
|
66
74
|
ids.push(item.id);
|
|
67
|
-
await
|
|
75
|
+
await (await getStorage()).setItem(`${ITEM_PREFIX}${item.id}`, JSON.stringify(item));
|
|
68
76
|
await this.setManifest(ids);
|
|
69
77
|
return item.id;
|
|
70
78
|
}
|
|
@@ -76,7 +84,7 @@ export class OfflineQueue {
|
|
|
76
84
|
if (ids.length === 0)
|
|
77
85
|
return [];
|
|
78
86
|
const keys = ids.map((id) => `${ITEM_PREFIX}${id}`);
|
|
79
|
-
const pairs = await
|
|
87
|
+
const pairs = await (await getStorage()).multiGet(keys);
|
|
80
88
|
const items = [];
|
|
81
89
|
for (const [, value] of pairs) {
|
|
82
90
|
if (value) {
|
|
@@ -104,7 +112,7 @@ export class OfflineQueue {
|
|
|
104
112
|
const ids = await this.getManifest();
|
|
105
113
|
const filtered = ids.filter((i) => i !== id);
|
|
106
114
|
await this.setManifest(filtered);
|
|
107
|
-
await
|
|
115
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
108
116
|
}
|
|
109
117
|
/**
|
|
110
118
|
* Clear all items from the queue.
|
|
@@ -113,10 +121,10 @@ export class OfflineQueue {
|
|
|
113
121
|
const ids = await this.getManifest();
|
|
114
122
|
if (ids.length > 0) {
|
|
115
123
|
const keys = ids.map((id) => `${ITEM_PREFIX}${id}`);
|
|
116
|
-
await
|
|
124
|
+
await (await getStorage()).multiRemove([MANIFEST_KEY, ...keys]);
|
|
117
125
|
}
|
|
118
126
|
else {
|
|
119
|
-
await
|
|
127
|
+
await (await getStorage()).removeItem(MANIFEST_KEY);
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
/**
|
|
@@ -140,7 +148,7 @@ export class OfflineQueue {
|
|
|
140
148
|
try {
|
|
141
149
|
const ids = await this.getManifest();
|
|
142
150
|
for (const id of ids) {
|
|
143
|
-
const raw = await
|
|
151
|
+
const raw = await (await getStorage()).getItem(`${ITEM_PREFIX}${id}`);
|
|
144
152
|
if (!raw)
|
|
145
153
|
continue;
|
|
146
154
|
let item;
|
|
@@ -149,13 +157,13 @@ export class OfflineQueue {
|
|
|
149
157
|
}
|
|
150
158
|
catch {
|
|
151
159
|
// Remove corrupt item
|
|
152
|
-
await
|
|
160
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
153
161
|
continue;
|
|
154
162
|
}
|
|
155
163
|
try {
|
|
156
164
|
const result = await this.client.verify(item.request, { idempotencyKey: item.id });
|
|
157
165
|
processed++;
|
|
158
|
-
await
|
|
166
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
159
167
|
onResult?.(item.id, result);
|
|
160
168
|
}
|
|
161
169
|
catch (err) {
|
|
@@ -163,12 +171,12 @@ export class OfflineQueue {
|
|
|
163
171
|
const shouldRetry = !requestError || requestError.isRetryable;
|
|
164
172
|
item.retryCount++;
|
|
165
173
|
if (shouldRetry && item.retryCount < maxRetries) {
|
|
166
|
-
await
|
|
174
|
+
await (await getStorage()).setItem(`${ITEM_PREFIX}${id}`, JSON.stringify(item));
|
|
167
175
|
remainingIds.push(id);
|
|
168
176
|
}
|
|
169
177
|
else {
|
|
170
178
|
failed++;
|
|
171
|
-
await
|
|
179
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
182
|
}
|
package/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "
|
|
1
|
+
export declare const SDK_VERSION = "2.1.0";
|
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = '
|
|
1
|
+
export const SDK_VERSION = '2.1.0';
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@switchlabs/verify-ai-react-native",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "React Native SDK for Verify AI - photo verification with AI vision processing",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./lib/index.d.ts",
|
|
10
|
+
"default": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./scanner": {
|
|
13
|
+
"types": "./lib/scanner.d.ts",
|
|
14
|
+
"default": "./lib/scanner.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"files": [
|
|
8
18
|
"src",
|
|
9
19
|
"lib"
|
package/src/client/index.ts
CHANGED
|
@@ -10,10 +10,30 @@ import type {
|
|
|
10
10
|
PolicyConfigResponse,
|
|
11
11
|
} from '../types';
|
|
12
12
|
import { TelemetryReporter } from '../telemetry/TelemetryReporter';
|
|
13
|
+
import { SDK_VERSION } from '../version';
|
|
13
14
|
|
|
14
15
|
const DEFAULT_BASE_URL = 'https://verify.switchlabs.dev/api/v1';
|
|
15
16
|
const DEFAULT_TIMEOUT = 30000;
|
|
16
17
|
|
|
18
|
+
const VEHICLE_TYPE_LABELS: Record<string, string> = {
|
|
19
|
+
scooter: 'Scooter',
|
|
20
|
+
'e-bike': 'E-Bike',
|
|
21
|
+
ebike: 'E-Bike',
|
|
22
|
+
bike: 'Bike',
|
|
23
|
+
moped: 'Moped',
|
|
24
|
+
car: 'Car',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Auto-inject sdkVersion and vehicleTypeLabel into metadata. */
|
|
28
|
+
function enrichMetadata(metadata?: Record<string, unknown>): Record<string, unknown> {
|
|
29
|
+
const enriched: Record<string, unknown> = { ...metadata, sdkVersion: SDK_VERSION };
|
|
30
|
+
const vehicleType = enriched.vehicleType;
|
|
31
|
+
if (typeof vehicleType === 'string' && !enriched.vehicleTypeLabel) {
|
|
32
|
+
enriched.vehicleTypeLabel = VEHICLE_TYPE_LABELS[vehicleType.toLowerCase()] || vehicleType;
|
|
33
|
+
}
|
|
34
|
+
return enriched;
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
interface RequestContext {
|
|
18
38
|
path: string;
|
|
19
39
|
url: string;
|
|
@@ -204,10 +224,11 @@ export class VerifyAIClient {
|
|
|
204
224
|
if (options?.idempotencyKey) {
|
|
205
225
|
headers['Idempotency-Key'] = options.idempotencyKey;
|
|
206
226
|
}
|
|
227
|
+
const enrichedRequest = { ...request, metadata: enrichMetadata(request.metadata) };
|
|
207
228
|
return this.request<VerificationResult>('/verify', {
|
|
208
229
|
method: 'POST',
|
|
209
230
|
headers,
|
|
210
|
-
body: JSON.stringify(
|
|
231
|
+
body: JSON.stringify(enrichedRequest),
|
|
211
232
|
});
|
|
212
233
|
}
|
|
213
234
|
|
|
@@ -215,9 +236,6 @@ export class VerifyAIClient {
|
|
|
215
236
|
* Submit a photo for AI verification using multipart/form-data.
|
|
216
237
|
* Streams the image directly from disk — avoids base64 encoding overhead.
|
|
217
238
|
*
|
|
218
|
-
* **Breaking change:** `onCapture` prop on `VerifyAIScanner` now receives
|
|
219
|
-
* an image URI instead of a base64 string.
|
|
220
|
-
*
|
|
221
239
|
* @param request - Multipart request with file URI and policy
|
|
222
240
|
* @param options - Optional verify options (e.g. idempotency key)
|
|
223
241
|
* @returns The verification result with compliance status and feedback
|
|
@@ -231,9 +249,7 @@ export class VerifyAIClient {
|
|
|
231
249
|
name: 'photo.jpg',
|
|
232
250
|
} as unknown as Blob);
|
|
233
251
|
formData.append('policy', request.policy);
|
|
234
|
-
|
|
235
|
-
formData.append('metadata', JSON.stringify(request.metadata));
|
|
236
|
-
}
|
|
252
|
+
formData.append('metadata', JSON.stringify(enrichMetadata(request.metadata)));
|
|
237
253
|
if (request.provider) {
|
|
238
254
|
formData.append('provider', request.provider);
|
|
239
255
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,6 @@ export { VerifyAIClient, VerifyAIRequestError } from './client';
|
|
|
5
5
|
export { useVerifyAI } from './hooks/useVerifyAI';
|
|
6
6
|
export type { UseVerifyAIReturn, UseVerifyAIConfig } from './hooks/useVerifyAI';
|
|
7
7
|
|
|
8
|
-
// Components
|
|
9
|
-
export { VerifyAIScanner } from './components/VerifyAIScanner';
|
|
10
|
-
export type { VerifyAIScannerProps } from './components/VerifyAIScanner';
|
|
11
|
-
|
|
12
8
|
// Telemetry
|
|
13
9
|
export { TelemetryReporter } from './telemetry/TelemetryReporter';
|
|
14
10
|
export { TelemetryContext } from './telemetry/TelemetryContext';
|
package/src/scanner.ts
ADDED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
1
|
import type { VerificationRequest, VerificationResult, QueueItem } from '../types';
|
|
3
2
|
import { VerifyAIClient, VerifyAIRequestError } from '../client';
|
|
4
3
|
|
|
4
|
+
let _storage: typeof import('@react-native-async-storage/async-storage').default | null = null;
|
|
5
|
+
async function getStorage() {
|
|
6
|
+
if (!_storage) {
|
|
7
|
+
const mod = await import('@react-native-async-storage/async-storage');
|
|
8
|
+
_storage = mod.default;
|
|
9
|
+
}
|
|
10
|
+
return _storage;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
const MANIFEST_KEY = '@verifyai/queue_manifest';
|
|
6
14
|
const ITEM_PREFIX = '@verifyai/queue_item_';
|
|
7
15
|
const LEGACY_KEY = '@verifyai/offline_queue';
|
|
@@ -23,13 +31,13 @@ export class OfflineQueue {
|
|
|
23
31
|
if (this.migrated) return;
|
|
24
32
|
this.migrated = true;
|
|
25
33
|
|
|
26
|
-
const legacy = await
|
|
34
|
+
const legacy = await (await getStorage()).getItem(LEGACY_KEY);
|
|
27
35
|
if (!legacy) return;
|
|
28
36
|
|
|
29
37
|
try {
|
|
30
38
|
const items: QueueItem[] = JSON.parse(legacy);
|
|
31
39
|
if (!Array.isArray(items) || items.length === 0) {
|
|
32
|
-
await
|
|
40
|
+
await (await getStorage()).removeItem(LEGACY_KEY);
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -40,25 +48,26 @@ export class OfflineQueue {
|
|
|
40
48
|
pairs.push([`${ITEM_PREFIX}${item.id}`, JSON.stringify(item)]);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
await
|
|
51
|
+
const storage = await getStorage();
|
|
52
|
+
await storage.multiSet([
|
|
44
53
|
[MANIFEST_KEY, JSON.stringify(ids)],
|
|
45
54
|
...pairs,
|
|
46
55
|
]);
|
|
47
|
-
await
|
|
56
|
+
await storage.removeItem(LEGACY_KEY);
|
|
48
57
|
} catch {
|
|
49
58
|
// If migration fails, remove corrupt legacy data
|
|
50
|
-
await
|
|
59
|
+
await (await getStorage()).removeItem(LEGACY_KEY);
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
private async getManifest(): Promise<string[]> {
|
|
55
64
|
await this.migrateIfNeeded();
|
|
56
|
-
const raw = await
|
|
65
|
+
const raw = await (await getStorage()).getItem(MANIFEST_KEY);
|
|
57
66
|
return raw ? JSON.parse(raw) : [];
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
private async setManifest(ids: string[]): Promise<void> {
|
|
61
|
-
await
|
|
70
|
+
await (await getStorage()).setItem(MANIFEST_KEY, JSON.stringify(ids));
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
/**
|
|
@@ -76,7 +85,7 @@ export class OfflineQueue {
|
|
|
76
85
|
const ids = await this.getManifest();
|
|
77
86
|
ids.push(item.id);
|
|
78
87
|
|
|
79
|
-
await
|
|
88
|
+
await (await getStorage()).setItem(`${ITEM_PREFIX}${item.id}`, JSON.stringify(item));
|
|
80
89
|
await this.setManifest(ids);
|
|
81
90
|
return item.id;
|
|
82
91
|
}
|
|
@@ -89,7 +98,7 @@ export class OfflineQueue {
|
|
|
89
98
|
if (ids.length === 0) return [];
|
|
90
99
|
|
|
91
100
|
const keys = ids.map((id) => `${ITEM_PREFIX}${id}`);
|
|
92
|
-
const pairs = await
|
|
101
|
+
const pairs = await (await getStorage()).multiGet(keys);
|
|
93
102
|
|
|
94
103
|
const items: QueueItem[] = [];
|
|
95
104
|
for (const [, value] of pairs) {
|
|
@@ -119,7 +128,7 @@ export class OfflineQueue {
|
|
|
119
128
|
const ids = await this.getManifest();
|
|
120
129
|
const filtered = ids.filter((i) => i !== id);
|
|
121
130
|
await this.setManifest(filtered);
|
|
122
|
-
await
|
|
131
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
/**
|
|
@@ -129,9 +138,9 @@ export class OfflineQueue {
|
|
|
129
138
|
const ids = await this.getManifest();
|
|
130
139
|
if (ids.length > 0) {
|
|
131
140
|
const keys = ids.map((id) => `${ITEM_PREFIX}${id}`);
|
|
132
|
-
await
|
|
141
|
+
await (await getStorage()).multiRemove([MANIFEST_KEY, ...keys]);
|
|
133
142
|
} else {
|
|
134
|
-
await
|
|
143
|
+
await (await getStorage()).removeItem(MANIFEST_KEY);
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
|
|
@@ -162,7 +171,7 @@ export class OfflineQueue {
|
|
|
162
171
|
const ids = await this.getManifest();
|
|
163
172
|
|
|
164
173
|
for (const id of ids) {
|
|
165
|
-
const raw = await
|
|
174
|
+
const raw = await (await getStorage()).getItem(`${ITEM_PREFIX}${id}`);
|
|
166
175
|
if (!raw) continue;
|
|
167
176
|
|
|
168
177
|
let item: QueueItem;
|
|
@@ -170,14 +179,14 @@ export class OfflineQueue {
|
|
|
170
179
|
item = JSON.parse(raw);
|
|
171
180
|
} catch {
|
|
172
181
|
// Remove corrupt item
|
|
173
|
-
await
|
|
182
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
174
183
|
continue;
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
try {
|
|
178
187
|
const result = await this.client.verify(item.request, { idempotencyKey: item.id });
|
|
179
188
|
processed++;
|
|
180
|
-
await
|
|
189
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
181
190
|
onResult?.(item.id, result);
|
|
182
191
|
} catch (err) {
|
|
183
192
|
const requestError = err instanceof VerifyAIRequestError ? err : null;
|
|
@@ -185,11 +194,11 @@ export class OfflineQueue {
|
|
|
185
194
|
|
|
186
195
|
item.retryCount++;
|
|
187
196
|
if (shouldRetry && item.retryCount < maxRetries) {
|
|
188
|
-
await
|
|
197
|
+
await (await getStorage()).setItem(`${ITEM_PREFIX}${id}`, JSON.stringify(item));
|
|
189
198
|
remainingIds.push(id);
|
|
190
199
|
} else {
|
|
191
200
|
failed++;
|
|
192
|
-
await
|
|
201
|
+
await (await getStorage()).removeItem(`${ITEM_PREFIX}${id}`);
|
|
193
202
|
}
|
|
194
203
|
}
|
|
195
204
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = '
|
|
1
|
+
export const SDK_VERSION = '2.1.0';
|