@truewatchtech/mobile-react-navigation 0.0.0-stage → 0.4.3-alpha.1
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 +101 -2
- package/lib/commonjs/FTRumReactNavigationTracking.js +195 -0
- package/lib/commonjs/FTRumReactNavigationTracking.js.map +1 -0
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/FTRumReactNavigationTracking.js +188 -0
- package/lib/module/FTRumReactNavigationTracking.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/FTRumReactNavigationTracking.d.ts +50 -0
- package/lib/typescript/index.d.ts +2 -0
- package/package.json +64 -4
- package/src/FTRumReactNavigationTracking.ts +238 -0
- package/src/index.ts +2 -0
package/README.md
CHANGED
|
@@ -1,3 +1,102 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TrueWatch React Navigation plugin
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This plugin tracks React Navigation route changes as TrueWatch RUM Views and
|
|
4
|
+
measures native-stack transition loading.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
yarn add @truewatchtech/react-native-mobile @truewatchtech/mobile-react-navigation
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Keep your existing React Navigation dependencies. This package contains only
|
|
13
|
+
JavaScript and types; it adds no native linking or build configuration.
|
|
14
|
+
Initialize the mobile SDK and RUM before enabling View tracking.
|
|
15
|
+
|
|
16
|
+
## Route tracking
|
|
17
|
+
|
|
18
|
+
Use this mode for route changes, including nested navigators and tabs. It records
|
|
19
|
+
the current leaf route when the container is ready, then listens for changes.
|
|
20
|
+
It does not invent a loading duration when no transition boundary is available.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import {
|
|
24
|
+
NavigationContainer,
|
|
25
|
+
createNavigationContainerRef,
|
|
26
|
+
} from '@react-navigation/native';
|
|
27
|
+
import { FTRumReactNavigationTracking } from '@truewatchtech/mobile-react-navigation';
|
|
28
|
+
|
|
29
|
+
const navigationRef = createNavigationContainerRef<RootStackParamList>();
|
|
30
|
+
|
|
31
|
+
<NavigationContainer
|
|
32
|
+
ref={navigationRef}
|
|
33
|
+
onReady={() => FTRumReactNavigationTracking.startTrackingViews(navigationRef)}
|
|
34
|
+
>
|
|
35
|
+
{/* navigators */}
|
|
36
|
+
</NavigationContainer>;
|
|
37
|
+
|
|
38
|
+
// Before disposing this navigation container or shutting down the SDK:
|
|
39
|
+
FTRumReactNavigationTracking.stopTrackingViews(navigationRef);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Calling `startTrackingViews` repeatedly with the same ref registers once.
|
|
43
|
+
Registering another ref disposes the previous registration. A cleanup using an
|
|
44
|
+
old ref cannot stop the new registration.
|
|
45
|
+
|
|
46
|
+
## Transition loading
|
|
47
|
+
|
|
48
|
+
Choose this mode instead of `startTrackingViews` when using
|
|
49
|
+
`@react-navigation/native-stack`. Attach the listeners to the stack that owns the
|
|
50
|
+
screen transitions:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { FTRumReactNavigationTracking } from '@truewatchtech/mobile-react-navigation';
|
|
54
|
+
|
|
55
|
+
<Stack.Navigator screenListeners={FTRumReactNavigationTracking.StackListener}>
|
|
56
|
+
{/* screens */}
|
|
57
|
+
</Stack.Navigator>;
|
|
58
|
+
|
|
59
|
+
// Before disposing the tracked stack or shutting down the SDK:
|
|
60
|
+
FTRumReactNavigationTracking.stopTrackingViews();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Views start when the selected route changes, including the initial screen and
|
|
64
|
+
screens without animations. Loading is recorded only for a route instance's
|
|
65
|
+
first appearance, from its first observed `focus` to the matching opening
|
|
66
|
+
`transitionEnd`. The interval uses a monotonic clock and is reported in
|
|
67
|
+
nanoseconds; for example, 500 ms is 500,000,000 ns. Repeated focus events do not
|
|
68
|
+
replace the original start, and `transitionStart` is not required.
|
|
69
|
+
|
|
70
|
+
Returning to an existing route, changing tabs, and foreground recovery create
|
|
71
|
+
Views without repeating loading measurements. An outer stack's transition is
|
|
72
|
+
never assigned to a nested leaf screen. Missing, cancelled, backgrounded, or
|
|
73
|
+
invalid intervals are discarded. Loading is applied only while the same View
|
|
74
|
+
appearance remains active.
|
|
75
|
+
|
|
76
|
+
This measures the interval between JavaScript focus and transition-end callbacks. It does not
|
|
77
|
+
measure network completion or when all screen content is ready, and native-to-JS
|
|
78
|
+
event delivery delays can affect the observed interval.
|
|
79
|
+
|
|
80
|
+
Use one mode on one root container/owning stack at a time. Do not attach separate
|
|
81
|
+
timing listeners to every level of a nested navigator. Starting route tracking
|
|
82
|
+
switches off stack tracking, and acquiring `StackListener` switches to stack
|
|
83
|
+
tracking. After disposal, acquire `StackListener` again when mounting a new stack;
|
|
84
|
+
callbacks retained from the disposed registration are ignored.
|
|
85
|
+
|
|
86
|
+
## Lifecycle
|
|
87
|
+
|
|
88
|
+
Both modes use the SDK's shared automatic View lifecycle. Native handles closing
|
|
89
|
+
Views in the background. Foreground recovery starts the current screen once
|
|
90
|
+
without reusing old loading measurements. Route names become View names; route
|
|
91
|
+
keys distinguish separate appearances of screens with the same name.
|
|
92
|
+
|
|
93
|
+
## Migration
|
|
94
|
+
|
|
95
|
+
Replace the copied `example/src/FTRumReactNavigationTracking.tsx` import with
|
|
96
|
+
`@truewatchtech/mobile-react-navigation`, retaining the class name and methods.
|
|
97
|
+
Remove the copied implementation so it cannot collect the same Views twice. Applications
|
|
98
|
+
using only this plugin do not need `react-native-navigation` or its TrueWatch plugin.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
Apache 2.0
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.FTRumReactNavigationTracking = void 0;
|
|
7
|
+
var _reactNativeMobile = require("@truewatchtech/react-native-mobile");
|
|
8
|
+
var _FTRumReactNavigationTracking;
|
|
9
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
10
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
11
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
12
|
+
const MAX_TRANSITION_MS = 15 * 60 * 1000;
|
|
13
|
+
/** One active navigation container, using either route or stack tracking. */
|
|
14
|
+
class FTRumReactNavigationTracking {
|
|
15
|
+
/** Call from NavigationContainer.onReady. This mode does not measure loading. */
|
|
16
|
+
static startTrackingViews(ref) {
|
|
17
|
+
if (!ref || this.registeredContainer === ref) return;
|
|
18
|
+
this.stopTrackingViews();
|
|
19
|
+
this.registeredContainer = ref;
|
|
20
|
+
this.tracker.start();
|
|
21
|
+
const lifecycle = this.lifecycle;
|
|
22
|
+
this.unsubscribe = ref.addListener('state', () => {
|
|
23
|
+
if (lifecycle !== this.lifecycle) return;
|
|
24
|
+
const route = ref.getCurrentRoute();
|
|
25
|
+
if (route) this.show(route);
|
|
26
|
+
});
|
|
27
|
+
const route = ref.getCurrentRoute();
|
|
28
|
+
if (route) this.show(route);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Omit ref to dispose stack tracking. Old containers cannot stop a new one. */
|
|
32
|
+
static stopTrackingViews(ref) {
|
|
33
|
+
var _this$unsubscribe;
|
|
34
|
+
if (ref !== undefined && ref !== this.registeredContainer) return;
|
|
35
|
+
++this.lifecycle;
|
|
36
|
+
(_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 || _this$unsubscribe.call(this);
|
|
37
|
+
this.unsubscribe = undefined;
|
|
38
|
+
this.registeredContainer = null;
|
|
39
|
+
this.stackListeners = undefined;
|
|
40
|
+
this.tracker.stop();
|
|
41
|
+
this.visited.clear();
|
|
42
|
+
this.appearance = undefined;
|
|
43
|
+
this.transition = undefined;
|
|
44
|
+
this.trackRouter = undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Track Views on state changes; time first focus through opening transitionEnd. */
|
|
48
|
+
static get StackListener() {
|
|
49
|
+
if (this.stackListeners) return this.stackListeners;
|
|
50
|
+
this.stopTrackingViews();
|
|
51
|
+
const lifecycle = this.lifecycle;
|
|
52
|
+
const current = () => lifecycle === this.lifecycle;
|
|
53
|
+
this.stackListeners = {
|
|
54
|
+
transitionStart: event => {
|
|
55
|
+
var _event$data;
|
|
56
|
+
if (current() && event.target && ((_event$data = event.data) === null || _event$data === void 0 ? void 0 : _event$data.closing) === true) this.cancelTransition(event.target);
|
|
57
|
+
},
|
|
58
|
+
transitionEnd: event => {
|
|
59
|
+
var _event$data2, _event$data3, _this$transition, _this$appearance;
|
|
60
|
+
const end = this.monotonicNow();
|
|
61
|
+
if (!current() || !event.target) return;
|
|
62
|
+
if (((_event$data2 = event.data) === null || _event$data2 === void 0 ? void 0 : _event$data2.closing) === true) {
|
|
63
|
+
this.cancelTransition(event.target);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (((_event$data3 = event.data) === null || _event$data3 === void 0 ? void 0 : _event$data3.closing) !== false) {
|
|
67
|
+
this.cancelTransition(event.target);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (((_this$transition = this.transition) === null || _this$transition === void 0 ? void 0 : _this$transition.target) === event.target) {
|
|
71
|
+
// Capture the end now, even when the matching state arrives later.
|
|
72
|
+
if (this.transition.end === undefined) this.transition.end = end;
|
|
73
|
+
this.finishTransition();
|
|
74
|
+
} else if (((_this$appearance = this.appearance) === null || _this$appearance === void 0 ? void 0 : _this$appearance.target) === event.target) {
|
|
75
|
+
// No observed focus: do not infer a start from a later callback.
|
|
76
|
+
this.appearance.first = false;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
gestureCancel: () => {
|
|
80
|
+
if (!current()) return;
|
|
81
|
+
this.transition = undefined;
|
|
82
|
+
if (this.appearance) this.appearance.first = false;
|
|
83
|
+
},
|
|
84
|
+
state: event => {
|
|
85
|
+
var _event$data4, _state$index;
|
|
86
|
+
if (!current()) return;
|
|
87
|
+
const state = (_event$data4 = event.data) === null || _event$data4 === void 0 ? void 0 : _event$data4.state;
|
|
88
|
+
if (!state) return;
|
|
89
|
+
const outer = state.routes[(_state$index = state.index) !== null && _state$index !== void 0 ? _state$index : 0];
|
|
90
|
+
const leaf = this.leafRoute(outer);
|
|
91
|
+
if (!outer || !leaf) {
|
|
92
|
+
this.tracker.stop();
|
|
93
|
+
this.visited.clear();
|
|
94
|
+
this.appearance = undefined;
|
|
95
|
+
this.transition = undefined;
|
|
96
|
+
this.trackRouter = undefined;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Retain instance history only while its route remains in this stack.
|
|
100
|
+
const keys = new Set(state.routes.map(route => route.key));
|
|
101
|
+
for (const key of this.visited) {
|
|
102
|
+
if (!keys.has(key)) this.visited.delete(key);
|
|
103
|
+
}
|
|
104
|
+
if (this.transition && this.transition.target !== outer.key) this.transition = undefined;
|
|
105
|
+
const previous = this.appearance;
|
|
106
|
+
if ((previous === null || previous === void 0 ? void 0 : previous.target) !== outer.key || previous.route.key !== leaf.key) {
|
|
107
|
+
const route = {
|
|
108
|
+
key: leaf.key,
|
|
109
|
+
name: leaf.name
|
|
110
|
+
};
|
|
111
|
+
const first = !this.visited.has(outer.key) && outer.key === leaf.key;
|
|
112
|
+
this.visited.add(outer.key);
|
|
113
|
+
this.appearance = {
|
|
114
|
+
target: outer.key,
|
|
115
|
+
route,
|
|
116
|
+
first,
|
|
117
|
+
generation: this.show(route)
|
|
118
|
+
};
|
|
119
|
+
} else {
|
|
120
|
+
this.tracker.setCurrent(previous.route);
|
|
121
|
+
}
|
|
122
|
+
this.finishTransition();
|
|
123
|
+
},
|
|
124
|
+
focus: event => {
|
|
125
|
+
var _this$transition3;
|
|
126
|
+
const start = this.monotonicNow();
|
|
127
|
+
if (!current() || !event.target) return;
|
|
128
|
+
this.tracker.start();
|
|
129
|
+
const appearance = this.appearance;
|
|
130
|
+
// A newly selected route invalidates the previous appearance even if
|
|
131
|
+
// React Navigation has not delivered the matching state update yet.
|
|
132
|
+
if ((appearance === null || appearance === void 0 ? void 0 : appearance.target) !== event.target) {
|
|
133
|
+
var _this$transition2;
|
|
134
|
+
if (appearance) appearance.first = false;
|
|
135
|
+
if (((_this$transition2 = this.transition) === null || _this$transition2 === void 0 ? void 0 : _this$transition2.target) !== event.target) this.transition = undefined;
|
|
136
|
+
}
|
|
137
|
+
// Keep the first focus boundary. Existing instances (including nested
|
|
138
|
+
// containers) may open Views again, but never start another load timer.
|
|
139
|
+
if (this.visited.has(event.target) && ((appearance === null || appearance === void 0 ? void 0 : appearance.target) !== event.target || !appearance.first) || ((_this$transition3 = this.transition) === null || _this$transition3 === void 0 ? void 0 : _this$transition3.target) === event.target) return;
|
|
140
|
+
this.transition = {
|
|
141
|
+
target: event.target,
|
|
142
|
+
start
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
return this.stackListeners;
|
|
147
|
+
}
|
|
148
|
+
static finishTransition() {
|
|
149
|
+
const transition = this.transition;
|
|
150
|
+
const appearance = this.appearance;
|
|
151
|
+
if (!transition || transition.end === undefined || !appearance || appearance.target !== transition.target) return;
|
|
152
|
+
this.transition = undefined;
|
|
153
|
+
const eligible = appearance.first;
|
|
154
|
+
appearance.first = false;
|
|
155
|
+
const elapsed = transition.end - transition.start;
|
|
156
|
+
if (!eligible || !Number.isFinite(elapsed) || elapsed <= 0 || elapsed >= MAX_TRANSITION_MS) return;
|
|
157
|
+
this.tracker.updateLoadingTime(appearance.route.key, appearance.generation, Math.floor(elapsed * 1000000));
|
|
158
|
+
}
|
|
159
|
+
static cancelTransition(target) {
|
|
160
|
+
var _this$transition4, _this$appearance2;
|
|
161
|
+
if (((_this$transition4 = this.transition) === null || _this$transition4 === void 0 ? void 0 : _this$transition4.target) === target) this.transition = undefined;
|
|
162
|
+
if (((_this$appearance2 = this.appearance) === null || _this$appearance2 === void 0 ? void 0 : _this$appearance2.target) === target) this.appearance.first = false;
|
|
163
|
+
}
|
|
164
|
+
static monotonicNow() {
|
|
165
|
+
return typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : NaN;
|
|
166
|
+
}
|
|
167
|
+
static leafRoute(route) {
|
|
168
|
+
while ((_route = route) !== null && _route !== void 0 && _route.state) {
|
|
169
|
+
var _route, _route$state$index;
|
|
170
|
+
const child = route.state.routes[(_route$state$index = route.state.index) !== null && _route$state$index !== void 0 ? _route$state$index : 0];
|
|
171
|
+
if (!child) break;
|
|
172
|
+
route = child;
|
|
173
|
+
}
|
|
174
|
+
return route;
|
|
175
|
+
}
|
|
176
|
+
static show(route) {
|
|
177
|
+
this.trackRouter = route;
|
|
178
|
+
return this.tracker.show(route);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.FTRumReactNavigationTracking = FTRumReactNavigationTracking;
|
|
182
|
+
_FTRumReactNavigationTracking = FTRumReactNavigationTracking;
|
|
183
|
+
_defineProperty(FTRumReactNavigationTracking, "tracker", new _reactNativeMobile.FTRumViewTracking(() => {
|
|
184
|
+
_FTRumReactNavigationTracking.transition = undefined;
|
|
185
|
+
if (_FTRumReactNavigationTracking.appearance) _FTRumReactNavigationTracking.appearance.first = false;
|
|
186
|
+
}));
|
|
187
|
+
_defineProperty(FTRumReactNavigationTracking, "registeredContainer", null);
|
|
188
|
+
_defineProperty(FTRumReactNavigationTracking, "unsubscribe", void 0);
|
|
189
|
+
_defineProperty(FTRumReactNavigationTracking, "lifecycle", 0);
|
|
190
|
+
_defineProperty(FTRumReactNavigationTracking, "stackListeners", void 0);
|
|
191
|
+
_defineProperty(FTRumReactNavigationTracking, "visited", new Set());
|
|
192
|
+
_defineProperty(FTRumReactNavigationTracking, "appearance", void 0);
|
|
193
|
+
_defineProperty(FTRumReactNavigationTracking, "transition", void 0);
|
|
194
|
+
_defineProperty(FTRumReactNavigationTracking, "trackRouter", void 0);
|
|
195
|
+
//# sourceMappingURL=FTRumReactNavigationTracking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_reactNativeMobile","require","_FTRumReactNavigationTracking","_defineProperty","e","r","t","_toPropertyKey","Object","defineProperty","value","enumerable","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","MAX_TRANSITION_MS","FTRumReactNavigationTracking","startTrackingViews","ref","registeredContainer","stopTrackingViews","tracker","start","lifecycle","unsubscribe","addListener","route","getCurrentRoute","show","_this$unsubscribe","undefined","stackListeners","stop","visited","clear","appearance","transition","trackRouter","StackListener","current","transitionStart","event","_event$data","target","data","closing","cancelTransition","transitionEnd","_event$data2","_event$data3","_this$transition","_this$appearance","end","monotonicNow","finishTransition","first","gestureCancel","state","_event$data4","_state$index","outer","routes","index","leaf","leafRoute","keys","Set","map","key","has","delete","previous","name","add","generation","setCurrent","focus","_this$transition3","_this$transition2","eligible","elapsed","isFinite","updateLoadingTime","Math","floor","_this$transition4","_this$appearance2","performance","now","NaN","_route","_route$state$index","child","exports","FTRumViewTracking"],"sources":["FTRumReactNavigationTracking.ts"],"sourcesContent":["import type {\n NavigationContainerRef,\n ParamListBase,\n} from '@react-navigation/native';\nimport { FTRumViewTracking } from '@truewatchtech/react-native-mobile';\n\ntype TrackedRoute = {\n key: string;\n name: string;\n state?: { index?: number; routes: readonly TrackedRoute[] };\n};\ntype StackEvent = {\n target?: string;\n data?: {\n closing?: boolean;\n state?: { index?: number; routes: readonly TrackedRoute[] };\n };\n};\ntype Appearance = {\n target: string;\n route: { key: string; name: string };\n generation?: number;\n first: boolean;\n};\ntype Transition = { target: string; start: number; end?: number };\nconst MAX_TRANSITION_MS = 15 * 60 * 1000;\n\nexport type FTRumStackListeners = {\n focus(event: StackEvent): void;\n state(event: StackEvent): void;\n transitionStart(event: StackEvent): void;\n transitionEnd(event: StackEvent): void;\n gestureCancel(event: StackEvent): void;\n};\n\n/** One active navigation container, using either route or stack tracking. */\nexport class FTRumReactNavigationTracking {\n private static tracker = new FTRumViewTracking(() => {\n this.transition = undefined;\n if (this.appearance) this.appearance.first = false;\n });\n private static registeredContainer: object | null = null;\n private static unsubscribe?: () => void;\n private static lifecycle = 0;\n private static stackListeners?: FTRumStackListeners;\n private static visited = new Set<string>();\n private static appearance?: Appearance;\n private static transition?: Transition;\n static trackRouter: TrackedRoute | undefined;\n\n /** Call from NavigationContainer.onReady. This mode does not measure loading. */\n static startTrackingViews<ParamList extends ParamListBase>(\n ref: NavigationContainerRef<ParamList> | null\n ): void {\n if (!ref || this.registeredContainer === ref) return;\n this.stopTrackingViews();\n this.registeredContainer = ref;\n this.tracker.start();\n const lifecycle = this.lifecycle;\n this.unsubscribe = ref.addListener('state', () => {\n if (lifecycle !== this.lifecycle) return;\n const route = ref.getCurrentRoute();\n if (route) this.show(route);\n });\n const route = ref.getCurrentRoute();\n if (route) this.show(route);\n }\n\n /** Omit ref to dispose stack tracking. Old containers cannot stop a new one. */\n static stopTrackingViews(ref?: object | null): void {\n if (ref !== undefined && ref !== this.registeredContainer) return;\n ++this.lifecycle;\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n this.registeredContainer = null;\n this.stackListeners = undefined;\n this.tracker.stop();\n this.visited.clear();\n this.appearance = undefined;\n this.transition = undefined;\n this.trackRouter = undefined;\n }\n\n /** Track Views on state changes; time first focus through opening transitionEnd. */\n static get StackListener(): FTRumStackListeners {\n if (this.stackListeners) return this.stackListeners;\n this.stopTrackingViews();\n const lifecycle = this.lifecycle;\n const current = (): boolean => lifecycle === this.lifecycle;\n this.stackListeners = {\n transitionStart: (event): void => {\n if (current() && event.target && event.data?.closing === true)\n this.cancelTransition(event.target);\n },\n transitionEnd: (event): void => {\n const end = this.monotonicNow();\n if (!current() || !event.target) return;\n if (event.data?.closing === true) {\n this.cancelTransition(event.target);\n return;\n }\n if (event.data?.closing !== false) {\n this.cancelTransition(event.target);\n return;\n }\n if (this.transition?.target === event.target) {\n // Capture the end now, even when the matching state arrives later.\n if (this.transition.end === undefined) this.transition.end = end;\n this.finishTransition();\n } else if (this.appearance?.target === event.target) {\n // No observed focus: do not infer a start from a later callback.\n this.appearance.first = false;\n }\n },\n gestureCancel: (): void => {\n if (!current()) return;\n this.transition = undefined;\n if (this.appearance) this.appearance.first = false;\n },\n state: (event): void => {\n if (!current()) return;\n const state = event.data?.state;\n if (!state) return;\n const outer = state.routes[state.index ?? 0];\n const leaf = this.leafRoute(outer);\n if (!outer || !leaf) {\n this.tracker.stop();\n this.visited.clear();\n this.appearance = undefined;\n this.transition = undefined;\n this.trackRouter = undefined;\n return;\n }\n // Retain instance history only while its route remains in this stack.\n const keys = new Set(state.routes.map((route) => route.key));\n for (const key of this.visited) {\n if (!keys.has(key)) this.visited.delete(key);\n }\n if (this.transition && this.transition.target !== outer.key)\n this.transition = undefined;\n const previous = this.appearance;\n if (previous?.target !== outer.key || previous.route.key !== leaf.key) {\n const route = { key: leaf.key, name: leaf.name };\n const first = !this.visited.has(outer.key) && outer.key === leaf.key;\n this.visited.add(outer.key);\n this.appearance = {\n target: outer.key,\n route,\n first,\n generation: this.show(route),\n };\n } else {\n this.tracker.setCurrent(previous.route);\n }\n this.finishTransition();\n },\n focus: (event): void => {\n const start = this.monotonicNow();\n if (!current() || !event.target) return;\n this.tracker.start();\n const appearance = this.appearance;\n // A newly selected route invalidates the previous appearance even if\n // React Navigation has not delivered the matching state update yet.\n if (appearance?.target !== event.target) {\n if (appearance) appearance.first = false;\n if (this.transition?.target !== event.target)\n this.transition = undefined;\n }\n // Keep the first focus boundary. Existing instances (including nested\n // containers) may open Views again, but never start another load timer.\n if (\n (this.visited.has(event.target) &&\n (appearance?.target !== event.target || !appearance.first)) ||\n this.transition?.target === event.target\n )\n return;\n this.transition = { target: event.target, start };\n },\n };\n return this.stackListeners;\n }\n\n private static finishTransition(): void {\n const transition = this.transition;\n const appearance = this.appearance;\n if (\n !transition ||\n transition.end === undefined ||\n !appearance ||\n appearance.target !== transition.target\n )\n return;\n this.transition = undefined;\n const eligible = appearance.first;\n appearance.first = false;\n const elapsed = transition.end - transition.start;\n if (\n !eligible ||\n !Number.isFinite(elapsed) ||\n elapsed <= 0 ||\n elapsed >= MAX_TRANSITION_MS\n )\n return;\n this.tracker.updateLoadingTime(\n appearance.route.key,\n appearance.generation,\n Math.floor(elapsed * 1000000)\n );\n }\n\n private static cancelTransition(target: string): void {\n if (this.transition?.target === target) this.transition = undefined;\n if (this.appearance?.target === target) this.appearance.first = false;\n }\n\n private static monotonicNow(): number {\n return typeof performance !== 'undefined' &&\n typeof performance.now === 'function'\n ? performance.now()\n : NaN;\n }\n\n private static leafRoute(\n route: TrackedRoute | undefined\n ): TrackedRoute | undefined {\n while (route?.state) {\n const child = route.state.routes[route.state.index ?? 0];\n if (!child) break;\n route = child;\n }\n return route;\n }\n\n private static show(route: TrackedRoute): number | undefined {\n this.trackRouter = route;\n return this.tracker.show(route);\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "FTRumReactNavigationTracking", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _FTRumReactNavigationTracking.FTRumReactNavigationTracking;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _FTRumReactNavigationTracking = require("./FTRumReactNavigationTracking");
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_FTRumReactNavigationTracking","require"],"sources":["index.ts"],"sourcesContent":["export { FTRumReactNavigationTracking } from './FTRumReactNavigationTracking';\nexport type { FTRumStackListeners } from './FTRumReactNavigationTracking';\n"],"mappings":";;;;;;;;;;;AAAA,IAAAA,6BAAA,GAAAC,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
var _FTRumReactNavigationTracking;
|
|
2
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
3
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
4
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
5
|
+
import { FTRumViewTracking } from '@truewatchtech/react-native-mobile';
|
|
6
|
+
const MAX_TRANSITION_MS = 15 * 60 * 1000;
|
|
7
|
+
/** One active navigation container, using either route or stack tracking. */
|
|
8
|
+
export class FTRumReactNavigationTracking {
|
|
9
|
+
/** Call from NavigationContainer.onReady. This mode does not measure loading. */
|
|
10
|
+
static startTrackingViews(ref) {
|
|
11
|
+
if (!ref || this.registeredContainer === ref) return;
|
|
12
|
+
this.stopTrackingViews();
|
|
13
|
+
this.registeredContainer = ref;
|
|
14
|
+
this.tracker.start();
|
|
15
|
+
const lifecycle = this.lifecycle;
|
|
16
|
+
this.unsubscribe = ref.addListener('state', () => {
|
|
17
|
+
if (lifecycle !== this.lifecycle) return;
|
|
18
|
+
const route = ref.getCurrentRoute();
|
|
19
|
+
if (route) this.show(route);
|
|
20
|
+
});
|
|
21
|
+
const route = ref.getCurrentRoute();
|
|
22
|
+
if (route) this.show(route);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Omit ref to dispose stack tracking. Old containers cannot stop a new one. */
|
|
26
|
+
static stopTrackingViews(ref) {
|
|
27
|
+
var _this$unsubscribe;
|
|
28
|
+
if (ref !== undefined && ref !== this.registeredContainer) return;
|
|
29
|
+
++this.lifecycle;
|
|
30
|
+
(_this$unsubscribe = this.unsubscribe) === null || _this$unsubscribe === void 0 || _this$unsubscribe.call(this);
|
|
31
|
+
this.unsubscribe = undefined;
|
|
32
|
+
this.registeredContainer = null;
|
|
33
|
+
this.stackListeners = undefined;
|
|
34
|
+
this.tracker.stop();
|
|
35
|
+
this.visited.clear();
|
|
36
|
+
this.appearance = undefined;
|
|
37
|
+
this.transition = undefined;
|
|
38
|
+
this.trackRouter = undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Track Views on state changes; time first focus through opening transitionEnd. */
|
|
42
|
+
static get StackListener() {
|
|
43
|
+
if (this.stackListeners) return this.stackListeners;
|
|
44
|
+
this.stopTrackingViews();
|
|
45
|
+
const lifecycle = this.lifecycle;
|
|
46
|
+
const current = () => lifecycle === this.lifecycle;
|
|
47
|
+
this.stackListeners = {
|
|
48
|
+
transitionStart: event => {
|
|
49
|
+
var _event$data;
|
|
50
|
+
if (current() && event.target && ((_event$data = event.data) === null || _event$data === void 0 ? void 0 : _event$data.closing) === true) this.cancelTransition(event.target);
|
|
51
|
+
},
|
|
52
|
+
transitionEnd: event => {
|
|
53
|
+
var _event$data2, _event$data3, _this$transition, _this$appearance;
|
|
54
|
+
const end = this.monotonicNow();
|
|
55
|
+
if (!current() || !event.target) return;
|
|
56
|
+
if (((_event$data2 = event.data) === null || _event$data2 === void 0 ? void 0 : _event$data2.closing) === true) {
|
|
57
|
+
this.cancelTransition(event.target);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (((_event$data3 = event.data) === null || _event$data3 === void 0 ? void 0 : _event$data3.closing) !== false) {
|
|
61
|
+
this.cancelTransition(event.target);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (((_this$transition = this.transition) === null || _this$transition === void 0 ? void 0 : _this$transition.target) === event.target) {
|
|
65
|
+
// Capture the end now, even when the matching state arrives later.
|
|
66
|
+
if (this.transition.end === undefined) this.transition.end = end;
|
|
67
|
+
this.finishTransition();
|
|
68
|
+
} else if (((_this$appearance = this.appearance) === null || _this$appearance === void 0 ? void 0 : _this$appearance.target) === event.target) {
|
|
69
|
+
// No observed focus: do not infer a start from a later callback.
|
|
70
|
+
this.appearance.first = false;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
gestureCancel: () => {
|
|
74
|
+
if (!current()) return;
|
|
75
|
+
this.transition = undefined;
|
|
76
|
+
if (this.appearance) this.appearance.first = false;
|
|
77
|
+
},
|
|
78
|
+
state: event => {
|
|
79
|
+
var _event$data4, _state$index;
|
|
80
|
+
if (!current()) return;
|
|
81
|
+
const state = (_event$data4 = event.data) === null || _event$data4 === void 0 ? void 0 : _event$data4.state;
|
|
82
|
+
if (!state) return;
|
|
83
|
+
const outer = state.routes[(_state$index = state.index) !== null && _state$index !== void 0 ? _state$index : 0];
|
|
84
|
+
const leaf = this.leafRoute(outer);
|
|
85
|
+
if (!outer || !leaf) {
|
|
86
|
+
this.tracker.stop();
|
|
87
|
+
this.visited.clear();
|
|
88
|
+
this.appearance = undefined;
|
|
89
|
+
this.transition = undefined;
|
|
90
|
+
this.trackRouter = undefined;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Retain instance history only while its route remains in this stack.
|
|
94
|
+
const keys = new Set(state.routes.map(route => route.key));
|
|
95
|
+
for (const key of this.visited) {
|
|
96
|
+
if (!keys.has(key)) this.visited.delete(key);
|
|
97
|
+
}
|
|
98
|
+
if (this.transition && this.transition.target !== outer.key) this.transition = undefined;
|
|
99
|
+
const previous = this.appearance;
|
|
100
|
+
if ((previous === null || previous === void 0 ? void 0 : previous.target) !== outer.key || previous.route.key !== leaf.key) {
|
|
101
|
+
const route = {
|
|
102
|
+
key: leaf.key,
|
|
103
|
+
name: leaf.name
|
|
104
|
+
};
|
|
105
|
+
const first = !this.visited.has(outer.key) && outer.key === leaf.key;
|
|
106
|
+
this.visited.add(outer.key);
|
|
107
|
+
this.appearance = {
|
|
108
|
+
target: outer.key,
|
|
109
|
+
route,
|
|
110
|
+
first,
|
|
111
|
+
generation: this.show(route)
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
this.tracker.setCurrent(previous.route);
|
|
115
|
+
}
|
|
116
|
+
this.finishTransition();
|
|
117
|
+
},
|
|
118
|
+
focus: event => {
|
|
119
|
+
var _this$transition3;
|
|
120
|
+
const start = this.monotonicNow();
|
|
121
|
+
if (!current() || !event.target) return;
|
|
122
|
+
this.tracker.start();
|
|
123
|
+
const appearance = this.appearance;
|
|
124
|
+
// A newly selected route invalidates the previous appearance even if
|
|
125
|
+
// React Navigation has not delivered the matching state update yet.
|
|
126
|
+
if ((appearance === null || appearance === void 0 ? void 0 : appearance.target) !== event.target) {
|
|
127
|
+
var _this$transition2;
|
|
128
|
+
if (appearance) appearance.first = false;
|
|
129
|
+
if (((_this$transition2 = this.transition) === null || _this$transition2 === void 0 ? void 0 : _this$transition2.target) !== event.target) this.transition = undefined;
|
|
130
|
+
}
|
|
131
|
+
// Keep the first focus boundary. Existing instances (including nested
|
|
132
|
+
// containers) may open Views again, but never start another load timer.
|
|
133
|
+
if (this.visited.has(event.target) && ((appearance === null || appearance === void 0 ? void 0 : appearance.target) !== event.target || !appearance.first) || ((_this$transition3 = this.transition) === null || _this$transition3 === void 0 ? void 0 : _this$transition3.target) === event.target) return;
|
|
134
|
+
this.transition = {
|
|
135
|
+
target: event.target,
|
|
136
|
+
start
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
return this.stackListeners;
|
|
141
|
+
}
|
|
142
|
+
static finishTransition() {
|
|
143
|
+
const transition = this.transition;
|
|
144
|
+
const appearance = this.appearance;
|
|
145
|
+
if (!transition || transition.end === undefined || !appearance || appearance.target !== transition.target) return;
|
|
146
|
+
this.transition = undefined;
|
|
147
|
+
const eligible = appearance.first;
|
|
148
|
+
appearance.first = false;
|
|
149
|
+
const elapsed = transition.end - transition.start;
|
|
150
|
+
if (!eligible || !Number.isFinite(elapsed) || elapsed <= 0 || elapsed >= MAX_TRANSITION_MS) return;
|
|
151
|
+
this.tracker.updateLoadingTime(appearance.route.key, appearance.generation, Math.floor(elapsed * 1000000));
|
|
152
|
+
}
|
|
153
|
+
static cancelTransition(target) {
|
|
154
|
+
var _this$transition4, _this$appearance2;
|
|
155
|
+
if (((_this$transition4 = this.transition) === null || _this$transition4 === void 0 ? void 0 : _this$transition4.target) === target) this.transition = undefined;
|
|
156
|
+
if (((_this$appearance2 = this.appearance) === null || _this$appearance2 === void 0 ? void 0 : _this$appearance2.target) === target) this.appearance.first = false;
|
|
157
|
+
}
|
|
158
|
+
static monotonicNow() {
|
|
159
|
+
return typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : NaN;
|
|
160
|
+
}
|
|
161
|
+
static leafRoute(route) {
|
|
162
|
+
while ((_route = route) !== null && _route !== void 0 && _route.state) {
|
|
163
|
+
var _route, _route$state$index;
|
|
164
|
+
const child = route.state.routes[(_route$state$index = route.state.index) !== null && _route$state$index !== void 0 ? _route$state$index : 0];
|
|
165
|
+
if (!child) break;
|
|
166
|
+
route = child;
|
|
167
|
+
}
|
|
168
|
+
return route;
|
|
169
|
+
}
|
|
170
|
+
static show(route) {
|
|
171
|
+
this.trackRouter = route;
|
|
172
|
+
return this.tracker.show(route);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
_FTRumReactNavigationTracking = FTRumReactNavigationTracking;
|
|
176
|
+
_defineProperty(FTRumReactNavigationTracking, "tracker", new FTRumViewTracking(() => {
|
|
177
|
+
_FTRumReactNavigationTracking.transition = undefined;
|
|
178
|
+
if (_FTRumReactNavigationTracking.appearance) _FTRumReactNavigationTracking.appearance.first = false;
|
|
179
|
+
}));
|
|
180
|
+
_defineProperty(FTRumReactNavigationTracking, "registeredContainer", null);
|
|
181
|
+
_defineProperty(FTRumReactNavigationTracking, "unsubscribe", void 0);
|
|
182
|
+
_defineProperty(FTRumReactNavigationTracking, "lifecycle", 0);
|
|
183
|
+
_defineProperty(FTRumReactNavigationTracking, "stackListeners", void 0);
|
|
184
|
+
_defineProperty(FTRumReactNavigationTracking, "visited", new Set());
|
|
185
|
+
_defineProperty(FTRumReactNavigationTracking, "appearance", void 0);
|
|
186
|
+
_defineProperty(FTRumReactNavigationTracking, "transition", void 0);
|
|
187
|
+
_defineProperty(FTRumReactNavigationTracking, "trackRouter", void 0);
|
|
188
|
+
//# sourceMappingURL=FTRumReactNavigationTracking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["FTRumViewTracking","MAX_TRANSITION_MS","FTRumReactNavigationTracking","startTrackingViews","ref","registeredContainer","stopTrackingViews","tracker","start","lifecycle","unsubscribe","addListener","route","getCurrentRoute","show","_this$unsubscribe","undefined","call","stackListeners","stop","visited","clear","appearance","transition","trackRouter","StackListener","current","transitionStart","event","_event$data","target","data","closing","cancelTransition","transitionEnd","_event$data2","_event$data3","_this$transition","_this$appearance","end","monotonicNow","finishTransition","first","gestureCancel","state","_event$data4","_state$index","outer","routes","index","leaf","leafRoute","keys","Set","map","key","has","delete","previous","name","add","generation","setCurrent","focus","_this$transition3","_this$transition2","eligible","elapsed","Number","isFinite","updateLoadingTime","Math","floor","_this$transition4","_this$appearance2","performance","now","NaN","_route","_route$state$index","child","_FTRumReactNavigationTracking","_defineProperty"],"sources":["FTRumReactNavigationTracking.ts"],"sourcesContent":["import type {\n NavigationContainerRef,\n ParamListBase,\n} from '@react-navigation/native';\nimport { FTRumViewTracking } from '@truewatchtech/react-native-mobile';\n\ntype TrackedRoute = {\n key: string;\n name: string;\n state?: { index?: number; routes: readonly TrackedRoute[] };\n};\ntype StackEvent = {\n target?: string;\n data?: {\n closing?: boolean;\n state?: { index?: number; routes: readonly TrackedRoute[] };\n };\n};\ntype Appearance = {\n target: string;\n route: { key: string; name: string };\n generation?: number;\n first: boolean;\n};\ntype Transition = { target: string; start: number; end?: number };\nconst MAX_TRANSITION_MS = 15 * 60 * 1000;\n\nexport type FTRumStackListeners = {\n focus(event: StackEvent): void;\n state(event: StackEvent): void;\n transitionStart(event: StackEvent): void;\n transitionEnd(event: StackEvent): void;\n gestureCancel(event: StackEvent): void;\n};\n\n/** One active navigation container, using either route or stack tracking. */\nexport class FTRumReactNavigationTracking {\n private static tracker = new FTRumViewTracking(() => {\n this.transition = undefined;\n if (this.appearance) this.appearance.first = false;\n });\n private static registeredContainer: object | null = null;\n private static unsubscribe?: () => void;\n private static lifecycle = 0;\n private static stackListeners?: FTRumStackListeners;\n private static visited = new Set<string>();\n private static appearance?: Appearance;\n private static transition?: Transition;\n static trackRouter: TrackedRoute | undefined;\n\n /** Call from NavigationContainer.onReady. This mode does not measure loading. */\n static startTrackingViews<ParamList extends ParamListBase>(\n ref: NavigationContainerRef<ParamList> | null\n ): void {\n if (!ref || this.registeredContainer === ref) return;\n this.stopTrackingViews();\n this.registeredContainer = ref;\n this.tracker.start();\n const lifecycle = this.lifecycle;\n this.unsubscribe = ref.addListener('state', () => {\n if (lifecycle !== this.lifecycle) return;\n const route = ref.getCurrentRoute();\n if (route) this.show(route);\n });\n const route = ref.getCurrentRoute();\n if (route) this.show(route);\n }\n\n /** Omit ref to dispose stack tracking. Old containers cannot stop a new one. */\n static stopTrackingViews(ref?: object | null): void {\n if (ref !== undefined && ref !== this.registeredContainer) return;\n ++this.lifecycle;\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n this.registeredContainer = null;\n this.stackListeners = undefined;\n this.tracker.stop();\n this.visited.clear();\n this.appearance = undefined;\n this.transition = undefined;\n this.trackRouter = undefined;\n }\n\n /** Track Views on state changes; time first focus through opening transitionEnd. */\n static get StackListener(): FTRumStackListeners {\n if (this.stackListeners) return this.stackListeners;\n this.stopTrackingViews();\n const lifecycle = this.lifecycle;\n const current = (): boolean => lifecycle === this.lifecycle;\n this.stackListeners = {\n transitionStart: (event): void => {\n if (current() && event.target && event.data?.closing === true)\n this.cancelTransition(event.target);\n },\n transitionEnd: (event): void => {\n const end = this.monotonicNow();\n if (!current() || !event.target) return;\n if (event.data?.closing === true) {\n this.cancelTransition(event.target);\n return;\n }\n if (event.data?.closing !== false) {\n this.cancelTransition(event.target);\n return;\n }\n if (this.transition?.target === event.target) {\n // Capture the end now, even when the matching state arrives later.\n if (this.transition.end === undefined) this.transition.end = end;\n this.finishTransition();\n } else if (this.appearance?.target === event.target) {\n // No observed focus: do not infer a start from a later callback.\n this.appearance.first = false;\n }\n },\n gestureCancel: (): void => {\n if (!current()) return;\n this.transition = undefined;\n if (this.appearance) this.appearance.first = false;\n },\n state: (event): void => {\n if (!current()) return;\n const state = event.data?.state;\n if (!state) return;\n const outer = state.routes[state.index ?? 0];\n const leaf = this.leafRoute(outer);\n if (!outer || !leaf) {\n this.tracker.stop();\n this.visited.clear();\n this.appearance = undefined;\n this.transition = undefined;\n this.trackRouter = undefined;\n return;\n }\n // Retain instance history only while its route remains in this stack.\n const keys = new Set(state.routes.map((route) => route.key));\n for (const key of this.visited) {\n if (!keys.has(key)) this.visited.delete(key);\n }\n if (this.transition && this.transition.target !== outer.key)\n this.transition = undefined;\n const previous = this.appearance;\n if (previous?.target !== outer.key || previous.route.key !== leaf.key) {\n const route = { key: leaf.key, name: leaf.name };\n const first = !this.visited.has(outer.key) && outer.key === leaf.key;\n this.visited.add(outer.key);\n this.appearance = {\n target: outer.key,\n route,\n first,\n generation: this.show(route),\n };\n } else {\n this.tracker.setCurrent(previous.route);\n }\n this.finishTransition();\n },\n focus: (event): void => {\n const start = this.monotonicNow();\n if (!current() || !event.target) return;\n this.tracker.start();\n const appearance = this.appearance;\n // A newly selected route invalidates the previous appearance even if\n // React Navigation has not delivered the matching state update yet.\n if (appearance?.target !== event.target) {\n if (appearance) appearance.first = false;\n if (this.transition?.target !== event.target)\n this.transition = undefined;\n }\n // Keep the first focus boundary. Existing instances (including nested\n // containers) may open Views again, but never start another load timer.\n if (\n (this.visited.has(event.target) &&\n (appearance?.target !== event.target || !appearance.first)) ||\n this.transition?.target === event.target\n )\n return;\n this.transition = { target: event.target, start };\n },\n };\n return this.stackListeners;\n }\n\n private static finishTransition(): void {\n const transition = this.transition;\n const appearance = this.appearance;\n if (\n !transition ||\n transition.end === undefined ||\n !appearance ||\n appearance.target !== transition.target\n )\n return;\n this.transition = undefined;\n const eligible = appearance.first;\n appearance.first = false;\n const elapsed = transition.end - transition.start;\n if (\n !eligible ||\n !Number.isFinite(elapsed) ||\n elapsed <= 0 ||\n elapsed >= MAX_TRANSITION_MS\n )\n return;\n this.tracker.updateLoadingTime(\n appearance.route.key,\n appearance.generation,\n Math.floor(elapsed * 1000000)\n );\n }\n\n private static cancelTransition(target: string): void {\n if (this.transition?.target === target) this.transition = undefined;\n if (this.appearance?.target === target) this.appearance.first = false;\n }\n\n private static monotonicNow(): number {\n return typeof performance !== 'undefined' &&\n typeof performance.now === 'function'\n ? performance.now()\n : NaN;\n }\n\n private static leafRoute(\n route: TrackedRoute | undefined\n ): TrackedRoute | undefined {\n while (route?.state) {\n const child = route.state.routes[route.state.index ?? 0];\n if (!child) break;\n route = child;\n }\n return route;\n }\n\n private static show(route: TrackedRoute): number | undefined {\n this.trackRouter = route;\n return this.tracker.show(route);\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["FTRumReactNavigationTracking"],"sources":["index.ts"],"sourcesContent":["export { FTRumReactNavigationTracking } from './FTRumReactNavigationTracking';\nexport type { FTRumStackListeners } from './FTRumReactNavigationTracking';\n"],"mappings":"AAAA,SAASA,4BAA4B,QAAQ,gCAAgC","ignoreList":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { NavigationContainerRef, ParamListBase } from '@react-navigation/native';
|
|
2
|
+
type TrackedRoute = {
|
|
3
|
+
key: string;
|
|
4
|
+
name: string;
|
|
5
|
+
state?: {
|
|
6
|
+
index?: number;
|
|
7
|
+
routes: readonly TrackedRoute[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
type StackEvent = {
|
|
11
|
+
target?: string;
|
|
12
|
+
data?: {
|
|
13
|
+
closing?: boolean;
|
|
14
|
+
state?: {
|
|
15
|
+
index?: number;
|
|
16
|
+
routes: readonly TrackedRoute[];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type FTRumStackListeners = {
|
|
21
|
+
focus(event: StackEvent): void;
|
|
22
|
+
state(event: StackEvent): void;
|
|
23
|
+
transitionStart(event: StackEvent): void;
|
|
24
|
+
transitionEnd(event: StackEvent): void;
|
|
25
|
+
gestureCancel(event: StackEvent): void;
|
|
26
|
+
};
|
|
27
|
+
/** One active navigation container, using either route or stack tracking. */
|
|
28
|
+
export declare class FTRumReactNavigationTracking {
|
|
29
|
+
private static tracker;
|
|
30
|
+
private static registeredContainer;
|
|
31
|
+
private static unsubscribe?;
|
|
32
|
+
private static lifecycle;
|
|
33
|
+
private static stackListeners?;
|
|
34
|
+
private static visited;
|
|
35
|
+
private static appearance?;
|
|
36
|
+
private static transition?;
|
|
37
|
+
static trackRouter: TrackedRoute | undefined;
|
|
38
|
+
/** Call from NavigationContainer.onReady. This mode does not measure loading. */
|
|
39
|
+
static startTrackingViews<ParamList extends ParamListBase>(ref: NavigationContainerRef<ParamList> | null): void;
|
|
40
|
+
/** Omit ref to dispose stack tracking. Old containers cannot stop a new one. */
|
|
41
|
+
static stopTrackingViews(ref?: object | null): void;
|
|
42
|
+
/** Track Views on state changes; time first focus through opening transitionEnd. */
|
|
43
|
+
static get StackListener(): FTRumStackListeners;
|
|
44
|
+
private static finishTransition;
|
|
45
|
+
private static cancelTransition;
|
|
46
|
+
private static monotonicNow;
|
|
47
|
+
private static leafRoute;
|
|
48
|
+
private static show;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truewatchtech/mobile-react-navigation",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.3-alpha.1",
|
|
4
|
+
"description": "Automatic TrueWatch RUM View tracking for @react-navigation/native.",
|
|
5
|
+
"main": "lib/commonjs/index",
|
|
6
|
+
"module": "lib/module/index",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"source": "src/index",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"README.md",
|
|
14
|
+
"!**/__tests__",
|
|
15
|
+
"!**/__mocks__"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"lint": "yarn run -T eslint \"src/**/*.{ts,tsx}\" \"tests/**/*.ts\"",
|
|
19
|
+
"prepare": "yarn run -T bob build",
|
|
20
|
+
"prepack": "yarn run -T check-version && yarn prepare",
|
|
21
|
+
"test": "yarn run -T jest",
|
|
22
|
+
"typescript": "yarn run -T tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/TrueWatchTech/datakit-react-native.git"
|
|
27
|
+
},
|
|
28
|
+
"license": "Apache-2.0",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"registry": "https://registry.npmjs.org/"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@truewatchtech/react-native-mobile": "^0.4.3",
|
|
35
|
+
"react": ">=16.13.1",
|
|
36
|
+
"react-native": ">=0.63.4 <1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@react-navigation/native": "^6.1.18",
|
|
40
|
+
"@react-navigation/native-stack": "^6.11.0",
|
|
41
|
+
"react-native-safe-area-context": "^3.3.2",
|
|
42
|
+
"react-native-screens": "^3.0.0"
|
|
43
|
+
},
|
|
44
|
+
"react-native-builder-bob": {
|
|
45
|
+
"source": "src",
|
|
46
|
+
"output": "lib",
|
|
47
|
+
"targets": [
|
|
48
|
+
"commonjs",
|
|
49
|
+
"module",
|
|
50
|
+
[
|
|
51
|
+
"typescript",
|
|
52
|
+
{
|
|
53
|
+
"tsc": "../../node_modules/.bin/tsc",
|
|
54
|
+
"project": "tsconfig.build.json"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"jest": {
|
|
60
|
+
"preset": "react-native",
|
|
61
|
+
"modulePathIgnorePatterns": [
|
|
62
|
+
"<rootDir>/lib/"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"gitHead": "7c1fd9454260071fc48c74b8e5bdb9669eee0b7f"
|
|
66
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NavigationContainerRef,
|
|
3
|
+
ParamListBase,
|
|
4
|
+
} from '@react-navigation/native';
|
|
5
|
+
import { FTRumViewTracking } from '@truewatchtech/react-native-mobile';
|
|
6
|
+
|
|
7
|
+
type TrackedRoute = {
|
|
8
|
+
key: string;
|
|
9
|
+
name: string;
|
|
10
|
+
state?: { index?: number; routes: readonly TrackedRoute[] };
|
|
11
|
+
};
|
|
12
|
+
type StackEvent = {
|
|
13
|
+
target?: string;
|
|
14
|
+
data?: {
|
|
15
|
+
closing?: boolean;
|
|
16
|
+
state?: { index?: number; routes: readonly TrackedRoute[] };
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type Appearance = {
|
|
20
|
+
target: string;
|
|
21
|
+
route: { key: string; name: string };
|
|
22
|
+
generation?: number;
|
|
23
|
+
first: boolean;
|
|
24
|
+
};
|
|
25
|
+
type Transition = { target: string; start: number; end?: number };
|
|
26
|
+
const MAX_TRANSITION_MS = 15 * 60 * 1000;
|
|
27
|
+
|
|
28
|
+
export type FTRumStackListeners = {
|
|
29
|
+
focus(event: StackEvent): void;
|
|
30
|
+
state(event: StackEvent): void;
|
|
31
|
+
transitionStart(event: StackEvent): void;
|
|
32
|
+
transitionEnd(event: StackEvent): void;
|
|
33
|
+
gestureCancel(event: StackEvent): void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** One active navigation container, using either route or stack tracking. */
|
|
37
|
+
export class FTRumReactNavigationTracking {
|
|
38
|
+
private static tracker = new FTRumViewTracking(() => {
|
|
39
|
+
this.transition = undefined;
|
|
40
|
+
if (this.appearance) this.appearance.first = false;
|
|
41
|
+
});
|
|
42
|
+
private static registeredContainer: object | null = null;
|
|
43
|
+
private static unsubscribe?: () => void;
|
|
44
|
+
private static lifecycle = 0;
|
|
45
|
+
private static stackListeners?: FTRumStackListeners;
|
|
46
|
+
private static visited = new Set<string>();
|
|
47
|
+
private static appearance?: Appearance;
|
|
48
|
+
private static transition?: Transition;
|
|
49
|
+
static trackRouter: TrackedRoute | undefined;
|
|
50
|
+
|
|
51
|
+
/** Call from NavigationContainer.onReady. This mode does not measure loading. */
|
|
52
|
+
static startTrackingViews<ParamList extends ParamListBase>(
|
|
53
|
+
ref: NavigationContainerRef<ParamList> | null
|
|
54
|
+
): void {
|
|
55
|
+
if (!ref || this.registeredContainer === ref) return;
|
|
56
|
+
this.stopTrackingViews();
|
|
57
|
+
this.registeredContainer = ref;
|
|
58
|
+
this.tracker.start();
|
|
59
|
+
const lifecycle = this.lifecycle;
|
|
60
|
+
this.unsubscribe = ref.addListener('state', () => {
|
|
61
|
+
if (lifecycle !== this.lifecycle) return;
|
|
62
|
+
const route = ref.getCurrentRoute();
|
|
63
|
+
if (route) this.show(route);
|
|
64
|
+
});
|
|
65
|
+
const route = ref.getCurrentRoute();
|
|
66
|
+
if (route) this.show(route);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Omit ref to dispose stack tracking. Old containers cannot stop a new one. */
|
|
70
|
+
static stopTrackingViews(ref?: object | null): void {
|
|
71
|
+
if (ref !== undefined && ref !== this.registeredContainer) return;
|
|
72
|
+
++this.lifecycle;
|
|
73
|
+
this.unsubscribe?.();
|
|
74
|
+
this.unsubscribe = undefined;
|
|
75
|
+
this.registeredContainer = null;
|
|
76
|
+
this.stackListeners = undefined;
|
|
77
|
+
this.tracker.stop();
|
|
78
|
+
this.visited.clear();
|
|
79
|
+
this.appearance = undefined;
|
|
80
|
+
this.transition = undefined;
|
|
81
|
+
this.trackRouter = undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Track Views on state changes; time first focus through opening transitionEnd. */
|
|
85
|
+
static get StackListener(): FTRumStackListeners {
|
|
86
|
+
if (this.stackListeners) return this.stackListeners;
|
|
87
|
+
this.stopTrackingViews();
|
|
88
|
+
const lifecycle = this.lifecycle;
|
|
89
|
+
const current = (): boolean => lifecycle === this.lifecycle;
|
|
90
|
+
this.stackListeners = {
|
|
91
|
+
transitionStart: (event): void => {
|
|
92
|
+
if (current() && event.target && event.data?.closing === true)
|
|
93
|
+
this.cancelTransition(event.target);
|
|
94
|
+
},
|
|
95
|
+
transitionEnd: (event): void => {
|
|
96
|
+
const end = this.monotonicNow();
|
|
97
|
+
if (!current() || !event.target) return;
|
|
98
|
+
if (event.data?.closing === true) {
|
|
99
|
+
this.cancelTransition(event.target);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (event.data?.closing !== false) {
|
|
103
|
+
this.cancelTransition(event.target);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.transition?.target === event.target) {
|
|
107
|
+
// Capture the end now, even when the matching state arrives later.
|
|
108
|
+
if (this.transition.end === undefined) this.transition.end = end;
|
|
109
|
+
this.finishTransition();
|
|
110
|
+
} else if (this.appearance?.target === event.target) {
|
|
111
|
+
// No observed focus: do not infer a start from a later callback.
|
|
112
|
+
this.appearance.first = false;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
gestureCancel: (): void => {
|
|
116
|
+
if (!current()) return;
|
|
117
|
+
this.transition = undefined;
|
|
118
|
+
if (this.appearance) this.appearance.first = false;
|
|
119
|
+
},
|
|
120
|
+
state: (event): void => {
|
|
121
|
+
if (!current()) return;
|
|
122
|
+
const state = event.data?.state;
|
|
123
|
+
if (!state) return;
|
|
124
|
+
const outer = state.routes[state.index ?? 0];
|
|
125
|
+
const leaf = this.leafRoute(outer);
|
|
126
|
+
if (!outer || !leaf) {
|
|
127
|
+
this.tracker.stop();
|
|
128
|
+
this.visited.clear();
|
|
129
|
+
this.appearance = undefined;
|
|
130
|
+
this.transition = undefined;
|
|
131
|
+
this.trackRouter = undefined;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Retain instance history only while its route remains in this stack.
|
|
135
|
+
const keys = new Set(state.routes.map((route) => route.key));
|
|
136
|
+
for (const key of this.visited) {
|
|
137
|
+
if (!keys.has(key)) this.visited.delete(key);
|
|
138
|
+
}
|
|
139
|
+
if (this.transition && this.transition.target !== outer.key)
|
|
140
|
+
this.transition = undefined;
|
|
141
|
+
const previous = this.appearance;
|
|
142
|
+
if (previous?.target !== outer.key || previous.route.key !== leaf.key) {
|
|
143
|
+
const route = { key: leaf.key, name: leaf.name };
|
|
144
|
+
const first = !this.visited.has(outer.key) && outer.key === leaf.key;
|
|
145
|
+
this.visited.add(outer.key);
|
|
146
|
+
this.appearance = {
|
|
147
|
+
target: outer.key,
|
|
148
|
+
route,
|
|
149
|
+
first,
|
|
150
|
+
generation: this.show(route),
|
|
151
|
+
};
|
|
152
|
+
} else {
|
|
153
|
+
this.tracker.setCurrent(previous.route);
|
|
154
|
+
}
|
|
155
|
+
this.finishTransition();
|
|
156
|
+
},
|
|
157
|
+
focus: (event): void => {
|
|
158
|
+
const start = this.monotonicNow();
|
|
159
|
+
if (!current() || !event.target) return;
|
|
160
|
+
this.tracker.start();
|
|
161
|
+
const appearance = this.appearance;
|
|
162
|
+
// A newly selected route invalidates the previous appearance even if
|
|
163
|
+
// React Navigation has not delivered the matching state update yet.
|
|
164
|
+
if (appearance?.target !== event.target) {
|
|
165
|
+
if (appearance) appearance.first = false;
|
|
166
|
+
if (this.transition?.target !== event.target)
|
|
167
|
+
this.transition = undefined;
|
|
168
|
+
}
|
|
169
|
+
// Keep the first focus boundary. Existing instances (including nested
|
|
170
|
+
// containers) may open Views again, but never start another load timer.
|
|
171
|
+
if (
|
|
172
|
+
(this.visited.has(event.target) &&
|
|
173
|
+
(appearance?.target !== event.target || !appearance.first)) ||
|
|
174
|
+
this.transition?.target === event.target
|
|
175
|
+
)
|
|
176
|
+
return;
|
|
177
|
+
this.transition = { target: event.target, start };
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
return this.stackListeners;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private static finishTransition(): void {
|
|
184
|
+
const transition = this.transition;
|
|
185
|
+
const appearance = this.appearance;
|
|
186
|
+
if (
|
|
187
|
+
!transition ||
|
|
188
|
+
transition.end === undefined ||
|
|
189
|
+
!appearance ||
|
|
190
|
+
appearance.target !== transition.target
|
|
191
|
+
)
|
|
192
|
+
return;
|
|
193
|
+
this.transition = undefined;
|
|
194
|
+
const eligible = appearance.first;
|
|
195
|
+
appearance.first = false;
|
|
196
|
+
const elapsed = transition.end - transition.start;
|
|
197
|
+
if (
|
|
198
|
+
!eligible ||
|
|
199
|
+
!Number.isFinite(elapsed) ||
|
|
200
|
+
elapsed <= 0 ||
|
|
201
|
+
elapsed >= MAX_TRANSITION_MS
|
|
202
|
+
)
|
|
203
|
+
return;
|
|
204
|
+
this.tracker.updateLoadingTime(
|
|
205
|
+
appearance.route.key,
|
|
206
|
+
appearance.generation,
|
|
207
|
+
Math.floor(elapsed * 1000000)
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private static cancelTransition(target: string): void {
|
|
212
|
+
if (this.transition?.target === target) this.transition = undefined;
|
|
213
|
+
if (this.appearance?.target === target) this.appearance.first = false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private static monotonicNow(): number {
|
|
217
|
+
return typeof performance !== 'undefined' &&
|
|
218
|
+
typeof performance.now === 'function'
|
|
219
|
+
? performance.now()
|
|
220
|
+
: NaN;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private static leafRoute(
|
|
224
|
+
route: TrackedRoute | undefined
|
|
225
|
+
): TrackedRoute | undefined {
|
|
226
|
+
while (route?.state) {
|
|
227
|
+
const child = route.state.routes[route.state.index ?? 0];
|
|
228
|
+
if (!child) break;
|
|
229
|
+
route = child;
|
|
230
|
+
}
|
|
231
|
+
return route;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private static show(route: TrackedRoute): number | undefined {
|
|
235
|
+
this.trackRouter = route;
|
|
236
|
+
return this.tracker.show(route);
|
|
237
|
+
}
|
|
238
|
+
}
|
package/src/index.ts
ADDED