UNPKG

@heap/react-native-heap

Version:

React Native event tracking with Heap.

130 lines (129 loc) 5.95 kB
import * as _ from 'lodash'; import { extractProps } from '../util/extractProps'; import { BASE_HEAP_IGNORE_PROPS, getNextHeapIgnoreProps } from './heapIgnore'; import { builtinPropExtractorConfig } from '../propExtractorConfig'; import { getContextualProps } from '../util/contextualProps'; import { stripReservedCharacters } from '../util/reservedCharacters'; // Returns an 'AutotrackProps' containing a base set of component properties if we're not ignoring // the full interaction due to HeapIgnore. // Returns null if we're ignoring the full interaction due to HeapIgnore. export const getBaseComponentProps = componentThis => { // Get the hierarchy traversal from root to target component, then get the actual hierarchy from // the traversal representation. const touchableHierarchyTraversal = getComponentHierarchyTraversal(componentThis); const { hierarchy, heapIgnoreProps } = getHierarchyStringFromTraversal(touchableHierarchyTraversal); if (!heapIgnoreProps.allowInteraction) { return null; } // Only look for target text if we're not HeapIgnore-ing target text. let targetText; if (heapIgnoreProps.allowTargetText) { targetText = getTargetText(getReactInternalFiber(componentThis)); } else { targetText = ''; } const screenProps = getContextualProps(); const autotrackProps = Object.assign({ rn_hierarchy: hierarchy }, screenProps); if (targetText !== '') { autotrackProps.target_text = targetText; } return autotrackProps; }; const getComponentHierarchyTraversal = componentThis => { // :TODO: (jmtaber129): Remove this if/when we support pre-fiber React. const fiber = getReactInternalFiber(componentThis); if (!fiber) { throw new Error('Pre-fiber React versions (React 16) are currently not supported by Heap autotrack.'); } return getFiberNodeComponentHierarchyTraversal(fiber); }; const getReactInternalFiber = (comp) => { return comp._reactInternals || comp._reactInternalFiber; }; // Traverse up the hierarchy from the current component up to the root, and return an array of // objects representing the component hierarchy from root to the current node. const getFiberNodeComponentHierarchyTraversal = currNode => { if (currNode === null) { return []; } // Skip components we don't care about. // :TODO: (jmtaber129): Skip components with names/display names like 'View' and '_class'. if (currNode.type === 'RCTView' || currNode.type === null || !(currNode.type.displayName || currNode.type.name)) { return getFiberNodeComponentHierarchyTraversal(currNode.return); } const elementName = currNode.type.displayName || currNode.type.name; // In dev builds, 'View' components remain in the fiber tree, but don't provide any useful // information, so exclude these from the hierarchy. if (elementName === 'View') { return getFiberNodeComponentHierarchyTraversal(currNode.return); } const propsString = extractProps(elementName, currNode, builtinPropExtractorConfig); const parentHierarchyRepresentation = getFiberNodeComponentHierarchyTraversal(currNode.return); parentHierarchyRepresentation.push({ elementName, fiberNode: currNode, propsString, // :TODO: Make this an object so we can use it with 'ignoreSpecificProps' }); return parentHierarchyRepresentation; }; // Given an object array representing the component hierarchy from root to target component, // traverse through the element list to create the string representation of the hierarchy, taking // into account HeapIgnore specifications. const getHierarchyStringFromTraversal = hierarchyArray => { let currentHeapIgnoreProps = _.mapValues(BASE_HEAP_IGNORE_PROPS, () => true); // Map each hierarchy element to its string representation, considering HeapIgnore specs. const hierarchyString = hierarchyArray .map(element => { let currElementString = ''; const sanitizedElementName = stripReservedCharacters(element.elementName); if (!currentHeapIgnoreProps.allowInteraction || !currentHeapIgnoreProps.allowInnerHierarchy) { // If we're not using any part of the hierarchy (for 'allowInteraction') or not capturing the // current subhierarchy, return an empty string for the current component. currElementString = ''; } else if (!currentHeapIgnoreProps.allowAllProps) { currElementString = `@${sanitizedElementName};|`; } else { currElementString = `@${sanitizedElementName};${element.propsString}|`; } // Doing this at the end allows us to capture HeapIgnore components. currentHeapIgnoreProps = getNextHeapIgnoreProps(currentHeapIgnoreProps, element); return currElementString; }) .join(''); return { heapIgnoreProps: currentHeapIgnoreProps, hierarchy: hierarchyString, }; }; // :TODO: (jmtaber129): Consider implementing sibling target text. const getTargetText = fiberNode => { if (fiberNode.type === 'RCTText') { return fiberNode.memoizedProps.children; } // In some cases, target text may not be within an 'RCTText' component. This has only been // observed in unit tests with Enzyme, but may still be a possibility in real RN apps. if (fiberNode.memoizedProps && typeof fiberNode.memoizedProps.children === 'string') { return fiberNode.memoizedProps.children; } if (fiberNode.child === null) { return ''; } const children = []; let currChild = fiberNode.child; while (currChild) { children.push(currChild); currChild = currChild.sibling; } let targetText = ''; children.forEach(child => { targetText = (targetText + ' ' + getTargetText(child)).trim(); }); return targetText; };