From 4367780cd660e47402c5504491dd2cd0e38e0cf0 Mon Sep 17 00:00:00 2001 From: Aaron DeRuvo Date: Tue, 26 Aug 2025 15:40:14 +0200 Subject: [PATCH] Migrate Analytics (#951) * setup analytics adapter for self mobile sdk client and use in app * wrap for context * fix build * yarn types is an alias for build when build just compiles ts * ok unlock * deeper * ok this looks to work * fix license check * make sure it starts with this line * someone didnt commit * fix double analytics bug and builds * lint --- app/metro.config.cjs | 4 ++ .../buttons/HeldPrimaryButtonProveScreen.tsx | 3 +- app/src/hooks/useAppUpdates.ts | 14 +++--- app/src/hooks/useAppUpdates.web.ts | 9 ++-- app/src/hooks/useConnectionModal.ts | 3 +- app/src/providers/authProvider.tsx | 3 +- app/src/providers/authProvider.web.tsx | 3 +- .../notificationTrackingProvider.tsx | 3 +- app/src/providers/selfClientProvider.tsx | 11 ++++- .../aesop/PassportOnboardingScreen.tsx | 3 +- app/src/screens/dev/MockDataScreen.tsx | 6 +-- .../screens/dev/MockDataScreenDeepLink.tsx | 2 +- app/src/screens/home/DisclaimerScreen.tsx | 3 +- app/src/screens/home/HomeScreen.tsx | 12 +++--- app/src/screens/misc/LaunchScreen.tsx | 3 +- .../screens/passport/PassportCameraScreen.tsx | 2 +- .../passport/PassportNFCScanScreen.tsx | 7 ++- .../passport/PassportNFCScanScreen.web.tsx | 3 +- .../passport/PassportOnboardingScreen.tsx | 3 +- .../passport/UnsupportedPassportScreen.tsx | 3 +- .../screens/prove/ConfirmBelongingScreen.tsx | 11 +++-- .../prove/ProofRequestStatusScreen.tsx | 9 ++-- app/src/screens/prove/ProveScreen.tsx | 7 ++- app/src/screens/prove/ViewFinderScreen.tsx | 8 ++-- .../recovery/AccountRecoveryChoiceScreen.tsx | 10 ++--- .../recovery/AccountRecoveryScreen.tsx | 3 +- .../recovery/AccountVerifiedSuccessScreen.tsx | 3 +- .../recovery/RecoverWithPhraseScreen.tsx | 7 +-- .../screens/settings/CloudBackupScreen.tsx | 10 +++-- .../settings/ManageDocumentsScreen.tsx | 20 +++++---- .../settings/PassportDataInfoScreen.tsx | 9 ++-- app/src/utils/analytics.ts | 43 ++----------------- app/src/utils/proving/provingMachine.ts | 5 ++- app/src/utils/proving/validateDocument.ts | 2 +- ...Updates.test.ts => useAppUpdates.test.tsx} | 10 ++++- app/tsconfig.json | 3 ++ packages/mobile-sdk-alpha/package.json | 7 ++- .../mobile-sdk-alpha/scripts/postBuild.mjs | 1 + .../mobile-sdk-alpha/scripts/shimConfigs.js | 5 ++- packages/mobile-sdk-alpha/src/client.ts | 9 ++++ .../src/constants}/analytics.ts | 9 ++-- packages/mobile-sdk-alpha/src/context.tsx | 6 +-- packages/mobile-sdk-alpha/src/index.ts | 2 + packages/mobile-sdk-alpha/src/types/public.ts | 33 ++++++++++++++ .../mobile-sdk-alpha/tests/client.test.ts | 14 ++++++ packages/mobile-sdk-alpha/tsup.config.ts | 22 +++++----- scripts/check-duplicate-headers.cjs | 2 +- yarn.lock | 8 ---- 48 files changed, 221 insertions(+), 157 deletions(-) rename app/tests/src/hooks/{useAppUpdates.test.ts => useAppUpdates.test.tsx} (83%) rename {app/src/consts => packages/mobile-sdk-alpha/src/constants}/analytics.ts (96%) diff --git a/app/metro.config.cjs b/app/metro.config.cjs index 7ef1005d5..b97935f76 100644 --- a/app/metro.config.cjs +++ b/app/metro.config.cjs @@ -21,6 +21,10 @@ const extraNodeModules = { '@': path.join(__dirname, 'src'), '@selfxyz/common': path.resolve(commonPath, 'dist'), '@selfxyz/mobile-sdk-alpha': path.resolve(sdkAlphaPath, 'dist'), + '@selfxyz/mobile-sdk-alpha/constants/analytics': path.resolve( + sdkAlphaPath, + 'dist/esm/constants/analytics.js', + ), // Main exports '@selfxyz/common/utils': path.resolve( commonPath, diff --git a/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx b/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx index 900aaf0c5..ba5e4f311 100644 --- a/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx +++ b/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx @@ -7,9 +7,10 @@ import { ActivityIndicator, View } from 'react-native'; import { assign, createMachine } from 'xstate'; import { useMachine } from '@xstate/react'; +import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { HeldPrimaryButton } from '@/components/buttons/PrimaryButtonLongHold'; import Description from '@/components/typography/Description'; -import { ProofEvents } from '@/consts/analytics'; import { black } from '@/utils/colors'; interface HeldPrimaryButtonProveScreenProps { diff --git a/app/src/hooks/useAppUpdates.ts b/app/src/hooks/useAppUpdates.ts index bcc5e63a3..976b517fa 100644 --- a/app/src/hooks/useAppUpdates.ts +++ b/app/src/hooks/useAppUpdates.ts @@ -7,16 +7,16 @@ import { Linking } from 'react-native'; import { checkVersion } from 'react-native-check-version'; import { useNavigation } from '@react-navigation/native'; -import { AppEvents } from '@/consts/analytics'; -import analytics from '@/utils/analytics'; -import { registerModalCallbacks } from '@/utils/modalCallbackRegistry'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { AppEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; -const { trackEvent } = analytics(); +import { registerModalCallbacks } from '@/utils/modalCallbackRegistry'; export const useAppUpdates = (): [boolean, () => void, boolean] => { const navigation = useNavigation(); const [newVersionUrl, setNewVersionUrl] = useState(null); const [isModalDismissed, setIsModalDismissed] = useState(false); + const selfClient = useSelfClient(); useEffect(() => { checkVersion().then(version => { @@ -30,13 +30,13 @@ export const useAppUpdates = (): [boolean, () => void, boolean] => { const callbackId = registerModalCallbacks({ onButtonPress: async () => { if (newVersionUrl !== null) { - trackEvent(AppEvents.UPDATE_STARTED); + selfClient.trackEvent(AppEvents.UPDATE_STARTED); await Linking.openURL(newVersionUrl); } }, onModalDismiss: () => { setIsModalDismissed(true); - trackEvent(AppEvents.UPDATE_MODAL_CLOSED); + selfClient.trackEvent(AppEvents.UPDATE_MODAL_CLOSED); }, }); @@ -47,7 +47,7 @@ export const useAppUpdates = (): [boolean, () => void, boolean] => { buttonText: 'Update and restart', callbackId, }); - trackEvent(AppEvents.UPDATE_MODAL_OPENED); + selfClient.trackEvent(AppEvents.UPDATE_MODAL_OPENED); }; return [newVersionUrl !== null, showAppUpdateModal, isModalDismissed]; diff --git a/app/src/hooks/useAppUpdates.web.ts b/app/src/hooks/useAppUpdates.web.ts index 1baad2aea..a11d14963 100644 --- a/app/src/hooks/useAppUpdates.web.ts +++ b/app/src/hooks/useAppUpdates.web.ts @@ -5,16 +5,15 @@ import { useState } from 'react'; import { useNavigation } from '@react-navigation/native'; -import { AppEvents } from '@/consts/analytics'; -import analytics from '@/utils/analytics'; -import { registerModalCallbacks } from '@/utils/modalCallbackRegistry'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { AppEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; -const { trackEvent } = analytics(); +import { registerModalCallbacks } from '@/utils/modalCallbackRegistry'; export const useAppUpdates = (): [boolean, () => void, boolean] => { const navigation = useNavigation(); const [isModalDismissed, setIsModalDismissed] = useState(false); - + const { trackEvent } = useSelfClient(); const showAppUpdateModal = () => { const callbackId = registerModalCallbacks({ onButtonPress: async () => { diff --git a/app/src/hooks/useConnectionModal.ts b/app/src/hooks/useConnectionModal.ts index 699b02884..a36c1a8d3 100644 --- a/app/src/hooks/useConnectionModal.ts +++ b/app/src/hooks/useConnectionModal.ts @@ -5,7 +5,8 @@ import { useEffect } from 'react'; import { Linking, Platform } from 'react-native'; -import { SettingsEvents } from '@/consts/analytics'; +import { SettingsEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { useModal } from '@/hooks/useModal'; import { useNetInfo } from '@/hooks/useNetInfo'; import { navigationRef } from '@/navigation'; diff --git a/app/src/providers/authProvider.tsx b/app/src/providers/authProvider.tsx index 8e3ccbfde..810f302fe 100644 --- a/app/src/providers/authProvider.tsx +++ b/app/src/providers/authProvider.tsx @@ -14,7 +14,8 @@ import React, { import ReactNativeBiometrics from 'react-native-biometrics'; import Keychain from 'react-native-keychain'; -import { AuthEvents } from '@/consts/analytics'; +import { AuthEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { useSettingStore } from '@/stores/settingStore'; import type { Mnemonic } from '@/types/mnemonic'; import analytics from '@/utils/analytics'; diff --git a/app/src/providers/authProvider.web.tsx b/app/src/providers/authProvider.web.tsx index 8aaa00757..ebdee4dd5 100644 --- a/app/src/providers/authProvider.web.tsx +++ b/app/src/providers/authProvider.web.tsx @@ -16,7 +16,8 @@ import React, { useState, } from 'react'; -import { AuthEvents } from '@/consts/analytics'; +import { AuthEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import type { Mnemonic } from '@/types/mnemonic'; import analytics from '@/utils/analytics'; diff --git a/app/src/providers/notificationTrackingProvider.tsx b/app/src/providers/notificationTrackingProvider.tsx index a3fa827a7..31383d715 100644 --- a/app/src/providers/notificationTrackingProvider.tsx +++ b/app/src/providers/notificationTrackingProvider.tsx @@ -6,7 +6,8 @@ import type { PropsWithChildren } from 'react'; import React, { useEffect } from 'react'; import messaging from '@react-native-firebase/messaging'; -import { NotificationEvents } from '@/consts/analytics'; +import { NotificationEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import analytics from '@/utils/analytics'; const { trackEvent } = analytics(); diff --git a/app/src/providers/selfClientProvider.tsx b/app/src/providers/selfClientProvider.tsx index ea65aa8a3..f1e5cd0dd 100644 --- a/app/src/providers/selfClientProvider.tsx +++ b/app/src/providers/selfClientProvider.tsx @@ -5,10 +5,14 @@ import { type PropsWithChildren, useMemo } from 'react'; import { + Adapters, SelfClientProvider as SDKSelfClientProvider, webScannerShim, type WsConn, } from '@selfxyz/mobile-sdk-alpha'; +import { TrackEventParams } from '@selfxyz/mobile-sdk-alpha'; + +import analytics from '@/utils/analytics'; /** * Provides a configured Self SDK client instance to all descendants. @@ -20,7 +24,7 @@ import { */ export const SelfClientProvider = ({ children }: PropsWithChildren) => { const config = useMemo(() => ({}), []); - const adapters = useMemo( + const adapters: Partial = useMemo( () => ({ scanner: webScannerShim, network: { @@ -71,6 +75,11 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { ); }, }, + analytics: { + trackEvent: (event: string, data?: TrackEventParams) => { + analytics().trackEvent(event, data); + }, + }, }), [], ); diff --git a/app/src/screens/aesop/PassportOnboardingScreen.tsx b/app/src/screens/aesop/PassportOnboardingScreen.tsx index 5d8875a6b..c33381090 100644 --- a/app/src/screens/aesop/PassportOnboardingScreen.tsx +++ b/app/src/screens/aesop/PassportOnboardingScreen.tsx @@ -7,6 +7,8 @@ import React, { useEffect, useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import { SystemBars } from 'react-native-edge-to-edge'; +import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import passportOnboardingAnimation from '@/assets/animations/passport_onboarding.json'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; @@ -15,7 +17,6 @@ import TextsContainer from '@/components/TextsContainer'; import Additional from '@/components/typography/Additional'; import Description from '@/components/typography/Description'; import { DescriptionTitle } from '@/components/typography/DescriptionTitle'; -import { PassportEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import Scan from '@/images/icons/passport_camera_scan.svg'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; diff --git a/app/src/screens/dev/MockDataScreen.tsx b/app/src/screens/dev/MockDataScreen.tsx index 6355335d6..1c4e2639f 100644 --- a/app/src/screens/dev/MockDataScreen.tsx +++ b/app/src/screens/dev/MockDataScreen.tsx @@ -30,11 +30,12 @@ import { genMockIdDoc, initPassportDataParsing, } from '@selfxyz/common/utils/passports'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { MockDataEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import ButtonsContainer from '@/components/ButtonsContainer'; import { Caption } from '@/components/typography/Caption'; -import { MockDataEvents } from '@/consts/analytics'; import SelfDevCard from '@/images/card-dev.svg'; import IdIcon from '@/images/icons/id_icon.svg'; import NoteIcon from '@/images/icons/note.svg'; @@ -56,8 +57,6 @@ import { extraYPadding } from '@/utils/constants'; import { dinot, plexMono } from '@/utils/fonts'; import { buttonTap, selectionChange } from '@/utils/haptic'; -const { trackEvent } = analytics(); - const documentTypes = { mock_passport: 'Passport', mock_id_card: 'ID Card', @@ -246,6 +245,7 @@ const FormSection: React.FC = ({ }; const MockDataScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const navigation = useNavigation(); const [age, setAge] = useState(21); const [expiryYears, setExpiryYears] = useState(5); diff --git a/app/src/screens/dev/MockDataScreenDeepLink.tsx b/app/src/screens/dev/MockDataScreenDeepLink.tsx index 6654da0dd..ec5ba42d1 100644 --- a/app/src/screens/dev/MockDataScreenDeepLink.tsx +++ b/app/src/screens/dev/MockDataScreenDeepLink.tsx @@ -13,13 +13,13 @@ import { useNavigation } from '@react-navigation/native'; import { countryCodes } from '@selfxyz/common/constants'; import type { IdDocInput } from '@selfxyz/common/utils'; import { genMockIdDocAndInitDataParsing } from '@selfxyz/common/utils/passports'; +import { MockDataEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import ButtonsContainer from '@/components/ButtonsContainer'; import { BodyText } from '@/components/typography/BodyText'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { MockDataEvents } from '@/consts/analytics'; import { storePassportData } from '@/providers/passportDataProvider'; import useUserStore from '@/stores/userStore'; import { black, borderColor, white } from '@/utils/colors'; diff --git a/app/src/screens/home/DisclaimerScreen.tsx b/app/src/screens/home/DisclaimerScreen.tsx index 5c82d0248..741ea2c89 100644 --- a/app/src/screens/home/DisclaimerScreen.tsx +++ b/app/src/screens/home/DisclaimerScreen.tsx @@ -8,11 +8,12 @@ import { StyleSheet } from 'react-native'; import { YStack } from 'tamagui'; import { useNavigation } from '@react-navigation/native'; +import { AppEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import warningAnimation from '@/assets/animations/warning.json'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import Caution from '@/components/typography/Caution'; import { SubHeader } from '@/components/typography/SubHeader'; -import { AppEvents } from '@/consts/analytics'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { useSettingStore } from '@/stores/settingStore'; import { black, white } from '@/utils/colors'; diff --git a/app/src/screens/home/HomeScreen.tsx b/app/src/screens/home/HomeScreen.tsx index 688b874fd..47e8d468b 100644 --- a/app/src/screens/home/HomeScreen.tsx +++ b/app/src/screens/home/HomeScreen.tsx @@ -11,10 +11,12 @@ import { usePreventRemove, } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { pressedStyle } from '@/components/buttons/pressedStyle'; import { BodyText } from '@/components/typography/BodyText'; import { Caption } from '@/components/typography/Caption'; -import { ProofEvents } from '@/consts/analytics'; import { useAppUpdates } from '@/hooks/useAppUpdates'; import useConnectionModal from '@/hooks/useConnectionModal'; import useHapticNavigation from '@/hooks/useHapticNavigation'; @@ -23,7 +25,6 @@ import ScanIcon from '@/images/icons/qr_scan.svg'; import WarnIcon from '@/images/icons/warning.svg'; import { usePassport } from '@/providers/passportDataProvider'; import { useSettingStore } from '@/stores/settingStore'; -import analytics from '@/utils/analytics'; import { amber500, black, neutral700, slate800, white } from '@/utils/colors'; import { extraYPadding } from '@/utils/constants'; @@ -38,9 +39,8 @@ const ScanButton = styled(Button, { justifyContent: 'center', }); -const { trackEvent } = analytics(); - const HomeScreen: React.FC = () => { + const selfClient = useSelfClient(); useConnectionModal(); const navigation = useNavigation(); const { getAllDocuments } = usePassport(); @@ -72,12 +72,12 @@ const HomeScreen: React.FC = () => { const goToQRCodeViewFinder = useHapticNavigation('QRCodeViewFinder'); const onScanButtonPress = useCallback(() => { - trackEvent(ProofEvents.QR_SCAN_REQUESTED, { + selfClient.trackEvent(ProofEvents.QR_SCAN_REQUESTED, { from: 'Home', }); goToQRCodeViewFinder(); - }, [goToQRCodeViewFinder]); + }, [goToQRCodeViewFinder, selfClient]); // Prevents back navigation usePreventRemove(true, () => {}); diff --git a/app/src/screens/misc/LaunchScreen.tsx b/app/src/screens/misc/LaunchScreen.tsx index 7ed43a209..0e11eba22 100644 --- a/app/src/screens/misc/LaunchScreen.tsx +++ b/app/src/screens/misc/LaunchScreen.tsx @@ -8,10 +8,11 @@ import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Anchor, Text, YStack } from 'tamagui'; +import { AppEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import AbstractButton from '@/components/buttons/AbstractButton'; import { BodyText } from '@/components/typography/BodyText'; import { Caption } from '@/components/typography/Caption'; -import { AppEvents } from '@/consts/analytics'; import { privacyUrl, supportedBiometricIdsUrl, termsUrl } from '@/consts/links'; import useConnectionModal from '@/hooks/useConnectionModal'; import useHapticNavigation from '@/hooks/useHapticNavigation'; diff --git a/app/src/screens/passport/PassportCameraScreen.tsx b/app/src/screens/passport/PassportCameraScreen.tsx index 52a4fcd41..dc6912c89 100644 --- a/app/src/screens/passport/PassportCameraScreen.tsx +++ b/app/src/screens/passport/PassportCameraScreen.tsx @@ -9,6 +9,7 @@ import { View, XStack, YStack } from 'tamagui'; import { useIsFocused, useNavigation } from '@react-navigation/native'; import { formatDateToYYMMDD } from '@selfxyz/mobile-sdk-alpha'; +import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import passportScanAnimation from '@/assets/animations/passport_scan.json'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; @@ -17,7 +18,6 @@ import { PassportCamera } from '@/components/native/PassportCamera'; import Additional from '@/components/typography/Additional'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { PassportEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import Scan from '@/images/icons/passport_camera_scan.svg'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; diff --git a/app/src/screens/passport/PassportNFCScanScreen.tsx b/app/src/screens/passport/PassportNFCScanScreen.tsx index c6267af7e..7b4242418 100644 --- a/app/src/screens/passport/PassportNFCScanScreen.tsx +++ b/app/src/screens/passport/PassportNFCScanScreen.tsx @@ -25,6 +25,8 @@ import { CircleHelp } from '@tamagui/lucide-icons'; import type { PassportData } from '@selfxyz/common/types'; import { getSKIPEM } from '@selfxyz/common/utils/csca'; import { initPassportDataParsing } from '@selfxyz/common/utils/passports'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import passportVerifyAnimation from '@/assets/animations/passport_verify.json'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; @@ -33,7 +35,6 @@ import ButtonsContainer from '@/components/ButtonsContainer'; import TextsContainer from '@/components/TextsContainer'; import { BodyText } from '@/components/typography/BodyText'; import { Title } from '@/components/typography/Title'; -import { PassportEvents } from '@/consts/analytics'; import { useFeedbackAutoHide } from '@/hooks/useFeedbackAutoHide'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import NFC_IMAGE from '@/images/nfc.png'; @@ -41,7 +42,6 @@ import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { useFeedback } from '@/providers/feedbackProvider'; import { storePassportData } from '@/providers/passportDataProvider'; import useUserStore from '@/stores/userStore'; -import analytics from '@/utils/analytics'; import { black, slate100, slate400, slate500, white } from '@/utils/colors'; import { sendFeedbackEmail } from '@/utils/email'; import { dinot } from '@/utils/fonts'; @@ -55,8 +55,6 @@ import { parseScanResponse, scan } from '@/utils/nfcScanner'; import { hasAnyValidRegisteredDocument } from '@/utils/proving/validateDocument'; import { sanitizeErrorMessage } from '@/utils/utils'; -const { trackEvent } = analytics(); - const emitter = Platform.OS === 'android' ? new NativeEventEmitter(NativeModules.nativeModule) @@ -77,6 +75,7 @@ type PassportNFCScanRoute = RouteProp< >; const PassportNFCScanScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const navigation = useNavigation(); const route = useRoute(); const { showModal } = useFeedback(); diff --git a/app/src/screens/passport/PassportNFCScanScreen.web.tsx b/app/src/screens/passport/PassportNFCScanScreen.web.tsx index 0e17c66a3..596bd27de 100644 --- a/app/src/screens/passport/PassportNFCScanScreen.web.tsx +++ b/app/src/screens/passport/PassportNFCScanScreen.web.tsx @@ -5,12 +5,13 @@ import React from 'react'; import { Image } from 'tamagui'; +import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import ButtonsContainer from '@/components/ButtonsContainer'; import TextsContainer from '@/components/TextsContainer'; import { BodyText } from '@/components/typography/BodyText'; import { Title } from '@/components/typography/Title'; -import { PassportEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import NFC_IMAGE from '@/images/nfc.png'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; diff --git a/app/src/screens/passport/PassportOnboardingScreen.tsx b/app/src/screens/passport/PassportOnboardingScreen.tsx index c5f7fe527..f94bd7b8e 100644 --- a/app/src/screens/passport/PassportOnboardingScreen.tsx +++ b/app/src/screens/passport/PassportOnboardingScreen.tsx @@ -8,6 +8,8 @@ import { StyleSheet } from 'react-native'; import { SystemBars } from 'react-native-edge-to-edge'; import { useNavigation } from '@react-navigation/native'; +import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import passportOnboardingAnimation from '@/assets/animations/passport_onboarding.json'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; @@ -16,7 +18,6 @@ import TextsContainer from '@/components/TextsContainer'; import Additional from '@/components/typography/Additional'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { PassportEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { black, slate100, white } from '@/utils/colors'; diff --git a/app/src/screens/passport/UnsupportedPassportScreen.tsx b/app/src/screens/passport/UnsupportedPassportScreen.tsx index 4a3e81b81..826a7dc68 100644 --- a/app/src/screens/passport/UnsupportedPassportScreen.tsx +++ b/app/src/screens/passport/UnsupportedPassportScreen.tsx @@ -11,14 +11,13 @@ import type { RouteProp } from '@react-navigation/native'; import { countryCodes } from '@selfxyz/common/constants'; import type { PassportData } from '@selfxyz/common/types'; +import { PassportEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import { BodyText } from '@/components/typography/BodyText'; import { Title } from '@/components/typography/Title'; -import { PassportEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; -import LogoSvg from '@/images/logo.svg'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import analytics from '@/utils/analytics'; import { black, slate500, white } from '@/utils/colors'; diff --git a/app/src/screens/prove/ConfirmBelongingScreen.tsx b/app/src/screens/prove/ConfirmBelongingScreen.tsx index ca023cbdb..6eb8f2924 100644 --- a/app/src/screens/prove/ConfirmBelongingScreen.tsx +++ b/app/src/screens/prove/ConfirmBelongingScreen.tsx @@ -8,15 +8,19 @@ import { ActivityIndicator, View } from 'react-native'; import type { StaticScreenProps } from '@react-navigation/native'; import { usePreventRemove } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { + PassportEvents, + ProofEvents, +} from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import successAnimation from '@/assets/animations/loading/success.json'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { PassportEvents, ProofEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { styles } from '@/screens/prove/ProofRequestStatusScreen'; -import analytics from '@/utils/analytics'; import { black, white } from '@/utils/colors'; import { notificationSuccess } from '@/utils/haptic'; import { @@ -27,9 +31,8 @@ import { useProvingStore } from '@/utils/proving/provingMachine'; type ConfirmBelongingScreenProps = StaticScreenProps>; -const { trackEvent } = analytics(); - const ConfirmBelongingScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const navigate = useHapticNavigation('LoadingScreen', { params: {}, }); diff --git a/app/src/screens/prove/ProofRequestStatusScreen.tsx b/app/src/screens/prove/ProofRequestStatusScreen.tsx index 1f28a74b9..df9f91e81 100644 --- a/app/src/screens/prove/ProofRequestStatusScreen.tsx +++ b/app/src/screens/prove/ProofRequestStatusScreen.tsx @@ -9,6 +9,9 @@ import { SystemBars } from 'react-native-edge-to-edge'; import { ScrollView, Spinner } from 'tamagui'; import { useIsFocused } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import loadingAnimation from '@/assets/animations/loading/misc.json'; import failAnimation from '@/assets/animations/proof_failed.json'; import succesAnimation from '@/assets/animations/proof_success.json'; @@ -17,13 +20,11 @@ import { BodyText } from '@/components/typography/BodyText'; import Description from '@/components/typography/Description'; import { typography } from '@/components/typography/styles'; import { Title } from '@/components/typography/Title'; -import { ProofEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { ProofStatus } from '@/stores/proof-types'; import { useProofHistoryStore } from '@/stores/proofHistoryStore'; import { useSelfAppStore } from '@/stores/selfAppStore'; -import analytics from '@/utils/analytics'; import { black, white } from '@/utils/colors'; import { buttonTap, @@ -32,9 +33,8 @@ import { } from '@/utils/haptic'; import { useProvingStore } from '@/utils/proving/provingMachine'; -const { trackEvent } = analytics(); - const SuccessScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const { selfApp, cleanSelfApp } = useSelfAppStore(); const appName = selfApp?.appName; const goHome = useHapticNavigation('Home'); @@ -121,6 +121,7 @@ const SuccessScreen: React.FC = () => { setAnimationSource(loadingAnimation); } }, [ + trackEvent, currentState, isFocused, appName, diff --git a/app/src/screens/prove/ProveScreen.tsx b/app/src/screens/prove/ProveScreen.tsx index 21fa7ad6a..87b8ce623 100644 --- a/app/src/screens/prove/ProveScreen.tsx +++ b/app/src/screens/prove/ProveScreen.tsx @@ -22,27 +22,26 @@ import { Eye, EyeOff } from '@tamagui/lucide-icons'; import type { SelfAppDisclosureConfig } from '@selfxyz/common/utils/appType'; import { formatEndpoint } from '@selfxyz/common/utils/scope'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import miscAnimation from '@/assets/animations/loading/misc.json'; import { HeldPrimaryButtonProveScreen } from '@/components/buttons/HeldPrimaryButtonProveScreen'; import Disclosures from '@/components/Disclosures'; import { BodyText } from '@/components/typography/BodyText'; import { Caption } from '@/components/typography/Caption'; -import { ProofEvents } from '@/consts/analytics'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { setDefaultDocumentTypeIfNeeded } from '@/providers/passportDataProvider'; import { ProofStatus } from '@/stores/proof-types'; import { useProofHistoryStore } from '@/stores/proofHistoryStore'; import { useSelfAppStore } from '@/stores/selfAppStore'; -import analytics from '@/utils/analytics'; import { black, slate300, white } from '@/utils/colors'; import { formatUserId } from '@/utils/formatUserId'; import { buttonTap } from '@/utils/haptic'; import { useProvingStore } from '@/utils/proving/provingMachine'; -const { trackEvent } = analytics(); - const ProveScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const { navigate } = useNavigation(); const isFocused = useIsFocused(); const selectedApp = useSelfAppStore(state => state.selfApp); diff --git a/app/src/screens/prove/ViewFinderScreen.tsx b/app/src/screens/prove/ViewFinderScreen.tsx index 0cf2156dc..36a894115 100644 --- a/app/src/screens/prove/ViewFinderScreen.tsx +++ b/app/src/screens/prove/ViewFinderScreen.tsx @@ -12,6 +12,9 @@ import { useNavigation, } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import qrScanAnimation from '@/assets/animations/qr_scan.json'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import type { QRCodeScannerViewProps } from '@/components/native/QRCodeScanner'; @@ -19,19 +22,16 @@ import { QRCodeScannerView } from '@/components/native/QRCodeScanner'; import Additional from '@/components/typography/Additional'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { ProofEvents } from '@/consts/analytics'; import useConnectionModal from '@/hooks/useConnectionModal'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import QRScan from '@/images/icons/qr_code.svg'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { useSelfAppStore } from '@/stores/selfAppStore'; -import analytics from '@/utils/analytics'; import { black, slate800, white } from '@/utils/colors'; import { parseAndValidateUrlParams } from '@/utils/deeplinks'; -const { trackEvent } = analytics(); - const QRCodeViewFinderScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const { visible: connectionModalVisible } = useConnectionModal(); const navigation = useNavigation(); const isFocused = useIsFocused(); diff --git a/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx b/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx index e9357d806..fecd34a94 100644 --- a/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx +++ b/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx @@ -6,12 +6,14 @@ import React, { useCallback, useState } from 'react'; import { Separator, View, XStack, YStack } from 'tamagui'; import { useNavigation } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import { Caption } from '@/components/typography/Caption'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { BackupEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import Keyboard from '@/images/icons/keyboard.svg'; import RestoreAccountSvg from '@/images/icons/restore_account.svg'; @@ -22,14 +24,12 @@ import { reStorePassportDataWithRightCSCA, } from '@/providers/passportDataProvider'; import { useSettingStore } from '@/stores/settingStore'; -import analytics from '@/utils/analytics'; import { STORAGE_NAME, useBackupMnemonic } from '@/utils/cloudBackup'; import { black, slate500, slate600, white } from '@/utils/colors'; import { isUserRegisteredWithAlternativeCSCA } from '@/utils/proving/validateDocument'; -const { trackEvent } = analytics(); - const AccountRecoveryChoiceScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const { restoreAccountFromMnemonic } = useAuth(); const [restoring, setRestoring] = useState(false); const { cloudBackupEnabled, toggleCloudBackupEnabled, biometricsAvailable } = @@ -85,6 +85,7 @@ const AccountRecoveryChoiceScreen: React.FC = () => { throw new Error('Something wrong happened during cloud recovery'); } }, [ + trackEvent, download, restoreAccountFromMnemonic, cloudBackupEnabled, @@ -94,7 +95,6 @@ const AccountRecoveryChoiceScreen: React.FC = () => { ]); const handleManualRecoveryPress = useCallback(() => { - trackEvent(BackupEvents.MANUAL_RECOVERY_SELECTED); onEnterRecoveryPress(); }, [onEnterRecoveryPress]); diff --git a/app/src/screens/recovery/AccountRecoveryScreen.tsx b/app/src/screens/recovery/AccountRecoveryScreen.tsx index be38dcdb7..b151056f2 100644 --- a/app/src/screens/recovery/AccountRecoveryScreen.tsx +++ b/app/src/screens/recovery/AccountRecoveryScreen.tsx @@ -5,11 +5,12 @@ import React from 'react'; import { View, YStack } from 'tamagui'; +import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { BackupEvents } from '@/consts/analytics'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import RestoreAccountSvg from '@/images/icons/restore_account.svg'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; diff --git a/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx b/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx index bd4891394..4b5305ac6 100644 --- a/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx +++ b/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx @@ -7,11 +7,12 @@ import React from 'react'; import { YStack } from 'tamagui'; import { useNavigation } from '@react-navigation/native'; +import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import proofSuccessAnimation from '@/assets/animations/proof_success.json'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { BackupEvents } from '@/consts/analytics'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import { styles } from '@/screens/prove/ProofRequestStatusScreen'; import { black, white } from '@/utils/colors'; diff --git a/app/src/screens/recovery/RecoverWithPhraseScreen.tsx b/app/src/screens/recovery/RecoverWithPhraseScreen.tsx index 87cbfbea2..4c2b004a8 100644 --- a/app/src/screens/recovery/RecoverWithPhraseScreen.tsx +++ b/app/src/screens/recovery/RecoverWithPhraseScreen.tsx @@ -9,16 +9,17 @@ import { Text, TextArea, View, XStack, YStack } from 'tamagui'; import Clipboard from '@react-native-clipboard/clipboard'; import { useNavigation } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import Description from '@/components/typography/Description'; -import { BackupEvents } from '@/consts/analytics'; import Paste from '@/images/icons/paste.svg'; import { useAuth } from '@/providers/authProvider'; import { loadPassportDataAndSecret, reStorePassportDataWithRightCSCA, } from '@/providers/passportDataProvider'; -import analytics from '@/utils/analytics'; import { black, slate300, @@ -32,7 +33,7 @@ import { isUserRegisteredWithAlternativeCSCA } from '@/utils/proving/validateDoc const RecoverWithPhraseScreen: React.FC = () => { const navigation = useNavigation(); const { restoreAccountFromMnemonic } = useAuth(); - const { trackEvent } = analytics(); + const { trackEvent } = useSelfClient(); const [mnemonic, setMnemonic] = useState(); const [restoring, setRestoring] = useState(false); const onPaste = useCallback(async () => { diff --git a/app/src/screens/settings/CloudBackupScreen.tsx b/app/src/screens/settings/CloudBackupScreen.tsx index 92056c660..80a67b27e 100644 --- a/app/src/screens/settings/CloudBackupScreen.tsx +++ b/app/src/screens/settings/CloudBackupScreen.tsx @@ -7,26 +7,25 @@ import { YStack } from 'tamagui'; import type { StaticScreenProps } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import BackupDocumentationLink from '@/components/BackupDocumentationLink'; import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import { Caption } from '@/components/typography/Caption'; import Description from '@/components/typography/Description'; import { Title } from '@/components/typography/Title'; -import { BackupEvents } from '@/consts/analytics'; import { useModal } from '@/hooks/useModal'; import Cloud from '@/images/icons/logo_cloud_backup.svg'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import type { RootStackParamList } from '@/navigation'; import { useAuth } from '@/providers/authProvider'; import { useSettingStore } from '@/stores/settingStore'; -import analytics from '@/utils/analytics'; import { STORAGE_NAME, useBackupMnemonic } from '@/utils/cloudBackup'; import { black, white } from '@/utils/colors'; import { buttonTap, confirmTap } from '@/utils/haptic'; -const { trackEvent } = analytics(); - type NextScreen = keyof Pick; type CloudBackupScreenProps = StaticScreenProps< @@ -39,6 +38,7 @@ type CloudBackupScreenProps = StaticScreenProps< const CloudBackupScreen: React.FC = ({ route: { params }, }) => { + const { trackEvent } = useSelfClient(); const { getOrCreateMnemonic, loginWithBiometrics } = useAuth(); const { cloudBackupEnabled, toggleCloudBackupEnabled, biometricsAvailable } = useSettingStore(); @@ -95,6 +95,7 @@ const CloudBackupScreen: React.FC = ({ getOrCreateMnemonic, upload, toggleCloudBackupEnabled, + trackEvent, ]); const disableCloudBackups = useCallback(() => { @@ -174,6 +175,7 @@ function BottomButton({ cloudBackupEnabled: boolean; nextScreen?: NextScreen; }) { + const { trackEvent } = useSelfClient(); const navigation = useNavigation(); const goBack = () => { diff --git a/app/src/screens/settings/ManageDocumentsScreen.tsx b/app/src/screens/settings/ManageDocumentsScreen.tsx index 84d9ae1ea..e9f29295e 100644 --- a/app/src/screens/settings/ManageDocumentsScreen.tsx +++ b/app/src/screens/settings/ManageDocumentsScreen.tsx @@ -10,24 +10,24 @@ import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Check, Eraser } from '@tamagui/lucide-icons'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; + import { PrimaryButton } from '@/components/buttons/PrimaryButton'; import { SecondaryButton } from '@/components/buttons/SecondaryButton'; import ButtonsContainer from '@/components/ButtonsContainer'; -import { DocumentEvents } from '@/consts/analytics'; import type { RootStackParamList } from '@/navigation'; import type { DocumentCatalog, DocumentMetadata, } from '@/providers/passportDataProvider'; import { usePassport } from '@/providers/passportDataProvider'; -import analytics from '@/utils/analytics'; import { borderColor, textBlack, white } from '@/utils/colors'; import { extraYPadding } from '@/utils/constants'; import { impactLight } from '@/utils/haptic'; -const { trackEvent } = analytics(); - const PassportDataSelector = () => { + const selfClient = useSelfClient(); const { loadDocumentCatalog, getAllDocuments, @@ -48,14 +48,15 @@ const PassportDataSelector = () => { const docs = await getAllDocuments(); setDocumentCatalog(catalog); setAllDocuments(docs); - trackEvent(DocumentEvents.DOCUMENTS_FETCHED, { + selfClient.trackEvent(DocumentEvents.DOCUMENTS_FETCHED, { count: catalog.documents.length, }); if (catalog.documents.length === 0) { - trackEvent(DocumentEvents.NO_DOCUMENTS_FOUND); + selfClient.trackEvent(DocumentEvents.NO_DOCUMENTS_FOUND); } setLoading(false); }, [ + selfClient, loadDocumentCatalog, getAllDocuments, setDocumentCatalog, @@ -73,13 +74,13 @@ const PassportDataSelector = () => { const docs = await getAllDocuments(); setDocumentCatalog(catalog); setAllDocuments(docs); - trackEvent(DocumentEvents.DOCUMENT_SELECTED); + selfClient.trackEvent(DocumentEvents.DOCUMENT_SELECTED); }; const handleDeleteSpecific = async (documentId: string) => { setLoading(true); await deleteDocument(documentId); - trackEvent(DocumentEvents.DOCUMENT_DELETED); + selfClient.trackEvent(DocumentEvents.DOCUMENT_DELETED); await loadPassportDataInfo(); }; @@ -269,10 +270,11 @@ const ManageDocumentsScreen: React.FC = () => { const navigation = useNavigation>(); const { bottom } = useSafeAreaInsets(); + const { trackEvent } = useSelfClient(); useEffect(() => { trackEvent(DocumentEvents.MANAGE_SCREEN_OPENED); - }, []); + }, [trackEvent]); const handleScanDocument = () => { impactLight(); diff --git a/app/src/screens/settings/PassportDataInfoScreen.tsx b/app/src/screens/settings/PassportDataInfoScreen.tsx index 0e04be964..1b3968b6e 100644 --- a/app/src/screens/settings/PassportDataInfoScreen.tsx +++ b/app/src/screens/settings/PassportDataInfoScreen.tsx @@ -8,16 +8,14 @@ import { ScrollView, Separator, XStack, YStack } from 'tamagui'; import { useFocusEffect } from '@react-navigation/native'; import type { PassportMetadata } from '@selfxyz/common/types'; +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { Caption } from '@/components/typography/Caption'; -import { DocumentEvents } from '@/consts/analytics'; import { usePassport } from '@/providers/passportDataProvider'; -import analytics from '@/utils/analytics'; import { black, slate200, white } from '@/utils/colors'; import { extraYPadding } from '@/utils/constants'; -const { trackEvent } = analytics(); - // TODO clarify if we need more/less keys to be displayed const dataKeysToLabels: Record< keyof Omit, @@ -62,6 +60,7 @@ const InfoRow: React.FC<{ ); const PassportDataInfoScreen: React.FC = () => { + const { trackEvent } = useSelfClient(); const { getData } = usePassport(); const [metadata, setMetadata] = useState(null); const { bottom } = useSafeAreaInsets(); @@ -80,7 +79,7 @@ const PassportDataInfoScreen: React.FC = () => { setMetadata(result.data.passportMetadata!); trackEvent(DocumentEvents.PASSPORT_METADATA_LOADED); - }, [metadata, getData]); + }, [metadata, getData, trackEvent]); useFocusEffect(() => { trackEvent(DocumentEvents.PASSPORT_INFO_OPENED); diff --git a/app/src/utils/analytics.ts b/app/src/utils/analytics.ts index 9511c68a5..85ebb2d16 100644 --- a/app/src/utils/analytics.ts +++ b/app/src/utils/analytics.ts @@ -4,45 +4,10 @@ import type { JsonMap, JsonValue } from '@segment/analytics-react-native'; +import { TrackEventParams } from '@selfxyz/mobile-sdk-alpha'; + import { createSegmentClient } from '@/Segment'; -/** - * Generic reasons: - * - network_error: Network connectivity issues - * - user_cancelled: User cancelled the operation - * - permission_denied: Permission not granted - * - invalid_input: Invalid user input - * - timeout: Operation timed out - * - unknown_error: Unspecified error - * - * Auth specific: - * - invalid_credentials: Invalid login credentials - * - biometric_unavailable: Biometric authentication unavailable - * - invalid_mnemonic: Invalid mnemonic phrase - * - * Passport specific: - * - invalid_format: Invalid passport format - * - expired_passport: Passport is expired - * - scan_error: Error during scanning - * - nfc_error: NFC read error - * - * Proof specific: - * - verification_failed: Proof verification failed - * - session_expired: Session expired - * - missing_fields: Required fields missing - * - * Backup specific: - * - backup_not_found: Backup not found - * - cloud_service_unavailable: Cloud service unavailable - */ - -export interface EventParams { - reason?: string | null; - duration_seconds?: number; - attempt_count?: number; - [key: string]: unknown; -} - const segmentClient = createSegmentClient(); function coerceToJsonValue( @@ -102,7 +67,7 @@ function validateParams( ): JsonMap | undefined { if (!properties) return undefined; - const validatedProps = { ...properties } as EventParams; + const validatedProps = { ...properties }; // Ensure duration is formatted as a number with at most 2 decimal places if (validatedProps.duration_seconds !== undefined) { @@ -153,7 +118,7 @@ const analytics = () => { return { // Using LiteralCheck will allow constants but not plain string literals - trackEvent: (eventName: string, properties?: EventParams) => { + trackEvent: (eventName: string, properties?: TrackEventParams) => { _track('event', eventName, properties); }, trackScreenView: ( diff --git a/app/src/utils/proving/provingMachine.ts b/app/src/utils/proving/provingMachine.ts index 016881640..d4e3b2985 100644 --- a/app/src/utils/proving/provingMachine.ts +++ b/app/src/utils/proving/provingMachine.ts @@ -25,8 +25,11 @@ import { getPayload, getWSDbRelayerUrl, } from '@selfxyz/common/utils/proving'; +import { + PassportEvents, + ProofEvents, +} from '@selfxyz/mobile-sdk-alpha/constants/analytics'; -import { PassportEvents, ProofEvents } from '@/consts/analytics'; import { navigationRef } from '@/navigation'; import { unsafe_getPrivateKey } from '@/providers/authProvider'; import { diff --git a/app/src/utils/proving/validateDocument.ts b/app/src/utils/proving/validateDocument.ts index 0e8b6b65f..c26f3952a 100644 --- a/app/src/utils/proving/validateDocument.ts +++ b/app/src/utils/proving/validateDocument.ts @@ -24,8 +24,8 @@ import { import { getLeafDscTree } from '@selfxyz/common/utils/trees'; import type { PassportValidationCallbacks } from '@selfxyz/mobile-sdk-alpha'; import { isPassportDataValid } from '@selfxyz/mobile-sdk-alpha'; +import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; -import { DocumentEvents } from '@/consts/analytics'; import { getAllDocuments, loadDocumentCatalog, diff --git a/app/tests/src/hooks/useAppUpdates.test.ts b/app/tests/src/hooks/useAppUpdates.test.tsx similarity index 83% rename from app/tests/src/hooks/useAppUpdates.test.ts rename to app/tests/src/hooks/useAppUpdates.test.tsx index cf29242fc..6f2c73cac 100644 --- a/app/tests/src/hooks/useAppUpdates.test.ts +++ b/app/tests/src/hooks/useAppUpdates.test.tsx @@ -2,11 +2,13 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. +import { ReactNode } from 'react'; import { checkVersion } from 'react-native-check-version'; import { useNavigation } from '@react-navigation/native'; import { act, renderHook, waitFor } from '@testing-library/react-native'; import { useAppUpdates } from '@/hooks/useAppUpdates'; +import { SelfClientProvider } from '@/providers/selfClientProvider'; import { registerModalCallbacks } from '@/utils/modalCallbackRegistry'; jest.mock('@react-navigation/native', () => ({ @@ -28,6 +30,10 @@ jest.mock('@/utils/analytics', () => () => ({ const navigate = jest.fn(); (useNavigation as jest.Mock).mockReturnValue({ navigate }); +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); + describe('useAppUpdates', () => { beforeEach(() => { jest.clearAllMocks(); @@ -39,7 +45,7 @@ describe('useAppUpdates', () => { url: 'u', }); - const { result } = renderHook(() => useAppUpdates()); + const { result } = renderHook(() => useAppUpdates(), { wrapper }); // Wait for the async state update to complete await waitFor(() => { @@ -53,7 +59,7 @@ describe('useAppUpdates', () => { url: 'u', }); - const { result } = renderHook(() => useAppUpdates()); + const { result } = renderHook(() => useAppUpdates(), { wrapper }); // Wait for the async checkVersion to complete first await waitFor(() => { diff --git a/app/tsconfig.json b/app/tsconfig.json index cdd778df1..939933e2a 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -13,6 +13,9 @@ "../packages/mobile-sdk-alpha/src", "../packages/mobile-sdk-alpha/dist" ], + "@selfxyz/mobile-sdk-alpha/constants/analytics": [ + "../packages/mobile-sdk-alpha/dist/esm/constants/analytics.js" + ], "@/*": ["./src/*"] } }, diff --git a/packages/mobile-sdk-alpha/package.json b/packages/mobile-sdk-alpha/package.json index 2d973beee..30f6118e5 100644 --- a/packages/mobile-sdk-alpha/package.json +++ b/packages/mobile-sdk-alpha/package.json @@ -23,6 +23,11 @@ "types": "./dist/esm/browser.d.ts", "import": "./dist/esm/browser.js", "require": "./dist/cjs/browser.cjs" + }, + "./constants/analytics": { + "types": "./dist/esm/constants/analytics.d.ts", + "import": "./dist/esm/constants/analytics.js", + "require": "./dist/cjs/constants/analytics.cjs" } }, "main": "./dist/cjs/index.cjs", @@ -45,7 +50,7 @@ "test": "vitest run", "test:build": "yarn build && yarn test && yarn types && yarn lint", "typecheck": "tsc -p tsconfig.json --noEmit", - "types": "tsc -p tsconfig.json --noEmit", + "types": "yarn build", "validate:exports": "node ./scripts/validate-exports.mjs", "validate:pkg": "node ./scripts/verify-conditions.mjs" }, diff --git a/packages/mobile-sdk-alpha/scripts/postBuild.mjs b/packages/mobile-sdk-alpha/scripts/postBuild.mjs index a62b01c6d..14f653331 100644 --- a/packages/mobile-sdk-alpha/scripts/postBuild.mjs +++ b/packages/mobile-sdk-alpha/scripts/postBuild.mjs @@ -40,6 +40,7 @@ const distPackageJson = { exports: { '.': './esm/index.js', './browser': './esm/browser.js', + './constants/analytics': './esm/constants/analytics.js', }, }; try { diff --git a/packages/mobile-sdk-alpha/scripts/shimConfigs.js b/packages/mobile-sdk-alpha/scripts/shimConfigs.js index af6a414c7..dbd491c03 100644 --- a/packages/mobile-sdk-alpha/scripts/shimConfigs.js +++ b/packages/mobile-sdk-alpha/scripts/shimConfigs.js @@ -3,4 +3,7 @@ // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. // Shim configurations for Metro compatibility -export const shimConfigs = [{ shimPath: 'browser', targetPath: '../esm/browser.js', name: 'browser' }]; +export const shimConfigs = [ + { shimPath: 'browser', targetPath: '../esm/browser.js', name: 'browser' }, + { shimPath: 'constants/analytics', targetPath: '../../esm/constants/analytics.js', name: 'constants/analytics' }, +]; diff --git a/packages/mobile-sdk-alpha/src/client.ts b/packages/mobile-sdk-alpha/src/client.ts index a735a7812..511ab2605 100644 --- a/packages/mobile-sdk-alpha/src/client.ts +++ b/packages/mobile-sdk-alpha/src/client.ts @@ -23,6 +23,7 @@ import type { ValidationInput, ValidationResult, } from './types/public'; +import { TrackEventParams } from './types/public'; /** * Optional adapter implementations used when a consumer does not provide their @@ -118,9 +119,17 @@ export function createSelfClient({ config, adapters }: { config: Config; adapter }; } + async function trackEvent(event: string, payload?: TrackEventParams): Promise { + if (!adapters.analytics) { + return; + } + return adapters.analytics.trackEvent(event, payload); + } + return { scanDocument, validateDocument, + trackEvent, checkRegistration, registerDocument, generateProof, diff --git a/app/src/consts/analytics.ts b/packages/mobile-sdk-alpha/src/constants/analytics.ts similarity index 96% rename from app/src/consts/analytics.ts rename to packages/mobile-sdk-alpha/src/constants/analytics.ts index 9e9b6630d..4d81f5afc 100644 --- a/app/src/consts/analytics.ts +++ b/packages/mobile-sdk-alpha/src/constants/analytics.ts @@ -37,8 +37,7 @@ export const BackupEvents = { CLOUD_BACKUP_ENABLED_DONE: 'Backup: Cloud Backup Enabled Done', CLOUD_BACKUP_ENABLE_STARTED: 'Backup: Cloud Backup Enable Started', CLOUD_BACKUP_STARTED: 'Backup: Cloud Backup Started', - CLOUD_RESTORE_FAILED_PASSPORT_NOT_REGISTERED: - 'Backup: Cloud Restore Failed: Passport Not Registered', + CLOUD_RESTORE_FAILED_PASSPORT_NOT_REGISTERED: 'Backup: Cloud Restore Failed: Passport Not Registered', CLOUD_RESTORE_FAILED_UNKNOWN: 'Backup: Cloud Restore Failed: Unknown Error', CLOUD_RESTORE_SUCCESS: 'Backup: Cloud Restore Success', CREATE_NEW_ACCOUNT: 'Backup: Create New Account', @@ -77,10 +76,8 @@ export const MockDataEvents = { }; export const NotificationEvents = { - BACKGROUND_NOTIFICATION_OPENED: - 'Notification: Background Notification Opened', - COLD_START_NOTIFICATION_OPENED: - 'Notification: Cold Start Notification Opened', + BACKGROUND_NOTIFICATION_OPENED: 'Notification: Background Notification Opened', + COLD_START_NOTIFICATION_OPENED: 'Notification: Cold Start Notification Opened', }; export const PassportEvents = { diff --git a/packages/mobile-sdk-alpha/src/context.tsx b/packages/mobile-sdk-alpha/src/context.tsx index dcc8efeb1..90ced4824 100644 --- a/packages/mobile-sdk-alpha/src/context.tsx +++ b/packages/mobile-sdk-alpha/src/context.tsx @@ -52,7 +52,7 @@ export function SelfClientProvider({ config, adapters = {}, children }: PropsWit * @throws If used outside of a {@link SelfClientProvider}. */ export function useSelfClient(): SelfClient { - const ctx = useContext(SelfClientContext); - if (!ctx) throw new Error('useSelfClient must be used within a SelfClientProvider'); - return ctx; + const client = useContext(SelfClientContext); + if (!client) throw new Error('useSelfClient must be used within a SelfClientProvider'); + return client; } diff --git a/packages/mobile-sdk-alpha/src/index.ts b/packages/mobile-sdk-alpha/src/index.ts index 0331b829c..ab29c1e2c 100644 --- a/packages/mobile-sdk-alpha/src/index.ts +++ b/packages/mobile-sdk-alpha/src/index.ts @@ -5,6 +5,7 @@ // Types export type { Adapters, + AnalyticsAdapter, ClockAdapter, Config, CryptoAdapter, @@ -27,6 +28,7 @@ export type { ScannerAdapter, SelfClient, StorageAdapter, + TrackEventParams, Unsubscribe, ValidationInput, ValidationResult, diff --git a/packages/mobile-sdk-alpha/src/types/public.ts b/packages/mobile-sdk-alpha/src/types/public.ts index 920b97356..4cbbf14c3 100644 --- a/packages/mobile-sdk-alpha/src/types/public.ts +++ b/packages/mobile-sdk-alpha/src/types/public.ts @@ -32,6 +32,37 @@ export interface MRZInfo { validation: MRZValidation; } +/** * Generic reasons: + * - network_error: Network connectivity issues + * - user_cancelled: User cancelled the operation + * - permission_denied: Permission not granted + * - invalid_input: Invalid user input + * - timeout: Operation timed out + * - unknown_error: Unspecified error * * Auth specific: + * - invalid_credentials: Invalid login credentials + * - biometric_unavailable: Biometric authentication unavailable + * - invalid_mnemonic: Invalid mnemonic phrase * * Passport specific: + * - invalid_format: Invalid passport format + * - expired_passport: Passport is expired + * - scan_error: Error during scanning + * - nfc_error: NFC read error * * Proof specific: + * - verification_failed: Proof verification failed + * - session_expired: Session expired + * - missing_fields: Required fields missing * * Backup specific: + * - backup_not_found: Backup not found + * - cloud_service_unavailable: Cloud service unavailable + * */ +export interface TrackEventParams { + reason?: string | null; + duration_seconds?: number; + attempt_count?: number; + [key: string]: unknown; +} + +export interface AnalyticsAdapter { + trackEvent(event: string, payload?: TrackEventParams): void; +} + export interface ClockAdapter { now(): number; sleep(ms: number, signal?: AbortSignal): Promise; @@ -59,6 +90,7 @@ export interface Adapters { network: NetworkAdapter; clock: ClockAdapter; logger: LoggerAdapter; + analytics: AnalyticsAdapter; } export interface ProofHandle { @@ -130,6 +162,7 @@ export interface SelfClient { }, ): Promise; extractMRZInfo(mrz: string): MRZInfo; + trackEvent(event: string, payload?: TrackEventParams): void; on(event: E, cb: (payload: SDKEventMap[E]) => void): Unsubscribe; emit(event: E, payload: SDKEventMap[E]): void; } diff --git a/packages/mobile-sdk-alpha/tests/client.test.ts b/packages/mobile-sdk-alpha/tests/client.test.ts index 10981d602..71c05e547 100644 --- a/packages/mobile-sdk-alpha/tests/client.test.ts +++ b/packages/mobile-sdk-alpha/tests/client.test.ts @@ -97,6 +97,20 @@ describe('createSelfClient', () => { reason: 'SELF_REG_STATUS_STUB', }); }); + describe('when analytics adapter is given', () => { + it('calls that adapter for trackEvent', () => { + const trackEvent = vi.fn(); + const client = createSelfClient({ + config: {}, + adapters: { scanner, network, crypto, analytics: { trackEvent } }, + }); + + client.trackEvent('test_event'); + expect(trackEvent).toHaveBeenCalledWith('test_event', undefined); + client.trackEvent('another_event', { foo: 'bar' }); + expect(trackEvent).toHaveBeenCalledWith('another_event', { foo: 'bar' }); + }); + }); }); const scanner: ScannerAdapter = { diff --git a/packages/mobile-sdk-alpha/tsup.config.ts b/packages/mobile-sdk-alpha/tsup.config.ts index aa1c12994..a25761ee2 100644 --- a/packages/mobile-sdk-alpha/tsup.config.ts +++ b/packages/mobile-sdk-alpha/tsup.config.ts @@ -4,12 +4,17 @@ 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`; + +const entry = { + index: 'src/index.ts', + browser: 'src/browser.ts', + 'constants/analytics': 'src/constants/analytics.ts', +}; + export default defineConfig([ { - entry: { - index: 'src/index.ts', - browser: 'src/browser.ts', - }, + entry, format: ['esm'], dts: true, sourcemap: true, @@ -24,14 +29,11 @@ export default defineConfig([ options.legalComments = 'eof'; }, banner: { - js: `// 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`, + js: banner, }, }, { - entry: { - index: 'src/index.ts', - browser: 'src/browser.ts', - }, + entry, format: ['cjs'], dts: false, sourcemap: true, @@ -47,7 +49,7 @@ export default defineConfig([ options.legalComments = 'eof'; }, banner: { - js: `// 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`, + js: banner, }, }, ]); diff --git a/scripts/check-duplicate-headers.cjs b/scripts/check-duplicate-headers.cjs index 0134e3b94..630bd5d84 100644 --- a/scripts/check-duplicate-headers.cjs +++ b/scripts/check-duplicate-headers.cjs @@ -7,7 +7,7 @@ const fs = require('fs'); const path = require('path'); const { glob } = require('glob'); -const LICENSE_HEADER_PATTERN = /SPDX-License-Identifier:/; +const LICENSE_HEADER_PATTERN = /^\/\/\s*SPDX-FileCopyrightText:/; const EXTENSIONS = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx']; function checkFile(filePath) { diff --git a/yarn.lock b/yarn.lock index 5f34c1c52..015e3aeda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5234,7 +5234,6 @@ __metadata: react-native-logs: "npm:^5.3.0" react-native-nfc-manager: "npm:^3.15.1" react-native-passport-reader: "npm:^1.0.3" - react-native-round-flags: "npm:^1.0.4" react-native-safe-area-context: "npm:^5.5.1" react-native-screens: "npm:4.9.0" react-native-sqlite-storage: "npm:^6.0.1" @@ -22585,13 +22584,6 @@ __metadata: languageName: node linkType: hard -"react-native-round-flags@npm:^1.0.4": - version: 1.0.4 - resolution: "react-native-round-flags@npm:1.0.4" - checksum: 10c0/d09bf2fe9fd16aac3b7eba9034b30cb5a21c6dff9f9f46670cba3b393c0a8b8b37c06fde8292eb1ff492568e89619883f499091b689ddf1e556cf96e2e2ca03a - languageName: node - linkType: hard - "react-native-safe-area-context@npm:^5.5.1": version: 5.5.1 resolution: "react-native-safe-area-context@npm:5.5.1"