@heap/react-native-heap
Version:
React Native event tracking with Heap.
118 lines (117 loc) • 6.61 kB
JavaScript
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 }));
});
};