Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b49e2c9
WIP: Read document catalog from selfClient
shazarre Aug 21, 2025
9806373
remove console.log
shazarre Aug 21, 2025
1a371e6
refactor all calls to `hasAnyValidRegisteredDocument`
shazarre Aug 22, 2025
95e8e88
refactor proving machine methods that need to to take their own self …
aaronmgdr Aug 22, 2025
65cebe1
expose loadDocumentById + getAllDocuments via PassportContext now use…
shazarre Aug 22, 2025
0eb0514
fix wrong hook usage
shazarre Aug 25, 2025
bbc24cd
Merge branch 'dev' into shazarre/read_document_catalog_from_the_self_…
shazarre Aug 25, 2025
dd117ff
yarn lint
shazarre Aug 25, 2025
167a172
Merge branch 'shazarre/read_document_catalog_from_the_self_client' of…
shazarre Aug 25, 2025
aef32dc
sdk: yarn lint
shazarre Aug 25, 2025
55b22e1
formatting + test fixes
shazarre Aug 25, 2025
77f8766
rename explicit keychain access + restore ability to work without sel…
shazarre Aug 25, 2025
231e42a
fix testingUtils
shazarre Aug 25, 2025
92ce174
fix tests
shazarre Aug 25, 2025
3e7c0fa
lint
shazarre Aug 25, 2025
d0f1d92
fix web build
shazarre Aug 25, 2025
974f76b
remove TODO
shazarre Aug 25, 2025
f76f9e2
Merge branch 'dev' into shazarre/read_document_catalog_from_the_self_…
shazarre Aug 26, 2025
ba6a315
remove animation + lint
shazarre Aug 26, 2025
f856ddf
Merge branch 'dev' into shazarre/read_document_catalog_from_the_self_…
shazarre Aug 26, 2025
8293f92
lint
shazarre Aug 26, 2025
2c36f32
missing license
shazarre Aug 26, 2025
65c1d74
fix test
shazarre Aug 26, 2025
5b9601c
lint
shazarre Aug 26, 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
153 changes: 82 additions & 71 deletions app/src/providers/passportDataProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import type { PropsWithChildren } from 'react';
import React, { createContext, useCallback, useContext, useMemo } from 'react';
import Keychain from 'react-native-keychain';

