diff --git a/app/App.tsx b/app/App.tsx index 12fe27516..98bd2d896 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -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'; @@ -16,15 +17,17 @@ global.Buffer = Buffer; function App(): React.JSX.Element { return ( - - - - - - - - - + + + + + + + + + + + ); } diff --git a/app/ios/OpenPassport/AppDelegate.mm b/app/ios/OpenPassport/AppDelegate.mm index fd3d91a5c..ef53149e0 100644 --- a/app/ios/OpenPassport/AppDelegate.mm +++ b/app/ios/OpenPassport/AppDelegate.mm @@ -5,6 +5,7 @@ #import #import #import +#import @implementation AppDelegate @@ -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 *)options { + + [AnalyticsReactNative trackDeepLink:url withOptions:options]; + return YES; +} + @end diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 1845ddb38..dd1f38650 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -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): @@ -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 diff --git a/app/package.json b/app/package.json index 512fb0d6a..fd4fa06bd 100644 --- a/app/package.json +++ b/app/package.json @@ -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", diff --git a/app/src/Segment.ts b/app/src/Segment.ts index 21fe64b31..2f20e6714 100644 --- a/app/src/Segment.ts +++ b/app/src/Segment.ts @@ -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 | null = null; @@ -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: { @@ -55,6 +60,7 @@ export const createSegmentClient = () => { }, }, }, + flushPolicies, }); client.add({ plugin: new DisableTrackingPlugin() }); diff --git a/app/src/components/ErrorBoundary.tsx b/app/src/components/ErrorBoundary.tsx new file mode 100644 index 000000000..c8b502923 --- /dev/null +++ b/app/src/components/ErrorBoundary.tsx @@ -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 { + 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 ( + + Something went wrong. Please restart the app. + + ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/app/src/components/buttons/AbstractButton.tsx b/app/src/components/buttons/AbstractButton.tsx index 49c158f85..d5bd4d5db 100644 --- a/app/src/components/buttons/AbstractButton.tsx +++ b/app/src/components/buttons/AbstractButton.tsx @@ -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 @@ -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 (