Skip to content

Commit 6c3cd97

Browse files
authored
Read document catalog from selfClient (#936)
1 parent 4367780 commit 6c3cd97

30 files changed

+431
-183
lines changed

app/src/providers/passportDataProvider.tsx

Lines changed: 82 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import type { PropsWithChildren } from 'react';
4545
import React, { createContext, useCallback, useContext, useMemo } from 'react';
4646
import Keychain from 'react-native-keychain';
4747

48-
import type { DocumentCategory, PassportData } from '@selfxyz/common/types';
4948
import type {
5049
PublicKeyDetailsECDSA,
5150
PublicKeyDetailsRSA,
@@ -54,14 +53,26 @@ import {
5453
brutforceSignatureAlgorithmDsc,
5554
parseCertificateSimple,
5655
} from '@selfxyz/common/utils';
56+
import type {
57+
DocumentCatalog,
58+
DocumentCategory,
59+
DocumentMetadata,
60+
PassportData,
61+
} from '@selfxyz/common/utils/types';
62+
import {
63+
DocumentsAdapter,
64+
getAllDocuments,
65+
SelfClient,
66+
useSelfClient,
67+
} from '@selfxyz/mobile-sdk-alpha';
5768

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

6071
// Create safe wrapper functions to prevent undefined errors during early initialization
6172
// These need to be declared early to avoid dependency issues
6273
const safeLoadDocumentCatalog = async (): Promise<DocumentCatalog> => {
6374
try {
64-
return await loadDocumentCatalog();
75+
return await loadDocumentCatalogDirectlyFromKeychain();
6576
} catch (error) {
6677
console.warn(
6778
'Error in safeLoadDocumentCatalog, returning empty catalog:',
@@ -71,9 +82,9 @@ const safeLoadDocumentCatalog = async (): Promise<DocumentCatalog> => {
7182
}
7283
};
7384

74-
const safeGetAllDocuments = async () => {
85+
const safeGetAllDocuments = async (selfClient: SelfClient) => {
7586
try {
76-
return await getAllDocuments();
87+
return await getAllDocuments(selfClient);
7788
} catch (error) {
7889
console.warn(
7990
'Error in safeGetAllDocuments, returning empty object:',
@@ -83,20 +94,6 @@ const safeGetAllDocuments = async () => {
8394
}
8495
};
8596

86-
export interface DocumentMetadata {
87-
id: string; // contentHash as ID for deduplication
88-
documentType: string; // passport, mock_passport, id_card, etc.
89-
documentCategory: DocumentCategory; // passport, id_card, aadhaar
90-
data: string; // DG1/MRZ data for passports/IDs, relevant data for aadhaar
91-
mock: boolean; // whether this is a mock document
92-
isRegistered?: boolean; // whether the document is registered onChain
93-
}
94-
95-
export interface DocumentCatalog {
96-
documents: DocumentMetadata[];
97-
selectedDocumentId?: string; // This is now a contentHash
98-
}
99-
10097
type DocumentChangeCallback = (isMock: boolean) => void;
10198

10299
const documentChangeCallbacks: DocumentChangeCallback[] = [];
@@ -163,7 +160,7 @@ export const PassportContext = createContext<IPassportContext>({
163160
clearPassportData: clearPassportData,
164161
clearSpecificData: clearSpecificPassportData,
165162
loadDocumentCatalog: safeLoadDocumentCatalog,
166-
getAllDocuments: safeGetAllDocuments,
163+
getAllDocuments: () => Promise.resolve({}),
167164
setSelectedDocument: setSelectedDocument,
168165
deleteDocument: deleteDocument,
169166
migrateFromLegacyStorage: migrateFromLegacyStorage,
@@ -173,12 +170,12 @@ export const PassportContext = createContext<IPassportContext>({
173170
markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered,
174171
updateDocumentRegistrationState: updateDocumentRegistrationState,
175172
checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration,
176-
hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument,
177173
checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates,
178174
});
179175

180176
export const PassportProvider = ({ children }: PassportProviderProps) => {
181177
const { _getSecurely } = useAuth();
178+
const selfClient = useSelfClient();
182179

183180
const getData = useCallback(
184181
() => _getSecurely<PassportData>(loadPassportData, str => JSON.parse(str)),
@@ -192,7 +189,10 @@ export const PassportProvider = ({ children }: PassportProviderProps) => {
192189
);
193190
}, [_getSecurely]);
194191

195-
const getAllData = useCallback(() => loadAllPassportData(), []);
192+
const getAllData = useCallback(
193+
() => loadAllPassportData(selfClient),
194+
[selfClient],
195+
);
196196

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

@@ -224,7 +224,7 @@ export const PassportProvider = ({ children }: PassportProviderProps) => {
224224
clearPassportData: clearPassportData,
225225
clearSpecificData: clearSpecificPassportData,
226226
loadDocumentCatalog: safeLoadDocumentCatalog,
227-
getAllDocuments: safeGetAllDocuments,
227+
getAllDocuments: () => safeGetAllDocuments(selfClient),
228228
setSelectedDocument: setSelectedDocument,
229229
deleteDocument: deleteDocument,
230230
migrateFromLegacyStorage: migrateFromLegacyStorage,
@@ -234,7 +234,6 @@ export const PassportProvider = ({ children }: PassportProviderProps) => {
234234
markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered,
235235
updateDocumentRegistrationState: updateDocumentRegistrationState,
236236
checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration,
237-
hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument,
238237
checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates,
239238
}),
240239
[
@@ -263,7 +262,7 @@ export async function checkAndUpdateRegistrationStates(): Promise<void> {
263262

264263
export async function checkIfAnyDocumentsNeedMigration(): Promise<boolean> {
265264
try {
266-
const catalog = await loadDocumentCatalog();
265+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
267266
return catalog.documents.some(doc => doc.isRegistered === undefined);
268267
} catch (error) {
269268
console.warn('Error checking if documents need migration:', error);
@@ -273,7 +272,7 @@ export async function checkIfAnyDocumentsNeedMigration(): Promise<boolean> {
273272

274273
export async function clearDocumentCatalogForMigrationTesting() {
275274
console.log('Clearing document catalog for migration testing...');
276-
const catalog = await loadDocumentCatalog();
275+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
277276

278277
// Delete all new-style documents
279278
for (const doc of catalog.documents) {
@@ -301,7 +300,7 @@ export async function clearDocumentCatalogForMigrationTesting() {
301300
}
302301

303302
export async function clearPassportData() {
304-
const catalog = await loadDocumentCatalog();
303+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
305304

306305
// Delete all documents
307306
for (const doc of catalog.documents) {
@@ -317,7 +316,7 @@ export async function clearPassportData() {
317316
}
318317

319318
export async function clearSpecificPassportData(documentType: string) {
320-
const catalog = await loadDocumentCatalog();
319+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
321320
const docsToDelete = catalog.documents.filter(
322321
d => d.documentType === documentType,
323322
);
@@ -328,7 +327,7 @@ export async function clearSpecificPassportData(documentType: string) {
328327
}
329328

330329
export async function deleteDocument(documentId: string): Promise<void> {
331-
const catalog = await loadDocumentCatalog();
330+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
332331

333332
// Remove from catalog
334333
catalog.documents = catalog.documents.filter(d => d.id !== documentId);
@@ -352,32 +351,14 @@ export async function deleteDocument(documentId: string): Promise<void> {
352351
}
353352
}
354353

355-
export async function getAllDocuments(): Promise<{
356-
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
357-
}> {
358-
const catalog = await loadDocumentCatalog();
359-
const allDocs: {
360-
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
361-
} = {};
362-
363-
for (const metadata of catalog.documents) {
364-
const data = await loadDocumentById(metadata.id);
365-
if (data) {
366-
allDocs[metadata.id] = { data, metadata };
367-
}
368-
}
369-
370-
return allDocs;
371-
}
372-
373354
export async function getAvailableDocumentTypes(): Promise<string[]> {
374-
const catalog = await loadDocumentCatalog();
355+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
375356
return [...new Set(catalog.documents.map(d => d.documentType))];
376357
}
377358

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

383364
const metadata = catalog.documents.find(
@@ -404,16 +385,6 @@ function getServiceNameForDocumentType(documentType: string): string {
404385
}
405386
}
406387

407-
export async function hasAnyValidRegisteredDocument(): Promise<boolean> {
408-
try {
409-
const catalog = await loadDocumentCatalog();
410-
return catalog.documents.some(doc => doc.isRegistered === true);
411-
} catch (error) {
412-
console.error('Error loading document catalog:', error);
413-
return false;
414-
}
415-
}
416-
417388
/**
418389
* Global initialization function to wait for native modules to be ready
419390
* Call this once at app startup before any native module operations
@@ -460,10 +431,11 @@ export async function initializeNativeModules(
460431
return false;
461432
}
462433

463-
export async function loadAllPassportData(): Promise<{
434+
// TODO: is this used?
435+
async function loadAllPassportData(selfClient: SelfClient): Promise<{
464436
[service: string]: PassportData;
465437
}> {
466-
const allDocs = await getAllDocuments();
438+
const allDocs = await getAllDocuments(selfClient);
467439
const result: { [service: string]: PassportData } = {};
468440

469441
// Convert to legacy format for backward compatibility
@@ -475,7 +447,7 @@ export async function loadAllPassportData(): Promise<{
475447
return result;
476448
}
477449

478-
export async function loadDocumentById(
450+
export async function loadDocumentByIdDirectlyFromKeychain(
479451
documentId: string,
480452
): Promise<PassportData | null> {
481453
try {
@@ -499,7 +471,12 @@ export async function loadDocumentById(
499471
return null;
500472
}
501473

502-
export async function loadDocumentCatalog(): Promise<DocumentCatalog> {
474+
export const selfClientDocumentsAdapter: DocumentsAdapter = {
475+
loadDocumentCatalog: loadDocumentCatalogDirectlyFromKeychain,
476+
loadDocumentById: loadDocumentByIdDirectlyFromKeychain,
477+
};
478+
479+
export async function loadDocumentCatalogDirectlyFromKeychain(): Promise<DocumentCatalog> {
503480
try {
504481
// Extra safety check for module initialization
505482
if (typeof Keychain === 'undefined' || !Keychain) {
@@ -524,6 +501,9 @@ export async function loadDocumentCatalog(): Promise<DocumentCatalog> {
524501
if (parsed === null) {
525502
throw new TypeError('Cannot parse null password');
526503
}
504+
505+
console.log('Successfully loaded document catalog from keychain');
506+
527507
return parsed;
528508
}
529509
} catch (error) {
@@ -592,7 +572,7 @@ export async function loadSelectedDocument(): Promise<{
592572
data: PassportData;
593573
metadata: DocumentMetadata;
594574
} | null> {
595-
const catalog = await loadDocumentCatalog();
575+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
596576
console.log('Catalog loaded');
597577

598578
if (!catalog.selectedDocumentId) {
@@ -618,7 +598,9 @@ export async function loadSelectedDocument(): Promise<{
618598
return null;
619599
}
620600

621-
const data = await loadDocumentById(catalog.selectedDocumentId);
601+
const data = await loadDocumentByIdDirectlyFromKeychain(
602+
catalog.selectedDocumentId,
603+
);
622604
if (!data) {
623605
console.log('Document data not found for id:', catalog.selectedDocumentId);
624606
return null;
@@ -660,6 +642,7 @@ interface IPassportContext {
660642
signature: string;
661643
data: PassportData;
662644
} | null>;
645+
// TODO: is this even used?
663646
getAllData: () => Promise<{ [service: string]: PassportData }>;
664647
getAvailableTypes: () => Promise<string[]>;
665648
setData: (data: PassportData) => Promise<void>;
@@ -673,12 +656,15 @@ interface IPassportContext {
673656
} | null>;
674657
clearPassportData: () => Promise<void>;
675658
clearSpecificData: (documentType: string) => Promise<void>;
659+
676660
loadDocumentCatalog: () => Promise<DocumentCatalog>;
677661
getAllDocuments: () => Promise<{
678662
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
679663
}>;
664+
680665
setSelectedDocument: (documentId: string) => Promise<void>;
681666
deleteDocument: (documentId: string) => Promise<void>;
667+
682668
migrateFromLegacyStorage: () => Promise<void>;
683669
getCurrentDocumentType: () => Promise<string | null>;
684670
clearDocumentCatalogForMigrationTesting: () => Promise<void>;
@@ -688,12 +674,11 @@ interface IPassportContext {
688674
isRegistered: boolean,
689675
) => Promise<void>;
690676
checkIfAnyDocumentsNeedMigration: () => Promise<boolean>;
691-
hasAnyValidRegisteredDocument: () => Promise<boolean>;
692677
checkAndUpdateRegistrationStates: () => Promise<void>;
693678
}
694679

695680
export async function markCurrentDocumentAsRegistered(): Promise<void> {
696-
const catalog = await loadDocumentCatalog();
681+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
697682
if (catalog.selectedDocumentId) {
698683
await updateDocumentRegistrationState(catalog.selectedDocumentId, true);
699684
} else {
@@ -703,7 +688,7 @@ export async function markCurrentDocumentAsRegistered(): Promise<void> {
703688

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

708693
// If catalog already has documents, skip migration
709694
if (catalog.documents.length > 0) {
@@ -789,15 +774,15 @@ export async function saveDocumentCatalog(
789774
}
790775

791776
export async function setDefaultDocumentTypeIfNeeded() {
792-
const catalog = await loadDocumentCatalog();
777+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
793778

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

799784
export async function setSelectedDocument(documentId: string): Promise<void> {
800-
const catalog = await loadDocumentCatalog();
785+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
801786
const metadata = catalog.documents.find(d => d.id === documentId);
802787

803788
if (metadata) {
@@ -812,7 +797,7 @@ export async function storeDocumentWithDeduplication(
812797
passportData: PassportData,
813798
): Promise<string> {
814799
const contentHash = calculateContentHash(passportData);
815-
const catalog = await loadDocumentCatalog();
800+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
816801

817802
// Check for existing document with same content
818803
const existing = catalog.documents.find(d => d.id === contentHash);
@@ -868,7 +853,7 @@ export async function updateDocumentRegistrationState(
868853
documentId: string,
869854
isRegistered: boolean,
870855
): Promise<void> {
871-
const catalog = await loadDocumentCatalog();
856+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
872857
const documentIndex = catalog.documents.findIndex(d => d.id === documentId);
873858

874859
if (documentIndex !== -1) {
@@ -885,3 +870,29 @@ export async function updateDocumentRegistrationState(
885870
export const usePassport = () => {
886871
return useContext(PassportContext);
887872
};
873+
874+
/**
875+
* Get all documents directly from the keychain.
876+
*
877+
* It's here to avoid dependency on self client where it's not strictly necessary,
878+
* for example when migrating legacy data.
879+
*
880+
* @returns A dictionary of document IDs to their data and metadata.
881+
*/
882+
export const getAllDocumentsDirectlyFromKeychain = async (): Promise<{
883+
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
884+
}> => {
885+
const catalog = await loadDocumentCatalogDirectlyFromKeychain();
886+
const allDocs: {
887+
[documentId: string]: { data: PassportData; metadata: DocumentMetadata };
888+
} = {};
889+
890+
for (const metadata of catalog.documents) {
891+
const data = await loadDocumentByIdDirectlyFromKeychain(metadata.id);
892+
if (data) {
893+
allDocs[metadata.id] = { data, metadata };
894+
}
895+
}
896+
897+
return allDocs;
898+
};

0 commit comments

Comments
 (0)