@switchlabs/verify-ai-react-native 2.4.0 → 2.4.1

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.
@@ -118,9 +118,12 @@ export class VerifyAIClient {
118
118
  : normalized.status >= 400 && normalized.status < 500
119
119
  ? 'request_error'
120
120
  : 'server_error';
121
+ const telemetryError = eventType === 'request_timeout'
122
+ ? `${normalized.message} [${context.method} ${context.path}; timeout=${this.timeout}ms]`
123
+ : normalized;
121
124
  this.telemetry.track(eventType, {
122
125
  component: 'client',
123
- error: normalized,
126
+ error: telemetryError,
124
127
  errorCode,
125
128
  });
126
129
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useRef, useState, useCallback, useEffect } from 'react';
3
- import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, } from 'react-native';
3
+ import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, AppState, useWindowDimensions, } from 'react-native';
4
4
  import { CameraView, useCameraPermissions, } from 'expo-camera';
5
5
  import { useTelemetry } from '../telemetry/TelemetryContext';
6
6
  /** Quality used when expo-image-manipulator is not available (lower = smaller). */
@@ -89,6 +89,35 @@ export function VerifyAIScanner({ onCapture, onResult, onError, overlay, style,
89
89
  const cameraReadyRef = useRef(false);
90
90
  const cameraInitFailedRef = useRef(false);
91
91
  const permissionDeniedTrackedRef = useRef(false);
92
+ // Track dimensions to detect orientation changes and remount camera
93
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
94
+ const prevDimensionsRef = useRef({ width: windowWidth, height: windowHeight });
95
+ useEffect(() => {
96
+ const prev = prevDimensionsRef.current;
97
+ const orientationChanged = (prev.width > prev.height) !== (windowWidth > windowHeight);
98
+ prevDimensionsRef.current = { width: windowWidth, height: windowHeight };
99
+ if (orientationChanged && !terminated) {
100
+ // Force camera remount to fix preview distortion on iOS
101
+ setCameraReady(false);
102
+ cameraReadyRef.current = false;
103
+ setCameraKey((k) => k + 1);
104
+ }
105
+ }, [windowWidth, windowHeight, terminated]);
106
+ // Resume camera when app returns from background/inactive (e.g. notification bar)
107
+ useEffect(() => {
108
+ if (terminated)
109
+ return;
110
+ const subscription = AppState.addEventListener('change', (nextState) => {
111
+ if (nextState === 'active') {
112
+ // Force camera remount — on iOS, AVCaptureSession often fails to resume
113
+ // its preview layer after returning from the notification bar or control center.
114
+ setCameraReady(false);
115
+ cameraReadyRef.current = false;
116
+ setCameraKey((k) => k + 1);
117
+ }
118
+ });
119
+ return () => subscription.remove();
120
+ }, [terminated]);
92
121
  const pausePreview = useCallback(() => {
93
122
  cameraRef.current?.pausePreview?.().catch(() => { });
94
123
  }, []);
@@ -24,17 +24,18 @@ export declare class TelemetryReporter {
24
24
  private flushNow;
25
25
  private clearFlushTimer;
26
26
  /**
27
- * Persist the current buffer to AsyncStorage so events survive app crashes.
27
+ * Persist the current buffer to AsyncStorage so events survive abrupt app exits.
28
28
  * Fire-and-forget — never throws or blocks.
29
29
  *
30
30
  * IMPORTANT: skips persist if loadPersistedBuffer() hasn't completed yet,
31
- * to avoid overwriting orphaned events from a crashed previous session.
31
+ * to avoid overwriting orphaned events from a previous session before recovery.
32
32
  */
33
33
  private persistBuffer;
34
34
  /**
35
- * Load crash-persisted events from a previous session and merge into current buffer.
36
- * Runs once per instance. If orphaned events are found, emits an `sdk_crash` event
37
- * so the monitor can detect and investigate app crashes.
35
+ * Load persisted events from a previous session and merge into current buffer.
36
+ * Runs once per instance. If orphaned events are found, emits an
37
+ * `sdk_session_recovered` event so monitoring can flag that the prior
38
+ * session ended without a clean telemetry flush.
38
39
  */
39
40
  private loadPersistedBuffer;
40
41
  }
@@ -26,7 +26,7 @@ export class TelemetryReporter {
26
26
  this.apiKey = apiKey;
27
27
  this.baseUrl = baseUrl.replace(/\/$/, '');
28
28
  this.sessionId = Math.random().toString(36).slice(2) + Date.now().toString(36);
29
- // Load any crash-persisted events from the previous session
29
+ // Load any persisted events left behind by a previous session
30
30
  this.loadPersistedBuffer();
31
31
  }
32
32
  /** Track an error event. Fire-and-forget — never throws. */
@@ -72,7 +72,7 @@ export class TelemetryReporter {
72
72
  else {
73
73
  this.scheduleFlush();
74
74
  }
75
- // Persist immediately once crash recovery has completed. If startup
75
+ // Persist immediately once prior-session recovery has completed. If startup
76
76
  // recovery is still in flight, write the merged buffer back afterwards.
77
77
  if (this.loadedPersisted) {
78
78
  this.persistBuffer();
@@ -91,7 +91,7 @@ export class TelemetryReporter {
91
91
  }
92
92
  /** Flush all buffered events immediately. Returns a promise but never rejects. */
93
93
  async flush() {
94
- // Merge any crash-persisted events from previous session before flushing
94
+ // Merge any persisted events from a previous session before flushing
95
95
  await this.loadPersistedBuffer();
96
96
  if (this.buffer.size === 0 || this.flushing)
97
97
  return;
@@ -168,15 +168,15 @@ export class TelemetryReporter {
168
168
  }
169
169
  }
170
170
  /**
171
- * Persist the current buffer to AsyncStorage so events survive app crashes.
171
+ * Persist the current buffer to AsyncStorage so events survive abrupt app exits.
172
172
  * Fire-and-forget — never throws or blocks.
173
173
  *
174
174
  * IMPORTANT: skips persist if loadPersistedBuffer() hasn't completed yet,
175
- * to avoid overwriting orphaned events from a crashed previous session.
175
+ * to avoid overwriting orphaned events from a previous session before recovery.
176
176
  */
177
177
  persistBuffer() {
178
178
  if (!this.loadedPersisted)
179
- return; // Don't overwrite orphaned crash data before it's loaded
179
+ return; // Don't overwrite orphaned prior-session data before it's loaded
180
180
  try {
181
181
  const entries = {};
182
182
  for (const [key, event] of this.buffer) {
@@ -196,9 +196,10 @@ export class TelemetryReporter {
196
196
  }
197
197
  }
198
198
  /**
199
- * Load crash-persisted events from a previous session and merge into current buffer.
200
- * Runs once per instance. If orphaned events are found, emits an `sdk_crash` event
201
- * so the monitor can detect and investigate app crashes.
199
+ * Load persisted events from a previous session and merge into current buffer.
200
+ * Runs once per instance. If orphaned events are found, emits an
201
+ * `sdk_session_recovered` event so monitoring can flag that the prior
202
+ * session ended without a clean telemetry flush.
202
203
  */
203
204
  async loadPersistedBuffer() {
204
205
  if (this.loadedPersisted)
@@ -236,14 +237,14 @@ export class TelemetryReporter {
236
237
  this.buffer.set(key, event);
237
238
  }
238
239
  }
239
- // Emit sdk_crash event if we recovered orphaned events from a dead session
240
+ // Emit sdk_session_recovered if we recovered orphaned events from a previous session
240
241
  if (orphanedCount > 0) {
241
242
  const now = new Date().toISOString();
242
243
  const uniqueTypes = [...new Set(orphanedTypes)];
243
244
  const crashEvent = {
244
- event_type: 'sdk_crash',
245
+ event_type: 'sdk_session_recovered',
245
246
  component: 'TelemetryReporter',
246
- error_message: `Recovered ${orphanedCount} unflushed event(s) from crashed/killed session: ${uniqueTypes.join(', ')}`,
247
+ error_message: `Recovered ${orphanedCount} unflushed event(s) from a previous session that ended without a clean telemetry flush: ${uniqueTypes.join(', ')}`,
247
248
  sdk_platform: Platform.OS,
248
249
  sdk_version: SDK_VERSION,
249
250
  os_name: Platform.OS,
@@ -253,7 +254,7 @@ export class TelemetryReporter {
253
254
  first_occurred_at: now,
254
255
  last_occurred_at: now,
255
256
  };
256
- this.buffer.set(`sdk_crash|recovered|${this.sessionId}`, crashEvent);
257
+ this.buffer.set(`sdk_session_recovered|recovered|${this.sessionId}`, crashEvent);
257
258
  }
258
259
  // Clear persisted data now that it's loaded into memory. The merged
259
260
  // buffer will be re-persisted below until a flush succeeds.
package/lib/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.4.0";
1
+ export declare const SDK_VERSION = "2.4.1";
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.0';
1
+ export const SDK_VERSION = '2.4.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@switchlabs/verify-ai-react-native",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
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",
@@ -173,13 +173,16 @@ export class VerifyAIClient {
173
173
  : normalized.status === 429
174
174
  ? 'rate_limited'
175
175
  : normalized.status >= 400 && normalized.status < 500
176
- ? 'request_error'
176
+ ? 'request_error'
177
177
  : 'server_error';
178
+ const telemetryError = eventType === 'request_timeout'
179
+ ? `${normalized.message} [${context.method} ${context.path}; timeout=${this.timeout}ms]`
180
+ : normalized;
178
181
  this.telemetry.track(
179
182
  eventType,
180
183
  {
181
184
  component: 'client',
182
- error: normalized,
185
+ error: telemetryError,
183
186
  errorCode,
184
187
  },
185
188
  );
@@ -5,6 +5,8 @@ import {
5
5
  TouchableOpacity,
6
6
  StyleSheet,
7
7
  ActivityIndicator,
8
+ AppState,
9
+ useWindowDimensions,
8
10
  type ViewStyle,
9
11
  } from 'react-native';
10
12
  import {
@@ -152,6 +154,41 @@ export function VerifyAIScanner({
152
154
  const cameraInitFailedRef = useRef(false);
153
155
  const permissionDeniedTrackedRef = useRef(false);
154
156
 
157
+ // Track dimensions to detect orientation changes and remount camera
158
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
159
+ const prevDimensionsRef = useRef({ width: windowWidth, height: windowHeight });
160
+
161
+ useEffect(() => {
162
+ const prev = prevDimensionsRef.current;
163
+ const orientationChanged =
164
+ (prev.width > prev.height) !== (windowWidth > windowHeight);
165
+ prevDimensionsRef.current = { width: windowWidth, height: windowHeight };
166
+
167
+ if (orientationChanged && !terminated) {
168
+ // Force camera remount to fix preview distortion on iOS
169
+ setCameraReady(false);
170
+ cameraReadyRef.current = false;
171
+ setCameraKey((k) => k + 1);
172
+ }
173
+ }, [windowWidth, windowHeight, terminated]);
174
+
175
+ // Resume camera when app returns from background/inactive (e.g. notification bar)
176
+ useEffect(() => {
177
+ if (terminated) return;
178
+
179
+ const subscription = AppState.addEventListener('change', (nextState) => {
180
+ if (nextState === 'active') {
181
+ // Force camera remount — on iOS, AVCaptureSession often fails to resume
182
+ // its preview layer after returning from the notification bar or control center.
183
+ setCameraReady(false);
184
+ cameraReadyRef.current = false;
185
+ setCameraKey((k) => k + 1);
186
+ }
187
+ });
188
+
189
+ return () => subscription.remove();
190
+ }, [terminated]);
191
+
155
192
  const pausePreview = useCallback(() => {
156
193
  cameraRef.current?.pausePreview?.().catch(() => {});
157
194
  }, []);
@@ -56,7 +56,7 @@ export class TelemetryReporter {
56
56
  this.apiKey = apiKey;
57
57
  this.baseUrl = baseUrl.replace(/\/$/, '');
58
58
  this.sessionId = Math.random().toString(36).slice(2) + Date.now().toString(36);
59
- // Load any crash-persisted events from the previous session
59
+ // Load any persisted events left behind by a previous session
60
60
  this.loadPersistedBuffer();
61
61
  }
62
62
 
@@ -116,7 +116,7 @@ export class TelemetryReporter {
116
116
  this.scheduleFlush();
117
117
  }
118
118
 
119
- // Persist immediately once crash recovery has completed. If startup
119
+ // Persist immediately once prior-session recovery has completed. If startup
120
120
  // recovery is still in flight, write the merged buffer back afterwards.
121
121
  if (this.loadedPersisted) {
122
122
  this.persistBuffer();
@@ -134,7 +134,7 @@ export class TelemetryReporter {
134
134
 
135
135
  /** Flush all buffered events immediately. Returns a promise but never rejects. */
136
136
  async flush(): Promise<void> {
137
- // Merge any crash-persisted events from previous session before flushing
137
+ // Merge any persisted events from a previous session before flushing
138
138
  await this.loadPersistedBuffer();
139
139
 
140
140
  if (this.buffer.size === 0 || this.flushing) return;
@@ -216,14 +216,14 @@ export class TelemetryReporter {
216
216
  }
217
217
 
218
218
  /**
219
- * Persist the current buffer to AsyncStorage so events survive app crashes.
219
+ * Persist the current buffer to AsyncStorage so events survive abrupt app exits.
220
220
  * Fire-and-forget — never throws or blocks.
221
221
  *
222
222
  * IMPORTANT: skips persist if loadPersistedBuffer() hasn't completed yet,
223
- * to avoid overwriting orphaned events from a crashed previous session.
223
+ * to avoid overwriting orphaned events from a previous session before recovery.
224
224
  */
225
225
  private persistBuffer(): void {
226
- if (!this.loadedPersisted) return; // Don't overwrite orphaned crash data before it's loaded
226
+ if (!this.loadedPersisted) return; // Don't overwrite orphaned prior-session data before it's loaded
227
227
  try {
228
228
  const entries: Record<string, TelemetryEvent> = {};
229
229
  for (const [key, event] of this.buffer) {
@@ -242,9 +242,10 @@ export class TelemetryReporter {
242
242
  }
243
243
 
244
244
  /**
245
- * Load crash-persisted events from a previous session and merge into current buffer.
246
- * Runs once per instance. If orphaned events are found, emits an `sdk_crash` event
247
- * so the monitor can detect and investigate app crashes.
245
+ * Load persisted events from a previous session and merge into current buffer.
246
+ * Runs once per instance. If orphaned events are found, emits an
247
+ * `sdk_session_recovered` event so monitoring can flag that the prior
248
+ * session ended without a clean telemetry flush.
248
249
  */
249
250
  private async loadPersistedBuffer(): Promise<void> {
250
251
  if (this.loadedPersisted) return;
@@ -282,14 +283,14 @@ export class TelemetryReporter {
282
283
  }
283
284
  }
284
285
 
285
- // Emit sdk_crash event if we recovered orphaned events from a dead session
286
+ // Emit sdk_session_recovered if we recovered orphaned events from a previous session
286
287
  if (orphanedCount > 0) {
287
288
  const now = new Date().toISOString();
288
289
  const uniqueTypes = [...new Set(orphanedTypes)];
289
290
  const crashEvent: TelemetryEvent = {
290
- event_type: 'sdk_crash',
291
+ event_type: 'sdk_session_recovered',
291
292
  component: 'TelemetryReporter',
292
- error_message: `Recovered ${orphanedCount} unflushed event(s) from crashed/killed session: ${uniqueTypes.join(', ')}`,
293
+ error_message: `Recovered ${orphanedCount} unflushed event(s) from a previous session that ended without a clean telemetry flush: ${uniqueTypes.join(', ')}`,
293
294
  sdk_platform: Platform.OS,
294
295
  sdk_version: SDK_VERSION,
295
296
  os_name: Platform.OS,
@@ -299,7 +300,7 @@ export class TelemetryReporter {
299
300
  first_occurred_at: now,
300
301
  last_occurred_at: now,
301
302
  };
302
- this.buffer.set(`sdk_crash|recovered|${this.sessionId}`, crashEvent);
303
+ this.buffer.set(`sdk_session_recovered|recovered|${this.sessionId}`, crashEvent);
303
304
  }
304
305
 
305
306
  // Clear persisted data now that it's loaded into memory. The merged
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '2.4.0';
1
+ export const SDK_VERSION = '2.4.1';