Skip to content
This repository was archived by the owner on Dec 18, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8bfa20e
Use YAML configuration file
patatoid Oct 3, 2025
498aecc
bootstrap global store for application state management
patatoid Oct 16, 2025
701f41d
Bootstrap sessions global state
patatoid Oct 24, 2025
8e24b52
Use is online from global state in HTTPProxy
patatoid Oct 24, 2025
afc7e64
Prefer tabs overspaces for indentation
patatoid Oct 24, 2025
193d7e8
Error dialog context implementation
patatoid Oct 22, 2025
f9a796f
Bootstrap wwwallet client core
patatoid Oct 21, 2025
e891e7e
Implement useClientCore hook
patatoid Oct 22, 2025
537caf0
Update uuid dependency
patatoid Oct 22, 2025
bcc6989
Add client core context in app provider tree
patatoid Oct 22, 2025
f2b0b06
Custom logger bootstrap (log levels)
patatoid Sep 18, 2025
2371059
refactor: bind logger methods to retain caller trace
smncd Sep 18, 2025
186a26c
refactor: Style logger prefix
smncd Sep 18, 2025
a96951a
refactor: add jsonToLog helper
smncd Sep 18, 2025
7bbb757
refactor: add logger groups
smncd Sep 18, 2025
fc52f1a
chore: formatting
smncd Sep 18, 2025
59c2bd5
refactor: move helper to end of file
smncd Sep 18, 2025
c19116a
refactor: declare logger on window interface
smncd Sep 18, 2025
03760ed
refactor: adjust initial indent size
smncd Sep 18, 2025
d5ee781
Fix lint errors
patatoid Oct 22, 2025
e231d91
Log errors messages in nested objects
patatoid Oct 22, 2025
0b6a65c
WIP Use components for protocol step handlers implementation
patatoid Oct 6, 2025
6c7aeb2
Use components for protocol step handlers implementation
patatoid Oct 8, 2025
d61d3b7
Cleanup uri handler context
patatoid Oct 8, 2025
8dd8f91
Update client core configuration
patatoid Oct 17, 2025
2d90a8c
Implement core vp token signer adapter
patatoid Oct 17, 2025
ef491dd
Oid4vp client core integration
patatoid Oct 17, 2025
c6c6345
Protocol errors en locales
patatoid Oct 21, 2025
ce8697c
Fix yarn lockfile
patatoid Oct 21, 2025
47398cb
Remove refactored uri handler
patatoid Oct 22, 2025
57cbf2e
Fix uri handler re-renders
patatoid Oct 24, 2025
4877c8e
Disable vp token signature
patatoid Nov 3, 2025
c296389
Dispatch online / offline events
patatoid Nov 3, 2025
1f411fe
Use global storage for keystore data
patatoid Nov 3, 2025
1b64fb1
WIP Remove consent from credential request handler
patatoid Nov 3, 2025
3b9ed93
Calculated wallet state from global state in credential context provider
patatoid Nov 3, 2025
dce2107
Cleanup session context
patatoid Nov 3, 2025
d1d2f4c
Do not parse uri if not online
patatoid Nov 3, 2025
2fc8168
Remove keystore dependency in client state store
patatoid Nov 3, 2025
8054dac
Use isOnline from global store in http proxy
patatoid Nov 3, 2025
17f004b
Credentials in global storage + signJwtPresentation store action
patatoid Nov 5, 2025
b6ea1a0
Cleanup + Migration TODO list
patatoid Nov 7, 2025
31f8522
Update TODOs
patatoid Nov 10, 2025
514a8ba
Fix linter warnings
patatoid Nov 14, 2025
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
40 changes: 40 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
urls: &urls
backend_url: &backend_url "http://wallet-backend-server:8002"
wallet_url: &wallet_url "http://localhost:3000"
wallet_callback_url: &wallet_callback_url "http://localhost:3000/cb"
ws_url: &ws_url "ws://localhost:8002"

