@@ -2,7 +2,7 @@ import { addBreadcrumb, getCurrentHub } from '@sentry/core';
22import type { SeverityLevel } from '@sentry/types' ;
33import { logger } from '@sentry/utils' ;
44import * as React from 'react' ;
5- import type { GestureResponderEvent } from 'react-native' ;
5+ import type { GestureResponderEvent } from 'react-native' ;
66import { StyleSheet , View } from 'react-native' ;
77
88import { createIntegration } from './integrations/factory' ;
@@ -53,6 +53,9 @@ const DEFAULT_BREADCRUMB_TYPE = 'user';
5353const DEFAULT_MAX_COMPONENT_TREE_SIZE = 20 ;
5454
5555const SENTRY_LABEL_PROP_KEY = 'sentry-label' ;
56+ const SENTRY_COMPONENT_PROP_KEY = 'data-sentry-component' ;
57+ const SENTRY_ELEMENT_PROP_KEY = 'data-sentry-element' ;
58+ const SENTRY_FILE_PROP_KEY = 'data-sentry-source-file' ;
5659
5760interface ElementInstance {
5861 elementType ?: {
@@ -63,6 +66,13 @@ interface ElementInstance {
6366 return ?: ElementInstance ;
6467}
6568
69+ interface TouchedComponentInfo {
70+ name ?: string ;
71+ label ?: string ;
72+ element ?: string ;
73+ file ?: string ;
74+ }
75+
6676interface PrivateGestureResponderEvent extends GestureResponderEvent {
6777 _targetInst ?: ElementInstance ;
6878}
@@ -71,7 +81,6 @@ interface PrivateGestureResponderEvent extends GestureResponderEvent {
7181 * Boundary to log breadcrumbs for interaction events.
7282 */
7383class TouchEventBoundary extends React . Component < TouchEventBoundaryProps > {
74-
7584 public static displayName : string = '__Sentry.TouchEventBoundary' ;
7685 public static defaultProps : Partial < TouchEventBoundaryProps > = {
7786 breadcrumbCategory : DEFAULT_BREADCRUMB_CATEGORY ,
@@ -113,18 +122,17 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
113122 /**
114123 * Logs the touch event given the component tree names and a label.
115124 */
116- private _logTouchEvent (
117- componentTreeNames : string [ ] ,
118- activeLabel ?: string
119- ) : void {
125+ private _logTouchEvent ( touchPath : TouchedComponentInfo [ ] , label ?: string ) : void {
120126 const level = 'info' as SeverityLevel ;
127+
128+ const root = touchPath [ 0 ] ;
129+ const detail = label ? label : `${ root . name } ${ root . file ? ` (${ root . file } )` : '' } ` ;
130+
121131 const crumb = {
122132 category : this . props . breadcrumbCategory ,
123- data : { componentTree : componentTreeNames } ,
133+ data : { path : touchPath } ,
124134 level : level ,
125- message : activeLabel
126- ? `Touch event within element: ${ activeLabel } `
127- : 'Touch event within component tree' ,
135+ message : `Touch event within element: ${ detail } ` ,
128136 type : this . props . breadcrumbType ,
129137 } ;
130138 addBreadcrumb ( crumb ) ;
@@ -147,7 +155,7 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
147155 return ignoreNames . some (
148156 ( ignoreName : string | RegExp ) =>
149157 ( typeof ignoreName === 'string' && name === ignoreName ) ||
150- ( ignoreName instanceof RegExp && name . match ( ignoreName ) )
158+ ( ignoreName instanceof RegExp && name . match ( ignoreName ) ) ,
151159 ) ;
152160 }
153161
@@ -166,80 +174,91 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
166174 }
167175
168176 let currentInst : ElementInstance | undefined = e . _targetInst ;
169-
170- let activeLabel : string | undefined ;
171- let activeDisplayName : string | undefined ;
172- const componentTreeNames : string [ ] = [ ] ;
177+ const touchPath : TouchedComponentInfo [ ] = [ ] ;
173178
174179 while (
175180 currentInst &&
176181 // maxComponentTreeSize will always be defined as we have a defaultProps. But ts needs a check so this is here.
177182 this . props . maxComponentTreeSize &&
178- componentTreeNames . length < this . props . maxComponentTreeSize
183+ touchPath . length < this . props . maxComponentTreeSize
179184 ) {
180185 if (
181186 // If the loop gets to the boundary itself, break.
182- currentInst . elementType ?. displayName ===
183- TouchEventBoundary . displayName
187+ currentInst . elementType ?. displayName === TouchEventBoundary . displayName
184188 ) {
185189 break ;
186190 }
187191
188- const props = currentInst . memoizedProps ;
189- const sentryLabel =
190- typeof props ?. [ SENTRY_LABEL_PROP_KEY ] !== 'undefined'
191- ? `${ props [ SENTRY_LABEL_PROP_KEY ] } `
192+ const props = currentInst . memoizedProps ?? { } ;
193+ const info : TouchedComponentInfo = { } ;
194+
195+ // provided by @sentry /babel-plugin-component-annotate
196+ if ( typeof props [ SENTRY_COMPONENT_PROP_KEY ] === 'string' && props [ SENTRY_COMPONENT_PROP_KEY ] . length > 0 && props [ SENTRY_COMPONENT_PROP_KEY ] !== 'unknown' ) {
197+ info . name = props [ SENTRY_COMPONENT_PROP_KEY ] ;
198+ }
199+ if ( typeof props [ SENTRY_ELEMENT_PROP_KEY ] === 'string' && props [ SENTRY_ELEMENT_PROP_KEY ] . length > 0 && props [ SENTRY_ELEMENT_PROP_KEY ] !== 'unknown' ) {
200+ info . element = props [ SENTRY_ELEMENT_PROP_KEY ] ;
201+ }
202+ if ( typeof props [ SENTRY_FILE_PROP_KEY ] === 'string' && props [ SENTRY_FILE_PROP_KEY ] . length > 0 && props [ SENTRY_FILE_PROP_KEY ] !== 'unknown' ) {
203+ info . file = props [ SENTRY_FILE_PROP_KEY ] ;
204+ }
205+
206+ // use custom label if provided by the user, or displayName if available
207+ const labelValue =
208+ typeof props [ SENTRY_LABEL_PROP_KEY ] === 'string'
209+ ? props [ SENTRY_LABEL_PROP_KEY ]
210+ : // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
211+ // the "check-label" if sentence, so we have to assign it to a variable here first
212+ typeof this . props . labelName === 'string'
213+ ? props [ this . props . labelName ]
192214 : undefined ;
193215
194- // For some reason type narrowing doesn't work as expected with indexing when checking it all in one go in
195- // the "check-label" if sentence, so we have to assign it to a variable here first
196- let labelValue ;
197- if ( typeof this . props . labelName === 'string' )
198- labelValue = props ?. [ this . props . labelName ] ;
199-
200- // Check the label first
201- if ( sentryLabel && ! this . _isNameIgnored ( sentryLabel ) ) {
202- if ( ! activeLabel ) {
203- activeLabel = sentryLabel ;
204- }
205- componentTreeNames . push ( sentryLabel ) ;
206- } else if (
207- typeof labelValue === 'string' &&
208- ! this . _isNameIgnored ( labelValue )
209- ) {
210- if ( ! activeLabel ) {
211- activeLabel = labelValue ;
212- }
213- componentTreeNames . push ( labelValue ) ;
214- } else if ( currentInst . elementType ) {
215- const { elementType } = currentInst ;
216-
217- if (
218- elementType . displayName &&
219- ! this . _isNameIgnored ( elementType . displayName )
220- ) {
221- // Check display name
222- if ( ! activeDisplayName ) {
223- activeDisplayName = elementType . displayName ;
224- }
225- componentTreeNames . push ( elementType . displayName ) ;
226- }
216+ if ( typeof labelValue === 'string' && labelValue . length > 0 ) {
217+ info . label = labelValue ;
218+ }
219+
220+ if ( ! info . name && currentInst . elementType ?. displayName ) {
221+ info . name = currentInst . elementType ?. displayName ;
227222 }
228223
224+ this . _pushIfNotIgnored ( touchPath , info ) ;
225+
229226 currentInst = currentInst . return ;
230227 }
231228
232- const finalLabel = activeLabel ?? activeDisplayName ;
233-
234- if ( componentTreeNames . length > 0 || finalLabel ) {
235- this . _logTouchEvent ( componentTreeNames , finalLabel ) ;
229+ const label = touchPath . find ( info => info . label ) ?. label ;
230+ if ( touchPath . length > 0 ) {
231+ this . _logTouchEvent ( touchPath , label ) ;
236232 }
237233
238234 this . _tracingIntegration ?. startUserInteractionTransaction ( {
239- elementId : activeLabel ,
235+ elementId : label ,
240236 op : UI_ACTION_TOUCH ,
241237 } ) ;
242238 }
239+
240+ /**
241+ * Pushes the name to the componentTreeNames array if it is not ignored.
242+ */
243+ private _pushIfNotIgnored ( touchPath : TouchedComponentInfo [ ] , value : TouchedComponentInfo ) : boolean {
244+ if ( ! value . name && ! value . label ) {
245+ return false ;
246+ }
247+ if ( value . name && this . _isNameIgnored ( value . name ) ) {
248+ return false ;
249+ }
250+ if ( value . label && this . _isNameIgnored ( value . label ) ) {
251+ return false ;
252+ }
253+
254+ // Deduplicate same subsequent items.
255+ if ( touchPath . length > 0 && JSON . stringify ( touchPath [ touchPath . length - 1 ] ) === JSON . stringify ( value ) ) {
256+ return false ;
257+ }
258+
259+ touchPath . push ( value ) ;
260+ return true ;
261+ }
243262}
244263
245264/**
@@ -250,9 +269,9 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
250269const withTouchEventBoundary = (
251270 // eslint-disable-next-line @typescript-eslint/no-explicit-any
252271 InnerComponent : React . ComponentType < any > ,
253- boundaryProps ?: TouchEventBoundaryProps
272+ boundaryProps ?: TouchEventBoundaryProps ,
254273) : React . FunctionComponent => {
255- const WrappedComponent : React . FunctionComponent = ( props ) => (
274+ const WrappedComponent : React . FunctionComponent = props => (
256275 < TouchEventBoundary { ...( boundaryProps ?? { } ) } >
257276 < InnerComponent { ...props } />
258277 </ TouchEventBoundary >
0 commit comments