react-native-usesense 1.0.0 → 2.0.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/src/index.ts CHANGED
@@ -1,250 +1,308 @@
1
- import { NativeModules, NativeEventEmitter } from 'react-native';
1
+ import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
2
2
 
3
- const { UseSenseModule } = NativeModules;
3
+ const LINKING_ERROR =
4
+ `react-native-usesense: the native module could not be found.\n\n` +
5
+ Platform.select({
6
+ ios: `iOS: run \`cd ios && pod install\` and rebuild the app.`,
7
+ android: `Android: rebuild the app after installing the package.`,
8
+ default: '',
9
+ }) +
10
+ `\n\nIf you're using Expo, the plugin must be consumed through a development build; it does not support Expo Go because it wraps a native camera SDK.`;
11
+
12
+ const UseSenseModule = NativeModules.UseSenseModule;
4
13
 
5
14
  if (!UseSenseModule) {
6
- throw new Error(
7
- 'react-native-usesense: NativeModule not found. Make sure you have linked the library correctly.\n' +
8
- 'iOS: Run `cd ios && pod install`\n' +
9
- 'Android: Rebuild the app after installing the package.',
10
- );
15
+ throw new Error(LINKING_ERROR);
11
16
  }
12
17
 
13
- // ─── Types ───────────────────────────────────────────────────────────
18
+ // ─── Types ───────────────────────────────────────────────────────────────
14
19
 
15
- /** Environment for the UseSense API. */
16
- export type UseSenseEnvironment = 'production' | 'sandbox';
20
+ /**
21
+ * Target environment for the UseSense API. `auto` asks the native SDK
22
+ * to detect from the API key prefix (`sk_sandbox_*` → sandbox,
23
+ * `sk_prod_*` → production).
24
+ */
25
+ export type UseSenseEnvironment = 'sandbox' | 'production' | 'auto';
17
26
 
18
- /** Session type for verification. */
27
+ /** Type of verification session. */
19
28
  export type SessionType = 'enrollment' | 'authentication';
20
29
 
21
- /** Challenge policy controlling verification difficulty. */
22
- export type ChallengePolicy = 'standard' | 'enhanced' | 'adaptive';
23
-
24
- /** Decision returned by the verification engine. */
25
- export type UseSenseDecision = 'approved' | 'rejected' | 'manual_review';
26
-
27
- /** Processing stage during server-side analysis. */
28
- export type ProcessingStage = 'deepsense' | 'livesense' | 'matchsense' | 'fusion';
30
+ /**
31
+ * Decision returned by the native SDK.
32
+ *
33
+ * These values come straight from the server's verdict and are
34
+ * exposed to the integrator for UI feedback only. The definitive
35
+ * verdict arrives at your backend via an HMAC-signed webhook; never
36
+ * trust the client-side decision for access-control.
37
+ */
38
+ export type Decision = 'APPROVE' | 'REJECT' | 'MANUAL_REVIEW';
29
39
 
30
40
  /**
31
- * Configuration for initializing the UseSense plugin.
41
+ * Branding overrides applied to the native camera UI.
32
42
  *
33
- * @example
34
- * ```ts
35
- * await UseSense.initialize({
36
- * apiKey: 'your_api_key',
37
- * environment: 'sandbox',
38
- * });
39
- * ```
43
+ * - `logoUrl`, `primaryColor`, `buttonRadius`, `fontFamily` are
44
+ * honoured on both iOS and Android.
45
+ * - `displayName`, `redirectUrl` are Android-only fields. They're
46
+ * accepted on iOS for cross-platform API parity but ignored.
47
+ */
48
+ export interface BrandingConfig {
49
+ logoUrl?: string;
50
+ primaryColor?: string;
51
+ buttonRadius?: number;
52
+ fontFamily?: string;
53
+ displayName?: string;
54
+ redirectUrl?: string;
55
+ }
56
+
57
+ /**
58
+ * Configuration for `UseSense.initialize`. The minimum required
59
+ * field is the API key; every other field has a sensible default.
40
60
  */
