@smileid/usesmileid 12.0.1 → 12.0.2
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 +4 -0
- package/README.md +1 -1
- package/dist/camera/camera_defaults.d.ts +2 -0
- package/dist/camera/camera_defaults.d.ts.map +1 -1
- package/dist/camera/camera_defaults.js +2 -0
- package/dist/camera/camera_defaults.js.map +1 -1
- package/dist/core/exception/flow_exceptions.d.ts +4 -2
- package/dist/core/exception/flow_exceptions.d.ts.map +1 -1
- package/dist/core/exception/flow_exceptions.js +3 -2
- package/dist/core/exception/flow_exceptions.js.map +1 -1
- package/dist/core/exception/missing_job_type_exception.d.ts +1 -3
- package/dist/core/exception/missing_job_type_exception.d.ts.map +1 -1
- package/dist/core/exception/missing_job_type_exception.js +2 -4
- package/dist/core/exception/missing_job_type_exception.js.map +1 -1
- package/dist/data/api/interceptors/smile_auth_interceptor.js +1 -1
- package/dist/data/api/interceptors/smile_auth_interceptor.js.map +1 -1
- package/dist/data/api/smile_id_api.d.ts +4 -0
- package/dist/data/api/smile_id_api.d.ts.map +1 -1
- package/dist/data/api/smile_id_api.js +30 -27
- package/dist/data/api/smile_id_api.js.map +1 -1
- package/dist/data/model/job_request.d.ts +28 -12
- package/dist/data/model/job_request.d.ts.map +1 -1
- package/dist/data/model/token_payload.d.ts +78 -0
- package/dist/data/model/token_payload.d.ts.map +1 -0
- package/dist/data/model/token_payload.js +132 -0
- package/dist/data/model/token_payload.js.map +1 -0
- package/dist/data/model/user_details_payload.d.ts +39 -0
- package/dist/data/model/user_details_payload.d.ts.map +1 -0
- package/dist/data/model/user_details_payload.js +53 -0
- package/dist/data/model/user_details_payload.js.map +1 -0
- package/dist/data/util/use_smile_id_token_decoder.d.ts +15 -3
- package/dist/data/util/use_smile_id_token_decoder.d.ts.map +1 -1
- package/dist/data/util/use_smile_id_token_decoder.js +20 -8
- package/dist/data/util/use_smile_id_token_decoder.js.map +1 -1
- package/dist/l10n/strings_en.d.ts +4 -1
- package/dist/l10n/strings_en.d.ts.map +1 -1
- package/dist/l10n/strings_en.js +4 -1
- package/dist/l10n/strings_en.js.map +1 -1
- package/dist/l10n/use_smile_id_localizations.d.ts +10 -0
- package/dist/l10n/use_smile_id_localizations.d.ts.map +1 -1
- package/dist/l10n/use_smile_id_localizations.js +59 -3
- package/dist/l10n/use_smile_id_localizations.js.map +1 -1
- package/dist/metadata/metadata_configuration_mappers.js +1 -1
- package/dist/metadata/metadata_configuration_mappers.js.map +1 -1
- package/dist/metadata/use_smile_id_metadata_factory.js +1 -1
- package/dist/metadata/use_smile_id_metadata_factory.js.map +1 -1
- package/dist/presentation/components/use_smile_id_button.d.ts.map +1 -1
- package/dist/presentation/components/use_smile_id_button.js +8 -2
- package/dist/presentation/components/use_smile_id_button.js.map +1 -1
- package/dist/presentation/flow/dsl/builder/screens_builder.d.ts +3 -6
- package/dist/presentation/flow/dsl/builder/screens_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/builder/screens_builder.js +4 -6
- package/dist/presentation/flow/dsl/builder/screens_builder.js.map +1 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js +23 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.d.ts +17 -2
- package/dist/presentation/flow/flow_navigation_manager.d.ts.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.js +77 -45
- package/dist/presentation/flow/flow_navigation_manager.js.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_processing_screen.d.ts.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_processing_screen.js +25 -5
- package/dist/presentation/flow/screen/orchestrated_processing_screen.js.map +1 -1
- package/dist/presentation/flow/use_smile_id_flow.d.ts.map +1 -1
- package/dist/presentation/flow/use_smile_id_flow.js +35 -8
- package/dist/presentation/flow/use_smile_id_flow.js.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.d.ts +16 -2
- package/dist/presentation/flow/validation/flow_validator.d.ts.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.js +19 -27
- package/dist/presentation/flow/validation/flow_validator.js.map +1 -1
- package/dist/presentation/flow/validation/job_type_validator.d.ts +7 -0
- package/dist/presentation/flow/validation/job_type_validator.d.ts.map +1 -1
- package/dist/presentation/flow/validation/job_type_validator.js +52 -27
- package/dist/presentation/flow/validation/job_type_validator.js.map +1 -1
- package/dist/presentation/flow/validation/user_details_validator.d.ts +9 -0
- package/dist/presentation/flow/validation/user_details_validator.d.ts.map +1 -0
- package/dist/presentation/flow/validation/user_details_validator.js +40 -0
- package/dist/presentation/flow/validation/user_details_validator.js.map +1 -0
- package/dist/presentation/screen/helpers/capture_screen_helpers.d.ts.map +1 -1
- package/dist/presentation/screen/helpers/capture_screen_helpers.js +1 -0
- package/dist/presentation/screen/helpers/capture_screen_helpers.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.d.ts.map +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.js +64 -18
- package/dist/presentation/screen/use_smile_id_capture_screen.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/react_native_mock.tsx +22 -0
- package/src/__tests__/snapshots/__snapshots__/screens_structural.test.tsx.snap +14 -8
- package/src/camera/__tests__/camera_permissions.test.ts +72 -0
- package/src/camera/__tests__/camera_system_focus.test.ts +173 -0
- package/src/camera/__tests__/use_smile_id_camera_preview.test.tsx +461 -0
- package/src/camera/__tests__/use_smile_id_frame_stream_hook.test.tsx +229 -0
- package/src/camera/camera_defaults.ts +3 -0
- package/src/core/exception/__tests__/builder_validation_exceptions.test.ts +14 -0
- package/src/core/exception/flow_exceptions.test.ts +10 -0
- package/src/core/exception/flow_exceptions.ts +3 -2
- package/src/core/exception/missing_job_type_exception.ts +2 -4
- package/src/data/api/__tests__/smile_id_api.test.ts +275 -1
- package/src/data/api/interceptors/__tests__/credential_header_redaction.test.ts +144 -0
- package/src/data/api/interceptors/smile_auth_interceptor.ts +1 -1
- package/src/data/api/smile_id_api.ts +36 -49
- package/src/data/dsl/__tests__/use_smile_id_network_builder.test.ts +157 -0
- package/src/data/factory/__tests__/signed_job_submission_headers.test.ts +1 -1
- package/src/data/model/__tests__/token_payload.test.ts +224 -0
- package/src/data/model/__tests__/user_details_payload.test.ts +100 -0
- package/src/data/model/job_request.ts +27 -12
- package/src/data/model/token_payload.ts +182 -0
- package/src/data/model/user_details_payload.ts +84 -0
- package/src/data/repository/__tests__/network_retries.test.ts +1 -1
- package/src/data/repository/__tests__/repository_dispatch.test.ts +366 -0
- package/src/data/repository/__tests__/use_smile_id_repository.test.ts +1 -1
- package/src/data/security/__tests__/signing_crypto.test.ts +203 -0
- package/src/data/util/__tests__/use_smile_id_token_decoder.test.ts +179 -0
- package/src/data/util/use_smile_id_token_decoder.ts +29 -8
- package/src/l10n/__tests__/use_smile_id_localizations.test.ts +122 -5
- package/src/l10n/__tests__/use_smile_id_localizations_provider.test.tsx +112 -0
- package/src/l10n/__tests__/use_smile_id_localizations_surface.test.ts +231 -0
- package/src/l10n/strings_en.ts +5 -1
- package/src/l10n/use_smile_id_localizations.ts +80 -3
- package/src/metadata/__tests__/device_orientation_sampling.test.ts +165 -0
- package/src/metadata/__tests__/geolocation.test.ts +150 -0
- package/src/metadata/__tests__/metadata_configuration_objects.test.ts +1 -1
- package/src/metadata/__tests__/metadata_device_context.test.ts +3 -3
- package/src/metadata/__tests__/metadata_golden_fixture.json +1 -1
- package/src/metadata/__tests__/metadata_payload_golden.test.ts +4 -4
- package/src/metadata/__tests__/optional_native_modules.test.ts +234 -0
- package/src/metadata/__tests__/use_smile_id_metadata.test.ts +1 -1
- package/src/metadata/__tests__/use_smile_id_metadata_factory.test.ts +615 -0
- package/src/metadata/metadata_configuration_mappers.ts +1 -1
- package/src/metadata/use_smile_id_metadata_factory.ts +1 -1
- package/src/presentation/components/__tests__/use_smile_id_content_card.test.tsx +112 -0
- package/src/presentation/components/__tests__/use_smile_id_face_oval_image.test.tsx +190 -0
- package/src/presentation/components/__tests__/use_smile_id_learn_more_content.test.tsx +159 -0
- package/src/presentation/components/__tests__/use_smile_id_processing_center_indicator.test.tsx +88 -0
- package/src/presentation/components/use_smile_id_button.tsx +7 -2
- package/src/presentation/flow/__tests__/flow_navigation_manager.test.ts +323 -1
- package/src/presentation/flow/__tests__/use_smile_id_flow.test.tsx +651 -0
- package/src/presentation/flow/dsl/__tests__/use_smile_id_builder_component.test.tsx +120 -0
- package/src/presentation/flow/dsl/builder/screens_builder.ts +4 -6
- package/src/presentation/flow/dsl/use_smile_id_flow_builder.ts +27 -1
- package/src/presentation/flow/flow_navigation_manager.ts +102 -92
- package/src/presentation/flow/graph/__tests__/screen_renderer.test.tsx +102 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_capture_screen.test.tsx +836 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_consent_screen.test.tsx +165 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_instructions_screen.test.tsx +163 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_preview_screen.test.tsx +224 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_processing_screen.test.tsx +338 -0
- package/src/presentation/flow/screen/orchestrated_processing_screen.tsx +42 -5
- package/src/presentation/flow/use_smile_id_flow.tsx +52 -8
- package/src/presentation/flow/validation/__tests__/flow_validator.test.ts +158 -0
- package/src/presentation/flow/validation/__tests__/job_type_validator.test.ts +200 -0
- package/src/presentation/flow/validation/flow_validator.ts +30 -31
- package/src/presentation/flow/validation/job_type_validator.ts +73 -39
- package/src/presentation/flow/validation/user_details_validator.ts +53 -0
- package/src/presentation/screen/__tests__/feedback_copy_table.test.ts +145 -0
- package/src/presentation/screen/__tests__/use_smile_id_capture_screen.test.tsx +348 -0
- package/src/presentation/screen/__tests__/use_smile_id_document_back_instructions.test.tsx +3 -1
- package/src/presentation/screen/__tests__/use_smile_id_permission_screen.test.tsx +68 -0
- package/src/presentation/screen/helpers/capture_screen_helpers.ts +1 -0
- package/src/presentation/screen/use_smile_id_capture_screen.tsx +69 -18
- package/src/presentation/theme/__tests__/use_use_smile_id_fonts.test.tsx +166 -0
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
} from "../../../core/models/result";
|
|
22
22
|
import { JobType } from "../../../core/models/job_type";
|
|
23
23
|
import { DocumentType } from "../../../core/models/document_type";
|
|
24
|
-
import {
|
|
24
|
+
import { GlobalPrivacyControl } from "../../../core/models/global_privacy_control";
|
|
25
|
+
import {
|
|
26
|
+
MissingJobTypeException,
|
|
27
|
+
MissingRequiredFieldException,
|
|
28
|
+
UseSmileIDErrorCode,
|
|
29
|
+
} from "../../../core/exception";
|
|
25
30
|
import type { UseSmileIDAnalyticsEvent } from "../analytics/use_smile_id_analytics_event";
|
|
26
31
|
|
|
27
32
|
function buildScreens(block: (s: ScreensBuilder) => void): ScreensConfig {
|
|
@@ -528,6 +533,41 @@ describe("FlowNavigationManager — submission lifecycle", () => {
|
|
|
528
533
|
expect(manager.jobSubmissionState.kind).toBe("failure");
|
|
529
534
|
});
|
|
530
535
|
|
|
536
|
+
it("an unrecognised jobType fails submission with the typed builder exception", async () => {
|
|
537
|
+
const repository = new StubRepository();
|
|
538
|
+
const manager = makeManager(
|
|
539
|
+
buildScreens((screens) => screens.processing()),
|
|
540
|
+
vi.fn(),
|
|
541
|
+
{
|
|
542
|
+
userDetails: SAMPLE_USER_DETAILS,
|
|
543
|
+
network: {
|
|
544
|
+
config: {
|
|
545
|
+
jobType: 99 as JobType,
|
|
546
|
+
token: "k",
|
|
547
|
+
partnerConfig: {
|
|
548
|
+
partnerId: "p",
|
|
549
|
+
callbackUrl: "",
|
|
550
|
+
useSandbox: true,
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
repository,
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
manager.beginSubmissionIfNeeded();
|
|
559
|
+
await flushMicrotasks();
|
|
560
|
+
expect(manager.jobSubmissionState.kind).toBe("failure");
|
|
561
|
+
if (manager.jobSubmissionState.kind === "failure") {
|
|
562
|
+
expect(manager.jobSubmissionState.error).toBeInstanceOf(
|
|
563
|
+
MissingJobTypeException,
|
|
564
|
+
);
|
|
565
|
+
expect(
|
|
566
|
+
(manager.jobSubmissionState.error as MissingJobTypeException).errorCode,
|
|
567
|
+
).toBe(UseSmileIDErrorCode.Builder.MissingJobType);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
531
571
|
it("beginSubmissionIfNeeded is idempotent — second call while loading is a no-op", async () => {
|
|
532
572
|
const repository = new StubRepository();
|
|
533
573
|
repository.manual = true;
|
|
@@ -938,6 +978,288 @@ describe("FlowNavigationManager — request assembly per JobType", () => {
|
|
|
938
978
|
});
|
|
939
979
|
});
|
|
940
980
|
|
|
981
|
+
describe("FlowNavigationManager — token-provided user details & consent (assembly)", () => {
|
|
982
|
+
// Mint a structurally valid JWT with a caller-supplied payload — only
|
|
983
|
+
// the base64url-encoded middle segment is decoded.
|
|
984
|
+
function makeJwt(payload: Record<string, unknown>): string {
|
|
985
|
+
const payloadSegment = Buffer.from(JSON.stringify(payload)).toString(
|
|
986
|
+
"base64url",
|
|
987
|
+
);
|
|
988
|
+
return `eyJhbGciOiJub25lIn0.${payloadSegment}.stub-sig`;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const completeConsentClaim = {
|
|
992
|
+
granted: true,
|
|
993
|
+
granted_at: "2026-01-01T09:00:00.000Z",
|
|
994
|
+
notice_language: "EN",
|
|
995
|
+
notice_privacy_policy_url: "https://partner.test/privacy",
|
|
996
|
+
} as const;
|
|
997
|
+
|
|
998
|
+
const fullBindingToken = makeJwt({
|
|
999
|
+
payload: {
|
|
1000
|
+
given_names: "pii_abc",
|
|
1001
|
+
last_name: "pii_def",
|
|
1002
|
+
email: "pii_ghi",
|
|
1003
|
+
phone_number: "pii_jkl",
|
|
1004
|
+
consent: completeConsentClaim,
|
|
1005
|
+
},
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
// Real tokens minted by the /v3/token deployment (same fixtures as
|
|
1009
|
+
// use_smile_id_token_decoder.test.ts): ES256-signed, expired minutes
|
|
1010
|
+
// after minting, PII values as opaque vault references. Decoded through
|
|
1011
|
+
// the production decoder so the whole flow — decode, screen skip,
|
|
1012
|
+
// assembly reduction — runs exactly as in production.
|
|
1013
|
+
const MINTED_FULL_BINDING_TOKEN =
|
|
1014
|
+
"eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleV8wMWtibWd6bXczZXloOW5jdjlqcG1kNDlqZiIsInR5cCI6IkpXVCJ9." +
|
|
1015
|
+
"eyJhcGlfdXJsIjoiaHR0cHM6Ly92My10b2tlbi1jb25zZW50LXBheWxvYWQuYXBpLnNtaWxlaWQuY28vdjMiLCJrZXlfaWQiOiIyY2NjOWMyMy01Y2U3LTRkMGUtYWRkYy1lN2I1NGQ4YjkxZGQiLCJwYXlsb2FkIjp7ImNvbnNlbnQiOnsiZ3JhbnRlZCI6dHJ1ZSwiZ3JhbnRlZF9hdCI6IjIwMjYtMDEtMDFUMDk6MDA6MDAuMDAwWiIsIm5vdGljZV9sYW5ndWFnZSI6IkVOIiwibm90aWNlX3ByaXZhY3lfcG9saWN5X3VybCI6Imh0dHBzOi8vanVtYS5wcml2YWN5LXBvbGljeS5jb20ifSwiY291bnRyeSI6Ik5HIiwiaWRfdHlwZSI6Ik5JTl9WMiIsImVtYWlsIjoicGlpXzAxa3Azczl0NzhmZXg5MXFoNDZ5cDg3cmpxIiwiZ2l2ZW5fbmFtZXMiOiJwaWlfMDFrenJtMTcwdGY2OXJxOTUzOHJ6Z2V0eXoiLCJpZF9udW1iZXIiOiJwaWlfMDFra2dhYTliNWZ2Z2EyMnllZXM1ZjRyNnQiLCJsYXN0X25hbWUiOiJwaWlfMDFrcDNzOXQ3cmU4aHI2ZGVyOXEzbnB6N2MiLCJwaG9uZV9udW1iZXIiOiJwaWlfMDFrenJtdHdkeGZlenJ0dGRmOXQzc3Rzem4iLCJjYWxsYmFja191cmwiOiJjYWxsYmFja18wMWt6cm10d2VmZTV5YWgzbmR2NW03MDc3ZyJ9LCJwYXJ0bmVyX2lkIjoiMTQ4MyIsInBvbGljeSI6MTUsImF1ZCI6InNtaWxlaWQtYXBpIiwiZXhwIjoxNzg2NDYwNzEyLCJpYXQiOjE3ODY0NTk4MTIsImlzcyI6InNtaWxlaWQtYXV0aCJ9." +
|
|
1016
|
+
"cuZgo64eEt5vNvND_RvCVpJyTv4rqax9ebkLV4i4uI4raz6fBhazxspbaoErYC0wxwRmn5EhPAdAdDnNKI6tEQ";
|
|
1017
|
+
|
|
1018
|
+
const MINTED_NAMES_AND_EMAIL_TOKEN =
|
|
1019
|
+
"eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleV8wMWtibWd6bXczZXloOW5jdjlqcG1kNDlqZiIsInR5cCI6IkpXVCJ9." +
|
|
1020
|
+
"eyJhcGlfdXJsIjoiaHR0cHM6Ly92My10b2tlbi1jb25zZW50LXBheWxvYWQuYXBpLnNtaWxlaWQuY28vdjMiLCJrZXlfaWQiOiIyY2NjOWMyMy01Y2U3LTRkMGUtYWRkYy1lN2I1NGQ4YjkxZGQiLCJwYXlsb2FkIjp7ImNvbnNlbnQiOnsiZ3JhbnRlZCI6dHJ1ZSwiZ3JhbnRlZF9hdCI6IjIwMjYtMDEtMDFUMDk6MDA6MDAuMDAwWiIsIm5vdGljZV9sYW5ndWFnZSI6IkVOIiwibm90aWNlX3ByaXZhY3lfcG9saWN5X3VybCI6Imh0dHBzOi8vanVtYS5wcml2YWN5LXBvbGljeS5jb20ifSwiZW1haWwiOiJwaWlfMDFrcDNzOXQ3OGZleDkxcWg0NnlwODdyanEiLCJnaXZlbl9uYW1lcyI6InBpaV8wMWt6cm0xNzB0ZjY5cnE5NTM4cnpnZXR5eiIsImxhc3RfbmFtZSI6InBpaV8wMWtwM3M5dDdyZThocjZkZXI5cTNucHo3YyJ9LCJwYXJ0bmVyX2lkIjoiMTQ4MyIsInBvbGljeSI6MTUsImF1ZCI6InNtaWxlaWQtYXBpIiwiZXhwIjoxNzg2NDU5ODcxLCJpYXQiOjE3ODY0NTg5NzEsImlzcyI6InNtaWxlaWQtYXV0aCJ9." +
|
|
1021
|
+
"s5BFthU2B1aKxYDYyyWYDmRpLoHibH34r2Lk4OS-wZQC8pmYROjWBiORx6n2_0srmxz_SA2eZo_FHyAr-ew_Zg";
|
|
1022
|
+
|
|
1023
|
+
function assemble(
|
|
1024
|
+
token: string,
|
|
1025
|
+
extraConfig: Partial<FlowConfigurationSnapshot> = {},
|
|
1026
|
+
seedManager: (m: FlowNavigationManager) => void = () => {},
|
|
1027
|
+
): {
|
|
1028
|
+
readonly repository: StubRepository;
|
|
1029
|
+
readonly manager: FlowNavigationManager;
|
|
1030
|
+
} {
|
|
1031
|
+
const repository = new StubRepository();
|
|
1032
|
+
const manager = makeManager(
|
|
1033
|
+
buildScreens((screens) => screens.processing()),
|
|
1034
|
+
vi.fn(),
|
|
1035
|
+
{
|
|
1036
|
+
enhancedKYCParams: {
|
|
1037
|
+
idType: "PASSPORT",
|
|
1038
|
+
idNumber: "X1",
|
|
1039
|
+
country: "NG",
|
|
1040
|
+
},
|
|
1041
|
+
...extraConfig,
|
|
1042
|
+
network: {
|
|
1043
|
+
config: {
|
|
1044
|
+
jobType: JobType.enhancedKyc,
|
|
1045
|
+
token,
|
|
1046
|
+
partnerConfig: {
|
|
1047
|
+
partnerId: "p",
|
|
1048
|
+
callbackUrl: "",
|
|
1049
|
+
useSandbox: true,
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
},
|
|
1053
|
+
},
|
|
1054
|
+
repository,
|
|
1055
|
+
);
|
|
1056
|
+
seedManager(manager);
|
|
1057
|
+
manager.beginSubmissionIfNeeded();
|
|
1058
|
+
return { repository, manager };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
it("assembles without userDetails when the token binds names + a contact field", () => {
|
|
1062
|
+
const { repository } = assemble(fullBindingToken);
|
|
1063
|
+
expect(repository.received).toHaveLength(1);
|
|
1064
|
+
const request = repository.received[0]!;
|
|
1065
|
+
expect(request.userDetails).toBeUndefined();
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
it("omits the consent record when token consent is complete and no local source exists", () => {
|
|
1069
|
+
// The DEFAULT_CONSENT granted:false placeholder must NOT be emitted
|
|
1070
|
+
// on the token-complete path — the server injects the token's consent.
|
|
1071
|
+
const { repository } = assemble(fullBindingToken, {
|
|
1072
|
+
userDetails: SAMPLE_USER_DETAILS,
|
|
1073
|
+
});
|
|
1074
|
+
const request = repository.received[0]!;
|
|
1075
|
+
expect(request.consent).toBeUndefined();
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
it("still throws MissingRequiredFieldException(userDetails) when the token binds names only", async () => {
|
|
1079
|
+
const namesOnlyToken = makeJwt({
|
|
1080
|
+
payload: { given_names: "pii_abc", last_name: "pii_def" },
|
|
1081
|
+
});
|
|
1082
|
+
const { repository, manager } = assemble(namesOnlyToken);
|
|
1083
|
+
await flushMicrotasks();
|
|
1084
|
+
expect(repository.received).toHaveLength(0);
|
|
1085
|
+
const state = manager.jobSubmissionState;
|
|
1086
|
+
expect(state.kind).toBe("failure");
|
|
1087
|
+
if (state.kind === "failure") {
|
|
1088
|
+
expect(state.error).toBeInstanceOf(MissingRequiredFieldException);
|
|
1089
|
+
expect(
|
|
1090
|
+
(state.error as MissingRequiredFieldException).errorDetails,
|
|
1091
|
+
).toEqual({ fieldName: "userDetails" });
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
it("keeps the granted:false placeholder when token consent is only partial", () => {
|
|
1096
|
+
const partialConsentToken = makeJwt({
|
|
1097
|
+
payload: {
|
|
1098
|
+
given_names: "pii_abc",
|
|
1099
|
+
last_name: "pii_def",
|
|
1100
|
+
email: "pii_ghi",
|
|
1101
|
+
consent: { granted: true, granted_at: "2026-01-01T09:00:00.000Z" },
|
|
1102
|
+
},
|
|
1103
|
+
});
|
|
1104
|
+
const { repository } = assemble(partialConsentToken);
|
|
1105
|
+
const request = repository.received[0]!;
|
|
1106
|
+
expect(request.consent).toEqual({
|
|
1107
|
+
granted: false,
|
|
1108
|
+
grantedAt: "",
|
|
1109
|
+
noticeLanguage: "",
|
|
1110
|
+
noticePrivacyPolicyUrl: "",
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
it("a complete token consent omits the consent record even when a local one exists", () => {
|
|
1115
|
+
// Token data always wins: even a captured local consent is not sent —
|
|
1116
|
+
// the part is omitted and the server injects the token's record.
|
|
1117
|
+
const { repository } = assemble(
|
|
1118
|
+
fullBindingToken,
|
|
1119
|
+
{ userDetails: SAMPLE_USER_DETAILS },
|
|
1120
|
+
(m) => m.setConsent(consentInfo),
|
|
1121
|
+
);
|
|
1122
|
+
const request = repository.received[0]!;
|
|
1123
|
+
expect(request.consent).toBeUndefined();
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
it("a complete token consent drops the consent screen from the flow", () => {
|
|
1127
|
+
const repository = new StubRepository();
|
|
1128
|
+
const manager = makeManager(
|
|
1129
|
+
buildScreens((screens) => {
|
|
1130
|
+
screens.consent((c) => {
|
|
1131
|
+
c.partnerName = "Acme";
|
|
1132
|
+
c.partnerPrivacyPolicyUrl = "https://example.test/privacy";
|
|
1133
|
+
});
|
|
1134
|
+
screens.capture((c) => {
|
|
1135
|
+
c.captureType = CaptureType.selfie;
|
|
1136
|
+
});
|
|
1137
|
+
}),
|
|
1138
|
+
vi.fn(),
|
|
1139
|
+
{
|
|
1140
|
+
userDetails: SAMPLE_USER_DETAILS,
|
|
1141
|
+
network: {
|
|
1142
|
+
config: {
|
|
1143
|
+
jobType: JobType.smartSelfieEnrollment,
|
|
1144
|
+
token: fullBindingToken,
|
|
1145
|
+
partnerConfig: {
|
|
1146
|
+
partnerId: "p",
|
|
1147
|
+
callbackUrl: "",
|
|
1148
|
+
useSandbox: true,
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
repository,
|
|
1154
|
+
);
|
|
1155
|
+
expect(manager.currentScreen?.kind).toBe("capture");
|
|
1156
|
+
expect(manager.navigationPath.some((step) => step.kind === "consent")).toBe(
|
|
1157
|
+
false,
|
|
1158
|
+
);
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
it("a preset consentInformation is still sent in full when token consent is partial", () => {
|
|
1162
|
+
const partialConsentToken = makeJwt({
|
|
1163
|
+
payload: { consent: { granted: true } },
|
|
1164
|
+
});
|
|
1165
|
+
const { repository } = assemble(partialConsentToken, {
|
|
1166
|
+
userDetails: SAMPLE_USER_DETAILS,
|
|
1167
|
+
consentInformation: consentInfo,
|
|
1168
|
+
});
|
|
1169
|
+
const request = repository.received[0]!;
|
|
1170
|
+
expect(request.consent).toEqual(consentInfo);
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
it("the minted full-binding token drives the flow end to end with nothing local", () => {
|
|
1174
|
+
const repository = new StubRepository();
|
|
1175
|
+
const manager = makeManager(
|
|
1176
|
+
buildScreens((screens) => {
|
|
1177
|
+
screens.consent((c) => {
|
|
1178
|
+
c.partnerName = "Acme";
|
|
1179
|
+
c.partnerPrivacyPolicyUrl = "https://example.test/privacy";
|
|
1180
|
+
});
|
|
1181
|
+
screens.capture((c) => {
|
|
1182
|
+
c.captureType = CaptureType.selfie;
|
|
1183
|
+
});
|
|
1184
|
+
screens.processing();
|
|
1185
|
+
}),
|
|
1186
|
+
vi.fn(),
|
|
1187
|
+
{
|
|
1188
|
+
network: {
|
|
1189
|
+
config: {
|
|
1190
|
+
jobType: JobType.smartSelfieEnrollment,
|
|
1191
|
+
token: MINTED_FULL_BINDING_TOKEN,
|
|
1192
|
+
partnerConfig: {
|
|
1193
|
+
partnerId: "p",
|
|
1194
|
+
callbackUrl: "",
|
|
1195
|
+
useSandbox: true,
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
repository,
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
expect(manager.currentScreen?.kind).toBe("capture");
|
|
1204
|
+
expect(manager.navigationPath.some((step) => step.kind === "consent")).toBe(
|
|
1205
|
+
false,
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
manager.setCapturedSelfie("file:///selfie.jpg", ["file:///l1.jpg"]);
|
|
1209
|
+
manager.beginSubmissionIfNeeded();
|
|
1210
|
+
|
|
1211
|
+
expect(repository.received).toHaveLength(1);
|
|
1212
|
+
const request = repository.received[0]!;
|
|
1213
|
+
expect(request.userDetails).toBeUndefined();
|
|
1214
|
+
expect(request.consent).toBeUndefined();
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it("the minted names-and-email token reduces builder details to the unbound phone number", () => {
|
|
1218
|
+
const { repository } = assemble(MINTED_NAMES_AND_EMAIL_TOKEN, {
|
|
1219
|
+
userDetails: SAMPLE_USER_DETAILS,
|
|
1220
|
+
});
|
|
1221
|
+
const request = repository.received[0]!;
|
|
1222
|
+
expect(request.userDetails).toEqual({ phoneNumber: "+2348000000000" });
|
|
1223
|
+
expect(request.consent).toBeUndefined();
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
it("regression: without a payload claim the request carries userDetails and the placeholder consent", () => {
|
|
1227
|
+
const legacyToken = makeJwt({ sdk_config: { livenessImageCount: 5 } });
|
|
1228
|
+
const { repository } = assemble(legacyToken, {
|
|
1229
|
+
userDetails: SAMPLE_USER_DETAILS,
|
|
1230
|
+
});
|
|
1231
|
+
const request = repository.received[0]!;
|
|
1232
|
+
expect(request.userDetails).toEqual(SAMPLE_USER_DETAILS);
|
|
1233
|
+
expect(request.tokenPayload).toBeUndefined();
|
|
1234
|
+
expect(request.consent).toEqual({
|
|
1235
|
+
granted: false,
|
|
1236
|
+
grantedAt: "",
|
|
1237
|
+
noticeLanguage: "",
|
|
1238
|
+
noticePrivacyPolicyUrl: "",
|
|
1239
|
+
});
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
it("allowOfflineMode skips the token decode — strict legacy assembly applies", async () => {
|
|
1243
|
+
const { repository, manager } = assemble(fullBindingToken, {
|
|
1244
|
+
config: {
|
|
1245
|
+
enableCrashReporting: false,
|
|
1246
|
+
enableDebugMode: false,
|
|
1247
|
+
allowOfflineMode: true,
|
|
1248
|
+
globalPrivacyControl: GlobalPrivacyControl.OptOut,
|
|
1249
|
+
},
|
|
1250
|
+
});
|
|
1251
|
+
await flushMicrotasks();
|
|
1252
|
+
// Token binds everything, but offline mode never reads it — the
|
|
1253
|
+
// missing builder userDetails still fails assembly.
|
|
1254
|
+
expect(repository.received).toHaveLength(0);
|
|
1255
|
+
const state = manager.jobSubmissionState;
|
|
1256
|
+
expect(state.kind).toBe("failure");
|
|
1257
|
+
if (state.kind === "failure") {
|
|
1258
|
+
expect(state.error).toBeInstanceOf(MissingRequiredFieldException);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
});
|
|
1262
|
+
|
|
941
1263
|
describe("FlowNavigationManager — typed missing-field validation (parity)", () => {
|
|
942
1264
|
// Drives a submission to its failure state and returns the surfaced
|
|
943
1265
|
// error so we can assert the typed `MissingRequiredFieldException`
|