wallet:
environment: development
log_level: debug
ws_url: *ws_url
wallet_backend_url: *backend_url
login_with_password: false
did_key_version: jwk_jcs-pub
app_version: $npm_package_version
generate_sourcemap: false
display_console: true
webauthn_rpid: localhost
openid4vci_redirect_uri: *wallet_callback_url
openid4vci_proof_type_precedence: attestation,jwt
openid4vp_san_dns_check: false
openid4vp_san_dns_check_ssl_certs: false
validate_credentials_with_trust_anchors: true
multi_language_display: true
static_public_url: *wallet_url
static_name: wwWallet
core_configuration:
wallet_url: *wallet_callback_url
wallet_callback_url: *wallet_callback_url
dpop_ttl_seconds: 60
static_clients:
- issuer: "http://issuer.localhost:8003"
client_id: "CLIENT123"
client_secret: "superSecretString"
- issuer: "http://wallet-enterprise-issuer:8003"
client_id: "CLIENT123"
client_secret: "superSecretString"
- issuer: "http://wwwallet-issuer-poc:5000"
client_id: "CLIENT123"
client_secret: "321TNEILC"
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"@cef-ebsi/key-did-resolver": "^1.1.0",
"@hpke/core": "^1.7.4",
"@hpke/dhkem-x25519": "^1.6.4",
"@sd-jwt/core": "^0.10.0",
"@reduxjs/toolkit": "^2.9.0",
"@sd-jwt/core": "^0.15.0",
"@wwwallet-private/client-core": "link:../../wwwallet-core/packages/client-core",
"ajv": "^8.17.1",
"asn1js": "^3.0.5",
"autoprefixer": "^10.4.14",
Expand All @@ -33,6 +35,7 @@
"react-icons": "^4.10.1",
"react-modal": "^3.16.1",
"react-qr-code": "^2.0.15",
"react-redux": "^9.2.0",
"react-router-dom": "^6.14.1",
"react-snowfall": "^1.2.1",
"react-transition-group": "^4.4.5",
Expand All @@ -43,7 +46,7 @@
"tailwindcss": "^3.3.2",
"ts-results": "^3.3.0",
"typescript": "^5.1.6",
"uuid": "^9.0.0",
"uuid": "^13.0.0",
"vite-plugin-pwa": "^0.21.1",
"wallet-common": "git+https://github.com/wwWallet/wallet-common.git#1aece0a0b823a295557711f1ce4b92b1b14f0544",
"web-vitals": "^2.1.4",
Expand All @@ -52,6 +55,7 @@
"workbox-precaching": "^7.3.0",
"workbox-routing": "^7.3.0",
"workbox-strategies": "^7.3.0",
"yaml": "^2.8.1",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
54 changes: 32 additions & 22 deletions src/AppProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// AppProvider.tsx
import { Provider as StateProvider } from 'react-redux';
import React, { ReactNode } from 'react';

// Import i18next and set up translations
Expand All @@ -8,43 +9,52 @@ import i18n from './i18n';
// Contexts
import { StatusContextProvider } from './context/StatusContextProvider';
import { SessionContextProvider } from './context/SessionContextProvider';
import { ClientCoreContextProvider } from './context/ClientCoreContextProvider';
import { CredentialsContextProvider } from './context/CredentialsContextProvider';
import { ErrorDialogContextProvider } from './context/ErrorDialogContextProvider';
import { OpenID4VPContextProvider } from './context/OpenID4VPContextProvider';
import { OpenID4VCIContextProvider } from './context/OpenID4VCIContextProvider';
import { AppSettingsProvider } from './context/AppSettingsProvider';
import { NotificationProvider } from './context/NotificationProvider';
import { NativeWrapperProvider } from './context/NativeWrapper';

// Hocs
import UriHandler from './hocs/UriHandler';
import { store } from './store';
import { UriHandler } from './hocs/UriHandler/UriHandler';

type RootProviderProps = {
children: ReactNode;
};

const AppProvider: React.FC<RootProviderProps> = ({ children }) => {
return (
<StatusContextProvider>
<SessionContextProvider>
<CredentialsContextProvider>
<I18nextProvider i18n={i18n}>
<OpenID4VPContextProvider>
<OpenID4VCIContextProvider>
<UriHandler>
<AppSettingsProvider>
<NotificationProvider>
<NativeWrapperProvider>
{children}
</NativeWrapperProvider>
</NotificationProvider>
</AppSettingsProvider>
</UriHandler>
</OpenID4VCIContextProvider>
</OpenID4VPContextProvider>
</I18nextProvider>
</CredentialsContextProvider>
</SessionContextProvider>
</StatusContextProvider>
<StateProvider store={store}>
<ErrorDialogContextProvider>
<StatusContextProvider>
<SessionContextProvider>
<CredentialsContextProvider>
<I18nextProvider i18n={i18n}>
<ClientCoreContextProvider>
<OpenID4VPContextProvider>
<OpenID4VCIContextProvider>
<UriHandler>
<AppSettingsProvider>
<NotificationProvider>
<NativeWrapperProvider>
{children}
</NativeWrapperProvider>
</NotificationProvider>
</AppSettingsProvider>
</UriHandler>
</OpenID4VCIContextProvider>
</OpenID4VPContextProvider>
</ClientCoreContextProvider>
</I18nextProvider>
</CredentialsContextProvider>
</SessionContextProvider>
</StatusContextProvider>
</ErrorDialogContextProvider>
</StateProvider>
);
};

