diff --git a/.github/workflows/mobile-ci.yml b/.github/workflows/mobile-ci.yml index 2d1faaeba..0aa8d78a7 100644 --- a/.github/workflows/mobile-ci.yml +++ b/.github/workflows/mobile-ci.yml @@ -178,7 +178,7 @@ jobs: else echo "✅ All required dependency files exist" fi - - name: Test + - name: App Tests run: | # Final verification from app directory perspective echo "Final verification before running tests (from app directory)..." @@ -190,7 +190,7 @@ jobs: fi echo "✅ All dependencies verified, running tests..." # Run jest through yarn to avoid the build:deps step since CI already built dependencies - yarn jest --passWithNoTests && node --test scripts/tests/*.cjs + yarn test:ci working-directory: ./app build-ios: runs-on: macos-latest-large diff --git a/app/jest.config.cjs b/app/jest.config.cjs index 872a0ae00..7d552df0d 100644 --- a/app/jest.config.cjs +++ b/app/jest.config.cjs @@ -19,6 +19,10 @@ module.exports = { '^@tests$': '/tests/src', '^@selfxyz/mobile-sdk-alpha$': '/../packages/mobile-sdk-alpha/dist/cjs/index.cjs', + '^@selfxyz/mobile-sdk-alpha/onboarding/(.*)$': + '/../packages/mobile-sdk-alpha/dist/cjs/flows/onboarding/$1.cjs', + '^@selfxyz/mobile-sdk-alpha/disclosing/(.*)$': + '/../packages/mobile-sdk-alpha/dist/cjs/flows/disclosing/$1.cjs', '^@selfxyz/mobile-sdk-alpha/(.*)$': '/../packages/mobile-sdk-alpha/dist/cjs/$1.cjs', // Fix snarkjs resolution for @anon-aadhaar/core diff --git a/app/metro.config.cjs b/app/metro.config.cjs index c8ed5943e..768039970 100644 --- a/app/metro.config.cjs +++ b/app/metro.config.cjs @@ -4,6 +4,7 @@ const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const path = require('node:path'); +const fs = require('node:fs'); const findYarnWorkspaceRoot = require('find-yarn-workspace-root'); const defaultConfig = getDefaultConfig(__dirname); @@ -112,6 +113,35 @@ const config = { 'react-native-gesture-handler': 'react-native-gesture-handler/lib/commonjs/index.js', }; + const sdkAlphaPath = path.resolve( + workspaceRoot, + 'packages/mobile-sdk-alpha', + ); + + // Custom resolver to handle Node.js modules and dynamic flow imports + if (moduleName.startsWith('@selfxyz/mobile-sdk-alpha/')) { + const subPath = moduleName.replace('@selfxyz/mobile-sdk-alpha/', ''); + + // Check if it's a flow import (onboarding/* or disclosing/*) + if ( + subPath.startsWith('onboarding/') || + subPath.startsWith('disclosing/') + ) { + const flowPath = path.resolve( + sdkAlphaPath, + 'dist/esm/flows', + `${subPath}.js`, + ); + + // Check if the file exists + if (fs.existsSync(flowPath)) { + return { + type: 'sourceFile', + filePath: flowPath, + }; + } + } + } if (appLevelModules[moduleName]) { try { diff --git a/app/package.json b/app/package.json index 8a30dda4a..22860c423 100644 --- a/app/package.json +++ b/app/package.json @@ -56,6 +56,7 @@ "tag:release": "node scripts/tag.cjs release", "tag:remove": "node scripts/tag.cjs remove", "test": "yarn build:deps && jest --passWithNoTests && node --test scripts/tests/*.cjs", + "test:ci": "jest --passWithNoTests && node --test scripts/tests/*.cjs", "test:build": "yarn build:deps && yarn types && node ./scripts/bundle-analyze-ci.cjs ios && yarn test", "test:coverage": "jest --coverage --passWithNoTests", "test:coverage:ci": "jest --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json", diff --git a/app/src/screens/prove/ConfirmBelongingScreen.tsx b/app/src/screens/prove/ConfirmBelongingScreen.tsx index c21b0b0d8..00dfe5dc4 100644 --- a/app/src/screens/prove/ConfirmBelongingScreen.tsx +++ b/app/src/screens/prove/ConfirmBelongingScreen.tsx @@ -8,14 +8,15 @@ import { ActivityIndicator, View } from 'react-native'; import type { StaticScreenProps } from '@react-navigation/native'; import { usePreventRemove } from '@react-navigation/native'; -import { - usePrepareDocumentProof, - useSelfClient, -} from '@selfxyz/mobile-sdk-alpha'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; import { PassportEvents, ProofEvents, } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; +import { + getPreRegistrationDescription, + usePrepareDocumentProof, +} from '@selfxyz/mobile-sdk-alpha/onboarding/confirm-identification'; import successAnimation from '@/assets/animations/loading/success.json'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; @@ -109,10 +110,7 @@ const ConfirmBelongingScreen: React.FC = () => { > Confirm your identity - By continuing, you certify that this passport, biometric ID or - Aadhaar card belongs to you and is not stolen or forged. Once - registered with Self, this document will be permanently linked to - your identity and can't be linked to another one. + {getPreRegistrationDescription()} = ({}) => { // Get current state from proving machine, default to 'idle' if undefined const currentState = useProvingStore(state => state.currentState) ?? 'idle'; - const fcmToken = useProvingStore(state => state.fcmToken); + const fcmToken = useSettingStore(state => state.fcmToken); const isFocused = useIsFocused(); const { bottom } = useSafeAreaInsets(); diff --git a/app/tsconfig.json b/app/tsconfig.json index 38ac62482..1da1335af 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -14,6 +14,12 @@ "../packages/mobile-sdk-alpha/src", "../packages/mobile-sdk-alpha/dist" ], + "@selfxyz/mobile-sdk-alpha/onboarding/*": [ + "../packages/mobile-sdk-alpha/dist/esm/flows/onboarding/*" + ], + "@selfxyz/mobile-sdk-alpha/disclosing/*": [ + "../packages/mobile-sdk-alpha/dist/esm/flows/disclosing/*" + ], "@/*": ["./src/*"] } }, diff --git a/packages/mobile-sdk-alpha/package.json b/packages/mobile-sdk-alpha/package.json index 428a8e597..33803983c 100644 --- a/packages/mobile-sdk-alpha/package.json +++ b/packages/mobile-sdk-alpha/package.json @@ -24,6 +24,16 @@ "import": "./dist/esm/browser.js", "require": "./dist/cjs/browser.cjs" }, + "./onboarding/*": { + "types": "./dist/esm/flows/onboarding/*.d.ts", + "import": "./dist/esm/flows/onboarding/*.js", + "require": "./dist/cjs/flows/onboarding/*.cjs" + }, + "./disclosing/*": { + "types": "./dist/esm/flows/disclosing/*.d.ts", + "import": "./dist/esm/flows/disclosing/*.js", + "require": "./dist/cjs/flows/disclosing/*.cjs" + }, "./constants": { "types": "./dist/esm/constants/index.d.ts", "react-native": "./dist/esm/constants/index.js", diff --git a/packages/mobile-sdk-alpha/src/browser.ts b/packages/mobile-sdk-alpha/src/browser.ts index ea2a34876..dc1368271 100644 --- a/packages/mobile-sdk-alpha/src/browser.ts +++ b/packages/mobile-sdk-alpha/src/browser.ts @@ -40,7 +40,7 @@ export { type ProvingStateType } from './proving/provingMachine'; export { SCANNER_ERROR_CODES, notImplemented, sdkError } from './errors'; export { SdkEvents } from './types/events'; -export { SelfClientContext, SelfClientProvider, usePrepareDocumentProof, useSelfClient } from './context'; +export { SelfClientContext, SelfClientProvider, useSelfClient } from './context'; export { clearPassportData, diff --git a/packages/mobile-sdk-alpha/src/components/MRZScannerView.tsx b/packages/mobile-sdk-alpha/src/components/MRZScannerView.tsx index 7743d3f6d..c7944e821 100644 --- a/packages/mobile-sdk-alpha/src/components/MRZScannerView.tsx +++ b/packages/mobile-sdk-alpha/src/components/MRZScannerView.tsx @@ -42,7 +42,7 @@ const NativeMRZScannerView = requireNativeComponent( })!, ); -interface MRZScannerViewProps { +export interface MRZScannerViewProps { style?: ViewStyle; height?: DimensionValue; width?: DimensionValue; diff --git a/packages/mobile-sdk-alpha/src/context.tsx b/packages/mobile-sdk-alpha/src/context.tsx index 15c094b63..0f1ff7402 100644 --- a/packages/mobile-sdk-alpha/src/context.tsx +++ b/packages/mobile-sdk-alpha/src/context.tsx @@ -2,10 +2,9 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import { createContext, type PropsWithChildren, useContext, useEffect, useMemo } from 'react'; +import { createContext, type PropsWithChildren, useContext, useMemo } from 'react'; import { createSelfClient } from './client'; -import { loadSelectedDocument } from './documents/utils'; import { SdkEvents } from './types/events'; import type { Adapters, Config, SelfClient } from './types/public'; @@ -57,35 +56,6 @@ export function SelfClientProvider({ return {children}; } -export function usePrepareDocumentProof() { - const selfClient = useSelfClient(); - const { useProvingStore } = selfClient; - const currentState = useProvingStore(state => state.currentState); - const init = useProvingStore(state => state.init); - const setUserConfirmed = useProvingStore(state => state.setUserConfirmed); - const isReadyToProve = currentState === 'ready_to_prove'; - - useEffect(() => { - const initializeProving = async () => { - try { - const selectedDocument = await loadSelectedDocument(selfClient); - if (selectedDocument?.data?.documentCategory === 'aadhaar') { - init(selfClient, 'register'); - } else { - init(selfClient, 'dsc'); - } - } catch (error) { - console.error('Error loading selected document:', error); - init(selfClient, 'dsc'); - } - }; - - initializeProving(); - }, [init, selfClient]); - - return { setUserConfirmed, isReadyToProve }; -} - /** * Retrieves the current {@link SelfClient} from context. * diff --git a/packages/mobile-sdk-alpha/src/flows/about.md b/packages/mobile-sdk-alpha/src/flows/about.md new file mode 100644 index 000000000..30cdac0e7 --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/about.md @@ -0,0 +1,13 @@ +# Read This + +This folder contains folders for flows ie steps to complete a task like onboarding aka document registration or disclosing. In each folder each file represents roughly 1 step. Usually this means 1 screen but can be multiple depending on how error and bad states are represented in the UI. This helps with implementation as consumers of the api when building out their screens will more easily know which functions, hooks, Components, and constants to use together. + +The files here and their structure are part of the external mobile sdk API. + +convention is for folder for each flow to end in --ing and for file names to be verb-noun.ts + +read-mrz +scan-nfc +import-aadhaar +confirm-ownership +generate-proof diff --git a/packages/mobile-sdk-alpha/src/flows/disclosing/await-verification.ts b/packages/mobile-sdk-alpha/src/flows/disclosing/await-verification.ts new file mode 100644 index 000000000..61addd6a6 --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/disclosing/await-verification.ts @@ -0,0 +1,3 @@ +// 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. diff --git a/packages/mobile-sdk-alpha/src/flows/disclosing/confirm-selection.ts b/packages/mobile-sdk-alpha/src/flows/disclosing/confirm-selection.ts new file mode 100644 index 000000000..61addd6a6 --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/disclosing/confirm-selection.ts @@ -0,0 +1,3 @@ +// 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. diff --git a/packages/mobile-sdk-alpha/src/flows/disclosing/scan-qr-code.ts b/packages/mobile-sdk-alpha/src/flows/disclosing/scan-qr-code.ts new file mode 100644 index 000000000..61addd6a6 --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/disclosing/scan-qr-code.ts @@ -0,0 +1,3 @@ +// 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. diff --git a/packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.ts b/packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.ts new file mode 100644 index 000000000..d29af102d --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/onboarding/confirm-identification.ts @@ -0,0 +1,54 @@ +// 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 { useEffect } from 'react'; + +import { useSelfClient } from '../../context'; +import { loadSelectedDocument } from '../../documents/utils'; + +/*Add a comment on lines R7 to R9Add diff commentMarkdown input: edit mode selected.WritePreviewAdd a suggestionHeadingBoldItalicQuoteCodeLinkUnordered listNumbered listTask listMentionReferenceSaved repliesAdd FilesPaste, drop, or click to add filesCancelCommentStart a reviewReturn to code + Display this to users before they confirm ownership of a document +*/ +export function getPreRegistrationDescription() { + return "By continuing, you certify that this passport, biometric ID or Aadhaar card belongs to you and is not stolen or forged. Once registered with Self, this document will be permanently linked to your identity and can't be linked to another one."; +} + +/* + Hook to prepare for proving a document by initializing the proving state machine. + It loads the selected document and initializes the proving process based on the document type. + returns functions to set FCM token and mark user confirmation, along with a boolean indicating readiness to prove. + + Usage: + use `isReadyToProve` to enable/disable the confirmation button. + call `setUserConfirmed` when the user presses your confirm button. + after calling `setUserConfirmed`, the proving process will start. You MUST Navigate to wait-generation screen. +*/ +export function usePrepareDocumentProof() { + const selfClient = useSelfClient(); + const { useProvingStore } = selfClient; + const currentState = useProvingStore(state => state.currentState); + const init = useProvingStore(state => state.init); + const setUserConfirmed = useProvingStore(state => state.setUserConfirmed); + const isReadyToProve = currentState === 'ready_to_prove'; + + useEffect(() => { + const initializeProving = async () => { + try { + const selectedDocument = await loadSelectedDocument(selfClient); + if (selectedDocument?.data?.documentCategory === 'aadhaar') { + init(selfClient, 'register'); + } else { + init(selfClient, 'dsc'); + } + } catch (error) { + console.error('Error loading selected document:', error); + init(selfClient, 'dsc'); + } + }; + + initializeProving(); + }, [init, selfClient]); + + return { setUserConfirmed, isReadyToProve }; +} diff --git a/packages/mobile-sdk-alpha/src/flows/onboarding/read-mrz.ts b/packages/mobile-sdk-alpha/src/flows/onboarding/read-mrz.ts new file mode 100644 index 000000000..dc4ae68ca --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/onboarding/read-mrz.ts @@ -0,0 +1,8 @@ +// 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. + +export { MRZScannerView, MRZScannerViewProps } from '../../components/MRZScannerView'; +export function mrzReadInstructions() { + return 'Lay your document flat and position the machine readable text in the viewfinder'; +} diff --git a/packages/mobile-sdk-alpha/src/flows/onboarding/scan-nfc.ts b/packages/mobile-sdk-alpha/src/flows/onboarding/scan-nfc.ts new file mode 100644 index 000000000..61addd6a6 --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/onboarding/scan-nfc.ts @@ -0,0 +1,3 @@ +// 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. diff --git a/packages/mobile-sdk-alpha/src/index.ts b/packages/mobile-sdk-alpha/src/index.ts index aa63493b7..ade6701cc 100644 --- a/packages/mobile-sdk-alpha/src/index.ts +++ b/packages/mobile-sdk-alpha/src/index.ts @@ -63,14 +63,13 @@ export { } from './errors'; export { NFCScannerScreen } from './components/screens/NFCScannerScreen'; export { PassportCameraScreen } from './components/screens/PassportCameraScreen'; +export { type ProvingStateType } from './proving/provingMachine'; // Context and Client export { QRCodeScreen } from './components/screens/QRCodeScreen'; export { SdkEvents } from './types/events'; - // Components -export { SelfClientContext, SelfClientProvider, usePrepareDocumentProof, useSelfClient } from './context'; - +export { SelfClientContext, SelfClientProvider, useSelfClient } from './context'; // Documents utils export { SelfMobileSdk } from './entry'; diff --git a/packages/mobile-sdk-alpha/tsup.config.ts b/packages/mobile-sdk-alpha/tsup.config.ts index da0723eb3..4d0d883a1 100644 --- a/packages/mobile-sdk-alpha/tsup.config.ts +++ b/packages/mobile-sdk-alpha/tsup.config.ts @@ -2,15 +2,44 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. +import * as fs from 'node:fs'; +import * as path from 'node:path'; + import { defineConfig } from 'tsup'; const banner = `// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11`; +// Dynamically find all flow files +function findFlowFiles(dir: string, basePath = ''): Record { + const entries: Record = {}; + + if (!fs.existsSync(dir)) return entries; + + const items = fs.readdirSync(dir, { withFileTypes: true }); + + for (const item of items) { + const itemPath = path.join(dir, item.name); + const relativePath = basePath ? path.join(basePath, item.name) : item.name; + + if (item.isDirectory()) { + Object.assign(entries, findFlowFiles(itemPath, relativePath)); + } else if (item.isFile() && item.name.endsWith('.ts')) { + const key = path.join('flows', relativePath).replace(/\.ts$/, ''); + entries[key] = path.join('src', 'flows', relativePath); + } + } + + return entries; +} + +const flowEntries = findFlowFiles('src/flows'); + const entry = { index: 'src/index.ts', browser: 'src/browser.ts', 'constants/analytics': 'src/constants/analytics.ts', stores: 'src/stores/index.ts', + ...flowEntries, }; export default defineConfig([