@dreamhorizonorg/pulse-react-native 0.0.2 → 0.0.4

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 (102) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/java/com/pulsereactnativeotel/Pulse.kt +6 -0
  3. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelModule.kt +45 -24
  4. package/ios/PulseReactNativeOtel.mm +124 -63
  5. package/lib/module/NativePulseReactNativeOtel.js.map +1 -1
  6. package/lib/module/config.js +79 -19
  7. package/lib/module/config.js.map +1 -1
  8. package/lib/module/errorBoundary.js +4 -1
  9. package/lib/module/errorBoundary.js.map +1 -1
  10. package/lib/module/errorHandler.js +5 -1
  11. package/lib/module/errorHandler.js.map +1 -1
  12. package/lib/module/events.js +2 -2
  13. package/lib/module/events.js.map +1 -1
  14. package/lib/module/index.js +2 -1
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/module/navigation/index.js +21 -3
  17. package/lib/module/navigation/index.js.map +1 -1
  18. package/lib/module/navigation/navigation.interface.js +6 -0
  19. package/lib/module/navigation/navigation.interface.js.map +1 -1
  20. package/lib/module/navigation/screen-interactive.js +4 -0
  21. package/lib/module/navigation/screen-interactive.js.map +1 -1
  22. package/lib/module/navigation/screen-load.js +1 -2
  23. package/lib/module/navigation/screen-load.js.map +1 -1
  24. package/lib/module/navigation/useNavigationTracking.js +4 -3
  25. package/lib/module/navigation/useNavigationTracking.js.map +1 -1
  26. package/lib/module/network-interceptor/header-helper.js +24 -0
  27. package/lib/module/network-interceptor/header-helper.js.map +1 -0
  28. package/lib/module/network-interceptor/initialization.js +31 -2
  29. package/lib/module/network-interceptor/initialization.js.map +1 -1
  30. package/lib/module/network-interceptor/request-tracker-xhr.js +74 -5
  31. package/lib/module/network-interceptor/request-tracker-xhr.js.map +1 -1
  32. package/lib/module/network-interceptor/span-helpers.js +15 -3
  33. package/lib/module/network-interceptor/span-helpers.js.map +1 -1
  34. package/lib/module/pulse.constants.js +11 -6
  35. package/lib/module/pulse.constants.js.map +1 -1
  36. package/lib/module/trace.js +16 -10
  37. package/lib/module/trace.js.map +1 -1
  38. package/lib/module/user.js +4 -3
  39. package/lib/module/user.js.map +1 -1
  40. package/lib/typescript/plugin/src/types.d.ts +6 -0
  41. package/lib/typescript/plugin/src/types.d.ts.map +1 -1
  42. package/lib/typescript/plugin/src/utils.d.ts +2 -0
  43. package/lib/typescript/plugin/src/utils.d.ts.map +1 -1
  44. package/lib/typescript/plugin/src/withAndroidPulse.d.ts.map +1 -1
  45. package/lib/typescript/src/NativePulseReactNativeOtel.d.ts +10 -1
  46. package/lib/typescript/src/NativePulseReactNativeOtel.d.ts.map +1 -1
  47. package/lib/typescript/src/config.d.ts +11 -7
  48. package/lib/typescript/src/config.d.ts.map +1 -1
  49. package/lib/typescript/src/errorBoundary.d.ts.map +1 -1
  50. package/lib/typescript/src/errorHandler.d.ts +1 -0
  51. package/lib/typescript/src/errorHandler.d.ts.map +1 -1
  52. package/lib/typescript/src/events.d.ts.map +1 -1
  53. package/lib/typescript/src/index.d.ts +3 -2
  54. package/lib/typescript/src/index.d.ts.map +1 -1
  55. package/lib/typescript/src/navigation/index.d.ts +2 -0
  56. package/lib/typescript/src/navigation/index.d.ts.map +1 -1
  57. package/lib/typescript/src/navigation/navigation.interface.d.ts +1 -0
  58. package/lib/typescript/src/navigation/navigation.interface.d.ts.map +1 -1
  59. package/lib/typescript/src/navigation/screen-interactive.d.ts.map +1 -1
  60. package/lib/typescript/src/navigation/screen-load.d.ts.map +1 -1
  61. package/lib/typescript/src/navigation/useNavigationTracking.d.ts +1 -1
  62. package/lib/typescript/src/navigation/useNavigationTracking.d.ts.map +1 -1
  63. package/lib/typescript/src/network-interceptor/header-helper.d.ts +15 -0
  64. package/lib/typescript/src/network-interceptor/header-helper.d.ts.map +1 -0
  65. package/lib/typescript/src/network-interceptor/initialization.d.ts +5 -1
  66. package/lib/typescript/src/network-interceptor/initialization.d.ts.map +1 -1
  67. package/lib/typescript/src/network-interceptor/network.interface.d.ts +3 -0
  68. package/lib/typescript/src/network-interceptor/network.interface.d.ts.map +1 -1
  69. package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts +5 -1
  70. package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts.map +1 -1
  71. package/lib/typescript/src/network-interceptor/span-helpers.d.ts.map +1 -1
  72. package/lib/typescript/src/pulse.constants.d.ts +13 -5
  73. package/lib/typescript/src/pulse.constants.d.ts.map +1 -1
  74. package/lib/typescript/src/pulse.interface.d.ts +1 -1
  75. package/lib/typescript/src/pulse.interface.d.ts.map +1 -1
  76. package/lib/typescript/src/trace.d.ts.map +1 -1
  77. package/lib/typescript/src/user.d.ts.map +1 -1
  78. package/package.json +1 -1
  79. package/plugin/build/types.d.ts +6 -0
  80. package/plugin/build/utils.d.ts +2 -0
  81. package/plugin/build/utils.js +6 -2
  82. package/plugin/build/withAndroidPulse.js +3 -1
  83. package/src/NativePulseReactNativeOtel.ts +11 -1
  84. package/src/config.ts +120 -28
  85. package/src/errorBoundary.tsx +4 -1
  86. package/src/errorHandler.ts +6 -1
  87. package/src/events.ts +6 -2
  88. package/src/index.tsx +3 -2
  89. package/src/navigation/index.ts +49 -7
  90. package/src/navigation/navigation.interface.ts +7 -0
  91. package/src/navigation/screen-interactive.ts +4 -0
  92. package/src/navigation/screen-load.ts +1 -7
  93. package/src/navigation/useNavigationTracking.ts +13 -4
  94. package/src/network-interceptor/header-helper.ts +26 -0
  95. package/src/network-interceptor/initialization.ts +36 -2
  96. package/src/network-interceptor/network.interface.ts +3 -0
  97. package/src/network-interceptor/request-tracker-xhr.ts +106 -5
  98. package/src/network-interceptor/span-helpers.ts +23 -3
  99. package/src/pulse.constants.ts +19 -5
  100. package/src/pulse.interface.ts +1 -1
  101. package/src/trace.ts +17 -10
  102. package/src/user.ts +4 -3
