@cxpinsight/react-native 0.19.1 → 0.21.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/android/src/main/java/com/cxpinsight/reactnative/CXPVitalsModule.java +170 -0
  3. package/android/src/main/java/com/cxpinsight/reactnative/CXPinsightPackage.java +2 -1
  4. package/ios/CXPVitals.m +143 -0
  5. package/lib/CXPinsight.d.ts +8 -1
  6. package/lib/CXPinsight.d.ts.map +1 -1
  7. package/lib/CXPinsight.js +26 -1
  8. package/lib/CXPinsight.js.map +1 -1
  9. package/lib/CXPinsightProvider.d.ts.map +1 -1
  10. package/lib/CXPinsightProvider.js +7 -1
  11. package/lib/CXPinsightProvider.js.map +1 -1
  12. package/lib/core/autoTapCapture.d.ts.map +1 -1
  13. package/lib/core/autoTapCapture.js +12 -2
  14. package/lib/core/autoTapCapture.js.map +1 -1
  15. package/lib/core/captureStatus.d.ts +4 -0
  16. package/lib/core/captureStatus.d.ts.map +1 -1
  17. package/lib/core/captureStatus.js +2 -0
  18. package/lib/core/captureStatus.js.map +1 -1
  19. package/lib/core/network.d.ts +3 -1
  20. package/lib/core/network.d.ts.map +1 -1
  21. package/lib/core/network.js +9 -3
  22. package/lib/core/network.js.map +1 -1
  23. package/lib/core/triggerEngine.d.ts.map +1 -1
  24. package/lib/core/triggerEngine.js +72 -10
  25. package/lib/core/triggerEngine.js.map +1 -1
  26. package/lib/core/vitals.d.ts +50 -0
  27. package/lib/core/vitals.d.ts.map +1 -0
  28. package/lib/core/vitals.js +343 -0
  29. package/lib/core/vitals.js.map +1 -0
  30. package/lib/instrumented/Pressable.d.ts.map +1 -1
  31. package/lib/instrumented/Pressable.js +5 -0
  32. package/lib/instrumented/Pressable.js.map +1 -1
  33. package/lib/instrumented/TouchableOpacity.d.ts.map +1 -1
  34. package/lib/instrumented/TouchableOpacity.js +5 -0
  35. package/lib/instrumented/TouchableOpacity.js.map +1 -1
  36. package/lib/survey/SurveyModal.d.ts.map +1 -1
  37. package/lib/survey/SurveyModal.js +8 -0
  38. package/lib/survey/SurveyModal.js.map +1 -1
  39. package/lib/types.d.ts +6 -0
  40. package/lib/types.d.ts.map +1 -1
  41. package/lib/types.js.map +1 -1
  42. package/lib/version.d.ts +1 -1
  43. package/lib/version.js +1 -1
  44. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.21.0