41
61
  export interface UseSenseConfig {
42
- /** API key from the UseSense dashboard. */
62
+ /** API key from watchtower.usesense.ai. */
43
63
  apiKey: string;
44
- /** Target environment. Defaults to `'sandbox'`. */
64
+ /** Target environment. Defaults to `auto` (detected from key prefix). */
45
65
  environment?: UseSenseEnvironment;
46
- /** Your organization ID (optional, inferred from API key). */
47
- organizationId?: string;
48
- /** Default session type. Defaults to `'enrollment'`. */
49
- sessionType?: SessionType;
50
- /** Identity ID for authentication sessions. */
66
+ /**
67
+ * Override the API endpoint. Defaults to `https://api.usesense.ai/v1`.
68
+ * Only set this if you're pointing at a staging or self-hosted
69
+ * proxy. Maps to `apiEndpoint` on iOS and `baseUrl` on Android
70
+ * under the hood.
71
+ */
72
+ apiEndpoint?: string;
73
+ /** SDK-level branding overrides. */
74
+ branding?: BrandingConfig;
75
+ }
76
+
77
+ /**
78
+ * Request to start a verification session.
79
+ */
80
+ export interface VerificationRequest {
81
+ sessionType: SessionType;
82
+ /** Your internal user identifier (optional, stored in session metadata). */
83
+ externalUserId?: string;
84
+ /**
85
+ * Required for `authentication` sessions. The previously-enrolled
86
+ * identity you're verifying against.
87
+ */
51
88
  identityId?: string;
52
- /** Challenge difficulty policy. Defaults to `'standard'`. */
53
- challengePolicy?: ChallengePolicy;
54
- /** Enable audio capture for voice deepfake detection. Defaults to `false`. */
55
- enableAudio?: boolean;
56
- /** Maximum session duration in milliseconds. Defaults to `60000`. */
57
- timeout?: number;
58
89
  /** Custom key-value pairs attached to the session. */
59
- metadata?: Record<string, string>;
90
+ metadata?: Record<string, string | number | boolean>;
60
91
  }
61
92
 
62
93
  /**
63
- * Result returned when a verification session completes.
94
+ * Redacted decision object returned when a session completes.
64
95
  *
65
- * **Important:** This result is for UI feedback only.
66
- * The definitive verdict arrives at your backend via HMAC-signed webhook.
96
+ * This is the ONLY shape the plugin exposes to React Native. Pillar
97
+ * scores (channel trust, liveness, matchsense risk) are intentionally
98
+ * not included — the native SDKs strip those fields before returning
99
+ * across the bridge to prevent reverse-engineering of the server-side
100
+ * scoring logic. If you need full scoring details, consume the signed
101
+ * webhook delivered to your backend.
67
102
  */
68
103
  export interface UseSenseResult {
69
- /** Unique session identifier. */
70
104
  sessionId: string;
71
- /** Verification decision. */
72
- decision: UseSenseDecision;
73
- /** DeepSense channel trust score (0-100). */
74
- channelTrustScore: number;
75
- /** LiveSense liveness score (0-100). */
76
- livenessScore: number;
77
- /** MatchSense risk score (0-100). Lower is better. */
78
- matchSenseRiskScore: number;
79
- /** Fused presence confidence score (0-100). */
80
- presenceConfidence: number;
81
- /** Reasons contributing to the decision. */
82
- reasons: string[];
83
- /** Rule that triggered the decision, if any. */
84
- ruleTriggered?: string;
85
- /** Recommended action for the integrator. */
86
- recommendedAction?: string;
87
- /** Cryptographic signature for result verification. */
88
- sessionSignature: string;
105
+ sessionType: string | null;
106
+ identityId: string | null;
107
+ decision: Decision;
108
+ timestamp: string;
109
+ /** Convenience: `decision === 'APPROVE'`. */
110
+ isApproved: boolean;
111
+ /** Convenience: `decision === 'REJECT'`. */
112
+ isRejected: boolean;
113
+ /** Convenience: `decision === 'MANUAL_REVIEW'`. */
114
+ isPendingReview: boolean;
89
115
  }
90
116
 
91
117
  /**
92
- * Error returned when a verification session fails.
118
+ * Error thrown when a verification session fails.
119
+ *
120
+ * Matches the native SDKs' error shape: machine-readable code,
121
+ * human-readable message, retry hint.
93
122
  */
