Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Buffer } from 'buffer';
import React from 'react';
import { YStack } from 'tamagui';

import ErrorBoundary from './src/components/ErrorBoundary';
import AppNavigation from './src/navigation';
import { initSentry, wrapWithSentry } from './src/Sentry';
import { AuthProvider } from './src/stores/authProvider';
Expand All @@ -16,15 +17,17 @@ global.Buffer = Buffer;

function App(): React.JSX.Element {
return (
<YStack f={1} h="100%" w="100%">
<AuthProvider>
<PassportProvider>
<DatabaseProvider>
<AppNavigation />
</DatabaseProvider>
</PassportProvider>
</AuthProvider>
</YStack>
<ErrorBoundary>
<YStack f={1} h="100%" w="100%">
<AuthProvider>
<PassportProvider>
<DatabaseProvider>
<AppNavigation />
</DatabaseProvider>
</PassportProvider>
</AuthProvider>
</YStack>
</ErrorBoundary>
);
}

Expand Down
10 changes: 10 additions & 0 deletions app/ios/OpenPassport/AppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#import <React/RCTLinkingManager.h>
#import <Firebase.h>
#import <UserNotifications/UserNotifications.h>
#import <segment_analytics_react_native-Swift.h>

@implementation AppDelegate

Expand Down Expand Up @@ -82,4 +83,13 @@ - (NSString *)stringFromDeviceToken:(NSData *)deviceToken
return [tokenString copy];
}

// for segment deep link tracking
- (BOOL)application:(UIApplication *)application
openURL: (NSURL *)url
options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {

[AnalyticsReactNative trackDeepLink:url withOptions:options];
return YES;
}

@end
4 changes: 2 additions & 2 deletions app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1804,7 +1804,7 @@ PODS:
- Yoga
- RNSVG (15.11.1):
- React-Core
- segment-analytics-react-native (2.20.3):
- segment-analytics-react-native (2.21.0):
- React-Core
- sovran-react-native
- Sentry (8.50.2):
Expand Down Expand Up @@ -2231,7 +2231,7 @@ SPEC CHECKSUMS:
RNScreens: b7e8d29c6be98f478bc3fb4a97cc770aa9ba7509
RNSentry: c462461c0a5aaba206265f1f3db01b237cd33239
RNSVG: 46769c92d1609e617dbf9326ad8a0cff912d0982
segment-analytics-react-native: 6f98edf18246782ee7428c5380c6519a3d2acf5e
segment-analytics-react-native: dab2ae61d917629b8d040b572f9f7a11b2cc5940
Sentry: 1ca8405451040482877dcd344dfa3ef80b646631
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@react-native-google-signin/google-signin": "^13.1.0",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.2.0",
"@segment/analytics-react-native": "^2.20.3",
"@segment/analytics-react-native": "^2.21.0",
"@segment/sovran-react-native": "^1.1.3",
"@sentry/react-native": "^6.10.0",
"@stablelib/cbor": "^2.0.1",
Expand Down
8 changes: 7 additions & 1 deletion app/src/Segment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import '@ethersproject/shims';

import { SEGMENT_KEY } from '@env';
import {
BackgroundFlushPolicy,
createClient,
EventPlugin,
PluginType,
SegmentEvent,
StartupFlushPolicy,
} from '@segment/analytics-react-native';

let segmentClient: ReturnType<typeof createClient> | null = null;
Expand Down Expand Up @@ -43,10 +45,13 @@ export const createSegmentClient = () => {
return segmentClient;
}

const flushPolicies = [new StartupFlushPolicy(), new BackgroundFlushPolicy()];

const client = createClient({
writeKey: SEGMENT_KEY,
trackAppLifecycleEvents: true,
debug: true,
trackDeepLinks: true,
debug: __DEV__,
collectDeviceId: false,
defaultSettings: {
integrations: {
Expand All @@ -55,6 +60,7 @@ export const createSegmentClient = () => {
},
},
},
flushPolicies,
});

