@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.
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/pulsereactnativeotel/Pulse.kt +6 -0
- package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelModule.kt +45 -24
- package/ios/PulseReactNativeOtel.mm +124 -63
- package/lib/module/NativePulseReactNativeOtel.js.map +1 -1
- package/lib/module/config.js +79 -19
- package/lib/module/config.js.map +1 -1
- package/lib/module/errorBoundary.js +4 -1
- package/lib/module/errorBoundary.js.map +1 -1
- package/lib/module/errorHandler.js +5 -1
- package/lib/module/errorHandler.js.map +1 -1
- package/lib/module/events.js +2 -2
- package/lib/module/events.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigation/index.js +21 -3
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/navigation.interface.js +6 -0
- package/lib/module/navigation/navigation.interface.js.map +1 -1
- package/lib/module/navigation/screen-interactive.js +4 -0
- package/lib/module/navigation/screen-interactive.js.map +1 -1
- package/lib/module/navigation/screen-load.js +1 -2
- package/lib/module/navigation/screen-load.js.map +1 -1
- package/lib/module/navigation/useNavigationTracking.js +4 -3
- package/lib/module/navigation/useNavigationTracking.js.map +1 -1
- package/lib/module/network-interceptor/header-helper.js +24 -0
- package/lib/module/network-interceptor/header-helper.js.map +1 -0
- package/lib/module/network-interceptor/initialization.js +31 -2
- package/lib/module/network-interceptor/initialization.js.map +1 -1
- package/lib/module/network-interceptor/request-tracker-xhr.js +74 -5
- package/lib/module/network-interceptor/request-tracker-xhr.js.map +1 -1
- package/lib/module/network-interceptor/span-helpers.js +15 -3
- package/lib/module/network-interceptor/span-helpers.js.map +1 -1
- package/lib/module/pulse.constants.js +11 -6
- package/lib/module/pulse.constants.js.map +1 -1
- package/lib/module/trace.js +16 -10
- package/lib/module/trace.js.map +1 -1
- package/lib/module/user.js +4 -3
- package/lib/module/user.js.map +1 -1
- package/lib/typescript/plugin/src/types.d.ts +6 -0
- package/lib/typescript/plugin/src/types.d.ts.map +1 -1
- package/lib/typescript/plugin/src/utils.d.ts +2 -0
- package/lib/typescript/plugin/src/utils.d.ts.map +1 -1
- package/lib/typescript/plugin/src/withAndroidPulse.d.ts.map +1 -1
- package/lib/typescript/src/NativePulseReactNativeOtel.d.ts +10 -1
- package/lib/typescript/src/NativePulseReactNativeOtel.d.ts.map +1 -1
- package/lib/typescript/src/config.d.ts +11 -7
- package/lib/typescript/src/config.d.ts.map +1 -1
- package/lib/typescript/src/errorBoundary.d.ts.map +1 -1
- package/lib/typescript/src/errorHandler.d.ts +1 -0
- package/lib/typescript/src/errorHandler.d.ts.map +1 -1
- package/lib/typescript/src/events.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/index.d.ts +2 -0
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/navigation.interface.d.ts +1 -0
- package/lib/typescript/src/navigation/navigation.interface.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen-interactive.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen-load.d.ts.map +1 -1
- package/lib/typescript/src/navigation/useNavigationTracking.d.ts +1 -1
- package/lib/typescript/src/navigation/useNavigationTracking.d.ts.map +1 -1
- package/lib/typescript/src/network-interceptor/header-helper.d.ts +15 -0
- package/lib/typescript/src/network-interceptor/header-helper.d.ts.map +1 -0
- package/lib/typescript/src/network-interceptor/initialization.d.ts +5 -1
- package/lib/typescript/src/network-interceptor/initialization.d.ts.map +1 -1
- package/lib/typescript/src/network-interceptor/network.interface.d.ts +3 -0
- package/lib/typescript/src/network-interceptor/network.interface.d.ts.map +1 -1
- package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts +5 -1
- package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts.map +1 -1
- package/lib/typescript/src/network-interceptor/span-helpers.d.ts.map +1 -1
- package/lib/typescript/src/pulse.constants.d.ts +13 -5
- package/lib/typescript/src/pulse.constants.d.ts.map +1 -1
- package/lib/typescript/src/pulse.interface.d.ts +1 -1
- package/lib/typescript/src/pulse.interface.d.ts.map +1 -1
- package/lib/typescript/src/trace.d.ts.map +1 -1
- package/lib/typescript/src/user.d.ts.map +1 -1
- package/package.json +1 -1
- package/plugin/build/types.d.ts +6 -0
- package/plugin/build/utils.d.ts +2 -0
- package/plugin/build/utils.js +6 -2
- package/plugin/build/withAndroidPulse.js +3 -1
- package/src/NativePulseReactNativeOtel.ts +11 -1
- package/src/config.ts +120 -28
- package/src/errorBoundary.tsx +4 -1
- package/src/errorHandler.ts +6 -1
- package/src/events.ts +6 -2
- package/src/index.tsx +3 -2
- package/src/navigation/index.ts +49 -7
- package/src/navigation/navigation.interface.ts +7 -0
- package/src/navigation/screen-interactive.ts +4 -0
- package/src/navigation/screen-load.ts +1 -7
- package/src/navigation/useNavigationTracking.ts +13 -4
- package/src/network-interceptor/header-helper.ts +26 -0
- package/src/network-interceptor/initialization.ts +36 -2
- package/src/network-interceptor/network.interface.ts +3 -0
- package/src/network-interceptor/request-tracker-xhr.ts +106 -5
- package/src/network-interceptor/span-helpers.ts +23 -3
- package/src/pulse.constants.ts +19 -5
- package/src/pulse.interface.ts +1 -1
- package/src/trace.ts +17 -10
- 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():
|
|
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 {
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
autoDetectNetwork?: boolean;
|
|
17
|
+
export type NetworkHeaderConfig = {
|
|
18
|
+
requestHeaders?: string[];
|
|
19
|
+
responseHeaders?: string[];
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
export type
|
|
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
|
|
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 !==
|
|
64
|
+
if (cachedFeatures !== undefined) {
|
|
41
65
|
return cachedFeatures;
|
|
42
66
|
}
|
|
43
67
|
|
|
44
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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.'
|
package/src/errorBoundary.tsx
CHANGED
|
@@ -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
|
-
|
|
50
|
+
if (!getIsShutdown()) {
|
|
51
|
+
Pulse.reportException(errorToReport, !handled);
|
|
52
|
+
}
|
|
50
53
|
|
|
51
54
|
if (onError) {
|
|
52
55
|
onError(error, componentStack);
|
package/src/errorHandler.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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,
|
package/src/navigation/index.ts
CHANGED
|
@@ -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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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 =
|