@smileid/usesmileid 12.2.1-SNAPSHOT.44 → 12.3.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/CHANGELOG.md +5 -0
- package/README.md +19 -13
- package/dist/camera/camera_defaults.d.ts +0 -2
- package/dist/camera/camera_defaults.js +1 -1
- package/dist/core/models/params.d.ts +8 -6
- package/dist/data/api/interceptors/smile_auth_interceptor.js +1 -1
- package/dist/data/api/smile_id_api.js +1 -1
- package/dist/data/model/token_payload.d.ts +5 -7
- package/dist/data/model/token_payload.js +1 -1
- package/dist/data/model/user_details_payload.d.ts +6 -4
- package/dist/metadata/metadata_configuration_mappers.js +1 -1
- package/dist/metadata/use_smile_id_metadata_factory.js +1 -1
- package/dist/presentation/components/helpers/document_interior_crop.d.ts +13 -2
- package/dist/presentation/components/helpers/document_interior_crop.js +1 -1
- package/dist/presentation/components/use_smile_id_captured_document_image.d.ts +8 -4
- package/dist/presentation/components/use_smile_id_captured_document_image.js +1 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts +0 -9
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js +1 -1
- package/dist/presentation/flow/flow_navigation_manager_job_request.js +1 -1
- package/dist/presentation/flow/screen/helpers/failure_message.js +1 -1
- package/dist/presentation/flow/validation/flow_validator.d.ts +0 -19
- package/dist/presentation/flow/validation/flow_validator.js +1 -1
- package/dist/presentation/flow/validation/job_type_validator.js +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.js +1 -1
- package/package.json +6 -6
- package/dist/presentation/flow/validation/user_details_validator.d.ts +0 -8
- package/dist/presentation/flow/validation/user_details_validator.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
Full release notes: [Smile ID docs](https://docs.usesmileid.com/developer-resources/sdks/mobile/release-notes/react-native-expo).
|
|
2
2
|
|
|
3
|
+
## 12.3.0
|
|
4
|
+
|
|
5
|
+
See the repository CHANGELOG for the 12.3.0 release notes.
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
## 12.2.0
|
|
4
9
|
|
|
5
10
|
See the repository CHANGELOG for the 12.2.0 release notes.
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Sizes are measured on every push to `main` and updated automatically by CI.
|
|
|
32
32
|
- Xcode (latest stable) — iOS 16.4 deployment target (set by Expo SDK 56's modules; the Smile ID iOS SDK itself supports iOS 15)
|
|
33
33
|
- Android Gradle Plugin 8.11.1+ — `compileSdk` 36 or newer (React Native's default; nothing to set) and `minSdk` 24, matching the native Smile ID SDK and the kamera engine. The ML Kit config plugins set the Kotlin and KSP versions the AARs were compiled with. The 12.0.0–12.0.2 AARs required compileSdk 37 and `minSdk` 26 instead; on those releases, install platform 37 before Gradle runs (on EAS, an `eas-build-pre-install` script: `yes | sdkmanager --licenses >/dev/null; sdkmanager "platforms;android-37.0"`).
|
|
34
34
|
|
|
35
|
-
`12.
|
|
35
|
+
`12.3.0` is built and verified against Expo SDK 57 and React Native 0.86. The floors above are the supported minimum and keep working.
|
|
36
36
|
|
|
37
37
|
## Installation
|
|
38
38
|
|
|
@@ -53,7 +53,7 @@ npx expo install expo-brightness expo-file-system expo-font expo-haptics \
|
|
|
53
53
|
expo-image-manipulator expo-image-picker expo-keep-awake expo-screen-orientation
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
(
|
|
56
|
+
(For Android phones without Google Play services, use `@smileid/usesmileid_huawei_{face,document}` instead of the ML Kit packages in the build you ship to those phones, such as your AppGallery build. Your Google Play build keeps ML Kit.)
|
|
57
57
|
|
|
58
58
|
Everything above except the `@smileid/*` packages is a **required peer dependency**, and must be installed in your app directly: React Native autolinking only links native modules declared in the app's own `package.json`, not a library's transitive ones. `expo`, `expo-modules-core`, `react` and `react-native` are omitted because they ship with your app.
|
|
59
59
|
|
|
@@ -124,11 +124,17 @@ export default function VerificationScreen() {
|
|
|
124
124
|
b.onResult = (result) => {
|
|
125
125
|
switch (result.status) {
|
|
126
126
|
case "success":
|
|
127
|
-
console.log("
|
|
127
|
+
console.log("Submitted job", result.value.jobId);
|
|
128
128
|
break;
|
|
129
129
|
case "failure":
|
|
130
130
|
console.error("Error:", result.error);
|
|
131
131
|
break;
|
|
132
|
+
case "cancelled":
|
|
133
|
+
console.log("The user left the flow");
|
|
134
|
+
break;
|
|
135
|
+
case "captured":
|
|
136
|
+
console.log("Captured", result.job.jobType);
|
|
137
|
+
break;
|
|
132
138
|
}
|
|
133
139
|
};
|
|
134
140
|
}}
|
|
@@ -446,13 +452,13 @@ function ExitButton() {
|
|
|
446
452
|
|
|
447
453
|
The hook returns the active `FlowNavigationManager`:
|
|
448
454
|
|
|
449
|
-
| Member | Behaviour
|
|
450
|
-
| ------------------- |
|
|
451
|
-
| `navigateToNext()` | Advance to the next screen. From the last step, calls `onResult({ status: 'success', ... })`.
|
|
452
|
-
| `navigateBack()` | Pop the active screen. From the first step, calls `onResult({ status: '
|
|
453
|
-
| `navigateToStart()` | Reset the path to the first screen — a flow restart after a terminal failure.
|
|
454
|
-
| `exit(reason?)` | Exit immediately — useful for "abort" buttons. Defaults to `cancelled`.
|
|
455
|
-
| `captured` | Snapshot of artifacts collected so far (`selfiePath`, `livenessPaths`, `frontDocumentPath`, etc).
|
|
455
|
+
| Member | Behaviour |
|
|
456
|
+
| ------------------- | ------------------------------------------------------------------------------------------------- |
|
|
457
|
+
| `navigateToNext()` | Advance to the next screen. From the last step, calls `onResult({ status: 'success', ... })`. |
|
|
458
|
+
| `navigateBack()` | Pop the active screen. From the first step, calls `onResult({ status: 'cancelled' })`. |
|
|
459
|
+
| `navigateToStart()` | Reset the path to the first screen — a flow restart after a terminal failure. |
|
|
460
|
+
| `exit(reason?)` | Exit immediately — useful for "abort" buttons. Defaults to `cancelled`. |
|
|
461
|
+
| `captured` | Snapshot of artifacts collected so far (`selfiePath`, `livenessPaths`, `frontDocumentPath`, etc). |
|
|
456
462
|
|
|
457
463
|
Calling `useFlowNavigation()` outside `<UseSmileIDBuilder>` throws — the misuse is loud at first render rather than silently no-op-ing.
|
|
458
464
|
|
|
@@ -469,7 +475,7 @@ Calling `useFlowNavigation()` outside `<UseSmileIDBuilder>` throws — the misus
|
|
|
469
475
|
|
|
470
476
|
## Job-specific parameters
|
|
471
477
|
|
|
472
|
-
Each job type that needs identity fields reads them from a top-level builder property. The shared `user_details` part (`b.userDetails`) is
|
|
478
|
+
Each job type that needs identity fields reads them from a top-level builder property. The shared `user_details` part (`b.userDetails`) is optional: the SDK checks none of its fields, and the API reports any a job needs. `country` is the ISO 3166-1 alpha-2 code.
|
|
473
479
|
|
|
474
480
|
| Job type | Builder property | Required fields | Notes |
|
|
475
481
|
| --------------------------------------- | --------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -507,7 +513,7 @@ b.userDetails = {
|
|
|
507
513
|
|
|
508
514
|
`DocumentSide` (exported from `@smileid/usesmileid`) gains `Visa` (`"visa"`) for the residency visa page, so an exhaustive `switch` over it needs the new case.
|
|
509
515
|
|
|
510
|
-
|
|
516
|
+
Pre-flight any payload with `b.validateDocumentVerificationParams(...)` or `b.validateEnhancedDocumentVerificationParams(...)` before assigning.
|
|
511
517
|
|
|
512
518
|
---
|
|
513
519
|
|
|
@@ -557,7 +563,7 @@ A `"captured"` result carries a `CapturedJob`:
|
|
|
557
563
|
|
|
558
564
|
To forward the job, send the job type's `/v3` request with `headers`, the images as their usual form parts, and the three package fields. The package holds everything else the SDK would have sent (the consent record, the metadata and the request signature), encrypted with AES-256-GCM for Smile ID. Forward it as is: the server rejects a package that was changed. Forward the image files byte-for-byte too: they carry an integrity seal that re-encoding invalidates. A document imported from the gallery or taken with the manual shutter is kept in the app's cache, which the OS may clear under storage pressure, so forward such a job promptly.
|
|
559
565
|
|
|
560
|
-
Send each file under the form part the SDK would have used: `selfieImage` as `selfie_image`, `livenessImages` as `liveness_images` in the list's order, `frontDocument` as `document`, `backDocument` as `document_back` and `visaDocument` as `visa`. The request signature in the package covers the images by part name and order, so the server rejects a job sent any other way. Forward the job
|
|
566
|
+
Send each file under the form part the SDK would have used: `selfieImage` as `selfie_image`, `livenessImages` as `liveness_images` in the list's order, `frontDocument` as `document`, `backDocument` as `document_back` and `visaDocument` as `visa`. The request signature in the package covers the images by part name and order, so the server rejects a job sent any other way. Forward the job before its access token expires: `headers` carries the token from the capture, and the server rejects the job once that token has expired.
|
|
561
567
|
|
|
562
568
|
On iOS the system can move the app's data container between launches, for example across an app update, so don't save these absolute URIs. To forward a job in a later launch, save each path relative to the container and rebuild it then.
|
|
563
569
|
|
|
@@ -16,8 +16,6 @@ export declare const CameraDefaults: {
|
|
|
16
16
|
readonly width: 1280;
|
|
17
17
|
readonly height: 720;
|
|
18
18
|
};
|
|
19
|
-
/** How long a streaming capture waits for its first analyzed frame before reporting the camera unavailable. */
|
|
20
|
-
readonly firstFrameTimeoutMs: 10000;
|
|
21
19
|
/** Longest-edge ceiling (px) for an imported document image; downscale-only. */
|
|
22
20
|
readonly documentMaxDimension: 1600;
|
|
23
21
|
/** JPEG quality (0–1) for imported document images. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export const CameraDefaults={focusRunningDurationMs:600,focusSettledDurationMs:500,analysisTargetFps:30,analysisTargetResolution:{width:640,height:480},documentAnalysisResolution:{width:1280,height:720},
|
|
1
|
+
"use strict";export const CameraDefaults={focusRunningDurationMs:600,focusSettledDurationMs:500,analysisTargetFps:30,analysisTargetResolution:{width:640,height:480},documentAnalysisResolution:{width:1280,height:720},documentMaxDimension:1600,documentJpegQuality:.85,minGalleryDocumentDimensionPx:600,selfieJpegTargetWidth:480,selfieJpegTargetHeight:640};
|
|
@@ -16,17 +16,19 @@ export type ConsentInformation = {
|
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* Subject's personal details, sent as the `user_details` JSON part.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* The SDK checks none of these fields: it submits them as given, empty
|
|
20
|
+
* included, and the API rejects a job that lacks one it needs, naming the
|
|
21
|
+
* field. Which fields a job needs is the API's call, per partner and
|
|
22
|
+
* product: `GET /v3/services/config` lists them.
|
|
21
23
|
*/
|
|
22
24
|
export type UserDetails = {
|
|
23
|
-
/** Subject's given / first names.
|
|
25
|
+
/** Subject's given / first names. */
|
|
24
26
|
readonly givenNames: string;
|
|
25
|
-
/** Subject's family / last name.
|
|
27
|
+
/** Subject's family / last name. */
|
|
26
28
|
readonly lastName: string;
|
|
27
|
-
/**
|
|
29
|
+
/** Email address. */
|
|
28
30
|
readonly email?: string;
|
|
29
|
-
/**
|
|
31
|
+
/** Phone number in international format (leading `+`). */
|
|
30
32
|
readonly phoneNumber?: string;
|
|
31
33
|
};
|
|
32
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{sourceSdkIdentifier as t}from"@smileid/usesmileid_bridge";import{currentRequestTimestamp as s}from"../../security/request_timestamp";const n="12.
|
|
1
|
+
"use strict";import{sourceSdkIdentifier as t}from"@smileid/usesmileid_bridge";import{currentRequestTimestamp as s}from"../../security/request_timestamp";const n="12.3.0";export class SmileAuthInterceptor{_partnerConfig;_tokenHolder;constructor(e){this._partnerConfig=e.partnerConfig,this._tokenHolder=e.tokenHolder}attach(e){return{request:e.interceptors.request.use(async r=>this._onRequest(r))}}async _onRequest(e){return e.headers.set("smileid-token",this._tokenHolder.current),e.headers.set("smileid-source-sdk",t()),e.headers.set("smileid-source-sdk-version",n),e.headers.set("smileid-partner-id",this._partnerConfig.partnerId),e.headers.set("smileid-request-timestamp",s()),e}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import u from"axios";import{SmileIDUrls as d}from"../smile_id_urls";import{userDetailsPayloadToWire as g}from"../model/user_details_payload";import{SmileAuthInterceptor as h}from"./interceptors/smile_auth_interceptor";import{attachLoggingInterceptor as y}from"./interceptors/logging_interceptor";import{REQUEST_MAC_OPT_IN_KEY as b}from"./interceptors/smile_request_mac_interceptor";import{ATTESTATION_OPT_IN_KEY as D}from"./interceptors/smile_integrity_interceptor";import{NetworkResponseParseException as m}from"../../core/exception";import{LivenessFrameCountException as S}from"../../core/exception";const l=d.production,_=d.sandbox,C={[b]:!0,[D]:!0};export class SmileIDAPI{_client;_partnerConfig;constructor(t,n){this._client=t,this._partnerConfig=n}async smartSelfieEnrollment(t){const n=this._buildSelfieFormData(t);return r(n,t),this._appendPartnerConfigParts(n),this._appendConsentAndMetadata(n,t.consent,t.metadata),this._postMultipart("/v3/registration",n)}async smartSelfieAuthentication(t){const n=this._buildSelfieFormData(t);return i(n,"user_id",t.userId),r(n,t),this._appendPartnerConfigParts(n),this._appendConsentAndMetadata(n,t.consent,t.metadata),this._postMultipart("/v3/authentication",n)}async enhancedKyc(t){const n=new FormData;return this._appendIdFields(n,t.params),r(n,t),t.params.bankCode&&i(n,"bank_code",t.params.bankCode),t.params.mobileOperator&&i(n,"operator",t.params.mobileOperator),this._appendPartnerConfigParts(n),this._appendConsentAndMetadata(n,t.consent,t.metadata),this._postMultipart("/v3/enhanced_kyc",n)}async biometricKyc(t){const n=t.params.useEnrolledImage,e=this._buildSelfieFormData(t);return this._appendIdFields(e,t.params),r(e,t),n&&i(e,"use_enrolled_image","true"),this._appendPartnerConfigParts(e),this._appendConsentAndMetadata(e,t.consent,t.metadata),this._postMultipart("/v3/biometric_kyc",e)}async documentVerification(t){return this._postDocumentVerification(t,"/v3/document_verification")}async enhancedDocumentVerification(t){return this._postDocumentVerification(t,"/v3/enhanced_document_verification")}async residencyDocumentVerification(t){return this._postDocumentVerification(t,"/v3/residency_document_verification")}async _postDocumentVerification(t,n){const e=this._buildSelfieFormData(t);return s(e,"document",t.frontDocument),"backDocument"in t&&t.backDocument&&s(e,"document_back",t.backDocument),"visaDocument"in t&&s(e,"visa",t.visaDocument),t.country&&i(e,"country",t.country),t.idType&&i(e,"id_type",t.idType),r(e,t),this._appendPartnerConfigParts(e),this._appendConsentAndMetadata(e,t.consent,t.metadata),this._postMultipart(n,e)}_buildSelfieFormData(t){SmileIDAPI._validateLivenessCount(t.livenessImages,t.expectedLivenessImageCount);const n=new FormData;s(n,"selfie_image",t.selfieImage);for(const e of t.livenessImages)s(n,"liveness_images",e);return n}static _validateLivenessCount(t,n){if(t.length!==n)throw new S({expected:n,actual:t.length})}_appendIdFields(t,n){n.idType&&i(t,"id_type",n.idType),n.idNumber&&i(t,"id_number",n.idNumber),n.country&&i(t,"country",n.country)}_appendPartnerConfigParts(t){const n=this._partnerConfig.callbackUrl;n&&i(t,"callback_url",n);const e=this._partnerConfig.partnerParams;e&&Object.keys(e).length>0&&c(t,"partner_params",e)}_appendConsentAndMetadata(t,n,e){n!==void 0&&c(t,"consent",{granted:n.granted,granted_at:n.grantedAt,notice_language:n.noticeLanguage,notice_privacy_policy_url:n.noticePrivacyPolicyUrl}),c(t,"metadata",e.toEntries())}async _postMultipart(t,n){const e=await this._client.post(t,n,{headers:{"Content-Type":"multipart/form-data"},...C});return P(e.data)}}function P(a){if(a==null||typeof a!="object"||Array.isArray(a))throw new m({message:"SmileIDAPI: server returned an empty or non-object response body."});const t=a,n=(p,f)=>{if(typeof p!="string")throw new m({message:`SmileIDAPI: response is missing the required string field '${f}'.`});return p},e={jobId:n(t.job_id??t.jobId,"job_id"),userId:n(t.user_id??t.userId,"user_id"),status:n(t.status,"status"),message:n(t.message,"message")},o=t.created_at??t.createdAt;return typeof o=="string"&&o.length>0?{...e,createdAt:o}:e}function r(a,t){const n=t.userDetails;
|
|
1
|
+
"use strict";import u from"axios";import{SmileIDUrls as d}from"../smile_id_urls";import{userDetailsPayloadToWire as g}from"../model/user_details_payload";import{SmileAuthInterceptor as h}from"./interceptors/smile_auth_interceptor";import{attachLoggingInterceptor as y}from"./interceptors/logging_interceptor";import{REQUEST_MAC_OPT_IN_KEY as b}from"./interceptors/smile_request_mac_interceptor";import{ATTESTATION_OPT_IN_KEY as D}from"./interceptors/smile_integrity_interceptor";import{NetworkResponseParseException as m}from"../../core/exception";import{LivenessFrameCountException as S}from"../../core/exception";const l=d.production,_=d.sandbox,C={[b]:!0,[D]:!0};export class SmileIDAPI{_client;_partnerConfig;constructor(t,n){this._client=t,this._partnerConfig=n}async smartSelfieEnrollment(t){const n=this._buildSelfieFormData(t);return r(n,t),this._appendPartnerConfigParts(n),this._appendConsentAndMetadata(n,t.consent,t.metadata),this._postMultipart("/v3/registration",n)}async smartSelfieAuthentication(t){const n=this._buildSelfieFormData(t);return i(n,"user_id",t.userId),r(n,t),this._appendPartnerConfigParts(n),this._appendConsentAndMetadata(n,t.consent,t.metadata),this._postMultipart("/v3/authentication",n)}async enhancedKyc(t){const n=new FormData;return this._appendIdFields(n,t.params),r(n,t),t.params.bankCode&&i(n,"bank_code",t.params.bankCode),t.params.mobileOperator&&i(n,"operator",t.params.mobileOperator),this._appendPartnerConfigParts(n),this._appendConsentAndMetadata(n,t.consent,t.metadata),this._postMultipart("/v3/enhanced_kyc",n)}async biometricKyc(t){const n=t.params.useEnrolledImage,e=this._buildSelfieFormData(t);return this._appendIdFields(e,t.params),r(e,t),n&&i(e,"use_enrolled_image","true"),this._appendPartnerConfigParts(e),this._appendConsentAndMetadata(e,t.consent,t.metadata),this._postMultipart("/v3/biometric_kyc",e)}async documentVerification(t){return this._postDocumentVerification(t,"/v3/document_verification")}async enhancedDocumentVerification(t){return this._postDocumentVerification(t,"/v3/enhanced_document_verification")}async residencyDocumentVerification(t){return this._postDocumentVerification(t,"/v3/residency_document_verification")}async _postDocumentVerification(t,n){const e=this._buildSelfieFormData(t);return s(e,"document",t.frontDocument),"backDocument"in t&&t.backDocument&&s(e,"document_back",t.backDocument),"visaDocument"in t&&s(e,"visa",t.visaDocument),t.country&&i(e,"country",t.country),t.idType&&i(e,"id_type",t.idType),r(e,t),this._appendPartnerConfigParts(e),this._appendConsentAndMetadata(e,t.consent,t.metadata),this._postMultipart(n,e)}_buildSelfieFormData(t){SmileIDAPI._validateLivenessCount(t.livenessImages,t.expectedLivenessImageCount);const n=new FormData;s(n,"selfie_image",t.selfieImage);for(const e of t.livenessImages)s(n,"liveness_images",e);return n}static _validateLivenessCount(t,n){if(t.length!==n)throw new S({expected:n,actual:t.length})}_appendIdFields(t,n){n.idType&&i(t,"id_type",n.idType),n.idNumber&&i(t,"id_number",n.idNumber),n.country&&i(t,"country",n.country)}_appendPartnerConfigParts(t){const n=this._partnerConfig.callbackUrl;n&&i(t,"callback_url",n);const e=this._partnerConfig.partnerParams;e&&Object.keys(e).length>0&&c(t,"partner_params",e)}_appendConsentAndMetadata(t,n,e){n!==void 0&&c(t,"consent",{granted:n.granted,granted_at:n.grantedAt,notice_language:n.noticeLanguage,notice_privacy_policy_url:n.noticePrivacyPolicyUrl}),c(t,"metadata",e.toEntries())}async _postMultipart(t,n){const e=await this._client.post(t,n,{headers:{"Content-Type":"multipart/form-data"},...C});return P(e.data)}}function P(a){if(a==null||typeof a!="object"||Array.isArray(a))throw new m({message:"SmileIDAPI: server returned an empty or non-object response body."});const t=a,n=(p,f)=>{if(typeof p!="string")throw new m({message:`SmileIDAPI: response is missing the required string field '${f}'.`});return p},e={jobId:n(t.job_id??t.jobId,"job_id"),userId:n(t.user_id??t.userId,"user_id"),status:n(t.status,"status"),message:n(t.message,"message")},o=t.created_at??t.createdAt;return typeof o=="string"&&o.length>0?{...e,createdAt:o}:e}function r(a,t){const n=t.userDetails;n!==void 0&&c(a,"user_details",g(n))}function i(a,t,n){a.append(t,n)}function c(a,t,n){const e=JSON.stringify(n);a.append(t,e)}function s(a,t,n){const e=n.split("/").pop()??`${t}.jpg`;a.append(t,{uri:n,name:e,type:"image/jpeg"})}export function buildAxiosClient(a){const e=a.config?.partnerConfig?.useSandbox??!0?_:l,o=a.timeouts?.call??120;return u.create({baseURL:e,timeout:o*1e3})}export{h as SmileAuthInterceptor,y as attachLoggingInterceptor,l as PROD_BASE_URL,_ as SANDBOX_BASE_URL};
|
|
@@ -69,6 +69,11 @@ export type TokenBoundIdField = "country" | "idType" | "idNumber";
|
|
|
69
69
|
* (a bound field is never sent) read, so the two cannot disagree.
|
|
70
70
|
*/
|
|
71
71
|
export declare function isIdFieldBoundByToken(payload: TokenPayload | undefined, field: TokenBoundIdField): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Whether the token binds any user detail, which the server then injects
|
|
74
|
+
* into `user_details`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function bindsUserDetails(payload: TokenPayload | undefined): boolean;
|
|
72
77
|
/**
|
|
73
78
|
* Token consent is COMPLETE when `granted` is `true` and every other
|
|
74
79
|
* subfield is non-blank. A complete binding — granted with every
|
|
@@ -84,10 +89,3 @@ export declare function isTokenConsentComplete(consent: TokenConsent | undefined
|
|
|
84
89
|
* what the token must add.
|
|
85
90
|
*/
|
|
86
91
|
export declare function tokenConsentMissingSubfields(consent: TokenConsent): readonly string[];
|
|
87
|
-
/**
|
|
88
|
-
* Whether the token binds every user-detail field the SDK would
|
|
89
|
-
* otherwise require from the builder: `given_names` AND `last_name`
|
|
90
|
-
* AND at least one of `email` / `phone_number`. Only then may
|
|
91
|
-
* `builder.userDetails` be omitted entirely.
|
|
92
|
-
*/
|
|
93
|
-
export declare function bindsRequiredUserDetails(payload: TokenPayload | undefined): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{z as t}from"zod";const a=t.object({granted:t.literal(!0).optional().catch(void 0),granted_at:t.string().optional().catch(void 0),notice_language:t.string().optional().catch(void 0),notice_privacy_policy_url:t.string().optional().catch(void 0)});export const tokenPayloadSchema=t.object({given_names:t.string().optional().catch(void 0),last_name:t.string().optional().catch(void 0),email:t.string().optional().catch(void 0),phone_number:t.string().optional().catch(void 0),country:t.string().optional().catch(void 0),id_type:t.string().optional().catch(void 0),id_number:t.string().optional().catch(void 0),consent:a.optional().catch(void 0)});export function parseTokenPayload(n){if(n===null||typeof n!="object"||Array.isArray(n))return;const e=tokenPayloadSchema.safeParse(n);if(!e.success)return;const i=e.data,u=s(i.consent);return{hasGivenNames:r(i.given_names),hasLastName:r(i.last_name),hasEmail:r(i.email),hasPhoneNumber:r(i.phone_number),...u!==void 0?{consent:u}:{},hasCountry:r(i.country),hasIdType:r(i.id_type),hasIdNumber:r(i.id_number)}}export function isIdFieldBoundByToken(n,e){switch(e){case"country":return n?.hasCountry===!0;case"idType":return n?.hasIdType===!0;case"idNumber":return n?.hasIdNumber===!0}}export function isTokenConsentComplete(n){return n!==void 0&&tokenConsentMissingSubfields(n).length===0}export function tokenConsentMissingSubfields(n){const e=[];return n.granted!==!0&&e.push("granted"),o(n.grantedAt)||e.push("granted_at"),o(n.noticeLanguage)||e.push("notice_language"),o(n.noticePrivacyPolicyUrl)||e.push("notice_privacy_policy_url"),e}
|
|
1
|
+
"use strict";import{z as t}from"zod";const a=t.object({granted:t.literal(!0).optional().catch(void 0),granted_at:t.string().optional().catch(void 0),notice_language:t.string().optional().catch(void 0),notice_privacy_policy_url:t.string().optional().catch(void 0)});export const tokenPayloadSchema=t.object({given_names:t.string().optional().catch(void 0),last_name:t.string().optional().catch(void 0),email:t.string().optional().catch(void 0),phone_number:t.string().optional().catch(void 0),country:t.string().optional().catch(void 0),id_type:t.string().optional().catch(void 0),id_number:t.string().optional().catch(void 0),consent:a.optional().catch(void 0)});export function parseTokenPayload(n){if(n===null||typeof n!="object"||Array.isArray(n))return;const e=tokenPayloadSchema.safeParse(n);if(!e.success)return;const i=e.data,u=s(i.consent);return{hasGivenNames:r(i.given_names),hasLastName:r(i.last_name),hasEmail:r(i.email),hasPhoneNumber:r(i.phone_number),...u!==void 0?{consent:u}:{},hasCountry:r(i.country),hasIdType:r(i.id_type),hasIdNumber:r(i.id_number)}}export function isIdFieldBoundByToken(n,e){switch(e){case"country":return n?.hasCountry===!0;case"idType":return n?.hasIdType===!0;case"idNumber":return n?.hasIdNumber===!0}}export function bindsUserDetails(n){return n?.hasGivenNames===!0||n?.hasLastName===!0||n?.hasEmail===!0||n?.hasPhoneNumber===!0}export function isTokenConsentComplete(n){return n!==void 0&&tokenConsentMissingSubfields(n).length===0}export function tokenConsentMissingSubfields(n){const e=[];return n.granted!==!0&&e.push("granted"),o(n.grantedAt)||e.push("granted_at"),o(n.noticeLanguage)||e.push("notice_language"),o(n.noticePrivacyPolicyUrl)||e.push("notice_privacy_policy_url"),e}function s(n){if(n===void 0)return;const e={...n.granted===!0?{granted:!0}:{},...n.granted_at!==void 0?{grantedAt:n.granted_at}:{},...n.notice_language!==void 0?{noticeLanguage:n.notice_language}:{},...n.notice_privacy_policy_url!==void 0?{noticePrivacyPolicyUrl:n.notice_privacy_policy_url}:{}};return Object.keys(e).length>0?e:void 0}function r(n){return n!==void 0&&n.length>0}function o(n){return n!==void 0&&n.trim().length>0}
|
|
@@ -8,7 +8,9 @@ import type { TokenPayload } from "./token_payload";
|
|
|
8
8
|
* fields the token does **not** already bind. Token-bound fields are dropped
|
|
9
9
|
* at assembly (`userDetailsPayloadFrom`): the server injects the bound values
|
|
10
10
|
* and they take precedence over anything the client sends. When nothing
|
|
11
|
-
* remains, assembly omits the part
|
|
11
|
+
* remains, assembly omits the part under a token that binds user details,
|
|
12
|
+
* and otherwise sends `{}` so the API can name the field a job needs
|
|
13
|
+
* (`isUserDetailsPayloadEmpty`).
|
|
12
14
|
*
|
|
13
15
|
* Type and helper names are a deliberate cross-SDK shared contract and
|
|
14
16
|
* intentionally omit the `UseSmileID` prefix (like `TokenPayload`).
|
|
@@ -26,9 +28,9 @@ export type UserDetailsPayload = {
|
|
|
26
28
|
*/
|
|
27
29
|
export declare function userDetailsPayloadFrom(userDetails: UserDetails, droppingFieldsBoundBy: TokenPayload | undefined): UserDetailsPayload;
|
|
28
30
|
/**
|
|
29
|
-
* True when `userDetailsPayloadToWire` would produce an empty object
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* True when `userDetailsPayloadToWire` would produce an empty object. Blank
|
|
32
|
+
* fields count as empty, mirroring the wire encoding that trims and drops
|
|
33
|
+
* them.
|
|
32
34
|
*/
|
|
33
35
|
export declare function isUserDetailsPayloadEmpty(payload: UserDetailsPayload): boolean;
|
|
34
36
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{GlobalPrivacyControl as g}from"../core/models/global_privacy_control";import{AnalyzerFactoryKey as c,DocumentAnalyzerKey as u}from"@smileid/usesmileid_bridge";import{CaptureType as f}from"../core/models/ml_config";import{JobType as y}from"../core/models/job_type";import{autoCaptureWireValue as b}from"../core/models/document_capture_mode";import{flowShowAttribution as w}from"../presentation/flow/config/attribution_resolver";import{builderConfigurationToJson as C}from"./builder_configuration";import{MODEL_DESCRIPTOR_UNKNOWN as _,ModelProvider as r,modelConfigurationToJson as T}from"./model_configuration";export function flowConfigurationToBuilderConfiguration(e){const o=e.screens.steps;let n;const t=[];let d=!1,a=!1,p=!1;for(const i of o)switch(i.kind){case"consent":n=i.config;break;case"instructions":d=!0;break;case"capture":t.push(i.config);break;case"preview":a=!0;break;case"processing":p=!0;break}const l=t.find(i=>i.selfieConfig)?.selfieConfig,s=t.find(i=>i.documentConfig)?.documentConfig,v=l!==void 0||t.some(i=>i.captureType===f.selfie),m=s!==void 0||t.some(i=>i.captureType===f.document);return{consentScreenUsed:n!==void 0,consentIconProvided:n?.partnerIcon!=null,consentIconDimensions:null,consentAttributionShown:n!==void 0&&w(e),instructionsScreenUsed:d,selfiePreviewEnabled:a&&v,documentPreviewEnabled:a&&m,submissionSkipped:!p,allowAgentMode:l?.allowAgentMode??!1,enhancedLivenessEnabled:l?.enableEnhancedLiveness??!1,autoCaptureEnabled:b(s?.captureMode),allowGalleryUpload:s?.allowGalleryUpload??!1,captureBothSides:e.network?.config?.jobType===y.residencyDocumentVerification&&m||(s?.captureBothSides??!1),allowSkipBack:s?.allowSkipBack??!1,globalPrivacyControl:e.config?.globalPrivacyControl??g.OptOut}}export function flowConfigurationToModelConfiguration(e){const o=e.ml?.analyzers.selfieConfig?.analyzerFactoryKey,n=e.ml?.analyzers.document?.key;return{faceModel:h(o),documentModel:k(n)}}export function configurationMetadataEntries(e){const o=[{name:"builder_configuration_object",value:JSON.stringify(C(flowConfigurationToBuilderConfiguration(e)))},{name:"model_configuration_object",value:JSON.stringify(T(flowConfigurationToModelConfiguration(e)))}],n=flowConfigurationToSelfieLivenessTelemetry(e);return n.activeLivenessType!==null&&o.push({name:"active_liveness_type",value:n.activeLivenessType},{name:"active_liveness_version",value:n.activeLivenessVersion},{name:"selfie_image_origin",value:n.selfieImageOrigin}),o}export function flowConfigurationToSelfieLivenessTelemetry(e){let o,n=!1;for(const t of e.screens.steps)t.kind==="capture"&&(o??=t.config.selfieConfig,t.config.captureType===f.selfie&&(n=!0));return!n&&o===void 0?{activeLivenessType:null,activeLivenessVersion:null,selfieImageOrigin:null}:{activeLivenessType:O(o),activeLivenessVersion:"2.0.0",selfieImageOrigin:S(o)}}function O(e){return e?.enableEnhancedLiveness?"head_pose":"smile"}function S(e){return e?.allowAgentMode?"back_camera":"front_camera"}function h(e){switch(e){case c.MLKitFace:return{provider:r.Google,name:"play-services-mlkit-face-detection",version:"17.1.0",bundled:!1};case c.HuaweiFace:return{provider:r.Huawei,name:"ml-computer-vision-face",version:"3.18.1.300",bundled:!1};case c.VisionFace:return{provider:r.Apple,name:"vision_face_detector",version:"system",bundled:!0};default:return _}}function k(e){switch(e){case u.MLKitDocument:return{provider:r.Google,name:"object-detection",version:"17.0.2",bundled:!0};case u.HuaweiDocument:return{provider:r.Huawei,name:"ml-computer-vision-object",version:"3.7.0.301",bundled:!1};case u.VisionDocument:return{provider:r.Apple,name:"vision_document_detector",version:"system",bundled:!0};default:return L}}const L={provider:r.Smile,name:"noop_document_detector",version:"12.
|
|
1
|
+
"use strict";import{GlobalPrivacyControl as g}from"../core/models/global_privacy_control";import{AnalyzerFactoryKey as c,DocumentAnalyzerKey as u}from"@smileid/usesmileid_bridge";import{CaptureType as f}from"../core/models/ml_config";import{JobType as y}from"../core/models/job_type";import{autoCaptureWireValue as b}from"../core/models/document_capture_mode";import{flowShowAttribution as w}from"../presentation/flow/config/attribution_resolver";import{builderConfigurationToJson as C}from"./builder_configuration";import{MODEL_DESCRIPTOR_UNKNOWN as _,ModelProvider as r,modelConfigurationToJson as T}from"./model_configuration";export function flowConfigurationToBuilderConfiguration(e){const o=e.screens.steps;let n;const t=[];let d=!1,a=!1,p=!1;for(const i of o)switch(i.kind){case"consent":n=i.config;break;case"instructions":d=!0;break;case"capture":t.push(i.config);break;case"preview":a=!0;break;case"processing":p=!0;break}const l=t.find(i=>i.selfieConfig)?.selfieConfig,s=t.find(i=>i.documentConfig)?.documentConfig,v=l!==void 0||t.some(i=>i.captureType===f.selfie),m=s!==void 0||t.some(i=>i.captureType===f.document);return{consentScreenUsed:n!==void 0,consentIconProvided:n?.partnerIcon!=null,consentIconDimensions:null,consentAttributionShown:n!==void 0&&w(e),instructionsScreenUsed:d,selfiePreviewEnabled:a&&v,documentPreviewEnabled:a&&m,submissionSkipped:!p,allowAgentMode:l?.allowAgentMode??!1,enhancedLivenessEnabled:l?.enableEnhancedLiveness??!1,autoCaptureEnabled:b(s?.captureMode),allowGalleryUpload:s?.allowGalleryUpload??!1,captureBothSides:e.network?.config?.jobType===y.residencyDocumentVerification&&m||(s?.captureBothSides??!1),allowSkipBack:s?.allowSkipBack??!1,globalPrivacyControl:e.config?.globalPrivacyControl??g.OptOut}}export function flowConfigurationToModelConfiguration(e){const o=e.ml?.analyzers.selfieConfig?.analyzerFactoryKey,n=e.ml?.analyzers.document?.key;return{faceModel:h(o),documentModel:k(n)}}export function configurationMetadataEntries(e){const o=[{name:"builder_configuration_object",value:JSON.stringify(C(flowConfigurationToBuilderConfiguration(e)))},{name:"model_configuration_object",value:JSON.stringify(T(flowConfigurationToModelConfiguration(e)))}],n=flowConfigurationToSelfieLivenessTelemetry(e);return n.activeLivenessType!==null&&o.push({name:"active_liveness_type",value:n.activeLivenessType},{name:"active_liveness_version",value:n.activeLivenessVersion},{name:"selfie_image_origin",value:n.selfieImageOrigin}),o}export function flowConfigurationToSelfieLivenessTelemetry(e){let o,n=!1;for(const t of e.screens.steps)t.kind==="capture"&&(o??=t.config.selfieConfig,t.config.captureType===f.selfie&&(n=!0));return!n&&o===void 0?{activeLivenessType:null,activeLivenessVersion:null,selfieImageOrigin:null}:{activeLivenessType:O(o),activeLivenessVersion:"2.0.0",selfieImageOrigin:S(o)}}function O(e){return e?.enableEnhancedLiveness?"head_pose":"smile"}function S(e){return e?.allowAgentMode?"back_camera":"front_camera"}function h(e){switch(e){case c.MLKitFace:return{provider:r.Google,name:"play-services-mlkit-face-detection",version:"17.1.0",bundled:!1};case c.HuaweiFace:return{provider:r.Huawei,name:"ml-computer-vision-face",version:"3.18.1.300",bundled:!1};case c.VisionFace:return{provider:r.Apple,name:"vision_face_detector",version:"system",bundled:!0};default:return _}}function k(e){switch(e){case u.MLKitDocument:return{provider:r.Google,name:"object-detection",version:"17.0.2",bundled:!0};case u.HuaweiDocument:return{provider:r.Huawei,name:"ml-computer-vision-object",version:"3.7.0.301",bundled:!1};case u.VisionDocument:return{provider:r.Apple,name:"vision_document_detector",version:"system",bundled:!0};default:return L}}const L={provider:r.Smile,name:"noop_document_detector",version:"12.3.0",bundled:!0};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{Dimensions as d,Platform as r}from"react-native";import{Attestation as w,DeviceIntegrityProvider as b,PrivilegesProvider as S}from"@smileid/usesmileid_bridge";import{UseSmileIDMetadata as A}from"./use_smile_id_metadata";import{loadAsyncStorage as D,loadExpoBattery as O,loadExpoCellular as C,loadExpoDevice as l,loadExpoNetwork as M}from"./optional_native_modules";import{resolveGeolocation as U}from"./geolocation";import{resolveDimensions as u}from"../data/model/sdk_config_capture";import{SdkConfigSource as h}from"../data/model/sdk_config_source";import{CameraDefaults as o}from"../camera/camera_defaults";const m="12.
|
|
1
|
+
"use strict";import{Dimensions as d,Platform as r}from"react-native";import{Attestation as w,DeviceIntegrityProvider as b,PrivilegesProvider as S}from"@smileid/usesmileid_bridge";import{UseSmileIDMetadata as A}from"./use_smile_id_metadata";import{loadAsyncStorage as D,loadExpoBattery as O,loadExpoCellular as C,loadExpoDevice as l,loadExpoNetwork as M}from"./optional_native_modules";import{resolveGeolocation as U}from"./geolocation";import{resolveDimensions as u}from"../data/model/sdk_config_capture";import{SdkConfigSource as h}from"../data/model/sdk_config_source";import{CameraDefaults as o}from"../camera/camera_defaults";const m="12.3.0",k="12.0.0",N="react-native-expo",p="com.usesmileid.metadata.sdk_launch_count";export class UseSmileIDMetadataFactory{_sessionId;_deviceOs;_deviceModel;_appName;_hostApplication;_systemArchitecture;_memoryInfoMb;_buildBrand;_buildDevice;_buildFingerprint;_buildProduct;_carrierInfo;_batteryStatus;_sdkLaunchCount;_geolocation;_privileges;_deviceIntegrity;_clientIp="";_networkConnection="";_hardwareAttestation;constructor(t={}){this._sessionId=generateSessionId(),this._deviceOs=x(),this._appName=t.appName??"",this._hostApplication=t.hostApplication??"";const e=l();e?(this._deviceModel=resolveDeviceModel(e),this._systemArchitecture=e.supportedCpuArchitectures?.[0]?.trim()??"",this._memoryInfoMb=typeof e.totalMemory=="number"&&e.totalMemory>0?Math.round(e.totalMemory/(1024*1024)):0,this._buildBrand=e.brand?.trim()??"",this._buildDevice=(r.OS==="ios"?e.modelId:e.designName)?.trim()??"",this._buildFingerprint=e.osBuildFingerprint?.trim()??"",this._buildProduct=e.productName?.trim()??""):(this._deviceModel="",this._systemArchitecture="",this._memoryInfoMb=0,this._buildBrand="",this._buildDevice="",this._buildFingerprint="",this._buildProduct=""),this._carrierInfo=C()?.carrier?.trim()??"",this._warmupAsync(),this._warmUpHardwareAttestation()}get sessionId(){return this._sessionId}async warmup(){return this._warmupAsync()}async _warmupAsync(){const t=M();if(!t)return;const e=t.getNetworkStateAsync().then(s=>{this._networkConnection=(s.type??"").toString()}).catch(()=>{}),n=t.getIpAddressAsync?t.getIpAddressAsync().then(s=>{this._clientIp=s??""}).catch(()=>{}):Promise.resolve();await Promise.all([e,n,this._warmUpBatteryStatus(),this._warmUpLaunchCount(),this._warmUpGeolocation(),this._warmUpPrivileges(),this._warmUpDeviceIntegrity()])}async _warmUpGeolocation(){const t=await U();t&&(this._geolocation=t)}async _warmUpPrivileges(){const t=await S.privileges();t.length>0&&(this._privileges=t)}async _warmUpDeviceIntegrity(){this._deviceIntegrity=await b.signals()}async _warmUpLaunchCount(){const t=D();if(t)try{const n=(Number(await t.getItem(p))||0)+1;await t.setItem(p,String(n)),this._sdkLaunchCount=n}catch{}}async _warmUpBatteryStatus(){const t=O();if(t)try{switch(await t.getBatteryStateAsync()){case 1:this._batteryStatus=0;break;case 2:case 3:this._batteryStatus=1;break;default:this._batteryStatus=-1}}catch{}}async _warmUpHardwareAttestation(){try{this._hardwareAttestation=await w.generateHardwareAttestation(this._sessionId)}catch{}}make(t){const e=this._hardwareAttestation,n=r.OS==="android"&&e?{hardwareAttestationSecurityLevel:e.securityLevel,...e.certificateChain&&e.certificateChain.length>0?{attestationCertificateChain:e.certificateChain}:{}}:{},s=r.OS==="ios"&&e?{supportsAppAttest:e.supportsAppAttest}:{},a=u(t?.selfie),_=t!==void 0?h.sdkConfig:h.defaultSource,f=a?.width??o.selfieJpegTargetWidth,g=a?.height??o.selfieJpegTargetHeight,c=u(t?.liveness),y=c?.width??o.selfieJpegTargetWidth,v=c?.height??o.selfieJpegTargetHeight,I={sdkVersion:m,sdk:N,deviceOs:this._deviceOs,deviceModel:this._deviceModel,appName:this._appName,sessionId:this._sessionId,clientIp:this._clientIp,networkConnection:this._networkConnection,carrierInfo:this._carrierInfo,timezone:L(),timezoneOffsetMinutes:E(),locale:B(),screenResolution:H(),memoryInfoMb:this._memoryInfoMb,deviceOrientation:R(),systemArchitecture:this._systemArchitecture,numberOfCameras:0,hostApplication:this._hostApplication,securityPolicyVersion:k,...n,...s,sdkConfigSource:_,imageWidth:f,imageHeight:g,livenessImageWidth:y,livenessImageHeight:v,buildBrand:this._buildBrand,buildDevice:this._buildDevice,buildFingerprint:this._buildFingerprint,buildProduct:this._buildProduct,buildHardware:this._deviceIntegrity?.buildHardware??"",buildSource:this._deviceIntegrity?.buildSource??"",packageName:this._deviceIntegrity?.packageName??"",fingerprint:this._deviceIntegrity?.fingerprint??"",localTimeOfEnrolment:T(),wrapperName:"react_native_expo",wrapperVersion:m,...this._deviceIntegrity?.vpn!==void 0?{vpn:this._deviceIntegrity.vpn}:{},...this._deviceIntegrity?.certificateDigest!==void 0?{certificateDigest:this._deviceIntegrity.certificateDigest}:{},...this._batteryStatus!==void 0?{batteryStatus:this._batteryStatus}:{},...this._sdkLaunchCount!==void 0?{sdkLaunchCount:this._sdkLaunchCount}:{},...this._geolocation!==void 0?{geolocation:this._geolocation}:{},...this._privileges!==void 0?{privileges:this._privileges}:{}};return new A(I)}}export function resolveDeviceModel(i=l()){if(!i)return"";const t=i.brand?.trim()??"",e=i.modelName?.trim()??"";return[t,e].filter(Boolean).join(" ")}function x(){const i=r.OS==="ios"?"iOS":r.OS==="android"?"Android":r.OS,t=P();return t?`${i} ${t}`:i}function P(){if(r.OS==="android"){const i=r.constants?.Release;if(i!=null&&`${i}`!="")return`${i}`}return typeof r.Version<"u"?String(r.Version):""}function T(){try{return new Date().toISOString()}catch{return""}}function L(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone??""}catch{return""}}function E(){return-new Date().getTimezoneOffset()}function B(){try{return Intl.DateTimeFormat().resolvedOptions().locale??""}catch{return""}}function H(){try{const{width:i,height:t,scale:e}=d.get("screen");if(!i||!t)return"";const n=Math.round(i*(e||1)),s=Math.round(t*(e||1));return`${n} \xD7 ${s}`}catch{return""}}function R(){try{const{width:i,height:t}=d.get("window");return!i||!t?"":t>=i?"Portrait":"Landscape"}catch{return""}}export function generateSessionId(){const i=globalThis.crypto;if(i&&typeof i.randomUUID=="function")return i.randomUUID();const t=()=>typeof i?.getRandomValues=="function"?i.getRandomValues(new Uint16Array(1))[0]??0:Math.floor(Math.random()*65536),e=()=>t().toString(16).padStart(4,"0");return[e()+e(),e(),"4"+e().slice(1),(parseInt(e(),16)&16383|32768).toString(16),e()+e()+e()].join("-")}
|
|
@@ -20,7 +20,8 @@ export type DocumentImageRect = {
|
|
|
20
20
|
* each edge, so showing the outline alone could cut a captured document off; the
|
|
21
21
|
* screens that show the document fit the outline grown by that slack into their
|
|
22
22
|
* card instead (`shownSize`). A manual-shutter still or a gallery import is saved
|
|
23
|
-
* without a border, so its border fraction is 0 and it is shown whole
|
|
23
|
+
* without a border, so its border fraction is 0 and it is shown whole, in a card
|
|
24
|
+
* of its own shape (`cardAspectRatio`).
|
|
24
25
|
*/
|
|
25
26
|
export declare const DocumentInteriorCrop: {
|
|
26
27
|
/** The full border, as a fraction of the saved document's short side on every edge. */
|
|
@@ -39,10 +40,20 @@ export declare const DocumentInteriorCrop: {
|
|
|
39
40
|
* edge, and held to the image. The whole image when `borderFraction` is 0.
|
|
40
41
|
*/
|
|
41
42
|
readonly shownSize: (imageSize: SelfieSize, borderFraction: number) => SelfieSize;
|
|
43
|
+
/**
|
|
44
|
+
* Aspect ratio of the card a document is drawn in: `documentAspectRatio`, the
|
|
45
|
+
* shape of the outline the user framed, for a document saved with a border.
|
|
46
|
+
* One saved with none takes the shape of its `imageSize` once that is known,
|
|
47
|
+
* so the whole image fills the card with nothing beside it; a portrait photo
|
|
48
|
+
* of a passport would otherwise be a strip down the middle of a wide card.
|
|
49
|
+
*/
|
|
50
|
+
readonly cardAspectRatio: (imageSize: SelfieSize | null, documentAspectRatio: number, borderFraction: number) => number;
|
|
42
51
|
/**
|
|
43
52
|
* Uniform scale at which an `imageSize` document is drawn in a `boxSize` card,
|
|
44
53
|
* centred: its `shownSize` part fitted to the card, or, with no border, the
|
|
45
|
-
* whole image
|
|
54
|
+
* whole image fitted to it: a gallery import keeps the shape it was picked
|
|
55
|
+
* in, and covering the card with it would cut the document off. Returns 1
|
|
56
|
+
* when either size is degenerate.
|
|
46
57
|
*/
|
|
47
58
|
readonly cardScale: (imageSize: SelfieSize, boxSize: SelfieSize, borderFraction: number) => number;
|
|
48
59
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export const DocumentInteriorCrop={fullBorderFraction:.15,guideEdgeSlack:.1,shownSize(t,h){const n=Math.min(Math.max(h,0),DocumentInteriorCrop.fullBorderFraction)*Math.min(t.width,t.height),
|
|
1
|
+
"use strict";export const DocumentInteriorCrop={fullBorderFraction:.15,guideEdgeSlack:.1,shownSize(t,h){const n=Math.min(Math.max(h,0),DocumentInteriorCrop.fullBorderFraction)*Math.min(t.width,t.height),d=1+2*DocumentInteriorCrop.guideEdgeSlack;return{width:Math.min(t.width,(t.width-2*n)*d),height:Math.min(t.height,(t.height-2*n)*d)}},cardAspectRatio(t,h,r){return r>0||t===null||t.width<=0||t.height<=0?h:t.width/t.height},cardScale(t,h,r){if(t.width<=0||t.height<=0||h.width<=0||h.height<=0)return 1;if(r<=0)return Math.min(h.width/t.width,h.height/t.height);const n=DocumentInteriorCrop.shownSize(t,r);return Math.min(h.width/n.width,h.height/n.height)},imageRect(t,h,r){const n=DocumentInteriorCrop.cardScale(t,h,r),d=t.width*n,c=t.height*n;return{left:(h.width-d)/2,top:(h.height-c)/2,width:d,height:c}}};
|
|
@@ -3,7 +3,10 @@ import type { UseSmileIDTheme } from "../theme/theme";
|
|
|
3
3
|
export type UseSmileIDCapturedDocumentImageProps = {
|
|
4
4
|
/** Local file URI (`file:///…`) of the captured document side. */
|
|
5
5
|
readonly uri?: string;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Capture-frame aspect ratio (width / height) the box keeps while fitting the region; a document
|
|
8
|
+
* saved with no border keeps its image's own instead.
|
|
9
|
+
*/
|
|
7
10
|
readonly aspectRatio: number;
|
|
8
11
|
/**
|
|
9
12
|
* The hidden border `uri` was saved with, as a fraction of its short side on every edge. The
|
|
@@ -30,8 +33,9 @@ export type UseSmileIDCapturedDocumentImageProps = {
|
|
|
30
33
|
readonly theme?: UseSmileIDTheme;
|
|
31
34
|
};
|
|
32
35
|
/**
|
|
33
|
-
* Captured document image at its capture aspect,
|
|
34
|
-
* to the largest box that fits the padded region —
|
|
35
|
-
* {@link UseSmileIDCapturedImageOval}. Shared by the document
|
|
36
|
+
* Captured document image at its capture aspect, or a document saved with no border at its own,
|
|
37
|
+
* clipped to an 8dp rounded rectangle and sized to the largest box that fits the padded region —
|
|
38
|
+
* the rectangular counterpart to {@link UseSmileIDCapturedImageOval}. Shared by the document
|
|
39
|
+
* preview and the processing screen.
|
|
36
40
|
*/
|
|
37
41
|
export declare function UseSmileIDCapturedDocumentImage(props: UseSmileIDCapturedDocumentImageProps): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{jsx as l,jsxs as
|
|
1
|
+
"use strict";import{jsx as l,jsxs as E}from"react/jsx-runtime";import{useState as b}from"react";import{Image as k,StyleSheet as f,View as c}from"react-native";import{useUseSmileIDTheme as F}from"../theme/theme_context";import{fitCapturedDocumentImage as A}from"./helpers/captured_document_image_fit";import{DocumentInteriorCrop as I}from"./helpers/document_interior_crop";import{useImageSize as B}from"./helpers/selfie_interior_crop";import{useDocumentPreviewOverlayEnabled as O}from"../../debug/use_smile_id_debug";import{DocumentPreviewHiddenRegionsInPlace as U}from"../../debug/use_smile_id_document_preview_debug_overlay";import{capturedImageUri as P}from"./helpers/captured_image_uri";const w=8;export function UseSmileIDCapturedDocumentImage(e){const d=e.uri===void 0?void 0:P(e.uri),x=F(),R=e.theme??x,n=e.borderFraction??0,t=B(d),g=I.cardAspectRatio(t.kind==="read"?t.size:null,e.aspectRatio,n),h=R.spacing.large,[y,v]=b({width:0,height:0}),D=u=>{const{width:o,height:a}=u.nativeEvent.layout;v(i=>i.width===o&&i.height===a?i:{width:o,height:a})},S=A(g,y.width-2*h,y.height)??{...r.fillWidth,aspectRatio:g},z=O()&&e.showDebugOverlay===!0,[s,C]=b(null),L=u=>{const{width:o,height:a}=u.nativeEvent.layout;C(i=>i?.width===o&&i.height===a?i:{width:o,height:a}),e.onCardLayout?.({width:o,height:a})},m=n>0&&s!==null&&t.kind==="read"?I.imageRect(t.size,s,n):null,j=!!d&&(m!==null||t.kind==="unreadable"||n<=0&&t.kind==="read");return l(c,{style:[r.region,{paddingHorizontal:h}],onLayout:D,testID:e.testID,children:E(c,{style:[r.imageBox,S],onLayout:L,children:[j?l(k,{source:{uri:d},resizeMode:n>0?"cover":"contain",style:m?[r.interiorImage,m]:r.image,testID:e.imageTestID,...e.imageAccessibilityLabel?{accessible:!0,accessibilityRole:"image",accessibilityLabel:e.imageAccessibilityLabel}:{}}):null,z&&s!==null&&t.kind==="read"?l(c,{pointerEvents:"none",style:f.absoluteFill,children:l(U,{imageSize:t.size,cardSize:s,borderFraction:n})}):null,e.overlayChild?l(c,{pointerEvents:"box-none",style:r.overlay,children:e.overlayChild}):null]})})}const r=f.create({region:{flex:1,alignItems:"center",justifyContent:"center"},imageBox:{borderRadius:w,overflow:"hidden"},fillWidth:{width:"100%"},image:{width:"100%",height:"100%",borderRadius:w},interiorImage:{position:"absolute"},overlay:{position:"absolute",top:0,bottom:0,start:0,end:0,alignItems:"center",justifyContent:"center"}});
|
|
@@ -191,15 +191,6 @@ export declare class UseSmileIDFlowBuilder {
|
|
|
191
191
|
* `build()`.
|
|
192
192
|
*/
|
|
193
193
|
validateResidencyDocumentVerificationParams(params: ResidencyDocumentVerificationParams): ValidationState;
|
|
194
|
-
/**
|
|
195
|
-
* Validates a `UserDetails` payload in isolation and returns a
|
|
196
|
-
* `ValidationState`, without throwing. Useful when the partner pulls
|
|
197
|
-
* user PII from a form or remote config and wants to pre-flight it
|
|
198
|
-
* before assigning to `b.userDetails`. Surfaces the same dotted-path
|
|
199
|
-
* field names (`userDetails.givenNames` etc.) that the in-build
|
|
200
|
-
* validator emits.
|
|
201
|
-
*/
|
|
202
|
-
validateUserDetails(userDetails: UserDetails): ValidationState;
|
|
203
194
|
/**
|
|
204
195
|
* Snapshot the current builder state and report the validation
|
|
205
196
|
* outcome as a [FlowBuildResult]. Returns `{kind: 'success', ...}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{RESIDENCY_ID_TYPE as d}from"../../../core/models/params";import{UseSmileIDConfigBuilder as l}from"./builder/config_builder";import{ThemeConfigBuilder as u}from"./builder/theme_builder";import{UseSmileIDNetworkBuilder as h}from"../../../data/dsl/use_smile_id_network_builder";import{UseSmileIDMLBuilder as f}from"./builder/ml_builder";import{ScreensBuilder as P}from"./builder/screens_builder";import{IssueSeverity as D}from"../../../core/exception/issue_severity";import{runBiometricKYCParamsValidation as V,runBuilderValidation as v,runConsentValidation as _,runDocumentVerificationParamsValidation as I,runEnhancedDocumentVerificationParamsValidation as g,runEnhancedKYCParamsValidation as w,runFlowValidation as y,runResidencyDocumentVerificationParamsValidation as C
|
|
1
|
+
"use strict";import{RESIDENCY_ID_TYPE as d}from"../../../core/models/params";import{UseSmileIDConfigBuilder as l}from"./builder/config_builder";import{ThemeConfigBuilder as u}from"./builder/theme_builder";import{UseSmileIDNetworkBuilder as h}from"../../../data/dsl/use_smile_id_network_builder";import{UseSmileIDMLBuilder as f}from"./builder/ml_builder";import{ScreensBuilder as P}from"./builder/screens_builder";import{IssueSeverity as D}from"../../../core/exception/issue_severity";import{runBiometricKYCParamsValidation as V,runBuilderValidation as v,runConsentValidation as _,runDocumentVerificationParamsValidation as I,runEnhancedDocumentVerificationParamsValidation as g,runEnhancedKYCParamsValidation as w,runFlowValidation as y,runResidencyDocumentVerificationParamsValidation as C}from"../validation/flow_validator";import{ValidationState as n}from"../validation/validation_state";import{residencyIdTypeIssue as p}from"../validation/job_type_validator";import{decodeSmileIDToken as Y}from"../../../data/util/use_smile_id_token_decoder";export class UseSmileIDFlowBuilder{consentInformation;enhancedKYCParams;biometricKYCParams;documentVerificationParams;enhancedDocumentVerificationParams;residencyDocumentVerificationParams;userDetails;userId;localizations;onResult=()=>{};onAnalyticsEvent;_config;_theme;_network;_ml;_screens;config(i){this._config||(this._config=new l),i(this._config)}theme(i){this._theme=new u,i(this._theme)}network(i){this._network=new h,i(this._network)}ml(i){this._ml||(this._ml=new f),i(this._ml)}screens(i){this._screens||(this._screens=new P),i(this._screens)}validate(){return n.invalid(v(this._screens?.build().steps??[]))}validateConsent(i){return n.invalid(_(i))}validateEnhancedKYCParams(i){return n.invalid(w(i))}validateBiometricKYCParams(i){return n.invalid(V(i))}validateDocumentVerificationParams(i){return n.invalid(I(i))}validateEnhancedDocumentVerificationParams(i){return n.invalid(g(i))}validateResidencyDocumentVerificationParams(i){const e=i.idType.trim()!==""&&i.idType!==d?[p()]:[];return n.invalid([...C(i),...e])}build(){const i=this._screens?.build(),e=this._network?.build(),a=this._ml?.build(),s=this._config?.build(),m=K(s?.allowOfflineMode,e),o=y({steps:i?.steps??[],network:e,ml:a,consentInformation:this.consentInformation,enhancedKYCParams:this.enhancedKYCParams,biometricKYCParams:this.biometricKYCParams,documentVerificationParams:this.documentVerificationParams,enhancedDocumentVerificationParams:this.enhancedDocumentVerificationParams,residencyDocumentVerificationParams:this.residencyDocumentVerificationParams,userDetails:this.userDetails,userId:this.userId,tokenPayload:m}),c=n.invalid(o);if(!c.valid)return{kind:"invalid",validation:c};for(const t of o)t.severity===D.warning&&(console.warn(`[UseSmileIDFlowBuilder] ${t.message}`),t.suggestedFix&&console.warn(`[UseSmileIDFlowBuilder] Suggested fix: ${t.suggestedFix}`));if(!i)throw new Error("UseSmileIDFlowBuilder: internal invariant violated \u2014 screens missing after validation passed.");return{kind:"success",configuration:Object.freeze({...this.consentInformation?{consentInformation:this.consentInformation}:{},...this.enhancedKYCParams?{enhancedKYCParams:this.enhancedKYCParams}:{},...this.biometricKYCParams?{biometricKYCParams:this.biometricKYCParams}:{},...this.documentVerificationParams?{documentVerificationParams:this.documentVerificationParams}:{},...this.enhancedDocumentVerificationParams?{enhancedDocumentVerificationParams:this.enhancedDocumentVerificationParams}:{},...this.residencyDocumentVerificationParams?{residencyDocumentVerificationParams:this.residencyDocumentVerificationParams}:{},...this.userDetails?{userDetails:this.userDetails}:{},...this.userId?{userId:this.userId}:{},...s?{config:s}:{},...this._theme?{theme:this._theme.build()}:{},...e?{network:e}:{},...a?{ml:a}:{},screens:i,...this.localizations?{localizations:this.localizations}:{},onResult:this.onResult,...this.onAnalyticsEvent?{onAnalyticsEvent:this.onAnalyticsEvent}:{}})}}}function K(r,i){if(r)return;const e=i?.config?.token;if(!(!e||e.length===0))return Y(e)?.tokenPayload}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{JobType as c}from"../../core/models/job_type";import{MissingJobTypeException as
|
|
1
|
+
"use strict";import{JobType as c}from"../../core/models/job_type";import{MissingJobTypeException as b,MissingRequiredFieldException as r}from"../../core/exception/job_validation_exceptions";import{InvalidFieldValueException as w}from"../../core/exception/invalid_field_value_exception";import{configurationMetadataEntries as E}from"../../metadata/metadata_configuration_mappers";import{numSamplesOverride as C}from"../../data/model/sdk_config";import{bindsUserDetails as N,isIdFieldBoundByToken as k,isTokenConsentComplete as V}from"../../data/model/token_payload";import{isUserDetailsPayloadEmpty as S,userDetailsPayloadFrom as _}from"../../data/model/user_details_payload";import{EnhancedSmartSelfieConfig as L,FaceCaptureDefaults as x}from"@smileid/usesmileid_bridge";import{captureTelemetryEntries as j,documentImageOriginEntries as F}from"./flow_navigation_manager_analytics";const K=Object.freeze({granted:!1,grantedAt:"",noticeLanguage:"",noticePrivacyPolicyUrl:""});export function assembleJob(e){const n=e._configuration,s=n.network?.config?.jobType??c.unknown,d=V(e._resolvedTokenPayload?.consent)?void 0:e._captured.consentInformation??n.consentInformation??K,f=d!==void 0?{consent:d}:{},D=e._metadataProvider.make(e.resolvedSdkConfig),I=[...F(e),...j(e),...E(n)],l=D.withAdditionalEntries(I);switch(s){case c.smartSelfieEnrollment:{const t=p(e),i=m(e,n);return{jobType:c.smartSelfieEnrollment,selfieImage:t.selfiePath,livenessImages:t.livenessPaths,...i!==void 0?{userDetails:i}:{},...f,metadata:l,enhanced:!1,expectedLivenessImageCount:y(e)}}case c.smartSelfieAuthentication:{const t=p(e),i=m(e,n),o=U(e,n);return{jobType:c.smartSelfieAuthentication,selfieImage:t.selfiePath,livenessImages:t.livenessPaths,...i!==void 0?{userDetails:i}:{},userId:o,...f,metadata:l,enhanced:!1,expectedLivenessImageCount:y(e)}}case c.biometricKyc:{const t=p(e),i=n.biometricKYCParams;if(!i)throw new r({fieldName:"biometricKYCParams"});const o=m(e,n);return{jobType:c.biometricKyc,selfieImage:t.selfiePath,livenessImages:t.livenessPaths,params:{...i,...v(e,i)},...o!==void 0?{userDetails:o}:{},...f,metadata:l,expectedLivenessImageCount:y(e)}}case c.documentVerification:{const t=p(e),i=h(e),o=n.documentVerificationParams;if(!o)throw new r({fieldName:"documentVerificationParams"});const a=m(e,n);return{jobType:c.documentVerification,...P(e,t,i),country:u(e,o.country,"country"),...o.idType?{idType:u(e,o.idType,"idType")}:{},...a!==void 0?{userDetails:a}:{},...f,metadata:l,expectedLivenessImageCount:y(e)}}case c.enhancedDocumentVerification:{const t=p(e),i=h(e),o=n.enhancedDocumentVerificationParams;if(!o)throw new r({fieldName:"enhancedDocumentVerificationParams"});const a=m(e,n);return{jobType:c.enhancedDocumentVerification,...P(e,t,i),country:u(e,o.country,"country"),idType:u(e,o.idType,"idType"),...a!==void 0?{userDetails:a}:{},...f,metadata:l,expectedLivenessImageCount:y(e)}}case c.residencyDocumentVerification:{const t=p(e),i=h(e),o=n.residencyDocumentVerificationParams;if(!o)throw new r({fieldName:"residencyDocumentVerificationParams"});const a=A(e);if(!a)throw new r({fieldName:"visaDocument"});const T=m(e,n);return{jobType:c.residencyDocumentVerification,selfieImage:t.selfiePath,livenessImages:t.livenessPaths,frontDocument:i,visaDocument:a,country:u(e,o.country,"country"),idType:u(e,o.idType,"idType"),...T!==void 0?{userDetails:T}:{},...f,metadata:l,expectedLivenessImageCount:y(e)}}case c.enhancedKyc:{const t=n.enhancedKYCParams;if(!t)throw new r({fieldName:"enhancedKYCParams"});const i=m(e,n);return{jobType:c.enhancedKyc,params:{...t,...v(e,t)},...i!==void 0?{userDetails:i}:{},...f,metadata:l}}case c.bvn:throw new w({fieldName:"jobType",reason:"BVN submission is not yet supported"});case c.unknown:default:throw new b}}function p(e){const{selfiePath:n,livenessPaths:s}=e._captured;if(!n)throw new r({fieldName:"selfieImage"});return{selfiePath:n,livenessPaths:s}}function u(e,n,s){if(k(e._resolvedTokenPayload,s))return"";if(!n.trim())throw new r({fieldName:s});return n}function v(e,n){return{idType:u(e,n.idType,"idType"),idNumber:u(e,n.idNumber,"idNumber"),country:u(e,n.country,"country")}}function m(e,n){const s=e._resolvedTokenPayload,d=n.userDetails?_(n.userDetails,s):void 0;return d&&!S(d)?d:N(s)?void 0:{}}function U(e,n){const s=n.userId?.trim();if(!s)throw new r({fieldName:"userId"});return s}function h(e){const{frontDocumentPath:n}=e._captured;if(!n)throw new r({fieldName:"frontDocument"});return n}function P(e,n,s){const{backDocumentPath:d}=e._captured;return{selfieImage:n.selfiePath,livenessImages:n.livenessPaths,frontDocument:s,...d?{backDocument:d}:{}}}function A(e){return e._captured.visaDocumentPath}function y(e){if(e.enhancedLivenessEnabled)return L.EXPECTED_LIVENESS_IMAGE_COUNT;const s=C(e.resolvedSdkConfig);return s!==void 0?s-1:x.numSamples-1}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{UseSmileIDErrorCode as
|
|
1
|
+
"use strict";import{NetworkServerException as s,UseSmileIDErrorCode as i,UseSmileIDException as n,isNetworkErrorCode as t,isRetryableErrorCode as E}from"../../../../core/exception";export function resolveFailureMessage(o,r){if(!(o instanceof n))return r.siProcessingErrorGeneric;const e=o.errorCode;return e===i.Camera.PermissionDenied?r.siProcessingErrorCameraPermission:t(e)?e===i.Network.RequestFailed?o instanceof s?r.siProcessingErrorGeneric:r.siProcessingErrorNoConnection:e===i.Network.RateLimit||e===i.Network.ServerError?r.siProcessingErrorRetryLater:E(e)?r.siProcessingErrorNoConnection:r.siProcessingErrorUnavailable:r.siProcessingErrorGeneric}
|
|
@@ -89,23 +89,4 @@ export declare function runResidencyDocumentVerificationParamsValidation(params:
|
|
|
89
89
|
* pulled from their own storage before assigning it to the builder.
|
|
90
90
|
*/
|
|
91
91
|
export declare function runConsentValidation(consent: ConsentInformation): ValidationIssue[];
|
|
92
|
-
/**
|
|
93
|
-
* Validate a top-level `UserDetails` payload. Required fields use the
|
|
94
|
-
* dotted path (`userDetails.givenNames`) so partner regex-matchers
|
|
95
|
-
* keep working unchanged after the consolidation away from nesting
|
|
96
|
-
* `userDetails` inside the KYC param shapes.
|
|
97
|
-
*
|
|
98
|
-
* Each field requirement is evaluated against the union of builder
|
|
99
|
-
* input and token bindings: a field the token binds (see
|
|
100
|
-
* `TokenPayload`) never has to be supplied locally — the server
|
|
101
|
-
* injects it, and the SDK drops the local value from the wire. With
|
|
102
|
-
* no `tokenPayload` the behavior is exactly the pre-token rule set.
|
|
103
|
-
*
|
|
104
|
-
* Exposed via `b.validateUserDetails(payload)` so partners can
|
|
105
|
-
* pre-flight a payload before assigning to `b.userDetails`, mirroring
|
|
106
|
-
* the existing `b.validateConsent` / `b.validateEnhancedKYCParams`
|
|
107
|
-
* pattern (the pre-flight helper always validates strictly, without
|
|
108
|
-
* token context).
|
|
109
|
-
*/
|
|
110
|
-
export declare function runUserDetailsValidation(userDetails: UserDetails, tokenPayload?: TokenPayload): ValidationIssue[];
|
|
111
92
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{CaptureType as d}from"../../../core/models/ml_config";import{JobType as g,jobTypeFromValue as w}from"../../../core/models/job_type";import{InvalidConsentConfigException as c,InvalidConsentInformationException as m}from"../../../core/exception/consent_validation_exceptions";import{InvalidFieldValueException as s,MissingJobTypeException as P,MissingMLConfigurationBlockException as k,MissingNetworkConfigurationBlockException as b}from"../../../core/exception/job_validation_exceptions";import{AgentModeWithEnhancedLivenessException as C,InvalidDocumentCaptureConfigException as u,InvalidMissingCaptureTypeException as I}from"../../../core/exception/capture_validation_exceptions";import{EmptyScreensBlockException as
|
|
1
|
+
"use strict";import{CaptureType as d}from"../../../core/models/ml_config";import{JobType as g,jobTypeFromValue as w}from"../../../core/models/job_type";import{InvalidConsentConfigException as c,InvalidConsentInformationException as m}from"../../../core/exception/consent_validation_exceptions";import{InvalidFieldValueException as s,MissingJobTypeException as P,MissingMLConfigurationBlockException as k,MissingNetworkConfigurationBlockException as b}from"../../../core/exception/job_validation_exceptions";import{AgentModeWithEnhancedLivenessException as C,InvalidDocumentCaptureConfigException as u,InvalidMissingCaptureTypeException as I}from"../../../core/exception/capture_validation_exceptions";import{EmptyScreensBlockException as p}from"../../../core/exception/screen_config_validation_exceptions";import{isIdFieldBoundByToken as a}from"../../../data/model/token_payload";import{collectJobTypeIssues as T}from"./job_type_validator";function V(e){return e?.allowAgentMode&&e.enableEnhancedLiveness?[new C]:[]}function x(e){return e===void 0?[new u({reason:"document is required when mode is Document"})]:e.documentType===void 0?[new u({reason:"documentType is required for document capture"})]:e.knownIdAspectRatio!==void 0&&e.knownIdAspectRatio<=0?[new u({reason:"knownIdAspectRatio must be > 0 if provided"})]:[]}export function runBuilderValidation(e){return e.length===0?[new p]:[...l(e),...y(e)]}function l(e){const n=[];for(const o of e)o.kind==="capture"&&(o.config.captureType===void 0&&n.push(new I),o.config.captureType===d.selfie&&n.push(...V(o.config.selfieConfig)),o.config.captureType===d.document&&n.push(...x(o.config.documentConfig)));return n}function y(e){const n=[],o=e.find(i=>i.kind==="consent");return o&&(!o.config.partnerName||!o.config.partnerName.trim())&&n.push(new c({reason:"partnerName cannot be empty"})),o&&o.config.partnerIcon===void 0&&n.push(new c({reason:"partnerIcon is required for the consent screen"})),o&&(o.config.partnerPrivacyPolicyUrl===void 0||!o.config.partnerPrivacyPolicyUrl.trim())&&n.push(new c({reason:"partnerPrivacyPolicyUrl is required for the consent screen"})),n}export function runFlowValidation(e){const n=[];if(e.steps.length===0)return n.push(new p),n;n.push(...l(e.steps));const o=e.steps.some(t=>t.kind==="capture"),i=e.steps.some(t=>t.kind==="processing"),r=e.network?.config?.jobType,f=r===void 0||w(r)===g.unknown;return o&&!e.ml&&f&&n.push(new k),i&&!e.network&&n.push(new b),i&&f&&n.push(new P),n.push(...y(e.steps)),e.steps.some(t=>t.kind==="consent")&&e.consentInformation!==void 0&&n.push(new c({reason:"Exactly one form of consent is required \u2014 remove the consent { } screen if using consentInformation, or clear consentInformation if using the consent screen"})),e.consentInformation&&e.tokenPayload?.consent===void 0&&n.push(...runConsentValidation(e.consentInformation)),e.enhancedKYCParams&&n.push(...runEnhancedKYCParamsValidation(e.enhancedKYCParams,e.tokenPayload)),e.biometricKYCParams&&n.push(...runBiometricKYCParamsValidation(e.biometricKYCParams,e.tokenPayload)),e.documentVerificationParams&&n.push(...runDocumentVerificationParamsValidation(e.documentVerificationParams,e.tokenPayload)),e.enhancedDocumentVerificationParams&&n.push(...runEnhancedDocumentVerificationParamsValidation(e.enhancedDocumentVerificationParams,e.tokenPayload)),e.residencyDocumentVerificationParams&&n.push(...runResidencyDocumentVerificationParamsValidation(e.residencyDocumentVerificationParams,e.tokenPayload)),T(e,n),n}export function runEnhancedKYCParamsValidation(e,n){return h(e,n)}export function runBiometricKYCParamsValidation(e,n){return h(e,n)}export function runDocumentVerificationParamsValidation(e,n){const o=[];return!a(n,"country")&&!e.country?.trim()&&o.push(new s({fieldName:"country",reason:"must not be blank"})),!a(n,"idType")&&e.idType!==void 0&&!e.idType.trim()&&o.push(new s({fieldName:"idType",reason:"must not be blank"})),o}export function runEnhancedDocumentVerificationParamsValidation(e,n){const o=[];for(const i of["country","idType"]){const r=e[i];!a(n,i)&&!r?.trim()&&o.push(new s({fieldName:i,reason:"must not be blank"}))}return o}export function runResidencyDocumentVerificationParamsValidation(e,n){const o=[];for(const i of["country","idType"]){const r=e[i];!a(n,i)&&!r?.trim()&&o.push(new s({fieldName:i,reason:"must not be blank"}))}return o}export function runConsentValidation(e){const n=[];e.granted||n.push(new m({detail:"granted must be true"}));for(const o of["grantedAt","noticeLanguage","noticePrivacyPolicyUrl"])(!e[o]||!e[o].trim())&&n.push(new m({detail:`${o} must not be blank`}));return n}function h(e,n){const o=[];for(const i of["idType","idNumber","country"]){const r=e[i];!a(n,i)&&!r?.trim()&&o.push(new s({fieldName:i,reason:"must not be blank"}))}return o}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{RESIDENCY_ID_TYPE as h}from"../../../core/models/params";import{JobType as i}from"../../../core/models/job_type";import{CaptureType as y}from"../dsl/builder/screens_builder";import{InvalidJobTypeScreenConfigException as a}from"../../../core/exception/job_validation_exceptions";import{JOB_PARAMS_OWNERS as D,UnusedJobParamsException as x}from"../../../core/exception/unused_job_params_exception";import{DuplicateScreenTypeException as P,InvalidScreenOrderException as u,MissingCaptureForPreviewException as V}from"../../../core/exception/screen_config_validation_exceptions";import{IssueSeverity as p}from"../../../core/exception/issue_severity";import{tokenConsentMissingSubfields as E,bindsRequiredUserDetails as I}from"../../../data/model/token_payload";import{missingUserDetailsFieldIssues as J}from"./user_details_validator";const v=new Map([[i.biometricKyc,"BiometricKyc"],[i.smartSelfieAuthentication,"SmartSelfieAuthentication"],[i.smartSelfieEnrollment,"SmartSelfieEnrollment"],[i.enhancedKyc,"EnhancedKyc"],[i.documentVerification,"DocumentVerification"],[i.bvn,"BVN"],[i.enhancedDocumentVerification,"EnhancedDocumentVerification"],[i.residencyDocumentVerification,"ResidencyDocumentVerification"],[i.unknown,"Unknown"]]);function b(o){return v.get(o)??"Unknown"}function T(o,e,n){const r=e.steps.some(s=>s.kind==="consent"),t=e.consentInformation!==void 0,c=e.tokenPayload?.consent;if(c!==void 0){const s=E(c);s.length>0&&n.push(a.forJob({jobType:o,reason:`the token's consent binding is incomplete \u2014 missing: ${s.join(", ")}`,suggestedFix:"Bind all four consent fields when minting the token at /v3/token, or bind none and collect consent in the app"}));return}!r&&!t&&n.push(a.forJob({jobType:o,reason:"must include either a Consent screen or a pre-supplied consentInformation",suggestedFix:"Add a consent { } screen to your flow, or supply consentInformation via the builder"}))}export const JOB_PARAMS_SUPPLIERS={enhancedKYCParams:o=>o.enhancedKYCParams,biometricKYCParams:o=>o.biometricKYCParams,documentVerificationParams:o=>o.documentVerificationParams,enhancedDocumentVerificationParams:o=>o.enhancedDocumentVerificationParams,residencyDocumentVerificationParams:o=>o.residencyDocumentVerificationParams};function g(o,e,n){for(const{fieldName:r,owner:t}of D)t!==o&&JOB_PARAMS_SUPPLIERS[r]?.(e)!==void 0&&n.push(new x({jobType:o,fieldName:r}))}function j(o,e,n){if(e.userDetails===void 0&&!I(e.tokenPayload)){if(e.tokenPayload===void 0){n.push(a.forJob({jobType:o,reason:`userDetails is required for ${b(o)} jobs`,suggestedFix:"Set builder.userDetails with the end user PII before building"}));return}n.push(...J(void 0,e.tokenPayload))}}function f(o,e){const n=new Map;let r=0;o.forEach((t,c)=>{if(t.kind==="capture"){r++;return}if(t.kind==="preview")return;const s=t.kind==="instructions"?`${t.kind}:${r}`:t.kind,d=n.get(s)??{kind:t.kind,indices:[]};d.indices.push(c),n.set(s,d)});for(const{kind:t,indices:c}of n.values())c.length>1&&e.push(new P({screenType:t,indices:c}))}function m(o,e){o.findIndex(c=>c.kind==="consent")>0&&e.push(new u({description:"Consent screen must be the first screen in the flow",severity:p.error}));const r=o.findIndex(c=>c.kind==="processing");r!==-1&&r!==o.length-1&&e.push(new u({description:"Processing screen must be the last screen in the flow",severity:p.error}));const t=o.some(c=>c.kind==="capture");o.forEach((c,s)=>{const d=o[s+1]?.kind;c.kind==="instructions"&&t&&d!=="capture"&&d!=="instructions"&&e.push(new u({description:`Instructions screen at index ${s} must be followed by a Capture screen`,severity:p.error}))})}function l(o,e,n){let r=!1,t=0;o.forEach((c,s)=>{c.kind==="capture"?r=!0:c.kind==="preview"&&(t+=1,r?t>e&&n.push(new u({description:`Preview screen at index ${s} exceeds the maximum of ${e} preview screen(s) allowed for this flow`,severity:p.error})):n.push(new V({previewIndex:s})))})}function w(o,e,n){e.steps.some(t=>t.kind==="processing")||n.push(a.forJob({jobType:o,reason:"must include a Processing screen",suggestedFix:"Add a processing { } screen as the last screen in the flow"}))}function k(o,e,n){const r=e.steps.filter(t=>t.kind==="capture"&&t.config.captureType===y.selfie);r.length===0?n.push(a.forJob({jobType:o,reason:"must include exactly one Capture screen with captureType = SELFIE",suggestedFix:"Add a capture { captureType = CaptureType.SELFIE } screen to your flow"})):r.length>1&&n.push(a.forJob({jobType:o,reason:`must include exactly one Capture screen with captureType = SELFIE, but found ${r.length}`,suggestedFix:"Remove the extra selfie capture screens \u2014 only one is allowed"}))}function S(o,e,n,r){e.ml?e.ml.analyzers.selfie||r.push(a.forJob({jobType:o,reason:"ml { } block must register analyzers for CaptureType.SELFIE",suggestedFix:"Add forCaptureType(CaptureType.SELFIE) { } inside the ml { } block"})):r.push(a.forJob({jobType:o,reason:"ml { } block is required and must register analyzers for CaptureType.SELFIE",suggestedFix:n}))}export class SelfieJobTypeValidator{jobType;constructor(e){this.jobType=e}validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,1,n),T(this.jobType,e,n),g(this.jobType,e,n),k(this.jobType,e,n),e.steps.some(t=>t.kind==="capture"&&t.config.captureType===y.document)&&n.push(a.forJob({jobType:this.jobType,reason:"must not include a Document capture screen",suggestedFix:`Remove the Document capture screen \u2014 ${b(this.jobType)} requires selfie capture only`})),w(this.jobType,e,n),S(this.jobType,e,"Add the ml { } block with forCaptureType(CaptureType.SELFIE) { } inside",n),e.ml?.analyzers.document&&n.push(a.forJob({jobType:this.jobType,reason:"ml { } block must not register analyzers for CaptureType.DOCUMENT",suggestedFix:`Remove forCaptureType(CaptureType.DOCUMENT) { } from the ml { } block \u2014 ${b(this.jobType)} does not use document capture`})),this.jobType===i.biometricKyc&&e.biometricKYCParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"biometricKYCParams is required for BiometricKyc jobs",suggestedFix:"Set builder.biometricKYCParams with the identity fields before building"})),this.jobType===i.smartSelfieAuthentication&&(e.userId===void 0||e.userId.trim()==="")&&n.push(a.forJob({jobType:this.jobType,reason:"userId is required for SmartSelfieAuthentication jobs",suggestedFix:"Set builder.userId with the identifier returned by a prior Smart Selfie Enrollment before building"})),j(this.jobType,e,n)}}export class DocumentJobTypeValidator{jobType;constructor(e){this.jobType=e}validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,2,n),T(this.jobType,e,n),g(this.jobType,e,n),k(this.jobType,e,n);const r=e.steps.filter(t=>t.kind==="capture"&&t.config.captureType===y.document);if(r.length===0?n.push(a.forJob({jobType:this.jobType,reason:"must include exactly one Capture screen with captureType = DOCUMENT",suggestedFix:"Add a capture { captureType = CaptureType.DOCUMENT } screen to your flow"})):r.length>1&&n.push(a.forJob({jobType:this.jobType,reason:`must include exactly one Capture screen with captureType = DOCUMENT, but found ${r.length}`,suggestedFix:"Remove the extra document capture screens \u2014 only one is allowed"})),w(this.jobType,e,n),S(this.jobType,e,"Add the ml { } block with forCaptureType(CaptureType.SELFIE) { } inside \u2014 document capture uses NoopDocumentDetectorAnalyzer by default",n),this.appendParamsRule(e,n),this.jobType===i.enhancedDocumentVerification&&r.some(t=>t.kind==="capture"&&t.config.documentConfig?.documentType?.kind==="southAfricaGreenBook")&&n.push(a.forJob({jobType:this.jobType,reason:"South Africa Green Book is not supported for Enhanced Document Verification",suggestedFix:"Use JobType.documentVerification for the Green Book, or choose a supported document type for Enhanced Document Verification"})),this.jobType===i.residencyDocumentVerification)for(const t of r){if(t.kind!=="capture")continue;const c=t.config.documentConfig,s=c?.documentType;s?.kind!=="passport"&&n.push(a.forJob({jobType:this.jobType,reason:`documentType must be passport \u2014 ${s?.kind??"no document type"} is not a residency primary document`,suggestedFix:"Set documentType to the passport preset on the document capture screen"})),c&&c.allowSkipBack===!0&&n.push(a.forJob({jobType:this.jobType,reason:"allowSkipBack must be false \u2014 the visa capture is mandatory",suggestedFix:"Remove allowSkipBack from the document capture screen"}))}j(this.jobType,e,n)}appendParamsRule(e,n){this.jobType===i.documentVerification&&e.documentVerificationParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"documentVerificationParams is required for DocumentVerification jobs",suggestedFix:"Set builder.documentVerificationParams with the country (and optional idType) before building"})),this.jobType===i.enhancedDocumentVerification&&e.enhancedDocumentVerificationParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"enhancedDocumentVerificationParams is required for EnhancedDocumentVerification jobs",suggestedFix:"Set builder.enhancedDocumentVerificationParams with the country and idType before building"})),this.jobType===i.residencyDocumentVerification&&e.tokenPayload?.hasIdType!==!0&&e.residencyDocumentVerificationParams!==void 0&&e.residencyDocumentVerificationParams.idType!==h&&n.push(residencyIdTypeIssue()),this.jobType===i.residencyDocumentVerification&&e.residencyDocumentVerificationParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"residencyDocumentVerificationParams is required for ResidencyDocumentVerification jobs",suggestedFix:"Set builder.residencyDocumentVerificationParams with the country and idType before building"}))}}export class NonCaptureJobTypeValidator{jobType;constructor(e){this.jobType=e}validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,0,n),T(this.jobType,e,n),g(this.jobType,e,n),w(this.jobType,e,n);const r=e.steps.filter(t=>t.kind!=="consent"&&t.kind!=="processing");if(r.length>0){const t=[...new Set(r.map(c=>c.kind))].join(", ");n.push(a.forJob({jobType:this.jobType,reason:`must only contain consent and processing screens, but found: ${t}`,suggestedFix:"Remove all screens except consent { } and processing { } from your flow"}))}this.jobType===i.enhancedKyc&&e.enhancedKYCParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"enhancedKYCParams is required for EnhancedKyc jobs",suggestedFix:"Set builder.enhancedKYCParams with the identity fields before building"})),this.jobType===i.enhancedKyc&&j(this.jobType,e,n)}}export class DefaultStructuralValidator{validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,1,n)}}export const jobTypeValidators=new Map([[i.biometricKyc,new SelfieJobTypeValidator(i.biometricKyc)],[i.smartSelfieAuthentication,new SelfieJobTypeValidator(i.smartSelfieAuthentication)],[i.smartSelfieEnrollment,new SelfieJobTypeValidator(i.smartSelfieEnrollment)],[i.documentVerification,new DocumentJobTypeValidator(i.documentVerification)],[i.enhancedDocumentVerification,new DocumentJobTypeValidator(i.enhancedDocumentVerification)],[i.residencyDocumentVerification,new DocumentJobTypeValidator(i.residencyDocumentVerification)],[i.enhancedKyc,new NonCaptureJobTypeValidator(i.enhancedKyc)],[i.bvn,new NonCaptureJobTypeValidator(i.bvn)]]);const C=new DefaultStructuralValidator;export function resolveJobTypeValidator(o){return o===void 0||o===i.unknown?C:jobTypeValidators.get(o)??C}export function collectJobTypeIssues(o,e){const n=o.network?.config?.jobType;resolveJobTypeValidator(n).validate(o,e)}export function residencyIdTypeIssue(){return a.forJob({jobType:i.residencyDocumentVerification,reason:`idType must be "${h}" \u2014 the backend rejects any other value for this job type`,suggestedFix:`Set builder.residencyDocumentVerificationParams with idType: "${h}"`})}
|
|
1
|
+
"use strict";import{RESIDENCY_ID_TYPE as h}from"../../../core/models/params";import{JobType as i}from"../../../core/models/job_type";import{CaptureType as y}from"../dsl/builder/screens_builder";import{InvalidJobTypeScreenConfigException as a}from"../../../core/exception/job_validation_exceptions";import{JOB_PARAMS_OWNERS as C,UnusedJobParamsException as x}from"../../../core/exception/unused_job_params_exception";import{DuplicateScreenTypeException as V,InvalidScreenOrderException as p,MissingCaptureForPreviewException as D}from"../../../core/exception/screen_config_validation_exceptions";import{IssueSeverity as u}from"../../../core/exception/issue_severity";import{tokenConsentMissingSubfields as P}from"../../../data/model/token_payload";const E=new Map([[i.biometricKyc,"BiometricKyc"],[i.smartSelfieAuthentication,"SmartSelfieAuthentication"],[i.smartSelfieEnrollment,"SmartSelfieEnrollment"],[i.enhancedKyc,"EnhancedKyc"],[i.documentVerification,"DocumentVerification"],[i.bvn,"BVN"],[i.enhancedDocumentVerification,"EnhancedDocumentVerification"],[i.residencyDocumentVerification,"ResidencyDocumentVerification"],[i.unknown,"Unknown"]]);function j(o){return E.get(o)??"Unknown"}function b(o,e,n){const r=e.steps.some(s=>s.kind==="consent"),t=e.consentInformation!==void 0,c=e.tokenPayload?.consent;if(c!==void 0){const s=P(c);s.length>0&&n.push(a.forJob({jobType:o,reason:`the token's consent binding is incomplete \u2014 missing: ${s.join(", ")}`,suggestedFix:"Bind all four consent fields when minting the token at /v3/token, or bind none and collect consent in the app"}));return}!r&&!t&&n.push(a.forJob({jobType:o,reason:"must include either a Consent screen or a pre-supplied consentInformation",suggestedFix:"Add a consent { } screen to your flow, or supply consentInformation via the builder"}))}export const JOB_PARAMS_SUPPLIERS={enhancedKYCParams:o=>o.enhancedKYCParams,biometricKYCParams:o=>o.biometricKYCParams,documentVerificationParams:o=>o.documentVerificationParams,enhancedDocumentVerificationParams:o=>o.enhancedDocumentVerificationParams,residencyDocumentVerificationParams:o=>o.residencyDocumentVerificationParams};function T(o,e,n){for(const{fieldName:r,owner:t}of C)t!==o&&JOB_PARAMS_SUPPLIERS[r]?.(e)!==void 0&&n.push(new x({jobType:o,fieldName:r}))}function f(o,e){const n=new Map;let r=0;o.forEach((t,c)=>{if(t.kind==="capture"){r++;return}if(t.kind==="preview")return;const s=t.kind==="instructions"?`${t.kind}:${r}`:t.kind,d=n.get(s)??{kind:t.kind,indices:[]};d.indices.push(c),n.set(s,d)});for(const{kind:t,indices:c}of n.values())c.length>1&&e.push(new V({screenType:t,indices:c}))}function m(o,e){o.findIndex(c=>c.kind==="consent")>0&&e.push(new p({description:"Consent screen must be the first screen in the flow",severity:u.error}));const r=o.findIndex(c=>c.kind==="processing");r!==-1&&r!==o.length-1&&e.push(new p({description:"Processing screen must be the last screen in the flow",severity:u.error}));const t=o.some(c=>c.kind==="capture");o.forEach((c,s)=>{const d=o[s+1]?.kind;c.kind==="instructions"&&t&&d!=="capture"&&d!=="instructions"&&e.push(new p({description:`Instructions screen at index ${s} must be followed by a Capture screen`,severity:u.error}))})}function l(o,e,n){let r=!1,t=0;o.forEach((c,s)=>{c.kind==="capture"?r=!0:c.kind==="preview"&&(t+=1,r?t>e&&n.push(new p({description:`Preview screen at index ${s} exceeds the maximum of ${e} preview screen(s) allowed for this flow`,severity:u.error})):n.push(new D({previewIndex:s})))})}function g(o,e,n){e.steps.some(t=>t.kind==="processing")||n.push(a.forJob({jobType:o,reason:"must include a Processing screen",suggestedFix:"Add a processing { } screen as the last screen in the flow"}))}function w(o,e,n){const r=e.steps.filter(t=>t.kind==="capture"&&t.config.captureType===y.selfie);r.length===0?n.push(a.forJob({jobType:o,reason:"must include exactly one Capture screen with captureType = SELFIE",suggestedFix:"Add a capture { captureType = CaptureType.SELFIE } screen to your flow"})):r.length>1&&n.push(a.forJob({jobType:o,reason:`must include exactly one Capture screen with captureType = SELFIE, but found ${r.length}`,suggestedFix:"Remove the extra selfie capture screens \u2014 only one is allowed"}))}function S(o,e,n,r){e.ml?e.ml.analyzers.selfie||r.push(a.forJob({jobType:o,reason:"ml { } block must register analyzers for CaptureType.SELFIE",suggestedFix:"Add forCaptureType(CaptureType.SELFIE) { } inside the ml { } block"})):r.push(a.forJob({jobType:o,reason:"ml { } block is required and must register analyzers for CaptureType.SELFIE",suggestedFix:n}))}export class SelfieJobTypeValidator{jobType;constructor(e){this.jobType=e}validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,1,n),b(this.jobType,e,n),T(this.jobType,e,n),w(this.jobType,e,n),e.steps.some(t=>t.kind==="capture"&&t.config.captureType===y.document)&&n.push(a.forJob({jobType:this.jobType,reason:"must not include a Document capture screen",suggestedFix:`Remove the Document capture screen \u2014 ${j(this.jobType)} requires selfie capture only`})),g(this.jobType,e,n),S(this.jobType,e,"Add the ml { } block with forCaptureType(CaptureType.SELFIE) { } inside",n),e.ml?.analyzers.document&&n.push(a.forJob({jobType:this.jobType,reason:"ml { } block must not register analyzers for CaptureType.DOCUMENT",suggestedFix:`Remove forCaptureType(CaptureType.DOCUMENT) { } from the ml { } block \u2014 ${j(this.jobType)} does not use document capture`})),this.jobType===i.biometricKyc&&e.biometricKYCParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"biometricKYCParams is required for BiometricKyc jobs",suggestedFix:"Set builder.biometricKYCParams with the identity fields before building"})),this.jobType===i.smartSelfieAuthentication&&(e.userId===void 0||e.userId.trim()==="")&&n.push(a.forJob({jobType:this.jobType,reason:"userId is required for SmartSelfieAuthentication jobs",suggestedFix:"Set builder.userId with the identifier returned by a prior Smart Selfie Enrollment before building"}))}}export class DocumentJobTypeValidator{jobType;constructor(e){this.jobType=e}validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,2,n),b(this.jobType,e,n),T(this.jobType,e,n),w(this.jobType,e,n);const r=e.steps.filter(t=>t.kind==="capture"&&t.config.captureType===y.document);if(r.length===0?n.push(a.forJob({jobType:this.jobType,reason:"must include exactly one Capture screen with captureType = DOCUMENT",suggestedFix:"Add a capture { captureType = CaptureType.DOCUMENT } screen to your flow"})):r.length>1&&n.push(a.forJob({jobType:this.jobType,reason:`must include exactly one Capture screen with captureType = DOCUMENT, but found ${r.length}`,suggestedFix:"Remove the extra document capture screens \u2014 only one is allowed"})),g(this.jobType,e,n),S(this.jobType,e,"Add the ml { } block with forCaptureType(CaptureType.SELFIE) { } inside \u2014 document capture uses NoopDocumentDetectorAnalyzer by default",n),this.appendParamsRule(e,n),this.jobType===i.enhancedDocumentVerification&&r.some(t=>t.kind==="capture"&&t.config.documentConfig?.documentType?.kind==="southAfricaGreenBook")&&n.push(a.forJob({jobType:this.jobType,reason:"South Africa Green Book is not supported for Enhanced Document Verification",suggestedFix:"Use JobType.documentVerification for the Green Book, or choose a supported document type for Enhanced Document Verification"})),this.jobType===i.residencyDocumentVerification)for(const t of r){if(t.kind!=="capture")continue;const c=t.config.documentConfig,s=c?.documentType;s?.kind!=="passport"&&n.push(a.forJob({jobType:this.jobType,reason:`documentType must be passport \u2014 ${s?.kind??"no document type"} is not a residency primary document`,suggestedFix:"Set documentType to the passport preset on the document capture screen"})),c&&c.allowSkipBack===!0&&n.push(a.forJob({jobType:this.jobType,reason:"allowSkipBack must be false \u2014 the visa capture is mandatory",suggestedFix:"Remove allowSkipBack from the document capture screen"}))}}appendParamsRule(e,n){this.jobType===i.documentVerification&&e.documentVerificationParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"documentVerificationParams is required for DocumentVerification jobs",suggestedFix:"Set builder.documentVerificationParams with the country (and optional idType) before building"})),this.jobType===i.enhancedDocumentVerification&&e.enhancedDocumentVerificationParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"enhancedDocumentVerificationParams is required for EnhancedDocumentVerification jobs",suggestedFix:"Set builder.enhancedDocumentVerificationParams with the country and idType before building"})),this.jobType===i.residencyDocumentVerification&&e.tokenPayload?.hasIdType!==!0&&e.residencyDocumentVerificationParams!==void 0&&e.residencyDocumentVerificationParams.idType!==h&&n.push(residencyIdTypeIssue()),this.jobType===i.residencyDocumentVerification&&e.residencyDocumentVerificationParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"residencyDocumentVerificationParams is required for ResidencyDocumentVerification jobs",suggestedFix:"Set builder.residencyDocumentVerificationParams with the country and idType before building"}))}}export class NonCaptureJobTypeValidator{jobType;constructor(e){this.jobType=e}validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,0,n),b(this.jobType,e,n),T(this.jobType,e,n),g(this.jobType,e,n);const r=e.steps.filter(t=>t.kind!=="consent"&&t.kind!=="processing");if(r.length>0){const t=[...new Set(r.map(c=>c.kind))].join(", ");n.push(a.forJob({jobType:this.jobType,reason:`must only contain consent and processing screens, but found: ${t}`,suggestedFix:"Remove all screens except consent { } and processing { } from your flow"}))}this.jobType===i.enhancedKyc&&e.enhancedKYCParams===void 0&&n.push(a.forJob({jobType:this.jobType,reason:"enhancedKYCParams is required for EnhancedKyc jobs",suggestedFix:"Set builder.enhancedKYCParams with the identity fields before building"}))}}export class DefaultStructuralValidator{validate(e,n){f(e.steps,n),m(e.steps,n),l(e.steps,1,n)}}export const jobTypeValidators=new Map([[i.biometricKyc,new SelfieJobTypeValidator(i.biometricKyc)],[i.smartSelfieAuthentication,new SelfieJobTypeValidator(i.smartSelfieAuthentication)],[i.smartSelfieEnrollment,new SelfieJobTypeValidator(i.smartSelfieEnrollment)],[i.documentVerification,new DocumentJobTypeValidator(i.documentVerification)],[i.enhancedDocumentVerification,new DocumentJobTypeValidator(i.enhancedDocumentVerification)],[i.residencyDocumentVerification,new DocumentJobTypeValidator(i.residencyDocumentVerification)],[i.enhancedKyc,new NonCaptureJobTypeValidator(i.enhancedKyc)],[i.bvn,new NonCaptureJobTypeValidator(i.bvn)]]);const k=new DefaultStructuralValidator;export function resolveJobTypeValidator(o){return o===void 0||o===i.unknown?k:jobTypeValidators.get(o)??k}export function collectJobTypeIssues(o,e){const n=o.network?.config?.jobType;resolveJobTypeValidator(n).validate(o,e)}export function residencyIdTypeIssue(){return a.forJob({jobType:i.residencyDocumentVerification,reason:`idType must be "${h}" \u2014 the backend rejects any other value for this job type`,suggestedFix:`Set builder.residencyDocumentVerificationParams with idType: "${h}"`})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{jsx as b,Fragment as gt,jsxs as Ne}from"react/jsx-runtime";import{useCallback as x,useEffect as f,useLayoutEffect as ze,useMemo as ee,useRef as c,useState as h}from"react";import{Platform as je,StatusBar as yt,StyleSheet as Ye,View as wt}from"react-native";import*as $e from"expo-image-picker";import{CaptureReadyEventKind as k,DocumentSide as Ct,UseSmileIDScanStateType as v}from"@smileid/usesmileid_bridge";import{CaptureType as j}from"../../core/models/ml_config";import{resampleImportedDocument as Xe}from"../../camera/document_image_pipeline";import{defaultLightTheme as kt}from"../theme/theme";import{UseSmileIDThemeProvider as Et}from"../theme/theme_context";import{UseSmileIDCaptureContent as Tt}from"../components/use_smile_id_capture_content";import{DEFAULT_DOCUMENT_ASPECT as Dt,documentGuideRect as Rt}from"../components/use_smile_id_document_overlay";import{UploadImageCheck as At,UseSmileIDCrashReporting as E}from"@smileid/usesmileid_bridge";import{ForceMaxBrightness as vt}from"../utils/force_max_brightness";import{captureDebugBoxesMirrored as Je}from"../../debug/capture_debug_geometry";import{UseSmileIDDebugHooks as Qe}from"../../debug/use_smile_id_debug";import{CapturePerfMark as T,CapturePerfTrace as Ce,capturePerfOutcomeContinues as It}from"../../core/perf/capture_perf_trace";import{DocumentCaptureDebugOverlayHost as Mt}from"../../debug/use_smile_id_document_capture_debug_overlay";import{SelfieCaptureDebugOverlayHost as Pt}from"../../debug/use_smile_id_selfie_capture_debug_overlay";import{largestFace as _t}from"../../camera/bounding_box_metering_region";import{CameraDefaults as Ze}from"../../camera/camera_defaults";import{DocumentImageOrigin as ke}from"../../metadata/document_image_origin";import{DeviceOrientationCollector as bt}from"../../metadata/device_orientation_collector";import{useSmileIDFrameStream as xt}from"../../camera/use_smile_id_frame_stream";import{CameraLens as te}from"../../camera/camera_lens";import{SELFIE_ANALYSIS_INTERVAL_MS as Ft}from"../../camera/kamera_config_factory";import{deleteDroppedFrames as Ut}from"./helpers/dropped_frames";import{setKameraMetricsWanted as Lt,UseSmileIDKameraCameraSystem as Ot}from"../../camera/use_smile_id_kamera_camera_system";import{UseSmileIDKameraPreview as Kt}from"../../camera/use_smile_id_kamera_preview";import{useDeviceTiltDegrees as Bt,useLockedInterfaceOrientationDegrees as Ht}from"../../camera/use_kamera_orientation";import{useKameraCameraPermission as Wt}from"../../camera/use_kamera_camera_permission";import{isTerminalKameraError as Gt}from"../../camera/kamera_error_mapping";import{isSessionBoundary as Vt,mapCaptureUiState as qt,preCaptureUiState as Nt,steppedTurnProgress as et}from"./helpers/capture_ui_state";import{useDebouncedFeedbackKey as zt}from"./helpers/use_debounced_feedback_key";import{essTurnHaptic as jt,triggerCaptureCompleteHaptic as Yt,triggerEssTurnHaptic as $t}from"./helpers/ess_turn_haptics";import{UnsatisfiedReasons as Ee}from"../flow/analytics/unsatisfied_reasons";const Xt=33,Jt=Math.round(Ft/3);export function UseSmileIDCaptureScreen(t){const F=t.theme??kt,Y=t.showAttribution??!0,I=t.enableEnhancedLiveness??!1,U=t.captureType===j.selfie,tt=t.allowAgentMode??!1,O=t.captureType===j.selfie&&!tt?"front":"back",{hasPermission:K,requestPermission:Te}=Wt(),l=ee(()=>new Ot(O==="front"?te.front:te.back,t.createCameraSession),[]),De=c(!1);f(()=>{!K&&!De.current&&(De.current=!0,Te())},[K,Te]);const[B]=h(()=>Qe.isPerfTraceEnabled()),[rt]=h(()=>B?new Ce:null),d=c(rt),Re=c(!1),H=c(!1);f(()=>{B&&d.current===null&&(d.current=new Ce)},[B,t.documentSide]);const Ae=c(t.onCameraError);ze(()=>{Ae.current=t.onCameraError},[t.onCameraError]),f(()=>{if(!K)return;const e=l.addEventListener(r=>{r.type==="error"&&Gt(r.code)?Ae.current?.(r.code,r.message):r.type==="metric"&&(r.metric.name!=="frameAnalyzed"||!w.current)&&d.current?.onMetric(r.metric)});return Lt(B),l.configure({captureType:t.captureType,lens:O==="front"?te.front:te.back}),d.current?.mark(T.cameraStartRequested),l.start().then(()=>{d.current?.mark(T.cameraRunning)}).catch(r=>{E.captureException(r instanceof Error?r:new Error(String(r)))}),()=>{e(),l.release()}},[K,l,O,t.captureType,B]);const[ve,re]=h({type:v.Initial}),[s,S]=h(!1);f(()=>{s&&d.current?.mark(T.streamingStarted)},[s]);const[nt,Ie]=h(0),[W,ne]=h(!1),[at,ct]=h(!1),[ae,ce]=h(!1),w=c(!1),[Me,ie]=h(!1),[oe,Pe]=h(!1),[se,ue]=h(!1),$=c(!1),_e=c(0),X=c(!0);f(()=>(X.current=!0,()=>{X.current=!1}),[]);const[it,P]=h(!1),[J,le]=h(null);f(()=>{if(J===null)return;const e=setTimeout(()=>le(null),3e3);return()=>clearTimeout(e)},[J]);const y=c([]),fe=c(new Set),o=c(!1),de=c(null),G=c(null),me=c(0),V=c(!1),L=c(null),Q=c(new bt),pe=x(()=>({durationMs:G.current===null?0:Date.now()-G.current,retries:me.current,devicePositions:Q.current.devicePositions,deviceMovementDetected:Q.current.deviceMovementDetected}),[]),be=t.onStartCapture,he=x(()=>{if(s||o.current||$.current)return;$.current=!0,ue(!0),H.current=!0,be?.(),Ie(r=>r+1),de.current=null;const e=w.current;w.current=!1,ce(!1),ie(!1),Pe(!1),re({type:v.Initial}),ne(!1),y.current=[],V.current=!1,L.current=null,e?t.analyzer.reset().catch(r=>E.captureException(r)).finally(()=>{$.current=!1,ue(!1),o.current||S(!0)}):($.current=!1,ue(!1),S(!0))},[s,be,t.analyzer]),_=t.analyzer,q=t.onCaptureComplete,N=t.onDocumentOrigin,Se=t.onCaptureTimeout,ge=t.onAnalyticsEvent,Z=t.captureMode,ot=t.allowGalleryUpload??!1,D=t.documentSide??Ct.Front,g=x(e=>{const r=d.current;if(!r)return;const a=Re.current,R=H.current,p=It(e);if(d.current=p?new Ce:null,Re.current=p,H.current=!1,e==="abandoned"&&a&&!R&&r.snapshot().marks[T.streamingStarted]===void 0)return;const z=t.captureType===j.document?`document ${D}`:String(t.captureType),n=r.report(z,e);console.log(n.line);const C=Qe.perfTraceListener();C&&setTimeout(()=>{try{C(n)}catch(A){E.captureException(A instanceof Error?A:new Error(String(A)))}},0)},[t.captureType,D]),ye=c(g);ze(()=>{ye.current=g},[g]),f(()=>()=>ye.current("abandoned"),[]);const u=t.captureType===j.document;f(()=>{G.current=null,me.current=0,V.current=!1,L.current=null},[D]),f(()=>(s&&Q.current.start(),()=>Q.current.stop()),[s]);const we=Z?.kind==="autoCaptureWithManualFallback"?Z.activateManualAfterMs:null;f(()=>{if(!s||we===null)return;const e=setTimeout(()=>{ct(!0)},we);return()=>clearTimeout(e)},[s,we]);const st=x(()=>{o.current||(H.current=!0,o.current=!0,S(!1),P(!0),(async()=>{try{const e=await l.capture(),r=e.filePath.startsWith("file://")?e.filePath:`file://${e.filePath}`,a=await Xe({uri:r,width:e.width,height:e.height},D);d.current?.mark(T.captureComplete),d.current?.mark(T.filesReady),N?.(ke.CameraManualCapture),q?.([{kind:k.Document,captureIndex:0,url:a}],pe()),g("captured")}catch(e){E.captureException(e instanceof Error?e:new Error(String(e))),g("failed"),o.current=!1,P(!1),S(!0)}})())},[q,N,D,g]),ut=x(()=>{o.current||(H.current=!0,S(!1),P(!0),(async()=>{try{if(!(await $e.requestMediaLibraryPermissionsAsync()).granted){P(!1),o.current||S(!0);return}const r=await $e.launchImageLibraryAsync({mediaTypes:["images"],allowsMultipleSelection:!1,quality:1}),a=r.canceled?void 0:r.assets[0];if(a===void 0){P(!1),o.current||S(!0);return}if(a.width!==void 0&&a.height!==void 0&&Math.min(a.width,a.height)<Ze.minGalleryDocumentDimensionPx){le("si_document_gallery_image_too_small"),P(!1),o.current||S(!0);return}if(o.current)return;o.current=!0;const R=await Xe({uri:a.uri,width:a.width,height:a.height},D);d.current?.mark(T.filesReady),N?.(ke.Gallery),q?.([{kind:k.Document,captureIndex:0,url:R}],pe()),g("gallery")}catch(e){E.captureException(e instanceof Error?e:new Error(String(e))),g("failed"),le("si_document_gallery_image_failed"),o.current=!1,P(!1),S(!0)}})())},[q,N,D,g]),xe=c(!1),Fe=c(he);Fe.current=he,f(()=>{!u||xe.current||o.current||(xe.current=!0,Fe.current())},[u]);const lt=x(async()=>{const e=y.current,r=[...e.filter(n=>n.kind===k.Selfie),...e.filter(n=>n.kind===k.Liveness).sort((n,C)=>n.captureIndex-C.captureIndex)];if(r.length===0)return!1;const a=r.map(n=>n.url),p=(await At.restartReasons(a))[0];if(p===void 0)return!1;if(!X.current)return!0;if(_e.current>=er)return!1;_e.current+=1;const z=L.current??Date.now();return y.current=[],o.current=!1,w.current=!0,re({type:v.Initial}),ne(!1),Pe(!0),ge?.({kind:"selfieSessionRestarted",reason:p.startsWith(Ee.uploadCheckPrefix)?p:Ee.fallback,framesDiscarded:a.length,durationMs:Date.now()-z}),!0},[ge]),ft=xt(s?_:null,e=>{const r=de.current;if(r!==null&&Vt(r,e.state)&&Ie(n=>n+1),r!==null){const n=jt(r,e.state);n!==null&&$t(n)}if(de.current=e.state,!u)l.meterExposure(_t(e.output.faces));else{const{documentGuide:n}=e;l.setDocumentGuide(n??null)}w.current||d.current?.mark(T.firstScanResult),G.current===null&&(G.current=Date.now()),L.current??=Date.now();const a=e.state.type===v.Unsatisfied,R=a&&!V.current,p=y.current.filter(n=>n.kind===k.Selfie||n.kind===k.Liveness).length,z=L.current;if(R?me.current+=1:!a&&V.current&&(L.current=Date.now()),V.current=a,e.state.type===v.Unsatisfied)y.current=[];else if(e.capturedEvents.length>0){const C=e.capturedEvents.some(A=>A.kind===k.Selfie)?y.current.filter(A=>{const qe=A.kind===k.Selfie||A.kind===k.Liveness;return qe&&fe.current.add(A.url),!qe}):y.current;y.current=[...C,...e.capturedEvents]}if(R&&!u&&ge?.({kind:"selfieSessionRestarted",reason:e.state.reason??Ee.fallback,framesDiscarded:p,durationMs:Date.now()-z}),re(n=>n.type===e.state.type&&n.feedback===e.state.feedback&&n.reason===e.state.reason&&n.currentDirection===e.state.currentDirection&&(n.poseMatched??!1)===(e.state.poseMatched??!1)&&n.captureStep===e.state.captureStep&&Math.round((n.captureProgress??0)*100)===Math.round((e.state.captureProgress??0)*100)&&et(n.turnProgress)===et(e.state.turnProgress)?n:e.state),ne(!0),e.state.type===v.Finished){if(o.current||w.current)return;o.current=!0,S(!1),d.current?.mark(T.captureComplete),Ut(fe.current,y.current.map(n=>n.url)),fe.current.clear(),U&&!I&&Yt(),_.flush().then(()=>d.current?.mark(T.filesReady)).catch(n=>{E.captureException(n)}).then(()=>Qt(_,y.current)).then(async n=>{if(!u&&await lt().catch(C=>(E.captureException(C instanceof Error?C:new Error(String(C))),!1))){g("retake");return}X.current&&(g("captured"),N?.(ke.CameraAutoCapture),q?.(y.current,pe(),n,...u||m.width<=0||m.height<=0?[]:[{viewport:m,frameAspect:l.uprightPreviewAspect}]))})}else if(e.state.type===v.Timeout){if(o.current||w.current)return;S(!1),w.current=!0,ce(!0),g("timeout"),Se?.(e.state)}},{onError:e=>{E.captureException(e)}}),M=W?qt(ve,t.captureType,I):Nt(t.captureType,I,s||se),Ue=zt(M.feedbackKey,nt,M.showsAtOnce),Le=(ve.type===v.Initial&&!s&&!se||ae||oe)&&!u,dt=!s&&!se&&!o.current,Oe=K,Ke=c(Se);Ke.current=Se,f(()=>{if(!s||W)return;const e=setTimeout(()=>{o.current||w.current||(w.current=!0,ie(!0),ce(!0),ye.current("timeout"),S(!1),Ke.current?.({type:v.Timeout}))},Ze.firstFrameTimeoutMs);return()=>clearTimeout(e)},[s,W]),f(()=>{W&&ie(!1)},[W]);const Be=ft.onFrameResult,He=Bt({retainWhenFlat:u}),We=Ht(),[m,mt]=h({width:0,height:0}),pt=x(e=>{mt(r=>r.width===e.width&&r.height===e.height?r:e)},[]),i=ee(()=>{if(!u||m.width<=0||m.height<=0)return;const e=t.knownIdAspectRatio??Dt,r=Rt(e,m.width,m.height);return{left:r.left/m.width,top:r.top/m.height,width:r.width/m.width,height:r.height/m.height,viewportWidthPx:Math.round(m.width),viewportHeightPx:Math.round(m.height)}},[u,m,t.knownIdAspectRatio]);f(()=>{if(!u||i===void 0)return;const e=_;if(typeof e.setCropRegion!="function")return;let r=!1;return e.setCropRegion({cropLeft:i.left,cropTop:i.top,cropWidth:i.width,cropHeight:i.height,cropViewportWidth:i.viewportWidthPx,cropViewportHeight:i.viewportHeightPx}).then(a=>{r||l.setDocumentGuide(a)}).catch(a=>{E.captureException(a instanceof Error?a:new Error(String(a)))}),()=>{r=!0}},[u,i,_,l]),f(()=>{if(!s)return;let e=!1,r=!1;const R=setInterval(()=>{e||r||(e=!0,_.analyzeKameraFrame({rotation:He,viewportOrientation:We,cropLeft:i?i.left:0,cropTop:i?i.top:0,cropWidth:i?i.width:0,cropHeight:i?i.height:0,cropViewportWidth:i?i.viewportWidthPx:0,cropViewportHeight:i?i.viewportHeightPx:0}).then(p=>{r||Be(p)}).catch(p=>{E.captureException(p instanceof Error?p:new Error(String(p)))}).finally(()=>{e=!1}))},U?Jt:Xt);return()=>{r=!0,clearInterval(R)}},[s,_,Be,He,We,i]);const Ge=u&&(ae||s&&(Z?.kind==="manualCapture"||Z?.kind==="autoCaptureWithManualFallback"&&at)),Ve=u&&(s||ae)&&ot,ht=ee(()=>({captureType:t.captureType,overlayState:M.overlayState,enableEnhancedLiveness:I,animationDirection:M.animationDirection,feedbackKey:Me?"si_capture_camera_unavailable":oe?"si_capture_failed":J??Ue,showCaptureButton:Le,showManualShutter:Ge,showGalleryUpload:Ve,documentSide:D,...t.knownIdAspectRatio!==void 0?{knownIdAspectRatio:t.knownIdAspectRatio}:{},...t.documentTypeKind!==void 0?{documentTypeKind:t.documentTypeKind}:{}}),[t.captureType,M.overlayState,I,M.animationDirection,Ue,J,Me,oe,Le,u,Ge,Ve,D,t.knownIdAspectRatio,t.documentTypeKind]),St=ee(()=>Ne(gt,{children:[b(vt,{}),Oe?b(Kt,{cameraSystem:l,style:Ye.absoluteFill,testID:"si_capture_camera_preview"}):b(wt,{style:[Ye.absoluteFill,{backgroundColor:F.colors.offBlack}],testID:"si_capture_camera_placeholder"})]}),[Oe,l,F.colors.offBlack]);return Ne(Et,{value:F,children:[b(yt,{translucent:!0,backgroundColor:"transparent",barStyle:"dark-content"}),b(Tt,{state:ht,captureProgress:M.captureProgress,turnProgress:M.turnProgress,cameraPreview:St,onStartCapture:he,startCaptureEnabled:dt,onBack:t.onBack,showAttribution:Y,startButton:t.startButton,onViewportLayout:pt,...u?{onManualShutter:st,onGalleryUpload:ut,documentControlsEnabled:!it,...t.onClose?{onClose:t.onClose}:{}}:{},theme:F}),t.captureType===j.selfie?b(Pt,{frameAspect:l.uprightPreviewAspect,mirrorBoxes:Je(O==="front",je.OS==="android")}):u?b(Mt,{streamAspect:l.uprightPreviewAspect,mirrorBoxes:Je(O==="front",je.OS==="android")}):null]})}async function Qt(t,F){if(t.kind!=="document"||!t.documentBorderFraction)return 0;const Y=F.find(U=>U.kind===k.Document);if(!Y)return 0;let I;try{return await Promise.race([t.documentBorderFraction(Y.url),new Promise(U=>{I=setTimeout(()=>U(0),Zt)})])}catch{return 0}finally{clearTimeout(I)}}const Zt=1e3,er=1;
|
|
1
|
+
"use strict";import{jsx as _,Fragment as mt,jsxs as He}from"react/jsx-runtime";import{useCallback as x,useEffect as p,useLayoutEffect as We,useMemo as Z,useRef as i,useState as h}from"react";import{Platform as Ge,StatusBar as pt,StyleSheet as Ve,View as ht}from"react-native";import*as qe from"expo-image-picker";import{CaptureReadyEventKind as k,DocumentSide as gt,UseSmileIDScanStateType as P}from"@smileid/usesmileid_bridge";import{CaptureType as z}from"../../core/models/ml_config";import{resampleImportedDocument as Ne}from"../../camera/document_image_pipeline";import{defaultLightTheme as St}from"../theme/theme";import{UseSmileIDThemeProvider as yt}from"../theme/theme_context";import{UseSmileIDCaptureContent as wt}from"../components/use_smile_id_capture_content";import{DEFAULT_DOCUMENT_ASPECT as kt,documentGuideRect as Ct}from"../components/use_smile_id_document_overlay";import{UploadImageCheck as Et,UseSmileIDCrashReporting as C}from"@smileid/usesmileid_bridge";import{ForceMaxBrightness as Dt}from"../utils/force_max_brightness";import{captureDebugBoxesMirrored as ze}from"../../debug/capture_debug_geometry";import{UseSmileIDDebugHooks as je}from"../../debug/use_smile_id_debug";import{CapturePerfMark as E,CapturePerfTrace as he,capturePerfOutcomeContinues as Tt}from"../../core/perf/capture_perf_trace";import{DocumentCaptureDebugOverlayHost as Rt}from"../../debug/use_smile_id_document_capture_debug_overlay";import{SelfieCaptureDebugOverlayHost as At}from"../../debug/use_smile_id_selfie_capture_debug_overlay";import{largestFace as vt}from"../../camera/bounding_box_metering_region";import{CameraDefaults as It}from"../../camera/camera_defaults";import{DocumentImageOrigin as ge}from"../../metadata/document_image_origin";import{DeviceOrientationCollector as Pt}from"../../metadata/device_orientation_collector";import{useSmileIDFrameStream as Mt}from"../../camera/use_smile_id_frame_stream";import{CameraLens as ee}from"../../camera/camera_lens";import{SELFIE_ANALYSIS_INTERVAL_MS as bt}from"../../camera/kamera_config_factory";import{deleteDroppedFrames as _t}from"./helpers/dropped_frames";import{setKameraMetricsWanted as xt,UseSmileIDKameraCameraSystem as Ft}from"../../camera/use_smile_id_kamera_camera_system";import{UseSmileIDKameraPreview as Ut}from"../../camera/use_smile_id_kamera_preview";import{useDeviceTiltDegrees as Lt,useLockedInterfaceOrientationDegrees as Ot}from"../../camera/use_kamera_orientation";import{useKameraCameraPermission as Kt}from"../../camera/use_kamera_camera_permission";import{isTerminalKameraError as Bt}from"../../camera/kamera_error_mapping";import{isSessionBoundary as Ht,mapCaptureUiState as Wt,preCaptureUiState as Gt,steppedTurnProgress as Ye}from"./helpers/capture_ui_state";import{useDebouncedFeedbackKey as Vt}from"./helpers/use_debounced_feedback_key";import{essTurnHaptic as qt,triggerCaptureCompleteHaptic as Nt,triggerEssTurnHaptic as zt}from"./helpers/ess_turn_haptics";import{UnsatisfiedReasons as Se}from"../flow/analytics/unsatisfied_reasons";const jt=33,Yt=Math.round(bt/3);export function UseSmileIDCaptureScreen(t){const F=t.theme??St,j=t.showAttribution??!0,A=t.enableEnhancedLiveness??!1,U=t.captureType===z.selfie,$e=t.allowAgentMode??!1,O=t.captureType===z.selfie&&!$e?"front":"back",{hasPermission:K,requestPermission:ye}=Kt(),l=Z(()=>new Ft(O==="front"?ee.front:ee.back,t.createCameraSession),[]),we=i(!1);p(()=>{!K&&!we.current&&(we.current=!0,ye())},[K,ye]);const[B]=h(()=>je.isPerfTraceEnabled()),[Xe]=h(()=>B?new he:null),f=i(Xe),ke=i(!1),H=i(!1);p(()=>{B&&f.current===null&&(f.current=new he)},[B,t.documentSide]);const Ce=i(t.onCameraError);We(()=>{Ce.current=t.onCameraError},[t.onCameraError]),p(()=>{if(!K)return;const e=l.addEventListener(r=>{r.type==="error"&&Bt(r.code)?Ce.current?.(r.code,r.message):r.type==="metric"&&(r.metric.name!=="frameAnalyzed"||!v.current)&&f.current?.onMetric(r.metric)});return xt(B),l.configure({captureType:t.captureType,lens:O==="front"?ee.front:ee.back}),f.current?.mark(E.cameraStartRequested),l.start().then(()=>{f.current?.mark(E.cameraRunning)}).catch(r=>{C.captureException(r instanceof Error?r:new Error(String(r)))}),()=>{e(),l.release()}},[K,l,O,t.captureType,B]);const[Ee,te]=h({type:P.Initial}),[u,S]=h(!1);p(()=>{u&&f.current?.mark(E.streamingStarted)},[u]);const[Je,De]=h(0),[Qe,re]=h(!1),[Ze,et]=h(!1),[ne,Te]=h(!1),v=i(!1),[ae,Re]=h(!1),[ce,ie]=h(!1),Y=i(!1),Ae=i(0),$=i(!0);p(()=>($.current=!0,()=>{$.current=!1}),[]);const[tt,M]=h(!1),[X,oe]=h(null);p(()=>{if(X===null)return;const e=setTimeout(()=>oe(null),3e3);return()=>clearTimeout(e)},[X]);const y=i([]),se=i(new Set),o=i(!1),ue=i(null),W=i(null),le=i(0),G=i(!1),L=i(null),J=i(new Pt),fe=x(()=>({durationMs:W.current===null?0:Date.now()-W.current,retries:le.current,devicePositions:J.current.devicePositions,deviceMovementDetected:J.current.deviceMovementDetected}),[]),ve=t.onStartCapture,de=x(()=>{if(u||o.current||Y.current)return;Y.current=!0,ie(!0),H.current=!0,ve?.(),De(r=>r+1),ue.current=null;const e=v.current;v.current=!1,Te(!1),Re(!1),te({type:P.Initial}),re(!1),y.current=[],G.current=!1,L.current=null,e?t.analyzer.reset().catch(r=>C.captureException(r)).finally(()=>{Y.current=!1,ie(!1),o.current||S(!0)}):(Y.current=!1,ie(!1),S(!0))},[u,ve,t.analyzer]),b=t.analyzer,V=t.onCaptureComplete,q=t.onDocumentOrigin,rt=t.onCaptureTimeout,me=t.onAnalyticsEvent,Q=t.captureMode,nt=t.allowGalleryUpload??!1,D=t.documentSide??gt.Front,g=x(e=>{const r=f.current;if(!r)return;const a=ke.current,T=H.current,m=Tt(e);if(f.current=m?new he:null,ke.current=m,H.current=!1,e==="abandoned"&&a&&!T&&r.snapshot().marks[E.streamingStarted]===void 0)return;const N=t.captureType===z.document?`document ${D}`:String(t.captureType),n=r.report(N,e);console.log(n.line);const w=je.perfTraceListener();w&&setTimeout(()=>{try{w(n)}catch(R){C.captureException(R instanceof Error?R:new Error(String(R)))}},0)},[t.captureType,D]),Ie=i(g);We(()=>{Ie.current=g},[g]),p(()=>()=>Ie.current("abandoned"),[]);const s=t.captureType===z.document;p(()=>{W.current=null,le.current=0,G.current=!1,L.current=null},[D]),p(()=>(u&&J.current.start(),()=>J.current.stop()),[u]);const pe=Q?.kind==="autoCaptureWithManualFallback"?Q.activateManualAfterMs:null;p(()=>{if(!u||pe===null)return;const e=setTimeout(()=>{et(!0)},pe);return()=>clearTimeout(e)},[u,pe]);const at=x(()=>{o.current||(H.current=!0,o.current=!0,S(!1),M(!0),(async()=>{try{const e=await l.capture(),r=e.filePath.startsWith("file://")?e.filePath:`file://${e.filePath}`,a=await Ne({uri:r,width:e.width,height:e.height},D);f.current?.mark(E.captureComplete),f.current?.mark(E.filesReady),q?.(ge.CameraManualCapture),V?.([{kind:k.Document,captureIndex:0,url:a}],fe()),g("captured")}catch(e){C.captureException(e instanceof Error?e:new Error(String(e))),g("failed"),o.current=!1,M(!1),S(!0)}})())},[V,q,D,g]),ct=x(()=>{o.current||(H.current=!0,S(!1),M(!0),(async()=>{try{if(!(await qe.requestMediaLibraryPermissionsAsync()).granted){M(!1),o.current||S(!0);return}const r=await qe.launchImageLibraryAsync({mediaTypes:["images"],allowsMultipleSelection:!1,quality:1}),a=r.canceled?void 0:r.assets[0];if(a===void 0){M(!1),o.current||S(!0);return}if(a.width!==void 0&&a.height!==void 0&&Math.min(a.width,a.height)<It.minGalleryDocumentDimensionPx){oe("si_document_gallery_image_too_small"),M(!1),o.current||S(!0);return}if(o.current)return;o.current=!0;const T=await Ne({uri:a.uri,width:a.width,height:a.height},D);f.current?.mark(E.filesReady),q?.(ge.Gallery),V?.([{kind:k.Document,captureIndex:0,url:T}],fe()),g("gallery")}catch(e){C.captureException(e instanceof Error?e:new Error(String(e))),g("failed"),oe("si_document_gallery_image_failed"),o.current=!1,M(!1),S(!0)}})())},[V,q,D,g]),Pe=i(!1),Me=i(de);Me.current=de,p(()=>{!s||Pe.current||o.current||(Pe.current=!0,Me.current())},[s]);const it=x(async()=>{const e=y.current,r=[...e.filter(n=>n.kind===k.Selfie),...e.filter(n=>n.kind===k.Liveness).sort((n,w)=>n.captureIndex-w.captureIndex)];if(r.length===0)return!1;const a=r.map(n=>n.url),m=(await Et.restartReasons(a))[0];if(m===void 0)return!1;if(!$.current)return!0;if(Ae.current>=Jt)return!1;Ae.current+=1;const N=L.current??Date.now();return y.current=[],o.current=!1,v.current=!0,te({type:P.Initial}),re(!1),Re(!0),me?.({kind:"selfieSessionRestarted",reason:m.startsWith(Se.uploadCheckPrefix)?m:Se.fallback,framesDiscarded:a.length,durationMs:Date.now()-N}),!0},[me]),ot=Mt(u?b:null,e=>{const r=ue.current;if(r!==null&&Ht(r,e.state)&&De(n=>n+1),r!==null){const n=qt(r,e.state);n!==null&&zt(n)}if(ue.current=e.state,!s)l.meterExposure(vt(e.output.faces));else{const{documentGuide:n}=e;l.setDocumentGuide(n??null)}v.current||f.current?.mark(E.firstScanResult),W.current===null&&(W.current=Date.now()),L.current??=Date.now();const a=e.state.type===P.Unsatisfied,T=a&&!G.current,m=y.current.filter(n=>n.kind===k.Selfie||n.kind===k.Liveness).length,N=L.current;if(T?le.current+=1:!a&&G.current&&(L.current=Date.now()),G.current=a,e.state.type===P.Unsatisfied)y.current=[];else if(e.capturedEvents.length>0){const w=e.capturedEvents.some(R=>R.kind===k.Selfie)?y.current.filter(R=>{const Be=R.kind===k.Selfie||R.kind===k.Liveness;return Be&&se.current.add(R.url),!Be}):y.current;y.current=[...w,...e.capturedEvents]}if(T&&!s&&me?.({kind:"selfieSessionRestarted",reason:e.state.reason??Se.fallback,framesDiscarded:m,durationMs:Date.now()-N}),te(n=>n.type===e.state.type&&n.feedback===e.state.feedback&&n.reason===e.state.reason&&n.currentDirection===e.state.currentDirection&&(n.poseMatched??!1)===(e.state.poseMatched??!1)&&n.captureStep===e.state.captureStep&&Math.round((n.captureProgress??0)*100)===Math.round((e.state.captureProgress??0)*100)&&Ye(n.turnProgress)===Ye(e.state.turnProgress)?n:e.state),re(!0),e.state.type===P.Finished){if(o.current||v.current)return;o.current=!0,S(!1),f.current?.mark(E.captureComplete),_t(se.current,y.current.map(n=>n.url)),se.current.clear(),U&&!A&&Nt(),b.flush().then(()=>f.current?.mark(E.filesReady)).catch(n=>{C.captureException(n)}).then(()=>$t(b,y.current)).then(async n=>{if(!s&&await it().catch(w=>(C.captureException(w instanceof Error?w:new Error(String(w))),!1))){g("retake");return}$.current&&(g("captured"),q?.(ge.CameraAutoCapture),V?.(y.current,fe(),n,...s||d.width<=0||d.height<=0?[]:[{viewport:d,frameAspect:l.uprightPreviewAspect}]))})}else if(e.state.type===P.Timeout){if(o.current||v.current)return;S(!1),v.current=!0,Te(!0),g("timeout"),rt?.(e.state)}},{onError:e=>{C.captureException(e)}}),I=Qe?Wt(Ee,t.captureType,A):Gt(t.captureType,A,u||ce),be=Vt(I.feedbackKey,Je,I.showsAtOnce),_e=(Ee.type===P.Initial&&!u&&!ce||ne||ae)&&!s,st=!u&&!ce&&!o.current,xe=K,Fe=ot.onFrameResult,Ue=Lt({retainWhenFlat:s}),Le=Ot(),[d,ut]=h({width:0,height:0}),lt=x(e=>{ut(r=>r.width===e.width&&r.height===e.height?r:e)},[]),c=Z(()=>{if(!s||d.width<=0||d.height<=0)return;const e=t.knownIdAspectRatio??kt,r=Ct(e,d.width,d.height);return{left:r.left/d.width,top:r.top/d.height,width:r.width/d.width,height:r.height/d.height,viewportWidthPx:Math.round(d.width),viewportHeightPx:Math.round(d.height)}},[s,d,t.knownIdAspectRatio]);p(()=>{if(!s||c===void 0)return;const e=b;if(typeof e.setCropRegion!="function")return;let r=!1;return e.setCropRegion({cropLeft:c.left,cropTop:c.top,cropWidth:c.width,cropHeight:c.height,cropViewportWidth:c.viewportWidthPx,cropViewportHeight:c.viewportHeightPx}).then(a=>{r||l.setDocumentGuide(a)}).catch(a=>{C.captureException(a instanceof Error?a:new Error(String(a)))}),()=>{r=!0}},[s,c,b,l]),p(()=>{if(!u)return;let e=!1,r=!1;const T=setInterval(()=>{e||r||(e=!0,b.analyzeKameraFrame({rotation:Ue,viewportOrientation:Le,cropLeft:c?c.left:0,cropTop:c?c.top:0,cropWidth:c?c.width:0,cropHeight:c?c.height:0,cropViewportWidth:c?c.viewportWidthPx:0,cropViewportHeight:c?c.viewportHeightPx:0}).then(m=>{r||Fe(m)}).catch(m=>{C.captureException(m instanceof Error?m:new Error(String(m)))}).finally(()=>{e=!1}))},U?Yt:jt);return()=>{r=!0,clearInterval(T)}},[u,b,Fe,Ue,Le,c]);const Oe=s&&(ne||u&&(Q?.kind==="manualCapture"||Q?.kind==="autoCaptureWithManualFallback"&&Ze)),Ke=s&&(u||ne)&&nt,ft=Z(()=>({captureType:t.captureType,overlayState:I.overlayState,enableEnhancedLiveness:A,animationDirection:I.animationDirection,feedbackKey:ae?"si_capture_failed":X??be,showCaptureButton:_e,showManualShutter:Oe,showGalleryUpload:Ke,documentSide:D,...t.knownIdAspectRatio!==void 0?{knownIdAspectRatio:t.knownIdAspectRatio}:{},...t.documentTypeKind!==void 0?{documentTypeKind:t.documentTypeKind}:{}}),[t.captureType,I.overlayState,A,I.animationDirection,be,X,ae,_e,s,Oe,Ke,D,t.knownIdAspectRatio,t.documentTypeKind]),dt=Z(()=>He(mt,{children:[_(Dt,{}),xe?_(Ut,{cameraSystem:l,style:Ve.absoluteFill,testID:"si_capture_camera_preview"}):_(ht,{style:[Ve.absoluteFill,{backgroundColor:F.colors.offBlack}],testID:"si_capture_camera_placeholder"})]}),[xe,l,F.colors.offBlack]);return He(yt,{value:F,children:[_(pt,{translucent:!0,backgroundColor:"transparent",barStyle:"dark-content"}),_(wt,{state:ft,captureProgress:I.captureProgress,turnProgress:I.turnProgress,cameraPreview:dt,onStartCapture:de,startCaptureEnabled:st,onBack:t.onBack,showAttribution:j,startButton:t.startButton,onViewportLayout:lt,...s?{onManualShutter:at,onGalleryUpload:ct,documentControlsEnabled:!tt,...t.onClose?{onClose:t.onClose}:{}}:{},theme:F}),t.captureType===z.selfie?_(At,{frameAspect:l.uprightPreviewAspect,mirrorBoxes:ze(O==="front",Ge.OS==="android")}):s?_(Rt,{streamAspect:l.uprightPreviewAspect,mirrorBoxes:ze(O==="front",Ge.OS==="android")}):null]})}async function $t(t,F){if(t.kind!=="document"||!t.documentBorderFraction)return 0;const j=F.find(U=>U.kind===k.Document);if(!j)return 0;let A;try{return await Promise.race([t.documentBorderFraction(j.url),new Promise(U=>{A=setTimeout(()=>U(0),Xt)})])}catch{return 0}finally{clearTimeout(A)}}const Xt=1e3,Jt=1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/usesmileid",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.3.0",
|
|
4
4
|
"description": "Official Smile ID React Native (Expo) SDK for identity verification: selfie capture, liveness checks, and document verification powered by on-device ML.",
|
|
5
5
|
"homepage": "https://usesmileid.com",
|
|
6
6
|
"repository": {
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"CHANGELOG.md"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@smileid/kamera": "1.
|
|
51
|
+
"@smileid/kamera": "1.2.0",
|
|
52
52
|
"axios": "^1.20.0",
|
|
53
53
|
"zod": "^4.6.5",
|
|
54
|
-
"@smileid/usesmileid_bridge": "12.
|
|
55
|
-
"@smileid/usesmileid_platform_interface": "12.
|
|
54
|
+
"@smileid/usesmileid_bridge": "12.3.0",
|
|
55
|
+
"@smileid/usesmileid_platform_interface": "12.3.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@react-native-async-storage/async-storage": ">=1.21.0",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@smileid/kamera-testing": "1.
|
|
108
|
+
"@smileid/kamera-testing": "1.2.0",
|
|
109
109
|
"@types/react": "^19.2.18",
|
|
110
110
|
"expo": "~57.0.26",
|
|
111
111
|
"expo-brightness": "~57.0.2",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"expo-image-manipulator": "~57.0.20",
|
|
116
116
|
"expo-image-picker": "~57.0.20",
|
|
117
117
|
"expo-keep-awake": "~57.0.2",
|
|
118
|
-
"expo-modules-core": "^57.0.
|
|
118
|
+
"expo-modules-core": "^57.0.21",
|
|
119
119
|
"expo-screen-orientation": "~57.0.2",
|
|
120
120
|
"jpeg-js": "^0.4.4",
|
|
121
121
|
"lottie-react-native": "~7.5.0",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { UserDetails } from "../../../core/models/params";
|
|
2
|
-
import type { TokenPayload } from "../../../data/model/token_payload";
|
|
3
|
-
import type { ValidationIssue } from "./validation_state";
|
|
4
|
-
/**
|
|
5
|
-
* Field-union rule shared by the content validator and the job-type required
|
|
6
|
-
* rule: a field is covered when the token binds it or the builder supplies it.
|
|
7
|
-
*/
|
|
8
|
-
export declare function missingUserDetailsFieldIssues(userDetails: UserDetails | undefined, tokenPayload: TokenPayload | undefined): ValidationIssue[];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";import{InvalidFieldValueException as o}from"../../../core/exception/job_validation_exceptions";export function missingUserDetailsFieldIssues(n,e){const t=[],a=e===void 0?"must not be blank":"not bound by the token and not supplied on the builder",m={givenNames:e?.hasGivenNames===!0,lastName:e?.hasLastName===!0};for(const s of["givenNames","lastName"]){if(m[s])continue;const i=n?.[s];(!i||!i.trim())&&t.push(new o({fieldName:`userDetails.${s}`,reason:a}))}const u=!!n?.email&&n.email.trim().length>0,l=!!n?.phoneNumber&&n.phoneNumber.trim().length>0,r=e?.hasEmail===!0||e?.hasPhoneNumber===!0;return!u&&!l&&!r&&t.push(new o({fieldName:"userDetails",reason:e===void 0?"at least one of email or phoneNumber must be non blank":"at least one contact field must be bound by the token or supplied on the builder"})),t}
|