client.add({ plugin: new DisableTrackingPlugin() });
Expand Down
46 changes: 46 additions & 0 deletions app/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { Text, View } from 'react-native';

import analytics from '../utils/analytics';

const { flush: flushAnalytics } = analytics();

interface Props {
children: React.ReactNode;
}

interface State {
hasError: boolean;
}

class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(_: Error): State {
return { hasError: true };
}

componentDidCatch() {
// Flush analytics before the app crashes
flushAnalytics();
}

render() {
if (this.state.hasError) {
return (
<View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
>
<Text>Something went wrong. Please restart the app.</Text>
</View>
);
}

return this.props.children;
}
}

export default ErrorBoundary;
20 changes: 20 additions & 0 deletions app/src/components/buttons/AbstractButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,25 @@ import { StyleSheet, ViewStyle } from 'react-native';
import { Button, Text, ViewProps } from 'tamagui';

import { shouldShowAesopRedesign } from '../../hooks/useAesopRedesign';
import analytics from '../../utils/analytics';
import { dinot } from '../../utils/fonts';
import { pressedStyle } from './pressedStyle';

export interface ButtonProps extends ViewProps {
children: React.ReactNode;
animatedComponent?: React.ReactNode;
trackEvent?: string;
}

interface AbstractButtonProps extends ButtonProps {
bgColor: string;
borderColor?: string;
color: string;
onPress?: ((e: any) => void) | null | undefined;
}

const { trackEvent: analyticsTrackEvent } = analytics();