import type { DocumentCategory, PassportData } from '@selfxyz/common/types';
import type {
PublicKeyDetailsECDSA,
PublicKeyDetailsRSA,
Expand All @@ -52,14 +51,26 @@ import {
brutforceSignatureAlgorithmDsc,
parseCertificateSimple,
} from '@selfxyz/common/utils';
import type {
DocumentCatalog,
DocumentCategory,
DocumentMetadata,
PassportData,
} from '@selfxyz/common/utils/types';
import {
DocumentsAdapter,
getAllDocuments,
SelfClient,
useSelfClient,
} from '@selfxyz/mobile-sdk-alpha';

import { unsafe_getPrivateKey, useAuth } from '@/providers/authProvider';

// Create safe wrapper functions to prevent undefined errors during early initialization
// These need to be declared early to avoid dependency issues
const safeLoadDocumentCatalog = async (): Promise<DocumentCatalog> => {
try {
return await loadDocumentCatalog();
return await loadDocumentCatalogDirectlyFromKeychain();
} catch (error) {
console.warn(
'Error in safeLoadDocumentCatalog, returning empty catalog:',
Expand All @@ -69,9 +80,9 @@ const safeLoadDocumentCatalog = async (): Promise<DocumentCatalog> => {
}
};

const safeGetAllDocuments = async () => {
const safeGetAllDocuments = async (selfClient: SelfClient) => {
try {
return await getAllDocuments();
return await getAllDocuments(selfClient);
} catch (error) {
console.warn(
'Error in safeGetAllDocuments, returning empty object:',
Expand All @@ -81,20 +92,6 @@ const safeGetAllDocuments = async () => {
}
};

export interface DocumentMetadata {
id: string; // contentHash as ID for deduplication
documentType: string; // passport, mock_passport, id_card, etc.
documentCategory: DocumentCategory; // passport, id_card, aadhaar
data: string; // DG1/MRZ data for passports/IDs, relevant data for aadhaar
mock: boolean; // whether this is a mock document
isRegistered?: boolean; // whether the document is registered onChain
}

export interface DocumentCatalog {
documents: DocumentMetadata[];
selectedDocumentId?: string; // This is now a contentHash
}

type DocumentChangeCallback = (isMock: boolean) => void;

const documentChangeCallbacks: DocumentChangeCallback[] = [];
Expand Down Expand Up @@ -161,7 +158,7 @@ export const PassportContext = createContext<IPassportContext>({
clearPassportData: clearPassportData,
clearSpecificData: clearSpecificPassportData,
loadDocumentCatalog: safeLoadDocumentCatalog,
getAllDocuments: safeGetAllDocuments,
getAllDocuments: () => Promise.resolve({}),
setSelectedDocument: setSelectedDocument,
deleteDocument: deleteDocument,
migrateFromLegacyStorage: migrateFromLegacyStorage,
Expand All @@ -171,12 +168,12 @@ export const PassportContext = createContext<IPassportContext>({
markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered,
updateDocumentRegistrationState: updateDocumentRegistrationState,
checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration,
hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument,
checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates,
});

export const PassportProvider = ({ children }: PassportProviderProps) => {
const { _getSecurely } = useAuth();
const selfClient = useSelfClient();

const getData = useCallback(
() => _getSecurely<PassportData>(loadPassportData, str => JSON.parse(str)),
Expand All @@ -190,7 +187,10 @@ export const PassportProvider = ({ children }: PassportProviderProps) => {
);
}, [_getSecurely]);

const getAllData = useCallback(() => loadAllPassportData(), []);
const getAllData = useCallback(
() => loadAllPassportData(selfClient),
[selfClient],
);

const getAvailableTypes = useCallback(() => getAvailableDocumentTypes(), []);

Expand Down Expand Up @@ -222,7 +222,7 @@ export const PassportProvider = ({ children }: PassportProviderProps) => {
clearPassportData: clearPassportData,
clearSpecificData: clearSpecificPassportData,
loadDocumentCatalog: safeLoadDocumentCatalog,
getAllDocuments: safeGetAllDocuments,
getAllDocuments: () => safeGetAllDocuments(selfClient),
setSelectedDocument: setSelectedDocument,
deleteDocument: deleteDocument,
migrateFromLegacyStorage: migrateFromLegacyStorage,
Expand All @@ -232,7 +232,6 @@ export const PassportProvider = ({ children }: PassportProviderProps) => {
markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered,
updateDocumentRegistrationState: updateDocumentRegistrationState,
checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration,
hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument,
checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates,
}),
[
Expand Down Expand Up @@ -261,7 +260,7 @@ export async function checkAndUpdateRegistrationStates(): Promise<void> {

export async function checkIfAnyDocumentsNeedMigration(): Promise<boolean> {
try {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
return catalog.documents.some(doc => doc.isRegistered === undefined);
} catch (error) {
console.warn('Error checking if documents need migration:', error);
Expand All @@ -271,7 +270,7 @@ export async function checkIfAnyDocumentsNeedMigration(): Promise<boolean> {

export async function clearDocumentCatalogForMigrationTesting() {
console.log('Clearing document catalog for migration testing...');
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();

// Delete all new-style documents
for (const doc of catalog.documents) {
Expand Down Expand Up @@ -299,7 +298,7 @@ export async function clearDocumentCatalogForMigrationTesting() {
}

export async function clearPassportData() {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();

// Delete all documents
for (const doc of catalog.documents) {
Expand All @@ -315,7 +314,7 @@ export async function clearPassportData() {
}

export async function clearSpecificPassportData(documentType: string) {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
const docsToDelete = catalog.documents.filter(
d => d.documentType === documentType,
);
Expand All @@ -326,7 +325,7 @@ export async function clearSpecificPassportData(documentType: string) {
}

export async function deleteDocument(documentId: string): Promise<void> {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();

// Remove from catalog
catalog.documents = catalog.documents.filter(d => d.id !== documentId);
Expand All @@ -350,32 +349,14 @@ export async function deleteDocument(documentId: string): Promise<void> {
}
}

export async function getAllDocuments(): Promise<{
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
}> {
const catalog = await loadDocumentCatalog();
const allDocs: {
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
} = {};

for (const metadata of catalog.documents) {
const data = await loadDocumentById(metadata.id);
if (data) {
allDocs[metadata.id] = { data, metadata };
}
}

return allDocs;
}

export async function getAvailableDocumentTypes(): Promise<string[]> {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
return [...new Set(catalog.documents.map(d => d.documentType))];
}

// Helper function to get current document type from catalog
export async function getCurrentDocumentType(): Promise<string | null> {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
if (!catalog.selectedDocumentId) return null;

const metadata = catalog.documents.find(
Expand All @@ -402,16 +383,6 @@ function getServiceNameForDocumentType(documentType: string): string {
}
}

export async function hasAnyValidRegisteredDocument(): Promise<boolean> {
try {
const catalog = await loadDocumentCatalog();
return catalog.documents.some(doc => doc.isRegistered === true);
} catch (error) {
console.error('Error loading document catalog:', error);
return false;
}
}

/**
* Global initialization function to wait for native modules to be ready
* Call this once at app startup before any native module operations
Expand Down Expand Up @@ -458,10 +429,11 @@ export async function initializeNativeModules(
return false;
}

export async function loadAllPassportData(): Promise<{
// TODO: is this used?
async function loadAllPassportData(selfClient: SelfClient): Promise<{
[service: string]: PassportData;
}> {
const allDocs = await getAllDocuments();
const allDocs = await getAllDocuments(selfClient);
const result: { [service: string]: PassportData } = {};

// Convert to legacy format for backward compatibility
Expand All @@ -473,7 +445,7 @@ export async function loadAllPassportData(): Promise<{
return result;
}

export async function loadDocumentById(
export async function loadDocumentByIdDirectlyFromKeychain(
documentId: string,
): Promise<PassportData | null> {
try {
Expand All @@ -497,7 +469,12 @@ export async function loadDocumentById(
return null;
}

export async function loadDocumentCatalog(): Promise<DocumentCatalog> {
export const selfClientDocumentsAdapter: DocumentsAdapter = {
loadDocumentCatalog: loadDocumentCatalogDirectlyFromKeychain,
loadDocumentById: loadDocumentByIdDirectlyFromKeychain,
};

export async function loadDocumentCatalogDirectlyFromKeychain(): Promise<DocumentCatalog> {
try {
// Extra safety check for module initialization
if (typeof Keychain === 'undefined' || !Keychain) {
Expand All @@ -522,6 +499,9 @@ export async function loadDocumentCatalog(): Promise<DocumentCatalog> {
if (parsed === null) {
throw new TypeError('Cannot parse null password');
}

console.log('Successfully loaded document catalog from keychain');

return parsed;
}
} catch (error) {
Expand Down Expand Up @@ -590,7 +570,7 @@ export async function loadSelectedDocument(): Promise<{
data: PassportData;
metadata: DocumentMetadata;
} | null> {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
console.log('Catalog loaded');

if (!catalog.selectedDocumentId) {
Expand All @@ -616,7 +596,9 @@ export async function loadSelectedDocument(): Promise<{
return null;
}

const data = await loadDocumentById(catalog.selectedDocumentId);
const data = await loadDocumentByIdDirectlyFromKeychain(
catalog.selectedDocumentId,
);
if (!data) {
console.log('Document data not found for id:', catalog.selectedDocumentId);
return null;
Expand Down Expand Up @@ -658,6 +640,7 @@ interface IPassportContext {
signature: string;
data: PassportData;
} | null>;
// TODO: is this even used?
getAllData: () => Promise<{ [service: string]: PassportData }>;
getAvailableTypes: () => Promise<string[]>;
setData: (data: PassportData) => Promise<void>;
Expand All @@ -671,12 +654,15 @@ interface IPassportContext {
} | null>;
clearPassportData: () => Promise<void>;
clearSpecificData: (documentType: string) => Promise<void>;

loadDocumentCatalog: () => Promise<DocumentCatalog>;
getAllDocuments: () => Promise<{
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
}>;

setSelectedDocument: (documentId: string) => Promise<void>;
deleteDocument: (documentId: string) => Promise<void>;

migrateFromLegacyStorage: () => Promise<void>;
getCurrentDocumentType: () => Promise<string | null>;
clearDocumentCatalogForMigrationTesting: () => Promise<void>;
Expand All @@ -686,12 +672,11 @@ interface IPassportContext {
isRegistered: boolean,
) => Promise<void>;
checkIfAnyDocumentsNeedMigration: () => Promise<boolean>;
hasAnyValidRegisteredDocument: () => Promise<boolean>;
checkAndUpdateRegistrationStates: () => Promise<void>;
}

export async function markCurrentDocumentAsRegistered(): Promise<void> {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
if (catalog.selectedDocumentId) {
await updateDocumentRegistrationState(catalog.selectedDocumentId, true);
} else {
Expand All @@ -701,7 +686,7 @@ export async function markCurrentDocumentAsRegistered(): Promise<void> {

export async function migrateFromLegacyStorage(): Promise<void> {
console.log('Migrating from legacy storage to new architecture...');
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();

// If catalog already has documents, skip migration
if (catalog.documents.length > 0) {
Expand Down Expand Up @@ -787,15 +772,15 @@ export async function saveDocumentCatalog(
}

export async function setDefaultDocumentTypeIfNeeded() {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();

if (!catalog.selectedDocumentId && catalog.documents.length > 0) {
await setSelectedDocument(catalog.documents[0].id);
}
}

export async function setSelectedDocument(documentId: string): Promise<void> {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
const metadata = catalog.documents.find(d => d.id === documentId);

if (metadata) {
Expand All @@ -810,7 +795,7 @@ export async function storeDocumentWithDeduplication(
passportData: PassportData,
): Promise<string> {
const contentHash = calculateContentHash(passportData);
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();

// Check for existing document with same content
const existing = catalog.documents.find(d => d.id === contentHash);
Expand Down Expand Up @@ -866,7 +851,7 @@ export async function updateDocumentRegistrationState(
documentId: string,
isRegistered: boolean,
): Promise<void> {
const catalog = await loadDocumentCatalog();
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
const documentIndex = catalog.documents.findIndex(d => d.id === documentId);

if (documentIndex !== -1) {
Expand All @@ -883,3 +868,29 @@ export async function updateDocumentRegistrationState(
export const usePassport = () => {
return useContext(PassportContext);
};

/**
* Get all documents directly from the keychain.
*
* It's here to avoid dependency on self client where it's not strictly necessary,
* for example when migrating legacy data.
*
* @returns A dictionary of document IDs to their data and metadata.
*/
export const getAllDocumentsDirectlyFromKeychain = async (): Promise<{
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
}> => {
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
const allDocs: {
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
} = {};

for (const metadata of catalog.documents) {
const data = await loadDocumentByIdDirectlyFromKeychain(metadata.id);
if (data) {
allDocs[metadata.id] = { data, metadata };
}
}

return allDocs;
};
Loading
Loading