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/README.md +267 -373
- package/android/build.gradle +7 -2
- package/android/src/main/java/com/usesense/reactnative/UseSenseModule.kt +6 -1
- package/ios/UseSenseModule.swift +223 -88
- package/lib/commonjs/index.js +122 -101
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +123 -102
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +157 -141
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-usesense.podspec +9 -3
- package/src/index.ts +222 -164
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
|
|
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
|
-
/**
|
|
16
|
-
|
|
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
|
-
/**
|
|
27
|
+
/** Type of verification session. */
|
|
19
28
|
export type SessionType = 'enrollment' | 'authentication';
|
|
20
29
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
*
|
|
41
|
+
* Branding overrides applied to the native camera UI.
|
|
32
42
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
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
|
|
62
|
+
/** API key from watchtower.usesense.ai. */
|
|
43
63
|
apiKey: string;
|
|
44
|
-
/** Target environment. Defaults to `
|
|
64
|
+
/** Target environment. Defaults to `auto` (detected from key prefix). */
|
|
45
65
|
environment?: UseSenseEnvironment;
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
*
|
|
94
|
+
* Redacted decision object returned when a session completes.
|
|
64
95
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
100
|
-
|
|
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
|
-
*
|
|
147
|
+
* Lifecycle event types emitted by the native SDK during a
|
|
148
|
+
* verification session.
|
|
105
149
|
*/
|
|
106
|
-
export type
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
/**
|
|
123
|
-
export interface
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
/**
|
|
180
|
+
/** Handle returned by `UseSense.addListener`. */
|
|
131
181
|
export interface UseSenseSubscription {
|
|
132
|
-
|
|
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
|
-
*
|
|
144
|
-
*
|
|
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({
|
|
151
|
-
*
|
|
152
|
-
*
|
|
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
|
|
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
|
-
* @
|
|
160
|
-
*
|
|
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
|
-
|
|
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 ?? '
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
207
|
-
*
|
|
271
|
+
* Returns a subscription handle; call `.remove()` to stop
|
|
272
|
+
* receiving events.
|
|
208
273
|
*/
|
|
209
|
-
static
|
|
210
|
-
|
|
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
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
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
|
|
231
|
-
UseSenseModule.
|
|
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
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
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
|
|
247
|
-
return UseSenseModule.
|
|
304
|
+
static async isInitialized(): Promise<boolean> {
|
|
305
|
+
return UseSenseModule.isInitialized();
|
|
248
306
|
}
|
|
249
307
|
}
|
|
250
308
|
|