/*
Base Button component that can be used to create different types of buttons
use PrimaryButton and SecondaryButton instead of this component or create a new button component
Expand All @@ -30,13 +35,28 @@ export default function AbstractButton({
borderColor,
style,
animatedComponent,
trackEvent,
onPress,
...props
}: AbstractButtonProps) {
const hasBorder = borderColor ? true : false;

const handlePress = (e: any) => {
if (trackEvent) {
// attempt to hide the event category
trackEvent = trackEvent.split(':')[1].trim() ?? trackEvent;
analyticsTrackEvent(`Click: ${trackEvent}`);
}
if (onPress) {
onPress(e);
}
};

return (
<Button
unstyled
{...props}
onPress={handlePress}
style={[
styles.container,
{ backgroundColor: bgColor, borderColor: borderColor },
Expand Down
2 changes: 2 additions & 0 deletions app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { assign, createMachine } from 'xstate';

import { ProofEvents } from '../../consts/analytics';
import { black } from '../../utils/colors';
import Description from '../typography/Description';
import { HeldPrimaryButton } from './PrimaryButtonLongHold';
Expand Down Expand Up @@ -253,6 +254,7 @@ export const HeldPrimaryButtonProveScreen: React.FC<

return (
<HeldPrimaryButton
trackEvent={ProofEvents.PROOF_VERIFICATION_STARTED}
onLongPress={() => {
if (state.matches('ready')) {
send({ type: 'VERIFY' });
Expand Down
95 changes: 95 additions & 0 deletions app/src/consts/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
export const AppEvents = {
DISMISS_PRIVACY_DISCLAIMER: 'App: Dismiss Privacy Disclaimer',
GET_STARTED: 'App: Get Started',
UPDATE_MODAL_CLOSED: 'App: Update Modal Closed',
UPDATE_MODAL_OPENED: 'App: Update Modal Opened',
UPDATE_STARTED: 'App: Update Started',
};

export const AuthEvents = {
AUTHENTICATION_TIMEOUT: 'Auth: Authentication Timeout',
BIOMETRIC_AUTH_FAILED: 'Auth: Biometric Auth Failed',
BIOMETRIC_AUTH_SUCCESS: 'Auth: Biometric Auth Success',
BIOMETRIC_CHECK: 'Auth: Biometrics Check',
BIOMETRIC_LOGIN_ATTEMPT: 'Auth: Biometric Login Attempt',
BIOMETRIC_LOGIN_CANCELLED: 'Auth: Biometric Login Cancelled',
BIOMETRIC_LOGIN_FAILED: 'Auth: Biometric Login Failed',
BIOMETRIC_LOGIN_SUCCESS: 'Auth: Biometric Login Success',
MNEMONIC_CREATED: 'Auth: Mnemonic Created',
MNEMONIC_LOADED: 'Auth: Mnemonic Loaded',
MNEMONIC_RESTORE_FAILED: 'Auth: Mnemonic Restore Failed',
MNEMONIC_RESTORE_SUCCESS: 'Auth: Mnemonic Restore Success',
};

export const PassportEvents = {
CAMERA_SCAN_CANCELLED: 'Passport: Camera Scan Cancelled',
CAMERA_SCREEN_CLOSED: 'Passport: Camera View Closed',
CAMERA_SCAN_FAILED: 'Passport: Camera Scan Failed',
CAMERA_SCAN_STARTED: 'Passport: Camera Scan Started',
CAMERA_SCAN_SUCCESS: 'Passport: Camera Scan Success',
CANCEL_PASSPORT_NFC: 'Passport: Cancel Passport NFC',
DATA_LOAD_ERROR: 'Passport: Passport Data Load Error',
DISMISS_UNSUPPORTED_PASSPORT: 'Passport: Dismiss Unsupported Passport',
NFC_RESPONSE_PARSE_FAILED: 'Passport: Parsing NFC Response Unsuccessful',
NFC_SCAN_FAILED: 'Passport: NFC Scan Failed',
NFC_SCAN_SUCCESS: 'Passport: NFC Scan Success',
OPEN_NFC_SETTINGS: 'Passport: Open NFC Settings',
OWNERSHIP_CONFIRMED: 'Passport: Passport Ownership Confirmed',
PASSPORT_PARSE_FAILED: 'Passport: Passport Parse Failed',
PASSPORT_PARSED: 'Passport: Passport Parsed',
START_PASSPORT_NFC: 'Passport: Start Passport NFC',
UNSUPPORTED_PASSPORT: 'Passport: Passport Not Supported',
};

export const ProofEvents = {
FCM_TOKEN_STORED: 'Proof: FCM Token Stored Successfully',
NOTIFICATION_PERMISSION_REQUESTED: 'Proof: Notification Permission Requested',
PROOF_COMPLETED: 'Proof: Proof Completed',
PROOF_DISCLOSURES_SCROLLED: 'Proof: Proof Disclosures Scrolled',
PROOF_FAILED: 'Proof: Proof Failed',
PROOF_RESULT_ACKNOWLEDGED: 'Proof: Proof Result Acknowledged',
PROOF_VERIFICATION_STARTED: 'Proof: Proof Verification Started',
PROVING_PROCESS_ERROR: 'Proof: Proving Process Error',
PROVING_STATE_CHANGE: 'Proof: Proving State Change',
QR_SCAN_CANCELLED: 'Proof: QR Scan Cancelled',
QR_SCAN_FAILED: 'Proof: QR Scan Failed',
QR_SCAN_SUCCESS: 'Proof: QR Scan Success',
};

export const SettingsEvents = {
CONNECTION_MODAL_CLOSED: 'Settings: Connection Modal Closed',
CONNECTION_MODAL_OPENED: 'Settings: Connection Modal Opened',
CONNECTION_SETTINGS_OPENED: 'Settings: Connection Settings Opened',
};

export const BackupEvents = {
ACCOUNT_RECOVERY_STARTED: 'Backup: Account Recovery Started',
ACCOUNT_VERIFICATION_COMPLETED: 'Backup: Account Verification Completed',
CLOUD_BACKUP_CONTINUE: 'Backup: Cloud Backup Continue',
CLOUD_BACKUP_STARTED: 'Backup: Cloud Backup Started',
CLOUD_BACKUP_CANCELLED: 'Backup: Cloud Backup Cancelled',
CLOUD_BACKUP_DISABLED_DONE: 'Backup: Cloud Backup Disabled Done',
CLOUD_BACKUP_DISABLE_STARTED: 'Backup: Cloud Backup Disable Started',
CLOUD_BACKUP_ENABLED_DONE: 'Backup: Cloud Backup Enabled Done',
CLOUD_BACKUP_ENABLE_STARTED: 'Backup: Cloud Backup Enable Started',
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',
MANUAL_RECOVERY_SELECTED: 'Backup: Manual Recovery Selected',
};

export const MockDataEvents = {
CANCEL_GENERATION: 'Mock Data: Cancel Generation',
CREATE_DEEP_LINK: 'Mock Data: Create Deep Link',
DECREASE_EXPIRY_YEARS: 'Mock Data: Decrease Expiry Years',
ENABLE_ADVANCED_MODE: 'Mock Data: Enable Advanced Mode',
GENERATE_DATA: 'Mock Data: Generate Data',
INCREASE_EXPIRY_YEARS: 'Mock Data: Increase Expiry Years',
OPEN_ALGORITHM_SELECTION: 'Mock Data: Open Algorithm Selection',
OPEN_COUNTRY_SELECTION: 'Mock Data: Open Country Selection',
SELECT_ALGORITHM: 'Mock Data: Select Algorithm',
SELECT_COUNTRY: 'Mock Data: Select Country',
TOGGLE_OFAC_LIST: 'Mock Data: Toggle OFAC List',
};
8 changes: 8 additions & 0 deletions app/src/hooks/useAppUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { useEffect, useState } from 'react';
import { Linking } from 'react-native';
import { checkVersion } from 'react-native-check-version';

import { AppEvents } from '../consts/analytics';
import analytics from '../utils/analytics';

const { trackEvent } = analytics();

export const useAppUpdates = (): [boolean, () => void, boolean] => {
const navigation = useNavigation();
const [newVersionUrl, setNewVersionUrl] = useState<string | null>(null);
Expand All @@ -24,14 +29,17 @@ export const useAppUpdates = (): [boolean, () => void, boolean] => {
buttonText: 'Update and restart',
onButtonPress: async () => {
if (newVersionUrl !== null) {
trackEvent(AppEvents.UPDATE_STARTED);
// TODO or use: `Platform.OS === 'ios' ? appStoreUrl : playStoreUrl`
await Linking.openURL(newVersionUrl);
}
},
onModalDismiss: () => {
setIsModalDismissed(true);
trackEvent(AppEvents.UPDATE_MODAL_CLOSED);
},
});
trackEvent(AppEvents.UPDATE_MODAL_OPENED);
};

return [newVersionUrl !== null, showAppUpdateModal, isModalDismissed];
Expand Down
7 changes: 7 additions & 0 deletions app/src/hooks/useConnectionModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { useNetInfo } from '@react-native-community/netinfo';
import { useEffect } from 'react';
import { Linking, Platform } from 'react-native';

import { SettingsEvents } from '../consts/analytics';
import { navigationRef } from '../navigation';
import analytics from '../utils/analytics';
import { useModal } from './useModal';

const { trackEvent } = analytics();

const connectionModalParams = {
titleText: 'Internet connection error',
bodyText: 'In order to use SELF, you must have access to the internet.',
buttonText: 'Open settings',
onButtonPress: async () => {
trackEvent(SettingsEvents.CONNECTION_SETTINGS_OPENED);
return Platform.OS === 'ios'
? Linking.openURL('prefs://MOBILE_DATA_SETTINGS_ID')
: Linking.sendIntent('android.settings.WIRELESS_SETTINGS');
Expand All @@ -33,8 +38,10 @@ export default function useConnectionModal() {

if (!hasConnection && !visible) {
showModal();
trackEvent(SettingsEvents.CONNECTION_MODAL_OPENED);
} else if (visible && hasConnection) {
dismissModal();
trackEvent(SettingsEvents.CONNECTION_MODAL_CLOSED);
}
// Add a small delay to allow app initialization
}, 2000);
Expand Down
Loading