Expand Down
41 changes: 21 additions & 20 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import axios, { AxiosResponse } from 'axios';
import { Err, Ok, Result } from 'ts-results';

import * as config from '../config';
import { logger } from '@/logger';
import { fromBase64Url, jsonParseTaggedBinary, jsonStringifyTaggedBinary, toBase64Url } from '../util';
import { EncryptedContainer, makeAssertionPrfExtensionInputs, parsePrivateData, serializePrivateData } from '../services/keystore';
import { CachedUser, LocalStorageKeystore } from '../services/LocalStorageKeystore';
Expand Down Expand Up @@ -161,7 +162,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
options?: { appToken?: string, headers?: { [header: string]: string } },
forceIndexDB: boolean = false
): Promise<AxiosResponse> => {
console.log(`Get: ${path} ${isOnline ? 'online' : 'offline'} mode ${isOnline}`);
logger.debug(`Get: ${path} ${isOnline ? 'online' : 'offline'} mode ${isOnline}`);

// Offline case
if (!isOnline && !EXCLUDED_INDEXEDDB_PATHS.has(path)) {
Expand Down Expand Up @@ -222,7 +223,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
// getExternalEntity('/issuer/all') on credentialContext
// getCredentialIssuerMetadata() on credentialContext
} catch (error) {
console.error('Failed to perform get requests', error);
logger.error('Failed to perform get requests', error);
}
}, [get, getExternalEntity]);

Expand Down Expand Up @@ -345,7 +346,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {

await addItem('users', response.data.uuid, response.data);
if (isOnline) {
await fetchInitialData(response.data.appToken, response.data.uuid).catch((error) => console.error('Error in performGetRequests', error));
await fetchInitialData(response.data.appToken, response.data.uuid).catch((error) => logger.error('Error in performGetRequests', error));
}
}, [setAppToken, setSessionState, fetchInitialData, isOnline]);

