react-native-netmera 2.1.0-beta01 → 2.1.0-beta03
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -13
- package/android/src/main/java/com/netmera/reactnativesdk/RNNetmeraModule.kt +7 -7
- package/ios/RNNetmeraRCTEventEmitter.swift +3 -1
- package/lib/module/NetmeraAnalyticProvider.js +28 -23
- package/lib/module/NetmeraAnalyticProvider.js.map +1 -1
- package/lib/module/autotracking/useNavigationTracking.js +18 -52
- package/lib/module/autotracking/useNavigationTracking.js.map +1 -1
- package/lib/module/autotracking/useTapTracking.js +65 -29
- package/lib/module/autotracking/useTapTracking.js.map +1 -1
- package/lib/typescript/src/NetmeraAnalyticProvider.d.ts +14 -9
- package/lib/typescript/src/NetmeraAnalyticProvider.d.ts.map +1 -1
- package/lib/typescript/src/autotracking/useNavigationTracking.d.ts +8 -7
- package/lib/typescript/src/autotracking/useNavigationTracking.d.ts.map +1 -1
- package/lib/typescript/src/autotracking/useTapTracking.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NetmeraAnalyticProvider.tsx +53 -46
- package/src/autotracking/useNavigationTracking.ts +18 -65
- package/src/autotracking/useTapTracking.ts +61 -26
package/README.md
CHANGED
|
@@ -555,13 +555,20 @@ Netmera Autotracking automatically captures screen transitions via **React Navig
|
|
|
555
555
|
|
|
556
556
|
### NetmeraAnalyticProvider
|
|
557
557
|
|
|
558
|
-
Wrap your `
|
|
558
|
+
Wrap your app with `NetmeraAnalyticProvider` and pass a `navigationRef` so the provider can subscribe to screen changes. The provider handles both screen tracking and tap tracking based on the configuration received from the Netmera dashboard.
|
|
559
|
+
|
|
560
|
+
**Dynamic API** (React Navigation v6 / v7):
|
|
559
561
|
|
|
560
562
|
```tsx
|
|
563
|
+
import { useNavigationContainerRef, NavigationContainer } from '@react-navigation/native';
|
|
564
|
+
import { NetmeraAnalyticProvider } from 'react-native-netmera';
|
|
565
|
+
|
|
561
566
|
export default function App() {
|
|
567
|
+
const navigationRef = useNavigationContainerRef();
|
|
568
|
+
|
|
562
569
|
return (
|
|
563
|
-
<NetmeraAnalyticProvider>
|
|
564
|
-
<NavigationContainer>
|
|
570
|
+
<NetmeraAnalyticProvider navigationRef={navigationRef}>
|
|
571
|
+
<NavigationContainer ref={navigationRef}>
|
|
565
572
|
{/* your navigators */}
|
|
566
573
|
</NavigationContainer>
|
|
567
574
|
</NetmeraAnalyticProvider>
|
|
@@ -569,29 +576,27 @@ export default function App() {
|
|
|
569
576
|
}
|
|
570
577
|
```
|
|
571
578
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
On **React Navigation v7+** the provider locates the navigation container automatically via the React fiber tree (no extra configuration needed).
|
|
575
|
-
|
|
576
|
-
On **React Navigation v6 and below** the fiber-based detection is not available. Pass an explicit `navigationRef` prop so the provider can subscribe to screen changes:
|
|
579
|
+
**Static API** (React Navigation v7):
|
|
577
580
|
|
|
578
581
|
```tsx
|
|
579
|
-
import { useNavigationContainerRef,
|
|
582
|
+
import { useNavigationContainerRef, createStaticNavigation } from '@react-navigation/native';
|
|
580
583
|
import { NetmeraAnalyticProvider } from 'react-native-netmera';
|
|
581
584
|
|
|
585
|
+
const Navigation = createStaticNavigation(RootStack);
|
|
586
|
+
|
|
582
587
|
export default function App() {
|
|
583
588
|
const navigationRef = useNavigationContainerRef();
|
|
584
589
|
|
|
585
590
|
return (
|
|
586
591
|
<NetmeraAnalyticProvider navigationRef={navigationRef}>
|
|
587
|
-
<
|
|
588
|
-
{/* your navigators */}
|
|
589
|
-
</NavigationContainer>
|
|
592
|
+
<Navigation ref={navigationRef} />
|
|
590
593
|
</NetmeraAnalyticProvider>
|
|
591
594
|
);
|
|
592
595
|
}
|
|
593
596
|
```
|
|
594
597
|
|
|
598
|
+
> **Note:** Use a single `NetmeraAnalyticProvider` per application. The current screen name is stored in a module-level singleton — mounting multiple providers simultaneously will cause them to overwrite each other's screen state.
|
|
599
|
+
|
|
595
600
|
### Manual action tracking with `Netmera.trackAction`
|
|
596
601
|
|
|
597
602
|
For components that do not fire standard `onPress` or `onValueChange` events (e.g. third-party dropdowns, custom pickers, gesture-only views), call `Netmera.trackAction` to record the interaction manually.
|
|
@@ -614,7 +619,7 @@ When a tap is detected, Netmera resolves an identifier for the tapped component
|
|
|
614
619
|
1. **`accessibilityLabel`** — the preferred identifier; human-readable and stable.
|
|
615
620
|
2. **`testID`** — used when `accessibilityLabel` is absent.
|
|
616
621
|
3. **`placeholder`** — for `TextInput` components with no label or testID.
|
|
617
|
-
4. **Child text content** — the text of the first `<Text>` descendant
|
|
622
|
+
4. **Child text content** — the text of the first `<Text>` descendant.
|
|
618
623
|
|
|
619
624
|
If none of the above resolves to a non-empty string the tap is not recorded.
|
|
620
625
|
|
|
@@ -63,7 +63,7 @@ class RNNetmeraModule(reactContext: ReactApplicationContext) :
|
|
|
63
63
|
|
|
64
64
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
65
65
|
fun checkNotificationPermission(): String {
|
|
66
|
-
val activity: Activity? = reactContext
|
|
66
|
+
val activity: Activity? = reactContext?.currentActivity
|
|
67
67
|
return if (activity != null) {
|
|
68
68
|
Netmera.checkNotificationPermission(activity).toString()
|
|
69
69
|
} else {
|
|
@@ -74,7 +74,7 @@ class RNNetmeraModule(reactContext: ReactApplicationContext) :
|
|
|
74
74
|
|
|
75
75
|
@ReactMethod
|
|
76
76
|
fun requestPushNotificationAuthorization(promise: Promise) {
|
|
77
|
-
val activity: Activity? = reactContext
|
|
77
|
+
val activity: Activity? = reactContext?.currentActivity
|
|
78
78
|
if (activity == null) {
|
|
79
79
|
promise.reject(
|
|
80
80
|
ERROR_CODE_INVALID_ACTIVITY,
|
|
@@ -702,18 +702,18 @@ class RNNetmeraModule(reactContext: ReactApplicationContext) :
|
|
|
702
702
|
const val ERROR_MESSAGE_NOTIFICATION_PERMISSION =
|
|
703
703
|
"Error occurred while requesting notification permission. "
|
|
704
704
|
|
|
705
|
-
|
|
705
|
+
var reactContext: ReactApplicationContext? = null
|
|
706
706
|
|
|
707
707
|
fun emitEvent(eventName: String, eventData: String) {
|
|
708
708
|
reactContext
|
|
709
|
-
|
|
710
|
-
|
|
709
|
+
?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
710
|
+
?.emit(eventName, eventData)
|
|
711
711
|
}
|
|
712
712
|
|
|
713
713
|
fun emitEvent(eventName: String, eventData: WritableMap) {
|
|
714
714
|
reactContext
|
|
715
|
-
|
|
716
|
-
|
|
715
|
+
?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
716
|
+
?.emit(eventName, eventData)
|
|
717
717
|
}
|
|
718
718
|
|
|
719
719
|
private fun hasKey(map: MutableMap<*, *>, key: String?): Boolean {
|
|
@@ -154,6 +154,8 @@ public class RNNetmeraRCTEventEmitter: RCTEventEmitter {
|
|
|
154
154
|
|
|
155
155
|
static func onAutoTrackConfigUpdate(_ body: [String: Bool]) {
|
|
156
156
|
guard let emitter = emitterInstance else { return }
|
|
157
|
-
|
|
157
|
+
DispatchQueue.main.async {
|
|
158
|
+
emitter.sendEvent(withName: EventName.onAutoTrackConfigUpdate, body: body)
|
|
159
|
+
}
|
|
158
160
|
}
|
|
159
161
|
}
|
|
@@ -5,58 +5,63 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import React, { useEffect, useRef, useState } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { NativeModules, StyleSheet, View } from 'react-native';
|
|
9
|
+
import { netmeraRCTEventEmitter } from "./utils/RNNetmera.js";
|
|
9
10
|
import { useNavigationTracking } from "./autotracking/useNavigationTracking.js";
|
|
10
11
|
import { useTapTracking } from "./autotracking/useTapTracking.js";
|
|
11
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
13
|
/**
|
|
13
14
|
* Wraps your app to enable automatic screen tracking and tap tracking.
|
|
14
|
-
*
|
|
15
|
-
* <NetmeraAnalyticProvider>
|
|
16
|
-
* <NavigationContainer>…</NavigationContainer>
|
|
17
|
-
* </NetmeraAnalyticProvider>
|
|
15
|
+
* Place it outside your NavigationContainer (or Static Navigation component).
|
|
18
16
|
*/
|
|
19
17
|
export function NetmeraAnalyticProvider({
|
|
20
18
|
children,
|
|
21
19
|
navigationRef
|
|
22
20
|
}) {
|
|
23
|
-
const viewRef = useRef(null);
|
|
24
21
|
const [config, setConfig] = useState(null);
|
|
25
22
|
const appliedConfigRef = useRef(null);
|
|
26
|
-
const applyConfig =
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
const applyConfig = incomingConfig => {
|
|
24
|
+
const currentConfig = appliedConfigRef.current;
|
|
25
|
+
if (currentConfig?.isScreenFlowEnabled === incomingConfig.isScreenFlowEnabled && currentConfig?.isInputActionEnabled === incomingConfig.isInputActionEnabled && currentConfig?.shouldCollectValues === incomingConfig.shouldCollectValues) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
appliedConfigRef.current = incomingConfig;
|
|
29
|
+
setConfig(incomingConfig);
|
|
30
|
+
if (__DEV__) {
|
|
31
|
+
console.log('[NMAutotrack] screen tracking', incomingConfig.isScreenFlowEnabled ? 'enabled' : 'disabled');
|
|
32
|
+
console.log('[NMAutotrack] input action tracking', incomingConfig.isInputActionEnabled ? `enabled ${incomingConfig.shouldCollectValues ? 'with' : 'without'} collection values` : 'disabled');
|
|
33
|
+
}
|
|
33
34
|
};
|
|
34
35
|
useEffect(() => {
|
|
35
|
-
NativeModules.RNNetmera?.getAutoTrackConfig?.()?.then(
|
|
36
|
-
if (
|
|
36
|
+
NativeModules.RNNetmera?.getAutoTrackConfig?.()?.then(fetchedConfig => {
|
|
37
|
+
if (fetchedConfig) {
|
|
38
|
+
applyConfig(fetchedConfig);
|
|
39
|
+
}
|
|
37
40
|
})?.catch(_err => {});
|
|
38
41
|
}, []);
|
|
39
42
|
useEffect(() => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const subscription = netmeraRCTEventEmitter.addListener('onAutoTrackConfigUpdate', updatedConfig => {
|
|
44
|
+
if (updatedConfig) {
|
|
45
|
+
applyConfig(updatedConfig);
|
|
46
|
+
}
|
|
43
47
|
});
|
|
44
48
|
return () => subscription.remove();
|
|
45
49
|
}, []);
|
|
46
50
|
const screenFlowEnabled = config?.isScreenFlowEnabled ?? false;
|
|
47
51
|
const inputActionEnabled = config?.isInputActionEnabled ?? false;
|
|
48
52
|
const shouldCollectValues = config?.shouldCollectValues ?? false;
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
|
|
54
|
+
// Navigation tracking is needed whenever either flag is on: screen events
|
|
55
|
+
// need it for trackView, action events need it for the screen-name prefix.
|
|
56
|
+
useNavigationTracking(navigationRef, screenFlowEnabled || inputActionEnabled, screenFlowEnabled);
|
|
51
57
|
const {
|
|
52
58
|
onTouchStart,
|
|
53
59
|
onTouchEnd
|
|
54
60
|
} = useTapTracking(inputActionEnabled, shouldCollectValues);
|
|
55
61
|
return /*#__PURE__*/_jsx(View, {
|
|
56
|
-
ref: navigationEnabled ? viewRef : undefined,
|
|
57
62
|
style: styles.container,
|
|
58
|
-
onTouchStart: onTouchStart,
|
|
59
|
-
onTouchEnd: onTouchEnd,
|
|
63
|
+
onTouchStart: inputActionEnabled ? onTouchStart : undefined,
|
|
64
|
+
onTouchEnd: inputActionEnabled ? onTouchEnd : undefined,
|
|
60
65
|
children: children
|
|
61
66
|
});
|
|
62
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useEffect","useRef","useState","
|
|
1
|
+
{"version":3,"names":["React","useEffect","useRef","useState","NativeModules","StyleSheet","View","netmeraRCTEventEmitter","useNavigationTracking","useTapTracking","jsx","_jsx","NetmeraAnalyticProvider","children","navigationRef","config","setConfig","appliedConfigRef","applyConfig","incomingConfig","currentConfig","current","isScreenFlowEnabled","isInputActionEnabled","shouldCollectValues","__DEV__","console","log","RNNetmera","getAutoTrackConfig","then","fetchedConfig","catch","_err","subscription","addListener","updatedConfig","remove","screenFlowEnabled","inputActionEnabled","onTouchStart","onTouchEnd","style","styles","container","undefined","create","flex"],"sourceRoot":"../../src","sources":["NetmeraAnalyticProvider.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -4,75 +4,42 @@
|
|
|
4
4
|
* Copyright (c) 2026 Netmera Research.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { useEffect, useRef } from 'react';
|
|
8
8
|
import { setCurrentScreen } from "./NetmeraNavigationState.js";
|
|
9
9
|
import { RNNetmera } from "../utils/RNNetmera.js";
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
const CONTEXT_PROVIDER_TAG = 10;
|
|
13
|
-
const NAV_SEARCH_DEPTH = 25;
|
|
14
|
-
|
|
15
|
-
// Duck-type check: React Navigation v5/v6/v7 all expose this surface on the container ref.
|
|
11
|
+
// Duck-type check: React Navigation v5/v6/v7 all expose this surface.
|
|
16
12
|
function isNavContainerRef(value) {
|
|
17
13
|
return value != null && typeof value.addListener === 'function' && typeof value.getCurrentRoute === 'function' && typeof value.isReady === 'function';
|
|
18
14
|
}
|
|
19
15
|
|
|
20
|
-
// DFS through child fibers to find NavigationContainerRefContext.Provider.
|
|
21
|
-
// Siblings walk at the same depth — they sit at the same level in the tree.
|
|
22
|
-
function findNavContainerRef(fiber, depth = 0) {
|
|
23
|
-
if (!fiber || depth > NAV_SEARCH_DEPTH) return null;
|
|
24
|
-
if (fiber.tag === CONTEXT_PROVIDER_TAG && isNavContainerRef(fiber.memoizedProps?.value)) {
|
|
25
|
-
return fiber.memoizedProps.value;
|
|
26
|
-
}
|
|
27
|
-
return findNavContainerRef(fiber.child, depth + 1) ?? findNavContainerRef(fiber.sibling, depth);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
16
|
/**
|
|
31
17
|
* Subscribes to React Navigation screen changes and keeps NetmeraNavigationState
|
|
32
|
-
* up to date.
|
|
18
|
+
* up to date.
|
|
33
19
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
20
|
+
* Requires a valid navigation container ref — pass the same ref to both
|
|
21
|
+
* NetmeraAnalyticProvider and NavigationContainer (or the component created
|
|
22
|
+
* by createStaticNavigation).
|
|
36
23
|
*
|
|
37
|
-
* Silently no-ops
|
|
38
|
-
* Respects the
|
|
24
|
+
* Silently no-ops when navigationRef is absent or not yet populated.
|
|
25
|
+
* Respects the enabled flag — screen changes are ignored when disabled.
|
|
39
26
|
*/
|
|
40
|
-
export function useNavigationTracking(
|
|
41
|
-
// React 18: capture own fiber during render via ReactCurrentOwner.
|
|
42
|
-
// React 19 removed ReactCurrentOwner — this silently yields null, which is fine
|
|
43
|
-
// because the Fabric path (viewRef.__internalInstanceHandle) covers React 19.
|
|
27
|
+
export function useNavigationTracking(navigationRef, enabled, screenLogEnabled) {
|
|
44
28
|
const screenLogEnabledRef = useRef(screenLogEnabled);
|
|
29
|
+
const lastEmittedRouteRef = useRef(null);
|
|
45
30
|
useEffect(() => {
|
|
46
31
|
screenLogEnabledRef.current = screenLogEnabled;
|
|
47
32
|
}, [screenLogEnabled]);
|
|
48
|
-
const selfFiberRef = useRef(null);
|
|
49
|
-
if (selfFiberRef.current === null) {
|
|
50
|
-
try {
|
|
51
|
-
const R = React;
|
|
52
|
-
selfFiberRef.current = R.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactCurrentOwner?.current ?? null;
|
|
53
|
-
} catch (_) {}
|
|
54
|
-
}
|
|
55
33
|
useEffect(() => {
|
|
56
|
-
if (!enabled)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Path 1 — New Arch: ReactNativeElement exposes its fiber via __internalInstanceHandle.
|
|
60
|
-
// Guard with 'child' in candidate to confirm it's a fiber, not another host object.
|
|
61
|
-
const candidate = viewRef.current?.__internalInstanceHandle;
|
|
62
|
-
const viewFiber = candidate != null && 'child' in candidate ? candidate : selfFiberRef.current?.child ?? null;
|
|
63
|
-
if (viewFiber) {
|
|
64
|
-
navRef = findNavContainerRef(viewFiber);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Path 2 — Old Arch: use the ref passed explicitly via prop.
|
|
68
|
-
if (!navRef && isNavContainerRef(navigationRef?.current)) {
|
|
69
|
-
navRef = navigationRef.current;
|
|
34
|
+
if (!enabled || !isNavContainerRef(navigationRef?.current)) {
|
|
35
|
+
return;
|
|
70
36
|
}
|
|
71
|
-
|
|
37
|
+
const navRef = navigationRef.current;
|
|
72
38
|
const handleChange = () => {
|
|
73
39
|
try {
|
|
74
40
|
const route = navRef.getCurrentRoute();
|
|
75
|
-
if (route?.name) {
|
|
41
|
+
if (route?.name && route.name !== lastEmittedRouteRef.current) {
|
|
42
|
+
lastEmittedRouteRef.current = route.name;
|
|
76
43
|
setCurrentScreen(route.name);
|
|
77
44
|
if (screenLogEnabledRef.current) {
|
|
78
45
|
RNNetmera.trackView(route.name);
|
|
@@ -82,9 +49,8 @@ export function useNavigationTracking(viewRef, navigationRef, enabled, screenLog
|
|
|
82
49
|
};
|
|
83
50
|
const unsubState = navRef.addListener('state', handleChange);
|
|
84
51
|
|
|
85
|
-
// NavigationContainer's effects run before ours (
|
|
86
|
-
// already
|
|
87
|
-
// listener is a safety net for async startup (deferred auth, deep links, etc.).
|
|
52
|
+
// NavigationContainer's effects (child) run before ours (parent), so the ref
|
|
53
|
+
// is already populated and isReady() reflects the real mount state.
|
|
88
54
|
let unsubReady;
|
|
89
55
|
if (navRef.isReady()) {
|
|
90
56
|
handleChange();
|
|
@@ -99,6 +65,6 @@ export function useNavigationTracking(viewRef, navigationRef, enabled, screenLog
|
|
|
99
65
|
unsubState?.();
|
|
100
66
|
unsubReady?.();
|
|
101
67
|
};
|
|
102
|
-
}, [enabled]);
|
|
68
|
+
}, [enabled, navigationRef]);
|
|
103
69
|
}
|
|
104
70
|
//# sourceMappingURL=useNavigationTracking.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["useEffect","useRef","setCurrentScreen","RNNetmera","isNavContainerRef","value","addListener","getCurrentRoute","isReady","useNavigationTracking","navigationRef","enabled","screenLogEnabled","screenLogEnabledRef","lastEmittedRouteRef","current","navRef","handleChange","route","name","trackView","_","unsubState","unsubReady","undefined"],"sourceRoot":"../../../src","sources":["autotracking/useNavigationTracking.ts"],"mappings":";;AAAA;AACA;AACA;;AAEA,SAASA,SAAS,EAAEC,MAAM,QAAQ,OAAO;AAEzC,SAASC,gBAAgB,QAAQ,6BAA0B;AAC3D,SAASC,SAAS,QAAQ,uBAAoB;;AAE9C;AACA,SAASC,iBAAiBA,CAACC,KAAU,EAAW;EAC9C,OACEA,KAAK,IAAI,IAAI,IACb,OAAOA,KAAK,CAACC,WAAW,KAAK,UAAU,IACvC,OAAOD,KAAK,CAACE,eAAe,KAAK,UAAU,IAC3C,OAAOF,KAAK,CAACG,OAAO,KAAK,UAAU;AAEvC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,qBAAqBA,CACnCC,aAA+C,EAC/CC,OAAgB,EAChBC,gBAAyB,EACnB;EACN,MAAMC,mBAAmB,GAAGZ,MAAM,CAACW,gBAAgB,CAAC;EACpD,MAAME,mBAAmB,GAAGb,MAAM,CAAgB,IAAI,CAAC;EAEvDD,SAAS,CAAC,MAAM;IACda,mBAAmB,CAACE,OAAO,GAAGH,gBAAgB;EAChD,CAAC,EAAE,CAACA,gBAAgB,CAAC,CAAC;EAEtBZ,SAAS,CAAC,MAAM;IACd,IAAI,CAACW,OAAO,IAAI,CAACP,iBAAiB,CAACM,aAAa,EAAEK,OAAO,CAAC,EAAE;MAC1D;IACF;IAEA,MAAMC,MAAM,GAAGN,aAAa,CAAEK,OAAO;IAErC,MAAME,YAAY,GAAGA,CAAA,KAAM;MACzB,IAAI;QACF,MAAMC,KAAK,GAAGF,MAAM,CAACT,eAAe,CAAC,CAAC;QACtC,IAAIW,KAAK,EAAEC,IAAI,IAAID,KAAK,CAACC,IAAI,KAAKL,mBAAmB,CAACC,OAAO,EAAE;UAC7DD,mBAAmB,CAACC,OAAO,GAAGG,KAAK,CAACC,IAAI;UACxCjB,gBAAgB,CAACgB,KAAK,CAACC,IAAI,CAAC;UAC5B,IAAIN,mBAAmB,CAACE,OAAO,EAAE;YAC/BZ,SAAS,CAACiB,SAAS,CAACF,KAAK,CAACC,IAAI,CAAC;UACjC;QACF;MACF,CAAC,CAAC,OAAOE,CAAC,EAAE,CAAC;IACf,CAAC;IAED,MAAMC,UAAU,GAAGN,MAAM,CAACV,WAAW,CAAC,OAAO,EAAEW,YAAY,CAAC;;IAE5D;IACA;IACA,IAAIM,UAAoC;IACxC,IAAIP,MAAM,CAACR,OAAO,CAAC,CAAC,EAAE;MACpBS,YAAY,CAAC,CAAC;IAChB,CAAC,MAAM;MACLM,UAAU,GAAGP,MAAM,CAACV,WAAW,CAAC,OAAO,EAAE,MAAM;QAC7CW,YAAY,CAAC,CAAC;QACdM,UAAU,GAAG,CAAC;QACdA,UAAU,GAAGC,SAAS;MACxB,CAAC,CAAC;IACJ;IAEA,OAAO,MAAM;MACXF,UAAU,GAAG,CAAC;MACdC,UAAU,GAAG,CAAC;IAChB,CAAC;EACH,CAAC,EAAE,CAACZ,OAAO,EAAED,aAAa,CAAC,CAAC;AAC9B","ignoreList":[]}
|
|
@@ -27,8 +27,10 @@ function isSwitch(props) {
|
|
|
27
27
|
// Tapping them only opens the picker UI — the real select event fires separately.
|
|
28
28
|
function isInsideSelectionControl(fiber) {
|
|
29
29
|
let node = fiber?.return;
|
|
30
|
-
for (let d = 0; d <
|
|
31
|
-
if (node?.memoizedProps?.onSelect != null)
|
|
30
|
+
for (let d = 0; d < 3 && node; d++, node = node.return) {
|
|
31
|
+
if (node?.memoizedProps?.onSelect != null) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
return false;
|
|
34
36
|
}
|
|
@@ -36,16 +38,22 @@ function isInsideSelectionControl(fiber) {
|
|
|
36
38
|
// ── Identifier resolution ─────────────────────────────────────────────────────
|
|
37
39
|
|
|
38
40
|
function textFromChildren(children, depth = 0) {
|
|
39
|
-
if (depth > MAX_TEXT_DEPTH)
|
|
41
|
+
if (depth > MAX_TEXT_DEPTH) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
40
44
|
if (typeof children === 'string') {
|
|
41
45
|
const t = children.trim();
|
|
42
46
|
return t.length > 0 ? t : undefined;
|
|
43
47
|
}
|
|
44
|
-
if (typeof children === 'number')
|
|
48
|
+
if (typeof children === 'number') {
|
|
49
|
+
return String(children);
|
|
50
|
+
}
|
|
45
51
|
if (Array.isArray(children)) {
|
|
46
52
|
for (const child of children) {
|
|
47
53
|
const t = textFromChildren(child, depth + 1);
|
|
48
|
-
if (t)
|
|
54
|
+
if (t) {
|
|
55
|
+
return t;
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
return undefined;
|
|
51
59
|
}
|
|
@@ -57,7 +65,9 @@ function textFromChildren(children, depth = 0) {
|
|
|
57
65
|
|
|
58
66
|
// Priority: accessibilityLabel → testID → placeholder (TextInput) → first child text.
|
|
59
67
|
function resolveIdentifier(props) {
|
|
60
|
-
if (!props)
|
|
68
|
+
if (!props) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
61
71
|
if (typeof props.accessibilityLabel === 'string' && props.accessibilityLabel.trim()) {
|
|
62
72
|
return props.accessibilityLabel.trim();
|
|
63
73
|
}
|
|
@@ -96,7 +106,9 @@ function resolveListIndex(startFiber) {
|
|
|
96
106
|
}
|
|
97
107
|
}
|
|
98
108
|
if (sections) {
|
|
99
|
-
if (cellKey.endsWith(':header') || cellKey.endsWith(':footer'))
|
|
109
|
+
if (cellKey.endsWith(':header') || cellKey.endsWith(':footer')) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
100
112
|
|
|
101
113
|
// Mirrors VirtualizedSectionList._getItem offset arithmetic:
|
|
102
114
|
// flatIndex 0 → section 0 header
|
|
@@ -106,12 +118,16 @@ function resolveListIndex(startFiber) {
|
|
|
106
118
|
let idx = flatIndex - 1;
|
|
107
119
|
for (let si = 0; si < sections.length; si++) {
|
|
108
120
|
const count = sections[si].data?.length ?? 0;
|
|
109
|
-
if (idx === -1 || idx === count)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
if (idx === -1 || idx === count) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (idx < count) {
|
|
125
|
+
return {
|
|
126
|
+
type: 'sectionlist',
|
|
127
|
+
sectionIndex: si,
|
|
128
|
+
itemIndex: idx
|
|
129
|
+
};
|
|
130
|
+
}
|
|
115
131
|
idx -= count + 2;
|
|
116
132
|
}
|
|
117
133
|
return undefined;
|
|
@@ -126,9 +142,10 @@ function resolveListIndex(startFiber) {
|
|
|
126
142
|
|
|
127
143
|
// ── Log path ──────────────────────────────────────────────────────────────────
|
|
128
144
|
|
|
129
|
-
function buildPath(screen, listType, identifier, indexSuffix,
|
|
130
|
-
const
|
|
131
|
-
|
|
145
|
+
function buildPath(screen, listType, identifier, indexSuffix, switchValue) {
|
|
146
|
+
const screenName = screen ?? 'unknown';
|
|
147
|
+
const base = listType ? `${screenName}|${listType}|${identifier}` : `${screenName}|${identifier}`;
|
|
148
|
+
return switchValue !== undefined ? `${base}|${switchValue}${indexSuffix}` : `${base}${indexSuffix}`;
|
|
132
149
|
}
|
|
133
150
|
|
|
134
151
|
// ── Hook ──────────────────────────────────────────────────────────────────────
|
|
@@ -156,7 +173,9 @@ export function useTapTracking(enabled, shouldCollectValues) {
|
|
|
156
173
|
shouldCollectValuesRef.current = shouldCollectValues;
|
|
157
174
|
}, [shouldCollectValues]);
|
|
158
175
|
const onTouchStart = useCallback(e => {
|
|
159
|
-
if (!enabledRef.current)
|
|
176
|
+
if (!enabledRef.current) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
160
179
|
let switchValue;
|
|
161
180
|
try {
|
|
162
181
|
let fiber = e._targetInst;
|
|
@@ -171,20 +190,29 @@ export function useTapTracking(enabled, shouldCollectValues) {
|
|
|
171
190
|
touchStartRef.current = {
|
|
172
191
|
x: e.nativeEvent.pageX,
|
|
173
192
|
y: e.nativeEvent.pageY,
|
|
174
|
-
switchValue
|
|
193
|
+
switchValue,
|
|
194
|
+
isSwitch: switchValue !== undefined
|
|
175
195
|
};
|
|
176
196
|
}, []);
|
|
177
197
|
const onTouchEnd = useCallback(e => {
|
|
178
|
-
if (!enabledRef.current)
|
|
198
|
+
if (!enabledRef.current) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
179
201
|
const start = touchStartRef.current;
|
|
180
|
-
if (start) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
202
|
+
if (!start) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const dx = Math.abs(e.nativeEvent.pageX - start.x);
|
|
206
|
+
const dy = Math.abs(e.nativeEvent.pageY - start.y);
|
|
207
|
+
const isScrollGesture = dx > TAP_THRESHOLD || dy > TAP_THRESHOLD;
|
|
208
|
+
if (isScrollGesture && !start.isSwitch) {
|
|
209
|
+
return;
|
|
184
210
|
}
|
|
185
211
|
try {
|
|
186
212
|
let node = e._targetInst;
|
|
187
|
-
if (!node)
|
|
213
|
+
if (!node) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
188
216
|
|
|
189
217
|
// Pass 1: walk up to find the first interactive ancestor.
|
|
190
218
|
// e.g. tapping a <Text> inside a <Pressable> yields the Pressable.
|
|
@@ -198,7 +226,9 @@ export function useTapTracking(enabled, shouldCollectValues) {
|
|
|
198
226
|
node = node.return;
|
|
199
227
|
depth++;
|
|
200
228
|
}
|
|
201
|
-
if (!interactiveNode)
|
|
229
|
+
if (!interactiveNode) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
202
232
|
|
|
203
233
|
// Pass 2: resolve identifier from the interactive fiber and up to 2 ancestors.
|
|
204
234
|
// The look-ahead handles TouchableOpacity, where the inner Animated.View fiber
|
|
@@ -207,12 +237,18 @@ export function useTapTracking(enabled, shouldCollectValues) {
|
|
|
207
237
|
let identifier;
|
|
208
238
|
for (let i = 0; i < 3 && identifierNode; i++) {
|
|
209
239
|
identifier = resolveIdentifier(identifierNode.memoizedProps);
|
|
210
|
-
if (identifier)
|
|
240
|
+
if (identifier) {
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
211
243
|
identifierNode = identifierNode.return;
|
|
212
244
|
}
|
|
213
|
-
if (!identifier)
|
|
245
|
+
if (!identifier) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
214
248
|
const isSwitchNode = isSwitch(interactiveNode.memoizedProps);
|
|
215
|
-
if (!isSwitchNode && isInsideSelectionControl(interactiveNode))
|
|
249
|
+
if (!isSwitchNode && isInsideSelectionControl(interactiveNode)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
216
252
|
const screen = getCurrentScreen();
|
|
217
253
|
if (screen !== lastScreenRef.current) {
|
|
218
254
|
switchLastLoggedRef.current.clear();
|
|
@@ -231,7 +267,7 @@ export function useTapTracking(enabled, shouldCollectValues) {
|
|
|
231
267
|
newValue = preTapValue !== undefined ? !preTapValue : !interactiveNode.memoizedProps.value;
|
|
232
268
|
}
|
|
233
269
|
switchLastLoggedRef.current.set(identifier, newValue);
|
|
234
|
-
const switchPath = buildPath(screen, listType, identifier, indexSuffix, newValue);
|
|
270
|
+
const switchPath = buildPath(screen, listType, identifier, indexSuffix, shouldCollectValuesRef.current ? newValue : undefined);
|
|
235
271
|
RNNetmera.trackAction(switchPath);
|
|
236
272
|
} else {
|
|
237
273
|
const tapPath = buildPath(screen, listType, identifier, indexSuffix);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useRef","getCurrentScreen","RNNetmera","MAX_FIBER_DEPTH","MAX_TEXT_DEPTH","TAP_THRESHOLD","isInteractive","props","onPress","onClick","onValueChange","isSwitch","value","isInsideSelectionControl","fiber","node","return","d","memoizedProps","onSelect","textFromChildren","children","depth","undefined","t","trim","length","String","Array","isArray","child","resolveIdentifier","accessibilityLabel","testID","placeholder","resolveListIndex","startFiber","p","index","cellKey","onUnmount","flatIndex","stateNode","sections","up","i","upProps","endsWith","idx","si","count","data","type","sectionIndex","itemIndex","buildPath","screen","listType","identifier","indexSuffix","base","useTapTracking","enabled","shouldCollectValues","enabledRef","shouldCollectValuesRef","touchStartRef","lastScreenRef","switchLastLoggedRef","Map","current","clear","onTouchStart","e","
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useRef","getCurrentScreen","RNNetmera","MAX_FIBER_DEPTH","MAX_TEXT_DEPTH","TAP_THRESHOLD","isInteractive","props","onPress","onClick","onValueChange","isSwitch","value","isInsideSelectionControl","fiber","node","return","d","memoizedProps","onSelect","textFromChildren","children","depth","undefined","t","trim","length","String","Array","isArray","child","resolveIdentifier","accessibilityLabel","testID","placeholder","resolveListIndex","startFiber","p","index","cellKey","onUnmount","flatIndex","stateNode","sections","up","i","upProps","endsWith","idx","si","count","data","type","sectionIndex","itemIndex","buildPath","screen","listType","identifier","indexSuffix","switchValue","screenName","base","useTapTracking","enabled","shouldCollectValues","enabledRef","shouldCollectValuesRef","touchStartRef","lastScreenRef","switchLastLoggedRef","Map","current","clear","onTouchStart","e","_targetInst","_","x","nativeEvent","pageX","y","pageY","onTouchEnd","start","dx","Math","abs","dy","isScrollGesture","interactiveNode","identifierNode","isSwitchNode","listIndex","lastLogged","get","newValue","preTapValue","set","switchPath","trackAction","tapPath"],"sourceRoot":"../../../src","sources":["autotracking/useTapTracking.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,25 +2,30 @@ import React from 'react';
|
|
|
2
2
|
export interface NetmeraAnalyticProviderProps {
|
|
3
3
|
children: React.ReactNode;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Pass the navigation container ref so the provider can subscribe to screen
|
|
6
|
+
* changes. Required for screen tracking and for correct action event prefixes.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Use the same ref for both this prop and your NavigationContainer (or the
|
|
9
|
+
* component returned by createStaticNavigation).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* React Navigation v7 Dynamic API:
|
|
12
|
+
* const navRef = useNavigationContainerRef();
|
|
12
13
|
* <NetmeraAnalyticProvider navigationRef={navRef}>
|
|
13
14
|
* <NavigationContainer ref={navRef}>…</NavigationContainer>
|
|
14
15
|
* </NetmeraAnalyticProvider>
|
|
16
|
+
*
|
|
17
|
+
* React Navigation v7 Static API:
|
|
18
|
+
* const navRef = useNavigationContainerRef();
|
|
19
|
+
* const Navigation = createStaticNavigation(RootStack);
|
|
20
|
+
* <NetmeraAnalyticProvider navigationRef={navRef}>
|
|
21
|
+
* <Navigation ref={navRef} />
|
|
22
|
+
* </NetmeraAnalyticProvider>
|
|
15
23
|
*/
|
|
16
24
|
navigationRef?: React.RefObject<any>;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Wraps your app to enable automatic screen tracking and tap tracking.
|
|
20
|
-
*
|
|
21
|
-
* <NetmeraAnalyticProvider>
|
|
22
|
-
* <NavigationContainer>…</NavigationContainer>
|
|
23
|
-
* </NetmeraAnalyticProvider>
|
|
28
|
+
* Place it outside your NavigationContainer (or Static Navigation component).
|
|
24
29
|
*/
|
|
25
30
|
export declare function NetmeraAnalyticProvider({ children, navigationRef, }: NetmeraAnalyticProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
26
31
|
//# sourceMappingURL=NetmeraAnalyticProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetmeraAnalyticProvider.d.ts","sourceRoot":"","sources":["../../../src/NetmeraAnalyticProvider.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAsC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"NetmeraAnalyticProvider.d.ts","sourceRoot":"","sources":["../../../src/NetmeraAnalyticProvider.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAM3D,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;CACtC;AAQD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,aAAa,GACd,EAAE,4BAA4B,2CA8E9B"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import type React from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* Subscribes to React Navigation screen changes and keeps NetmeraNavigationState
|
|
4
|
-
* up to date.
|
|
4
|
+
* up to date.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Requires a valid navigation container ref — pass the same ref to both
|
|
7
|
+
* NetmeraAnalyticProvider and NavigationContainer (or the component created
|
|
8
|
+
* by createStaticNavigation).
|
|
8
9
|
*
|
|
9
|
-
* Silently no-ops
|
|
10
|
-
* Respects the
|
|
10
|
+
* Silently no-ops when navigationRef is absent or not yet populated.
|
|
11
|
+
* Respects the enabled flag — screen changes are ignored when disabled.
|
|
11
12
|
*/
|
|
12
|
-
export declare function useNavigationTracking(
|
|
13
|
+
export declare function useNavigationTracking(navigationRef: React.RefObject<any> | undefined, enabled: boolean, screenLogEnabled: boolean): void;
|
|
13
14
|
//# sourceMappingURL=useNavigationTracking.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNavigationTracking.d.ts","sourceRoot":"","sources":["../../../../src/autotracking/useNavigationTracking.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useNavigationTracking.d.ts","sourceRoot":"","sources":["../../../../src/autotracking/useNavigationTracking.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,EAC/C,OAAO,EAAE,OAAO,EAChB,gBAAgB,EAAE,OAAO,GACxB,IAAI,CAgDN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTapTracking.d.ts","sourceRoot":"","sources":["../../../../src/autotracking/useTapTracking.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"useTapTracking.d.ts","sourceRoot":"","sources":["../../../../src/autotracking/useTapTracking.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA4K1D,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO;sBA8BtC,qBAAqB;oBAuBvB,qBAAqB;EA8GzD"}
|
package/package.json
CHANGED
|
@@ -3,28 +3,32 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import React, { useEffect, useRef, useState } from 'react';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
NativeModules,
|
|
9
|
-
StyleSheet,
|
|
10
|
-
View,
|
|
11
|
-
} from 'react-native';
|
|
6
|
+
import { NativeModules, StyleSheet, View } from 'react-native';
|
|
7
|
+
import { netmeraRCTEventEmitter } from './utils/RNNetmera';
|
|
12
8
|
import { useNavigationTracking } from './autotracking/useNavigationTracking';
|
|
13
9
|
import { useTapTracking } from './autotracking/useTapTracking';
|
|
14
10
|
|
|
15
11
|
export interface NetmeraAnalyticProviderProps {
|
|
16
12
|
children: React.ReactNode;
|
|
17
13
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* Pass the navigation container ref so the provider can subscribe to screen
|
|
15
|
+
* changes. Required for screen tracking and for correct action event prefixes.
|
|
20
16
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* Use the same ref for both this prop and your NavigationContainer (or the
|
|
18
|
+
* component returned by createStaticNavigation).
|
|
23
19
|
*
|
|
24
|
-
*
|
|
20
|
+
* React Navigation v7 Dynamic API:
|
|
21
|
+
* const navRef = useNavigationContainerRef();
|
|
25
22
|
* <NetmeraAnalyticProvider navigationRef={navRef}>
|
|
26
23
|
* <NavigationContainer ref={navRef}>…</NavigationContainer>
|
|
27
24
|
* </NetmeraAnalyticProvider>
|
|
25
|
+
*
|
|
26
|
+
* React Navigation v7 Static API:
|
|
27
|
+
* const navRef = useNavigationContainerRef();
|
|
28
|
+
* const Navigation = createStaticNavigation(RootStack);
|
|
29
|
+
* <NetmeraAnalyticProvider navigationRef={navRef}>
|
|
30
|
+
* <Navigation ref={navRef} />
|
|
31
|
+
* </NetmeraAnalyticProvider>
|
|
28
32
|
*/
|
|
29
33
|
navigationRef?: React.RefObject<any>;
|
|
30
34
|
}
|
|
@@ -37,55 +41,59 @@ type AutoTrackConfig = {
|
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
43
|
* Wraps your app to enable automatic screen tracking and tap tracking.
|
|
40
|
-
*
|
|
41
|
-
* <NetmeraAnalyticProvider>
|
|
42
|
-
* <NavigationContainer>…</NavigationContainer>
|
|
43
|
-
* </NetmeraAnalyticProvider>
|
|
44
|
+
* Place it outside your NavigationContainer (or Static Navigation component).
|
|
44
45
|
*/
|
|
45
46
|
export function NetmeraAnalyticProvider({
|
|
46
47
|
children,
|
|
47
48
|
navigationRef,
|
|
48
49
|
}: NetmeraAnalyticProviderProps) {
|
|
49
|
-
const viewRef = useRef<any>(null);
|
|
50
50
|
const [config, setConfig] = useState<AutoTrackConfig | null>(null);
|
|
51
51
|
const appliedConfigRef = useRef<AutoTrackConfig | null>(null);
|
|
52
52
|
|
|
53
|
-
const applyConfig = (
|
|
54
|
-
const
|
|
53
|
+
const applyConfig = (incomingConfig: AutoTrackConfig) => {
|
|
54
|
+
const currentConfig = appliedConfigRef.current;
|
|
55
55
|
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
currentConfig?.isScreenFlowEnabled ===
|
|
57
|
+
incomingConfig.isScreenFlowEnabled &&
|
|
58
|
+
currentConfig?.isInputActionEnabled ===
|
|
59
|
+
incomingConfig.isInputActionEnabled &&
|
|
60
|
+
currentConfig?.shouldCollectValues === incomingConfig.shouldCollectValues
|
|
61
|
+
) {
|
|
60
62
|
return;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
}
|
|
64
|
+
appliedConfigRef.current = incomingConfig;
|
|
65
|
+
setConfig(incomingConfig);
|
|
66
|
+
if (__DEV__) {
|
|
67
|
+
console.log(
|
|
68
|
+
'[NMAutotrack] screen tracking',
|
|
69
|
+
incomingConfig.isScreenFlowEnabled ? 'enabled' : 'disabled'
|
|
70
|
+
);
|
|
71
|
+
console.log(
|
|
72
|
+
'[NMAutotrack] input action tracking',
|
|
73
|
+
incomingConfig.isInputActionEnabled
|
|
74
|
+
? `enabled ${incomingConfig.shouldCollectValues ? 'with' : 'without'} collection values`
|
|
75
|
+
: 'disabled'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
71
78
|
};
|
|
72
79
|
|
|
73
80
|
useEffect(() => {
|
|
74
81
|
NativeModules.RNNetmera?.getAutoTrackConfig?.()
|
|
75
|
-
?.then((
|
|
76
|
-
if (
|
|
82
|
+
?.then((fetchedConfig: AutoTrackConfig | null) => {
|
|
83
|
+
if (fetchedConfig) {
|
|
84
|
+
applyConfig(fetchedConfig);
|
|
85
|
+
}
|
|
77
86
|
})
|
|
78
87
|
?.catch((_err: unknown) => {});
|
|
79
88
|
}, []);
|
|
80
89
|
|
|
81
90
|
useEffect(() => {
|
|
82
|
-
const
|
|
83
|
-
NativeModules.RNNetmeraRCTEventEmitter
|
|
84
|
-
);
|
|
85
|
-
const subscription = emitter.addListener(
|
|
91
|
+
const subscription = netmeraRCTEventEmitter.addListener(
|
|
86
92
|
'onAutoTrackConfigUpdate',
|
|
87
|
-
(
|
|
88
|
-
if (
|
|
93
|
+
(updatedConfig: AutoTrackConfig) => {
|
|
94
|
+
if (updatedConfig) {
|
|
95
|
+
applyConfig(updatedConfig);
|
|
96
|
+
}
|
|
89
97
|
}
|
|
90
98
|
);
|
|
91
99
|
return () => subscription.remove();
|
|
@@ -95,11 +103,11 @@ export function NetmeraAnalyticProvider({
|
|
|
95
103
|
const inputActionEnabled = config?.isInputActionEnabled ?? false;
|
|
96
104
|
const shouldCollectValues = config?.shouldCollectValues ?? false;
|
|
97
105
|
|
|
98
|
-
|
|
106
|
+
// Navigation tracking is needed whenever either flag is on: screen events
|
|
107
|
+
// need it for trackView, action events need it for the screen-name prefix.
|
|
99
108
|
useNavigationTracking(
|
|
100
|
-
viewRef,
|
|
101
109
|
navigationRef,
|
|
102
|
-
|
|
110
|
+
screenFlowEnabled || inputActionEnabled,
|
|
103
111
|
screenFlowEnabled
|
|
104
112
|
);
|
|
105
113
|
const { onTouchStart, onTouchEnd } = useTapTracking(
|
|
@@ -109,10 +117,9 @@ export function NetmeraAnalyticProvider({
|
|
|
109
117
|
|
|
110
118
|
return (
|
|
111
119
|
<View
|
|
112
|
-
ref={navigationEnabled ? viewRef : undefined}
|
|
113
120
|
style={styles.container}
|
|
114
|
-
onTouchStart={onTouchStart}
|
|
115
|
-
onTouchEnd={onTouchEnd}
|
|
121
|
+
onTouchStart={inputActionEnabled ? onTouchStart : undefined}
|
|
122
|
+
onTouchEnd={inputActionEnabled ? onTouchEnd : undefined}
|
|
116
123
|
>
|
|
117
124
|
{children}
|
|
118
125
|
</View>
|
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
* Copyright (c) 2026 Netmera Research.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { useEffect, useRef } from 'react';
|
|
6
|
+
import type React from 'react';
|
|
6
7
|
import { setCurrentScreen } from './NetmeraNavigationState';
|
|
7
8
|
import { RNNetmera } from '../utils/RNNetmera';
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
const CONTEXT_PROVIDER_TAG = 10;
|
|
11
|
-
const NAV_SEARCH_DEPTH = 25;
|
|
12
|
-
|
|
13
|
-
// Duck-type check: React Navigation v5/v6/v7 all expose this surface on the container ref.
|
|
10
|
+
// Duck-type check: React Navigation v5/v6/v7 all expose this surface.
|
|
14
11
|
function isNavContainerRef(value: any): boolean {
|
|
15
12
|
return (
|
|
16
13
|
value != null &&
|
|
@@ -20,84 +17,41 @@ function isNavContainerRef(value: any): boolean {
|
|
|
20
17
|
);
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
// DFS through child fibers to find NavigationContainerRefContext.Provider.
|
|
24
|
-
// Siblings walk at the same depth — they sit at the same level in the tree.
|
|
25
|
-
function findNavContainerRef(fiber: any, depth = 0): any {
|
|
26
|
-
if (!fiber || depth > NAV_SEARCH_DEPTH) return null;
|
|
27
|
-
if (
|
|
28
|
-
fiber.tag === CONTEXT_PROVIDER_TAG &&
|
|
29
|
-
isNavContainerRef(fiber.memoizedProps?.value)
|
|
30
|
-
) {
|
|
31
|
-
return fiber.memoizedProps.value;
|
|
32
|
-
}
|
|
33
|
-
return (
|
|
34
|
-
findNavContainerRef(fiber.child, depth + 1) ??
|
|
35
|
-
findNavContainerRef(fiber.sibling, depth)
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
20
|
/**
|
|
40
21
|
* Subscribes to React Navigation screen changes and keeps NetmeraNavigationState
|
|
41
|
-
* up to date.
|
|
22
|
+
* up to date.
|
|
42
23
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
24
|
+
* Requires a valid navigation container ref — pass the same ref to both
|
|
25
|
+
* NetmeraAnalyticProvider and NavigationContainer (or the component created
|
|
26
|
+
* by createStaticNavigation).
|
|
45
27
|
*
|
|
46
|
-
* Silently no-ops
|
|
47
|
-
* Respects the
|
|
28
|
+
* Silently no-ops when navigationRef is absent or not yet populated.
|
|
29
|
+
* Respects the enabled flag — screen changes are ignored when disabled.
|
|
48
30
|
*/
|
|
49
31
|
export function useNavigationTracking(
|
|
50
|
-
viewRef: React.RefObject<any>,
|
|
51
32
|
navigationRef: React.RefObject<any> | undefined,
|
|
52
33
|
enabled: boolean,
|
|
53
34
|
screenLogEnabled: boolean
|
|
54
35
|
): void {
|
|
55
|
-
// React 18: capture own fiber during render via ReactCurrentOwner.
|
|
56
|
-
// React 19 removed ReactCurrentOwner — this silently yields null, which is fine
|
|
57
|
-
// because the Fabric path (viewRef.__internalInstanceHandle) covers React 19.
|
|
58
36
|
const screenLogEnabledRef = useRef(screenLogEnabled);
|
|
37
|
+
const lastEmittedRouteRef = useRef<string | null>(null);
|
|
59
38
|
|
|
60
39
|
useEffect(() => {
|
|
61
40
|
screenLogEnabledRef.current = screenLogEnabled;
|
|
62
41
|
}, [screenLogEnabled]);
|
|
63
42
|
|
|
64
|
-
const selfFiberRef = useRef<any>(null);
|
|
65
|
-
if (selfFiberRef.current === null) {
|
|
66
|
-
try {
|
|
67
|
-
const R = React as any;
|
|
68
|
-
selfFiberRef.current =
|
|
69
|
-
R.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactCurrentOwner
|
|
70
|
-
?.current ?? null;
|
|
71
|
-
} catch (_) {}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
43
|
useEffect(() => {
|
|
75
|
-
if (!enabled)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Path 1 — New Arch: ReactNativeElement exposes its fiber via __internalInstanceHandle.
|
|
79
|
-
// Guard with 'child' in candidate to confirm it's a fiber, not another host object.
|
|
80
|
-
const candidate = viewRef.current?.__internalInstanceHandle;
|
|
81
|
-
const viewFiber: any =
|
|
82
|
-
candidate != null && 'child' in candidate
|
|
83
|
-
? candidate
|
|
84
|
-
: (selfFiberRef.current?.child ?? null);
|
|
85
|
-
|
|
86
|
-
if (viewFiber) {
|
|
87
|
-
navRef = findNavContainerRef(viewFiber);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Path 2 — Old Arch: use the ref passed explicitly via prop.
|
|
91
|
-
if (!navRef && isNavContainerRef(navigationRef?.current)) {
|
|
92
|
-
navRef = navigationRef!.current;
|
|
44
|
+
if (!enabled || !isNavContainerRef(navigationRef?.current)) {
|
|
45
|
+
return;
|
|
93
46
|
}
|
|
94
47
|
|
|
95
|
-
|
|
48
|
+
const navRef = navigationRef!.current;
|
|
96
49
|
|
|
97
50
|
const handleChange = () => {
|
|
98
51
|
try {
|
|
99
52
|
const route = navRef.getCurrentRoute();
|
|
100
|
-
if (route?.name) {
|
|
53
|
+
if (route?.name && route.name !== lastEmittedRouteRef.current) {
|
|
54
|
+
lastEmittedRouteRef.current = route.name;
|
|
101
55
|
setCurrentScreen(route.name);
|
|
102
56
|
if (screenLogEnabledRef.current) {
|
|
103
57
|
RNNetmera.trackView(route.name);
|
|
@@ -108,9 +62,8 @@ export function useNavigationTracking(
|
|
|
108
62
|
|
|
109
63
|
const unsubState = navRef.addListener('state', handleChange);
|
|
110
64
|
|
|
111
|
-
// NavigationContainer's effects run before ours (
|
|
112
|
-
// already
|
|
113
|
-
// listener is a safety net for async startup (deferred auth, deep links, etc.).
|
|
65
|
+
// NavigationContainer's effects (child) run before ours (parent), so the ref
|
|
66
|
+
// is already populated and isReady() reflects the real mount state.
|
|
114
67
|
let unsubReady: (() => void) | undefined;
|
|
115
68
|
if (navRef.isReady()) {
|
|
116
69
|
handleChange();
|
|
@@ -126,5 +79,5 @@ export function useNavigationTracking(
|
|
|
126
79
|
unsubState?.();
|
|
127
80
|
unsubReady?.();
|
|
128
81
|
};
|
|
129
|
-
}, [enabled]);
|
|
82
|
+
}, [enabled, navigationRef]);
|
|
130
83
|
}
|
|
@@ -31,8 +31,10 @@ function isSwitch(props: any): boolean {
|
|
|
31
31
|
// Tapping them only opens the picker UI — the real select event fires separately.
|
|
32
32
|
function isInsideSelectionControl(fiber: any): boolean {
|
|
33
33
|
let node: any = fiber?.return;
|
|
34
|
-
for (let d = 0; d <
|
|
35
|
-
if (node?.memoizedProps?.onSelect != null)
|
|
34
|
+
for (let d = 0; d < 3 && node; d++, node = node.return) {
|
|
35
|
+
if (node?.memoizedProps?.onSelect != null) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
return false;
|
|
38
40
|
}
|
|
@@ -40,16 +42,22 @@ function isInsideSelectionControl(fiber: any): boolean {
|
|
|
40
42
|
// ── Identifier resolution ─────────────────────────────────────────────────────
|
|
41
43
|
|
|
42
44
|
function textFromChildren(children: any, depth = 0): string | undefined {
|
|
43
|
-
if (depth > MAX_TEXT_DEPTH)
|
|
45
|
+
if (depth > MAX_TEXT_DEPTH) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
44
48
|
if (typeof children === 'string') {
|
|
45
49
|
const t = children.trim();
|
|
46
50
|
return t.length > 0 ? t : undefined;
|
|
47
51
|
}
|
|
48
|
-
if (typeof children === 'number')
|
|
52
|
+
if (typeof children === 'number') {
|
|
53
|
+
return String(children);
|
|
54
|
+
}
|
|
49
55
|
if (Array.isArray(children)) {
|
|
50
56
|
for (const child of children) {
|
|
51
57
|
const t = textFromChildren(child, depth + 1);
|
|
52
|
-
if (t)
|
|
58
|
+
if (t) {
|
|
59
|
+
return t;
|
|
60
|
+
}
|
|
53
61
|
}
|
|
54
62
|
return undefined;
|
|
55
63
|
}
|
|
@@ -61,7 +69,9 @@ function textFromChildren(children: any, depth = 0): string | undefined {
|
|
|
61
69
|
|
|
62
70
|
// Priority: accessibilityLabel → testID → placeholder (TextInput) → first child text.
|
|
63
71
|
function resolveIdentifier(props: any): string | undefined {
|
|
64
|
-
if (!props)
|
|
72
|
+
if (!props) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
65
75
|
if (
|
|
66
76
|
typeof props.accessibilityLabel === 'string' &&
|
|
67
77
|
props.accessibilityLabel.trim()
|
|
@@ -117,8 +127,9 @@ function resolveListIndex(startFiber: any): ListIndex | undefined {
|
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
if (sections) {
|
|
120
|
-
if (cellKey.endsWith(':header') || cellKey.endsWith(':footer'))
|
|
130
|
+
if (cellKey.endsWith(':header') || cellKey.endsWith(':footer')) {
|
|
121
131
|
return undefined;
|
|
132
|
+
}
|
|
122
133
|
|
|
123
134
|
// Mirrors VirtualizedSectionList._getItem offset arithmetic:
|
|
124
135
|
// flatIndex 0 → section 0 header
|
|
@@ -128,9 +139,12 @@ function resolveListIndex(startFiber: any): ListIndex | undefined {
|
|
|
128
139
|
let idx = flatIndex - 1;
|
|
129
140
|
for (let si = 0; si < sections.length; si++) {
|
|
130
141
|
const count = sections[si].data?.length ?? 0;
|
|
131
|
-
if (idx === -1 || idx === count)
|
|
132
|
-
|
|
142
|
+
if (idx === -1 || idx === count) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
if (idx < count) {
|
|
133
146
|
return { type: 'sectionlist', sectionIndex: si, itemIndex: idx };
|
|
147
|
+
}
|
|
134
148
|
idx -= count + 2;
|
|
135
149
|
}
|
|
136
150
|
return undefined;
|
|
@@ -148,13 +162,14 @@ function buildPath(
|
|
|
148
162
|
listType: string | null,
|
|
149
163
|
identifier: string,
|
|
150
164
|
indexSuffix: string,
|
|
151
|
-
|
|
165
|
+
switchValue?: boolean
|
|
152
166
|
): string {
|
|
167
|
+
const screenName = screen ?? 'unknown';
|
|
153
168
|
const base = listType
|
|
154
|
-
? `${
|
|
155
|
-
: `${
|
|
156
|
-
return
|
|
157
|
-
? `${base}|${
|
|
169
|
+
? `${screenName}|${listType}|${identifier}`
|
|
170
|
+
: `${screenName}|${identifier}`;
|
|
171
|
+
return switchValue !== undefined
|
|
172
|
+
? `${base}|${switchValue}${indexSuffix}`
|
|
158
173
|
: `${base}${indexSuffix}`;
|
|
159
174
|
}
|
|
160
175
|
|
|
@@ -167,6 +182,7 @@ export function useTapTracking(enabled: boolean, shouldCollectValues: boolean) {
|
|
|
167
182
|
x: number;
|
|
168
183
|
y: number;
|
|
169
184
|
switchValue?: boolean;
|
|
185
|
+
isSwitch?: boolean;
|
|
170
186
|
} | null>(null);
|
|
171
187
|
const lastScreenRef = useRef<string | null | undefined>(undefined);
|
|
172
188
|
|
|
@@ -190,7 +206,9 @@ export function useTapTracking(enabled: boolean, shouldCollectValues: boolean) {
|
|
|
190
206
|
}, [shouldCollectValues]);
|
|
191
207
|
|
|
192
208
|
const onTouchStart = useCallback((e: GestureResponderEvent) => {
|
|
193
|
-
if (!enabledRef.current)
|
|
209
|
+
if (!enabledRef.current) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
194
212
|
let switchValue: boolean | undefined;
|
|
195
213
|
try {
|
|
196
214
|
let fiber: any = (e as any)._targetInst;
|
|
@@ -206,21 +224,30 @@ export function useTapTracking(enabled: boolean, shouldCollectValues: boolean) {
|
|
|
206
224
|
x: e.nativeEvent.pageX,
|
|
207
225
|
y: e.nativeEvent.pageY,
|
|
208
226
|
switchValue,
|
|
227
|
+
isSwitch: switchValue !== undefined,
|
|
209
228
|
};
|
|
210
229
|
}, []);
|
|
211
230
|
|
|
212
231
|
const onTouchEnd = useCallback((e: GestureResponderEvent) => {
|
|
213
|
-
if (!enabledRef.current)
|
|
232
|
+
if (!enabledRef.current) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
214
235
|
const start = touchStartRef.current;
|
|
215
|
-
if (start) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
if (!start) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const dx = Math.abs(e.nativeEvent.pageX - start.x);
|
|
240
|
+
const dy = Math.abs(e.nativeEvent.pageY - start.y);
|
|
241
|
+
const isScrollGesture = dx > TAP_THRESHOLD || dy > TAP_THRESHOLD;
|
|
242
|
+
if (isScrollGesture && !start.isSwitch) {
|
|
243
|
+
return;
|
|
219
244
|
}
|
|
220
245
|
|
|
221
246
|
try {
|
|
222
247
|
let node: any = (e as any)._targetInst;
|
|
223
|
-
if (!node)
|
|
248
|
+
if (!node) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
224
251
|
|
|
225
252
|
// Pass 1: walk up to find the first interactive ancestor.
|
|
226
253
|
// e.g. tapping a <Text> inside a <Pressable> yields the Pressable.
|
|
@@ -234,7 +261,9 @@ export function useTapTracking(enabled: boolean, shouldCollectValues: boolean) {
|
|
|
234
261
|
node = node.return;
|
|
235
262
|
depth++;
|
|
236
263
|
}
|
|
237
|
-
if (!interactiveNode)
|
|
264
|
+
if (!interactiveNode) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
238
267
|
|
|
239
268
|
// Pass 2: resolve identifier from the interactive fiber and up to 2 ancestors.
|
|
240
269
|
// The look-ahead handles TouchableOpacity, where the inner Animated.View fiber
|
|
@@ -243,14 +272,20 @@ export function useTapTracking(enabled: boolean, shouldCollectValues: boolean) {
|
|
|
243
272
|
let identifier: string | undefined;
|
|
244
273
|
for (let i = 0; i < 3 && identifierNode; i++) {
|
|
245
274
|
identifier = resolveIdentifier(identifierNode.memoizedProps);
|
|
246
|
-
if (identifier)
|
|
275
|
+
if (identifier) {
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
247
278
|
identifierNode = identifierNode.return;
|
|
248
279
|
}
|
|
249
280
|
|
|
250
|
-
if (!identifier)
|
|
281
|
+
if (!identifier) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
251
284
|
|
|
252
285
|
const isSwitchNode = isSwitch(interactiveNode.memoizedProps);
|
|
253
|
-
if (!isSwitchNode && isInsideSelectionControl(interactiveNode))
|
|
286
|
+
if (!isSwitchNode && isInsideSelectionControl(interactiveNode)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
254
289
|
|
|
255
290
|
const screen = getCurrentScreen();
|
|
256
291
|
if (screen !== lastScreenRef.current) {
|
|
@@ -290,7 +325,7 @@ export function useTapTracking(enabled: boolean, shouldCollectValues: boolean) {
|
|
|
290
325
|
listType,
|
|
291
326
|
identifier,
|
|
292
327
|
indexSuffix,
|
|
293
|
-
newValue
|
|
328
|
+
shouldCollectValuesRef.current ? newValue : undefined
|
|
294
329
|
);
|
|
295
330
|
RNNetmera.trackAction(switchPath);
|
|
296
331
|
} else {
|