Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions inji-verify-sdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion inji-verify-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@injistack/react-inji-verify-sdk",
"version": "0.18.0-beta.15",
"version": "0.18.0-beta.16",
"description": "A react component library to perform Inji verify tasks, such as OpenId4VP sharing, Reading VC QR codes",
"sideEffects": [
"*.css"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { QRCodeSVG } from "qrcode.react";
import {
AppError,
SessionState,
OpenID4VPVerificationProps,
VerificationResults,
CredentialResult
} from "./OpenID4VPVerification.types";
import { vpRequestStatus, vpRequest, vpResult, vpResultByResponseCode } from "../../utils/api";
import { vpRequestStatus, vpRequest, vpResult } from "../../utils/api";
import "./OpenID4VPVerification.css";
import { clearUrl, normalizeVp } from "../../utils/utils";
import { QrData } from "../../types/OVPSchemeQrData";
import { CROSS_DEVICE_FLOW, SAME_DEVICE_FLOW } from "../../utils/constants";
import { OVP_SESSION_REQUEST_ID_KEY, OVP_SESSION_TRANSACTION_ID_KEY, CROSS_DEVICE_FLOW, SAME_DEVICE_FLOW } from "../../utils/constants";

export const isMobileDevice = (): boolean => {
const userAgent = navigator.userAgent;
Expand Down Expand Up @@ -49,8 +50,6 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
const [loading, setLoading] = useState<boolean>(false);
const isActiveRef = useRef(false);
const redirectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const requestIdRef = useRef<string | null>(null);
const transactionIdRef = useRef<string | null>(null);

const shouldShowQRCode = !loading && qrCodeData;

Expand All @@ -75,6 +74,11 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({

const presentationFlow = isSameDeviceFlowEnabled ? SAME_DEVICE_FLOW : CROSS_DEVICE_FLOW;

const clearSessionData = useCallback(() => {
sessionStorage.removeItem(OVP_SESSION_REQUEST_ID_KEY);
sessionStorage.removeItem(OVP_SESSION_TRANSACTION_ID_KEY);
}, []);

const resetState = useCallback(() => {
if (redirectTimeoutRef.current) {
clearTimeout(redirectTimeoutRef.current);
Expand All @@ -83,8 +87,7 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
setQrCodeData(null);
setLoading(false);
isActiveRef.current = false;
requestIdRef.current = null;
transactionIdRef.current = null;
clearSessionData();
}, []);

const getPresentationDefinitionParams = useCallback(
Expand Down Expand Up @@ -139,40 +142,40 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
[onVPProcessed, onVPReceived]
);

const fetchVPResultByResponseCode = useCallback(
async (responseCode: string) => {
if (!isActiveRef.current) return;
setLoading(true);
try {
const response = await vpResultByResponseCode(verifyServiceUrl, responseCode, vpVerificationV2Request);
processVPResultResponse(response);
resetState();
} catch (error) {
if (isActiveRef.current) {
onError(error as AppError);
resetState();
}
} finally {
clearUrl(["response_code"]);
}
},
[verifyServiceUrl, onError, processVPResultResponse, resetState, vpVerificationV2Request]
);

const fetchVPResult = useCallback(
async (txnId: string) => {
async (txnId: string, responseCode?: string | null) => {
if (!isActiveRef.current) return;

setLoading(true);

try {
const response = await vpResult(
verifyServiceUrl,
txnId,
vpVerificationV2Request
);
processVPResultResponse(response, txnId);
resetState();
if (onVPProcessed && txnId) {
const response = await vpResult(
verifyServiceUrl,
txnId,
responseCode,
vpVerificationV2Request
);

const VPResult: VerificationResults =
(response.credentialResults ?? []).map((cred: CredentialResult) => {
const vc = normalizeVp(cred.verifiableCredential);

return {
vc,
verificationResponse: cred,
};
});

onVPProcessed(VPResult);
resetState();
return;
}

if (onVPReceived && txnId && isActiveRef.current) {
onVPReceived(txnId);
resetState();
}
} catch (error) {
if (isActiveRef.current) {
onError(error as AppError);
Expand All @@ -182,11 +185,11 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
clearUrl(["response_code"]);
}
},
[verifyServiceUrl, vpVerificationV2Request, processVPResultResponse, resetState]
[verifyServiceUrl, onVPProcessed, onVPReceived, onError, vpVerificationV2Request]
);

const fetchVPStatus = useCallback(
async (reqId: string, txnId: string) => {
async (reqId: string, txnId: string, responseCode?: string | null) => {
if (!isActiveRef.current) return;

try {
Expand All @@ -195,7 +198,7 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
if (response.status === "ACTIVE") {
fetchVPStatus(reqId, txnId);
} else if (response.status === "VP_SUBMITTED") {
fetchVPResult(txnId);
fetchVPResult(txnId, responseCode);
} else if (response.status === "EXPIRED") {
resetState();
onQrCodeExpired();
Expand Down Expand Up @@ -231,8 +234,8 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
);

if (isSameDeviceFlowEnabled) {
requestIdRef.current = data.requestId;
transactionIdRef.current = data.transactionId;
sessionStorage.setItem(OVP_SESSION_REQUEST_ID_KEY, data.requestId);
sessionStorage.setItem(OVP_SESSION_TRANSACTION_ID_KEY, data.transactionId);
}

if (!isSameDeviceFlowEnabled || !isMobileDevice()) {
Expand All @@ -250,9 +253,7 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
presentationDefinition,
getPresentationDefinitionParams,
onError,
clientId,
isSameDeviceFlowEnabled,
fetchVPStatus,
clientId
]);

const handleTriggerClick = () => {
Expand Down Expand Up @@ -286,18 +287,10 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
}
};

const getUrlParams = useCallback(() => {
const searchParams = new URLSearchParams(window.location.search);
const hashParams = new URLSearchParams(window.location.hash.slice(1));
return {
responseCode: searchParams.get("response_code") || hashParams.get("response_code") || null,
};
}, []);

useEffect(() => {
const handleVisibilityChange = () => {
const requestId = requestIdRef.current;
const transactionId = transactionIdRef.current;
const requestId = sessionStorage.getItem(OVP_SESSION_REQUEST_ID_KEY);
const transactionId = sessionStorage.getItem(OVP_SESSION_TRANSACTION_ID_KEY);
if (document.visibilityState === "visible" && isActiveRef.current && transactionId && requestId) {
fetchVPStatus(requestId, transactionId);
}
Expand All @@ -311,18 +304,23 @@ const OpenID4VPVerification: React.FC<OpenID4VPVerificationProps> = ({
}, [fetchVPStatus]);

useEffect(() => {
const { responseCode } = getUrlParams();

if (responseCode) {
isActiveRef.current = true;
setLoading(true);
fetchVPResultByResponseCode(responseCode);
if (!isActiveRef.current) {
const savedRequestId = sessionStorage.getItem(OVP_SESSION_REQUEST_ID_KEY);
const savedTransactionId = sessionStorage.getItem(OVP_SESSION_TRANSACTION_ID_KEY);

if (savedRequestId && savedTransactionId) {
isActiveRef.current = true;
setLoading(true);
const hashParams = new URLSearchParams(window.location.hash.slice(1));
const responseCode = hashParams.get("response_code") || null;
fetchVPStatus(savedRequestId, savedTransactionId, responseCode);
}
}

return () => {
isActiveRef.current = false;
};
}, [getUrlParams, fetchVPResultByResponseCode]);
}, []);

useEffect(() => {
if (!presentationDefinitionId && !presentationDefinition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,15 +528,15 @@ const QRCodeVerification: React.FC<QRCodeVerificationProps> = ({
return atob(base64);
}

const fetchVPResult = async (transactionId: string) => {
const fetchVPResult = async (transactionId: string, responseCode?: string | null) => {
if (hasFetchedVPResultRef.current) return;
hasFetchedVPResultRef.current = true;
try {
if (!transactionId) {
throw new Error("Transaction ID is required to fetch VP result");
}

const response = await vpResult(verifyServiceUrl, transactionId, vcVerificationV2Request);
const response = await vpResult(verifyServiceUrl, transactionId, responseCode, vcVerificationV2Request);

const VPResult: VerificationResults =
(response?.credentialResults ?? []).map((cred: CredentialResult) => {
Expand Down
40 changes: 4 additions & 36 deletions inji-verify-sdk/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,42 +146,7 @@ const isAppError = (error: unknown): error is AppError => (
typeof (error as Record<string, unknown>).errorMessage === 'string'
);


export const vpResultByResponseCode = async (url: string, responseCode: string, config?: VPVerificationV2Request) => {
const requestBody = {
skipStatusChecks: config?.skipStatusChecks ?? false,
statusCheckFilters: config?.statusCheckFilters ?? [],
includeClaims: config?.includeClaims ?? false,
};

try {
const baseUrl = new URL(`${url}/vp-results`);
baseUrl.searchParams.append("response_code", responseCode);
const response = await fetch(baseUrl.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw {
errorCode: errorData.errorCode,
errorMessage: errorData.errorMessage || errorData.error || "Unknown error",
transactionId: null,
} as AppError;
}
return await response.json();
} catch (error) {
if (isAppError(error)) {
throw error as AppError;
}
throw error;
}
};

export const vpResult = async (url: string, transactionId: string, config?: VPVerificationV2Request) => {
export const vpResult = async (url: string, transactionId: string, responseCode?: string | null, config?: VPVerificationV2Request) => {
if (!transactionId) {
throw new Error("Transaction ID is required for VP verification");
}
Expand All @@ -193,6 +158,9 @@ export const vpResult = async (url: string, transactionId: string, config?: VPVe

try {
const baseUrl = new URL(`${url}/v2/vp-results/${transactionId}`);
if (responseCode) {
baseUrl.searchParams.append("response_code", responseCode);
}
const response = await fetch(baseUrl.toString(), {
method: "POST",
headers: {
Expand Down
4 changes: 4 additions & 0 deletions inji-verify-sdk/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export const acceptedFileTypes = SupportedFileTypes.map(
// Constants for SD-JWT validation
export const VALID_SD_JWT_TYPES = new Set(['vc+sd-jwt', 'dc+sd-jwt']);

// Constants for session storage
export const OVP_SESSION_REQUEST_ID_KEY = "ovp_requestId";
export const OVP_SESSION_TRANSACTION_ID_KEY = "ovp_transactionId";

// Presentation flow types
export const SAME_DEVICE_FLOW = "same_device";
export const CROSS_DEVICE_FLOW = "cross_device";
Loading