@smileid/usesmileid 12.2.0 → 12.2.1-SNAPSHOT.44
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 +0 -1
- package/README.md +39 -6
- package/dist/camera/bounding_box_metering_region.d.ts +10 -0
- package/dist/camera/bounding_box_metering_region.js +1 -0
- package/dist/camera/document_image_pipeline.d.ts +9 -0
- package/dist/camera/document_image_pipeline.js +1 -1
- package/dist/camera/imported_document_folder.d.ts +15 -0
- package/dist/camera/imported_document_folder.js +1 -0
- package/dist/camera/kamera_config_factory.d.ts +11 -1
- package/dist/camera/kamera_config_factory.js +1 -1
- package/dist/camera/use_smile_id_frame_stream.js +1 -1
- package/dist/camera/use_smile_id_kamera_camera_system.d.ts +46 -2
- package/dist/camera/use_smile_id_kamera_camera_system.js +1 -1
- package/dist/core/exception/use_smile_id_error_code.js +1 -1
- package/dist/core/exception/use_smile_id_network_exception.d.ts +10 -3
- package/dist/core/exception/use_smile_id_network_exception.js +1 -1
- package/dist/core/is_development_build.d.ts +6 -0
- package/dist/core/is_development_build.js +1 -0
- package/dist/core/models/config.d.ts +6 -0
- package/dist/core/models/ml_config.d.ts +1 -1
- package/dist/core/models/network_config.d.ts +4 -1
- package/dist/core/models/result.d.ts +11 -0
- package/dist/core/models/result.js +1 -1
- package/dist/core/perf/capture_perf_trace.d.ts +90 -0
- package/dist/core/perf/capture_perf_trace.js +1 -0
- package/dist/data/api/interceptors/logging_interceptor.js +1 -1
- package/dist/data/api/request_capture.d.ts +16 -0
- package/dist/data/api/request_capture.js +1 -0
- package/dist/data/api/smile_id_api.d.ts +1 -1
- package/dist/data/factory/networking_factory.d.ts +3 -1
- package/dist/data/factory/networking_factory.js +1 -1
- package/dist/data/model/captured_job.d.ts +49 -0
- package/dist/data/model/captured_job.js +1 -0
- package/dist/data/model/job_request.d.ts +1 -1
- package/dist/data/model/sealed_job_request.d.ts +15 -0
- package/dist/data/model/sealed_job_request.js +1 -0
- package/dist/data/repository/use_smile_id_repository.d.ts +7 -0
- package/dist/data/repository/use_smile_id_repository_impl.d.ts +29 -2
- package/dist/data/repository/use_smile_id_repository_impl.js +1 -1
- package/dist/data/security/job_package_crypto.d.ts +41 -0
- package/dist/data/security/job_package_crypto.js +1 -0
- package/dist/data/security/job_package_sealer.d.ts +35 -0
- package/dist/data/security/job_package_sealer.js +1 -0
- package/dist/data/security/signing_crypto.d.ts +34 -0
- package/dist/data/security/signing_crypto.js +1 -1
- package/dist/data/util/retry_util.d.ts +9 -6
- package/dist/data/util/retry_util.js +1 -1
- package/dist/debug/selfie_preview_debug_geometry.d.ts +13 -13
- package/dist/debug/selfie_preview_debug_geometry.js +1 -1
- package/dist/debug/use_smile_id_debug.d.ts +25 -2
- package/dist/debug/use_smile_id_debug.js +1 -1
- package/dist/debug/use_smile_id_document_preview_debug_overlay.js +1 -1
- package/dist/debug/use_smile_id_selfie_preview_debug_overlay.d.ts +5 -1
- package/dist/debug/use_smile_id_selfie_preview_debug_overlay.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/dist/l10n/strings_en.d.ts +3 -3
- package/dist/l10n/strings_en.js +2 -2
- package/dist/l10n/use_smile_id_localizations.d.ts +5 -7
- package/dist/l10n/use_smile_id_localizations.js +1 -1
- package/dist/metadata/metadata_configuration_mappers.d.ts +3 -5
- package/dist/metadata/metadata_configuration_mappers.js +1 -1
- package/dist/presentation/assets/si_dark_lighting_animation.json +1 -927
- package/dist/presentation/assets/si_device_orientation_animation.json +1 -850
- package/dist/presentation/assets/si_document_green_book_hero_animation.json +1 -4740
- package/dist/presentation/assets/si_document_hero_animation.json +1 -4870
- package/dist/presentation/assets/si_document_hero_back_animation.json +1 -2122
- package/dist/presentation/assets/si_document_passport_hero_animation.json +1 -14540
- package/dist/presentation/assets/si_ess_all_directions_hero_animation.json +1 -5906
- package/dist/presentation/assets/si_ess_animation_all_directions.json +1 -5906
- package/dist/presentation/assets/si_selfie_hero_animation.json +1 -1468
- package/dist/presentation/assets/si_smile_to_capture_animation.json +1 -1412
- package/dist/presentation/components/helpers/captured_image_uri.d.ts +11 -0
- package/dist/presentation/components/helpers/captured_image_uri.js +1 -0
- package/dist/presentation/components/helpers/captured_oval_region.d.ts +40 -0
- package/dist/presentation/components/helpers/captured_oval_region.js +1 -0
- package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.d.ts +35 -29
- package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.js +1 -1
- package/dist/presentation/components/helpers/selfie_interior_crop.js +1 -1
- package/dist/presentation/components/use_smile_id_capture_content.d.ts +5 -2
- package/dist/presentation/components/use_smile_id_capture_content.js +1 -1
- package/dist/presentation/components/use_smile_id_captured_document_image.js +1 -1
- package/dist/presentation/components/use_smile_id_captured_image_oval.d.ts +6 -0
- package/dist/presentation/components/use_smile_id_captured_image_oval.js +1 -1
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.d.ts +17 -7
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js +1 -1
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_direction_animation.d.ts +13 -14
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_direction_animation.js +1 -1
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_full_animation.js +1 -1
- package/dist/presentation/components/use_smile_id_face_oval_image.js +1 -1
- package/dist/presentation/flow/analytics/unsatisfied_reasons.d.ts +14 -0
- package/dist/presentation/flow/analytics/unsatisfied_reasons.js +1 -0
- package/dist/presentation/flow/analytics/use_smile_id_analytics_event.d.ts +8 -7
- package/dist/presentation/flow/analytics/use_smile_id_analytics_event.js +1 -1
- package/dist/presentation/flow/analytics/use_smile_id_analytics_event_kind.d.ts +9 -0
- package/dist/presentation/flow/analytics/use_smile_id_analytics_event_kind.js +1 -1
- package/dist/presentation/flow/config/flow_configuration_camera.d.ts +9 -0
- package/dist/presentation/flow/config/flow_configuration_camera.js +1 -0
- package/dist/presentation/flow/dsl/builder/config_builder.d.ts +6 -0
- package/dist/presentation/flow/dsl/builder/config_builder.js +1 -1
- package/dist/presentation/flow/dsl/builder/ml_builder.d.ts +1 -7
- package/dist/presentation/flow/dsl/builder/ml_builder.js +1 -1
- package/dist/presentation/flow/flow_navigation_manager.d.ts +39 -70
- package/dist/presentation/flow/flow_navigation_manager.js +1 -1
- package/dist/presentation/flow/flow_navigation_manager_analytics.d.ts +23 -0
- package/dist/presentation/flow/flow_navigation_manager_analytics.js +1 -0
- package/dist/presentation/flow/flow_navigation_manager_documents.d.ts +18 -0
- package/dist/presentation/flow/flow_navigation_manager_documents.js +1 -0
- package/dist/presentation/flow/flow_navigation_manager_job_request.d.ts +7 -0
- package/dist/presentation/flow/flow_navigation_manager_job_request.js +1 -0
- package/dist/presentation/flow/flow_navigation_manager_routing.d.ts +12 -0
- package/dist/presentation/flow/flow_navigation_manager_routing.js +1 -0
- package/dist/presentation/flow/flow_navigation_manager_submission.d.ts +17 -0
- package/dist/presentation/flow/flow_navigation_manager_submission.js +1 -0
- package/dist/presentation/flow/job_submission_state.d.ts +4 -0
- package/dist/presentation/flow/screen/helpers/face_session_config.d.ts +1 -1
- package/dist/presentation/flow/screen/helpers/failure_message.js +1 -1
- package/dist/presentation/flow/screen/orchestrated_capture_screen.js +1 -1
- package/dist/presentation/flow/screen/orchestrated_instructions_screen.js +1 -1
- package/dist/presentation/flow/screen/orchestrated_preview_screen.js +1 -1
- package/dist/presentation/flow/screen/orchestrated_processing_screen.js +1 -1
- package/dist/presentation/flow/use_smile_id_flow.js +1 -1
- package/dist/presentation/flow/validation/job_type_validator.js +1 -1
- package/dist/presentation/screen/helpers/capture_screen_helpers.d.ts +0 -12
- package/dist/presentation/screen/helpers/capture_screen_helpers.js +1 -1
- package/dist/presentation/screen/helpers/capture_ui_state.d.ts +33 -6
- package/dist/presentation/screen/helpers/capture_ui_state.js +1 -1
- package/dist/presentation/screen/helpers/dropped_frames.d.ts +6 -0
- package/dist/presentation/screen/helpers/dropped_frames.js +1 -0
- package/dist/presentation/screen/helpers/ess_turn_haptics.d.ts +2 -0
- package/dist/presentation/screen/helpers/ess_turn_haptics.js +1 -1
- package/dist/presentation/screen/helpers/instructions_screen_helpers.d.ts +5 -1
- package/dist/presentation/screen/helpers/instructions_screen_helpers.js +1 -1
- package/dist/presentation/screen/helpers/use_capture_orientation_lock.d.ts +23 -1
- package/dist/presentation/screen/helpers/use_capture_orientation_lock.js +1 -1
- package/dist/presentation/screen/helpers/use_debounced_feedback_key.d.ts +1 -1
- package/dist/presentation/screen/helpers/use_debounced_feedback_key.js +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.d.ts +4 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.js +1 -1
- package/dist/presentation/screen/use_smile_id_consent_screen.js +1 -1
- package/dist/presentation/screen/use_smile_id_preview_screen.d.ts +3 -0
- package/dist/presentation/screen/use_smile_id_preview_screen.js +1 -1
- package/dist/presentation/screen/use_smile_id_processing_screen.d.ts +3 -0
- package/dist/presentation/screen/use_smile_id_processing_screen.js +1 -1
- package/dist/presentation/utils/locale_utils.d.ts +3 -6
- package/dist/presentation/utils/locale_utils.js +1 -1
- package/dist/testing/scenario_expectations.d.ts +7 -2
- package/dist/testing/scenario_expectations.js +1 -1
- package/dist/testing/scripted_scan_results.d.ts +2 -1
- package/dist/testing/scripted_scan_results.js +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{SmileIDAPI as
|
|
1
|
+
"use strict";import{SmileIDAPI as I,buildAxiosClient as d,SmileAuthInterceptor as k,attachLoggingInterceptor as y}from"../api/smile_id_api";import{createGzipInterceptor as w}from"../api/interceptors/gzip_interceptor";import{captureAdapter as C}from"../api/request_capture";import{SmileRequestMacInterceptor as S}from"../api/interceptors/smile_request_mac_interceptor";import{SmileIntegrityInterceptor as A}from"../api/interceptors/smile_integrity_interceptor";import{TokenRefreshInterceptor as P}from"../api/interceptors/token_refresh_interceptor";import{TokenHolder as F}from"../api/auth/token_holder";import{SmileIDCryptoManager as T}from"../security/smile_id_crypto_manager";import{SecurityPolicy as b}from"../security/security_policy";import{decodeSecurityPolicyBitCode as x}from"../util/use_smile_id_token_decoder";import{Attestation as M}from"@smileid/usesmileid_bridge";import{SigningSecretProvider as R}from"@smileid/usesmileid_bridge/internal";import{MissingPartnerConfigException as c}from"../../core/exception";import{UseSmileIDMetadataFactory as E}from"../../metadata/use_smile_id_metadata_factory";export function makeNetworkingFactory(t,o=()=>R.signingSecret(),a={}){const e=t.config?.partnerConfig;if(!e||e.partnerId.trim()==="")throw c.missingPartnerId();if(!t.config?.token||t.config.token.trim()==="")throw c.missingAuthToken();const n=/[\u0000-\u001F\u007F-\u009F]/;if(n.test(e.partnerId))throw c.controlCharacterPartnerId();if(n.test(t.config.token))throw c.controlCharacterAuthToken();if(!isResolvableCallbackUrl(e.callbackUrl??""))throw c.unresolvableCallbackUrl();const i=a.capturing===!0,r=d(t);i&&(r.defaults.adapter=C);const u=new E,h=new I(r,e);i||M.warmUp().catch(()=>{});const p=new F(t.config?.token??""),f=b.fromBitCode(x(t.config?.token??""));i||w().attach(r);const m=t.config?.logging;m&&!i&&y(r,m),new A({securityPolicy:f}).attach(r);const g=new T(o());if(new S({cryptoManager:g,securityPolicy:f}).attach(r),new k({partnerConfig:e,tokenHolder:p}).attach(r),new P({tokenHolder:p,onTokenExpired:t.config?.onTokenExpired}).attach(r),t.interceptors&&!i)for(const l of t.interceptors){const s=l;typeof s=="function"?s(r):typeof s?.attach=="function"&&s.attach(r)}return{api:h,client:r,metadata:u}}const U=/^callback_[0-9a-hjkmnp-tv-z]{26}$/;export function isResolvableCallbackUrl(t){if(t===""||U.test(t))return!0;const o=t.indexOf("://");if(o<=0)return!1;const a=t.slice(0,o).toLowerCase();if(a!=="http"&&a!=="https")return!1;const n=((t.slice(o+3).split(/[/?#]/)[0]??"").split("@").pop()??"").split(":")[0]??"";return n!==""&&!/[\s\u0000-\u001F\u007F-\u009F]/.test(n)}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { JobRequest } from "./job_request";
|
|
2
|
+
import type { SealedJobRequest } from "./sealed_job_request";
|
|
3
|
+
/**
|
|
4
|
+
* Everything the flow captured for a job that was handed back instead of
|
|
5
|
+
* submitted (`c.skipApiSubmission = true`). Forward it to Smile ID from your
|
|
6
|
+
* own backend: send the job type's `/v3` request with `headers` as request
|
|
7
|
+
* headers, the images as their usual form parts, and `encryptedPackage`,
|
|
8
|
+
* `packageNonce` and `packageTag` as the `encrypted_package`, `package_nonce`
|
|
9
|
+
* and `package_tag` form fields.
|
|
10
|
+
*
|
|
11
|
+
* Everything else the SDK would have sent (the consent record, the metadata,
|
|
12
|
+
* the signature and the other `smileid-*` headers) travels inside
|
|
13
|
+
* `encryptedPackage`, encrypted for Smile ID: forward it as is, since the
|
|
14
|
+
* server rejects a package that was changed. The image files must
|
|
15
|
+
* reach the server byte-for-byte too, since they carry an integrity seal that
|
|
16
|
+
* a re-encode invalidates. The SDK deletes neither the files nor
|
|
17
|
+
* `sessionDirectory` in this mode: once the job has been forwarded, delete
|
|
18
|
+
* every file it lists, then `sessionDirectory` and everything left in it.
|
|
19
|
+
* `sessionDirectory` is the SDK's session folder holding every listed file,
|
|
20
|
+
* and absent when they don't share one (the capture sessions can store them
|
|
21
|
+
* apart), the folder isn't one of the SDK's own, or the job captured no files
|
|
22
|
+
* (Enhanced KYC). A document imported from the gallery or taken with the manual
|
|
23
|
+
* shutter lives in the app's cache, which the OS may clear under storage
|
|
24
|
+
* pressure, so forward such a job promptly.
|
|
25
|
+
*/
|
|
26
|
+
export type CapturedJob = {
|
|
27
|
+
readonly jobType: JobRequest["jobType"];
|
|
28
|
+
readonly sessionId: string;
|
|
29
|
+
readonly sessionDirectory?: string;
|
|
30
|
+
readonly selfieImage?: string;
|
|
31
|
+
readonly livenessImages: readonly string[];
|
|
32
|
+
readonly frontDocument?: string;
|
|
33
|
+
readonly backDocument?: string;
|
|
34
|
+
/** The visa page of a Residency Document Verification job, sent as the `visa` part. */
|
|
35
|
+
readonly visaDocument?: string;
|
|
36
|
+
/** `smileid-token`, `smileid-source-sdk` and `smileid-source-sdk-version`, to send as they are. */
|
|
37
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
38
|
+
/** The sealed request, hex-encoded; the `encrypted_package` form field. */
|
|
39
|
+
readonly encryptedPackage: string;
|
|
40
|
+
/** The package's AES-GCM nonce, hex-encoded; the `package_nonce` form field. */
|
|
41
|
+
readonly packageNonce: string;
|
|
42
|
+
/** The package's AES-GCM authentication tag, hex-encoded; the `package_tag` form field. */
|
|
43
|
+
readonly packageTag: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The partner-facing hand-over of `request`: the files its legs produced and
|
|
47
|
+
* the `sealed` request, with nothing the partner supplied echoed back.
|
|
48
|
+
*/
|
|
49
|
+
export declare function capturedJobFrom(request: JobRequest, sessionId: string, sealed: SealedJobRequest): CapturedJob;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{JobType as t}from"../../core/models/job_type";export function capturedJobFrom(e,a,c){const n=m(e),i=r([n.selfieImage,...n.livenessImages,n.frontDocument,n.backDocument,n.visaDocument]);return Object.freeze({jobType:e.jobType,sessionId:a,...i!==void 0?{sessionDirectory:i}:{},...n.selfieImage!==void 0?{selfieImage:n.selfieImage}:{},livenessImages:Object.freeze([...n.livenessImages]),...n.frontDocument!==void 0?{frontDocument:n.frontDocument}:{},...n.backDocument!==void 0?{backDocument:n.backDocument}:{},...n.visaDocument!==void 0?{visaDocument:n.visaDocument}:{},headers:c.headers,encryptedPackage:c.encryptedPackage,packageNonce:c.packageNonce,packageTag:c.packageTag})}function m(e){switch(e.jobType){case t.smartSelfieEnrollment:case t.smartSelfieAuthentication:case t.biometricKyc:return{selfieImage:e.selfieImage,livenessImages:e.livenessImages};case t.documentVerification:case t.enhancedDocumentVerification:return{selfieImage:e.selfieImage,livenessImages:e.livenessImages,frontDocument:e.frontDocument,...e.backDocument!==void 0?{backDocument:e.backDocument}:{}};case t.residencyDocumentVerification:return{selfieImage:e.selfieImage,livenessImages:e.livenessImages,frontDocument:e.frontDocument,visaDocument:e.visaDocument};case t.enhancedKyc:case t.bvn:return{livenessImages:[]}}}function o(e){const a=e.lastIndexOf("/");if(a<=0)return;const c=e.slice(0,a);return/^[a-z][a-z0-9+.-]*:\/*$/i.test(c)?void 0:c}const f="UseSmileID";function r(e){const a=new Set(e.filter(s=>s!==void 0).map(o));if(a.size!==1)return;const[c]=a;if(c===void 0)return;const n=o(c);return n?.slice(n.lastIndexOf("/")+1)===f?c:void 0}
|
|
@@ -48,7 +48,7 @@ export type SmartSelfieEnrollmentRequest = BaseJobRequest & {
|
|
|
48
48
|
* from the `sdk_config` JWT claim's `livenessImageCount` when
|
|
49
49
|
* present (`livenessImageCount` maps to `numSamples = count + 1`,
|
|
50
50
|
* so `expectedLivenessImageCount = count`); otherwise the
|
|
51
|
-
* `
|
|
51
|
+
* native default (`FaceCaptureDefaults.numSamples`) minus one (`numSamples - 1`).
|
|
52
52
|
* For Enhanced Smart Selfie this is always
|
|
53
53
|
* `EnhancedSmartSelfieConfig.EXPECTED_LIVENESS_IMAGE_COUNT` (7).
|
|
54
54
|
*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A job request signed and sealed for the partner to forward
|
|
3
|
+
* (`c.skipApiSubmission = true`), produced by `JobPackageSealer`. The images
|
|
4
|
+
* travel beside it, unencrypted.
|
|
5
|
+
*
|
|
6
|
+
* `headers` go on the forwarded request as they are; `encryptedPackage`,
|
|
7
|
+
* `packageNonce` and `packageTag` are hex strings sent as the
|
|
8
|
+
* `encrypted_package`, `package_nonce` and `package_tag` form fields.
|
|
9
|
+
*/
|
|
10
|
+
export type SealedJobRequest = {
|
|
11
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
12
|
+
readonly encryptedPackage: string;
|
|
13
|
+
readonly packageNonce: string;
|
|
14
|
+
readonly packageTag: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";export{};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SdkConfig } from "../model/sdk_config";
|
|
2
2
|
import type { JobRequest } from "../model/job_request";
|
|
3
3
|
import type { JobSubmissionResponse } from "../model/job_submission_response";
|
|
4
|
+
import type { SealedJobRequest } from "../model/sealed_job_request";
|
|
4
5
|
import type { UseSmileIDResult } from "../../core/models/result";
|
|
5
6
|
/**
|
|
6
7
|
* Repository abstraction over `SmileIDAPI` + retry policy.
|
|
@@ -16,4 +17,10 @@ export interface UseSmileIDRepository {
|
|
|
16
17
|
* discriminator drives dispatch to the matching API method.
|
|
17
18
|
*/
|
|
18
19
|
submit(request: JobRequest, sdkConfig?: SdkConfig | undefined): Promise<UseSmileIDResult<JobSubmissionResponse>>;
|
|
20
|
+
/**
|
|
21
|
+
* Build and sign the request `submit` would send for `request` without
|
|
22
|
+
* sending it, and seal it into the package a skipped submission hands the
|
|
23
|
+
* partner to forward (`c.skipApiSubmission`).
|
|
24
|
+
*/
|
|
25
|
+
seal(request: JobRequest, sdkConfig?: SdkConfig | undefined): Promise<UseSmileIDResult<SealedJobRequest>>;
|
|
19
26
|
}
|
|
@@ -4,6 +4,13 @@ import type { SmileIDAPI } from "../api/smile_id_api";
|
|
|
4
4
|
import type { RetryConfig } from "../../core/models/network_config";
|
|
5
5
|
import type { UseSmileIDResult } from "../../core/models/result";
|
|
6
6
|
import type { SdkConfig } from "../model/sdk_config";
|
|
7
|
+
import type { SealedJobRequest } from "../model/sealed_job_request";
|
|
8
|
+
import type { JobPackageSealer } from "../security/job_package_sealer";
|
|
9
|
+
/** What a skipped submission seals its request with. */
|
|
10
|
+
export type CaptureDependencies = {
|
|
11
|
+
readonly api: SmileIDAPI;
|
|
12
|
+
readonly sealer: JobPackageSealer;
|
|
13
|
+
};
|
|
7
14
|
/**
|
|
8
15
|
* Concrete repository.
|
|
9
16
|
*
|
|
@@ -15,15 +22,35 @@ import type { SdkConfig } from "../model/sdk_config";
|
|
|
15
22
|
export declare class UseSmileIDRepositoryImpl {
|
|
16
23
|
private readonly _api;
|
|
17
24
|
private readonly _retry;
|
|
18
|
-
|
|
25
|
+
private readonly _capture;
|
|
26
|
+
/**
|
|
27
|
+
* `capture` is what a skipped submission seals its request with: the capture
|
|
28
|
+
* client (`makeNetworkingFactory(config, secret, { capturing: true })`, the
|
|
29
|
+
* same interceptors stopped before the network) and the sealer.
|
|
30
|
+
*/
|
|
31
|
+
constructor(api: SmileIDAPI, retry: RetryConfig, capture?: CaptureDependencies);
|
|
32
|
+
/**
|
|
33
|
+
* Sends `request` through the capture client, which runs the same
|
|
34
|
+
* interceptors as a submission and then stops the request
|
|
35
|
+
* (`CapturedRequest`) instead of sending it, so the sealed request is
|
|
36
|
+
* compressed and signed exactly as `submit`'s first attempt would be.
|
|
37
|
+
*/
|
|
38
|
+
seal(request: JobRequest, sdkConfig?: SdkConfig | undefined): Promise<UseSmileIDResult<SealedJobRequest>>;
|
|
19
39
|
submit(request: JobRequest, sdkConfig?: SdkConfig | undefined): Promise<UseSmileIDResult<JobSubmissionResponse>>;
|
|
40
|
+
/**
|
|
41
|
+
* Pre-flight payload compression — best effort: shrink the captured JPEGs under the Lambda
|
|
42
|
+
* cap before transmission (runs once, outside the retry loop), but never block submission, so
|
|
43
|
+
* any compression failure ships the captured images as-is and lets the server validate. The
|
|
44
|
+
* per-image original + final compressed sizes are stamped onto the metadata for telemetry.
|
|
45
|
+
*/
|
|
46
|
+
private _compressed;
|
|
20
47
|
/**
|
|
21
48
|
* Pre-flight payload compression entry — extracts the captured image URIs for the job type and
|
|
22
49
|
* compresses the bundle under the transmission cap (metadata is measured, never modified).
|
|
23
50
|
* No-op for job types that carry no captured images (Enhanced KYC / BVN).
|
|
24
51
|
*/
|
|
25
52
|
private _compressPayload;
|
|
26
|
-
private
|
|
53
|
+
private static _send;
|
|
27
54
|
/**
|
|
28
55
|
* Translate any caught error into the typed
|
|
29
56
|
* [UseSmileIDNetworkException] hierarchy. Delegates to the canonical
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{failure as
|
|
1
|
+
"use strict";import{failure as a,success as m}from"../../core/models/result";import{JobType as r}from"../../core/models/job_type";import{withRetry as d}from"../util/retry_util";import{InvalidFieldValueException as l}from"../../core/exception/job_validation_exceptions";import{UseSmileIDValidationException as f}from"../../core/exception/use_smile_id_validation_exception";import{mapAxiosError as p}from"../../core/exception/use_smile_id_network_exception";import{PayloadCompressionRunner as c}from"../compression/payload_compression_runner";import{CapturedRequest as _}from"../api/request_capture";function h(s){const e=[],t=(i,n)=>{n!==void 0&&n>0&&e.push({name:i,value:String(n)})};return t("selfie_original_size",s.selfieOriginalMb),t("selfie_final_compressed_size",s.selfieFinalMb),t("document_front_original_size",s.frontOriginalMb),t("document_front_final_compressed_size",s.frontFinalMb),t("document_back_original_size",s.backOriginalMb),t("document_back_final_compressed_size",s.backFinalMb),t("document_visa_original_size",s.visaOriginalMb),t("document_visa_final_compressed_size",s.visaFinalMb),e}export class UseSmileIDRepositoryImpl{_api;_retry;_capture;constructor(e,t,i){this._api=e,this._retry=t,this._capture=i}async seal(e,t){if(e.jobType===r.bvn)return a(new l({fieldName:"jobType",reason:"BVN submission is not yet supported"}));const i=this._capture;if(!i)return a(new Error("No capture client to seal the request through"));const n=await this._compressed(e,t);try{return await UseSmileIDRepositoryImpl._send(i.api,n,0),a(new Error("The capture client sent the request instead of stopping it"))}catch(o){if(o instanceof _)try{return m(await i.sealer.seal(o.config))}catch(u){return a(u)}return o instanceof f?a(o):a(this._mapError(o))}}async submit(e,t){if(e.jobType===r.bvn)return a(new l({fieldName:"jobType",reason:"BVN submission is not yet supported"}));const i=await this._compressed(e,t);try{const n=await d(this._retry,o=>UseSmileIDRepositoryImpl._send(this._api,i,o));return m(n)}catch(n){return n instanceof f?a(n):a(this._mapError(n))}}async _compressed(e,t){try{const i=await this._compressPayload(e,t),n=i?h(i):[];if(n.length>0)return{...e,metadata:e.metadata.withAdditionalEntries(n)}}catch{}return e}async _compressPayload(e,t){const i=JSON.stringify(e.metadata.toEntries()).length;switch(e.jobType){case r.smartSelfieEnrollment:case r.smartSelfieAuthentication:case r.biometricKyc:return c.compress({selfieImage:e.selfieImage,livenessImages:e.livenessImages,metadataBytes:i,sdkConfig:t});case r.documentVerification:case r.enhancedDocumentVerification:return c.compress({selfieImage:e.selfieImage,livenessImages:e.livenessImages,frontDocument:e.frontDocument,backDocument:e.backDocument,metadataBytes:i,sdkConfig:t});case r.residencyDocumentVerification:return c.compress({selfieImage:e.selfieImage,livenessImages:e.livenessImages,frontDocument:e.frontDocument,visaDocument:e.visaDocument,metadataBytes:i,sdkConfig:t});default:return}}static _send(e,t,i){const n=i>0?{...t,metadata:t.metadata.withAdditionalEntries([{name:"network_retries",value:String(i)}])}:t;switch(n.jobType){case r.smartSelfieEnrollment:return e.smartSelfieEnrollment(n);case r.smartSelfieAuthentication:return e.smartSelfieAuthentication(n);case r.enhancedKyc:return e.enhancedKyc(n);case r.biometricKyc:return e.biometricKyc(n);case r.documentVerification:return e.documentVerification(n);case r.enhancedDocumentVerification:return e.enhancedDocumentVerification(n);case r.residencyDocumentVerification:return e.residencyDocumentVerification(n)}}_mapError(e){return p(e)}}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** GCM's standard 96-bit nonce, fresh for every package. */
|
|
2
|
+
export declare const JOB_PACKAGE_NONCE_BYTES = 12;
|
|
3
|
+
/** GCM's full 128-bit authentication tag. */
|
|
4
|
+
export declare const JOB_PACKAGE_TAG_BYTES = 16;
|
|
5
|
+
/** AES-256's key length, which the secret's UTF-8 bytes must have. */
|
|
6
|
+
export declare const JOB_PACKAGE_KEY_BYTES = 32;
|
|
7
|
+
/** The encrypted bytes, and the nonce and tag the server needs to open them. */
|
|
8
|
+
export type SealedBytes = {
|
|
9
|
+
readonly ciphertext: Uint8Array;
|
|
10
|
+
readonly nonce: Uint8Array;
|
|
11
|
+
readonly tag: Uint8Array;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* AES-256-GCM sealing for the job package a skipped submission hands the
|
|
15
|
+
* partner (`c.skipApiSubmission = true`). The server opens it with the same SDK
|
|
16
|
+
* encryption secret, and GCM's tag fails on any changed byte, so the partner
|
|
17
|
+
* forwards the package as is. The secret ships in the app, so this keeps the
|
|
18
|
+
* package from casual reading, not from someone who extracts it.
|
|
19
|
+
*
|
|
20
|
+
* The secret is the secret rotator's `sdk_encryption` for this SDK version:
|
|
21
|
+
* 32 characters since secret-rotator #27, whose UTF-8 bytes are the AES-256 key
|
|
22
|
+
* as they are. The server keys the cipher the same way
|
|
23
|
+
* (`Buffer.from(secret, 'utf8')` in lambda's `decrypt.ts`). Runs on the Web
|
|
24
|
+
* Crypto provider the request signing uses.
|
|
25
|
+
*/
|
|
26
|
+
export declare const JobPackageCrypto: {
|
|
27
|
+
/**
|
|
28
|
+
* The AES key for `secret`: its UTF-8 bytes. Throws `SmileIDCryptoError`
|
|
29
|
+
* unless they are `JOB_PACKAGE_KEY_BYTES` long, as a secret bundled before
|
|
30
|
+
* secret-rotator #27 is not.
|
|
31
|
+
*/
|
|
32
|
+
key(secret: string): Uint8Array;
|
|
33
|
+
/**
|
|
34
|
+
* Encrypts `plaintext` under `key` with `nonce`, a fresh random one unless a
|
|
35
|
+
* test pins it. Web Crypto appends the tag to the ciphertext; it is split off
|
|
36
|
+
* so the two travel separately.
|
|
37
|
+
*/
|
|
38
|
+
seal(plaintext: Uint8Array, key: Uint8Array, nonce?: Uint8Array): Promise<SealedBytes>;
|
|
39
|
+
};
|
|
40
|
+
/** Lowercase hex, the encoding every field of the job package travels in. */
|
|
41
|
+
export declare function toHex(bytes: Uint8Array): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{SmileIDCryptoError as a}from"./smile_id_crypto_error";import{exactBuffer as c,requireSubtle as l,utf8Bytes as u}from"./signing_crypto";export const JOB_PACKAGE_NONCE_BYTES=12,JOB_PACKAGE_TAG_BYTES=16,JOB_PACKAGE_KEY_BYTES=32;function p(){try{const t=require("react-native-quick-crypto"),r=t.getRandomValues??t.default?.getRandomValues;if(r)return r}catch{}const e=globalThis.crypto;if(e?.getRandomValues)return e.getRandomValues.bind(e);throw new a("No secure random source available to seal the job package. Install the `react-native-quick-crypto` peer dependency, or expose a global `crypto`.")}export const JobPackageCrypto={key(e){const t=u(e);if(t.length!==JOB_PACKAGE_KEY_BYTES)throw new a("The SDK encryption secret is not the 32 characters AES-256 takes");return t},async seal(e,t,r=p()(new Uint8Array(JOB_PACKAGE_NONCE_BYTES))){const s=l();try{const o=await s.importKey("raw",c(t),"AES-GCM",!1,["encrypt"]),n=new Uint8Array(await s.encrypt({name:"AES-GCM",iv:c(r),tagLength:JOB_PACKAGE_TAG_BYTES*8},o,c(e))),i=n.length-JOB_PACKAGE_TAG_BYTES;return{ciphertext:n.slice(0,i),nonce:r.slice(),tag:n.slice(i)}}catch(o){throw o instanceof a?o:new a(`Sealing the job package failed: ${String(o)}`)}}};export function toHex(e){let t="";for(const r of e)t+=r.toString(16).padStart(2,"0");return t}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { InternalAxiosRequestConfig } from "axios";
|
|
2
|
+
import type { SealedJobRequest } from "../model/sealed_job_request";
|
|
3
|
+
/**
|
|
4
|
+
* Seals a signed job request into the package a skipped submission hands the
|
|
5
|
+
* partner.
|
|
6
|
+
*
|
|
7
|
+
* `seal` takes the request exactly as the SDK would have sent it, after the
|
|
8
|
+
* auth, request-MAC and integrity interceptors ran, so the signing is the same
|
|
9
|
+
* as for a submission. Everything but the images is encrypted together as
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* { "headers": { every smileid-* header, lowercase }, "params": { every non-file form field } }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* and the partner forwards the ciphertext, nonce and tag as the
|
|
16
|
+
* `encrypted_package`, `package_nonce` and `package_tag` form fields alongside
|
|
17
|
+
* the images. The server opens the package and merges it back into the request
|
|
18
|
+
* before anything downstream reads it, so `params` carries each field the way
|
|
19
|
+
* the server's form parser produces it: the JSON fields parsed, every other
|
|
20
|
+
* field its string.
|
|
21
|
+
*
|
|
22
|
+
* The partner sends the token and source SDK headers
|
|
23
|
+
* (`SealedJobRequest.headers`) in the clear: the server authorises the request
|
|
24
|
+
* with the token and picks the secret to open the package with by the source
|
|
25
|
+
* SDK and its version.
|
|
26
|
+
*/
|
|
27
|
+
export declare class JobPackageSealer {
|
|
28
|
+
private readonly _encryptionSecret;
|
|
29
|
+
constructor(encryptionSecret: () => string);
|
|
30
|
+
/**
|
|
31
|
+
* Seals `config`. Throws `SmileIDCryptoError` in a build without the secret,
|
|
32
|
+
* or with one that is not 32 characters.
|
|
33
|
+
*/
|
|
34
|
+
seal(config: InternalAxiosRequestConfig): Promise<SealedJobRequest>;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{JobPackageCrypto as f,toHex as o}from"./job_package_crypto";import{utf8Bytes as d}from"./signing_crypto";import{SmileIDCryptoError as u}from"./smile_id_crypto_error";const m="smileid-",S=new Set(["smileid-token","smileid-source-sdk","smileid-source-sdk-version"]),h=new Set(["partner_params","metadata","failure_reason","consent","user_details","payload","countries","aliases","id_types"]);function y(e){return typeof e=="object"&&e!==null&&typeof e.getParts=="function"}export class JobPackageSealer{_encryptionSecret;constructor(t){this._encryptionSecret=t}async seal(t){const r=this._encryptionSecret();if(r.trim()==="")throw new u("The build carries no SDK encryption secret; run ./secrets.sh");const i={};for(const[s,n]of Object.entries(t.headers?.toJSON?.()??t.headers??{})){const l=s.toLowerCase();l.startsWith(m)&&n!=null&&(i[l]=String(n))}const c=Object.fromEntries(Object.entries(i).sort(([s],[n])=>s.localeCompare(n))),p=JSON.stringify({headers:c,params:g(t.data)}),a=await f.seal(d(p),f.key(r));return Object.freeze({headers:Object.freeze(Object.fromEntries(Object.entries(c).filter(([s])=>S.has(s)))),encryptedPackage:o(a.ciphertext),packageNonce:o(a.nonce),packageTag:o(a.tag)})}}function g(e){const t={};if(!y(e))return t;for(const r of e.getParts())r.uri!=null||r.string===void 0||(t[r.fieldName]=h.has(r.fieldName)?O(r.string):r.string);return t}function O(e){try{return JSON.parse(e)}catch{return e}}
|
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
export declare function utf8Bytes(input: string): Uint8Array;
|
|
3
3
|
/** Standard-alphabet Base64 of [bytes] (no line wrap). */
|
|
4
4
|
export declare function base64Encode(bytes: Uint8Array): string;
|
|
5
|
+
/**
|
|
6
|
+
* Minimal structural type for the Web Crypto `SubtleCrypto` surface this module
|
|
7
|
+
* uses. Declared locally because the workspace tsconfig omits the DOM lib.
|
|
8
|
+
*/
|
|
9
|
+
export interface SubtleLike {
|
|
10
|
+
importKey(format: "raw", keyData: ArrayBuffer, algorithm: {
|
|
11
|
+
name: "HMAC";
|
|
12
|
+
hash: "SHA-256";
|
|
13
|
+
} | "PBKDF2" | "AES-GCM", extractable: boolean, keyUsages: readonly ("sign" | "deriveBits" | "encrypt")[]): Promise<unknown>;
|
|
14
|
+
encrypt(algorithm: {
|
|
15
|
+
name: "AES-GCM";
|
|
16
|
+
iv: ArrayBuffer;
|
|
17
|
+
tagLength: number;
|
|
18
|
+
}, key: unknown, data: ArrayBuffer): Promise<ArrayBuffer>;
|
|
19
|
+
sign(algorithm: "HMAC", key: unknown, data: ArrayBuffer): Promise<ArrayBuffer>;
|
|
20
|
+
deriveBits(algorithm: {
|
|
21
|
+
name: "PBKDF2";
|
|
22
|
+
salt: ArrayBuffer;
|
|
23
|
+
iterations: number;
|
|
24
|
+
hash: "SHA-256";
|
|
25
|
+
}, key: unknown, length: number): Promise<ArrayBuffer>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The resolved Web Crypto provider, shared with the job-package sealing.
|
|
29
|
+
*
|
|
30
|
+
* @throws {SmileIDCryptoError} when no provider is available.
|
|
31
|
+
*/
|
|
32
|
+
export declare function requireSubtle(): SubtleLike;
|
|
33
|
+
/**
|
|
34
|
+
* The exact bytes of [bytes] as a standalone `ArrayBuffer`. Reading `.buffer`
|
|
35
|
+
* directly is wrong when the view covers only part of a larger buffer (a
|
|
36
|
+
* `subarray` / `slice`), which would feed the surrounding bytes into PBKDF2/HMAC.
|
|
37
|
+
*/
|
|
38
|
+
export declare function exactBuffer(bytes: Uint8Array): ArrayBuffer;
|
|
5
39
|
/**
|
|
6
40
|
* Low-level signing primitives: PBKDF2 key derivation and HMAC-SHA256. Stateless
|
|
7
41
|
* and deterministic, so the server reproduces the same key and MAC from the same
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{SmileIDCryptoError as a}from"./smile_id_crypto_error";const
|
|
1
|
+
"use strict";import{SmileIDCryptoError as a}from"./smile_id_crypto_error";const l=32,u=2e5;export function utf8Bytes(t){return new TextEncoder().encode(t)}const c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";export function base64Encode(t){let r="";for(let e=0;e<t.length;e+=3){const n=t[e],o=e+1<t.length?t[e+1]:0,s=e+2<t.length?t[e+2]:0;r+=c[n>>2],r+=c[(n&3)<<4|o>>4],r+=e+1<t.length?c[(o&15)<<2|s>>6]:"=",r+=e+2<t.length?c[s&63]:"="}return r}let i;function f(){if(i)return i;const t=globalThis.crypto?.subtle;if(t)return i=t,i;try{const r=require("react-native-quick-crypto"),e=r.subtle??r.webcrypto?.subtle??r.default?.subtle??r.default?.webcrypto?.subtle;if(e)return i=e,i}catch{}return null}export function requireSubtle(){const t=f();if(!t)throw new a("No Web Crypto provider available for request signing. Install the `react-native-quick-crypto` peer dependency (with its peers `react-native-nitro-modules` and `react-native-quick-base64`), or expose a global `crypto.subtle`.");return t}export function exactBuffer(t){return t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)}export const SigningCrypto={async deriveKey(t,r){const e=requireSubtle();try{const n=await e.importKey("raw",exactBuffer(utf8Bytes(t)),"PBKDF2",!1,["deriveBits"]),o=await e.deriveBits({name:"PBKDF2",salt:exactBuffer(r),iterations:u,hash:"SHA-256"},n,l*8);return new Uint8Array(o)}catch(n){throw n instanceof a?n:new a(`Key derivation failed: ${String(n)}`)}},async hmacSha256(t,r){const e=requireSubtle();try{const n=await e.importKey("raw",exactBuffer(t),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await e.sign("HMAC",n,exactBuffer(r));return new Uint8Array(o)}catch(n){throw n instanceof a?n:new a(`MAC computation failed: ${String(n)}`)}}};
|
|
@@ -11,16 +11,19 @@ import { type RetryConfig } from "../../core/models/network_config";
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function withRetry<T>(config: RetryConfig, block: (attempt: number) => Promise<T>): Promise<T>;
|
|
13
13
|
/**
|
|
14
|
-
* Network-class failure check. Returns `true` for
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Network-class failure check. Returns `true` for a 5xx, 408 or 429
|
|
15
|
+
* response, and for any failure with none but a cancelled request: React
|
|
16
|
+
* Native's XHR adapter reports a connection failure, a failed TLS handshake
|
|
17
|
+
* and a redirect loop alike as `ERR_NETWORK`, so they can't be told apart.
|
|
18
|
+
* Other 4xx responses and non-axios errors (programming bugs) are not retried.
|
|
18
19
|
*/
|
|
19
20
|
export declare function isRetryable(error: unknown): boolean;
|
|
20
21
|
/**
|
|
21
22
|
* Compute the delay (in milliseconds) before the next retry attempt.
|
|
22
23
|
* `attempt` is 0-indexed (0 = first retry, after the initial call
|
|
23
24
|
* failed). Config delays are in seconds; the return value is converted
|
|
24
|
-
* to ms so it can drive `setTimeout` directly.
|
|
25
|
+
* to ms so it can drive `setTimeout` directly. Jitter moves the delay by
|
|
26
|
+
* up to `jitter` of itself either way (`jitterDraw` in [-1, 1], random by
|
|
27
|
+
* default), with jitter clamped to [0, 1] and never past `maxDelay`.
|
|
25
28
|
*/
|
|
26
|
-
export declare function computeDelayMs(config: RetryConfig, attempt: number): number;
|
|
29
|
+
export declare function computeDelayMs(config: RetryConfig, attempt: number, jitterDraw?: number): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{isAxiosError as
|
|
1
|
+
"use strict";import{isAxiosError as m}from"axios";import{CANCELED as u}from"../../core/exception/use_smile_id_network_exception";import{RetryStrategy as p}from"../../core/models/network_config";export async function withRetry(t,e){if(t.strategy===p.none||t.maxAttempts<=1)return e(0);let s;for(let r=0;r<t.maxAttempts;r++)try{return await e(r)}catch(a){if(s=a,!isRetryable(a)||r===t.maxAttempts-1)throw a;const n=computeDelayMs(t,r);await l(n)}throw s}export function isRetryable(t){if(!m(t)||t.code===u)return!1;if(!t.response)return!0;const e=t.response.status;return e>=500&&e<600||e===408||e===429}export function computeDelayMs(t,e,s=Math.random()*2-1){if(t.strategy==="none")return 0;if(t.strategy==="fixed")return t.fixedDelay*1e3;const r=t.maxDelay*1e3,a=t.initialDelay*1e3*Math.pow(t.multiplier,e),n=Math.min(a,r),i=s*Math.min(1,Math.max(0,t.jitter)),o=n*(1+i);return Math.max(0,Math.min(r,Math.round(o)))}function l(t){return new Promise(e=>setTimeout(e,t))}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { capturedOvalRegion } from "../presentation/components/helpers/captured_oval_region";
|
|
2
|
+
/** How the preview places the selfie in its oval. */
|
|
3
|
+
export type SelfiePreviewPlacement = ReturnType<typeof capturedOvalRegion>;
|
|
1
4
|
/** A width × height in layout units (or image pixels). */
|
|
2
5
|
export type DebugSize = {
|
|
3
6
|
readonly width: number;
|
|
@@ -24,10 +27,10 @@ export type SelfiePreviewPanelGeometry = {
|
|
|
24
27
|
/**
|
|
25
28
|
* Pure geometry for the selfie preview-screen debug overlay.
|
|
26
29
|
*
|
|
27
|
-
* The preview screen draws the captured selfie
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
* reaches the user. These helpers compute where the full frame, the ellipse
|
|
30
|
+
* The preview screen draws the captured selfie into an oval-sized box through
|
|
31
|
+
* `capturedOvalRegion` and clips it to an ellipse: the region the capture oval
|
|
32
|
+
* showed fills the box, and the frame's interior always covers it, so the
|
|
33
|
+
* border ring never reaches the user. These helpers compute where the full frame, the ellipse
|
|
31
34
|
* the user actually sees, and the interior crop land — in the oval box's
|
|
32
35
|
* coordinate space for the in-place marking (`inPlace`) and in a fit-scaled
|
|
33
36
|
* thumbnail's coordinate space for the full-frame panel (`panel`).
|
|
@@ -38,18 +41,15 @@ export declare const SelfiePreviewDebugGeometry: {
|
|
|
38
41
|
readonly plannedBorderFraction: 0.15;
|
|
39
42
|
/**
|
|
40
43
|
* Maps the captured frame onto the preview screen the way the production
|
|
41
|
-
* layout does
|
|
42
|
-
*
|
|
43
|
-
* when either size is degenerate.
|
|
44
|
+
* layout does, through `placement`. Returns `null` when either size is
|
|
45
|
+
* degenerate.
|
|
44
46
|
*/
|
|
45
|
-
readonly inPlace: (imageSize: DebugSize, ovalRect: DebugRect) => SelfiePreviewInPlaceGeometry | null;
|
|
47
|
+
readonly inPlace: (imageSize: DebugSize, ovalRect: DebugRect, placement: SelfiePreviewPlacement) => SelfiePreviewInPlaceGeometry | null;
|
|
46
48
|
/**
|
|
47
49
|
* Lays the full frame out inside a panel the way contain scaling would
|
|
48
50
|
* (uniform contain scale, centred) and maps the planned interior plus the
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* coordinates.
|
|
52
|
-
* Returns `null` when any size is degenerate.
|
|
51
|
+
* window the preview screen shows through `placement` into the same panel
|
|
52
|
+
* coordinates. Returns `null` when any size is degenerate.
|
|
53
53
|
*/
|
|
54
|
-
readonly panel: (imageSize: DebugSize, ovalSize: DebugSize, panelSize: DebugSize) => SelfiePreviewPanelGeometry | null;
|
|
54
|
+
readonly panel: (imageSize: DebugSize, ovalSize: DebugSize, panelSize: DebugSize, placement: SelfiePreviewPlacement) => SelfiePreviewPanelGeometry | null;
|
|
55
55
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{SelfieInteriorCrop as
|
|
1
|
+
"use strict";import{SelfieInteriorCrop as l}from"../presentation/components/helpers/selfie_interior_crop";const i=l.borderFraction;export const SelfiePreviewDebugGeometry={plannedBorderFraction:i,inPlace(t,h,o){if(e(t)||e(h))return null;const f=o.scale(t,h),n={width:t.width*f,height:t.height*f},d=o.offset(n,h),r={left:h.left+d.x,top:h.top+d.y,...n};return{frameRect:r,plannedInteriorRect:u(r,w(t)*f)}},panel(t,h,o,f){if(e(t)||e(h)||e(o))return null;const n=Math.min(o.width/t.width,o.height/t.height),d=s({x:o.width/2,y:o.height/2},{width:t.width*n,height:t.height*n}),r=f.scale(t,h),c=f.offset({width:t.width*r,height:t.height*r},h);return{frameRect:d,plannedInteriorRect:u(d,w(t)*n),visibleOvalRect:{left:d.left-c.x/r*n,top:d.top-c.y/r*n,width:h.width/r*n,height:h.height/r*n}}}};function w(t){return i*Math.min(t.width,t.height)}function s(t,h){return{left:t.x-h.width/2,top:t.y-h.height/2,width:h.width,height:h.height}}function u(t,h){return{left:t.left+h,top:t.top+h,width:t.width-2*h,height:t.height-2*h}}function e(t){return t.width<=0||t.height<=0}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CapturePerfReport } from "../core/perf/capture_perf_trace";
|
|
1
2
|
type Listener = () => void;
|
|
2
3
|
/**
|
|
3
4
|
* Debug hook state the SDK's screens subscribe to, so toggling a switch
|
|
@@ -12,6 +13,13 @@ export declare const UseSmileIDDebugHooks: {
|
|
|
12
13
|
readonly isDocumentPreviewOverlayEnabled: () => boolean;
|
|
13
14
|
/** Sets the flag and notifies subscribers; ungated, for tests and the public setter. */
|
|
14
15
|
readonly setDocumentPreviewOverlayEnabled: (enabled: boolean) => void;
|
|
16
|
+
/** Whether each capture screen logs its perf trace. */
|
|
17
|
+
readonly isPerfTraceEnabled: () => boolean;
|
|
18
|
+
/** Sets the flag; read when a capture screen opens. */
|
|
19
|
+
readonly setPerfTraceEnabled: (enabled: boolean) => void;
|
|
20
|
+
/** Where each capture attempt's perf report also goes, beside the log; null for nowhere. */
|
|
21
|
+
readonly perfTraceListener: () => ((report: CapturePerfReport) => void) | null;
|
|
22
|
+
readonly setPerfTraceListener: (listener: ((report: CapturePerfReport) => void) | null) => void;
|
|
15
23
|
readonly subscribe: (listener: Listener) => () => void;
|
|
16
24
|
};
|
|
17
25
|
/**
|
|
@@ -51,11 +59,26 @@ export declare const UseSmileIDDebug: {
|
|
|
51
59
|
readonly setDocumentDebugOverlaysEnabled: (enabled: boolean) => void;
|
|
52
60
|
/** Whether the document debug overlays are currently installed. */
|
|
53
61
|
readonly isDocumentDebugOverlaysEnabled: () => boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Logs each capture's timings, one line per capture screen through
|
|
64
|
+
* `console.log`: when the screen appeared, asked the camera to start, the
|
|
65
|
+
* camera ran, its first frame, the first analysis, the capture and its files
|
|
66
|
+
* on disk, plus the camera's analysis and still timings. Release builds too,
|
|
67
|
+
* since that is where timings mean something; it logs no images or personal
|
|
68
|
+
* data. Read when a capture screen opens.
|
|
69
|
+
*/
|
|
70
|
+
readonly setPerfTraceEnabled: (enabled: boolean) => void;
|
|
71
|
+
/** Whether capture screens log their perf trace. */
|
|
72
|
+
readonly isPerfTraceEnabled: () => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Also hands each capture attempt's {@link CapturePerfReport} to `listener`: its marks and
|
|
75
|
+
* timings, typed, and its log line, for a release build, whose `console.log` reaches no device
|
|
76
|
+
* log, or to forward the timings to your own analytics. Null stops it.
|
|
77
|
+
*/
|
|
78
|
+
readonly setPerfTraceListener: (listener: ((report: CapturePerfReport) => void) | null) => void;
|
|
54
79
|
};
|
|
55
80
|
/** Subscribes a component to the selfie preview overlay flag. */
|
|
56
81
|
export declare function useSelfiePreviewOverlayEnabled(): boolean;
|
|
57
82
|
/** Subscribes a component to the document overlays flag. */
|
|
58
83
|
export declare function useDocumentPreviewOverlayEnabled(): boolean;
|
|
59
|
-
/** Whether this is a dev build, where the debug overlays can switch on. @internal */
|
|
60
|
-
export declare function isDevBuild(): boolean;
|
|
61
84
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{useSyncExternalStore as t}from"react";let n=!1,
|
|
1
|
+
"use strict";import{useSyncExternalStore as t}from"react";import{isDevelopmentBuild as i}from"../core/is_development_build";let n=!1,a=!1,s=!1,u=null;const r=new Set;export const UseSmileIDDebugHooks={isSelfiePreviewOverlayEnabled(){return n},setSelfiePreviewOverlayEnabled(e){n!==e&&(n=e,r.forEach(l=>l()))},isDocumentPreviewOverlayEnabled(){return a},setDocumentPreviewOverlayEnabled(e){a!==e&&(a=e,r.forEach(l=>l()))},isPerfTraceEnabled(){return s},setPerfTraceEnabled(e){s=e},perfTraceListener(){return u},setPerfTraceListener(e){u=e},subscribe(e){return r.add(e),()=>r.delete(e)}},UseSmileIDDebug={setSelfieDebugOverlaysEnabled(e){i()&&UseSmileIDDebugHooks.setSelfiePreviewOverlayEnabled(e)},isSelfieDebugOverlaysEnabled(){return UseSmileIDDebugHooks.isSelfiePreviewOverlayEnabled()},setDocumentDebugOverlaysEnabled(e){i()&&UseSmileIDDebugHooks.setDocumentPreviewOverlayEnabled(e)},isDocumentDebugOverlaysEnabled(){return UseSmileIDDebugHooks.isDocumentPreviewOverlayEnabled()},setPerfTraceEnabled(e){UseSmileIDDebugHooks.setPerfTraceEnabled(e)},isPerfTraceEnabled(){return UseSmileIDDebugHooks.isPerfTraceEnabled()},setPerfTraceListener(e){UseSmileIDDebugHooks.setPerfTraceListener(e)}};export function useSelfiePreviewOverlayEnabled(){return t(UseSmileIDDebugHooks.subscribe,UseSmileIDDebugHooks.isSelfiePreviewOverlayEnabled,UseSmileIDDebugHooks.isSelfiePreviewOverlayEnabled)}export function useDocumentPreviewOverlayEnabled(){return t(UseSmileIDDebugHooks.subscribe,UseSmileIDDebugHooks.isDocumentPreviewOverlayEnabled,UseSmileIDDebugHooks.isDocumentPreviewOverlayEnabled)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{jsx as i,jsxs as d}from"react/jsx-runtime";import{useId as
|
|
1
|
+
"use strict";import{jsx as i,jsxs as d}from"react/jsx-runtime";import{useId as I}from"react";import{Image as v,StyleSheet as n,View as o}from"react-native";import{useSafeAreaInsets as b}from"react-native-safe-area-context";import h,{ClipPath as S,Defs as y,Path as c,Rect as a}from"react-native-svg";import{DocumentPreviewDebugGeometry as s}from"./document_preview_debug_geometry";import{BORDER_RING_FILL as u,BOUNDARY_STROKE as m,HIDDEN_TODAY_DIM as P,PANEL_WIDTH as g,PlannedBoundary as _,VISIBLE_BOUNDARY_COLOR as f,debugPreviewStyles as w,rectPath as R,ringPath as D}from"./preview_debug_drawing";import{SelfiePreviewDebugHud as p}from"./use_smile_id_selfie_preview_debug_overlay";import{capturedImageUri as z}from"../presentation/components/helpers/captured_image_uri";export function UseSmileIDDocumentPreviewDebugOverlay(t){const r=b();return i(o,{pointerEvents:"box-none",style:n.absoluteFill,testID:"si_debug_document_preview_overlay",children:i(o,{style:[w.hudAnchor,{top:r.top+8}],children:i(p,{title:"DOCUMENT",children:i(x,{...t})})})})}export function DocumentPreviewHiddenRegionsInPlace(t){const r=`si_debug_card_clip_${I().replace(/:/g,"")}`,l=s.inPlace(t.imageSize,t.cardSize,t.borderFraction);if(!l)return null;const e={left:0,top:0,width:t.cardSize.width,height:t.cardSize.height};return d(h,{width:"100%",height:"100%",style:n.absoluteFill,testID:"si_debug_document_preview_in_place",children:[i(y,{children:i(S,{id:r,children:i(a,{x:0,y:0,width:e.width,height:e.height})})}),i(c,{d:D(l.frameRect,l.plannedInteriorRect),fill:u,fillRule:"evenodd",clipPath:`url(#${r})`}),i(_,{rect:l.plannedInteriorRect}),i(a,{x:e.left,y:e.top,width:e.width,height:e.height,fill:"none",stroke:f,strokeWidth:m})]})}function x(t){const r=t.imageSize?t.imageSize.width/t.imageSize.height:E,l={width:g,height:g/r},e=t.imageSize?s.panel(t.imageSize,t.cardSize,l,t.borderFraction):null;return d(o,{style:[w.panel,l],testID:"si_debug_document_preview_panel",children:[i(v,{source:{uri:z(t.uri)},resizeMode:"contain",style:n.absoluteFill}),e?d(h,{width:l.width,height:l.height,style:n.absoluteFill,testID:"si_debug_document_preview_panel_markings",children:[i(c,{d:`${R(e.frameRect)} ${R(e.visibleCardRect)}`,fill:P,fillRule:"evenodd"}),i(c,{d:D(e.frameRect,e.plannedInteriorRect),fill:u,fillRule:"evenodd"}),i(_,{rect:e.plannedInteriorRect}),i(a,{x:e.visibleCardRect.left,y:e.visibleCardRect.top,width:e.visibleCardRect.width,height:e.visibleCardRect.height,fill:"none",stroke:f,strokeWidth:m})]}):null]})}const E=1.586;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import { type DebugRect, type DebugSize } from "./selfie_preview_debug_geometry";
|
|
2
|
+
import { type DebugRect, type DebugSize, type SelfiePreviewPlacement } from "./selfie_preview_debug_geometry";
|
|
3
3
|
export type UseSmileIDSelfiePreviewDebugOverlayProps = {
|
|
4
4
|
/** Local file URI of the captured selfie the preview screen shows. */
|
|
5
5
|
readonly uri: string;
|
|
6
6
|
/** The laid-out oval box, in this overlay's coordinate space. */
|
|
7
7
|
readonly ovalRect: DebugRect;
|
|
8
|
+
/** How the preview places the selfie in `ovalRect`. */
|
|
9
|
+
readonly placement: SelfiePreviewPlacement;
|
|
8
10
|
};
|
|
9
11
|
/**
|
|
10
12
|
* Developer overlay for the post-capture selfie preview screen: shows which
|
|
@@ -38,6 +40,7 @@ export declare function UseSmileIDSelfiePreviewDebugOverlay(props: UseSmileIDSel
|
|
|
38
40
|
export declare function SelfiePreviewHiddenRegionsInPlace(props: {
|
|
39
41
|
readonly imageSize: DebugSize;
|
|
40
42
|
readonly ovalRect: DebugRect;
|
|
43
|
+
readonly placement: SelfiePreviewPlacement;
|
|
41
44
|
}): import("react").JSX.Element | null;
|
|
42
45
|
/**
|
|
43
46
|
* Markings over the full-frame panel: everything outside the user-visible
|
|
@@ -48,6 +51,7 @@ export declare function SelfiePreviewPanelMarkings(props: {
|
|
|
48
51
|
readonly imageSize: DebugSize;
|
|
49
52
|
readonly ovalSize: DebugSize;
|
|
50
53
|
readonly panelSize: DebugSize;
|
|
54
|
+
readonly placement: SelfiePreviewPlacement;
|
|
51
55
|
}): import("react").JSX.Element | null;
|
|
52
56
|
/**
|
|
53
57
|
* Collapsible heads-up display chrome: dark rounded background, the title
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{jsx as i,jsxs as a}from"react/jsx-runtime";import{useEffect as b,useId as w,useState as
|
|
1
|
+
"use strict";import{jsx as i,jsxs as a}from"react/jsx-runtime";import{useEffect as b,useId as w,useState as m}from"react";import{Image as D,Pressable as x,StyleSheet as d,Text as f,View as o}from"react-native";import{useSafeAreaInsets as p}from"react-native-safe-area-context";import g,{ClipPath as E,Defs as $,Ellipse as u,Path as h}from"react-native-svg";import{decodeUprightImageSize as A}from"../presentation/components/helpers/selfie_interior_crop";import{SelfiePreviewDebugGeometry as S}from"./selfie_preview_debug_geometry";import{BORDER_RING_FILL as _,BOUNDARY_STROKE as v,HIDDEN_TODAY_DIM as F,PANEL_WIDTH as R,PlannedBoundary as y,VISIBLE_BOUNDARY_COLOR as I,debugPreviewStyles as O,rectPath as T,ringPath as P}from"./preview_debug_drawing";import{capturedImageUri as L}from"../presentation/components/helpers/captured_image_uri";export function UseSmileIDSelfiePreviewDebugOverlay(e){const t=p(),[l,n]=m(null);return b(()=>{let c=!0;return A(e.uri).then(z=>{c&&n(z)}),()=>{c=!1}},[e.uri]),a(o,{pointerEvents:"box-none",style:d.absoluteFill,testID:"si_debug_selfie_preview_overlay",children:[l?i(o,{pointerEvents:"none",style:d.absoluteFill,children:i(SelfiePreviewHiddenRegionsInPlace,{imageSize:l,ovalRect:e.ovalRect,placement:e.placement})}):null,i(o,{style:[r.hudAnchor,{top:t.top+8}],children:i(SelfiePreviewDebugHud,{title:"FRAME",children:i(k,{uri:e.uri,imageSize:l,ovalSize:e.ovalRect,placement:e.placement})})})]})}export function SelfiePreviewHiddenRegionsInPlace(e){const t=`si_debug_oval_clip_${w().replace(/:/g,"")}`,l=S.inPlace(e.imageSize,e.ovalRect,e.placement);if(!l)return null;const n=e.ovalRect;return a(g,{width:"100%",height:"100%",testID:"si_debug_selfie_preview_in_place",children:[i($,{children:i(E,{id:t,children:i(u,{...s(n)})})}),i(h,{d:P(l.frameRect,l.plannedInteriorRect),fill:_,fillRule:"evenodd",clipPath:`url(#${t})`}),i(y,{rect:l.plannedInteriorRect}),i(u,{...s(n),fill:"none",stroke:I,strokeWidth:v})]})}export function SelfiePreviewPanelMarkings(e){const t=S.panel(e.imageSize,e.ovalSize,e.panelSize,e.placement);if(!t)return null;const l=t.visibleOvalRect;return a(g,{width:e.panelSize.width,height:e.panelSize.height,style:d.absoluteFill,testID:"si_debug_selfie_preview_panel_markings",children:[i(h,{d:`${T(t.frameRect)} ${N(l)}`,fill:F,fillRule:"evenodd"}),i(h,{d:P(t.frameRect,t.plannedInteriorRect),fill:_,fillRule:"evenodd"}),i(y,{rect:t.plannedInteriorRect}),i(u,{...s(l),fill:"none",stroke:I,strokeWidth:v})]})}export function SelfiePreviewDebugHud(e){const[t,l]=m(!0);return a(o,{style:r.hud,children:[a(o,{style:r.hudHeader,children:[t?i(f,{style:r.hudText,children:e.title}):null,i(x,{accessibilityRole:"button",testID:"si_debug_hud_toggle",onPress:()=>l(n=>!n),hitSlop:8,style:r.hudChevron,children:i(f,{style:r.hudText,children:t?"\u25B2":"\u25BC"})})]}),t?e.children:null]})}function k(e){const t=e.imageSize?e.imageSize.width/e.imageSize.height:U,l={width:R,height:R/t};return a(o,{style:[r.panel,l],testID:"si_debug_selfie_preview_panel",children:[i(D,{source:{uri:L(e.uri)},resizeMode:"contain",style:d.absoluteFill}),e.imageSize?i(SelfiePreviewPanelMarkings,{imageSize:e.imageSize,ovalSize:e.ovalSize,placement:e.placement,panelSize:l}):null]})}function s(e){return{cx:e.left+e.width/2,cy:e.top+e.height/2,rx:e.width/2,ry:e.height/2}}function N(e){const{cx:t,cy:l,rx:n,ry:c}=s(e);return`M${t-n},${l} a${n},${c} 0 1,0 ${2*n},0 a${n},${c} 0 1,0 ${-2*n},0 Z`}const U=3/4,r=O;
|
package/dist/index.d.ts
CHANGED
|
@@ -47,7 +47,8 @@ export { JobType, jobTypeFromValue, requiresDocument, requiresSelfie, type JobTy
|
|
|
47
47
|
export { FlowStep } from "./core/models/flow_step";
|
|
48
48
|
export { DocumentType, type DocumentOrientation, type GenericDocumentArgs, } from "./core/models/document_type";
|
|
49
49
|
export { DEFAULT_ACTIVATE_MANUAL_AFTER_MS, DocumentCaptureMode, type AutoCaptureWithManualFallbackArgs, } from "./core/models/document_capture_mode";
|
|
50
|
-
export { cancelled, failure, success, type UseSmileIDResult, } from "./core/models/result";
|
|
50
|
+
export { cancelled, captured, failure, success, type UseSmileIDResult, } from "./core/models/result";
|
|
51
|
+
export type { CapturedJob } from "./data/model/captured_job";
|
|
51
52
|
export type { ConfigOptions } from "./core/models/config";
|
|
52
53
|
export { GlobalPrivacyControl, GLOBAL_PRIVACY_CONTROL_WIRE_KEY, globalPrivacyControlFromWire, } from "./core/models/global_privacy_control";
|
|
53
54
|
export { LogLevel } from "./core/models/network_config";
|
|
@@ -118,3 +119,5 @@ export { UseSmileIDProcessingScreen, type ProcessingState, type UseSmileIDProces
|
|
|
118
119
|
export { ForceMaxBrightness } from "./presentation/utils/force_max_brightness";
|
|
119
120
|
export type { ForceMaxBrightnessProps } from "./presentation/utils/force_max_brightness";
|
|
120
121
|
export { UseSmileIDDebug } from "./debug/use_smile_id_debug";
|
|
122
|
+
export { CapturePerfMark } from "./core/perf/capture_perf_trace";
|
|
123
|
+
export type { CapturePerfOutcome, CapturePerfReport, CapturePerfStat, } from "./core/perf/capture_perf_trace";
|