@truewatchtech/react-native-mobile 0.4.3-alpha.2 → 0.4.3

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 (95) hide show
  1. package/FTMobileReactNativeSDK.podspec +1 -1
  2. package/README.md +0 -34
  3. package/android/build.gradle +2 -2
  4. package/android/src/main/java/com/ft/sdk/reactnative/FTLogImpl.java +10 -2
  5. package/android/src/main/java/com/ft/sdk/reactnative/FTMobileImpl.java +45 -3
  6. package/android/src/main/java/com/ft/sdk/reactnative/FTRUMImpl.java +51 -9
  7. package/android/src/main/java/com/ft/sdk/reactnative/FTTraceImpl.java +5 -2
  8. package/android/src/main/java/com/ft/sdk/reactnative/ViewTiming.java +17 -0
  9. package/android/src/main/java/com/ft/sdk/reactnative/VisitorIdConfigMapper.java +50 -0
  10. package/android/src/newarch/java/com/ft/sdk/reactnative/FTMobileModule.java +5 -0
  11. package/android/src/newarch/java/com/ft/sdk/reactnative/FTRUMModule.java +18 -0
  12. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTMobileModule.java +5 -0
  13. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTRUMModule.java +15 -0
  14. package/android/src/test/java/com/ft/sdk/reactnative/FTMobileImplTest.java +53 -0
  15. package/ios/FTMobileReactNative.mm +37 -2
  16. package/ios/FTReactNativeLog.mm +7 -2
  17. package/ios/FTReactNativeRUM.mm +94 -4
  18. package/ios/FTReactNativeResource.m +6 -0
  19. package/ios/FTReactNativeTrace.mm +4 -2
  20. package/ios/FTReactNativeViewTiming.h +18 -0
  21. package/ios/FTWebSocketResourceData.h +4 -0
  22. package/ios/FTWebSocketResourceData.m +31 -0
  23. package/lib/commonjs/ft_logger.js +3 -1
  24. package/lib/commonjs/ft_logger.js.map +1 -1
  25. package/lib/commonjs/ft_mobile_agent.js +13 -1
  26. package/lib/commonjs/ft_mobile_agent.js.map +1 -1
  27. package/lib/commonjs/ft_rum.js +19 -3
  28. package/lib/commonjs/ft_rum.js.map +1 -1
  29. package/lib/commonjs/ft_tracing.js +2 -1
  30. package/lib/commonjs/ft_tracing.js.map +1 -1
  31. package/lib/commonjs/index.js +7 -0
  32. package/lib/commonjs/index.js.map +1 -1
  33. package/lib/commonjs/rum/FTRumViewTracking.js +147 -0
  34. package/lib/commonjs/rum/FTRumViewTracking.js.map +1 -0
  35. package/lib/commonjs/rum/FTRumWebSocketNative.js.map +1 -1
  36. package/lib/commonjs/rum/FTRumWebSocketTiming.js +19 -1
  37. package/lib/commonjs/rum/FTRumWebSocketTiming.js.map +1 -1
  38. package/lib/commonjs/rum/FTRumWebSocketTracking.js +7 -11
  39. package/lib/commonjs/rum/FTRumWebSocketTracking.js.map +1 -1
  40. package/lib/commonjs/specs/NativeFTMobileReactNative.js.map +1 -1
  41. package/lib/commonjs/specs/NativeFTReactNativeRUM.js.map +1 -1
  42. package/lib/commonjs/version.js +1 -1
  43. package/lib/commonjs/version.js.map +1 -1
  44. package/lib/commonjs/visitorIdConfig.js +37 -0
  45. package/lib/commonjs/visitorIdConfig.js.map +1 -0
  46. package/lib/module/ft_logger.js +3 -1
  47. package/lib/module/ft_logger.js.map +1 -1
  48. package/lib/module/ft_mobile_agent.js +13 -1
  49. package/lib/module/ft_mobile_agent.js.map +1 -1
  50. package/lib/module/ft_rum.js +19 -3
  51. package/lib/module/ft_rum.js.map +1 -1
  52. package/lib/module/ft_tracing.js +2 -1
  53. package/lib/module/ft_tracing.js.map +1 -1
  54. package/lib/module/index.js +1 -0
  55. package/lib/module/index.js.map +1 -1
  56. package/lib/module/rum/FTRumViewTracking.js +140 -0
  57. package/lib/module/rum/FTRumViewTracking.js.map +1 -0
  58. package/lib/module/rum/FTRumWebSocketNative.js.map +1 -1
  59. package/lib/module/rum/FTRumWebSocketTiming.js +19 -1
  60. package/lib/module/rum/FTRumWebSocketTiming.js.map +1 -1
  61. package/lib/module/rum/FTRumWebSocketTracking.js +7 -11
  62. package/lib/module/rum/FTRumWebSocketTracking.js.map +1 -1
  63. package/lib/module/specs/NativeFTMobileReactNative.js.map +1 -1
  64. package/lib/module/specs/NativeFTReactNativeRUM.js.map +1 -1
  65. package/lib/module/version.js +1 -1
  66. package/lib/module/version.js.map +1 -1
  67. package/lib/module/visitorIdConfig.js +31 -0
  68. package/lib/module/visitorIdConfig.js.map +1 -0
  69. package/lib/typescript/ft_logger.d.ts +11 -1
  70. package/lib/typescript/ft_mobile_agent.d.ts +25 -0
  71. package/lib/typescript/ft_rum.d.ts +33 -5
  72. package/lib/typescript/ft_tracing.d.ts +9 -1
  73. package/lib/typescript/index.d.ts +2 -0
  74. package/lib/typescript/rum/FTRumViewTracking.d.ts +32 -0
  75. package/lib/typescript/rum/FTRumWebSocketNative.d.ts +2 -3
  76. package/lib/typescript/rum/FTRumWebSocketTiming.d.ts +6 -1
  77. package/lib/typescript/rum/FTRumWebSocketTracking.d.ts +2 -4
  78. package/lib/typescript/specs/NativeFTMobileReactNative.d.ts +1 -0
  79. package/lib/typescript/specs/NativeFTReactNativeRUM.d.ts +7 -1
  80. package/lib/typescript/version.d.ts +1 -1
  81. package/lib/typescript/visitorIdConfig.d.ts +11 -0
  82. package/package.json +4 -3
  83. package/src/ft_logger.tsx +11 -1
  84. package/src/ft_mobile_agent.tsx +34 -1
  85. package/src/ft_rum.tsx +50 -5
  86. package/src/ft_tracing.tsx +9 -1
  87. package/src/index.tsx +4 -0
  88. package/src/rum/FTRumViewTracking.ts +179 -0
  89. package/src/rum/FTRumWebSocketNative.ts +2 -1
  90. package/src/rum/FTRumWebSocketTiming.ts +34 -2
  91. package/src/rum/FTRumWebSocketTracking.ts +9 -14
  92. package/src/specs/NativeFTMobileReactNative.ts +1 -0
  93. package/src/specs/NativeFTReactNativeRUM.ts +15 -1
  94. package/src/version.ts +1 -1
  95. package/src/visitorIdConfig.ts +123 -0
