UNPKG

@heap/react-native-heap

Version:

React Native event tracking with Heap.

118 lines (117 loc) 6.61 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import React from 'react'; import { logError, swallowErrors } from '../util/bailer'; import { getComponentDisplayName } from '../util/hocUtil'; import NavigationUtil from '../util/navigationUtil'; import { getContextualProps } from '../util/contextualProps'; const EVENT_TYPE = 'react_navigation_screenview'; // `react-native-navigation` uses `Navigation/{NAVIGATE,POP,BACK}` to represent // different types of navigation actions. We build the initial navigation action // ourselves, so we invent a fourth navigation type to represent this action. const INITIAL_ROUTE_TYPE = 'Heap_Navigation/INITIAL'; export const withReactNavigationAutotrack = track => AppContainer => { const existingWrapper = AppContainer.__heapWrapper; if (existingWrapper) { return existingWrapper; } const captureOldNavigationStateChange = swallowErrors((prev, next, action) => { const { screen_path: prevScreenRoute } = NavigationUtil.getActiveRouteProps(prev); const { screen_path: nextScreenRoute } = NavigationUtil.getActiveRouteProps(next); if (prevScreenRoute !== nextScreenRoute) { track(EVENT_TYPE, Object.assign(Object.assign({}, getContextualProps()), { screen_path: nextScreenRoute, action: action.type })); } }, 'Navigation event capture', true); class HeapNavigationWrapper extends React.Component { constructor() { super(...arguments); this.topLevelNavigator = null; this.currentPath = null; this.captureStateChange = swallowErrors(state => { const { screen_path: nextPath } = NavigationUtil.getActiveRouteProps(state); if (nextPath !== this.currentPath) { track(EVENT_TYPE, Object.assign(Object.assign({}, getContextualProps()), { screen_path: nextPath })); } this.currentPath = nextPath; }, 'Navigation event capture', true); this.captureOnReady = swallowErrors(() => { if (this.topLevelNavigator.getRootState) { this.trackInitialRouteForState(this.topLevelNavigator.getRootState()); const { screen_path: currentPath } = NavigationUtil.getActiveRouteProps(this.topLevelNavigator.getRootState()); this.currentPath = currentPath; } }, 'Navigation event capture', true); } setRef(ref, value) { if (typeof ref === 'function') { ref(value); } else if (ref !== null) { ref.current = value; } } trackInitialRouteForState(navigationState) { const { screen_path: initialPageviewPath, } = NavigationUtil.getActiveRouteProps(navigationState); track(EVENT_TYPE, Object.assign(Object.assign({}, getContextualProps()), { screen_path: initialPageviewPath, action: INITIAL_ROUTE_TYPE })); } render() { try { return this._render(); } catch (e) { logError('Heap: Failed to render React Navigation wrapper.', e); const _a = this.props, { forwardedRef } = _a, rest = __rest(_a, ["forwardedRef"]); return React.createElement(AppContainer, Object.assign({ ref: forwardedRef }, rest)); } } _render() { const _a = this.props, { forwardedRef, onNavigationStateChange, onStateChange } = _a, rest = __rest(_a, ["forwardedRef", "onNavigationStateChange", "onStateChange"]); return (React.createElement(AppContainer, Object.assign({ ref: swallowErrors(navigatorRef => { this.setRef(forwardedRef, navigatorRef); // Update the NavigationUtil's nav reference to the updated ref. NavigationUtil.setNavigationRef(navigatorRef); // Only update the 'topLevelNavigator' if the new nav ref is different and non-null. if (this.topLevelNavigator !== navigatorRef && navigatorRef !== null) { console.log('Heap: React Navigation is instrumented for autocapture.'); this.topLevelNavigator = navigatorRef; if (this.topLevelNavigator.state) { // We're on React Navigation 4, so track the initial route now. this.trackInitialRouteForState(this.topLevelNavigator.state.nav); } } }, 'Navigation event capture', true), onReady: (...args) => { this.captureOnReady(); if (typeof onReady === 'function') { onReady(...args); } }, onStateChange: (...args) => { this.captureStateChange(...args); if (typeof onStateChange === 'function') { onStateChange(...args); } }, onNavigationStateChange: (...args) => { // Capture the screenview, then delegate to the 'onNavigationStateChange' passed to the HOC if it's a function. The logic to // determine whether to call 'onNavigationStateChange' is the same as what's in the 'react-navigation' library. // See https://github.com/react-navigation/native/blob/d0b24924b2e075fed3bd6586339d34fdd4c2b78e/src/createAppContainer.js#L184 captureOldNavigationStateChange(...args); if (typeof onNavigationStateChange === 'function') { onNavigationStateChange(...args); } } }, rest), this.props.children)); } } HeapNavigationWrapper.displayName = `withReactNavigationAutotrack(${getComponentDisplayName(AppContainer)})`; return AppContainer.__heapWrapper = React.forwardRef((props, ref) => { return React.createElement(HeapNavigationWrapper, Object.assign({}, props, { forwardedRef: ref })); }); };