@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.
Files changed (169) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/README.md +57 -18
  3. package/dist/camera/bounding_box_metering_region.d.ts +10 -0
  4. package/dist/camera/bounding_box_metering_region.js +1 -0
  5. package/dist/camera/camera_defaults.d.ts +0 -2
  6. package/dist/camera/camera_defaults.js +1 -1
  7. package/dist/camera/document_image_pipeline.d.ts +9 -0
  8. package/dist/camera/document_image_pipeline.js +1 -1
  9. package/dist/camera/imported_document_folder.d.ts +15 -0
  10. package/dist/camera/imported_document_folder.js +1 -0
  11. package/dist/camera/kamera_config_factory.d.ts +11 -1
  12. package/dist/camera/kamera_config_factory.js +1 -1
  13. package/dist/camera/use_smile_id_frame_stream.js +1 -1
  14. package/dist/camera/use_smile_id_kamera_camera_system.d.ts +46 -2
  15. package/dist/camera/use_smile_id_kamera_camera_system.js +1 -1
  16. package/dist/core/exception/use_smile_id_error_code.js +1 -1
  17. package/dist/core/exception/use_smile_id_network_exception.d.ts +10 -3
  18. package/dist/core/exception/use_smile_id_network_exception.js +1 -1
  19. package/dist/core/is_development_build.d.ts +6 -0
  20. package/dist/core/is_development_build.js +1 -0
  21. package/dist/core/models/config.d.ts +6 -0
  22. package/dist/core/models/ml_config.d.ts +1 -1
  23. package/dist/core/models/network_config.d.ts +4 -1
  24. package/dist/core/models/params.d.ts +8 -6
  25. package/dist/core/models/result.d.ts +11 -0
  26. package/dist/core/models/result.js +1 -1
  27. package/dist/core/perf/capture_perf_trace.d.ts +90 -0
  28. package/dist/core/perf/capture_perf_trace.js +1 -0
  29. package/dist/data/api/interceptors/logging_interceptor.js +1 -1
  30. package/dist/data/api/interceptors/smile_auth_interceptor.js +1 -1
  31. package/dist/data/api/request_capture.d.ts +16 -0
  32. package/dist/data/api/request_capture.js +1 -0
  33. package/dist/data/api/smile_id_api.d.ts +1 -1
  34. package/dist/data/api/smile_id_api.js +1 -1
  35. package/dist/data/factory/networking_factory.d.ts +3 -1
  36. package/dist/data/factory/networking_factory.js +1 -1
  37. package/dist/data/model/captured_job.d.ts +49 -0
  38. package/dist/data/model/captured_job.js +1 -0
  39. package/dist/data/model/job_request.d.ts +1 -1
  40. package/dist/data/model/sealed_job_request.d.ts +15 -0
  41. package/dist/data/model/sealed_job_request.js +1 -0
  42. package/dist/data/model/token_payload.d.ts +5 -7
  43. package/dist/data/model/token_payload.js +1 -1
  44. package/dist/data/model/user_details_payload.d.ts +6 -4
  45. package/dist/data/repository/use_smile_id_repository.d.ts +7 -0
  46. package/dist/data/repository/use_smile_id_repository_impl.d.ts +29 -2
  47. package/dist/data/repository/use_smile_id_repository_impl.js +1 -1
  48. package/dist/data/security/job_package_crypto.d.ts +41 -0
  49. package/dist/data/security/job_package_crypto.js +1 -0
  50. package/dist/data/security/job_package_sealer.d.ts +35 -0
  51. package/dist/data/security/job_package_sealer.js +1 -0
  52. package/dist/data/security/signing_crypto.d.ts +34 -0
  53. package/dist/data/security/signing_crypto.js +1 -1
  54. package/dist/data/util/retry_util.d.ts +9 -6
  55. package/dist/data/util/retry_util.js +1 -1
  56. package/dist/debug/selfie_preview_debug_geometry.d.ts +13 -13
  57. package/dist/debug/selfie_preview_debug_geometry.js +1 -1
  58. package/dist/debug/use_smile_id_debug.d.ts +25 -2
  59. package/dist/debug/use_smile_id_debug.js +1 -1
  60. package/dist/debug/use_smile_id_document_preview_debug_overlay.js +1 -1
  61. package/dist/debug/use_smile_id_selfie_preview_debug_overlay.d.ts +5 -1
  62. package/dist/debug/use_smile_id_selfie_preview_debug_overlay.js +1 -1
  63. package/dist/index.d.ts +4 -1
  64. package/dist/index.js +1 -1
  65. package/dist/l10n/strings_en.d.ts +3 -3
  66. package/dist/l10n/strings_en.js +2 -2
  67. package/dist/l10n/use_smile_id_localizations.d.ts +5 -7
  68. package/dist/l10n/use_smile_id_localizations.js +1 -1
  69. package/dist/metadata/metadata_configuration_mappers.d.ts +3 -5
  70. package/dist/metadata/metadata_configuration_mappers.js +1 -1
  71. package/dist/metadata/use_smile_id_metadata_factory.js +1 -1
  72. package/dist/presentation/assets/si_dark_lighting_animation.json +1 -927
  73. package/dist/presentation/assets/si_device_orientation_animation.json +1 -850
  74. package/dist/presentation/assets/si_document_green_book_hero_animation.json +1 -4740
  75. package/dist/presentation/assets/si_document_hero_animation.json +1 -4870
  76. package/dist/presentation/assets/si_document_hero_back_animation.json +1 -2122
  77. package/dist/presentation/assets/si_document_passport_hero_animation.json +1 -14540
  78. package/dist/presentation/assets/si_ess_all_directions_hero_animation.json +1 -5906
  79. package/dist/presentation/assets/si_ess_animation_all_directions.json +1 -5906
  80. package/dist/presentation/assets/si_selfie_hero_animation.json +1 -1468
  81. package/dist/presentation/assets/si_smile_to_capture_animation.json +1 -1412
  82. package/dist/presentation/components/helpers/captured_image_uri.d.ts +11 -0
  83. package/dist/presentation/components/helpers/captured_image_uri.js +1 -0
  84. package/dist/presentation/components/helpers/captured_oval_region.d.ts +40 -0
  85. package/dist/presentation/components/helpers/captured_oval_region.js +1 -0
  86. package/dist/presentation/components/helpers/document_interior_crop.d.ts +13 -2
  87. package/dist/presentation/components/helpers/document_interior_crop.js +1 -1
  88. package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.d.ts +35 -29
  89. package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.js +1 -1
  90. package/dist/presentation/components/helpers/selfie_interior_crop.js +1 -1
  91. package/dist/presentation/components/use_smile_id_capture_content.d.ts +5 -2
  92. package/dist/presentation/components/use_smile_id_capture_content.js +1 -1
  93. package/dist/presentation/components/use_smile_id_captured_document_image.d.ts +8 -4
  94. package/dist/presentation/components/use_smile_id_captured_document_image.js +1 -1
  95. package/dist/presentation/components/use_smile_id_captured_image_oval.d.ts +6 -0
  96. package/dist/presentation/components/use_smile_id_captured_image_oval.js +1 -1
  97. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.d.ts +17 -7
  98. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js +1 -1
  99. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_direction_animation.d.ts +13 -14
  100. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_direction_animation.js +1 -1
  101. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_full_animation.js +1 -1
  102. package/dist/presentation/components/use_smile_id_face_oval_image.js +1 -1
  103. package/dist/presentation/flow/analytics/unsatisfied_reasons.d.ts +14 -0
  104. package/dist/presentation/flow/analytics/unsatisfied_reasons.js +1 -0
  105. package/dist/presentation/flow/analytics/use_smile_id_analytics_event.d.ts +8 -7
  106. package/dist/presentation/flow/analytics/use_smile_id_analytics_event.js +1 -1
  107. package/dist/presentation/flow/analytics/use_smile_id_analytics_event_kind.d.ts +9 -0
  108. package/dist/presentation/flow/analytics/use_smile_id_analytics_event_kind.js +1 -1
  109. package/dist/presentation/flow/config/flow_configuration_camera.d.ts +9 -0
  110. package/dist/presentation/flow/config/flow_configuration_camera.js +1 -0
  111. package/dist/presentation/flow/dsl/builder/config_builder.d.ts +6 -0
  112. package/dist/presentation/flow/dsl/builder/config_builder.js +1 -1
  113. package/dist/presentation/flow/dsl/builder/ml_builder.d.ts +1 -7
  114. package/dist/presentation/flow/dsl/builder/ml_builder.js +1 -1
  115. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts +0 -9
  116. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js +1 -1
  117. package/dist/presentation/flow/flow_navigation_manager.d.ts +39 -70
  118. package/dist/presentation/flow/flow_navigation_manager.js +1 -1
  119. package/dist/presentation/flow/flow_navigation_manager_analytics.d.ts +23 -0
  120. package/dist/presentation/flow/flow_navigation_manager_analytics.js +1 -0
  121. package/dist/presentation/flow/flow_navigation_manager_documents.d.ts +18 -0
  122. package/dist/presentation/flow/flow_navigation_manager_documents.js +1 -0
  123. package/dist/presentation/flow/flow_navigation_manager_job_request.d.ts +7 -0
  124. package/dist/presentation/flow/flow_navigation_manager_job_request.js +1 -0
  125. package/dist/presentation/flow/flow_navigation_manager_routing.d.ts +12 -0
  126. package/dist/presentation/flow/flow_navigation_manager_routing.js +1 -0
  127. package/dist/presentation/flow/flow_navigation_manager_submission.d.ts +17 -0
  128. package/dist/presentation/flow/flow_navigation_manager_submission.js +1 -0
  129. package/dist/presentation/flow/job_submission_state.d.ts +4 -0
  130. package/dist/presentation/flow/screen/helpers/face_session_config.d.ts +1 -1
  131. package/dist/presentation/flow/screen/helpers/failure_message.js +1 -1
  132. package/dist/presentation/flow/screen/orchestrated_capture_screen.js +1 -1
  133. package/dist/presentation/flow/screen/orchestrated_instructions_screen.js +1 -1
  134. package/dist/presentation/flow/screen/orchestrated_preview_screen.js +1 -1
  135. package/dist/presentation/flow/screen/orchestrated_processing_screen.js +1 -1
  136. package/dist/presentation/flow/use_smile_id_flow.js +1 -1
  137. package/dist/presentation/flow/validation/flow_validator.d.ts +0 -19
  138. package/dist/presentation/flow/validation/flow_validator.js +1 -1
  139. package/dist/presentation/flow/validation/job_type_validator.js +1 -1
  140. package/dist/presentation/screen/helpers/capture_screen_helpers.d.ts +0 -12
  141. package/dist/presentation/screen/helpers/capture_screen_helpers.js +1 -1
  142. package/dist/presentation/screen/helpers/capture_ui_state.d.ts +33 -6
  143. package/dist/presentation/screen/helpers/capture_ui_state.js +1 -1
  144. package/dist/presentation/screen/helpers/dropped_frames.d.ts +6 -0
  145. package/dist/presentation/screen/helpers/dropped_frames.js +1 -0
  146. package/dist/presentation/screen/helpers/ess_turn_haptics.d.ts +2 -0
  147. package/dist/presentation/screen/helpers/ess_turn_haptics.js +1 -1
  148. package/dist/presentation/screen/helpers/instructions_screen_helpers.d.ts +5 -1
  149. package/dist/presentation/screen/helpers/instructions_screen_helpers.js +1 -1
  150. package/dist/presentation/screen/helpers/use_capture_orientation_lock.d.ts +23 -1
  151. package/dist/presentation/screen/helpers/use_capture_orientation_lock.js +1 -1
  152. package/dist/presentation/screen/helpers/use_debounced_feedback_key.d.ts +1 -1
  153. package/dist/presentation/screen/helpers/use_debounced_feedback_key.js +1 -1
  154. package/dist/presentation/screen/use_smile_id_capture_screen.d.ts +4 -1
  155. package/dist/presentation/screen/use_smile_id_capture_screen.js +1 -1
  156. package/dist/presentation/screen/use_smile_id_consent_screen.js +1 -1
  157. package/dist/presentation/screen/use_smile_id_preview_screen.d.ts +3 -0
  158. package/dist/presentation/screen/use_smile_id_preview_screen.js +1 -1
  159. package/dist/presentation/screen/use_smile_id_processing_screen.d.ts +3 -0
  160. package/dist/presentation/screen/use_smile_id_processing_screen.js +1 -1
  161. package/dist/presentation/utils/locale_utils.d.ts +3 -6
  162. package/dist/presentation/utils/locale_utils.js +1 -1
  163. package/dist/testing/scenario_expectations.d.ts +7 -2
  164. package/dist/testing/scenario_expectations.js +1 -1
  165. package/dist/testing/scripted_scan_results.d.ts +2 -1
  166. package/dist/testing/scripted_scan_results.js +1 -1
  167. package/package.json +7 -7
  168. package/dist/presentation/flow/validation/user_details_validator.d.ts +0 -8
  169. 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 i,ALWAYS_REDACT_BODY_KEYS as p}from"./sensitive_headers";export function attachLoggingInterceptor(e,t){if(!t.enabled||t.level===u.none){const o=e.interceptors.request.use(l=>l),a=e.interceptors.response.use(l=>l);return{request:o,response:a}}const s=new Set(t.redactHeaders.map(o=>o.toLowerCase())),r=e.interceptors.request.use(o=>(m(o,t.level,s),o)),n=e.interceptors.response.use(o=>(b(o,t.level),o),o=>(y(o,t.level),Promise.reject(o)));return{request:r,response:n}}const d=/^([a-z][a-z\d+\-.]*:)?\/\//i;function f(e){const t=e.baseURL??"",s=e.url??"";return!t||d.test(s)?s:s?`${t.replace(/\/+$/,"")}/${s.replace(/^\/+/,"")}`:t}function m(e,t,s){const r=(e.method??"GET").toUpperCase(),n=f(e);if(console.log(`[UseSmileID] \u2192 ${r} ${n}`),t===u.headers||t===u.body){const o=D(e.headers,s);console.log("[UseSmileID] headers:",o)}}function b(e,t){console.log(`[UseSmileID] \u2190 ${e.status} ${e.statusText} ${e.config.url??""}`),t===u.body&&console.log("[UseSmileID] body:",c(e.data))}function y(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:",c(e.response.data))}const A=6;function c(e,t=0){if(t>=A)return"<depth-limit>";if(Array.isArray(e))return e.map(s=>c(s,t+1));if(e!==null&&typeof e=="object"){const s={};for(const[r,n]of Object.entries(e))s[r]=p.has(r.toLowerCase())?"<redacted>":c(n,t+1);return s}return e}function D(e,t){const s={};for(const[r,n]of Object.entries(e??{})){const o=r.toLowerCase();i.has(o)||t.has(o)?s[r]="<redacted>":s[r]=n}return s}
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.2.0";export class SmileAuthInterceptor{_partnerConfig;_tokenHolder;constructor(e){this._partnerConfig=e.partnerConfig,this._tokenHolder=e.tokenHolder}attach(e){return{request:e.interceptors.request.use(async r=>this._onRequest(r))}}async _onRequest(e){return e.headers.set("smileid-token",this._tokenHolder.current),e.headers.set("smileid-source-sdk",t()),e.headers.set("smileid-source-sdk-version",n),e.headers.set("smileid-partner-id",this._partnerConfig.partnerId),e.headers.set("smileid-request-timestamp",s()),e}}
1
+ "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 `ScanPerformanceProfile` default (7).
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;if(n===void 0)return;const e=g(n);Object.keys(e).length!==0&&c(a,"user_details",e)}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};
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 u,buildAxiosClient as g,SmileAuthInterceptor as I,attachLoggingInterceptor as k}from"../api/smile_id_api";import{createGzipInterceptor as d}from"../api/interceptors/gzip_interceptor";import{SmileRequestMacInterceptor as y}from"../api/interceptors/smile_request_mac_interceptor";import{SmileIntegrityInterceptor as w}from"../api/interceptors/smile_integrity_interceptor";import{TokenRefreshInterceptor as C}from"../api/interceptors/token_refresh_interceptor";import{TokenHolder as S}from"../api/auth/token_holder";import{SmileIDCryptoManager as A}from"../security/smile_id_crypto_manager";import{SecurityPolicy as P}from"../security/security_policy";import{decodeSecurityPolicyBitCode as F}from"../util/use_smile_id_token_decoder";import{Attestation as T}from"@smileid/usesmileid_bridge";import{SigningSecretProvider as b}from"@smileid/usesmileid_bridge/internal";import{MissingPartnerConfigException as n}from"../../core/exception";import{UseSmileIDMetadataFactory as x}from"../../metadata/use_smile_id_metadata_factory";export function makeNetworkingFactory(t,o=()=>b.signingSecret()){const r=t.config?.partnerConfig;if(!r||r.partnerId.trim()==="")throw n.missingPartnerId();if(!t.config?.token||t.config.token.trim()==="")throw n.missingAuthToken();const c=/[\u0000-\u001F\u007F-\u009F]/;if(c.test(r.partnerId))throw n.controlCharacterPartnerId();if(c.test(t.config.token))throw n.controlCharacterAuthToken();if(!isResolvableCallbackUrl(r.callbackUrl??""))throw n.unresolvableCallbackUrl();const e=g(t),m=new x,f=new u(e,r);T.warmUp().catch(()=>{});const a=new S(t.config?.token??""),s=P.fromBitCode(F(t.config?.token??""));d().attach(e);const p=t.config?.logging;p&&k(e,p),new w({securityPolicy:s}).attach(e);const l=new A(o());if(new y({cryptoManager:l,securityPolicy:s}).attach(e),new I({partnerConfig:r,tokenHolder:a}).attach(e),new C({tokenHolder:a,onTokenExpired:t.config?.onTokenExpired}).attach(e),t.interceptors)for(const h of t.interceptors){const i=h;typeof i=="function"?i(e):typeof i?.attach=="function"&&i.attach(e)}return{api:f,client:e,metadata:m}}const M=/^callback_[0-9a-hjkmnp-tv-z]{26}$/;export function isResolvableCallbackUrl(t){if(t===""||M.test(t))return!0;const o=t.indexOf("://");if(o<=0)return!1;const r=t.slice(0,o).toLowerCase();if(r!=="http"&&r!=="https")return!1;const e=((t.slice(o+3).split(/[/?#]/)[0]??"").split("@").pop()??"").split(":")[0]??"";return e!==""&&!/[\s\u0000-\u001F\u007F-\u009F]/.test(e)}
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
- * `ScanPerformanceProfile` default minus one (`numSamples - 1`).
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}export function bindsRequiredUserDetails(n){return n===void 0?!1:n.hasGivenNames&&n.hasLastName&&(n.hasEmail||n.hasPhoneNumber)}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}
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 entirely (`isUserDetailsPayloadEmpty`).
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 — the
30
- * caller omits the part instead of sending `{}`. Blank contacts count as
31
- * empty, mirroring the wire encoding that trims and drops them.
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
- constructor(api: SmileIDAPI, retry: RetryConfig);
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 _dispatch;
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 s,success as m}from"../../core/models/result";import{JobType as t}from"../../core/models/job_type";import{withRetry as l}from"../util/retry_util";import{InvalidFieldValueException as f}from"../../core/exception/job_validation_exceptions";import{UseSmileIDValidationException as p}from"../../core/exception/use_smile_id_validation_exception";import{mapAxiosError as d}from"../../core/exception/use_smile_id_network_exception";import{PayloadCompressionRunner as c}from"../compression/payload_compression_runner";function _(r){const e=[],i=(n,a)=>{a!==void 0&&a>0&&e.push({name:n,value:String(a)})};return i("selfie_original_size",r.selfieOriginalMb),i("selfie_final_compressed_size",r.selfieFinalMb),i("document_front_original_size",r.frontOriginalMb),i("document_front_final_compressed_size",r.frontFinalMb),i("document_back_original_size",r.backOriginalMb),i("document_back_final_compressed_size",r.backFinalMb),i("document_visa_original_size",r.visaOriginalMb),i("document_visa_final_compressed_size",r.visaFinalMb),e}export class UseSmileIDRepositoryImpl{_api;_retry;constructor(e,i){this._api=e,this._retry=i}async submit(e,i){if(e.jobType===t.bvn)return s(new f({fieldName:"jobType",reason:"BVN submission is not yet supported"}));let n=e;try{const a=await this._compressPayload(e,i),o=a?_(a):[];o.length>0&&(n={...e,metadata:e.metadata.withAdditionalEntries(o)})}catch{}try{const a=await l(this._retry,o=>this._dispatch(n,o));return m(a)}catch(a){return a instanceof p?s(a):s(this._mapError(a))}}async _compressPayload(e,i){const n=JSON.stringify(e.metadata.toEntries()).length;switch(e.jobType){case t.smartSelfieEnrollment:case t.smartSelfieAuthentication:case t.biometricKyc:return c.compress({selfieImage:e.selfieImage,livenessImages:e.livenessImages,metadataBytes:n,sdkConfig:i});case t.documentVerification:case t.enhancedDocumentVerification:return c.compress({selfieImage:e.selfieImage,livenessImages:e.livenessImages,frontDocument:e.frontDocument,backDocument:e.backDocument,metadataBytes:n,sdkConfig:i});case t.residencyDocumentVerification:return c.compress({selfieImage:e.selfieImage,livenessImages:e.livenessImages,frontDocument:e.frontDocument,visaDocument:e.visaDocument,metadataBytes:n,sdkConfig:i});default:return}}_dispatch(e,i){const n=i>0?{...e,metadata:e.metadata.withAdditionalEntries([{name:"network_retries",value:String(i)}])}:e;switch(n.jobType){case t.smartSelfieEnrollment:return this._api.smartSelfieEnrollment(n);case t.smartSelfieAuthentication:return this._api.smartSelfieAuthentication(n);case t.enhancedKyc:return this._api.enhancedKyc(n);case t.biometricKyc:return this._api.biometricKyc(n);case t.documentVerification:return this._api.documentVerification(n);case t.enhancedDocumentVerification:return this._api.enhancedDocumentVerification(n);case t.residencyDocumentVerification:return this._api.residencyDocumentVerification(n)}}_mapError(e){return d(e)}}
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 f=32,h=2e5;export function utf8Bytes(t){return new TextEncoder().encode(t)}const c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";export function base64Encode(t){let n="";for(let e=0;e<t.length;e+=3){const r=t[e],o=e+1<t.length?t[e+1]:0,l=e+2<t.length?t[e+2]:0;n+=c[r>>2],n+=c[(r&3)<<4|o>>4],n+=e+1<t.length?c[(o&15)<<2|l>>6]:"=",n+=e+2<t.length?c[l&63]:"="}return n}let i;function p(){if(i)return i;const t=globalThis.crypto?.subtle;if(t)return i=t,i;try{const n=require("react-native-quick-crypto"),e=n.subtle??n.webcrypto?.subtle??n.default?.subtle??n.default?.webcrypto?.subtle;if(e)return i=e,i}catch{}return null}function u(){const t=p();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}function s(t){return t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)}export const SigningCrypto={async deriveKey(t,n){const e=u();try{const r=await e.importKey("raw",s(utf8Bytes(t)),"PBKDF2",!1,["deriveBits"]),o=await e.deriveBits({name:"PBKDF2",salt:s(n),iterations:h,hash:"SHA-256"},r,f*8);return new Uint8Array(o)}catch(r){throw r instanceof a?r:new a(`Key derivation failed: ${String(r)}`)}},async hmacSha256(t,n){const e=u();try{const r=await e.importKey("raw",s(t),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await e.sign("HMAC",r,s(n));return new Uint8Array(o)}catch(r){throw r instanceof a?r:new a(`MAC computation failed: ${String(r)}`)}}};
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)}`)}}};