mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
Add disclose proof request, generation, and result route spine (#1891)
* save wip * commit * fixes
This commit is contained in:
@@ -32,9 +32,11 @@ import { SocialSignOnMethodPickerScreen } from './screens/onboarding/SocialSignO
|
||||
import { SocialSignOnPickerScreen } from './screens/onboarding/SocialSignOnPickerScreen';
|
||||
import { TourScreen } from './screens/onboarding/TourScreen';
|
||||
import { DialogueWithCtaScreen } from './screens/proving/DialogueWithCtaScreen';
|
||||
import { DiscloseResultScreen } from './screens/proving/DiscloseResultScreen';
|
||||
import { KycPendingScreen } from './screens/proving/KycPendingScreen';
|
||||
import { KycSuccessScreen } from './screens/proving/KycSuccessScreen';
|
||||
import { ProofGenerationDialogueScreen } from './screens/proving/ProofGenerationDialogueScreen';
|
||||
import { ProofGenerationRouteScreen } from './screens/proving/ProofGenerationRouteScreen';
|
||||
import { ProofGenerationSuccessScreen } from './screens/proving/ProofGenerationSuccessScreen';
|
||||
import { ProofHistoryScreen } from './screens/proving/ProofHistoryScreen';
|
||||
import { ProofRequestReceiptScreen } from './screens/proving/ProofRequestReceiptScreen';
|
||||
@@ -72,7 +74,8 @@ export const App: React.FC = () => (
|
||||
<Route path="/onboarding/failure" element={<RegistrationFailureScreen />} />
|
||||
<Route path="/onboarding/kyc-failure" element={<KycFailureScreen />} />
|
||||
<Route path="/proving" element={<ProvingScreen />} />
|
||||
<Route path="/proving/result" element={<VerificationResultScreen />} />
|
||||
<Route path="/proving/generating" element={<ProofGenerationRouteScreen />} />
|
||||
<Route path="/proving/result" element={<DiscloseResultScreen />} />
|
||||
<Route path="/settings" element={<SettingsScreen />} />
|
||||
<Route path="/settings/security" element={<SecurityScreen />} />
|
||||
<Route path="/settings/notifications" element={<NotificationPreferencesScreen />} />
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { colors, StatusState, WarningOctagonIcon } from '@selfxyz/euclid';
|
||||
import type { BridgeError, VerificationResult } from '@selfxyz/webview-bridge';
|
||||
|
||||
import { useSelfClient } from '../../providers/SelfClientProvider';
|
||||
import { useVerificationRequest } from '../../providers/VerificationRequestProvider';
|
||||
import { WEB_SAFE_AREA } from '../../utils/insets';
|
||||
import { normalizeError } from '../../utils/provingUtils';
|
||||
|
||||
interface DiscloseResultLocationState {
|
||||
success?: boolean;
|
||||
error?: BridgeError | string;
|
||||
resultSent?: boolean;
|
||||
}
|
||||
|
||||
export const DiscloseResultScreen: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { analytics, haptic, lifecycle } = useSelfClient();
|
||||
const { request, verificationId } = useVerificationRequest();
|
||||
|
||||
const { success = true, error, resultSent = false } = (location.state as DiscloseResultLocationState | null) ?? {};
|
||||
const normalizedError = normalizeError(error);
|
||||
const result = useMemo<VerificationResult>(
|
||||
() =>
|
||||
success
|
||||
? {
|
||||
success: true,
|
||||
userId: request.userId,
|
||||
verificationId,
|
||||
claims: {
|
||||
resultType: 'proofRequested',
|
||||
},
|
||||
}
|
||||
: {
|
||||
success: false,
|
||||
userId: request.userId,
|
||||
verificationId,
|
||||
claims: {
|
||||
resultType: 'proofRequested',
|
||||
},
|
||||
error: normalizedError ?? {
|
||||
code: 'proof_generation_failed',
|
||||
message: 'The proof request could not be completed.',
|
||||
},
|
||||
},
|
||||
[normalizedError, request.userId, success, verificationId],
|
||||
);
|
||||
|
||||
const onContinue = useCallback(async () => {
|
||||
haptic.trigger('selection');
|
||||
let hasDeliveredResult = resultSent;
|
||||
|
||||
if (!resultSent && result) {
|
||||
try {
|
||||
await lifecycle.setResult(result);
|
||||
hasDeliveredResult = true;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to deliver result';
|
||||
analytics.trackEvent('verification_result_callback_failed', {
|
||||
error: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
if (hasDeliveredResult) {
|
||||
await lifecycle.dismiss();
|
||||
}
|
||||
navigate('/', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
navigate('/proving', { replace: true });
|
||||
}, [analytics, haptic, lifecycle, navigate, result, resultSent, success]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
paddingTop: WEB_SAFE_AREA.insets.top,
|
||||
paddingBottom: WEB_SAFE_AREA.insets.bottom,
|
||||
}}
|
||||
>
|
||||
<StatusState
|
||||
variant={success ? 'success' : 'fail'}
|
||||
title={success ? 'Proof Generated' : 'Proof Generation Failed'}
|
||||
description={
|
||||
success
|
||||
? 'Your identity was shared successfully for this request.'
|
||||
: (normalizedError?.message ?? 'The proof request could not be completed. Please try again.')
|
||||
}
|
||||
animationSource={success ? '/animations/proof-success.json' : undefined}
|
||||
animationSize={240}
|
||||
loopAnimation={false}
|
||||
buttonText={success ? 'Done' : 'Try Again'}
|
||||
onButtonPress={onContinue}
|
||||
icon={success ? undefined : <WarningOctagonIcon size={64} color={colors.red500} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type React from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ProofGenerationScreen as EuclidProofGenerationScreen } from '@selfxyz/euclid';
|
||||
import { useProvingStore } from '@selfxyz/mobile-sdk-alpha/browser';
|
||||
|
||||
import { useSelfClient } from '../../providers/SelfClientProvider';
|
||||
import { useVerificationRequest } from '../../providers/VerificationRequestProvider';
|
||||
import { WEB_SAFE_AREA } from '../../utils/insets';
|
||||
import { getFailureState, getGenerationStep, getIdCardProps } from '../../utils/provingUtils';
|
||||
import { hasDiscloseRequestContext } from '../../utils/verificationRequest';
|
||||
|
||||
export const ProofGenerationRouteScreen: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { client, analytics } = useSelfClient();
|
||||
const { request, displayLabels } = useVerificationRequest();
|
||||
const init = useProvingStore(state => state.init);
|
||||
const setUserConfirmed = useProvingStore(state => state.setUserConfirmed);
|
||||
const currentState = useProvingStore(state => state.currentState);
|
||||
const passportData = useProvingStore(state => state.passportData);
|
||||
const errorCode = useProvingStore(state => state.error_code);
|
||||
const reason = useProvingStore(state => state.reason);
|
||||
|
||||
const hasValidRequestContext = hasDiscloseRequestContext({ request, displayLabels });
|
||||
const hasInitializedRef = useRef(false);
|
||||
const hasAutoConfirmedRef = useRef(false);
|
||||
const hasNavigatedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasValidRequestContext) {
|
||||
navigate('/', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasInitializedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasInitializedRef.current = true;
|
||||
void init(client, 'disclose').catch(error => {
|
||||
if (hasNavigatedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasNavigatedRef.current = true;
|
||||
const message = error instanceof Error ? error.message : 'The proof request could not be completed.';
|
||||
analytics.trackEvent('prove_generation_init_failed', { error: message });
|
||||
navigate('/proving/result', {
|
||||
replace: true,
|
||||
state: {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'proof_generation_init_failed',
|
||||
message,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}, [analytics, client, hasValidRequestContext, init, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasNavigatedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentState === 'passport_data_not_found') {
|
||||
hasNavigatedRef.current = true;
|
||||
navigate('/proving/result', {
|
||||
replace: true,
|
||||
state: {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'passport_data_not_found',
|
||||
message: 'No document found. Please register a document first.',
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentState === 'ready_to_prove' && !hasAutoConfirmedRef.current) {
|
||||
hasAutoConfirmedRef.current = true;
|
||||
setUserConfirmed(client);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentState === 'completed') {
|
||||
hasNavigatedRef.current = true;
|
||||
navigate('/proving/result', {
|
||||
replace: true,
|
||||
state: { success: true },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentState === 'error' || currentState === 'failure' || currentState === 'passport_not_supported') {
|
||||
hasNavigatedRef.current = true;
|
||||
navigate('/proving/result', {
|
||||
replace: true,
|
||||
state: {
|
||||
success: false,
|
||||
error: getFailureState(currentState, errorCode, reason),
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [client, currentState, errorCode, navigate, reason, setUserConfirmed]);
|
||||
|
||||
const idCardProps = useMemo(() => getIdCardProps(passportData?.documentCategory), [passportData?.documentCategory]);
|
||||
|
||||
if (!hasValidRequestContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuclidProofGenerationScreen
|
||||
{...WEB_SAFE_AREA}
|
||||
step={getGenerationStep(currentState ?? 'idle')}
|
||||
idCardProps={idCardProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -3,30 +3,28 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ProofRequestScreen, SelfLogo } from '@selfxyz/euclid';
|
||||
import type { VerificationResult } from '@selfxyz/webview-bridge';
|
||||
|
||||
import { useSelfClient } from '../../providers/SelfClientProvider';
|
||||
import { useVerificationRequest } from '../../providers/VerificationRequestProvider';
|
||||
import { WEB_SAFE_AREA } from '../../utils/insets';
|
||||
|
||||
function titleCaseDisclosure(disclosure: string): string {
|
||||
return disclosure
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.replace(/\b\w/g, match => match.toUpperCase());
|
||||
}
|
||||
import { titleCaseDisclosure } from '../../utils/provingUtils';
|
||||
import { hasDiscloseRequestContext } from '../../utils/verificationRequest';
|
||||
|
||||
export const ProvingScreen: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { analytics, haptic, lifecycle } = useSelfClient();
|
||||
const { request, displayLabels, requestType, appName, appEndpoint, timestamp, verificationId } =
|
||||
useVerificationRequest();
|
||||
const [proving, setProving] = useState(false);
|
||||
const { request, displayLabels, appName, appEndpoint, timestamp } = useVerificationRequest();
|
||||
const hasValidRequestContext = hasDiscloseRequestContext({ request, displayLabels });
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasValidRequestContext) {
|
||||
navigate('/', { replace: true });
|
||||
}
|
||||
}, [hasValidRequestContext, navigate]);
|
||||
|
||||
const proofItems = useMemo(() => {
|
||||
if (displayLabels && displayLabels.length > 0) {
|
||||
@@ -37,48 +35,30 @@ export const ProvingScreen: React.FC = () => {
|
||||
}));
|
||||
}, [displayLabels, request.disclosures]);
|
||||
|
||||
const onVerify = useCallback(async () => {
|
||||
const result: VerificationResult = {
|
||||
success: true,
|
||||
userId: request.userId,
|
||||
verificationId,
|
||||
claims: {
|
||||
resultType: requestType,
|
||||
},
|
||||
};
|
||||
|
||||
const onVerify = useCallback(() => {
|
||||
haptic.trigger('selection');
|
||||
analytics.trackEvent('prove_verify_pressed');
|
||||
setProving(true);
|
||||
navigate('/proving/generating', { replace: true });
|
||||
}, [analytics, haptic, navigate]);
|
||||
|
||||
try {
|
||||
await lifecycle.setResult(result);
|
||||
|
||||
navigate('/proving/result', {
|
||||
state: { success: true, result, resultSent: true },
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Proving failed';
|
||||
analytics.trackEvent('prove_verify_failed', { error: message });
|
||||
navigate('/proving/result', {
|
||||
state: { success: false, error: message, result, resultSent: false },
|
||||
});
|
||||
} finally {
|
||||
setProving(false);
|
||||
}
|
||||
}, [analytics, haptic, lifecycle, navigate, request.userId, requestType, verificationId]);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
const onCancel = useCallback(async () => {
|
||||
haptic.trigger('selection');
|
||||
analytics.trackEvent('prove_verify_cancelled');
|
||||
lifecycle.dismiss({ reason: 'user_cancel' });
|
||||
navigate('/');
|
||||
try {
|
||||
await lifecycle.dismiss({ reason: 'user_cancel' });
|
||||
} finally {
|
||||
navigate('/', { replace: true });
|
||||
}
|
||||
}, [analytics, haptic, lifecycle, navigate]);
|
||||
|
||||
if (!hasValidRequestContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ProofRequestScreen
|
||||
{...WEB_SAFE_AREA}
|
||||
variant={proving ? 'loading' : 'default'}
|
||||
variant="default"
|
||||
onClose={onCancel}
|
||||
onConfirm={onVerify}
|
||||
appIcon={<SelfLogo size={40} />}
|
||||
|
||||
159
packages/webview-app/src/utils/provingUtils.test.ts
Normal file
159
packages/webview-app/src/utils/provingUtils.test.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
getFailureState,
|
||||
getGenerationStep,
|
||||
getIdCardProps,
|
||||
normalizeError,
|
||||
titleCaseDisclosure,
|
||||
} from './provingUtils';
|
||||
|
||||
describe('provingUtils', () => {
|
||||
describe('getGenerationStep', () => {
|
||||
it('should map early states to readingRegistry', () => {
|
||||
expect(getGenerationStep('parsing_id_document')).toBe('readingRegistry');
|
||||
expect(getGenerationStep('fetching_data')).toBe('readingRegistry');
|
||||
});
|
||||
|
||||
it('should map proving-phase states to generatingProof', () => {
|
||||
expect(getGenerationStep('validating_document')).toBe('generatingProof');
|
||||
expect(getGenerationStep('init_tee_connexion')).toBe('generatingProof');
|
||||
expect(getGenerationStep('ready_to_prove')).toBe('generatingProof');
|
||||
expect(getGenerationStep('proving')).toBe('generatingProof');
|
||||
});
|
||||
|
||||
it('should map listening_for_status to awaitingVerification', () => {
|
||||
expect(getGenerationStep('listening_for_status')).toBe('awaitingVerification');
|
||||
});
|
||||
|
||||
it('should map post_proving to finishingUp', () => {
|
||||
expect(getGenerationStep('post_proving')).toBe('finishingUp');
|
||||
});
|
||||
|
||||
it('should default unknown states to readingRegistry', () => {
|
||||
expect(getGenerationStep('idle')).toBe('readingRegistry');
|
||||
expect(getGenerationStep('completed')).toBe('readingRegistry');
|
||||
expect(getGenerationStep('')).toBe('readingRegistry');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFailureState', () => {
|
||||
it('should use provided code and reason', () => {
|
||||
expect(getFailureState('error', 'custom_code', 'Something broke')).toEqual({
|
||||
code: 'custom_code',
|
||||
message: 'Something broke',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fall back to currentState when code is null', () => {
|
||||
expect(getFailureState('failure', null, 'Reason')).toEqual({
|
||||
code: 'failure',
|
||||
message: 'Reason',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fall back to default message when reason is null', () => {
|
||||
expect(getFailureState('error', 'some_code', null)).toEqual({
|
||||
code: 'some_code',
|
||||
message: 'The proof request could not be completed.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fall back to both defaults when code and reason are null', () => {
|
||||
expect(getFailureState('passport_not_supported', null, null)).toEqual({
|
||||
code: 'passport_not_supported',
|
||||
message: 'The proof request could not be completed.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIdCardProps', () => {
|
||||
it('should return passport props by default', () => {
|
||||
expect(getIdCardProps()).toEqual({ variant: 'passport', title: 'Passport', subtitle: 'Verified Passport' });
|
||||
expect(getIdCardProps('passport')).toEqual({
|
||||
variant: 'passport',
|
||||
title: 'Passport',
|
||||
subtitle: 'Verified Passport',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return id-card props', () => {
|
||||
expect(getIdCardProps('id_card')).toEqual({ variant: 'id-card', title: 'ID Card', subtitle: 'Verified ID' });
|
||||
});
|
||||
|
||||
it('should return aadhaar props', () => {
|
||||
expect(getIdCardProps('aadhaar')).toEqual({
|
||||
variant: 'aadhaar',
|
||||
title: 'Aadhaar',
|
||||
subtitle: 'Verified IN Aadhaar ID',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return kyc props', () => {
|
||||
expect(getIdCardProps('kyc')).toEqual({
|
||||
variant: 'pending',
|
||||
title: 'KYC Record',
|
||||
subtitle: 'Verification document loaded',
|
||||
});
|
||||
});
|
||||
|
||||
it('should default to passport for unknown categories', () => {
|
||||
expect(getIdCardProps('drivers_license')).toEqual({
|
||||
variant: 'passport',
|
||||
title: 'Passport',
|
||||
subtitle: 'Verified Passport',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('titleCaseDisclosure', () => {
|
||||
it('should convert underscored keys to title case', () => {
|
||||
expect(titleCaseDisclosure('full_name')).toBe('Full Name');
|
||||
});
|
||||
|
||||
it('should convert hyphenated keys to title case', () => {
|
||||
expect(titleCaseDisclosure('date-of-birth')).toBe('Date Of Birth');
|
||||
});
|
||||
|
||||
it('should handle mixed separators', () => {
|
||||
expect(titleCaseDisclosure('some_mixed-key')).toBe('Some Mixed Key');
|
||||
});
|
||||
|
||||
it('should collapse multiple separators and whitespace', () => {
|
||||
expect(titleCaseDisclosure('full__name')).toBe('Full Name');
|
||||
expect(titleCaseDisclosure(' full_name ')).toBe('Full Name');
|
||||
});
|
||||
|
||||
it('should handle single word', () => {
|
||||
expect(titleCaseDisclosure('nationality')).toBe('Nationality');
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeError', () => {
|
||||
it('should return undefined for falsy input', () => {
|
||||
expect(normalizeError(undefined)).toBeUndefined();
|
||||
expect(normalizeError('')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should wrap a string into a BridgeError', () => {
|
||||
expect(normalizeError('Something failed')).toEqual({
|
||||
code: 'proof_generation_failed',
|
||||
message: 'Something failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass through a BridgeError object', () => {
|
||||
const err = { code: 'custom', message: 'msg' };
|
||||
expect(normalizeError(err)).toBe(err);
|
||||
});
|
||||
|
||||
it('should preserve details on a BridgeError object', () => {
|
||||
const err = { code: 'custom', message: 'msg', details: { extra: true } };
|
||||
expect(normalizeError(err)).toBe(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
73
packages/webview-app/src/utils/provingUtils.ts
Normal file
73
packages/webview-app/src/utils/provingUtils.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type { IDCardProps } from '@selfxyz/euclid';
|
||||
import type { BridgeError } from '@selfxyz/webview-bridge';
|
||||
|
||||
export type GenerationStep = 'readingRegistry' | 'generatingProof' | 'awaitingVerification' | 'finishingUp';
|
||||
|
||||
export function getFailureState(
|
||||
currentState: string,
|
||||
code: string | null,
|
||||
reason: string | null,
|
||||
): { code: string; message: string } {
|
||||
return {
|
||||
code: code ?? currentState ?? 'proof_generation_failed',
|
||||
message: reason ?? 'The proof request could not be completed.',
|
||||
};
|
||||
}
|
||||
|
||||
export function getGenerationStep(currentState: string): GenerationStep {
|
||||
switch (currentState) {
|
||||
case 'parsing_id_document':
|
||||
case 'fetching_data':
|
||||
return 'readingRegistry';
|
||||
case 'validating_document':
|
||||
case 'init_tee_connexion':
|
||||
case 'ready_to_prove':
|
||||
case 'proving':
|
||||
return 'generatingProof';
|
||||
case 'listening_for_status':
|
||||
return 'awaitingVerification';
|
||||
case 'post_proving':
|
||||
return 'finishingUp';
|
||||
default:
|
||||
return 'readingRegistry';
|
||||
}
|
||||
}
|
||||
|
||||
export function getIdCardProps(documentCategory?: string): IDCardProps {
|
||||
switch (documentCategory) {
|
||||
case 'id_card':
|
||||
return { variant: 'id-card', title: 'ID Card', subtitle: 'Verified ID' };
|
||||
case 'aadhaar':
|
||||
return { variant: 'aadhaar', title: 'Aadhaar', subtitle: 'Verified IN Aadhaar ID' };
|
||||
case 'kyc':
|
||||
return { variant: 'pending', title: 'KYC Record', subtitle: 'Verification document loaded' };
|
||||
case 'passport':
|
||||
default:
|
||||
return { variant: 'passport', title: 'Passport', subtitle: 'Verified Passport' };
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeError(error: BridgeError | string | undefined): BridgeError | undefined {
|
||||
if (!error) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof error === 'string') {
|
||||
return {
|
||||
code: 'proof_generation_failed',
|
||||
message: error,
|
||||
};
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
export function titleCaseDisclosure(disclosure: string): string {
|
||||
return disclosure
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.replace(/\b\w/g, match => match.toUpperCase());
|
||||
}
|
||||
@@ -5,7 +5,11 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { getPromptMockFromSearch, getPromptMockSearch } from './mockOnboardingFlow';
|
||||
import { parseBrowserHostTargetOrigin, parseVerificationRequestContext } from './verificationRequest';
|
||||
import {
|
||||
hasDiscloseRequestContext,
|
||||
parseBrowserHostTargetOrigin,
|
||||
parseVerificationRequestContext,
|
||||
} from './verificationRequest';
|
||||
|
||||
describe('verificationRequest utils', () => {
|
||||
describe('parseBrowserHostTargetOrigin', () => {
|
||||
@@ -90,6 +94,17 @@ describe('verificationRequest utils', () => {
|
||||
expect(context.appEndpoint).toBe('');
|
||||
expect(context.verificationId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should require disclose items for the disclose route', () => {
|
||||
const withDisclosures = parseVerificationRequestContext('?disclosures=full_name');
|
||||
expect(hasDiscloseRequestContext(withDisclosures)).toBe(true);
|
||||
|
||||
const withLabels = parseVerificationRequestContext('?proofItems=Full%20Name');
|
||||
expect(hasDiscloseRequestContext(withLabels)).toBe(true);
|
||||
|
||||
const empty = parseVerificationRequestContext('?userId=user-1');
|
||||
expect(hasDiscloseRequestContext(empty)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prompt mock utils', () => {
|
||||
|
||||
@@ -23,6 +23,12 @@ interface TargetOriginOptions {
|
||||
allowWildcard?: boolean;
|
||||
}
|
||||
|
||||
export function hasDiscloseRequestContext(
|
||||
context: Pick<ParsedVerificationRequestContext, 'request' | 'displayLabels'>,
|
||||
) {
|
||||
return Boolean((context.displayLabels && context.displayLabels.length > 0) || context.request.disclosures?.length);
|
||||
}
|
||||
|
||||
export function parseBrowserHostTargetOrigin(search: string, options: TargetOriginOptions = {}): string | undefined {
|
||||
const params = new URLSearchParams(search);
|
||||
return normalizeTargetOrigin(params.get('targetOrigin'), options);
|
||||
|
||||
Reference in New Issue
Block a user