@@ -7,7 +7,7 @@ const utils_1 = require("./utils");
7
7
  const withAndroidPulse = (config, props) => {
8
8
  return (0, config_plugins_1.withMainApplication)(config, (modConfig) => {
9
9
  try {
10
- const { endpointBaseUrl, endpointHeaders, globalAttributes, instrumentation, } = props;
10
+ const { endpointBaseUrl, tenantId, endpointHeaders, configEndpointUrl, globalAttributes, instrumentation, } = props;
11
11
  // 1. Add import statements
12
12
  modConfig.modResults.contents = (0, generateCode_1.mergeContents)({
13
13
  src: modConfig.modResults.contents,
@@ -29,7 +29,9 @@ const withAndroidPulse = (config, props) => {
29
29
  }
30
30
  const initCode = (0, utils_1.buildPulseInitializationCode)({
31
31
  endpointBaseUrl,
32
+ tenantId,
32
33
  endpointHeaders,
34
+ configEndpointUrl,
33
35
  globalAttributes,
34
36
  instrumentation,
35
37
  });
@@ -60,7 +60,17 @@ export interface Spec extends TurboModule {
60
60
  setCurrentScreenName(screenName: string): boolean;
61
61
 
62
62
  /** Get all SDK Remote Config features */
63
- getAllFeatures(): Object;
63
+ getAllFeatures(): {
64
+ rn_screen_load: boolean;
65
+ screen_session: boolean;
66
+ rn_screen_interactive: boolean;
67
+ network_instrumentation: boolean;
68
+ custom_events: boolean;
69
+ js_crash: boolean;
70
+ } | null;
71
+
72
+ /** Shut down the Pulse SDK. After this, re-init is not supported. */
73
+ shutdown(): boolean;
64
74
  }
65
75
 
66
76
  export default TurboModuleRegistry.getEnforcing<Spec>('PulseReactNativeOtel');
package/src/config.ts CHANGED
@@ -1,48 +1,71 @@
1
- import { setupErrorHandler } from './errorHandler';
1
+ import { setupErrorHandler, uninstallErrorHandler } from './errorHandler';
2
2
  import { isSupportedPlatform } from './initialization';
3
3
  import {
4
4
  createReactNavigationIntegration,
5
+ uninstallNavigationIntegration,
5
6
  type ReactNavigationIntegration,
6
7
  type NavigationIntegrationOptions,
7
8
  } from './navigation';
8
- import { initializeNetworkInterceptor } from './network-interceptor/initialization';
9
+ import {
10
+ initializeNetworkInterceptor,
11
+ uninstallNetworkInterceptor,
12
+ } from './network-interceptor/initialization';
9
13
  import PulseReactNativeOtel from './NativePulseReactNativeOtel';
10
14
  import type { PulseFeatureConfig } from './pulse.interface';
15
+ import { PULSE_FEATURE_NAMES } from './pulse.constants';
11
16
 
12
- export type PulseConfig = {
13
- autoDetectExceptions?: boolean;
14
- autoDetectNavigation?: boolean;
15
- autoDetectNetwork?: boolean;
17
+ export type NetworkHeaderConfig = {
18
+ requestHeaders?: string[];
19
+ responseHeaders?: string[];
16
20
  };
17
21
 
18
- export type PulseStartOptions = {
22
+ export type PulseConfig = {
19
23
  autoDetectExceptions?: boolean;
20
24
  autoDetectNavigation?: boolean;
21
25
  autoDetectNetwork?: boolean;
26
+ networkHeaders?: NetworkHeaderConfig;
22
27
  };
23
28
 
24
- const defaultConfig: PulseConfig = {
29
+ const defaultConfig: Required<PulseConfig> = {
25
30
  autoDetectExceptions: true,
26
31
  autoDetectNavigation: true,
27
32
  autoDetectNetwork: true,
33
+ networkHeaders: {
34
+ requestHeaders: [],
35
+ responseHeaders: [],
36
+ },
28
37
  };
29
38
 
30
39
  let currentConfig: PulseConfig = { ...defaultConfig };
31
40
 
41
+ /** After shutdown, start() and initialize are no-ops; re-initialization is not supported. */
42
+ let isShutdown = false;
43
+
44
+ /** True only after start() has been called at least once. Integrations (e.g. navigation) are no-ops until then. */
45
+ let isStarted = false;
46
+
32
47
  // Cache for features from remote SDK config
33
- let cachedFeatures: PulseFeatureConfig = null;
48
+ let cachedFeatures: PulseFeatureConfig;
49
+
50
+ export function getIsShutdown(): boolean {
51
+ return isShutdown;
52
+ }
53
+
54
+ /** True only after start() has been called at least once. Public APIs (trackEvent, reportException, startSpan, etc.) no-op until then. */
55
+ export function getIsStarted(): boolean {
56
+ return isStarted;
57
+ }
34
58
 
35
59
  /**
36
60
  * Gets all features from the remote SDK config.
37
- * @returns Record of feature names to their enabled status
61
+ * @returns Record of feature names to their enabled status, or null if config not available or start() not called
38
62
  */
39
63
  export function getFeaturesFromRemoteConfig(): PulseFeatureConfig {
40
- if (cachedFeatures !== null) {
64
+ if (cachedFeatures !== undefined) {
41
65
  return cachedFeatures;
42
66
  }
43
67
 
44
- const features = PulseReactNativeOtel.getAllFeatures();
45
- cachedFeatures = features as PulseFeatureConfig;
68
+ cachedFeatures = PulseReactNativeOtel.getAllFeatures();
46
69
  return cachedFeatures;
47
70
  }
48
71
 
@@ -54,27 +77,84 @@ function configure(config: PulseConfig): void {
54
77
  setupErrorHandler(currentConfig.autoDetectExceptions ?? true);
55
78
 
56
79
  if (currentConfig.autoDetectNetwork) {
57
- initializeNetworkInterceptor();
80
+ initializeNetworkInterceptor(
81
+ currentConfig.networkHeaders ?? {
82
+ requestHeaders: [],
83
+ responseHeaders: [],
84
+ }
85
+ );
58
86
  }
59
87
  }
60
88
 
61
- export function start(options?: PulseStartOptions): void {
62
- if (!isSupportedPlatform()) {
89
+ function resolveFeatureState(
90
+ features: PulseFeatureConfig,
91
+ featureName: string,
92
+ optionValue: boolean
93
+ ): boolean {
94
+ if (features !== undefined && features !== null)
95
+ return features[featureName] ?? optionValue;
96
+ return optionValue;
97
+ }
98
+
99
+ function resolveNavigationState(
100
+ features: PulseFeatureConfig,
101
+ optionValue: boolean
102
+ ): boolean {
103
+ if (features !== undefined && features !== null) {
104
+ const hasAny =
105
+ features[PULSE_FEATURE_NAMES.SCREEN_SESSION] === true ||
106
+ features[PULSE_FEATURE_NAMES.RN_SCREEN_LOAD] === true ||
107
+ features[PULSE_FEATURE_NAMES.RN_SCREEN_INTERACTIVE] === true;
108
+ return hasAny ?? optionValue;
109
+ }
110
+ return optionValue;
111
+ }
112
+
113
+ export function start(options?: PulseConfig): void {
114
+ if (!isSupportedPlatform()) return;
115
+ if (isShutdown) {
116
+ console.log(
117
+ '[Pulse] SDK has been shut down. Pulse.start() is a no-op; re-initialization is not supported.'
118
+ );
63
119
  return;
64
120
  }
121
+
122
+ isStarted = true;
65
123
  const features = getFeaturesFromRemoteConfig();
66
- const autoDetectExceptions =
67
- features?.js_crash ?? options?.autoDetectExceptions ?? true;
68
- const autoDetectNavigation =
69
- features?.rn_navigation ?? options?.autoDetectNavigation ?? true;
70
- const autoDetectNetwork =
71
- features?.network_instrumentation ?? options?.autoDetectNetwork ?? true;
72
-
73
- configure({
74
- autoDetectExceptions,
75
- autoDetectNavigation,
76
- autoDetectNetwork,
77
- });
124
+ const config: PulseConfig = {
125
+ autoDetectExceptions: resolveFeatureState(
126
+ features,
127
+ PULSE_FEATURE_NAMES.JS_CRASH,
128
+ options?.autoDetectExceptions ?? defaultConfig.autoDetectExceptions
129
+ ),
130
+ autoDetectNavigation: resolveNavigationState(
131
+ features,
132
+ options?.autoDetectNavigation ?? defaultConfig.autoDetectNavigation
133
+ ),
134
+ autoDetectNetwork: resolveFeatureState(
135
+ features,
136
+ PULSE_FEATURE_NAMES.NETWORK_INSTRUMENTATION,
137
+ options?.autoDetectNetwork ?? defaultConfig.autoDetectNetwork
138
+ ),
139
+ networkHeaders: options?.networkHeaders ?? {
140
+ requestHeaders: [],
141
+ responseHeaders: [],
142
+ },
143
+ };
144
+
145
+ configure(config);
146
+ }
147
+
148
+ export function shutdown(): void {
149
+ if (isShutdown) {
150
+ console.warn('[Pulse] SDK already shut down.');
151
+ return;
152
+ }
153
+ uninstallErrorHandler();
154
+ uninstallNetworkInterceptor();
155
+ uninstallNavigationIntegration();
156
+ PulseReactNativeOtel.shutdown();
157
+ isShutdown = true;
78
158
  }
79
159
 
80
160
  export function createNavigationIntegrationWithConfig(
@@ -86,6 +166,18 @@ export function createNavigationIntegrationWithConfig(
86
166
  markContentReady: () => {},
87
167
  };
88
168
  }
169
+ if (!isStarted) {
170
+ return {
171
+ registerNavigationContainer: (_: unknown) => () => {},
172
+ markContentReady: () => {},
173
+ };
174
+ }
175
+ if (isShutdown) {
176
+ return {
177
+ registerNavigationContainer: (_: unknown) => () => {},
178
+ markContentReady: () => {},
179
+ };
180
+ }
89
181
  if (!currentConfig.autoDetectNavigation) {
90
182
  console.warn(
91
183
  '[Pulse Navigation] auto-detection disabled via Pulse.start; createNavigationIntegration() returning no-op.'
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { Pulse } from './index';
3
+ import { getIsShutdown } from './config';
3
4
 
4
5
  export const UNKNOWN_COMPONENT = 'unknown';
5
6
  const COMPONENT_STACK_UNAVAILABLE = '<component stack unavailable>';
@@ -46,7 +47,9 @@ export class ErrorBoundary extends React.Component<
46
47
 
47
48
  const errorToReport =
48
49
  error instanceof Error ? error : new Error(String(error));
49
- Pulse.reportException(errorToReport, !handled);
50
+ if (!getIsShutdown()) {
51
+ Pulse.reportException(errorToReport, !handled);
52
+ }
50
53
 
51
54
  if (onError) {
52
55
  onError(error, componentStack);
@@ -1,4 +1,5 @@
1
1
  import PulseReactNativeOtel from './NativePulseReactNativeOtel';
2
+ import { getIsShutdown, getIsStarted } from './config';
2
3
  import { mergeWithGlobalAttributes } from './globalAttributes';
3
4
  import { isSupportedPlatform } from './initialization';
4
5
  import { extractErrorDetails } from './utility';
@@ -68,7 +69,7 @@ export function reportException(
68
69
  isFatal: boolean = false,
69
70
  attributes?: PulseAttributes
70
71
  ): void {
71
- if (!isSupportedPlatform()) {
72
+ if (!isSupportedPlatform() || !getIsStarted() || getIsShutdown()) {
72
73
  return;
73
74
  }
74
75
 
@@ -160,6 +161,10 @@ function disableErrorHandler(): void {
160
161
  handlingFatal = false;
161
162
  }
162
163
 
164
+ export function uninstallErrorHandler(): void {
165
+ disableErrorHandler();
166
+ }
167
+
163
168
  export function setupErrorHandler(enableErrorHandler: boolean): void {
164
169
  if (enableErrorHandler) {
165
170
  initializeErrorHandler();
package/src/events.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import PulseReactNativeOtel from './NativePulseReactNativeOtel';
2
- import { getFeaturesFromRemoteConfig } from './config';
2
+ import {
3
+ getFeaturesFromRemoteConfig,
4
+ getIsShutdown,
5
+ getIsStarted,
6
+ } from './config';
3
7
  import { mergeWithGlobalAttributes } from './globalAttributes';
4
8
  import { isSupportedPlatform } from './initialization';
5
9
  import type { PulseAttributes } from './pulse.interface';
6
10
 
7
11
  export function trackEvent(event: string, attributes?: PulseAttributes): void {
8
- if (!isSupportedPlatform()) {
12
+ if (!isSupportedPlatform() || !getIsStarted() || getIsShutdown()) {
9
13
  return;
10
14
  }
11
15
  const features = getFeaturesFromRemoteConfig();
package/src/index.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import { startSpan, trackSpan } from './trace';
2
2
  import { reportException } from './errorHandler';
3
3
  import { trackEvent } from './events';
4
- import { start } from './config';
4
+ import { start, shutdown } from './config';
5
5
  import { isInitialized } from './initialization';
6
6
  import { setGlobalAttribute } from './globalAttributes';
7
7
  import { setUserId, setUserProperty, setUserProperties } from './user';
@@ -9,7 +9,7 @@ import { ErrorBoundary, withErrorBoundary } from './errorBoundary';
9
9
  import { useNavigationTracking, markContentReady } from './navigation';
10
10
 
11
11
  export type { Span } from './trace';
12
- export type { PulseConfig, PulseStartOptions } from './config';
12
+ export type { PulseConfig } from './config';
13
13
  export type { PulseAttributes, PulseAttributeValue } from './pulse.interface';
14
14
  export type {
15
15
  ReactNavigationIntegration,
@@ -22,6 +22,7 @@ export type { ErrorBoundaryProps, FallbackRender } from './errorBoundary';
22
22
  export { SpanStatusCode } from './trace';
23
23
  export const Pulse = {
24
24
  start,
25
+ shutdown,
25
26
  isInitialized,
26
27
  useNavigationTracking,
27
28
  markContentReady,
@@ -5,6 +5,7 @@ import type {
5
5
  NavigationIntegrationOptions,
6
6
  NavigationRoute,
7
7
  } from './navigation.interface';
8
+ import { DEFAULT_NAVIGATION_OPTIONS } from './navigation.interface';
8
9
  import { pushRecentRouteKey, LOG_TAGS } from './utils';
9
10
  import { discardSpan } from '../trace';
10
11
  import {
@@ -28,8 +29,22 @@ import { useNavigationTracking as useNavigationTrackingBase } from './useNavigat
28
29
  import { isSupportedPlatform } from '../initialization';
29
30
  import PulseReactNativeOtel from '../NativePulseReactNativeOtel';
30
31
  import { getFeaturesFromRemoteConfig } from '../config';
32
+ import {
33
+ PULSE_FEATURE_NAMES,
34
+ type NavigationFeatureName,
35
+ } from '../pulse.constants';
31
36
 
32
37
  export type { NavigationRoute, NavigationIntegrationOptions };
38
+ export { DEFAULT_NAVIGATION_OPTIONS } from './navigation.interface';
39
+
40
+ let currentNavigationUnregister: (() => void) | null = null;
41
+
42
+ export function uninstallNavigationIntegration(): void {
43
+ if (currentNavigationUnregister) {
44
+ currentNavigationUnregister();
45
+ currentNavigationUnregister = null;
46
+ }
47
+ }
33
48
 
34
49
  export interface ReactNavigationIntegration {
35
50
  registerNavigationContainer: (
@@ -38,18 +53,41 @@ export interface ReactNavigationIntegration {
38
53
  markContentReady: () => void;
39
54
  }
40
55
 
56
+ function resolveNavigationFeatureState(
57
+ features: ReturnType<typeof getFeaturesFromRemoteConfig>,
58
+ featureName: NavigationFeatureName,
59
+ optionValue: boolean
60
+ ): boolean {
61
+ if (features !== undefined && features !== null)
62
+ return features[featureName] ?? optionValue;
63
+ return optionValue;
64
+ }
65
+
41
66
  export function createReactNavigationIntegration(
42
67
  options?: NavigationIntegrationOptions
43
68
  ): ReactNavigationIntegration {
44
69
  const features = getFeaturesFromRemoteConfig();
45
- const screenSessionTracking =
46
- features?.screen_session ?? options?.screenSessionTracking ?? true;
47
- const screenNavigationTracking =
48
- features?.rn_screen_load ?? options?.screenNavigationTracking ?? true;
49
- const screenInteractiveTracking =
50
- features?.rn_screen_interactive ??
70
+
71
+ const screenSessionTracking = resolveNavigationFeatureState(
72
+ features,
73
+ PULSE_FEATURE_NAMES.SCREEN_SESSION,
74
+ options?.screenSessionTracking ??
75
+ DEFAULT_NAVIGATION_OPTIONS.screenSessionTracking
76
+ );
77
+
78
+ const screenNavigationTracking = resolveNavigationFeatureState(
79
+ features,
80
+ PULSE_FEATURE_NAMES.RN_SCREEN_LOAD,
81
+ options?.screenNavigationTracking ??
82
+ DEFAULT_NAVIGATION_OPTIONS.screenNavigationTracking
83
+ );
84
+
85
+ const screenInteractiveTracking = resolveNavigationFeatureState(
86
+ features,
87
+ PULSE_FEATURE_NAMES.RN_SCREEN_INTERACTIVE,
51
88
  options?.screenInteractiveTracking ??
52
- false;
89
+ DEFAULT_NAVIGATION_OPTIONS.screenInteractiveTracking
90
+ );
53
91
 
54
92
  let navigationContainer: NavigationContainer | undefined;
55
93
  let recentRouteKeys: string[] = [];
@@ -241,6 +279,9 @@ export function createReactNavigationIntegration(
241
279
  }
242
280
  navigationContainer = undefined;
243
281
  isInitialized = false;
282
+ if (currentNavigationUnregister === unmountCleanup) {
283
+ currentNavigationUnregister = null;
284
+ }
244
285
 
245
286
  clearGlobalMarkContentReady(
246
287
  updatedInteractiveTracker.markContentReady
@@ -275,6 +316,7 @@ export function createReactNavigationIntegration(
275
316
  );
276
317
  isInitialized = true;
277
318
 
319
+ currentNavigationUnregister = unmountCleanup;
278
320
  return unmountCleanup;
279
321
  } catch (error) {
280
322
  console.error(
@@ -17,3 +17,10 @@ export interface NavigationIntegrationOptions {
17
17
  screenNavigationTracking?: boolean;
18
18
  screenInteractiveTracking?: boolean;
19
19
  }
20
+
21
+ export const DEFAULT_NAVIGATION_OPTIONS: Required<NavigationIntegrationOptions> =
22
+ {
23
+ screenSessionTracking: true,
24
+ screenNavigationTracking: true,
25
+ screenInteractiveTracking: false,
26
+ };
@@ -1,4 +1,5 @@
1
1
  import { Pulse, type Span } from '../index';
2
+ import { getIsStarted } from '../config';
2
3
  import { Platform } from 'react-native';
3
4
  import { SPAN_NAMES, ATTRIBUTE_KEYS, PULSE_TYPES } from '../pulse.constants';
4
5
  import { discardSpan } from '../trace';
@@ -131,6 +132,9 @@ export function createScreenInteractiveTracker(
131
132
  }
132
133
 
133
134
  export function markContentReady(): void {
135
+ if (!getIsStarted()) {
136
+ return;
137
+ }
134
138
  if (globalMarkContentReady) {
135
139
  globalMarkContentReady();
136
140
  } else {
@@ -1,11 +1,6 @@
1
1
  import { Pulse, type Span } from '../index';
2
2
  import { Platform } from 'react-native';
3
- import {
4
- SPAN_NAMES,
5
- ATTRIBUTE_KEYS,
6
- PULSE_TYPES,
7
- PHASE_VALUES,
8
- } from '../pulse.constants';
3
+ import { SPAN_NAMES, ATTRIBUTE_KEYS, PULSE_TYPES } from '../pulse.constants';
9
4
  import type { NavigationRoute } from './navigation.interface';
10
5
  import { LOG_TAGS } from './utils';
11
6
 
@@ -34,7 +29,6 @@ export function createScreenLoadTracker(
34
29
  state.navigationSpan = Pulse.startSpan(SPAN_NAMES.NAVIGATED, {
35
30
  attributes: {
36
31
  [ATTRIBUTE_KEYS.PULSE_TYPE]: PULSE_TYPES.SCREEN_LOAD,
37
- [ATTRIBUTE_KEYS.PHASE]: PHASE_VALUES.START,
38
32
  [ATTRIBUTE_KEYS.PLATFORM]: Platform.OS as 'android' | 'ios',
39
33
  },
40
34
  });
@@ -1,5 +1,8 @@
1
1
  import { useRef, useCallback, useEffect, useMemo, type RefObject } from 'react';
2
- import type { NavigationIntegrationOptions } from './navigation.interface';
2
+ import {
3
+ DEFAULT_NAVIGATION_OPTIONS,
4
+ type NavigationIntegrationOptions,
5
+ } from './navigation.interface';
3
6
  import type { ReactNavigationIntegration } from './index';
4
7
 
5
8
  export function useNavigationTracking(
@@ -9,9 +12,15 @@ export function useNavigationTracking(
9
12
  options?: NavigationIntegrationOptions
10
13
  ) => ReactNavigationIntegration
11
14
  ): () => void {
12
- const screenSessionTracking = options?.screenSessionTracking ?? true;
13
- const screenNavigationTracking = options?.screenNavigationTracking ?? true;
14
- const screenInteractiveTracking = options?.screenInteractiveTracking ?? false;
15
+ const screenSessionTracking =
16
+ options?.screenSessionTracking ??
17
+ DEFAULT_NAVIGATION_OPTIONS.screenSessionTracking;
18
+ const screenNavigationTracking =
19
+ options?.screenNavigationTracking ??
20
+ DEFAULT_NAVIGATION_OPTIONS.screenNavigationTracking;
21
+ const screenInteractiveTracking =
22
+ options?.screenInteractiveTracking ??
23
+ DEFAULT_NAVIGATION_OPTIONS.screenInteractiveTracking;
15
24
 
16
25
  const integration = useMemo(() => {
17
26
  if (createIntegration) {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Normalizes header name according to OpenTelemetry HTTP semantic conventions:
3
+ * - Lowercase
4
+ * - Dashes replaced by underscores
5
+ * - Reference: https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/
6
+ * @example
7
+ * normalizeHeaderName('Content-Type') => 'content_type'
8
+ * normalizeHeaderName('X-Request-ID') => 'x_request_id'
9
+ */
10
+ export function normalizeHeaderName(headerName: string): string {
11
+ return headerName.toLowerCase().replace(/-/g, '_');
12
+ }
13
+
14
+ /**
15
+ * Checks if a header should be captured based on configuration.
16
+ */
17
+ export function shouldCaptureHeader(
18
+ headerName: string,
19
+ headerList: string[]
20
+ ): boolean {
21
+ if (headerList.length === 0) return false;
22
+ // Case-insensitive comparison
23
+ return headerList.some(
24
+ (configHeader) => configHeader.toLowerCase() === headerName.toLowerCase()
25
+ );
26
+ }
@@ -1,20 +1,43 @@
1
1
  import createXmlHttpRequestTracker from './request-tracker-xhr';
2
+ import type { NetworkHeaderConfig } from '../config';
3
+ // Re-export header utilities for convenience (they're in a separate file to avoid dependency issues)
4
+ export { normalizeHeaderName, shouldCaptureHeader } from './header-helper';
2
5
 
3
6
  let isInitialized = false;
7
+ let uninstallXmlHttpRequestTracker: (() => void) | null = null;
8
+ let headerConfig: NetworkHeaderConfig = {
9
+ requestHeaders: [],
10
+ responseHeaders: [],
11
+ };
4
12
 
5
- export function initializeNetworkInterceptor(): void {
13
+ export function getHeaderConfig(): NetworkHeaderConfig {
14
+ return headerConfig;
15
+ }
16
+
17
+ export function initializeNetworkInterceptor(
18
+ config?: NetworkHeaderConfig
19
+ ): void {
6
20
  if (isInitialized) {
7
21
  console.warn('[Pulse] Network interceptor already initialized');
8
22
  return;
9
23
  }
10
24
 
25
+ // Store header configuration
26
+ if (config) {
27
+ headerConfig = {
28
+ requestHeaders: config.requestHeaders ?? [],
29
+ responseHeaders: config.responseHeaders ?? [],
30
+ };
31
+ }
32
+
11
33
  console.log('[Pulse] 🔄 Starting network interceptor initialization...');
12
34
 
13
35
  try {
14
36
  // In react-native, we are intercepting XMLHttpRequest only, since axios and fetch both use it internally.
15
37
  // See: https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Network/fetch.js
16
38
  if (typeof XMLHttpRequest !== 'undefined') {
17
- createXmlHttpRequestTracker(XMLHttpRequest);
39
+ const result = createXmlHttpRequestTracker(XMLHttpRequest);
40
+ uninstallXmlHttpRequestTracker = result.uninstall;
18
41
  } else {
19
42
  console.warn('[Pulse] XMLHttpRequest is not available');
20
43
  }
@@ -26,3 +49,14 @@ export function initializeNetworkInterceptor(): void {
26
49
  }
27
50
 
28
51
  export const isNetworkInterceptorInitialized = (): boolean => isInitialized;
52
+
53
+ export function uninstallNetworkInterceptor(): void {
54
+ if (!isInitialized) {
55
+ return;
56
+ }
57
+ if (uninstallXmlHttpRequestTracker) {
58
+ uninstallXmlHttpRequestTracker();
59
+ uninstallXmlHttpRequestTracker = null;
60
+ }
61
+ isInitialized = false;
62
+ }
@@ -3,17 +3,20 @@ export interface RequestStartContext {
3
3
  method: string;
4
4
  type: 'fetch' | 'xmlhttprequest';
5
5
  baseUrl?: string;
6
+ requestHeaders?: Record<string, string>;
6
7
  }
7
8
 
8
9
  export interface RequestEndContextSuccess {
9
10
  status: number;
10
11
  state: 'success';
12
+ responseHeaders?: Record<string, string>;
11
13
  }
12
14
 
13
15
  export interface RequestEndContextError {
14
16
  state: 'error';
15
17
  status?: number;
16
18
  error?: Error;
19
+ responseHeaders?: Record<string, string>;
17
20
  }
18
21
 
19
22
  export type RequestEndContext =