@@ -33,6 +33,7 @@ export interface Spec extends TurboModule {
33
33
  * @returns a Promise.
34
34
  */
35
35
  unbindRUMUserData(): Promise<void>;
36
+ resetVisitorId(): Promise<void>;
36
37
  /**
37
38
  * Add custom global parameters. Applies to RUM and Log data
38
39
  * @param context custom global parameters.
@@ -32,10 +32,16 @@ export interface Spec extends TurboModule {
32
32
  /**
33
33
  * view load duration.
34
34
  * @param viewName view name
35
- * @param loadTime view load duration
35
+ * @param loadTime monotonic view load duration in nanoseconds
36
36
  * @returns a Promise.
37
37
  */
38
38
  onCreateView(viewName: string, loadTime: number): Promise<void>;
39
+ /** Replace the current View's loading duration; non-negative safe integer nanoseconds. */
40
+ updateViewLoadingTime(duration: number): Promise<void>;
41
+ /** Framework integration: duration in ns; epoch start in ms, converted natively. */
42
+ onCreateViewWithLoadStart(viewName: string, loadTime: number, loadStartEpochMs: number): Promise<void>;
43
+ /** Framework integration: Manual = 0, Automatic = 1. */
44
+ startViewWithTrackingType(viewName: string, property: Object | undefined, trackingType: number): Promise<void>;
39
45
  /**
40
46
  * view start.
41
47
  * @param viewName page name
@@ -1 +1 @@
1
- export declare const version = "0.4.3-alpha.2";
1
+ export declare const version = "0.4.3";
@@ -0,0 +1,11 @@
1
+ /** Anonymous Visitor ID header configuration for HTTP(S), excluding WebSockets. */
2
+ export interface FTVisitorIdConfig {
3
+ header: string;
4
+ /** Fixed lifetime in hours; defaults to 24. Access does not renew it. */
5
+ ttlHours?: number;
6
+ /** Complete HTTP(S) prefixes constrained to the same scheme, host and port. */
7
+ urlPrefixes?: string[];
8
+ /** Native regular expressions searched against the complete URL; rules use OR. */
9
+ regularExpressions?: string[];
10
+ }
11
+ export declare function validateVisitorIdConfig(config: FTVisitorIdConfig): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truewatchtech/react-native-mobile",
3
- "version": "0.4.3-alpha.2",
3
+ "version": "0.4.3",
4
4
  "description": "Base on TrueWatch Android iOS React Native SDK",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -78,7 +78,8 @@
78
78
  [
79
79
  "typescript",
80
80
  {
81
- "tsc": "../../node_modules/.bin/tsc"
81
+ "tsc": "../../node_modules/.bin/tsc",
82
+ "project": "tsconfig.build.json"
82
83
  }
83
84
  ]
84
85
  ]
@@ -97,5 +98,5 @@
97
98
  "directories": {
98
99
  "lib": "lib"
99
100
  },
100
- "gitHead": "b7f95ab574f7e8c0e13c91d14eea9cba0fad7531"
101
+ "gitHead": "61ed39427761a210cbfe6b339e71296f952f9697"
101
102
  }
