react-native-usesense 1.0.0 → 2.1.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/UseSenseFlowsModule.kt +72 -0
- package/android/src/main/java/com/usesense/reactnative/UseSenseModule.kt +82 -1
- package/android/src/main/java/com/usesense/reactnative/UseSensePackage.kt +6 -1
- package/ios/UseSenseFlowsModule.m +20 -0
- package/ios/UseSenseFlowsModule.swift +111 -0
- package/ios/UseSenseModule.m +5 -0
- package/ios/UseSenseModule.swift +277 -88
- package/ios/V4BridgeDelegate.swift +62 -0
- package/lib/commonjs/flows.js +105 -0
- package/lib/commonjs/flows.js.map +1 -0
- package/lib/commonjs/index.js +171 -98
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/flows.js +99 -0
- package/lib/module/flows.js.map +1 -0
- package/lib/module/index.js +160 -97
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/flows.d.ts +72 -0
- package/lib/typescript/src/flows.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +210 -138
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +3 -1
- package/react-native-usesense.podspec +9 -3
- package/src/flows.ts +159 -0
- package/src/index.ts +313 -159
|
@@ -1,196 +1,268 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Target environment for the UseSense API. `auto` asks the native SDK
|
|
3
|
+
* to detect from the API key prefix (`sk_sandbox_*` → sandbox,
|
|
4
|
+
* `sk_prod_*` → production).
|
|
5
|
+
*/
|
|
6
|
+
export type UseSenseEnvironment = 'sandbox' | 'production' | 'auto';
|
|
7
|
+
/** Type of verification session. */
|
|
4
8
|
export type SessionType = 'enrollment' | 'authentication';
|
|
5
|
-
/** Challenge policy controlling verification difficulty. */
|
|
6
|
-
export type ChallengePolicy = 'standard' | 'enhanced' | 'adaptive';
|
|
7
|
-
/** Decision returned by the verification engine. */
|
|
8
|
-
export type UseSenseDecision = 'approved' | 'rejected' | 'manual_review';
|
|
9
|
-
/** Processing stage during server-side analysis. */
|
|
10
|
-
export type ProcessingStage = 'deepsense' | 'livesense' | 'matchsense' | 'fusion';
|
|
11
9
|
/**
|
|
12
|
-
*
|
|
10
|
+
* Decision returned by the native SDK.
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
* These values come straight from the server's verdict and are
|
|
13
|
+
* exposed to the integrator for UI feedback only. The definitive
|
|
14
|
+
* verdict arrives at your backend via an HMAC-signed webhook; never
|
|
15
|
+
* trust the client-side decision for access-control.
|
|
16
|
+
*/
|
|
17
|
+
export type Decision = 'APPROVE' | 'REJECT' | 'MANUAL_REVIEW';
|
|
18
|
+
/**
|
|
19
|
+
* Branding overrides applied to the native camera UI.
|
|
20
|
+
*
|
|
21
|
+
* - `logoUrl`, `primaryColor`, `buttonRadius`, `fontFamily` are
|
|
22
|
+
* honoured on both iOS and Android.
|
|
23
|
+
* - `displayName`, `redirectUrl` are Android-only fields. They're
|
|
24
|
+
* accepted on iOS for cross-platform API parity but ignored.
|
|
25
|
+
*/
|
|
26
|
+
export interface BrandingConfig {
|
|
27
|
+
logoUrl?: string;
|
|
28
|
+
primaryColor?: string;
|
|
29
|
+
buttonRadius?: number;
|
|
30
|
+
fontFamily?: string;
|
|
31
|
+
displayName?: string;
|
|
32
|
+
redirectUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Configuration for `UseSense.initialize`. The minimum required
|
|
36
|
+
* field is the API key; every other field has a sensible default.
|
|
21
37
|
*/
|
|
22
38
|
export interface UseSenseConfig {
|
|
23
|
-
/** API key from
|
|
39
|
+
/** API key from watchtower.usesense.ai. */
|
|
24
40
|
apiKey: string;
|
|
25
|
-
/** Target environment. Defaults to `
|
|
41
|
+
/** Target environment. Defaults to `auto` (detected from key prefix). */
|
|
26
42
|
environment?: UseSenseEnvironment;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Override the API endpoint. Defaults to `https://api.usesense.ai/v1`.
|
|
45
|
+
* Only set this if you're pointing at a staging or self-hosted
|
|
46
|
+
* proxy. Maps to `apiEndpoint` on iOS and `baseUrl` on Android
|
|
47
|
+
* under the hood.
|
|
48
|
+
*/
|
|
49
|
+
apiEndpoint?: string;
|
|
50
|
+
/** SDK-level branding overrides. */
|
|
51
|
+
branding?: BrandingConfig;
|
|
52
|
+
/**
|
|
53
|
+
* Opt in to the on-device antispoof classifier (v4.2). When enabled the
|
|
54
|
+
* native layer runs the bundled CelebA-Spoof model during capture and
|
|
55
|
+
* attaches per-frame spoof probabilities to the uploaded metadata. When
|
|
56
|
+
* disabled (default), the watchtower backend runs the classifier server-side.
|
|
57
|
+
* See `docs/sdk-specs/antispoof-classifier-sdk-spec.md` in the watchtower
|
|
58
|
+
* repo for the rollout plan and crosscheck policy.
|
|
59
|
+
*/
|
|
60
|
+
antispoofOnDeviceEnabled?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Opt the session into the LiveSense v4 capture flow. When true the native
|
|
63
|
+
* layer:
|
|
64
|
+
* - sends `x-usesense-sdk-version: v4` on session creation
|
|
65
|
+
* - inserts a constitutive zoom-motion phase between baseline and the
|
|
66
|
+
* active challenge (additive, not a replacement)
|
|
67
|
+
* - tags every captured frame with its capture phase
|
|
68
|
+
* The org must also have `livesense_v4_enabled` set in its features map
|
|
69
|
+
* server-side.
|
|
70
|
+
*/
|
|
71
|
+
liveSenseV4Enabled?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Request to start a verification session.
|
|
75
|
+
*/
|
|
76
|
+
export interface VerificationRequest {
|
|
77
|
+
sessionType: SessionType;
|
|
78
|
+
/** Your internal user identifier (optional, stored in session metadata). */
|
|
79
|
+
externalUserId?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Required for `authentication` sessions. The previously-enrolled
|
|
82
|
+
* identity you're verifying against.
|
|
83
|
+
*/
|
|
32
84
|
identityId?: string;
|
|
33
|
-
/** Challenge difficulty policy. Defaults to `'standard'`. */
|
|
34
|
-
challengePolicy?: ChallengePolicy;
|
|
35
|
-
/** Enable audio capture for voice deepfake detection. Defaults to `false`. */
|
|
36
|
-
enableAudio?: boolean;
|
|
37
|
-
/** Maximum session duration in milliseconds. Defaults to `60000`. */
|
|
38
|
-
timeout?: number;
|
|
39
85
|
/** Custom key-value pairs attached to the session. */
|
|
40
|
-
metadata?: Record<string, string>;
|
|
86
|
+
metadata?: Record<string, string | number | boolean>;
|
|
41
87
|
}
|
|
42
88
|
/**
|
|
43
|
-
*
|
|
89
|
+
* Redacted decision object returned when a session completes.
|
|
44
90
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
91
|
+
* This is the ONLY shape the plugin exposes to React Native. Pillar
|
|
92
|
+
* scores (channel trust, liveness, matchsense risk) are intentionally
|
|
93
|
+
* not included — the native SDKs strip those fields before returning
|
|
94
|
+
* across the bridge to prevent reverse-engineering of the server-side
|
|
95
|
+
* scoring logic. If you need full scoring details, consume the signed
|
|
96
|
+
* webhook delivered to your backend.
|
|
47
97
|
*/
|
|
48
98
|
export interface UseSenseResult {
|
|
49
|
-
/** Unique session identifier. */
|
|
50
99
|
sessionId: string;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
/** Reasons contributing to the decision. */
|
|
62
|
-
reasons: string[];
|
|
63
|
-
/** Rule that triggered the decision, if any. */
|
|
64
|
-
ruleTriggered?: string;
|
|
65
|
-
/** Recommended action for the integrator. */
|
|
66
|
-
recommendedAction?: string;
|
|
67
|
-
/** Cryptographic signature for result verification. */
|
|
68
|
-
sessionSignature: string;
|
|
100
|
+
sessionType: string | null;
|
|
101
|
+
identityId: string | null;
|
|
102
|
+
decision: Decision;
|
|
103
|
+
timestamp: string;
|
|
104
|
+
/** Convenience: `decision === 'APPROVE'`. */
|
|
105
|
+
isApproved: boolean;
|
|
106
|
+
/** Convenience: `decision === 'REJECT'`. */
|
|
107
|
+
isRejected: boolean;
|
|
108
|
+
/** Convenience: `decision === 'MANUAL_REVIEW'`. */
|
|
109
|
+
isPendingReview: boolean;
|
|
69
110
|
}
|
|
70
111
|
/**
|
|
71
|
-
* Error
|
|
112
|
+
* Error thrown when a verification session fails.
|
|
113
|
+
*
|
|
114
|
+
* Matches the native SDKs' error shape: machine-readable code,
|
|
115
|
+
* human-readable message, retry hint.
|
|
72
116
|
*/
|
|
73
117
|
export interface UseSenseError {
|
|
74
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Machine-readable error code. One of
|
|
120
|
+
* `CAMERA_UNAVAILABLE`, `CAMERA_PERMISSION_DENIED`,
|
|
121
|
+
* `MIC_PERMISSION_DENIED`, `NETWORK_ERROR`, `NETWORK_TIMEOUT`,
|
|
122
|
+
* `SESSION_EXPIRED`, `UNAUTHORIZED`, `INVALID_TOKEN`,
|
|
123
|
+
* `SESSION_NOT_FOUND`, `IDENTITY_NOT_FOUND`, `INVALID_REQUEST`,
|
|
124
|
+
* `INVALID_CONFIG`, `QUOTA_EXCEEDED`, `USER_CANCELLED`,
|
|
125
|
+
* `session_cancelled`, `sdk_not_initialized`, `no_view_controller`,
|
|
126
|
+
* `CAPTURE_FAILED`, `ENCODING_FAILED`, `UPLOAD_FAILED`,
|
|
127
|
+
* `FACE_NOT_DETECTED`, `LOW_LIGHT`, `TIMEOUT`, `SERVER_ERROR`,
|
|
128
|
+
* `SERVICE_UNAVAILABLE`, `TOKEN_EXPIRED`, `TOKEN_ALREADY_USED`,
|
|
129
|
+
* `INSUFFICIENT_CREDITS`, `NONCE_MISMATCH`, `UNKNOWN_ERROR`.
|
|
130
|
+
*/
|
|
75
131
|
code: string;
|
|
76
132
|
/** Human-readable error message. */
|
|
77
133
|
message: string;
|
|
78
|
-
/**
|
|
79
|
-
|
|
134
|
+
/** Whether the operation can be retried. */
|
|
135
|
+
isRetryable?: boolean;
|
|
136
|
+
/** Server-specific error code, if any. */
|
|
137
|
+
serverCode?: string;
|
|
80
138
|
}
|
|
81
139
|
/**
|
|
82
|
-
*
|
|
140
|
+
* Lifecycle event types emitted by the native SDK during a
|
|
141
|
+
* verification session.
|
|
83
142
|
*/
|
|
84
|
-
export type
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
challengeType: string;
|
|
93
|
-
} | {
|
|
94
|
-
type: 'processing';
|
|
95
|
-
stage: ProcessingStage;
|
|
96
|
-
} | {
|
|
97
|
-
type: 'session_completed';
|
|
98
|
-
result: UseSenseResult;
|
|
99
|
-
} | {
|
|
100
|
-
type: 'session_error';
|
|
101
|
-
error: UseSenseError;
|
|
102
|
-
};
|
|
103
|
-
/** Options for starting a verification session. */
|
|
104
|
-
export interface StartSessionOptions {
|
|
105
|
-
/** Session type. Overrides the value set in {@link UseSenseConfig}. */
|
|
106
|
-
sessionType?: SessionType;
|
|
107
|
-
/** Identity ID for authentication sessions. */
|
|
108
|
-
identityId?: string;
|
|
143
|
+
export type UseSenseEventType = 'SESSION_CREATED' | 'PERMISSIONS_REQUESTED' | 'PERMISSIONS_GRANTED' | 'PERMISSIONS_DENIED' | 'CAPTURE_STARTED' | 'FRAME_CAPTURED' | 'CAPTURE_COMPLETED' | 'AUDIO_RECORD_STARTED' | 'AUDIO_RECORD_COMPLETED' | 'CHALLENGE_STARTED' | 'CHALLENGE_COMPLETED' | 'UPLOAD_STARTED' | 'UPLOAD_PROGRESS' | 'UPLOAD_COMPLETED' | 'COMPLETE_STARTED' | 'DECISION_RECEIVED' | 'IMAGE_QUALITY_CHECK' | 'ERROR' | 'UNKNOWN';
|
|
144
|
+
/** Single event emitted during a session. */
|
|
145
|
+
export interface UseSenseEvent {
|
|
146
|
+
type: UseSenseEventType;
|
|
147
|
+
/** Unix epoch milliseconds. */
|
|
148
|
+
timestamp: number;
|
|
149
|
+
/** Event-specific payload. Opaque strings; never contains scoring data. */
|
|
150
|
+
data?: Record<string, string>;
|
|
109
151
|
}
|
|
110
|
-
/**
|
|
111
|
-
export interface SessionStatus {
|
|
112
|
-
/** Current session status. */
|
|
113
|
-
status: string;
|
|
114
|
-
/** Result, if the session has completed. */
|
|
115
|
-
result?: UseSenseResult;
|
|
116
|
-
}
|
|
117
|
-
/** Subscription handle returned by {@link UseSense.addListener}. */
|
|
152
|
+
/** Handle returned by `UseSense.addListener`. */
|
|
118
153
|
export interface UseSenseSubscription {
|
|
119
|
-
|
|
120
|
-
remove: () => void;
|
|
154
|
+
remove(): void;
|
|
121
155
|
}
|
|
122
156
|
/**
|
|
123
157
|
* UseSense React Native plugin.
|
|
124
158
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
159
|
+
* Human presence verification wrapping the native iOS and Android
|
|
160
|
+
* UseSense SDKs. Every session runs inside a full-screen native
|
|
161
|
+
* camera activity; the plugin is a thin bridge that forwards
|
|
162
|
+
* initialization, verification requests, and lifecycle events to
|
|
163
|
+
* the platform SDK and returns a redacted decision object.
|
|
127
164
|
*
|
|
128
165
|
* @example
|
|
129
166
|
* ```ts
|
|
130
167
|
* import { UseSense } from 'react-native-usesense';
|
|
131
168
|
*
|
|
132
|
-
* await UseSense.initialize({
|
|
133
|
-
*
|
|
134
|
-
*
|
|
169
|
+
* await UseSense.initialize({
|
|
170
|
+
* apiKey: 'sk_sandbox_...',
|
|
171
|
+
* environment: 'sandbox',
|
|
172
|
+
* });
|
|
173
|
+
*
|
|
174
|
+
* const unsubscribe = UseSense.addListener((event) => {
|
|
175
|
+
* console.log(event.type, event.data);
|
|
176
|
+
* });
|
|
177
|
+
*
|
|
178
|
+
* try {
|
|
179
|
+
* const result = await UseSense.startVerification({
|
|
180
|
+
* sessionType: 'enrollment',
|
|
181
|
+
* });
|
|
182
|
+
* console.log(result.decision); // 'APPROVE' | 'REJECT' | 'MANUAL_REVIEW'
|
|
183
|
+
* } finally {
|
|
184
|
+
* unsubscribe.remove();
|
|
185
|
+
* }
|
|
135
186
|
* ```
|
|
136
187
|
*/
|
|
137
188
|
export declare class UseSense {
|
|
138
189
|
/**
|
|
139
|
-
* Initialize the UseSense plugin. Must be called
|
|
190
|
+
* Initialize the UseSense plugin. Must be called before any other
|
|
191
|
+
* method. Calling again with a different API key or environment
|
|
192
|
+
* replaces the previous configuration.
|
|
140
193
|
*
|
|
141
|
-
* @
|
|
142
|
-
*
|
|
194
|
+
* @throws {UseSenseError} If the API key is empty or initialization
|
|
195
|
+
* fails on the native side.
|
|
143
196
|
*/
|
|
144
197
|
static initialize(config: UseSenseConfig): Promise<void>;
|
|
145
198
|
/**
|
|
146
|
-
* Start a verification session. Presents a full-screen native
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* @param options - Optional overrides for session type and identity ID.
|
|
152
|
-
* @returns The verification result (for UI feedback only).
|
|
153
|
-
* @throws {UseSenseError} On failure or cancellation.
|
|
199
|
+
* Start a verification session. Presents a full-screen native
|
|
200
|
+
* camera activity. The returned promise resolves when the session
|
|
201
|
+
* completes or rejects with a `UseSenseError` (including the
|
|
202
|
+
* `USER_CANCELLED` / `session_cancelled` cases when the user
|
|
203
|
+
* dismisses the camera screen).
|
|
154
204
|
*/
|
|
155
|
-
static
|
|
205
|
+
static startVerification(request: VerificationRequest): Promise<UseSenseResult>;
|
|
156
206
|
/**
|
|
157
|
-
*
|
|
207
|
+
* Subscribe to real-time lifecycle events emitted by the native
|
|
208
|
+
* SDK during a verification session. Call this **before**
|
|
209
|
+
* `startVerification` so the subscription captures the earliest
|
|
210
|
+
* events (`SESSION_CREATED`, `PERMISSIONS_REQUESTED`).
|
|
158
211
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
212
|
+
* Returns a subscription handle; call `.remove()` to stop
|
|
213
|
+
* receiving events.
|
|
161
214
|
*/
|
|
162
|
-
static
|
|
215
|
+
static addListener(callback: (event: UseSenseEvent) => void): UseSenseSubscription;
|
|
163
216
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
217
|
+
* Clear all event listeners and release native resources. Does not
|
|
218
|
+
* need to be called explicitly during normal usage; the plugin
|
|
219
|
+
* cleans up on process teardown. Useful when you want to re-
|
|
220
|
+
* initialize against a different API key or during test teardown.
|
|
168
221
|
*/
|
|
169
|
-
static
|
|
222
|
+
static reset(): Promise<void>;
|
|
170
223
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* @param callback - Function invoked for each event.
|
|
176
|
-
* @returns A subscription handle with a `remove()` method.
|
|
177
|
-
*
|
|
178
|
-
* @example
|
|
179
|
-
* ```ts
|
|
180
|
-
* const listener = UseSense.addListener((event) => {
|
|
181
|
-
* console.log(event.type);
|
|
182
|
-
* });
|
|
183
|
-
* // Later:
|
|
184
|
-
* listener.remove();
|
|
185
|
-
* ```
|
|
224
|
+
* Whether the native plugin has an active `UseSense` client. Set
|
|
225
|
+
* to `true` after a successful `initialize` and `false` after
|
|
226
|
+
* `reset`. Useful for feature-flagging UI that should only show
|
|
227
|
+
* when the SDK is ready.
|
|
186
228
|
*/
|
|
187
|
-
static
|
|
229
|
+
static isInitialized(): Promise<boolean>;
|
|
188
230
|
/**
|
|
189
|
-
*
|
|
231
|
+
* Start a LiveSense v4 session.
|
|
190
232
|
*
|
|
191
|
-
*
|
|
233
|
+
* The session must already have been created on your backend and the
|
|
234
|
+
* session_token + nonce forwarded to the client. The native SDK drives
|
|
235
|
+
* the camera, signs the frame hash chain with the platform-attested
|
|
236
|
+
* key, uploads, and returns the opaque verdict.
|
|
192
237
|
*/
|
|
193
|
-
static
|
|
238
|
+
static startV4Verification(request: V4VerificationRequest): Promise<V4Verdict>;
|
|
239
|
+
}
|
|
240
|
+
export interface V4VerificationRequest {
|
|
241
|
+
sessionId: string;
|
|
242
|
+
sessionToken: string;
|
|
243
|
+
nonce: string;
|
|
244
|
+
apiBaseUrl: string;
|
|
245
|
+
environment?: UseSenseEnvironment;
|
|
246
|
+
displayName?: string;
|
|
247
|
+
brandPrimaryColor?: string;
|
|
248
|
+
}
|
|
249
|
+
export type V4Decision = 'pass' | 'fail' | 'review';
|
|
250
|
+
export type V4Confidence = 'high' | 'medium' | 'low';
|
|
251
|
+
export type V4AssuranceLevel = 'mobile_hardware' | 'web_attested' | 'web_unattested';
|
|
252
|
+
export type V4CaptureChannel = 'ios' | 'android' | 'rn' | 'flutter' | 'web';
|
|
253
|
+
/**
|
|
254
|
+
* Opaque verdict from POST /v1/sessions/:id/result. Matches the server
|
|
255
|
+
* contract exactly; no sub-scores leak through.
|
|
256
|
+
*/
|
|
257
|
+
export interface V4Verdict {
|
|
258
|
+
session_id: string;
|
|
259
|
+
verdict: V4Decision;
|
|
260
|
+
confidence: V4Confidence;
|
|
261
|
+
assurance_level_achieved: V4AssuranceLevel;
|
|
262
|
+
capture_channel: V4CaptureChannel;
|
|
263
|
+
match_sense_embedding_id: string | null;
|
|
264
|
+
timestamp: string;
|
|
194
265
|
}
|
|
195
266
|
export default UseSense;
|
|
267
|
+
export { UseSenseFlows, FlowError, type FlowErrorCode, type FlowOutcome, type FlowRunResult, type FlowRunState, type RunFlowOptions, } from './flows';
|
|
196
268
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-usesense",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "React Native plugin for human presence verification. Wraps the native UseSense iOS and Android SDKs.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"react-native": ">=0.73.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
+
"@types/jest": "^29.5.14",
|
|
63
64
|
"@types/react": "^18.2.0",
|
|
64
65
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
65
66
|
"@typescript-eslint/parser": "^7.0.0",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"react": "^18.2.0",
|
|
70
71
|
"react-native": "^0.73.0",
|
|
71
72
|
"react-native-builder-bob": "^0.23.0",
|
|
73
|
+
"ts-jest": "^29.4.11",
|
|
72
74
|
"typescript": "^5.3.0"
|
|
73
75
|
},
|
|
74
76
|
"codegenConfig": {
|
|
@@ -11,7 +11,9 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.authors = { "UseSense Technologies Ltd." => "support@usesense.ai" }
|
|
12
12
|
s.source = { :git => package["repository"]["url"], :tag => "v#{s.version}" }
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Minimum iOS 15.0 to match the native UseSense iOS SDK's floor
|
|
15
|
+
# (v4.x uses SwiftUI features that require iOS 15.0 and up).
|
|
16
|
+
s.platforms = { :ios => "15.0" }
|
|
15
17
|
s.swift_version = "5.9"
|
|
16
18
|
|
|
17
19
|
s.source_files = "ios/**/*.{swift,h,m}"
|
|
@@ -19,6 +21,10 @@ Pod::Spec.new do |s|
|
|
|
19
21
|
# React Native dependency (supports both old and new architecture)
|
|
20
22
|
install_modules_dependencies(s)
|
|
21
23
|
|
|
22
|
-
# Native UseSense iOS SDK
|
|
23
|
-
|
|
24
|
+
# Native UseSense iOS SDK. Minimum 4.2.2 — earlier 4.x releases had
|
|
25
|
+
# terminal-screen centering bugs, and the 1.x series used a pre-
|
|
26
|
+
# redaction result type with a completely different UseSenseConfig
|
|
27
|
+
# init signature. See CHANGELOG [2.0.0] for the full rewrite
|
|
28
|
+
# rationale.
|
|
29
|
+
s.dependency "UseSenseSDK", "~> 4.3"
|
|
24
30
|
end
|
package/src/flows.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native wrapper for the UseSense Flows runner.
|
|
3
|
+
*
|
|
4
|
+
* Coexists with the existing Sessions API (`UseSense.startSession`, etc.);
|
|
5
|
+
* this is a parallel surface, not a replacement. See `guides/flows/sessions-vs-flows`
|
|
6
|
+
* in the API docs for when to use which.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
*
|
|
10
|
+
* import { UseSenseFlows, FlowError } from 'react-native-usesense';
|
|
11
|
+
*
|
|
12
|
+
* try {
|
|
13
|
+
* const result = await UseSenseFlows.runFlow({ flowRunId, sdkToken });
|
|
14
|
+
* console.log(result.outcome); // 'APPROVE' | 'REJECT' | 'MANUAL_REVIEW' | null
|
|
15
|
+
* } catch (e) {
|
|
16
|
+
* if (e instanceof FlowError) {
|
|
17
|
+
* // e.code: 'token_expired' | 'token_invalid' | ...
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { NativeModules, Platform } from 'react-native';
|
|
23
|
+
|
|
24
|
+
const LINKING_ERROR =
|
|
25
|
+
`react-native-usesense (flows): the native module could not be found.\n\n` +
|
|
26
|
+
Platform.select({
|
|
27
|
+
ios: `iOS: run \`cd ios && pod install\` and rebuild the app.`,
|
|
28
|
+
android: `Android: rebuild the app after installing the package.`,
|
|
29
|
+
default: '',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Lazy lookup so this module doesn't throw at import time on platforms that
|
|
33
|
+
// haven't been re-linked yet. The Sessions module mirrors this pattern.
|
|
34
|
+
function nativeModule() {
|
|
35
|
+
const m = NativeModules.UseSenseFlowsModule;
|
|
36
|
+
if (!m) throw new Error(LINKING_ERROR);
|
|
37
|
+
return m as { runFlow: (flowRunId: string, sdkToken: string, apiBaseUrl: string | null) => Promise<NativeFlowRunResult> };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Server-driven flow run state. Host apps see only the terminal ones via
|
|
42
|
+
* `FlowRunResult`; intermediate states drive the runner internally.
|
|
43
|
+
*/
|
|
44
|
+
export type FlowRunState =
|
|
45
|
+
| 'pending'
|
|
46
|
+
| 'in_progress'
|
|
47
|
+
| 'stalled'
|
|
48
|
+
| 'awaiting_review'
|
|
49
|
+
| 'completed'
|
|
50
|
+
| 'errored'
|
|
51
|
+
| 'abandoned'
|
|
52
|
+
| 'cancelled';
|
|
53
|
+
|
|
54
|
+
/** Terminal outcome of a flow run. */
|
|
55
|
+
export type FlowOutcome = 'APPROVE' | 'REJECT' | 'MANUAL_REVIEW';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Uniform error taxonomy across every SDK. See `guides/flows/errors` in the
|
|
59
|
+
* API docs for per-code recovery patterns.
|
|
60
|
+
*/
|
|
61
|
+
export type FlowErrorCode =
|
|
62
|
+
| 'token_expired'
|
|
63
|
+
| 'token_invalid'
|
|
64
|
+
| 'network_unavailable'
|
|
65
|
+
| 'permission_denied'
|
|
66
|
+
| 'provider_unavailable'
|
|
67
|
+
| 'cancelled'
|
|
68
|
+
| 'unsupported_action'
|
|
69
|
+
/** Server form validation failed. The native runner handles this inline
|
|
70
|
+
* (per-field highlights) and never reports terminal — but if a host app
|
|
71
|
+
* drives advance() outside the runner, the 422 surfaces with this code. */
|
|
72
|
+
| 'invalid_input'
|
|
73
|
+
| 'unknown';
|
|
74
|
+
|
|
75
|
+
const FLOW_ERROR_CODES: readonly FlowErrorCode[] = [
|
|
76
|
+
'token_expired',
|
|
77
|
+
'token_invalid',
|
|
78
|
+
'network_unavailable',
|
|
79
|
+
'permission_denied',
|
|
80
|
+
'provider_unavailable',
|
|
81
|
+
'cancelled',
|
|
82
|
+
'unsupported_action',
|
|
83
|
+
'invalid_input',
|
|
84
|
+
'unknown',
|
|
85
|
+
] as const;
|
|
86
|
+
|
|
87
|
+
function normaliseCode(code: unknown): FlowErrorCode {
|
|
88
|
+
return typeof code === 'string' && (FLOW_ERROR_CODES as readonly string[]).includes(code)
|
|
89
|
+
? (code as FlowErrorCode)
|
|
90
|
+
: 'unknown';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Result returned to the host app's `runFlow` promise on terminal state. */
|
|
94
|
+
export interface FlowRunResult {
|
|
95
|
+
flowRunId: string;
|
|
96
|
+
state: FlowRunState;
|
|
97
|
+
outcome: FlowOutcome | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface NativeFlowRunResult {
|
|
101
|
+
flowRunId: string;
|
|
102
|
+
state: FlowRunState;
|
|
103
|
+
outcome: FlowOutcome | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Typed error thrown by `runFlow` when the native runner reports a failure.
|
|
108
|
+
* Mirrors the web / iOS / Android / Flutter SDKs so host apps catch one
|
|
109
|
+
* taxonomy regardless of platform.
|
|
110
|
+
*/
|
|
111
|
+
export class FlowError extends Error {
|
|
112
|
+
readonly code: FlowErrorCode;
|
|
113
|
+
constructor(code: FlowErrorCode, message: string) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.name = 'FlowError';
|
|
116
|
+
this.code = code;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Options for `UseSenseFlows.runFlow`. */
|
|
121
|
+
export interface RunFlowOptions {
|
|
122
|
+
flowRunId: string;
|
|
123
|
+
sdkToken: string;
|
|
124
|
+
/** Defaults to `https://api.usesense.ai`. */
|
|
125
|
+
apiBaseUrl?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Public entry point for the Flows runner. Sessions APIs are untouched;
|
|
130
|
+
* this is a parallel namespace.
|
|
131
|
+
*/
|
|
132
|
+
export const UseSenseFlows = {
|
|
133
|
+
/**
|
|
134
|
+
* Run an operator-authored Flow inside the host app. Resolves with a
|
|
135
|
+
* [FlowRunResult] when the run reaches a terminal state (completed,
|
|
136
|
+
* cancelled, errored, abandoned). Rejects with a [FlowError] on transport
|
|
137
|
+
* / token / unsupported-action faults.
|
|
138
|
+
*/
|
|
139
|
+
async runFlow(options: RunFlowOptions): Promise<FlowRunResult> {
|
|
140
|
+
try {
|
|
141
|
+
const native = await nativeModule().runFlow(
|
|
142
|
+
options.flowRunId,
|
|
143
|
+
options.sdkToken,
|
|
144
|
+
options.apiBaseUrl ?? null,
|
|
145
|
+
);
|
|
146
|
+
return {
|
|
147
|
+
flowRunId: native.flowRunId,
|
|
148
|
+
state: native.state,
|
|
149
|
+
outcome: native.outcome,
|
|
150
|
+
};
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// RN bridge errors are surfaced as `Error` with `.code` and `.message`
|
|
153
|
+
// properties when the native side calls `reject(code, message, null)`.
|
|
154
|
+
const code = normaliseCode((e as { code?: string }).code);
|
|
155
|
+
const message = (e as { message?: string }).message ?? 'Flow failed';
|
|
156
|
+
throw new FlowError(code, message);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
};
|