@switchlabs/verify-ai-react-native 2.4.20 → 2.4.22

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.
@@ -93,7 +93,7 @@ export declare class VerifyAIClient {
93
93
  export declare class VerifyAIRequestError extends Error {
94
94
  status: number;
95
95
  body: VerifyAIError;
96
- constructor(message: string, status: number, body: VerifyAIError);
96
+ constructor(message: unknown, status: number, body: VerifyAIError);
97
97
  get isRateLimited(): boolean;
98
98
  get isUnauthorized(): boolean;
99
99
  get isServerError(): boolean;
@@ -19,10 +19,43 @@ function enrichMetadata(metadata) {
19
19
  }
20
20
  return enriched;
21
21
  }
22
+ function stringifyErrorMessage(value, fallback) {
23
+ if (typeof value === 'string' && value.trim()) {
24
+ return value;
25
+ }
26
+ if (value instanceof Error && value.message) {
27
+ return value.message;
28
+ }
29
+ if (value && typeof value === 'object') {
30
+ const record = value;
31
+ for (const key of ['message', 'error', 'detail', 'code']) {
32
+ const candidate = record[key];
33
+ if (typeof candidate === 'string' && candidate.trim()) {
34
+ return candidate;
35
+ }
36
+ }
37
+ try {
38
+ const json = JSON.stringify(value);
39
+ if (json && json !== '{}') {
40
+ return json;
41
+ }
42
+ }
43
+ catch {
44
+ // Fall through to String(value) below.
45
+ }
46
+ }
47
+ if (value != null) {
48
+ const text = String(value);
49
+ if (text && text !== '[object Object]') {
50
+ return text;
51
+ }
52
+ }
53
+ return fallback;
54
+ }
22
55
  export class VerifyAIClient {
23
56
  constructor(config) {
24
57
  if (!config.apiKey) {
25
- throw new Error('VerifyAI: apiKey is required');
58
+ throw new Error(`VerifyAI[v${SDK_VERSION}]: apiKey is required`);
26
59
  }
27
60
  this.apiKey = config.apiKey;
28
61
  this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
@@ -43,8 +76,9 @@ export class VerifyAIClient {
43
76
  }
44
77
  }
45
78
  buildRequestError(message, status, context, body = {}) {
79
+ const safeMessage = stringifyErrorMessage(message, 'VerifyAI request failed');
46
80
  const error = {
47
- error: message,
81
+ error: safeMessage,
48
82
  status,
49
83
  code: status === 408 ? 'timeout' : status === 0 ? 'network_error' : 'request_error',
50
84
  path: context.path,
@@ -52,7 +86,7 @@ export class VerifyAIClient {
52
86
  method: context.method,
53
87
  ...body,
54
88
  };
55
- return new VerifyAIRequestError(message, status, error);
89
+ return new VerifyAIRequestError(safeMessage, status, error);
56
90
  }
57
91
  normalizeRequestError(error, context) {
58
92
  if (error instanceof VerifyAIRequestError) {
@@ -64,7 +98,7 @@ export class VerifyAIClient {
64
98
  if (error instanceof TypeError) {
65
99
  return this.buildRequestError('Network request failed', 0, context, { code: 'network_error' });
66
100
  }
67
- const message = error instanceof Error ? error.message : 'VerifyAI request failed';
101
+ const message = error instanceof Error ? error.message : stringifyErrorMessage(error, 'VerifyAI request failed');
68
102
  return this.buildRequestError(message, 0, context);
69
103
  }
70
104
  async executeRequest(path, options = {}) {
@@ -86,7 +120,7 @@ export class VerifyAIClient {
86
120
  const body = this.parseResponseBody(rawBody);
87
121
  if (!response.ok) {
88
122
  const errorBody = (body && typeof body === 'object' ? body : null);
89
- throw this.buildRequestError(errorBody?.error || `Request failed with status ${response.status}`, response.status, context, {
123
+ throw this.buildRequestError(stringifyErrorMessage(errorBody?.error, `Request failed with status ${response.status}`), response.status, context, {
90
124
  current_usage: errorBody?.current_usage,
91
125
  limit: errorBody?.limit,
92
126
  upgrade_url: errorBody?.upgrade_url,
@@ -95,7 +129,7 @@ export class VerifyAIClient {
95
129
  });
96
130
  }
97
131
  if (!body || typeof body !== 'object') {
98
- throw this.buildRequestError('VerifyAI: Invalid response payload', 0, context, {
132
+ throw this.buildRequestError(`VerifyAI[v${SDK_VERSION}]: Invalid response payload`, 0, context, {
99
133
  code: 'invalid_response',
100
134
  request_id: response.headers.get('X-Request-Id') || undefined,
101
135
  });
@@ -220,9 +254,11 @@ export class VerifyAIClient {
220
254
  });
221
255
  }
222
256
  catch (error) {
223
- // Blank 500 means the server crashed during multipart parsing (e.g.
224
- // Vercel function crash). Retry as JSON/base64 with a fresh request
225
- // the crashed request may have partially recorded the idempotency key.
257
+ // 5xx during multipart can mean the server crashed before reservation
258
+ // (e.g. Vercel multipart-parse failure) OR after the verification was
259
+ // saved (e.g. signed-URL generation throw). Retry as base64 JSON and
260
+ // forward the original Idempotency-Key so the server can dedupe instead
261
+ // of inserting a second verify_ai_verifications row.
226
262
  if (error instanceof VerifyAIRequestError && error.status >= 500 && error.isServerError) {
227
263
  try {
228
264
  const { Buffer } = await import('buffer');
@@ -235,7 +271,7 @@ export class VerifyAIClient {
235
271
  metadata: request.metadata,
236
272
  provider: request.provider,
237
273
  include_image_data: request.include_image_data,
238
- });
274
+ }, options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : undefined);
239
275
  }
240
276
  catch {
241
277
  // If the retry itself fails, throw the original error
@@ -316,10 +352,14 @@ export class VerifyAIClient {
316
352
  }
317
353
  export class VerifyAIRequestError extends Error {
318
354
  constructor(message, status, body) {
319
- super(message);
355
+ const safeMessage = stringifyErrorMessage(message, 'VerifyAI request failed');
356
+ super(safeMessage);
320
357
  this.name = 'VerifyAIRequestError';
321
358
  this.status = status;
322
- this.body = body;
359
+ this.body = {
360
+ ...body,
361
+ error: stringifyErrorMessage(body.error, safeMessage),
362
+ };
323
363
  }
324
364
  get isRateLimited() {
325
365
  return this.status === 429;
@@ -3,6 +3,7 @@ import { AppState, Platform } from 'react-native';
3
3
  import { VerifyAIClient, VerifyAIRequestError } from '../client';
4
4
  import { OfflineQueue } from '../storage/offlineQueue';
5
5
  import { TelemetryContext } from '../telemetry/TelemetryContext';
6
+ import { SDK_VERSION } from '../version';
6
7
  function isQueueableError(error) {
7
8
  if (error instanceof VerifyAIRequestError) {
8
9
  return error.isRetryable;
@@ -86,7 +87,7 @@ export function useVerifyAI(config) {
86
87
  inferenceEngineRef.current = new InferenceEngine();
87
88
  }
88
89
  catch (err) {
89
- console.warn('[VerifyAI] Failed to load ML modules:', err);
90
+ console.warn(`[VerifyAI v${SDK_VERSION}] Failed to load ML modules:`, err);
90
91
  }
91
92
  })();
92
93
  return () => {
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { Buffer } from 'buffer';
6
6
  import { sha256 } from '@noble/hashes/sha2.js';
7
+ import { SDK_VERSION } from '../version';
7
8
  async function loadFileSystem() {
8
9
  try {
9
10
  return await import('expo-file-system');
@@ -68,7 +69,7 @@ export class ModelManager {
68
69
  async downloadArtifacts(manifest, policyId) {
69
70
  const fileSystem = await loadFileSystem();
70
71
  if (!fileSystem || !fileSystem.documentDirectory) {
71
- console.warn('[VerifyAI] expo-file-system not available, skipping download');
72
+ console.warn(`[VerifyAI v${SDK_VERSION}] expo-file-system not available, skipping download`);
72
73
  return;
73
74
  }
74
75
  const cacheDir = `${fileSystem.documentDirectory}verify-ai/bundles/${policyId}/`;
package/lib/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.4.20";
1
+ export declare const SDK_VERSION = "2.4.22";
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.20';
1
+ export const SDK_VERSION = '2.4.22';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@switchlabs/verify-ai-react-native",
3
- "version": "2.4.20",
3
+ "version": "2.4.22",
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",
@@ -34,6 +34,44 @@ function enrichMetadata(metadata?: Record<string, unknown>): Record<string, unkn
34
34
  return enriched;
35
35
  }
36
36
 
37
+ function stringifyErrorMessage(value: unknown, fallback: string): string {
38
+ if (typeof value === 'string' && value.trim()) {
39
+ return value;
40
+ }
41
+
42
+ if (value instanceof Error && value.message) {
43
+ return value.message;
44
+ }
45
+
46
+ if (value && typeof value === 'object') {
47
+ const record = value as Record<string, unknown>;
48
+ for (const key of ['message', 'error', 'detail', 'code']) {
49
+ const candidate = record[key];
50
+ if (typeof candidate === 'string' && candidate.trim()) {
51
+ return candidate;
52
+ }
53
+ }
54
+
55
+ try {
56
+ const json = JSON.stringify(value);
57
+ if (json && json !== '{}') {
58
+ return json;
59
+ }
60
+ } catch {
61
+ // Fall through to String(value) below.
62
+ }
63
+ }
64
+
65
+ if (value != null) {
66
+ const text = String(value);
67
+ if (text && text !== '[object Object]') {
68
+ return text;
69
+ }
70
+ }
71
+
72
+ return fallback;
73
+ }
74
+
37
75
  interface RequestContext {
38
76
  path: string;
39
77
  url: string;
@@ -48,7 +86,7 @@ export class VerifyAIClient {
48
86
 
49
87
  constructor(config: VerifyAIConfig) {
50
88
  if (!config.apiKey) {
51
- throw new Error('VerifyAI: apiKey is required');
89
+ throw new Error(`VerifyAI[v${SDK_VERSION}]: apiKey is required`);
52
90
  }
53
91
  this.apiKey = config.apiKey;
54
92
  this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
@@ -71,13 +109,14 @@ export class VerifyAIClient {
71
109
  }
72
110
 
73
111
  private buildRequestError(
74
- message: string,
112
+ message: unknown,
75
113
  status: number,
76
114
  context: RequestContext,
77
115
  body: Partial<VerifyAIError> = {}
78
116
  ): VerifyAIRequestError {
117
+ const safeMessage = stringifyErrorMessage(message, 'VerifyAI request failed');
79
118
  const error: VerifyAIError = {
80
- error: message,
119
+ error: safeMessage,
81
120
  status,
82
121
  code: status === 408 ? 'timeout' : status === 0 ? 'network_error' : 'request_error',
83
122
  path: context.path,
@@ -86,7 +125,7 @@ export class VerifyAIClient {
86
125
  ...body,
87
126
  };
88
127
 
89
- return new VerifyAIRequestError(message, status, error);
128
+ return new VerifyAIRequestError(safeMessage, status, error);
90
129
  }
91
130
 
92
131
  private normalizeRequestError(error: unknown, context: RequestContext): VerifyAIRequestError {
@@ -102,7 +141,7 @@ export class VerifyAIClient {
102
141
  return this.buildRequestError('Network request failed', 0, context, { code: 'network_error' });
103
142
  }
104
143
 
105
- const message = error instanceof Error ? error.message : 'VerifyAI request failed';
144
+ const message = error instanceof Error ? error.message : stringifyErrorMessage(error, 'VerifyAI request failed');
106
145
  return this.buildRequestError(message, 0, context);
107
146
  }
108
147
 
@@ -132,7 +171,7 @@ export class VerifyAIClient {
132
171
  if (!response.ok) {
133
172
  const errorBody = (body && typeof body === 'object' ? body : null) as VerifyAIError | null;
134
173
  throw this.buildRequestError(
135
- errorBody?.error || `Request failed with status ${response.status}`,
174
+ stringifyErrorMessage(errorBody?.error, `Request failed with status ${response.status}`),
136
175
  response.status,
137
176
  context,
138
177
  {
@@ -147,7 +186,7 @@ export class VerifyAIClient {
147
186
 
148
187
  if (!body || typeof body !== 'object') {
149
188
  throw this.buildRequestError(
150
- 'VerifyAI: Invalid response payload',
189
+ `VerifyAI[v${SDK_VERSION}]: Invalid response payload`,
151
190
  0,
152
191
  context,
153
192
  {
@@ -284,22 +323,27 @@ export class VerifyAIClient {
284
323
  body: formData,
285
324
  });
286
325
  } catch (error) {
287
- // Blank 500 means the server crashed during multipart parsing (e.g.
288
- // Vercel function crash). Retry as JSON/base64 with a fresh request
289
- // the crashed request may have partially recorded the idempotency key.
326
+ // 5xx during multipart can mean the server crashed before reservation
327
+ // (e.g. Vercel multipart-parse failure) OR after the verification was
328
+ // saved (e.g. signed-URL generation throw). Retry as base64 JSON and
329
+ // forward the original Idempotency-Key so the server can dedupe instead
330
+ // of inserting a second verify_ai_verifications row.
290
331
  if (error instanceof VerifyAIRequestError && error.status >= 500 && error.isServerError) {
291
332
  try {
292
333
  const { Buffer } = await import('buffer');
293
334
  const fileResponse = await fetch(request.imageUri);
294
335
  const arrayBuffer = await fileResponse.arrayBuffer();
295
336
  const base64 = Buffer.from(arrayBuffer).toString('base64');
296
- return this.verify({
297
- image: base64,
298
- policy: request.policy,
299
- metadata: request.metadata,
300
- provider: request.provider,
301
- include_image_data: request.include_image_data,
302
- });
337
+ return this.verify(
338
+ {
339
+ image: base64,
340
+ policy: request.policy,
341
+ metadata: request.metadata,
342
+ provider: request.provider,
343
+ include_image_data: request.include_image_data,
344
+ },
345
+ options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : undefined,
346
+ );
303
347
  } catch {
304
348
  // If the retry itself fails, throw the original error
305
349
  throw error;
@@ -384,11 +428,15 @@ export class VerifyAIRequestError extends Error {
384
428
  status: number;
385
429
  body: VerifyAIError;
386
430
 
387
- constructor(message: string, status: number, body: VerifyAIError) {
388
- super(message);
431
+ constructor(message: unknown, status: number, body: VerifyAIError) {
432
+ const safeMessage = stringifyErrorMessage(message, 'VerifyAI request failed');
433
+ super(safeMessage);
389
434
  this.name = 'VerifyAIRequestError';
390
435
  this.status = status;
391
- this.body = body;
436
+ this.body = {
437
+ ...body,
438
+ error: stringifyErrorMessage(body.error, safeMessage),
439
+ };
392
440
  }
393
441
 
394
442
  get isRateLimited(): boolean {
@@ -4,6 +4,7 @@ import { VerifyAIClient, VerifyAIRequestError } from '../client';
4
4
  import { OfflineQueue } from '../storage/offlineQueue';
5
5
  import { TelemetryContext } from '../telemetry/TelemetryContext';
6
6
  import type { TelemetryReporter } from '../telemetry/TelemetryReporter';
7
+ import { SDK_VERSION } from '../version';
7
8
  import type {
8
9
  VerifyAIConfig,
9
10
  VerificationRequest,
@@ -161,7 +162,7 @@ export function useVerifyAI(config: UseVerifyAIConfig): UseVerifyAIReturn {
161
162
  });
162
163
  inferenceEngineRef.current = new InferenceEngine();
163
164
  } catch (err) {
164
- console.warn('[VerifyAI] Failed to load ML modules:', err);
165
+ console.warn(`[VerifyAI v${SDK_VERSION}] Failed to load ML modules:`, err);
165
166
  }
166
167
  })();
167
168
 
@@ -6,6 +6,7 @@
6
6
  import { Buffer } from 'buffer';
7
7
  import { sha256 } from '@noble/hashes/sha2.js';
8
8
  import type { BundleManifest, ModelArtifact } from './types';
9
+ import { SDK_VERSION } from '../version';
9
10
 
10
11
  export type { BundleManifest, ModelArtifact };
11
12
 
@@ -113,7 +114,7 @@ export class ModelManager {
113
114
  private async downloadArtifacts(manifest: BundleManifest, policyId: string): Promise<void> {
114
115
  const fileSystem = await loadFileSystem();
115
116
  if (!fileSystem || !fileSystem.documentDirectory) {
116
- console.warn('[VerifyAI] expo-file-system not available, skipping download');
117
+ console.warn(`[VerifyAI v${SDK_VERSION}] expo-file-system not available, skipping download`);
117
118
  return;
118
119
  }
119
120
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.20';
1
+ export const SDK_VERSION = '2.4.22';