diff --git a/app/src/providers/selfClientProvider.tsx b/app/src/providers/selfClientProvider.tsx index 0686f45e0..c54b75d30 100644 --- a/app/src/providers/selfClientProvider.tsx +++ b/app/src/providers/selfClientProvider.tsx @@ -15,6 +15,8 @@ import { TrackEventParams } from '@selfxyz/mobile-sdk-alpha'; import { selfClientDocumentsAdapter } from '@/providers/passportDataProvider'; import analytics from '@/utils/analytics'; +import { unsafe_getPrivateKey } from './authProvider'; + /** * Provides a configured Self SDK client instance to all descendants. * @@ -25,7 +27,7 @@ import analytics from '@/utils/analytics'; */ export const SelfClientProvider = ({ children }: PropsWithChildren) => { const config = useMemo(() => ({}), []); - const adapters: Partial = useMemo( + const adapters: Adapters = useMemo( () => ({ scanner: webScannerShim, network: { @@ -82,6 +84,9 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { analytics().trackEvent(event, data); }, }, + auth: { + getPrivateKey: () => unsafe_getPrivateKey(), + }, }), [], ); diff --git a/app/src/screens/dev/DevPrivateKeyScreen.tsx b/app/src/screens/dev/DevPrivateKeyScreen.tsx index 04290a04e..2c4d0463c 100644 --- a/app/src/screens/dev/DevPrivateKeyScreen.tsx +++ b/app/src/screens/dev/DevPrivateKeyScreen.tsx @@ -6,7 +6,8 @@ import { useCallback, useEffect, useState } from 'react'; import { Button, Text, XStack, YStack } from 'tamagui'; import Clipboard from '@react-native-clipboard/clipboard'; -import { unsafe_getPrivateKey } from '@/providers/authProvider'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; + import { black, slate50, slate200, teal500, white } from '@/utils/colors'; import { confirmTap } from '@/utils/haptic'; @@ -16,12 +17,24 @@ const DevPrivateKeyScreen: React.FC = () => { ); const [isPrivateKeyRevealed, setIsPrivateKeyRevealed] = useState(false); const [copied, setCopied] = useState(false); + const selfClient = useSelfClient(); useEffect(() => { - unsafe_getPrivateKey().then(key => - setPrivateKey(key || 'No private key found'), - ); - }, []); + let mounted = true; + selfClient + .getPrivateKey() + .then(key => { + if (!mounted) return; + setPrivateKey(key || 'No private key found'); + }) + .catch(() => { + if (!mounted) return; + setPrivateKey('No private key found'); + }); + return () => { + mounted = false; + }; + }, [selfClient]); const handleRevealPrivateKey = useCallback(() => { confirmTap(); diff --git a/app/src/utils/proving/provingMachine.ts b/app/src/utils/proving/provingMachine.ts index 49897ee79..a4acd31ac 100644 --- a/app/src/utils/proving/provingMachine.ts +++ b/app/src/utils/proving/provingMachine.ts @@ -35,8 +35,6 @@ import { } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { navigationRef } from '@/navigation'; -// this will be pass as property of from selfClient -import { unsafe_getPrivateKey } from '@/providers/authProvider'; // will need to be passed in from selfClient import { clearPassportData, @@ -651,8 +649,7 @@ export const useProvingStore = create((set, get) => { const { data: passportData } = selectedDocument; - // TODO call on self client - const secret = await unsafe_getPrivateKey(); + const secret = await selfClient.getPrivateKey(); if (!secret) { console.error('Could not load secret'); trackEvent(ProofEvents.LOAD_SECRET_FAILED); diff --git a/app/tests/src/utils/proving/validateDocument.test.ts b/app/tests/src/utils/proving/validateDocument.test.ts index 834e8edb1..125b883c6 100644 --- a/app/tests/src/utils/proving/validateDocument.test.ts +++ b/app/tests/src/utils/proving/validateDocument.test.ts @@ -62,6 +62,7 @@ function createTestClient() { return createSelfClient({ config: {}, adapters: { + auth: { getPrivateKey: jest.fn() }, scanner: { scan: jest.fn() }, network: { http: { fetch: jest.fn() }, diff --git a/app/tests/utils/selfClientProvider.ts b/app/tests/utils/selfClientProvider.ts index 38231e174..318140433 100644 --- a/app/tests/utils/selfClientProvider.ts +++ b/app/tests/utils/selfClientProvider.ts @@ -3,6 +3,7 @@ /* eslint-disable sort-exports/sort-exports */ import type { + AuthAdapter, CryptoAdapter, DocumentsAdapter, NetworkAdapter, @@ -50,7 +51,12 @@ export const mockScanner: ScannerAdapter = { }), }; +export const mockAuth: AuthAdapter = { + getPrivateKey: async () => '0x-mock-private-key', +}; + export const mockAdapters = { + auth: mockAuth, scanner: mockScanner, network: mockNetwork, crypto: mockCrypto, diff --git a/packages/mobile-sdk-alpha/src/browser.ts b/packages/mobile-sdk-alpha/src/browser.ts index 8dd3951c5..1621fc82e 100644 --- a/packages/mobile-sdk-alpha/src/browser.ts +++ b/packages/mobile-sdk-alpha/src/browser.ts @@ -44,8 +44,6 @@ export type { SdkErrorCategory } from './errors'; export { SCANNER_ERROR_CODES, notImplemented, sdkError } from './errors'; export { SelfClientContext, SelfClientProvider, useSelfClient } from './context'; -// Browser-only high-level component (DOM-based) -export { SelfMobileSdk as SelfMobileSdkHighLevel } from './components/SelfMobileSdk'; export { createSelfClient } from './client'; diff --git a/packages/mobile-sdk-alpha/src/client.ts b/packages/mobile-sdk-alpha/src/client.ts index f8541b8bb..4f4585be0 100644 --- a/packages/mobile-sdk-alpha/src/client.ts +++ b/packages/mobile-sdk-alpha/src/client.ts @@ -30,7 +30,7 @@ import { TrackEventParams } from './types/public'; * own. These defaults are intentionally minimal no-ops suitable for tests and * non-production environments. */ -const optionalDefaults: Partial = { +const optionalDefaults: Required> = { storage: { get: async () => null, set: async () => {}, @@ -47,7 +47,7 @@ const optionalDefaults: Partial = { }, }; -const REQUIRED_ADAPTERS = ['scanner', 'network', 'crypto', 'documents'] as const; +const REQUIRED_ADAPTERS = ['auth', 'scanner', 'network', 'crypto', 'documents'] as const; /** * Creates a fully configured {@link SelfClient} instance. @@ -56,14 +56,14 @@ const REQUIRED_ADAPTERS = ['scanner', 'network', 'crypto', 'documents'] as const * provided configuration with sensible defaults. Missing optional adapters are * filled with benign no-op implementations. */ -export function createSelfClient({ config, adapters }: { config: Config; adapters: Partial }): SelfClient { +export function createSelfClient({ config, adapters }: { config: Config; adapters: Adapters }): SelfClient { const cfg = mergeConfig(defaultConfig, config); for (const name of REQUIRED_ADAPTERS) { if (!(name in adapters) || !adapters[name as keyof Adapters]) throw notImplemented(name); } - const _adapters = { ...optionalDefaults, ...adapters } as Adapters; + const _adapters = { ...optionalDefaults, ...adapters }; const listeners = new Map void>>(); function on(event: E, cb: (payload: SDKEventMap[E]) => void): Unsubscribe { @@ -128,10 +128,30 @@ export function createSelfClient({ config, adapters }: { config: Config; adapter return adapters.analytics.trackEvent(event, payload); } + /** + * Retrieves the private key via the auth adapter. + * With great power comes great responsibility + */ + async function getPrivateKey(): Promise { + return adapters.auth.getPrivateKey(); + } + + async function hasPrivateKey(): Promise { + if (!adapters.auth) return false; + try { + const key = await adapters.auth.getPrivateKey(); + return !!key; + } catch { + return false; + } + } + return { scanDocument, validateDocument, trackEvent, + getPrivateKey, + hasPrivateKey, checkRegistration, registerDocument, generateProof, diff --git a/packages/mobile-sdk-alpha/src/components/SelfMobileSdk.tsx b/packages/mobile-sdk-alpha/src/components/SelfMobileSdk.tsx deleted file mode 100644 index bd3a64ef4..000000000 --- a/packages/mobile-sdk-alpha/src/components/SelfMobileSdk.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. -// SPDX-License-Identifier: BUSL-1.1 -// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. - -import type { ComponentType, ReactNode } from 'react'; -import { Text, View } from 'tamagui'; - -import { SelfClientProvider } from '../context'; -import { useDocumentManager } from '../hooks/useDocumentManager'; -import type { Adapters, Config } from '../types/public'; -import type { ExternalAdapter, PassportCameraProps, ScreenProps } from '../types/ui'; -import { OnboardingFlow } from './flows/OnboardingFlow'; -import { QRCodeScreen } from './screens/QRCodeScreen'; - -interface SelfMobileSdkProps { - config: Config; - adapters?: Partial; - external: ExternalAdapter; - children?: ReactNode; - // Optional custom components - customScreens?: { - PassportCamera?: ComponentType; - NFCScanner?: ComponentType; - QRScanner?: ReactNode; - }; -} - -const SelfMobileSdkContent = ({ - external, - customScreens = {}, -}: { - external: ExternalAdapter; - customScreens?: SelfMobileSdkProps['customScreens']; -}) => { - const { documents, isLoading, hasRegisteredDocuments } = useDocumentManager(external); - - if (isLoading) { - return ( - - Loading documents... - - ); - } - - // Check if user has any registered documents - const hasDocuments = Object.keys(documents).length > 0 && hasRegisteredDocuments(); - - if (!hasDocuments) { - return ( - - ); - } - - // Show disclosure flow - return ( - customScreens.QRScanner || ( - - ) - ); -}; - -export const SelfMobileSdk = ({ config, adapters = {}, external, children, customScreens }: SelfMobileSdkProps) => { - return ( - - {children || } - - ); -}; diff --git a/packages/mobile-sdk-alpha/src/components/flows/OnboardingFlow.tsx b/packages/mobile-sdk-alpha/src/components/flows/OnboardingFlow.tsx deleted file mode 100644 index 9216b090e..000000000 --- a/packages/mobile-sdk-alpha/src/components/flows/OnboardingFlow.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. -// SPDX-License-Identifier: BUSL-1.1 -// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. - -import type { ComponentType } from 'react'; -import { useCallback, useState } from 'react'; - -import { useSelfClient } from '../../context'; -import type { MRZInfo } from '../../types/public'; -import type { DocumentData, ExternalAdapter, PassportCameraProps, ScreenProps } from '../../types/ui'; -import { NFCScannerScreen } from '../screens/NFCScannerScreen'; -import { PassportCameraScreen } from '../screens/PassportCameraScreen'; - -interface OnboardingFlowProps { - external: ExternalAdapter; - setDocument: (doc: DocumentData, documentId: string) => Promise; - PassportCamera?: ComponentType; - NFCScanner?: ComponentType; -} - -export const OnboardingFlow = ({ external, setDocument, PassportCamera, NFCScanner }: OnboardingFlowProps) => { - const [mrzData, setMrzData] = useState(null); - const client = useSelfClient(); - - const handleMRZDetected = useCallback( - async (mrzData: MRZInfo) => { - try { - const status = await client.registerDocument({ - scan: { - mode: 'mrz', - passportNumber: mrzData.passportNumber, - dateOfBirth: mrzData.dateOfBirth, - dateOfExpiry: mrzData.dateOfExpiry, - issuingCountry: mrzData.issuingCountry, - }, - }); - - if (status.registered) { - setMrzData(mrzData); - } else { - external.onOnboardingFailure(new Error('Registration failed')); - } - } catch (error) { - external.onOnboardingFailure(error as Error); - } - }, - [client, external, setDocument], - ); - - if (!mrzData) { - if (PassportCamera) { - const PCam = PassportCamera as ComponentType; - return ; - } - return ; - } - - if (NFCScanner) { - const NFC = NFCScanner as ComponentType; - return ; - } - return ; -}; diff --git a/packages/mobile-sdk-alpha/src/context.tsx b/packages/mobile-sdk-alpha/src/context.tsx index 90ced4824..87877af01 100644 --- a/packages/mobile-sdk-alpha/src/context.tsx +++ b/packages/mobile-sdk-alpha/src/context.tsx @@ -28,7 +28,7 @@ export interface SelfClientProviderProps { * Partial set of adapter implementations. Any missing optional adapters will * be replaced with default no-op implementations. */ - adapters?: Partial; + adapters: Adapters; } export { SelfClientContext }; @@ -40,7 +40,7 @@ export { SelfClientContext }; * Consumers should ensure that `config` and `adapters` are referentially stable * (e.g. wrapped in `useMemo`) to avoid recreating the client on every render. */ -export function SelfClientProvider({ config, adapters = {}, children }: PropsWithChildren) { +export function SelfClientProvider({ config, adapters, children }: PropsWithChildren) { const client = useMemo(() => createSelfClient({ config, adapters }), [config, adapters]); return {children}; diff --git a/packages/mobile-sdk-alpha/src/entry/index.tsx b/packages/mobile-sdk-alpha/src/entry/index.tsx index d68156810..da200cf4d 100644 --- a/packages/mobile-sdk-alpha/src/entry/index.tsx +++ b/packages/mobile-sdk-alpha/src/entry/index.tsx @@ -9,11 +9,11 @@ import type { Adapters, Config } from '../types/public'; export interface SelfMobileSdkProps { config: Config; - adapters?: Partial; + adapters: Adapters; children?: ReactNode; } -export const SelfMobileSdk = ({ config, adapters = {}, children }: SelfMobileSdkProps) => ( +export const SelfMobileSdk = ({ config, adapters, children }: SelfMobileSdkProps) => ( {children} diff --git a/packages/mobile-sdk-alpha/src/hooks/useDocumentManager.ts b/packages/mobile-sdk-alpha/src/hooks/useDocumentManager.ts deleted file mode 100644 index b4fcd5a49..000000000 --- a/packages/mobile-sdk-alpha/src/hooks/useDocumentManager.ts +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. -// SPDX-License-Identifier: BUSL-1.1 -// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. - -import { useCallback, useEffect, useState } from 'react'; - -import type { DocumentData, ExternalAdapter } from '../types/ui'; - -export const useDocumentManager = (external: ExternalAdapter) => { - const [documents, setDocuments] = useState<{ - [documentId: string]: DocumentData; - }>({}); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - external - .getAllDocuments() - .then(documents => { - setDocuments(documents); - setIsLoading(false); - }) - .catch(error => { - console.error('Failed to load documents:', error); - setIsLoading(false); - }); - }, [external]); - - const hasRegisteredDocuments = useCallback(() => { - return Object.values(documents).some(doc => doc.metadata.isRegistered); - }, [documents]); - - return { - documents, - isLoading, - hasRegisteredDocuments, - setDocuments, - }; -}; diff --git a/packages/mobile-sdk-alpha/src/index.ts b/packages/mobile-sdk-alpha/src/index.ts index 76bd091a8..46d77f3f4 100644 --- a/packages/mobile-sdk-alpha/src/index.ts +++ b/packages/mobile-sdk-alpha/src/index.ts @@ -6,6 +6,7 @@ export type { Adapters, AnalyticsAdapter, + AuthAdapter, ClockAdapter, Config, CryptoAdapter, @@ -40,7 +41,7 @@ export type { // MRZ module export type { DG1, DG2, NFCScanOptions, ParsedNFCResponse } from './nfc'; -export type { DocumentData, DocumentMetadata, ExternalAdapter, PassportCameraProps, ScreenProps } from './types/ui'; +export type { DocumentData, DocumentMetadata, PassportCameraProps, ScreenProps } from './types/ui'; export type { MRZScanOptions } from './mrz'; @@ -65,9 +66,6 @@ export { export { NFCScannerScreen } from './components/screens/NFCScannerScreen'; -// Flow Components -export { OnboardingFlow } from './components/flows/OnboardingFlow'; - // Screen Components export { PassportCameraScreen } from './components/screens/PassportCameraScreen'; @@ -100,8 +98,5 @@ export { parseNFCResponse, scanNFC } from './nfc'; export { scanQRProof } from './qr'; -// Hooks -export { useDocumentManager } from './hooks/useDocumentManager'; - // Error handling export { webScannerShim } from './adapters/web/shims'; diff --git a/packages/mobile-sdk-alpha/src/types/public.ts b/packages/mobile-sdk-alpha/src/types/public.ts index 7c57fbf90..c8466310e 100644 --- a/packages/mobile-sdk-alpha/src/types/public.ts +++ b/packages/mobile-sdk-alpha/src/types/public.ts @@ -64,6 +64,14 @@ export interface AnalyticsAdapter { trackEvent(event: string, payload?: TrackEventParams): void; } +export interface AuthAdapter { + /** + * Returns the hex-encoded private key. + * This key should only be used for self and not other crypto operations or signing. + */ + getPrivateKey(): Promise; +} + export interface ClockAdapter { now(): number; sleep(ms: number, signal?: AbortSignal): Promise; @@ -85,13 +93,14 @@ export interface Progress { percent?: number; } export interface Adapters { - storage: StorageAdapter; + storage?: StorageAdapter; scanner: ScannerAdapter; crypto: CryptoAdapter; network: NetworkAdapter; - clock: ClockAdapter; - logger: LoggerAdapter; - analytics: AnalyticsAdapter; + clock?: ClockAdapter; + logger?: LoggerAdapter; + analytics?: AnalyticsAdapter; + auth: AuthAdapter; documents: DocumentsAdapter; } @@ -171,6 +180,8 @@ export interface SelfClient { ): Promise; extractMRZInfo(mrz: string): MRZInfo; trackEvent(event: string, payload?: TrackEventParams): void; + getPrivateKey(): Promise; + hasPrivateKey(): Promise; on(event: E, cb: (payload: SDKEventMap[E]) => void): Unsubscribe; emit(event: E, payload: SDKEventMap[E]): void; diff --git a/packages/mobile-sdk-alpha/src/types/ui.ts b/packages/mobile-sdk-alpha/src/types/ui.ts index 8c5cb2743..e2120bc75 100644 --- a/packages/mobile-sdk-alpha/src/types/ui.ts +++ b/packages/mobile-sdk-alpha/src/types/ui.ts @@ -26,19 +26,6 @@ export interface DocumentData { metadata: DocumentMetadata; } -// External adapter interface -export interface ExternalAdapter { - getSecret: () => Promise; - getAllDocuments: () => Promise<{ - [documentId: string]: DocumentData; - }>; - setDocument: (doc: DocumentData, documentId: string) => Promise; - onOnboardingSuccess: () => void; - onOnboardingFailure: (error: Error) => void; - onDisclosureSuccess: () => void; - onDisclosureFailure: (error: Error) => void; -} - // Screen component props export interface ScreenProps { onSuccess: () => void; diff --git a/packages/mobile-sdk-alpha/tests/client.test.tsx b/packages/mobile-sdk-alpha/tests/client-mrz.test.ts similarity index 100% rename from packages/mobile-sdk-alpha/tests/client.test.tsx rename to packages/mobile-sdk-alpha/tests/client-mrz.test.ts diff --git a/packages/mobile-sdk-alpha/tests/client.test.ts b/packages/mobile-sdk-alpha/tests/client.test.ts index 140edc589..f7a180c73 100644 --- a/packages/mobile-sdk-alpha/tests/client.test.ts +++ b/packages/mobile-sdk-alpha/tests/client.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it, vi } from 'vitest'; import type { CryptoAdapter, DocumentsAdapter, NetworkAdapter, ScannerAdapter } from '../src'; import { createSelfClient } from '../src/index'; +import { AuthAdapter } from '../src/types/public'; describe('createSelfClient', () => { // Test eager validation during client creation @@ -13,8 +14,10 @@ describe('createSelfClient', () => { expect(() => createSelfClient({ config: {}, + // @ts-expect-error -- missing adapters adapters: { documents, + auth, network, crypto, }, @@ -23,25 +26,28 @@ describe('createSelfClient', () => { }); it('throws when network adapter missing during creation', () => { - expect(() => createSelfClient({ config: {}, adapters: { scanner, crypto, documents } })).toThrow( + // @ts-expect-error -- missing adapters + expect(() => createSelfClient({ config: {}, adapters: { scanner, crypto, documents, auth } })).toThrow( 'network adapter not provided', ); }); it('throws when crypto adapter missing during creation', () => { - expect(() => createSelfClient({ config: {}, adapters: { scanner, network, documents } })).toThrow( + // @ts-expect-error -- missing adapters + expect(() => createSelfClient({ config: {}, adapters: { scanner, network, documents, auth } })).toThrow( 'crypto adapter not provided', ); }); it('throws when documents adapter missing during creation', () => { - expect(() => createSelfClient({ config: {}, adapters: { scanner, network, crypto } })).toThrow( + // @ts-expect-error -- missing adapters + expect(() => createSelfClient({ config: {}, adapters: { scanner, network, crypto, auth } })).toThrow( 'documents adapter not provided', ); }); it('creates client successfully with all required adapters', () => { - const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents } }); + const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents, auth } }); expect(client).toBeTruthy(); }); @@ -49,7 +55,7 @@ describe('createSelfClient', () => { const scanMock = vi.fn().mockResolvedValue({ mode: 'qr', data: 'self://ok' }); const client = createSelfClient({ config: {}, - adapters: { scanner: { scan: scanMock }, network, crypto, documents }, + adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth }, }); const result = await client.scanDocument({ mode: 'qr' }); expect(result).toEqual({ mode: 'qr', data: 'self://ok' }); @@ -61,7 +67,7 @@ describe('createSelfClient', () => { const scanMock = vi.fn().mockRejectedValue(err); const client = createSelfClient({ config: {}, - adapters: { scanner: { scan: scanMock }, network, crypto, documents }, + adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth }, }); await expect(client.scanDocument({ mode: 'qr' })).rejects.toBe(err); }); @@ -70,7 +76,7 @@ describe('createSelfClient', () => { const network = { http: { fetch: vi.fn() }, ws: { connect: vi.fn() } } as any; const crypto = { hash: vi.fn(), sign: vi.fn() } as any; const scanner = { scan: vi.fn() } as any; - const client = createSelfClient({ config: {}, adapters: { network, crypto, scanner, documents } }); + const client = createSelfClient({ config: {}, adapters: { network, crypto, scanner, documents, auth } }); const handle = await client.generateProof({ type: 'register', payload: {} }); expect(handle.id).toBe('stub'); expect(handle.status).toBe('pending'); @@ -79,7 +85,7 @@ describe('createSelfClient', () => { }); it('emits and unsubscribes events', () => { - const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents } }); + const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents, auth } }); const cb = vi.fn(); const originalSet = Map.prototype.set; let eventSet: Set<(p: any) => void> | undefined; @@ -98,7 +104,7 @@ describe('createSelfClient', () => { }); it('parses MRZ via client', () => { - const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents } }); + const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents, auth } }); const sample = `P { }); it('returns stub registration status', async () => { - const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents } }); + const client = createSelfClient({ config: {}, adapters: { scanner, network, crypto, documents, auth } }); await expect(client.registerDocument({} as any)).resolves.toEqual({ registered: false, reason: 'SELF_REG_STATUS_STUB', @@ -117,15 +123,42 @@ describe('createSelfClient', () => { const trackEvent = vi.fn(); const client = createSelfClient({ config: {}, - adapters: { scanner, network, crypto, analytics: { trackEvent }, documents }, + adapters: { + scanner, + network, + crypto, + documents, + analytics: { trackEvent }, + auth: { getPrivateKey: () => Promise.resolve('stubbed-private-key') }, + }, }); client.trackEvent('test_event'); + expect(trackEvent).toHaveBeenCalledOnce(); expect(trackEvent).toHaveBeenCalledWith('test_event', undefined); client.trackEvent('another_event', { foo: 'bar' }); expect(trackEvent).toHaveBeenCalledWith('another_event', { foo: 'bar' }); }); }); + describe('when auth adapter is given', () => { + it('getPrivateKey becomes callable on the client', async () => { + const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key')); + const client = createSelfClient({ + config: {}, + adapters: { scanner, network, crypto, documents, auth: { getPrivateKey } }, + }); + + await expect(client.getPrivateKey()).resolves.toBe('stubbed-private-key'); + }); + it('hasPrivateKey becomes callable on the client', async () => { + const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key')); + const client = createSelfClient({ + config: {}, + adapters: { scanner, network, crypto, documents, auth: { getPrivateKey } }, + }); + await expect(client.hasPrivateKey()).resolves.toBe(true); + }); + }); }); const scanner: ScannerAdapter = { @@ -150,6 +183,10 @@ const crypto: CryptoAdapter = { sign: async () => new Uint8Array(), }; +const auth: AuthAdapter = { + getPrivateKey: async () => 'secret', +}; + const documents: DocumentsAdapter = { loadDocumentCatalog: async () => ({ documents: [] }), loadDocumentById: async () => null, diff --git a/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts b/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts index 1ea309255..7f3161fca 100644 --- a/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts +++ b/packages/mobile-sdk-alpha/tests/utils/testHelpers.ts @@ -49,11 +49,16 @@ export const mockDocuments: DocumentsAdapter = { loadDocumentById: async () => null, }; +const mockAuth = { + getPrivateKey: async () => 'stubbed-private-key', +}; + export const mockAdapters = { scanner: mockScanner, network: mockNetwork, crypto: mockCrypto, documents: mockDocuments, + auth: mockAuth, }; // Shared test expectations