Expand All @@ -360,13 +361,13 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
if (updateResp.status === 204) {
return;
} else {
console.error("Failed to update private data", updateResp.status, updateResp);
logger.error("Failed to update private data", updateResp.status, updateResp);
return Promise.reject(updateResp);
}
} catch (e) {
console.error("Failed to update private data", e, e?.response?.status);
logger.error("Failed to update private data", e, e?.response?.status);
if ((e?.response?.status === 412 && (e?.headers ?? {})['x-private-data-etag']) || (e.cause === 'x-private-data-etag')) {
console.error("Private data version conflict", { cause: 'x-private-data-etag' });
logger.error("Private data version conflict", { cause: 'x-private-data-etag' });
const cachedUser = cachedUsers.filter((u) => u.userHandleB64u === userHandle)[0];
await syncPrivateData(cachedUser);
return;
Expand All @@ -392,7 +393,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
await updatePrivateData(newPrivateData, { appToken: response.data.appToken });
await keystoreCommit();
} catch (e) {
console.error("Failed to upgrade password key", e, e.status);
logger.error("Failed to upgrade password key", e, e.status);
if (e?.cause === 'x-private-data-etag') {
return Err('x-private-data-etag');
}
Expand All @@ -402,12 +403,12 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
await setSession(response, null, 'login');
return Ok.EMPTY;
} catch (e) {
console.error("Failed to unlock local keystore", e);
logger.error("Failed to unlock local keystore", e);
return Err(e);
}

} catch (error) {
console.error('Failed to log in', error);
logger.error('Failed to log in', error);
return Err(error);
}
}, [post, setSession, updatePrivateDataEtag, updatePrivateData]);
Expand All @@ -433,12 +434,12 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
return Ok.EMPTY;

} catch (e) {
console.error("Signup failed", e);
logger.error("Signup failed", e);
return Err(e);
}

} catch (e) {
console.error("Failed to initialize local keystore", e);
logger.error("Failed to initialize local keystore", e);
return Err(e);
}
}, [post, setSession, updatePrivateDataEtag]);
Expand All @@ -447,11 +448,11 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
try {
const result = await getExternalEntity('/verifier/all', undefined, true);
const verifiers = result.data;
console.log("verifiers = ", verifiers)
logger.debug("verifiers = ", verifiers)
return verifiers;
}
catch (error) {
console.error("Failed to fetch all verifiers", error);
logger.error("Failed to fetch all verifiers", error);
throw error;
}
}, [getExternalEntity]);
Expand All @@ -462,7 +463,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
return result.data; // Return the Axios response.
}
catch (error) {
console.error("Failed to fetch all presentations", error);
logger.error("Failed to fetch all presentations", error);
throw error;
}
}, [get]);
Expand All @@ -477,7 +478,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
return { redirect_to };
}
catch (error) {
console.error("Failed to fetch all verifiers", error);
logger.error("Failed to fetch all verifiers", error);
throw error;
}
}, [post]);
Expand All @@ -501,7 +502,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
}> => {
if (isOnline) {
const beginResp = await post('/user/login-webauthn-begin', {});
console.log("begin", beginResp);
logger.debug("begin", beginResp);
return beginResp.data;
}
else {
Expand Down Expand Up @@ -589,7 +590,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
await updatePrivateData(newPrivateData, { appToken: finishResp.data.appToken });
await keystoreCommit();
} catch (e) {
console.error("Failed to upgrade PRF key", e, e.status);
logger.error("Failed to upgrade PRF key", e, e.status);
if (e?.cause === 'x-private-data-etag') {
return Err('x-private-data-etag');
}
Expand All @@ -599,7 +600,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
await setSession(finishResp, credential, 'login');
return Ok.EMPTY;
} catch (e) {
console.error("Failed to open keystore", e);
logger.error("Failed to open keystore", e);
return Err('loginKeystoreFailed');
}

Expand All @@ -625,7 +626,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
): Promise<Result<void, SignupWebauthnError>> => {
try {
const beginData = retryFrom?.beginData || (await post('/user/register-webauthn-begin', {})).data;
console.log("begin", beginData);
logger.debug("begin", beginData);

try {
const prfSalt = crypto.getRandomValues(new Uint8Array(32))
Expand All @@ -650,7 +651,7 @@ export function useApi(isOnlineProp: boolean = true): BackendApi {
},
}) as PublicKeyCredential;
const response = credential.response as AuthenticatorAttestationResponse;
console.log("created", credential);
logger.debug("created", credential);

try {
const privateData = await keystore.initPrf(
Expand Down
5 changes: 4 additions & 1 deletion src/components/Popups/MessagePopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Button from '../Buttons/Button';
import PopupLayout from './PopupLayout';

const MessagePopup = ({ type, message, onClose }) => {
const { title, description } = message || {};
const { title, emphasis, description } = message || {};
const { t } = useTranslation();

const IconComponent = type === 'error' ? FaExclamationCircle : FaCheckCircle;
Expand All @@ -31,6 +31,9 @@ const MessagePopup = ({ type, message, onClose }) => {
</button>
</div>
<hr className={`mb-2 border-t border-${color}/80`} />
{emphasis && <p className="text-gray-700 bg-gray-200 p-4">
{emphasis}
</p>}
<p className="mb-2 mt-4 dark:text-white">
{description}
</p>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Popups/SelectCredentialsPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,10 @@ function SelectCredentialsPopup({ popupState, setPopupState, showPopup, hidePopu
return;
}
try {
const filteredVcEntities = vcEntityList.filter(vcEntity =>
popupState.options.conformantCredentialsMap[keys[currentIndex]].credentials.includes(vcEntity.batchId)
);
setVcEntities(filteredVcEntities);
setVcEntities(
Object.values(popupState.options.conformantCredentialsMap)
.flatMap(({ credentials }) => credentials)
);
} catch (error) {
console.error('Failed to fetch data', error);
}
Expand Down
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
export type DidKeyVersion = "p256-pub" | "jwk_jcs-pub";

export const APP_VERSION = import.meta.env.VITE_APP_VERSION;

export const LOG_LEVEL = import.meta.env.VITE_LOG_LEVEL;
export const BACKEND_URL = import.meta.env.VITE_WALLET_BACKEND_URL;
export const DID_KEY_VERSION: DidKeyVersion = import.meta.env.VITE_DID_KEY_VERSION as DidKeyVersion;
export const DISPLAY_CONSOLE = import.meta.env.VITE_DISPLAY_CONSOLE;

export const CORE_CONFIGURATION = import.meta.env.VITE_CORE_CONFIGURATION;
export const MULTI_LANGUAGE_DISPLAY: boolean = import.meta.env.VITE_MULTI_LANGUAGE_DISPLAY ? JSON.parse(import.meta.env.VITE_MULTI_LANGUAGE_DISPLAY) : false;
export const I18N_WALLET_NAME_OVERRIDE: string | undefined = import.meta.env.VITE_I18N_WALLET_NAME_OVERRIDE;
export const INACTIVE_LOGOUT_MILLIS = (import.meta.env.VITE_INACTIVE_LOGOUT_SECONDS ? parseInt(import.meta.env.VITE_INACTIVE_LOGOUT_SECONDS, 10) : 60 * 15) * 1000
Expand Down
6 changes: 6 additions & 0 deletions src/context/ClientCoreContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createContext } from 'react';
import type { Core } from "@wwwallet-private/client-core";

const ClientCoreContext = createContext<Core|null>(null);

export default ClientCoreContext;
Loading
Loading