@smileid/usesmileid 12.2.0 → 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 -1
- package/README.md +57 -18
- package/dist/camera/bounding_box_metering_region.d.ts +10 -0
- package/dist/camera/bounding_box_metering_region.js +1 -0
- package/dist/camera/camera_defaults.d.ts +0 -2
- package/dist/camera/camera_defaults.js +1 -1
- 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/params.d.ts +8 -6
- 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/interceptors/smile_auth_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/api/smile_id_api.js +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/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/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/metadata/use_smile_id_metadata_factory.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/document_interior_crop.d.ts +13 -2
- package/dist/presentation/components/helpers/document_interior_crop.js +1 -1
- 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.d.ts +8 -4
- 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/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.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/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/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
- package/dist/presentation/flow/validation/user_details_validator.d.ts +0 -8
- package/dist/presentation/flow/validation/user_details_validator.js +0 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { KameraMetric } from "@smileid/kamera";
|
|
2
|
+
/**
|
|
3
|
+
* The points a capture passes, in order, each timed from the capture screen
|
|
4
|
+
* appearing. The names are the log's, so a run on any platform reads the same.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CapturePerfMark: {
|
|
7
|
+
/** The capture screen appeared: the trace's zero. */
|
|
8
|
+
readonly screenShown: "screen_shown";
|
|
9
|
+
/** The screen asked the camera to start. */
|
|
10
|
+
readonly cameraStartRequested: "camera_start_requested";
|
|
11
|
+
/** The camera's start resolved. */
|
|
12
|
+
readonly cameraRunning: "camera_running";
|
|
13
|
+
/** kamera delivered its first frame. */
|
|
14
|
+
readonly firstFrame: "first_frame";
|
|
15
|
+
/** Analysis began: as the camera runs, or on a retry's Start tap. */
|
|
16
|
+
readonly streamingStarted: "streaming_started";
|
|
17
|
+
/** The first frame analysis came back. */
|
|
18
|
+
readonly firstScanResult: "first_scan_result";
|
|
19
|
+
/** The capture reached its terminal captured state. */
|
|
20
|
+
readonly captureComplete: "capture_complete";
|
|
21
|
+
/** The captured images are ready to hand off. */
|
|
22
|
+
readonly filesReady: "files_ready";
|
|
23
|
+
};
|
|
24
|
+
export type CapturePerfMark = (typeof CapturePerfMark)[keyof typeof CapturePerfMark];
|
|
25
|
+
/**
|
|
26
|
+
* How a capture attempt ended, as the log line names it. After `failed`, `retake` or `timeout`, the
|
|
27
|
+
* next attempt gets its own line.
|
|
28
|
+
*/
|
|
29
|
+
export type CapturePerfOutcome = "captured" | "gallery" | "failed" | "retake" | "timeout" | "abandoned";
|
|
30
|
+
/** Whether the user can follow `outcome` with another attempt, which gets a fresh trace. */
|
|
31
|
+
export declare function capturePerfOutcomeContinues(outcome: CapturePerfOutcome): boolean;
|
|
32
|
+
/** Median and 95th percentile of some milliseconds, over `count` samples. */
|
|
33
|
+
export type CapturePerfStat = {
|
|
34
|
+
readonly count: number;
|
|
35
|
+
readonly p50Ms: number;
|
|
36
|
+
readonly p95Ms: number;
|
|
37
|
+
};
|
|
38
|
+
/** What one capture measured. */
|
|
39
|
+
export type CapturePerfSnapshot = {
|
|
40
|
+
/** Each mark reached, in milliseconds from `screen_shown`. */
|
|
41
|
+
readonly marks: Readonly<Partial<Record<CapturePerfMark, number>>>;
|
|
42
|
+
/** kamera's own start-to-first-frame, and whether it paid the camera stack's first start. */
|
|
43
|
+
readonly timeToFirstFrameMs?: number;
|
|
44
|
+
readonly coldStart?: boolean;
|
|
45
|
+
/** Take-to-close per analysed frame. */
|
|
46
|
+
readonly analysis?: CapturePerfStat;
|
|
47
|
+
/** How long each analysed frame waited in the frame hub before it was taken. */
|
|
48
|
+
readonly wait?: CapturePerfStat;
|
|
49
|
+
/** The last still's capture-to-result latency. */
|
|
50
|
+
readonly captureLatencyMs?: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* What one capture attempt measured, as `UseSmileIDDebug.setPerfTraceListener` hands it over: the
|
|
54
|
+
* same as its log line, typed.
|
|
55
|
+
*/
|
|
56
|
+
export type CapturePerfReport = CapturePerfSnapshot & {
|
|
57
|
+
/** The capture timed: `selfie`, or `document` and its side (`document front`). */
|
|
58
|
+
readonly capture: string;
|
|
59
|
+
/** How the attempt ended. */
|
|
60
|
+
readonly outcome: CapturePerfOutcome;
|
|
61
|
+
/** The log line: `[UseSmileID perf] <capture> <outcome> mark=ms …`. */
|
|
62
|
+
readonly line: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Times one capture screen: the marks it passes plus kamera's metrics. Pure: the
|
|
66
|
+
* screen feeds it and reads {@link CapturePerfTrace.summaryLine} back.
|
|
67
|
+
*/
|
|
68
|
+
export declare class CapturePerfTrace {
|
|
69
|
+
private readonly _now;
|
|
70
|
+
private readonly _startedAt;
|
|
71
|
+
private readonly _marks;
|
|
72
|
+
private readonly _analysisMs;
|
|
73
|
+
private readonly _waitMs;
|
|
74
|
+
private _timeToFirstFrameMs;
|
|
75
|
+
private _coldStart;
|
|
76
|
+
private _captureLatencyMs;
|
|
77
|
+
constructor(now?: () => number);
|
|
78
|
+
/** Records `mark` the first time it is reached; a repeat keeps the first. */
|
|
79
|
+
mark(mark: CapturePerfMark): void;
|
|
80
|
+
/** Folds in one kamera metric. */
|
|
81
|
+
onMetric(metric: KameraMetric): void;
|
|
82
|
+
snapshot(): CapturePerfSnapshot;
|
|
83
|
+
/** The attempt's report for `capture` ending with `outcome`, its log line included. */
|
|
84
|
+
report(capture: string, outcome: CapturePerfOutcome): CapturePerfReport;
|
|
85
|
+
/**
|
|
86
|
+
* One log line: `[UseSmileID perf] <label> mark=ms … analysis=p50/p95ms(n) …`, the marks in
|
|
87
|
+
* capture order and only those reached.
|
|
88
|
+
*/
|
|
89
|
+
summaryLine(label: string): string;
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";export const CapturePerfMark={screenShown:"screen_shown",cameraStartRequested:"camera_start_requested",cameraRunning:"camera_running",firstFrame:"first_frame",streamingStarted:"streaming_started",firstScanResult:"first_scan_result",captureComplete:"capture_complete",filesReady:"files_ready"};const u=[CapturePerfMark.screenShown,CapturePerfMark.cameraStartRequested,CapturePerfMark.cameraRunning,CapturePerfMark.firstFrame,CapturePerfMark.streamingStarted,CapturePerfMark.firstScanResult,CapturePerfMark.captureComplete,CapturePerfMark.filesReady];export function capturePerfOutcomeContinues(e){return e==="failed"||e==="retake"||e==="timeout"}export class CapturePerfTrace{_now;_startedAt;_marks={};_analysisMs=[];_waitMs=[];_timeToFirstFrameMs;_coldStart;_captureLatencyMs;constructor(t=m){this._now=t,this._startedAt=t(),this._marks[CapturePerfMark.screenShown]=0}mark(t){this._marks[t]===void 0&&(this._marks[t]=Math.max(0,Math.round(this._now()-this._startedAt)))}onMetric(t){switch(t.name){case"firstFrame":if(this._timeToFirstFrameMs!==void 0)return;this.mark(CapturePerfMark.firstFrame),this._timeToFirstFrameMs=t.timeToFirstFrameMs,this._coldStart=t.coldStart;break;case"frameAnalyzed":if(this._marks[CapturePerfMark.streamingStarted]===void 0||this._marks[CapturePerfMark.captureComplete]!==void 0)return;this._analysisMs.push(t.analysisMs),this._waitMs.push(t.waitMs);break;case"captureCompleted":this._captureLatencyMs=t.latencyMs;break}}snapshot(){return{marks:{...this._marks},...this._timeToFirstFrameMs!==void 0?{timeToFirstFrameMs:this._timeToFirstFrameMs}:{},...this._coldStart!==void 0?{coldStart:this._coldStart}:{},...this._analysisMs.length>0?{analysis:n(this._analysisMs)}:{},...this._waitMs.length>0?{wait:n(this._waitMs)}:{},...this._captureLatencyMs!==void 0?{captureLatencyMs:this._captureLatencyMs}:{}}}report(t,s){return{...this.snapshot(),capture:t,outcome:s,line:this.summaryLine(`${t} ${s}`)}}summaryLine(t){const s=this.snapshot(),a=u.filter(r=>s.marks[r]!==void 0).map(r=>`${r}=${s.marks[r]}ms`);return s.timeToFirstFrameMs!==void 0&&a.push(`kamera_first_frame=${s.timeToFirstFrameMs}ms${s.coldStart?"(cold)":""}`),s.analysis&&a.push(`analysis=${c(s.analysis)}`),s.wait&&a.push(`wait=${c(s.wait)}`),s.captureLatencyMs!==void 0&&a.push(`still=${s.captureLatencyMs}ms`),`[UseSmileID perf] ${t} ${a.join(" ")}`}}function m(){const e=globalThis.performance;return e?e.now():Date.now()}function n(e){const t=[...e].sort((s,a)=>s-a);return{count:t.length,p50Ms:i(t,.5),p95Ms:i(t,.95)}}function i(e,t){const s=Math.ceil(t*e.length);return e[Math.min(e.length,Math.max(1,s))-1]??0}function c(e){return`${Math.round(e.p50Ms)}/${Math.round(e.p95Ms)}ms(${e.count})`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{LogLevel as u}from"../../../core/models/network_config";import{ALWAYS_REDACT as
|
|
1
|
+
"use strict";import{LogLevel as u}from"../../../core/models/network_config";import{ALWAYS_REDACT as d,ALWAYS_REDACT_BODY_KEYS as p}from"./sensitive_headers";import{isDevelopmentBuild as f}from"../../../core/is_development_build";export function attachLoggingInterceptor(e,t){if(!t.enabled||t.level===u.none){const r=e.interceptors.request.use(a=>a),i=e.interceptors.response.use(a=>a);return{request:r,response:i}}const s=new Set(t.redactHeaders.map(r=>r.toLowerCase())),o=t.level===u.body&&!f()?u.headers:t.level,n=e.interceptors.request.use(r=>(y(r,o,s),r)),c=e.interceptors.response.use(r=>(D(r,o),r),r=>(A(r,o),Promise.reject(r)));return{request:n,response:c}}const m=/^([a-z][a-z\d+\-.]*:)?\/\//i;function b(e){const t=e.baseURL??"",s=e.url??"";return!t||m.test(s)?s:s?`${t.replace(/\/+$/,"")}/${s.replace(/^\/+/,"")}`:t}function y(e,t,s){const o=(e.method??"GET").toUpperCase(),n=b(e);if(console.log(`[UseSmileID] \u2192 ${o} ${n}`),t===u.headers||t===u.body){const c=S(e.headers,s);console.log("[UseSmileID] headers:",c)}}function D(e,t){console.log(`[UseSmileID] \u2190 ${e.status} ${e.statusText} ${e.config.url??""}`),t===u.body&&console.log("[UseSmileID] body:",l(e.data))}function A(e,t){const s=e.response?.status??"no-response";console.warn(`[UseSmileID] \u2717 ${s} ${e.config?.url??""} \u2014 ${e.message}`),t===u.body&&e.response&&console.warn("[UseSmileID] body:",l(e.response.data))}const L=6;function l(e,t=0){if(t>=L)return"<depth-limit>";if(Array.isArray(e))return e.map(s=>l(s,t+1));if(e!==null&&typeof e=="object"){const s={};for(const[o,n]of Object.entries(e))s[o]=p.has(o.toLowerCase())?"<redacted>":l(n,t+1);return s}return e}function S(e,t){const s={};for(const[o,n]of Object.entries(e??{})){const c=o.toLowerCase();d.has(c)||t.has(c)?s[o]="<redacted>":s[o]=n}return s}
|
|
@@ -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}}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AxiosAdapter, InternalAxiosRequestConfig } from "axios";
|
|
2
|
+
/**
|
|
3
|
+
* The request the capture client stopped: its config as it would have gone to
|
|
4
|
+
* the wire, after every request interceptor (auth, request-MAC, integrity) ran.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CapturedRequest extends Error {
|
|
7
|
+
readonly config: InternalAxiosRequestConfig;
|
|
8
|
+
constructor(config: InternalAxiosRequestConfig);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The capture client's adapter: axios calls it once the request interceptors
|
|
12
|
+
* have run, so it rejects with the signed request instead of sending it. A
|
|
13
|
+
* skipped submission seals its request through this client, so the request it
|
|
14
|
+
* seals is signed exactly as a submission is and never leaves the device.
|
|
15
|
+
*/
|
|
16
|
+
export declare const captureAdapter: AxiosAdapter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";export class CapturedRequest extends Error{config;constructor(t){super("The request was captured for sealing, not sent"),this.name="CapturedRequest",this.config=t}}export const captureAdapter=e=>Promise.reject(new CapturedRequest(e));
|
|
@@ -66,7 +66,7 @@ export declare class SmileIDAPI {
|
|
|
66
66
|
*
|
|
67
67
|
* `expectedLivenessCount` is the expected number of liveness frames
|
|
68
68
|
* for this submission — derived from the decoded `sdk_config` claim
|
|
69
|
-
* when present, otherwise the
|
|
69
|
+
* when present, otherwise the native default (7).
|
|
70
70
|
*/
|
|
71
71
|
private _buildSelfieFormData;
|
|
72
72
|
/**
|
|
@@ -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};
|
|
@@ -45,7 +45,9 @@ import type { MetadataProvider } from "../../metadata/use_smile_id_metadata_fact
|
|
|
45
45
|
* the request-path order so the LIFO unwind lands on the canonical
|
|
46
46
|
* order described above.
|
|
47
47
|
*/
|
|
48
|
-
export declare function makeNetworkingFactory(config: NetworkConfig, signingSecret?: () => string
|
|
48
|
+
export declare function makeNetworkingFactory(config: NetworkConfig, signingSecret?: () => string, options?: {
|
|
49
|
+
readonly capturing?: boolean;
|
|
50
|
+
}): {
|
|
49
51
|
readonly api: SmileIDAPI;
|
|
50
52
|
readonly client: AxiosInstance;
|
|
51
53
|
readonly metadata: MetadataProvider;
|
|
@@ -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{};
|
|
@@ -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,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)}`)}}};
|