94
123
  export interface UseSenseError {
95
- /** Machine-readable error code. */
124
+ /**
125
+ * Machine-readable error code. One of
126
+ * `CAMERA_UNAVAILABLE`, `CAMERA_PERMISSION_DENIED`,
127
+ * `MIC_PERMISSION_DENIED`, `NETWORK_ERROR`, `NETWORK_TIMEOUT`,
128
+ * `SESSION_EXPIRED`, `UNAUTHORIZED`, `INVALID_TOKEN`,
129
+ * `SESSION_NOT_FOUND`, `IDENTITY_NOT_FOUND`, `INVALID_REQUEST`,
130
+ * `INVALID_CONFIG`, `QUOTA_EXCEEDED`, `USER_CANCELLED`,
131
+ * `session_cancelled`, `sdk_not_initialized`, `no_view_controller`,
132
+ * `CAPTURE_FAILED`, `ENCODING_FAILED`, `UPLOAD_FAILED`,
133
+ * `FACE_NOT_DETECTED`, `LOW_LIGHT`, `TIMEOUT`, `SERVER_ERROR`,
134
+ * `SERVICE_UNAVAILABLE`, `TOKEN_EXPIRED`, `TOKEN_ALREADY_USED`,
135
+ * `INSUFFICIENT_CREDITS`, `NONCE_MISMATCH`, `UNKNOWN_ERROR`.
136
+ */
96
137
  code: string;
97
138
  /** Human-readable error message. */
98
139
  message: string;
99
- /** Additional error details. */
100
- details?: Record<string, any>;
140
+ /** Whether the operation can be retried. */
141
+ isRetryable?: boolean;
142
+ /** Server-specific error code, if any. */
143
+ serverCode?: string;
101
144
  }
102
145
 
103
146
  /**
104
- * Events emitted during the verification session lifecycle.
147
+ * Lifecycle event types emitted by the native SDK during a
148
+ * verification session.
105
149
  */
106
- export type UseSenseEvent =
107
- | { type: 'session_started'; sessionId: string }
108
- | { type: 'challenge_presented'; challengeType: string }
109
- | { type: 'challenge_completed'; challengeType: string }
110
- | { type: 'processing'; stage: ProcessingStage }
111
- | { type: 'session_completed'; result: UseSenseResult }
112
- | { type: 'session_error'; error: UseSenseError };
113
-
114
- /** Options for starting a verification session. */
115
- export interface StartSessionOptions {
116
- /** Session type. Overrides the value set in {@link UseSenseConfig}. */
117
- sessionType?: SessionType;
118
- /** Identity ID for authentication sessions. */
119
- identityId?: string;
120
- }
150
+ export type UseSenseEventType =
151
+ | 'SESSION_CREATED'
152
+ | 'PERMISSIONS_REQUESTED'
153
+ | 'PERMISSIONS_GRANTED'
154
+ | 'PERMISSIONS_DENIED'
155
+ | 'CAPTURE_STARTED'
156
+ | 'FRAME_CAPTURED'
157
+ | 'CAPTURE_COMPLETED'
158
+ | 'AUDIO_RECORD_STARTED'
159
+ | 'AUDIO_RECORD_COMPLETED'
160
+ | 'CHALLENGE_STARTED'
161
+ | 'CHALLENGE_COMPLETED'
162
+ | 'UPLOAD_STARTED'
163
+ | 'UPLOAD_PROGRESS'
164
+ | 'UPLOAD_COMPLETED'
165
+ | 'COMPLETE_STARTED'
166
+ | 'DECISION_RECEIVED'
167
+ | 'IMAGE_QUALITY_CHECK'
168
+ | 'ERROR'
169
+ | 'UNKNOWN';
121
170
 