package/src/ft_logger.tsx CHANGED
@@ -18,8 +18,10 @@ export enum FTLogCacheDiscard {
18
18
  }
19
19
  /**
20
20
  * Configure log output configuration.
21
- * @param sampleRate sampling rate
21
+ * @param logSamplingRate Log sampling percentage in [0, 100], default 100. Supports fractional percentages and takes precedence over sampleRate.
22
+ * @param sampleRate Deprecated log sampling probability in [0, 1], default 1. Used only when logSamplingRate is omitted; multiplied by 100 before passing to Native.
22
23
  * @param enableLinkRumData whether to link with RUM
24
+ * @param enableWebViewLog Collect Browser Logs SDK events from WebViews, default false
23
25
  * @param enableCustomLog whether to enable custom logs
24
26
  * @param discardStrategy log discard strategy
25
27
  * @param logLevelFilters log level filter
@@ -27,9 +29,17 @@ export enum FTLogCacheDiscard {
27
29
  * @param logCacheLimitCount get maximum log entry count limit [1000,), default 5000
28
30
  */
29
31
  export interface FTLogConfig {
32
+ /**
33
+ * Log sampling percentage in [0, 100], default 100.
34
+ * Supports fractional percentages: 12.5 means 12.5%, not 0.125.
35
+ * Takes precedence over sampleRate when both are supplied, including 0.
36
+ */
37
+ logSamplingRate?: number;
38
+ /** @deprecated Use logSamplingRate. Probability in [0, 1], default 1; 0.125 maps to 12.5%. Ignored when logSamplingRate is supplied. */
30
39
  sampleRate?: number;
31
40
  enableLinkRumData?: boolean;
32
41
  enableCustomLog?: boolean;
42
+ enableWebViewLog?: boolean;
33
43
  discardStrategy?: FTLogCacheDiscard;
34
44
  logLevelFilters?: Array<FTLogStatus>;
35
45
  globalContext?: object;
@@ -163,6 +163,8 @@ export type FTRemoteConfigOverrideMatch = {
163
163
  /**
164
164
  * Values that can be modified by remote config override rules.
165
165
  * These values will adjust the fetched remote configuration before it is applied.
166
+ * Omitted fields are not changed by this rule. Sampling percentages use [0, 100]
167
+ * and support fractions (12.5 means 12.5%); legacy probabilities use [0, 1].
166
168
  */
167
169
  export type FTRemoteConfigOverrideValues = {
168
170
  env?: string;
@@ -171,7 +173,21 @@ export type FTRemoteConfigOverrideValues = {
171
173
  compressIntakeRequests?: boolean;
172
174
  syncPageSize?: number;
173
175
  syncSleepTime?: number;
176
+ /** RUM session sampling percentage override in [0, 100]; takes precedence over rumSampleRate, including 0. */
177
+ sessionSamplingRate?: number;
178
+ /** Error session sampling percentage override in [0, 100]; takes precedence over rumSessionOnErrorSampleRate, including 0. */
179
+ sessionOnErrorSamplingRate?: number;
180
+ /** Trace sampling percentage override in [0, 100]; takes precedence over traceSampleRate, including 0. */
181
+ traceSamplingRate?: number;
182
+ /** Log sampling percentage override in [0, 100]; takes precedence over logSampleRate, including 0. */
183
+ logSamplingRate?: number;
184
+ /** Session Replay sampling percentage override in [0, 100]; fractional percentages are supported. */
185
+ sessionReplaySamplingRate?: number;
186
+ /** Error replay sampling percentage override in [0, 100]; fractional percentages are supported. */
187
+ sessionReplayOnErrorSamplingRate?: number;
188
+ /** @deprecated Use sessionSamplingRate. Legacy RUM probability override in [0, 1]; 0.125 maps to 12.5%. */
174
189
  rumSampleRate?: number;
190
+ /** @deprecated Use sessionOnErrorSamplingRate. Legacy error session probability override in [0, 1]. */
175
191
  rumSessionOnErrorSampleRate?: number;
176
192
  rumEnableTraceUserAction?: boolean;
177
193
  rumEnableTraceUserView?: boolean;
@@ -183,12 +199,15 @@ export type FTRemoteConfigOverrideValues = {
183
199
  rumEnableTrackAppANR?: boolean;
184
200
  rumEnableTraceWebView?: boolean;
185
201
  rumAllowWebViewHost?: Array<string>;
202
+ /** @deprecated Use traceSamplingRate. Legacy Trace probability override in [0, 1]. */
186
203
  traceSampleRate?: number;
187
204
  traceEnableAutoTrace?: boolean;
188
205
  traceType?: string;
206
+ /** @deprecated Use logSamplingRate. Legacy log probability override in [0, 1]. */
189
207
  logSampleRate?: number;
190
208
  logLevelFilters?: Array<string>;
191
209
  logEnableCustomLog?: boolean;
210
+ logEnableWebViewLog?: boolean;
192
211
  logEnableConsoleLog?: boolean;
193
212
  };
194
213
 
@@ -269,6 +288,10 @@ export interface FTMobileConfig {
269
288
  compressIntakeRequests?: boolean;
270
289
  enableDataFilter?: boolean;
271
290
  dataFilters?: Record<string, Array<string>>;
291
+ /** Shared WebView host allowlist for RUM and Log. Omitted uses the legacy RUM
292
+ * config; null allows every host; [] disables bridge access. Remote config wins.
293
+ */
294
+ allowWebViewHost?: Array<string> | null;
272
295
  globalContext?: object;
273
296
  groupIdentifiers?: Array<string>;
274
297
  enableLimitWithDbSize?: boolean;
@@ -320,6 +343,8 @@ type FTMobileReactNativeType = {
320
343
  * @returns a Promise.
321
344
  */
322
345
  unbindRUMUserData(): Promise<void>;
346
+ /** Rotate the native anonymous Visitor ID for subsequent requests. */
347
+ resetVisitorId(): Promise<void>;
323
348
  /**
324
349
  * Add custom global parameters. Applies to RUM and Log data
325
350
  * @param context custom global parameters.
@@ -415,7 +440,12 @@ class FTMobileReactNativeWrapper implements FTMobileReactNativeType {
415
440
  ) {
416
441
  config.datakitUrl = config.serverUrl;
417
442
  }
418
- return this.sdk.sdkConfig(config);
443
+ const nativeConfig = { ...config };
444
+ // Keep undefined equivalent to omission; explicit null overrides legacy hosts.
445
+ if (nativeConfig.allowWebViewHost === undefined) {
446
+ delete nativeConfig.allowWebViewHost;
447
+ }
448
+ return this.sdk.sdkConfig(nativeConfig);
419
449
  }
420
450
  setDatakitURL(datakitUrl: string): Promise<void> {
421
451
  return this.sdk.setDatakitURL(datakitUrl);
@@ -431,6 +461,9 @@ class FTMobileReactNativeWrapper implements FTMobileReactNativeType {
431
461
  ): Promise<void> {
432
462
  return this.sdk.bindRUMUserData(userId, userName, userEmail, extra);
433
463
  }
464
+ resetVisitorId(): Promise<void> {
465
+ return this.sdk.resetVisitorId();
466
+ }
434
467
  unbindRUMUserData(): Promise<void> {
435
468
  return this.sdk.unbindRUMUserData();
436
469
  }
package/src/ft_rum.tsx CHANGED
@@ -1,3 +1,4 @@
1
+ import { FTVisitorIdConfig, validateVisitorIdConfig } from './visitorIdConfig';
1
2
  // import { NativeModules } from 'react-native';
2
3
  import { Platform } from 'react-native';
3
4
  import { FTRumErrorTracking } from './rum/FTRumErrorTracking';
@@ -76,8 +77,10 @@ export enum IOSCrashMonitoringType {
76
77
  * Set RUM tracking conditions.
77
78
  * @param androidAppId appId, apply during monitoring
78
79
  * @param iOSAppId appId, apply during monitoring
79
- * @param sampleRate sampling rate
80
- * @param sessionOnErrorSampleRate error session sampling rate. For sessions not sampled, if ERROR is hit, collect data 1 minute before the error occurs
80
+ * @param sessionSamplingRate RUM session sampling percentage in [0, 100], default 100. Supports fractional percentages and takes precedence over sampleRate.
81
+ * @param sessionOnErrorSamplingRate Sampling percentage in [0, 100] for otherwise unsampled sessions when an error occurs, default 0. Supports fractional percentages and takes precedence over sessionOnErrorSampleRate.
82
+ * @param sampleRate Deprecated RUM session sampling probability in [0, 1], default 1. Used only when sessionSamplingRate is omitted; multiplied by 100 before passing to Native.
83
+ * @param sessionOnErrorSampleRate Deprecated error session sampling probability in [0, 1], default 0. Used only when sessionOnErrorSamplingRate is omitted; multiplied by 100 before passing to Native.
81
84
  * @param enableAutoTrackUserAction whether to automatically collect react-native control click events, can set actionName with accessibilityLabel when enabled
82
85
  * @param enableTrackError whether to automatically collect react-native Error
83
86
  * @param enableTrackNativeCrash whether to collect Native Error
@@ -101,13 +104,30 @@ export enum IOSCrashMonitoringType {
101
104
  * @param rumCacheLimitCount RUM max cache size, default 100_000
102
105
  * @param rumDiscardStrategy RUM data discard strategy
103
106
  * @param enableTraceWebView Set whether to enable WebView data collection, default true
104
- * @param allowWebViewHost Set specific hosts or domains allowed to collect WebView data, nil means collect all
107
+ * @param allowWebViewHost Deprecated; use FTMobileConfig.allowWebViewHost for shared WebView host filtering.
105
108
  * @param iosCrashMonitoringType iOS crash monitoring type , default is highCompatibility, which does not include Mach exceptions for better compatibility. you must enable system crash monitoring to get crash stack traces and crash information.
106
109
  */
107
110
  export interface FTRUMConfig {
111
+ /** Valid config enables Visitor ID; omitted/null disables it. */
112
+ visitorIdConfig?: FTVisitorIdConfig | null;
108
113
  androidAppId: string;
109
114
  iOSAppId: string;
115
+ /**
116
+ * RUM session sampling percentage in [0, 100], default 100.
117
+ * Supports fractional percentages: 12.5 means 12.5%, not 0.125.
118
+ * Takes precedence over sampleRate when both are supplied, including 0.
119
+ * Setting 0 disables regular session sampling; error session sampling is separate.
120
+ */
121
+ sessionSamplingRate?: number;
122
+ /** @deprecated Use sessionSamplingRate. Probability in [0, 1], default 1; 0.125 maps to 12.5%. Ignored when sessionSamplingRate is supplied. */
110
123
  sampleRate?: number;
124
+ /**
125
+ * Sampling percentage for otherwise unsampled RUM sessions when an error occurs.
126
+ * Range [0, 100], default 0; fractional percentages are supported.
127
+ * Takes precedence over sessionOnErrorSampleRate when both are supplied, including 0.
128
+ */
129
+ sessionOnErrorSamplingRate?: number;
130
+ /** @deprecated Use sessionOnErrorSamplingRate. Probability in [0, 1], default 0; multiplied by 100. Ignored when sessionOnErrorSamplingRate is supplied. */
111
131
  sessionOnErrorSampleRate?: number;
112
132
  enableAutoTrackUserAction?: boolean;
113
133
  enableAutoTrackError?: boolean;
@@ -129,7 +149,8 @@ export interface FTRUMConfig {
129
149
  rumCacheLimitCount?: number;
130
150
  rumDiscardStrategy?: FTRUMCacheDiscard;
131
151
  enableTraceWebView?: boolean;
132
- allowWebViewHost?: Array<string>;
152
+ /** @deprecated Configure the shared host allowlist in FTMobileConfig.allowWebViewHost. */
153
+ allowWebViewHost?: Array<string> | null;
133
154
  iosCrashMonitoringType?: IOSCrashMonitoringType;
134
155
  }
135
156
  /**
@@ -205,10 +226,17 @@ type FTReactNativeRUMType = {
205
226
  /**
206
227
  * view load duration.
207
228
  * @param viewName view name
208
- * @param loadTime view load duration
229
+ * @param loadTime monotonic view load duration in nanoseconds
209
230
  * @returns a Promise.
210
231
  */
211
232
  onCreateView(viewName: string, loadTime: number): Promise<void>;
233
+ /**
234
+ * Replace the current active View's loading duration, between startView and stopView.
235
+ * @param duration monotonic elapsed time in nanoseconds; a non-negative safe integer.
236
+ * Zero clears loading time. Late calls can update a newer View; no View ID is supplied.
237
+ * @returns a Promise for the native call, not for data upload.
238
+ */
239
+ updateViewLoadingTime(duration: number): Promise<void>;
212
240
  /**
213
241
  * view start.
214
242
  * @param viewName page name
@@ -289,6 +317,13 @@ class FTReactNativeRUMWrapper implements FTReactNativeRUMType {
289
317
  .default;
290
318
 
291
319
  setConfig(config: FTRUMConfig): Promise<void> {
320
+ if (config.visitorIdConfig != null) {
321
+ try {
322
+ validateVisitorIdConfig(config.visitorIdConfig);
323
+ } catch (error) {
324
+ return Promise.reject(error);
325
+ }
326
+ }
292
327
  bridgeContextManager.configureLongTaskContext(
293
328
  config.enableLongTask === true
294
329
  );
@@ -356,6 +391,16 @@ class FTReactNativeRUMWrapper implements FTReactNativeRUMType {
356
391
  onCreateView(viewName: string, loadTime: number): Promise<void> {
357
392
  return this.rum.onCreateView(viewName, loadTime);
358
393
  }
394
+ updateViewLoadingTime(duration: number): Promise<void> {
395
+ if (!Number.isSafeInteger(duration) || duration < 0) {
396
+ return Promise.reject(
397
+ new RangeError(
398
+ 'duration must be a non-negative safe integer in nanoseconds'
399
+ )
400
+ );
401
+ }
402
+ return this.rum.updateViewLoadingTime(duration);
403
+ }
359
404
  startView(viewName: string, property?: object): Promise<void> {
360
405
  // Automatically merge bridge context properties with local properties
361
406
  const mergedProperties =
@@ -35,12 +35,20 @@ export enum TraceType {
35
35
  }
36
36
  /**
37
37
  * Configure trace.
38
- * @param sampleRate sampling rate
38
+ * @param traceSamplingRate Trace sampling percentage in [0, 100], default 100. Supports fractional percentages and takes precedence over sampleRate.
39
+ * @param sampleRate Deprecated Trace sampling probability in [0, 1], default 1. Used only when traceSamplingRate is omitted; multiplied by 100 before passing to Native.
39
40
  * @param traceType trace type
40
41
  * @param enableLinkRUMData whether to link with RUM data
41
42
  * @param enableNativeAutoTrace whether to enable automatic tracing
42
43
  */
43
44
  export interface FTTraceConfig {
45
+ /**
46
+ * Trace sampling percentage in [0, 100], default 100.
47
+ * Supports fractional percentages: 12.5 means 12.5%, not 0.125.
48
+ * Takes precedence over sampleRate when both are supplied, including 0.
49
+ */
50
+ traceSamplingRate?: number;
51
+ /** @deprecated Use traceSamplingRate. Probability in [0, 1], default 1; 0.125 maps to 12.5%. Ignored when traceSamplingRate is supplied. */
44
52
  sampleRate?: number;
45
53
  traceType?: TraceType;
46
54
  enableLinkRUMData?: boolean;
package/src/index.tsx CHANGED
@@ -61,3 +61,7 @@ export {
61
61
  FTBabelInteractionTracking,
62
62
  __ftExtractText,
63
63
  };
64
+
65
+ export type { FTVisitorIdConfig } from './visitorIdConfig';
66
+
67
+ export { FTRumViewTracking } from './rum/FTRumViewTracking';
@@ -0,0 +1,179 @@
1
+ import { AppState } from 'react-native';
2
+ import type { AppStateStatus } from 'react-native';
3
+ import { FTReactNativeRUM } from '../ft_rum';
4
+ import { bridgeContextManager } from '../ft_mobile_agent';
5
+ import type { Spec as NativeRUMSpec } from '../specs/NativeFTReactNativeRUM';
6
+
7
+ type Route = { key: string; name: string };
8
+ type Loading = { monotonicMs: number; epochMs: number };
9
+ const MAX_PENDING_LOAD_MS = 15 * 60 * 1000;
10
+
11
+ /** Shared lifecycle coordinator for navigation adapters. Use one per navigator. */
12
+ export class FTRumViewTracking {
13
+ private current?: Route;
14
+ private active?: { key: string; generation: number };
15
+ private generation = 0;
16
+ private lifecycle = 0;
17
+ private state: AppStateStatus | null = null;
18
+ private subscription?: { remove(): void };
19
+ private loading = new Map<string, Loading>();
20
+
21
+ constructor(private readonly onBackground?: () => void) {}
22
+
23
+ start(): void {
24
+ if (this.subscription) return;
25
+ this.state = AppState.currentState;
26
+ const lifecycle = ++this.lifecycle;
27
+ const listener = (state: AppStateStatus): void => {
28
+ if (lifecycle === this.lifecycle) this.onAppState(state);
29
+ };
30
+ const subscription = AppState.addEventListener('change', listener);
31
+ // RN < 0.65 uses removeEventListener instead of returning a subscription.
32
+ const legacyAppState = AppState as typeof AppState & {
33
+ removeEventListener?: (type: string, callback: typeof listener) => void;
34
+ };
35
+ this.subscription = subscription || {
36
+ remove: () => legacyAppState.removeEventListener?.('change', listener),
37
+ };
38
+ }
39
+
40
+ /** Capture both clocks at the loading boundary, before any async work. */
41
+ beginLoad(key: string): void {
42
+ this.start();
43
+ this.pruneLoading();
44
+ if (this.state === 'background' || this.loading.has(key)) return;
45
+ if (
46
+ typeof performance === 'undefined' ||
47
+ typeof performance.now !== 'function'
48
+ )
49
+ return;
50
+ // Only the latest transition can supply loading for this navigator.
51
+ this.loading.clear();
52
+ this.loading.set(key, {
53
+ monotonicMs: performance.now(),
54
+ epochMs: Date.now(),
55
+ });
56
+ }
57
+
58
+ /** Remember a transitioning route so foreground recovery uses the current page. */
59
+ setCurrent(route: Route): void {
60
+ this.start();
61
+ this.current = { key: route.key, name: route.name };
62
+ }
63
+
64
+ show(route: Route, loadingKey: string = route.key): number | undefined {
65
+ this.setCurrent(route);
66
+ this.pruneLoading();
67
+ const loading = this.loading.get(loadingKey);
68
+ this.loading.clear();
69
+ if (this.state !== 'active') return undefined;
70
+ if (this.active?.key === route.key) return this.active.generation;
71
+ this.active = { key: route.key, generation: ++this.generation };
72
+ if (loading) {
73
+ const durationNs = Math.floor(
74
+ (performance.now() - loading.monotonicMs) * 1000000
75
+ );
76
+ if (Number.isFinite(durationNs) && durationNs > 0) {
77
+ this.report(
78
+ this.nativeRUM.onCreateViewWithLoadStart(
79
+ route.name,
80
+ durationNs,
81
+ Number.isFinite(loading.epochMs) ? loading.epochMs : 0
82
+ )
83
+ );
84
+ }
85
+ }
86
+ // Enqueue these calls together. Waiting for a Promise here could start a stale
87
+ // page after background/route changes; native consumes loading before start.
88
+ this.report(
89
+ this.nativeRUM.startViewWithTrackingType(
90
+ route.name,
91
+ bridgeContextManager.mergeWithLocalPropertiesSync(),
92
+ 1 // Automatic is an internal framework bridge detail.
93
+ )
94
+ );
95
+ return this.active.generation;
96
+ }
97
+
98
+ /** Apply a completed interval only to the appearance that started it. */
99
+ updateLoadingTime(
100
+ key: string,
101
+ generation: number | undefined,
102
+ durationNs: number
103
+ ): void {
104
+ if (
105
+ this.state !== 'active' ||
106
+ !this.active ||
107
+ this.active.key !== key ||
108
+ this.active.generation !== generation ||
109
+ !Number.isSafeInteger(durationNs) ||
110
+ durationNs <= 0
111
+ )
112
+ return;
113
+ // Enqueue immediately; never let a Promise continuation update another View.
114
+ this.report(FTReactNativeRUM.updateViewLoadingTime(durationNs));
115
+ }
116
+
117
+ /** Only the appearance that owns the active generation may close it. */
118
+ hide(key: string, generation: number | undefined): void {
119
+ if (this.state !== 'active') return;
120
+ if (
121
+ !this.active ||
122
+ this.active.key !== key ||
123
+ this.active.generation !== generation
124
+ )
125
+ return;
126
+ this.active = undefined;
127
+ this.current = undefined;
128
+ this.report(FTReactNativeRUM.stopView());
129
+ }
130
+
131
+ stop(): void {
132
+ ++this.lifecycle;
133
+ this.subscription?.remove();
134
+ this.subscription = undefined;
135
+ if (this.active && this.state === 'active')
136
+ this.report(FTReactNativeRUM.stopView());
137
+ this.active = undefined;
138
+ this.current = undefined;
139
+ this.loading.clear();
140
+ // Never reuse generation values: callbacks from a previous subscription can arrive late.
141
+ }
142
+
143
+ private onAppState = (state: AppStateStatus): void => {
144
+ if (state === this.state) return;
145
+ this.state = state;
146
+ if (state === 'background') {
147
+ // Native closes Automatic Views at its own history boundary. No JS stop.
148
+ this.active = undefined;
149
+ this.loading.clear();
150
+ this.onBackground?.();
151
+ } else if (state === 'active' && this.current) {
152
+ this.show(this.current);
153
+ }
154
+ };
155
+
156
+ private pruneLoading(): void {
157
+ const now =
158
+ typeof performance !== 'undefined' &&
159
+ typeof performance.now === 'function'
160
+ ? performance.now()
161
+ : NaN;
162
+ for (const [key, load] of this.loading) {
163
+ const age = now - load.monotonicMs;
164
+ if (!Number.isFinite(age) || age < 0 || age >= MAX_PENDING_LOAD_MS)
165
+ this.loading.delete(key);
166
+ }
167
+ }
168
+
169
+ private get nativeRUM(): NativeRUMSpec {
170
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
171
+ return require('../specs/NativeFTReactNativeRUM').default;
172
+ }
173
+
174
+ private report(result: Promise<void>): void {
175
+ result.catch((error) =>
176
+ console.warn('[TrueWatch] Automatic View tracking failed', error)
177
+ );
178
+ }
179
+ }
@@ -1,5 +1,6 @@
1
1
  import NativeWebSocket from '../specs/NativeFTReactNativeWebSocket';
2
2
  import { bridgeContextManager } from '../ft_mobile_agent';
3
+ import type { WebSocketTimingMetrics } from './FTRumWebSocketTiming';
3
4
 
4
5
  export interface NativeWebSocketResource {
5
6
  url: string;
@@ -51,7 +52,7 @@ export class NativeWebSocketSession {
51
52
  addResource(
52
53
  key: string,
53
54
  content: NativeWebSocketResource,
54
- metrics: { duration?: number }
55
+ metrics: Partial<WebSocketTimingMetrics>
55
56
  ): Promise<void> {
56
57
  return NativeWebSocket!.addResource(key, content, metrics, this.session);
57
58
  }
@@ -1,5 +1,14 @@
1
+ export interface WebSocketTimingMetrics {
2
+ duration: number;
3
+ // Decimal int64 nanoseconds: epoch nanoseconds exceed JS number precision.
4
+ fetchStartNsTimeInterval?: string;
5
+ fetchEndNsTimeInterval?: string;
6
+ }
7
+
1
8
  /** Measures only the JS-observed opening handshake; bridge delays are excluded. */
2
- export function startWebSocketTiming(): () => number | undefined {
9
+ export function startWebSocketTiming(): () =>
10
+ | WebSocketTimingMetrics
11
+ | undefined {
3
12
  const wallStart = readWallTime();
4
13
  let monotonicNow: (() => number) | undefined;
5
14
  let monotonicStart: number | undefined;
@@ -36,10 +45,33 @@ export function startWebSocketTiming(): () => number | undefined {
36
45
  }
37
46
  // Resource metrics use nanoseconds. Do not send an invalid bridge number.
38
47
  const durationNs = Math.round(durationMs * 1e6);
39
- return Number.isSafeInteger(durationNs) ? durationNs : undefined;
48
+ if (!Number.isSafeInteger(durationNs)) return undefined;
49
+ const metrics: WebSocketTimingMetrics = { duration: durationNs };
50
+ if (
51
+ wallStart !== undefined &&
52
+ Number.isSafeInteger(wallStart) &&
53
+ wallStart > 0
54
+ ) {
55
+ // Add the monotonic elapsed interval to the start epoch, not Date.now()
56
+ // at completion. Split milliseconds/remainder to avoid epoch-ns doubles.
57
+ const endMs = wallStart + Math.floor(durationNs / 1e6);
58
+ const start = `${wallStart}000000`;
59
+ const end = `${endMs}${String(durationNs % 1e6).padStart(6, '0')}`;
60
+ if (Number.isSafeInteger(endMs) && fitsInt64(start) && fitsInt64(end)) {
61
+ metrics.fetchStartNsTimeInterval = start;
62
+ metrics.fetchEndNsTimeInterval = end;
63
+ }
64
+ }
65
+ return metrics;
40
66
  };
41
67
  }
42
68
 
69
+ function fitsInt64(value: string): boolean {
70
+ return (
71
+ value.length < 19 || (value.length === 19 && value <= '9223372036854775807')
72
+ );
73
+ }
74
+
43
75
  function readWallTime(): number | undefined {
44
76
  try {
45
77
  const time = Date.now();
@@ -2,6 +2,7 @@
2
2
  import { Platform } from 'react-native';
3
3
  import NativeFTReactNativeTrace from '../specs/NativeFTReactNativeTrace';
4
4
  import { startWebSocketTiming } from './FTRumWebSocketTiming';
5
+ import type { WebSocketTimingMetrics } from './FTRumWebSocketTiming';
5
6
  import {
6
7
  clearNativeWebSocketSessions,
7
8
  startNativeWebSocketSession,
@@ -42,7 +43,7 @@ interface FTRumWebSocketResourceReporter {
42
43
  addResource(
43
44
  key: string,
44
45
  resource: FTRUMWebSocketResource,
45
- metrics?: { duration?: number; resource_http_protocol?: string }
46
+ metrics?: WebSocketTimingMetrics
46
47
  ): Promise<void>;
47
48
  }
48
49
 
@@ -330,7 +331,7 @@ export class FTRumWebSocketTracking {
330
331
  return;
331
332
  }
332
333
  completed = true;
333
- const duration = stopTiming();
334
+ const timing = stopTiming();
334
335
  // Freeze the observed boundary before inspecting or parsing error data.
335
336
  const error = getError?.();
336
337
  if (
@@ -368,7 +369,7 @@ export class FTRumWebSocketTracking {
368
369
  requestHeader: requestHeaders,
369
370
  webSocketEvent: event,
370
371
  },
371
- duration,
372
+ timing,
372
373
  error,
373
374
  errorReady
374
375
  );
@@ -383,7 +384,7 @@ export class FTRumWebSocketTracking {
383
384
  requestHeaders,
384
385
  resourceStatus,
385
386
  state,
386
- duration,
387
+ timing,
387
388
  error,
388
389
  errorReady
389
390
  );
@@ -620,7 +621,7 @@ export class FTRumWebSocketTracking {
620
621
  requestHeader: Record<string, unknown>,
621
622
  resourceStatus: number,
622
623
  webSocketHandshakeState: WebSocketHandshakeState,
623
- duration: number | undefined,
624
+ timing: WebSocketTimingMetrics | undefined,
624
625
  error?: WebSocketHandshakeError,
625
626
  errorReady?: Promise<void>
626
627
  ): Promise<void> {
@@ -657,14 +658,8 @@ export class FTRumWebSocketTracking {
657
658
  webSocketHandshakeState,
658
659
  };
659
660
  if (error?.message) resource.errorMessage = error.message;
660
- const metrics: { duration?: number } = {};
661
- if (duration !== undefined) metrics.duration = duration;
662
661
  if (isCurrentLifecycle()) {
663
- await resourceReporter.addResource(
664
- resourceKey,
665
- resource,
666
- Object.keys(metrics).length ? metrics : undefined
667
- );
662
+ await resourceReporter.addResource(resourceKey, resource, timing);
668
663
  }
669
664
  } catch {
670
665
  if (isCurrentLifecycle()) {
@@ -679,7 +674,7 @@ export class FTRumWebSocketTracking {
679
674
  key: string,
680
675
  started: Promise<boolean>,
681
676
  content: NativeWebSocketResource,
682
- duration: number | undefined,
677
+ timing: WebSocketTimingMetrics | undefined,
683
678
  error?: WebSocketHandshakeError,
684
679
  errorReady?: Promise<void>
685
680
  ): Promise<void> {
@@ -703,7 +698,7 @@ export class FTRumWebSocketTracking {
703
698
  await session.addResource(
704
699
  key,
705
700
  error?.message ? { ...content, errorMessage: error.message } : content,
706
- duration === undefined ? {} : { duration }
701
+ timing ?? {}
707
702
  );
708
703
  } catch {
709
704
  session.release(key);
@@ -41,6 +41,7 @@ export interface Spec extends TurboModule {
41
41
  * @returns a Promise.
42
42
  */
43
43
  unbindRUMUserData(): Promise<void>;
44
+ resetVisitorId(): Promise<void>;
44
45
  /**
45
46
  * Add custom global parameters. Applies to RUM and Log data
46
47
  * @param context custom global parameters.