react-native-usesense 1.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/LICENSE +18 -0
- package/README.md +529 -0
- package/android/build.gradle +42 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/usesense/reactnative/UseSenseModule.kt +158 -0
- package/android/src/main/java/com/usesense/reactnative/UseSensePackage.kt +17 -0
- package/ios/UseSenseModule.m +25 -0
- package/ios/UseSenseModule.swift +148 -0
- package/lib/commonjs/index.js +179 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/index.js +172 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/src/index.d.ts +196 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +93 -0
- package/react-native-usesense.podspec +24 -0
- package/src/index.ts +251 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
UseSense SDK - Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UseSense Technologies Ltd. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary property of UseSense Technologies Ltd.
|
|
7
|
+
|
|
8
|
+
Permission is granted to use the Software solely in conjunction with the
|
|
9
|
+
UseSense platform and a valid API key. Redistribution, modification, or
|
|
10
|
+
reverse engineering of the Software is prohibited.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
13
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
14
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
15
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
16
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
17
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
18
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
# UseSense React Native
|
|
2
|
+
|
|
3
|
+
React Native plugin for human presence verification. Wraps the native UseSense iOS and Android SDKs into a single cross-platform package.
|
|
4
|
+
|
|
5
|
+
UseSense verifies that a real human — not a bot, deepfake, or replay attack — is behind the camera during identity verification sessions. Three independent pillars run in parallel on every session:
|
|
6
|
+
|
|
7
|
+
- **DeepSense** (Channel & Device Integrity): Platform attestation, runtime integrity, capture pipeline analysis. Produces a `channelTrustScore` (0-100).
|
|
8
|
+
- **LiveSense** (Multimodal Proof-of-Life): Facial dynamics, visual integrity, presentation attack detection, audio deepfake detection. Produces a `livenessScore` (0-100).
|
|
9
|
+
- **MatchSense** (Identity Collision Detection): 1:N face search (enrollment), 1:1 face verification (authentication), cross-identity risk scoring. Produces a `matchSenseRiskScore` (0-100).
|
|
10
|
+
|
|
11
|
+
A fused `presenceConfidence` score and a final `decision` (`approved` / `rejected` / `manual_review`) are returned. The definitive verdict is delivered via HMAC-SHA256 signed webhook to your backend. **The SDK result is for UI feedback only and must never be trusted for access-control decisions.**
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- React Native 0.73+ (New Architecture supported; Old Architecture via interop layer)
|
|
16
|
+
- iOS 14.0+
|
|
17
|
+
- Android API 24+ (Android 7.0)
|
|
18
|
+
- Xcode 15.0+
|
|
19
|
+
- Node.js 18+
|
|
20
|
+
- Device with front-facing camera (required on both platforms)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install react-native-usesense
|
|
26
|
+
# or
|
|
27
|
+
yarn add react-native-usesense
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### iOS Setup
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd ios && pod install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The native UseSense iOS SDK is installed automatically as a CocoaPod dependency.
|
|
37
|
+
|
|
38
|
+
Add required permissions to your `Info.plist`:
|
|
39
|
+
|
|
40
|
+
```xml
|
|
41
|
+
<key>NSCameraUsageDescription</key>
|
|
42
|
+
<string>UseSense needs camera access to verify your identity.</string>
|
|
43
|
+
|
|
44
|
+
<!-- Only if enableAudio is true -->
|
|
45
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
46
|
+
<string>UseSense needs microphone access for voice verification.</string>
|
|
47
|
+
|
|
48
|
+
<!-- Optional, improves DeepSense accuracy -->
|
|
49
|
+
<key>NSMotionUsageDescription</key>
|
|
50
|
+
<string>UseSense uses motion data to improve verification accuracy.</string>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Android Setup
|
|
54
|
+
|
|
55
|
+
The native UseSense Android SDK is pulled automatically via Maven Central.
|
|
56
|
+
|
|
57
|
+
Add required permissions to your `AndroidManifest.xml`:
|
|
58
|
+
|
|
59
|
+
```xml
|
|
60
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
61
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
62
|
+
<!-- Only if enableAudio is true -->
|
|
63
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
No additional Gradle configuration needed. Auto-linking handles registration.
|
|
67
|
+
|
|
68
|
+
### New Architecture
|
|
69
|
+
|
|
70
|
+
The plugin supports React Native's New Architecture (Turbo Modules) out of the box. If your project has the New Architecture enabled, the plugin uses the Turbo Module path automatically. No configuration needed.
|
|
71
|
+
|
|
72
|
+
For projects still on the Old Architecture (Bridge), the plugin includes an interop layer and works without changes.
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { UseSense } from 'react-native-usesense';
|
|
78
|
+
|
|
79
|
+
// 1. Initialize (once, e.g. in App.tsx or app entry)
|
|
80
|
+
await UseSense.initialize({
|
|
81
|
+
apiKey: 'your_sandbox_api_key',
|
|
82
|
+
environment: 'sandbox',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 2. Run a verification session
|
|
86
|
+
try {
|
|
87
|
+
const result = await UseSense.startSession({
|
|
88
|
+
sessionType: 'enrollment',
|
|
89
|
+
});
|
|
90
|
+
console.log('Decision:', result.decision);
|
|
91
|
+
console.log('Liveness:', result.livenessScore);
|
|
92
|
+
console.log('Channel Trust:', result.channelTrustScore);
|
|
93
|
+
console.log('MatchSense Risk:', result.matchSenseRiskScore);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('Verification failed:', error.code, error.message);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The definitive verdict arrives at your backend via webhook.
|
|
99
|
+
// The SDK result is for UI feedback only.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
Full reference for `UseSenseConfig`:
|
|
105
|
+
|
|
106
|
+
| Parameter | Type | Required | Default | Description |
|
|
107
|
+
|-----------|------|----------|---------|-------------|
|
|
108
|
+
| `apiKey` | `string` | Yes | — | Your API key from the [UseSense dashboard](https://app.usesense.ai) |
|
|
109
|
+
| `environment` | `'production' \| 'sandbox'` | No | `'sandbox'` | Target environment |
|
|
110
|
+
| `organizationId` | `string` | No | — | Organization ID (inferred from API key) |
|
|
111
|
+
| `sessionType` | `'enrollment' \| 'authentication'` | No | `'enrollment'` | Default session type |
|
|
112
|
+
| `identityId` | `string` | No | — | Identity ID for authentication sessions |
|
|
113
|
+
| `challengePolicy` | `'standard' \| 'enhanced' \| 'adaptive'` | No | `'standard'` | Challenge difficulty policy |
|
|
114
|
+
| `enableAudio` | `boolean` | No | `false` | Enable audio capture for voice deepfake detection |
|
|
115
|
+
| `timeout` | `number` | No | `60000` | Maximum session duration in milliseconds |
|
|
116
|
+
| `metadata` | `Record<string, string>` | No | — | Custom key-value pairs attached to session |
|
|
117
|
+
|
|
118
|
+
## Session Types
|
|
119
|
+
|
|
120
|
+
### Enrollment
|
|
121
|
+
|
|
122
|
+
First-time face registration. The system indexes the user's face, performs a 1:N duplicate scan via MatchSense, and creates an identity record if approved.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const result = await UseSense.startSession({
|
|
126
|
+
sessionType: 'enrollment',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (result.decision === 'approved') {
|
|
130
|
+
// Identity created. Store result.sessionId for reference.
|
|
131
|
+
console.log('Enrollment approved. Presence confidence:', result.presenceConfidence);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Authentication
|
|
136
|
+
|
|
137
|
+
Returning user claims an existing identity. The system performs 1:1 verification against the enrolled template, plus a 1:N cross-identity scan.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const result = await UseSense.startSession({
|
|
141
|
+
sessionType: 'authentication',
|
|
142
|
+
identityId: 'idn_abc123', // required for authentication
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (result.decision === 'approved') {
|
|
146
|
+
console.log('Identity verified');
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Handling Results
|
|
151
|
+
|
|
152
|
+
`UseSenseResult` contains the full verification breakdown:
|
|
153
|
+
|
|
154
|
+
| Field | Type | Description |
|
|
155
|
+
|-------|------|-------------|
|
|
156
|
+
| `sessionId` | `string` | Unique session identifier |
|
|
157
|
+
| `decision` | `'approved' \| 'rejected' \| 'manual_review'` | Verification decision |
|
|
158
|
+
| `channelTrustScore` | `number` | DeepSense channel integrity score (0-100). Higher is more trusted. |
|
|
159
|
+
| `livenessScore` | `number` | LiveSense liveness score (0-100). Higher means more likely live. |
|
|
160
|
+
| `matchSenseRiskScore` | `number` | MatchSense risk score (0-100). Lower is better. |
|
|
161
|
+
| `presenceConfidence` | `number` | Fused presence confidence (0-100). Higher is more confident. |
|
|
162
|
+
| `reasons` | `string[]` | Reasons contributing to the decision |
|
|
163
|
+
| `ruleTriggered` | `string?` | Rule that triggered the decision, if any |
|
|
164
|
+
| `recommendedAction` | `string?` | Recommended action for the integrator |
|
|
165
|
+
| `sessionSignature` | `string` | Cryptographic signature for result verification |
|
|
166
|
+
|
|
167
|
+
Handle each decision type in your component:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import React from 'react';
|
|
171
|
+
import { View, Text, Button } from 'react-native';
|
|
172
|
+
import { UseSense, UseSenseResult } from 'react-native-usesense';
|
|
173
|
+
|
|
174
|
+
function VerificationScreen({ navigation }) {
|
|
175
|
+
const handleVerify = async () => {
|
|
176
|
+
try {
|
|
177
|
+
const result: UseSenseResult = await UseSense.startSession({
|
|
178
|
+
sessionType: 'enrollment',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
switch (result.decision) {
|
|
182
|
+
case 'approved':
|
|
183
|
+
navigation.navigate('Home');
|
|
184
|
+
break;
|
|
185
|
+
case 'rejected':
|
|
186
|
+
navigation.navigate('Retry', { reasons: result.reasons });
|
|
187
|
+
break;
|
|
188
|
+
case 'manual_review':
|
|
189
|
+
navigation.navigate('PendingReview', { sessionId: result.sessionId });
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('Session failed:', error.code);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<View>
|
|
199
|
+
<Button title="Verify Identity" onPress={handleVerify} />
|
|
200
|
+
</View>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Event Listening
|
|
206
|
+
|
|
207
|
+
Subscribe to real-time events during the verification lifecycle. Call `addListener()` before `startSession()` to receive all events.
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { UseSense, UseSenseEvent } from 'react-native-usesense';
|
|
211
|
+
import { useEffect, useState } from 'react';
|
|
212
|
+
|
|
213
|
+
function VerificationScreen() {
|
|
214
|
+
const [events, setEvents] = useState<UseSenseEvent[]>([]);
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
const listener = UseSense.addListener((event) => {
|
|
218
|
+
setEvents(prev => [...prev, event]);
|
|
219
|
+
|
|
220
|
+
switch (event.type) {
|
|
221
|
+
case 'session_started':
|
|
222
|
+
console.log('Session:', event.sessionId);
|
|
223
|
+
break;
|
|
224
|
+
case 'challenge_presented':
|
|
225
|
+
console.log('Challenge:', event.challengeType);
|
|
226
|
+
break;
|
|
227
|
+
case 'challenge_completed':
|
|
228
|
+
console.log('Challenge completed:', event.challengeType);
|
|
229
|
+
break;
|
|
230
|
+
case 'processing':
|
|
231
|
+
console.log('Stage:', event.stage); // 'deepsense' | 'livesense' | 'matchsense' | 'fusion'
|
|
232
|
+
break;
|
|
233
|
+
case 'session_completed':
|
|
234
|
+
console.log('Result:', event.result.decision);
|
|
235
|
+
break;
|
|
236
|
+
case 'session_error':
|
|
237
|
+
console.error('Error:', event.error.code);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return () => listener.remove();
|
|
243
|
+
}, []);
|
|
244
|
+
|
|
245
|
+
// render events...
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Error Handling
|
|
250
|
+
|
|
251
|
+
All errors include a machine-readable `code` and a human-readable `message`.
|
|
252
|
+
|
|
253
|
+
| Code | Description | Recovery |
|
|
254
|
+
|------|-------------|----------|
|
|
255
|
+
| `camera_permission_denied` | Camera access not granted | Prompt user; link to device Settings via `Linking.openSettings()` |
|
|
256
|
+
| `microphone_permission_denied` | Microphone not granted (audio enabled) | Prompt or disable audio |
|
|
257
|
+
| `session_timeout` | Exceeded configured timeout | Retry with new session |
|
|
258
|
+
| `session_expired` | 15-minute server-side expiry | Start new session |
|
|
259
|
+
| `network_error` | API unreachable | Check connectivity, retry |
|
|
260
|
+
| `sdk_not_initialized` | `startSession()` before `initialize()` | Call `initialize()` at app startup |
|
|
261
|
+
| `invalid_config` | Missing or invalid config | Check `apiKey` and required fields |
|
|
262
|
+
| `invalid_api_key` | Key rejected by server | Verify in dashboard; check environment |
|
|
263
|
+
| `insufficient_credits` | Zero credit balance | Purchase credits in dashboard |
|
|
264
|
+
| `identity_not_found` | `identityId` doesn't exist | Verify identity was enrolled |
|
|
265
|
+
| `camera_unavailable` | No front camera found | Inform user |
|
|
266
|
+
| `device_not_supported` | Fails hardware requirements | Inform user |
|
|
267
|
+
| `session_cancelled` | Cancelled by user or programmatically | Handle gracefully |
|
|
268
|
+
| `play_integrity_unavailable` | (Android) Play Integrity API unavailable | DeepSense uses fallback signals |
|
|
269
|
+
| `server_error` | 5xx from UseSense API | Retry; contact support if persistent |
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { UseSense, UseSenseError } from 'react-native-usesense';
|
|
273
|
+
import { Alert, Linking } from 'react-native';
|
|
274
|
+
|
|
275
|
+
async function verify() {
|
|
276
|
+
try {
|
|
277
|
+
const result = await UseSense.startSession({ sessionType: 'enrollment' });
|
|
278
|
+
// handle result
|
|
279
|
+
} catch (error) {
|
|
280
|
+
const err = error as UseSenseError;
|
|
281
|
+
|
|
282
|
+
switch (err.code) {
|
|
283
|
+
case 'camera_permission_denied':
|
|
284
|
+
Alert.alert(
|
|
285
|
+
'Camera Required',
|
|
286
|
+
'UseSense needs camera access to verify your identity.',
|
|
287
|
+
[
|
|
288
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
289
|
+
{ text: 'Open Settings', onPress: () => Linking.openSettings() },
|
|
290
|
+
],
|
|
291
|
+
);
|
|
292
|
+
break;
|
|
293
|
+
|
|
294
|
+
case 'session_timeout':
|
|
295
|
+
case 'session_expired':
|
|
296
|
+
case 'network_error':
|
|
297
|
+
Alert.alert('Try Again', 'The session could not be completed. Please try again.');
|
|
298
|
+
break;
|
|
299
|
+
|
|
300
|
+
case 'insufficient_credits':
|
|
301
|
+
console.error('UseSense credit balance is zero');
|
|
302
|
+
Alert.alert('Service Unavailable', 'Verification is temporarily unavailable.');
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case 'session_cancelled':
|
|
306
|
+
// User cancelled -- no action needed
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
default:
|
|
310
|
+
Alert.alert('Error', err.message);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Server-Side Webhook Verification
|
|
318
|
+
|
|
319
|
+
**Never trust the SDK result for access-control decisions.** The SDK response is for UI feedback only — showing the user a success or failure screen. The definitive verdict arrives at your backend via an HMAC-SHA256 signed webhook.
|
|
320
|
+
|
|
321
|
+
### Webhook Payload
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"event": "session.completed",
|
|
326
|
+
"session_id": "ses_abc123",
|
|
327
|
+
"organization_id": "org_xyz",
|
|
328
|
+
"timestamp": "2026-03-12T10:30:00Z",
|
|
329
|
+
"data": {
|
|
330
|
+
"decision": "approved",
|
|
331
|
+
"channel_trust_score": 95,
|
|
332
|
+
"liveness_score": 92,
|
|
333
|
+
"matchsense_risk_score": 8,
|
|
334
|
+
"presence_confidence": 94,
|
|
335
|
+
"session_type": "enrollment",
|
|
336
|
+
"identity_id": "idn_def456",
|
|
337
|
+
"reasons": [],
|
|
338
|
+
"rule_triggered": null,
|
|
339
|
+
"session_signature": "sig_..."
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Signature Verification — Node.js (Express)
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
const crypto = require('crypto');
|
|
348
|
+
|
|
349
|
+
app.post('/webhooks/usesense', express.raw({ type: 'application/json' }), (req, res) => {
|
|
350
|
+
const signature = req.headers['x-usesense-signature'];
|
|
351
|
+
const timestamp = req.headers['x-usesense-timestamp'];
|
|
352
|
+
|
|
353
|
+
const payload = `${timestamp}.${req.body}`;
|
|
354
|
+
const expected = crypto
|
|
355
|
+
.createHmac('sha256', process.env.USESENSE_WEBHOOK_SECRET)
|
|
356
|
+
.update(payload)
|
|
357
|
+
.digest('hex');
|
|
358
|
+
|
|
359
|
+
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
|
|
360
|
+
return res.status(401).send('Invalid signature');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const event = JSON.parse(req.body);
|
|
364
|
+
|
|
365
|
+
if (event.event === 'session.completed') {
|
|
366
|
+
const { decision, session_id } = event.data;
|
|
367
|
+
// Update your database based on the definitive decision
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
res.status(200).send('OK');
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Signature Verification — Python (Flask)
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
import hmac
|
|
378
|
+
import hashlib
|
|
379
|
+
|
|
380
|
+
@app.route('/webhooks/usesense', methods=['POST'])
|
|
381
|
+
def usesense_webhook():
|
|
382
|
+
signature = request.headers.get('X-UseSense-Signature')
|
|
383
|
+
timestamp = request.headers.get('X-UseSense-Timestamp')
|
|
384
|
+
|
|
385
|
+
payload = f"{timestamp}.{request.data.decode()}"
|
|
386
|
+
expected = hmac.new(
|
|
387
|
+
USESENSE_WEBHOOK_SECRET.encode(),
|
|
388
|
+
payload.encode(),
|
|
389
|
+
hashlib.sha256
|
|
390
|
+
).hexdigest()
|
|
391
|
+
|
|
392
|
+
if not hmac.compare_digest(signature, expected):
|
|
393
|
+
return 'Invalid signature', 401
|
|
394
|
+
|
|
395
|
+
event = request.get_json()
|
|
396
|
+
|
|
397
|
+
if event['event'] == 'session.completed':
|
|
398
|
+
decision = event['data']['decision']
|
|
399
|
+
session_id = event['session_id']
|
|
400
|
+
# Update your database based on the definitive decision
|
|
401
|
+
|
|
402
|
+
return 'OK', 200
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Signature Verification — Go (net/http)
|
|
406
|
+
|
|
407
|
+
```go
|
|
408
|
+
func useSenseWebhook(w http.ResponseWriter, r *http.Request) {
|
|
409
|
+
signature := r.Header.Get("X-UseSense-Signature")
|
|
410
|
+
timestamp := r.Header.Get("X-UseSense-Timestamp")
|
|
411
|
+
|
|
412
|
+
body, err := io.ReadAll(r.Body)
|
|
413
|
+
if err != nil {
|
|
414
|
+
http.Error(w, "Bad request", http.StatusBadRequest)
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
payload := fmt.Sprintf("%s.%s", timestamp, string(body))
|
|
419
|
+
mac := hmac.New(sha256.New, []byte(os.Getenv("USESENSE_WEBHOOK_SECRET")))
|
|
420
|
+
mac.Write([]byte(payload))
|
|
421
|
+
expected := hex.EncodeToString(mac.Sum(nil))
|
|
422
|
+
|
|
423
|
+
if !hmac.Equal([]byte(signature), []byte(expected)) {
|
|
424
|
+
http.Error(w, "Invalid signature", http.StatusUnauthorized)
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
var event map[string]interface{}
|
|
429
|
+
json.Unmarshal(body, &event)
|
|
430
|
+
|
|
431
|
+
// Process the event
|
|
432
|
+
w.WriteHeader(http.StatusOK)
|
|
433
|
+
w.Write([]byte("OK"))
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Sandbox vs Production
|
|
438
|
+
|
|
439
|
+
| | Sandbox | Production |
|
|
440
|
+
|---|---------|-----------|
|
|
441
|
+
| Cost | Free, unlimited | Credit-based |
|
|
442
|
+
| API keys | Separate | Separate |
|
|
443
|
+
| Features | All features identical | All features identical |
|
|
444
|
+
| Deduplication | Elevated risk scores (shared face collection) | Isolated per organization |
|
|
445
|
+
|
|
446
|
+
Switch by changing `environment` in config:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
await UseSense.initialize({
|
|
450
|
+
apiKey: process.env.USESENSE_API_KEY,
|
|
451
|
+
environment: __DEV__ ? 'sandbox' : 'production',
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Troubleshooting
|
|
456
|
+
|
|
457
|
+
| Problem | Solution |
|
|
458
|
+
|---------|----------|
|
|
459
|
+
| `Module 'react-native-usesense' not found` | Run `pod install` for iOS; rebuild for Android; verify auto-linking |
|
|
460
|
+
| `Camera permission denied on iOS` | Add `NSCameraUsageDescription` to `Info.plist` |
|
|
461
|
+
| `Build fails on iOS with 'pod not found'` | Run `pod repo update` then `pod install` |
|
|
462
|
+
| `Build fails on Android with dependency resolution error` | Ensure `mavenCentral()` is in your project-level `repositories` |
|
|
463
|
+
| `Session always times out` | Check network connectivity; increase `timeout` in config |
|
|
464
|
+
| `Deduplication always returns high risk on sandbox` | Expected — sandbox uses a shared face collection |
|
|
465
|
+
| `Events not firing` | Ensure `addListener()` is called before `startSession()` |
|
|
466
|
+
| `New Architecture errors` | Ensure codegen ran: `cd android && ./gradlew generateCodegenArtifactsFromSchema` |
|
|
467
|
+
| `App crashes on Android with TransactionTooLargeException` | Update to latest plugin version |
|
|
468
|
+
|
|
469
|
+
## TypeScript Support
|
|
470
|
+
|
|
471
|
+
The plugin ships with full TypeScript declarations. All types are exported from the main entry point:
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
import {
|
|
475
|
+
UseSense,
|
|
476
|
+
UseSenseConfig,
|
|
477
|
+
UseSenseResult,
|
|
478
|
+
UseSenseError,
|
|
479
|
+
UseSenseEvent,
|
|
480
|
+
UseSenseDecision,
|
|
481
|
+
UseSenseEnvironment,
|
|
482
|
+
SessionType,
|
|
483
|
+
ChallengePolicy,
|
|
484
|
+
ProcessingStage,
|
|
485
|
+
StartSessionOptions,
|
|
486
|
+
SessionStatus,
|
|
487
|
+
UseSenseSubscription,
|
|
488
|
+
} from 'react-native-usesense';
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## API Reference
|
|
492
|
+
|
|
493
|
+
### `UseSense.initialize(config: UseSenseConfig): Promise<void>`
|
|
494
|
+
|
|
495
|
+
Initialize the plugin. Must be called once before any other method.
|
|
496
|
+
|
|
497
|
+
### `UseSense.startSession(options?: StartSessionOptions): Promise<UseSenseResult>`
|
|
498
|
+
|
|
499
|
+
Start a verification session. Presents a full-screen native camera UI.
|
|
500
|
+
|
|
501
|
+
### `UseSense.cancelSession(): Promise<void>`
|
|
502
|
+
|
|
503
|
+
Cancel an in-progress verification session.
|
|
504
|
+
|
|
505
|
+
### `UseSense.getSessionStatus(sessionId: string): Promise<SessionStatus>`
|
|
506
|
+
|
|
507
|
+
Get the status of a verification session by ID.
|
|
508
|
+
|
|
509
|
+
### `UseSense.addListener(callback: (event: UseSenseEvent) => void): UseSenseSubscription`
|
|
510
|
+
|
|
511
|
+
Subscribe to real-time events during the verification lifecycle.
|
|
512
|
+
|
|
513
|
+
### `UseSense.getSdkVersion(): string`
|
|
514
|
+
|
|
515
|
+
Get the native SDK version string.
|
|
516
|
+
|
|
517
|
+
## Changelog
|
|
518
|
+
|
|
519
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
|
520
|
+
|
|
521
|
+
## License
|
|
522
|
+
|
|
523
|
+
Proprietary. See [LICENSE](./LICENSE) file.
|
|
524
|
+
|
|
525
|
+
## Support
|
|
526
|
+
|
|
527
|
+
- Documentation: [https://watch.usesense.ai/developer-docs](https://watchtower.usesense.ai/developer-docs)
|
|
528
|
+
- Dashboard: [https://watchtower.usesense.ai](https://watchtower.usesense.ai)
|
|
529
|
+
- Email: [support@usesense.ai](mailto:support@usesense.ai)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.safeExtGet = {prop, fallback ->
|
|
3
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
apply plugin: 'com.android.library'
|
|
8
|
+
apply plugin: 'kotlin-android'
|
|
9
|
+
|
|
10
|
+
android {
|
|
11
|
+
namespace "com.usesense.reactnative"
|
|
12
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 34)
|
|
13
|
+
|
|
14
|
+
defaultConfig {
|
|
15
|
+
minSdkVersion safeExtGet('minSdkVersion', 24)
|
|
16
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 34)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
compileOptions {
|
|
20
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
21
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
kotlinOptions {
|
|
25
|
+
jvmTarget = "17"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
repositories {
|
|
30
|
+
mavenCentral()
|
|
31
|
+
mavenLocal()
|
|
32
|
+
google()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
dependencies {
|
|
36
|
+
implementation "com.facebook.react:react-native:+"
|
|
37
|
+
|
|
38
|
+
// UseSense Android SDK
|
|
39
|
+
implementation "ai.usesense:sdk:1.0.0"
|
|
40
|
+
|
|
41
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '2.0.0')}"
|
|
42
|
+
}
|