122
- /** Status of a verification session. */
123
- export interface SessionStatus {
124
- /** Current session status. */
125
- status: string;
126
- /** Result, if the session has completed. */
127
- result?: UseSenseResult;
171
+ /** Single event emitted during a session. */
172
+ export interface UseSenseEvent {
173
+ type: UseSenseEventType;
174
+ /** Unix epoch milliseconds. */
175
+ timestamp: number;
176
+ /** Event-specific payload. Opaque strings; never contains scoring data. */
177
+ data?: Record<string, string>;
128
178
  }
129
179
 
130
- /** Subscription handle returned by {@link UseSense.addListener}. */
180
+ /** Handle returned by `UseSense.addListener`. */
131
181
  export interface UseSenseSubscription {
132
- /** Remove the event listener. */
133
- remove: () => void;
182
+ remove(): void;
134
183
  }
135
184
 
136
- // ─── Implementation ──────────────────────────────────────────────────
185
+ // ─── Implementation ──────────────────────────────────────────────────────
137
186
 
138
187
  const eventEmitter = new NativeEventEmitter(UseSenseModule);
139
188
 
140
189
  /**
141
190
  * UseSense React Native plugin.
142
191
  *
143
- * Provides human presence verification by wrapping the native UseSense
144
- * iOS and Android SDKs into a single cross-platform API.
192
+ * Human presence verification wrapping the native iOS and Android
193
+ * UseSense SDKs. Every session runs inside a full-screen native
194
+ * camera activity; the plugin is a thin bridge that forwards
195
+ * initialization, verification requests, and lifecycle events to
196
+ * the platform SDK and returns a redacted decision object.
145
197
  *
146
198
  * @example
147
199
  * ```ts
148
200
  * import { UseSense } from 'react-native-usesense';
149
201
  *
150
- * await UseSense.initialize({ apiKey: 'your_key', environment: 'sandbox' });
151
- * const result = await UseSense.startSession({ sessionType: 'enrollment' });
152
- * console.log(result.decision);
202
+ * await UseSense.initialize({
203
+ * apiKey: 'sk_sandbox_...',
204
+ * environment: 'sandbox',
205
+ * });
206
+ *
207
+ * const unsubscribe = UseSense.addListener((event) => {
208
+ * console.log(event.type, event.data);
209
+ * });
210
+ *
211
+ * try {
212
+ * const result = await UseSense.startVerification({
213
+ * sessionType: 'enrollment',
214
+ * });
215
+ * console.log(result.decision); // 'APPROVE' | 'REJECT' | 'MANUAL_REVIEW'
216
+ * } finally {
217
+ * unsubscribe.remove();
218
+ * }
153
219
  * ```
154
220
  */
155
221
  export class UseSense {
156
222
  /**
157
- * Initialize the UseSense plugin. Must be called once before any other method.
223
+ * Initialize the UseSense plugin. Must be called before any other
224
+ * method. Calling again with a different API key or environment
225
+ * replaces the previous configuration.
158
226
  *
159
- * @param config - Plugin configuration including API key and environment.
160
- * @throws {UseSenseError} If the API key is invalid or configuration is malformed.
227
+ * @throws {UseSenseError} If the API key is empty or initialization
228
+ * fails on the native side.
161
229
  */
162
230
  static async initialize(config: UseSenseConfig): Promise<void> {
163
231
  if (!config.apiKey) {
164
- throw { code: 'invalid_config', message: 'apiKey is required' } as UseSenseError;
232
+ const err: UseSenseError = {
233
+ code: 'invalid_config',
234
+ message: 'apiKey is required',
235
+ isRetryable: false,
236
+ };
237
+ throw err;
165
238
  }
166
239
  return UseSenseModule.initialize({
167
240
  apiKey: config.apiKey,
168
- environment: config.environment ?? 'sandbox',
169
- organizationId: config.organizationId,
170
- sessionType: config.sessionType ?? 'enrollment',
171
- identityId: config.identityId,
172
- challengePolicy: config.challengePolicy ?? 'standard',
173
- enableAudio: config.enableAudio ?? false,
174
- timeout: config.timeout ?? 60000,
175
- metadata: config.metadata,
241
+ environment: config.environment ?? 'auto',
242
+ apiEndpoint: config.apiEndpoint,
243
+ branding: config.branding,
176
244
  });
177
245
  }
178
246
 
179
247
  /**
180
- * Start a verification session. Presents a full-screen native camera UI.
181
- *
182
- * The returned promise resolves when the session completes and the modal
183
- * dismisses, or rejects if an error occurs.
184
- *
185
- * @param options - Optional overrides for session type and identity ID.
186
- * @returns The verification result (for UI feedback only).
187
- * @throws {UseSenseError} On failure or cancellation.
248
+ * Start a verification session. Presents a full-screen native
249
+ * camera activity. The returned promise resolves when the session
250
+ * completes or rejects with a `UseSenseError` (including the
251
+ * `USER_CANCELLED` / `session_cancelled` cases when the user
252
+ * dismisses the camera screen).
188
253
  */
189
- static async startSession(options?: StartSessionOptions): Promise<UseSenseResult> {
190
- return UseSenseModule.startSession(options ?? {});
191
- }
192
-
193
- /**
194
- * Cancel an in-progress verification session.
195
- *
196
- * The camera UI is dismissed and the `startSession` promise rejects
197
- * with a `session_cancelled` error.
198
- */
199
- static async cancelSession(): Promise<void> {
200
- return UseSenseModule.cancelSession();
254
+ static async startVerification(
255
+ request: VerificationRequest,
256
+ ): Promise<UseSenseResult> {
257
+ return UseSenseModule.startVerification({
258
+ sessionType: request.sessionType,
259
+ externalUserId: request.externalUserId,
260
+ identityId: request.identityId,
261
+ metadata: request.metadata,
262
+ });
201
263
  }
202
264
 
203
265
  /**
204
- * Get the status of a verification session by ID.
266
+ * Subscribe to real-time lifecycle events emitted by the native
267
+ * SDK during a verification session. Call this **before**
268
+ * `startVerification` so the subscription captures the earliest
269
+ * events (`SESSION_CREATED`, `PERMISSIONS_REQUESTED`).
205
270
  *
206
- * @param sessionId - The session ID to query.
207
- * @returns The current status and optional result.
271
+ * Returns a subscription handle; call `.remove()` to stop
272
+ * receiving events.
208
273
  */
209
- static async getSessionStatus(sessionId: string): Promise<SessionStatus> {
210
- return UseSenseModule.getSessionStatus(sessionId);
274
+ static addListener(
275
+ callback: (event: UseSenseEvent) => void,
276
+ ): UseSenseSubscription {
277
+ const subscription = eventEmitter.addListener(
278
+ 'UseSenseEvent',
279
+ (raw: UseSenseEvent) => {
280
+ callback(raw);
281
+ },
282
+ );
283
+ return {
284
+ remove: () => subscription.remove(),
285
+ };
211
286
  }
212
287
 
213
288
  /**
214
- * Subscribe to real-time events during the verification lifecycle.
215
- *
216
- * Call this before `startSession()` to receive all events.
217
- *
218
- * @param callback - Function invoked for each event.
219
- * @returns A subscription handle with a `remove()` method.
220
- *
221
- * @example
222
- * ```ts
223
- * const listener = UseSense.addListener((event) => {
224
- * console.log(event.type);
225
- * });
226
- * // Later:
227
- * listener.remove();
228
- * ```
289
+ * Clear all event listeners and release native resources. Does not
290
+ * need to be called explicitly during normal usage; the plugin
291
+ * cleans up on process teardown. Useful when you want to re-
292
+ * initialize against a different API key or during test teardown.
229
293
  */
230
- static addListener(callback: (event: UseSenseEvent) => void): UseSenseSubscription {
231
- UseSenseModule.subscribeToEvents();
232
- const subscription = eventEmitter.addListener('UseSenseEvent', callback);
233
- return {
234
- remove: () => {
235
- subscription.remove();
236
- UseSenseModule.unsubscribeFromEvents();
237
- },
238
- };
294
+ static async reset(): Promise<void> {
295
+ return UseSenseModule.reset();
239
296
  }
240
297
 
241
298
  /**
242
- * Get the native SDK version string.
243
- *
244
- * @returns Version string in semver format (e.g. `"1.0.0"`).
299
+ * Whether the native plugin has an active `UseSense` client. Set
300
+ * to `true` after a successful `initialize` and `false` after
301
+ * `reset`. Useful for feature-flagging UI that should only show
302
+ * when the SDK is ready.
245
303
  */
246
- static getSdkVersion(): string {
247
- return UseSenseModule.getSdkVersion();
304
+ static async isInitialized(): Promise<boolean> {
305
+ return UseSenseModule.isInitialized();
248
306
  }
249
307
  }
250
308