4
+
5
+ Requires `@cxpinsight/survey-spec@^0.11.0` (unchanged).
6
+
7
+ ### Added
8
+ - **App start and frame vitals on Android.** `APP_START_COLD` (process start → the first
9
+ screen's first frame, once per process), `APP_START_WARM` (activity re-created while the
10
+ process lived on), `APP_START_HOT` (back to the foreground) and, per screen visit,
11
+ `SLOW_FRAMES` / `FROZEN_FRAMES` (share of drawn frames over their deadline / over 700 ms,
12
+ Android vitals definitions), as `web_vitals` events like the rest. Android is also told
13
+ when the first screen is fully drawn (`reportFullyDrawn`), so the system's own
14
+ startup numbers (Play Console, Macrobenchmark) see it. Measured on an API 35 emulator
15
+ against Android's own numbers: cold start within 64 ms of "Fully drawn", hot start within
16
+ a frame of `am start -W`, slow frames 8.0 % vs `gfxinfo` 8.6 %.
17
+ Needs a native build (not Expo Go); visits under 30 frames report no frame share.
18
+ - iOS ships the same native module, switched off until it has been measured on iOS; iOS
19
+ keeps screen load, INP and JS stall from 0.20.0.
20
+
21
+ ## 0.20.0
22
+
23
+ Requires `@cxpinsight/survey-spec@^0.11.0`. Publish that first.
24
+
25
+ ### Added
26
+ - **Performance vitals on mobile, in the same shape as web.** Each screen visit reports
27
+ `SCREEN_LOAD` (the tap or route change that opened the screen → its first frame on the
28
+ display), `INP` (an input → the frame showing its result, the worst in the visit) and
29
+ `JS_STALL` (the longest time the JS thread was blocked), as `web_vitals` events with the
30
+ web SDK's fields, so the dashboard's Performance tab reads both platforms the same way.
31
+ Thresholds follow published standards: LCP's for screen load, web INP's, Apple's hangs.
32
+ Measured on iOS against a 60 fps screen recording: start exact, end within a frame.
33
+ No setup needed; screens come from the provider's navigation tracking.
34
+ - **An ask can send the respondent to a link after submitting** (the survey's
35
+ `settings.completion.redirectUrl`, resolved by survey-spec's `submitRedirect`).
36
+
37
+ ### Fixed
38
+ - **The offline copy of the trigger config works on Android again.** It was one
39
+ AsyncStorage value, which Android cannot read back over ~2 MB (passed at ~4,000+
40
+ interactions). It is now stored in pieces under 900 KB; a copy stored by an earlier SDK
41
+ is read once and replaced.
42
+ - **App starts revalidate the trigger config** with its ETag and use the stored copy on
43
+ 304, instead of downloading ~300 KB each time.
44
+
3
45
  ## 0.19.1
4
46
 
5
47
  Requires `@cxpinsight/survey-spec@^0.10.0` (unchanged).
@@ -0,0 +1,170 @@
1
+ package com.cxpinsight.reactnative;
2
+
3
+ import android.app.Activity;
4
+ import android.app.Application;
5
+ import android.os.Build;
6
+ import android.os.Bundle;
7
+ import android.os.Handler;
8
+ import android.os.HandlerThread;
9
+ import android.os.Process;
10
+ import android.os.SystemClock;
11
+ import android.view.FrameMetrics;
12
+ import android.view.Window;
13
+
14
+ import androidx.annotation.NonNull;
15
+ import androidx.annotation.Nullable;
16
+
17
+ import com.facebook.react.bridge.Arguments;
18
+ import com.facebook.react.bridge.Promise;
19
+ import com.facebook.react.bridge.ReactApplicationContext;
20
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
21
+ import com.facebook.react.bridge.ReactMethod;
22
+ import com.facebook.react.bridge.WritableMap;
23
+
24
+ import java.lang.ref.WeakReference;
25
+
26
+ /**
27
+ * Mobile vitals that only native code can see — the Android half of ios/CXPVitals.m and the native
28
+ * half of src/core/vitals.ts.
29
+ *
30
+ * getLaunchInfo when this process started, when an activity was last created (a WARM start: the
31
+ * process lived on, the activity did not) and when the app last came to the
32
+ * foreground (a HOT start) — wall-clock ms, the clock JS's Date.now() reads.
33
+ * startFrameMonitor / getFrameStats
34
+ * frames DRAWN since the monitor started (FrameMetrics, the source Google's JankStats
35
+ * uses): total, slow (over the frame's deadline — the vsync interval) and frozen (over
36
+ * 700 ms), the Android vitals definitions. Cumulative; JS takes the difference per
37
+ * screen visit.
38
+ */
39
+ public class CXPVitalsModule extends ReactContextBaseJavaModule {
40
+ static final String NAME = "CXPVitals";
41
+
42
+ private static final long FROZEN_NS = 700_000_000L;
43
+
44
+ private static volatile double activityCreatedMs = 0;
45
+ private static volatile double foregroundMs = 0;
46
+ private static volatile boolean callbacksRegistered = false;
47
+ // A cold start happens once per process; a JS reload in the same process is not one.
48
+ private static volatile boolean launchReported = false;
49
+
50
+ private long total = 0, slow = 0, frozen = 0;
51
+ private boolean monitoring = false;
52
+ private WeakReference<Window> watched = new WeakReference<>(null);
53
+ private Window.OnFrameMetricsAvailableListener listener;
54
+ private HandlerThread metricsThread;
55
+ private Handler metricsHandler;
56
+
57
+ CXPVitalsModule(ReactApplicationContext context) {
58
+ super(context);
59
+ registerLifecycle(context);
60
+ }
61
+
62
+ @NonNull
63
+ @Override
64
+ public String getName() { return NAME; }
65
+
66
+ private static double wallFromElapsed(long elapsedMs) {
67
+ return System.currentTimeMillis() - (SystemClock.elapsedRealtime() - elapsedMs);
68
+ }
69
+
70
+ private void registerLifecycle(ReactApplicationContext context) {
71
+ if (callbacksRegistered) return;
72
+ if (!(context.getApplicationContext() instanceof Application)) return;
73
+ callbacksRegistered = true;
74
+ ((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks(
75
+ new Application.ActivityLifecycleCallbacks() {
76
+ @Override public void onActivityCreated(@NonNull Activity a, @Nullable Bundle b) {
77
+ activityCreatedMs = System.currentTimeMillis();
78
+ }
79
+ @Override public void onActivityStarted(@NonNull Activity a) {
80
+ foregroundMs = System.currentTimeMillis();
81
+ if (monitoring) attach(a);
82
+ }
83
+ @Override public void onActivityResumed(@NonNull Activity a) { if (monitoring) attach(a); }
84
+ @Override public void onActivityPaused(@NonNull Activity a) {}
85
+ @Override public void onActivityStopped(@NonNull Activity a) {}
86
+ @Override public void onActivitySaveInstanceState(@NonNull Activity a, @NonNull Bundle b) {}
87
+ @Override public void onActivityDestroyed(@NonNull Activity a) {}
88
+ });
89
+ }
90
+
91
+ @ReactMethod
92
+ public void getLaunchInfo(Promise promise) {
93
+ WritableMap m = Arguments.createMap();
94
+ boolean first = !launchReported;
95
+ launchReported = true;
96
+ m.putBoolean("firstInProcess", first);
97
+ double processStart = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
98
+ ? wallFromElapsed(Process.getStartElapsedRealtime()) : 0;
99
+ m.putDouble("processStartMs", processStart);
100
+ m.putBoolean("prewarmed", false);
101
+ m.putDouble("finishedLaunchingMs", 0);
102
+ m.putDouble("foregroundMs", foregroundMs);
103
+ m.putDouble("activityCreatedMs", activityCreatedMs);
104
+ m.putDouble("nowMs", System.currentTimeMillis());
105
+ m.putString("frameSource", "frame_metrics");
106
+ promise.resolve(m);
107
+ }
108
+
109
+ @ReactMethod
110
+ public void startFrameMonitor() {
111
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return;
112
+ monitoring = true;
113
+ Activity a = getCurrentActivity();
114
+ if (a != null) a.runOnUiThread(() -> attach(a));
115
+ }
116
+
117
+ private synchronized void attach(Activity activity) {
118
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || activity == null) return;
119
+ Window window = activity.getWindow();
120
+ if (window == null || window == watched.get()) return;
121
+ Window previous = watched.get();
122
+ if (previous != null && listener != null) {
123
+ try { previous.removeOnFrameMetricsAvailableListener(listener); } catch (Exception ignored) {}
124
+ }
125
+ if (metricsThread == null) {
126
+ metricsThread = new HandlerThread("cxp-frame-metrics");
127
+ metricsThread.start();
128
+ metricsHandler = new Handler(metricsThread.getLooper());
129
+ }
130
+ if (listener == null) {
131
+ listener = (w, metrics, dropped) -> {
132
+ long duration = metrics.getMetric(FrameMetrics.TOTAL_DURATION);
133
+ long deadline = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
134
+ ? metrics.getMetric(FrameMetrics.DEADLINE) : 16_666_667L;
135
+ synchronized (CXPVitalsModule.this) {
136
+ total += 1 + dropped;
137
+ if (duration > deadline) slow += 1;
138
+ if (duration > FROZEN_NS) frozen += 1;
139
+ }
140
+ };
141
+ }
142
+ try {
143
+ window.addOnFrameMetricsAvailableListener(listener, metricsHandler);
144
+ watched = new WeakReference<>(window);
145
+ } catch (Exception ignored) {}
146
+ }
147
+
148
+ /**
149
+ * The first screen's content is on the display: tell Android, so the system logs "Fully drawn"
150
+ * (Time to Full Display, measured by Android from the launch) — what Play Console's startup
151
+ * metrics and Macrobenchmark read. Once per process; Android ignores repeats.
152
+ */
153
+ @ReactMethod
154
+ public void reportFullyDrawn() {
155
+ Activity a = getCurrentActivity();
156
+ if (a == null) return;
157
+ a.runOnUiThread(() -> { try { a.reportFullyDrawn(); } catch (Exception ignored) {} });
158
+ }
159
+
160
+ @ReactMethod
161
+ public void getFrameStats(Promise promise) {
162
+ WritableMap m = Arguments.createMap();
163
+ synchronized (this) {
164
+ m.putDouble("total", total);
165
+ m.putDouble("slow", slow);
166
+ m.putDouble("frozen", frozen);
167
+ }
168
+ promise.resolve(m);
169
+ }
170
+ }
@@ -7,6 +7,7 @@ import com.facebook.react.bridge.NativeModule;
7
7
  import com.facebook.react.bridge.ReactApplicationContext;
8
8
  import com.facebook.react.uimanager.ViewManager;
9
9
 
10
+ import java.util.Arrays;
10
11
  import java.util.Collections;
11
12
  import java.util.List;
12
13
 
@@ -14,7 +15,7 @@ public class CXPinsightPackage implements ReactPackage {
14
15
  @NonNull
15
16
  @Override
16
17
  public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
17
- return Collections.<NativeModule>singletonList(new CXPScreenshotModule(reactContext));
18
+ return Arrays.<NativeModule>asList(new CXPScreenshotModule(reactContext), new CXPVitalsModule(reactContext));
18
19
  }
19
20
 
20
21
  @NonNull
@@ -0,0 +1,143 @@
1
+ // Mobile vitals that only native code can see — the native half of src/core/vitals.ts.
2
+ //
3
+ // getLaunchInfo when this process started (and whether iOS prewarmed it), when the app finished
4
+ // launching, and when it last came to the foreground — wall-clock ms, the clock
5
+ // JS's Date.now() reads, so JS can measure up to the first frame it sees.
6
+ // startFrameMonitor / getFrameStats
7
+ // frames the main thread delivered since the monitor started: total, slow (later
8
+ // than 1.5 refresh intervals — a missed deadline) and frozen (over 700 ms), the
9
+ // Android vitals definitions. Counts are cumulative; JS takes the difference per
10
+ // screen visit. Paused while the app is in the background.
11
+ //
12
+ // Prewarm: since iOS 15 the system may start an app's process before anyone opens it, so the process
13
+ // start can be minutes before the launch. Apple's advice is to measure such launches from when the
14
+ // app finishes launching instead; `prewarmed` says which one JS should use.
15
+
16
+ #import <React/RCTBridgeModule.h>
17
+ #import <UIKit/UIKit.h>
18
+ #import <QuartzCore/QuartzCore.h>
19
+ #include <sys/sysctl.h>
20
+ #include <unistd.h>
21
+
22
+ static double CXPNowMs(void) { return [[NSDate date] timeIntervalSince1970] * 1000.0; }
23
+
24
+ static double cxpProcessStartMs = 0;
25
+ static double cxpFinishedLaunchingMs = 0;
26
+ static double cxpForegroundMs = 0;
27
+ static BOOL cxpPrewarmed = NO;
28
+ // A cold start happens once per process; a JS reload in the same process is not one.
29
+ static BOOL cxpLaunchReported = NO;
30
+
31
+ static double CXPProcessStartMs(void)
32
+ {
33
+ struct kinfo_proc info;
34
+ size_t size = sizeof(info);
35
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
36
+ if (sysctl(mib, 4, &info, &size, NULL, 0) != 0) return 0;
37
+ struct timeval t = info.kp_proc.p_starttime;
38
+ return t.tv_sec * 1000.0 + t.tv_usec / 1000.0;
39
+ }
40
+
41
+ // Runs before main() — so the launch notifications below are never missed. A C constructor rather
42
+ // than +load: RCT_EXPORT_MODULE defines +load on this class to register the module.
43
+ __attribute__((constructor)) static void CXPVitalsLaunchHooks(void)
44
+ {
45
+ cxpProcessStartMs = CXPProcessStartMs();
46
+ const char *prewarm = getenv("ActivePrewarm");
47
+ cxpPrewarmed = prewarm != NULL && strcmp(prewarm, "1") == 0;
48
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
49
+ [nc addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:nil
50
+ usingBlock:^(__unused NSNotification *n) { cxpFinishedLaunchingMs = CXPNowMs(); }];
51
+ [nc addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil
52
+ usingBlock:^(__unused NSNotification *n) { cxpForegroundMs = CXPNowMs(); }];
53
+ }
54
+
55
+ @interface CXPFrameMonitor : NSObject
56
+ @property (nonatomic, strong) CADisplayLink *link;
57
+ @property (nonatomic) CFTimeInterval last;
58
+ @property (nonatomic) long long total;
59
+ @property (nonatomic) long long slow;
60
+ @property (nonatomic) long long frozen;
61
+ @end
62
+
63
+ @implementation CXPFrameMonitor
64
+
65
+ - (void)start
66
+ {
67
+ if (self.link) return;
68
+ self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
69
+ [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
70
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
71
+ [nc addObserver:self selector:@selector(pause) name:UIApplicationDidEnterBackgroundNotification object:nil];
72
+ [nc addObserver:self selector:@selector(resume) name:UIApplicationDidBecomeActiveNotification object:nil];
73
+ }
74
+
75
+ - (void)pause { self.link.paused = YES; self.last = 0; }
76
+ - (void)resume { self.last = 0; self.link.paused = NO; }
77
+
78
+ - (void)tick:(CADisplayLink *)link
79
+ {
80
+ CFTimeInterval now = link.timestamp;
81
+ if (self.last > 0) {
82
+ CFTimeInterval expected = link.targetTimestamp - link.timestamp;
83
+ if (expected <= 0 || expected > 0.1) expected = 1.0 / 60.0;
84
+ CFTimeInterval dt = now - self.last;
85
+ self.total += 1;
86
+ if (dt > expected * 1.5) self.slow += 1;
87
+ if (dt > 0.7) self.frozen += 1;
88
+ }
89
+ self.last = now;
90
+ }
91
+
92
+ @end
93
+
94
+ @interface CXPVitals : NSObject <RCTBridgeModule>
95
+ @end
96
+
97
+ @implementation CXPVitals
98
+
99
+ RCT_EXPORT_MODULE(CXPVitals)
100
+
101
+ static CXPFrameMonitor *cxpMonitor = nil;
102
+
103
+
104
+ + (BOOL)requiresMainQueueSetup { return NO; }
105
+ - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); }
106
+
107
+ RCT_EXPORT_METHOD(getLaunchInfo:(RCTPromiseResolveBlock)resolve
108
+ reject:(__unused RCTPromiseRejectBlock)reject)
109
+ {
110
+ BOOL first = !cxpLaunchReported;
111
+ cxpLaunchReported = YES;
112
+ resolve(@{
113
+ @"firstInProcess": @(first),
114
+ @"processStartMs": @(cxpProcessStartMs),
115
+ @"prewarmed": @(cxpPrewarmed),
116
+ @"finishedLaunchingMs": @(cxpFinishedLaunchingMs),
117
+ @"foregroundMs": @(cxpForegroundMs),
118
+ @"activityCreatedMs": @0,
119
+ @"nowMs": @(CXPNowMs()),
120
+ @"frameSource": @"display_link",
121
+ });
122
+ }
123
+
124
+ RCT_EXPORT_METHOD(startFrameMonitor)
125
+ {
126
+ if (!cxpMonitor) cxpMonitor = [CXPFrameMonitor new];
127
+ [cxpMonitor start];
128
+ }
129
+
130
+ // Android's "Fully drawn" has no iOS equivalent; kept so JS calls one API on both.
131
+ RCT_EXPORT_METHOD(reportFullyDrawn) {}
132
+
133
+ RCT_EXPORT_METHOD(getFrameStats:(RCTPromiseResolveBlock)resolve
134
+ reject:(__unused RCTPromiseRejectBlock)reject)
135
+ {
136
+ resolve(@{
137
+ @"total": @(cxpMonitor ? cxpMonitor.total : 0),
138
+ @"slow": @(cxpMonitor ? cxpMonitor.slow : 0),
139
+ @"frozen": @(cxpMonitor ? cxpMonitor.frozen : 0),
140
+ });
141
+ }
142
+
143
+ @end
@@ -100,13 +100,20 @@ declare class CXPinsightSDK {
100
100
  private verifyConnectivity;
101
101
  track(eventName: string, data?: Record<string, unknown>, eventType?: string, teeToRecording?: boolean,
102
102
  /** Internal: preserves the original time of a call buffered before init(). */
103
- eventTimestamp?: string): void;
103
+ eventTimestamp?: string,
104
+ /** Internal: top-level performance values (vitals), as the web SDK sends them. */
105
+ performance?: Record<string, number>): void;
104
106
  /**
105
107
  * The screenshot capturer for a recording that is starting now: the host's
106
108
  * captureScreen, else the SDK's own (nativeScreenshot.ts), unless the
107
109
  * dashboard turned screenshots off.
108
110
  */
109
111
  private screenshotProvider;
112
+ /**
113
+ * One mobile vital, as a `web_vitals` event in the web SDK's shape, so the Performance tab reads
114
+ * both platforms the same way (docs/architecture/performance-vitals-web-and-mobile.md).
115
+ */
116
+ private trackVital;
110
117
  /** Track a screen view (triggers remote page_view rules) */
111
118
  trackScreen(screenName: string, data?: Record<string, unknown>): void;
112
119
  /** Identify the current user */
@@ -1 +1 @@
1
- {"version":3,"file":"CXPinsight.d.ts","sourceRoot":"","sources":["../src/CXPinsight.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CXPinsight.d.ts","sourceRoot":"","sources":["../src/CXPinsight.ts"],"names":[],"mappings":"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"}
package/lib/CXPinsight.js CHANGED
@@ -52,6 +52,7 @@ const SetupController_1 = require("./setup/SetupController");
52
52
  const recorder = __importStar(require("./core/mobileRecorder"));
53
53
  const interactionObserver_1 = require("./instrumented/interactionObserver");
54
54
  const lifecycle_1 = require("./core/lifecycle");
55
+ const vitals_1 = require("./core/vitals");
55
56
  const networkCapture_1 = require("./core/networkCapture");
56
57
  const scrollCapture_1 = require("./core/scrollCapture");
57
58
  const version_1 = require("./version");
@@ -877,6 +878,8 @@ class CXPinsightSDK {
877
878
  }
878
879
  // App lifecycle → navigation events (app_opened / foregrounded / backgrounded).
879
880
  // Also pause/resume the engagement clock so time-on-app excludes backgrounded time.
881
+ // Mobile vitals (core/vitals.ts): screen load, INP, JS stalls — reported in the web's shape.
882
+ (0, vitals_1.startVitals)((r) => this.trackVital(r));
880
883
  (0, lifecycle_1.startLifecycleTracking)((evt) => {
881
884
  if (evt.type === 'app_backgrounded') {
882
885
  this.pauseEngagementClock();
@@ -982,7 +985,9 @@ class CXPinsightSDK {
982
985
  }
983
986
  track(eventName, data, eventType, teeToRecording = true,
984
987
  /** Internal: preserves the original time of a call buffered before init(). */
985
- eventTimestamp) {
988
+ eventTimestamp,
989
+ /** Internal: top-level performance values (vitals), as the web SDK sends them. */
990
+ performance) {
986
991
  if (!this.initialized || !this.config) {
987
992
  // Held, not dropped. Drained by flushPending() once init() completes.
988
993
  if (this.pending.length < CXPinsightSDK.MAX_PENDING) {
@@ -1024,6 +1029,7 @@ class CXPinsightSDK {
1024
1029
  // Active foreground time this session (parity with web's engagementTimeMs).
1025
1030
  engagementTimeMs: this.getEngagementMs(),
1026
1031
  ...(userProperties ? { userProperties } : {}),
1032
+ ...(performance ? { performance } : {}),
1027
1033
  };
1028
1034
  // Product analytics consent, finally enforced. CONSENT_CATEGORIES has always
1029
1035
  // offered `analytics` and the banner has always shown "Product analytics",
@@ -1085,6 +1091,23 @@ class CXPinsightSDK {
1085
1091
  this.screenshotSource = r.source;
1086
1092
  return r.provider;
1087
1093
  }
1094
+ /**
1095
+ * One mobile vital, as a `web_vitals` event in the web SDK's shape, so the Performance tab reads
1096
+ * both platforms the same way (docs/architecture/performance-vitals-web-and-mobile.md).
1097
+ */
1098
+ trackVital(r) {
1099
+ this.track('web_vitals', {
1100
+ metric_name: r.name,
1101
+ metric_value: r.value,
1102
+ metric_rating: r.rating,
1103
+ page_type: r.screen,
1104
+ page: r.screen,
1105
+ page_load_id: r.visitId,
1106
+ navigation_type: r.navigationType,
1107
+ vitals_source: 'cxpinsight-rn@' + version_1.SDK_VERSION,
1108
+ attribution: r.attribution,
1109
+ }, 'performance', false, undefined, { [r.name.toLowerCase()]: r.value });
1110
+ }
1088
1111
  /** Track a screen view (triggers remote page_view rules) */
1089
1112
  trackScreen(screenName, data) {
1090
1113
  // IDEMPOTENT FOR THE SCREEN ALREADY SHOWING.
@@ -1105,6 +1128,7 @@ class CXPinsightSDK {
1105
1128
  this.currentScreen = screenName;
1106
1129
  if (recorder.isRecording())
1107
1130
  recorder.recordScreen(screenName, this.previousScreen);
1131
+ (0, vitals_1.onScreen)(screenName); // ends the previous visit's vitals, times this screen's load
1108
1132
  // Cross-platform normalization: emit as `page_view` (web's canonical navigation
1109
1133
  // event name) so the eventSignature (eventType|page|elementKey) collapses web
1110
1134
  // and mobile navigations to the same key when they share a page identifier.
@@ -1443,6 +1467,7 @@ class CXPinsightSDK {
1443
1467
  (0, autoTapCapture_1.uninstallAutoTapCapture)();
1444
1468
  (0, session_1.setRolloverHandler)(null);
1445
1469
  (0, lifecycle_1.stopLifecycleTracking)();
1470
+ (0, vitals_1.stopVitals)();
1446
1471
  (0, networkCapture_1.stopNetworkCapture)();
1447
1472
  this.engagementActive = false;
1448
1473
  this.engagementAccumMs = 0;