@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
@@ -6,6 +6,8 @@ import { RequestTracker } from './request-tracker';
6
6
  import { getAbsoluteUrl } from '../utility';
7
7
  import type { Span } from '../index';
8
8
  import { createNetworkSpan, completeNetworkSpan } from './span-helpers';
9
+ import { getHeaderConfig } from './initialization';
10
+ import { shouldCaptureHeader } from './header-helper';
9
11
 
10
12
  interface RequestData {
11
13
  method: string;
@@ -16,12 +18,17 @@ type ReadyStateChangeHandler = (this: XMLHttpRequest, ev: Event) => any;
16
18
 
17
19
  let isXHRIntercepted = false;
18
20
 
21
+ export interface XmlHttpRequestTrackerResult {
22
+ requestTracker: RequestTracker;
23
+ uninstall: () => void;
24
+ }
25
+
19
26
  function createXmlHttpRequestTracker(
20
27
  xhr: typeof XMLHttpRequest
21
- ): RequestTracker {
28
+ ): XmlHttpRequestTrackerResult {
22
29
  if (isXHRIntercepted) {
23
30
  console.warn('[Pulse] XMLHttpRequest already intercepted');
24
- return new RequestTracker();
31
+ return { requestTracker: new RequestTracker(), uninstall: () => {} };
25
32
  }
26
33
 
27
34
  const requestTracker = new RequestTracker();
@@ -48,6 +55,28 @@ function createXmlHttpRequestTracker(
48
55
  };
49
56
  isXHRIntercepted = true;
50
57
 
58
+ // Store request headers before send
59
+ const requestHeadersMap = new WeakMap<
60
+ XMLHttpRequest,
61
+ Record<string, string>
62
+ >();
63
+ const originalSetRequestHeader = xhr.prototype.setRequestHeader;
64
+ xhr.prototype.setRequestHeader = function setRequestHeader(
65
+ name: string,
66
+ value: string
67
+ ): void {
68
+ const headerConfig = getHeaderConfig();
69
+ const requestHeadersList = headerConfig.requestHeaders ?? [];
70
+ if (
71
+ requestHeadersList.length > 0 &&
72
+ shouldCaptureHeader(name, requestHeadersList)
73
+ ) {
74
+ const existing = requestHeadersMap.get(this) || {};
75
+ requestHeadersMap.set(this, { ...existing, [name]: value });
76
+ }
77
+ originalSetRequestHeader.call(this, name, value);
78
+ };
79
+
51
80
  const originalSend = xhr.prototype.send;
52
81
  xhr.prototype.send = function send(
53
82
  body?: Document | XMLHttpRequestBodyInit | null
@@ -58,10 +87,28 @@ function createXmlHttpRequestTracker(
58
87
  if (existingHandler)
59
88
  this.removeEventListener('readystatechange', existingHandler);
60
89
 
90
+ // Capture request headers
91
+ const headerConfig = getHeaderConfig();
92
+ const capturedRequestHeaders = requestHeadersMap.get(this);
93
+ const requestHeadersList = headerConfig.requestHeaders ?? [];
94
+ const filteredRequestHeaders: Record<string, string> | undefined =
95
+ capturedRequestHeaders && requestHeadersList.length > 0
96
+ ? Object.fromEntries(
97
+ Object.entries(capturedRequestHeaders).filter(([name]) =>
98
+ shouldCaptureHeader(name, requestHeadersList)
99
+ )
100
+ )
101
+ : undefined;
102
+
61
103
  const startContext: RequestStartContext = {
62
104
  type: 'xmlhttprequest',
63
105
  method: requestData.method,
64
106
  url: requestData.url,
107
+ requestHeaders:
108
+ filteredRequestHeaders &&
109
+ Object.keys(filteredRequestHeaders).length > 0
110
+ ? filteredRequestHeaders
111
+ : undefined,
65
112
  };
66
113
 
67
114
  this.setRequestHeader('X-Pulse-RN-Tracked', 'true');
@@ -74,13 +121,56 @@ function createXmlHttpRequestTracker(
74
121
  if (this.readyState === xhr.DONE && onRequestEnd) {
75
122
  const activeSpan = trackedSpans.get(this);
76
123
 
124
+ // Capture response headers
125
+ const responseHeaderConfig = getHeaderConfig();
126
+ const capturedResponseHeaders: Record<string, string> = {};
127
+ const responseHeadersList =
128
+ responseHeaderConfig.responseHeaders ?? [];
129
+ if (responseHeadersList.length > 0) {
130
+ try {
131
+ const allHeaders = this.getAllResponseHeaders();
132
+ if (allHeaders) {
133
+ const headerLines = allHeaders.trim().split(/[\r\n]+/);
134
+ for (const line of headerLines) {
135
+ const parts = line.split(': ');
136
+ if (parts.length === 2) {
137
+ const [name, value] = parts;
138
+ if (
139
+ name &&
140
+ value &&
141
+ shouldCaptureHeader(name, responseHeadersList)
142
+ ) {
143
+ capturedResponseHeaders[name] = value;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ } catch (e) {
149
+ // Headers may not be available in some cases (CORS, etc.)
150
+ console.debug('[Pulse] Could not read response headers:', e);
151
+ }
152
+ }
153
+
77
154
  // Determine request outcome based on status code
78
155
  let endContext: RequestEndContext;
79
156
 
157
+ const responseHeaders =
158
+ Object.keys(capturedResponseHeaders).length > 0
159
+ ? capturedResponseHeaders
160
+ : undefined;
161
+
80
162
  if (this.status <= 0 || this.status >= 400) {
81
- endContext = { state: 'error', status: this.status };
163
+ endContext = {
164
+ state: 'error',
165
+ status: this.status,
166
+ responseHeaders,
167
+ };
82
168
  } else {
83
- endContext = { state: 'success', status: this.status };
169
+ endContext = {
170
+ state: 'success',
171
+ status: this.status,
172
+ responseHeaders,
173
+ };
84
174
  }
85
175
 
86
176
  if (activeSpan) {
@@ -93,6 +183,9 @@ function createXmlHttpRequestTracker(
93
183
  trackedSpans.delete(this);
94
184
  }
95
185
 
186
+ // Clean up
187
+ requestHeadersMap.delete(this);
188
+
96
189
  onRequestEnd(endContext);
97
190
  }
98
191
  };
@@ -104,7 +197,15 @@ function createXmlHttpRequestTracker(
104
197
  originalSend.call(this, body);
105
198
  };
106
199
 
107
- return requestTracker;
200
+ const uninstall = (): void => {
201
+ if (!isXHRIntercepted) return;
202
+ xhr.prototype.open = originalOpen;
203
+ xhr.prototype.setRequestHeader = originalSetRequestHeader;
204
+ xhr.prototype.send = originalSend;
205
+ isXHRIntercepted = false;
206
+ };
207
+
208
+ return { requestTracker, uninstall };
108
209
  }
109
210
 
110
211
  export default createXmlHttpRequestTracker;
@@ -8,7 +8,8 @@ import { Pulse, SpanStatusCode } from '../index';
8
8
  import type { PulseAttributes } from '../pulse.interface';
9
9
  import { extractHttpAttributes } from './url-helper';
10
10
  import { updateAttributesWithGraphQLData } from './graphql-helper';
11
- import { ATTRIBUTE_KEYS, PHASE_VALUES } from '../pulse.constants';
11
+ import { ATTRIBUTE_KEYS, PULSE_TYPES } from '../pulse.constants';
12
+ import { normalizeHeaderName } from './header-helper';
12
13
 
13
14
  export function setNetworkSpanAttributes(
14
15
  span: Span,
@@ -19,7 +20,7 @@ export function setNetworkSpanAttributes(
19
20
  let attributes: PulseAttributes = {
20
21
  [ATTRIBUTE_KEYS.HTTP_METHOD]: method,
21
22
  [ATTRIBUTE_KEYS.HTTP_URL]: startContext.url,
22
- [ATTRIBUTE_KEYS.PULSE_TYPE]: `network.${endContext.status ?? 0}`,
23
+ [ATTRIBUTE_KEYS.PULSE_TYPE]: `${PULSE_TYPES.NETWORK}.${endContext.status ?? 0}`,
23
24
  [ATTRIBUTE_KEYS.HTTP_REQUEST_TYPE]: startContext.type,
24
25
  [ATTRIBUTE_KEYS.PLATFORM]: Platform.OS,
25
26
  };
@@ -43,6 +44,26 @@ export function setNetworkSpanAttributes(
43
44
  span.recordException(endContext.error, attributes);
44
45
  }
45
46
 
47
+ if (startContext.requestHeaders) {
48
+ for (const [headerName, headerValue] of Object.entries(
49
+ startContext.requestHeaders
50
+ )) {
51
+ const normalizedName = normalizeHeaderName(headerName);
52
+ attributes[`${ATTRIBUTE_KEYS.HTTP_REQUEST_HEADER}.${normalizedName}`] =
53
+ headerValue;
54
+ }
55
+ }
56
+
57
+ if (endContext.responseHeaders) {
58
+ for (const [headerName, headerValue] of Object.entries(
59
+ endContext.responseHeaders
60
+ )) {
61
+ const normalizedName = normalizeHeaderName(headerName);
62
+ attributes[`${ATTRIBUTE_KEYS.HTTP_RESPONSE_HEADER}.${normalizedName}`] =
63
+ headerValue;
64
+ }
65
+ }
66
+
46
67
  span.setAttributes(attributes);
47
68
  return attributes;
48
69
  }
@@ -58,7 +79,6 @@ export function createNetworkSpan(
58
79
  let baseAttributes: PulseAttributes = {
59
80
  [ATTRIBUTE_KEYS.HTTP_METHOD]: method,
60
81
  [ATTRIBUTE_KEYS.HTTP_URL]: startContext.url,
61
- [ATTRIBUTE_KEYS.PULSE_TYPE]: PHASE_VALUES.NETWORK,
62
82
  [ATTRIBUTE_KEYS.HTTP_REQUEST_TYPE]: interceptorType,
63
83
  };
64
84
 
@@ -12,7 +12,6 @@ export enum ATTRIBUTE_KEYS {
12
12
  PULSE_TYPE = 'pulse.type',
13
13
  SCREEN_NAME = 'screen.name',
14
14
  ROUTE_KEY = 'routeKey',
15
- PHASE = 'phase',
16
15
  LAST_SCREEN_NAME = 'last.screen.name',
17
16
  ROUTE_HAS_BEEN_SEEN = 'routeHasBeenSeen',
18
17
  PLATFORM = 'platform',
@@ -22,6 +21,8 @@ export enum ATTRIBUTE_KEYS {
22
21
  HTTP_URL = 'http.url',
23
22
  HTTP_STATUS_CODE = 'http.status_code',
24
23
  HTTP_REQUEST_TYPE = 'http.request.type',
24
+ HTTP_REQUEST_HEADER = 'http.request.header',
25
+ HTTP_RESPONSE_HEADER = 'http.response.header',
25
26
  ERROR_MESSAGE = 'error.message',
26
27
  ERROR_STACK = 'error.stack',
27
28
  }
@@ -30,9 +31,22 @@ export enum PULSE_TYPES {
30
31
  SCREEN_SESSION = 'screen_session',
31
32
  SCREEN_LOAD = 'screen_load',
32
33
  SCREEN_INTERACTIVE = 'screen_interactive',
33
- }
34
-
35
- export enum PHASE_VALUES {
36
- START = 'start',
37
34
  NETWORK = 'network',
38
35
  }
36
+
37
+ export const PULSE_FEATURE_NAMES = {
38
+ RN_SCREEN_LOAD: 'rn_screen_load',
39
+ SCREEN_SESSION: 'screen_session',
40
+ RN_SCREEN_INTERACTIVE: 'rn_screen_interactive',
41
+ NETWORK_INSTRUMENTATION: 'network_instrumentation',
42
+ CUSTOM_EVENTS: 'custom_events',
43
+ JS_CRASH: 'js_crash',
44
+ } as const;
45
+
46
+ export type PulseFeatureName =
47
+ (typeof PULSE_FEATURE_NAMES)[keyof typeof PULSE_FEATURE_NAMES];
48
+
49
+ export type NavigationFeatureName =
50
+ | typeof PULSE_FEATURE_NAMES.SCREEN_SESSION
51
+ | typeof PULSE_FEATURE_NAMES.RN_SCREEN_LOAD
52
+ | typeof PULSE_FEATURE_NAMES.RN_SCREEN_INTERACTIVE;
@@ -16,4 +16,4 @@ export type PulseAttributes = Record<
16
16
  PulseAttributeValue | undefined | null
17
17
  >;
18
18
 
19
- export type PulseFeatureConfig = Record<string, boolean> | null;
19
+ export type PulseFeatureConfig = Record<string, boolean> | null | undefined;
package/src/trace.ts CHANGED
@@ -1,9 +1,18 @@
1
1
  import PulseReactNativeOtel from './NativePulseReactNativeOtel';
2
+ import { getIsShutdown, getIsStarted } from './config';
2
3
  import { isSupportedPlatform } from './initialization';
3
4
  import { mergeWithGlobalAttributes } from './globalAttributes';
4
5
  import { extractErrorDetails } from './utility';
5
6
  import type { PulseAttributes } from './pulse.interface';
6
7
 
8
+ const noopSpan: Span = {
9
+ end: (_statusCode?: SpanStatusCode) => {},
10
+ addEvent: (_eventName: string, _eventAttributes?: PulseAttributes) => {},
11
+ setAttributes: (_attributes?: PulseAttributes) => {},
12
+ recordException: (_error: Error, _attributes?: PulseAttributes) => {},
13
+ spanId: undefined,
14
+ };
15
+
7
16
  /**
8
17
  * Options for starting a span.
9
18
  * @param attributes - Attributes to set on the span.
@@ -30,14 +39,8 @@ export type Span = {
30
39
  };
31
40
 
32
41
  export function startSpan(name: string, options?: SpanOptions): Span {
33
- if (!isSupportedPlatform()) {
34
- return {
35
- end: (_statusCode?: SpanStatusCode) => {},
36
- addEvent: (_eventName: string, _eventAttributes?: PulseAttributes) => {},
37
- setAttributes: (_attributes?: PulseAttributes) => {},
38
- recordException: (_error: Error, _attributes?: PulseAttributes) => {},
39
- spanId: undefined,
40
- };
42
+ if (!isSupportedPlatform() || !getIsStarted() || getIsShutdown()) {
43
+ return noopSpan;
41
44
  }
42
45
 
43
46
  const mergedAttributes = mergeWithGlobalAttributes(options?.attributes || {});
@@ -69,7 +72,7 @@ export function trackSpan<T>(
69
72
  options: SpanOptions,
70
73
  fn: () => T | Promise<T>
71
74
  ): T | Promise<T> {
72
- if (!isSupportedPlatform()) {
75
+ if (!isSupportedPlatform() || !getIsStarted() || getIsShutdown()) {
73
76
  return fn();
74
77
  }
75
78
 
@@ -94,11 +97,12 @@ export function trackSpan<T>(
94
97
  }
95
98
 
96
99
  function endSpan(spanId: string, statusCode?: SpanStatusCode): void {
100
+ if (getIsShutdown()) return;
97
101
  PulseReactNativeOtel.endSpan(spanId, statusCode);
98
102
  }
99
103
 
100
104
  export function discardSpan(spanId: string): void {
101
- if (!isSupportedPlatform()) {
105
+ if (!isSupportedPlatform() || !getIsStarted() || getIsShutdown()) {
102
106
  return;
103
107
  }
104
108
  PulseReactNativeOtel.discardSpan(spanId);
@@ -109,14 +113,17 @@ function addSpanEvent(
109
113
  name: string,
110
114
  attributes?: PulseAttributes
111
115
  ): void {
116
+ if (getIsShutdown()) return;
112
117
  PulseReactNativeOtel.addSpanEvent(spanId, name, attributes || undefined);
113
118
  }
114
119
 
115
120
  function setSpanAttributes(spanId: string, attributes?: PulseAttributes): void {
121
+ if (getIsShutdown()) return;
116
122
  PulseReactNativeOtel.setSpanAttributes(spanId, attributes || undefined);
117
123
  }
118
124
 
119
125
  function recordSpanException(error: Error, attributes?: PulseAttributes): void {
126
+ if (getIsShutdown()) return;
120
127
  const { message, stackTrace, errorType } = extractErrorDetails(error);
121
128
  const observedTimeMs = Date.now();
122
129
  const mergedAttributes = mergeWithGlobalAttributes(attributes || {});
package/src/user.ts CHANGED
@@ -1,23 +1,24 @@
1
1
  import PulseReactNativeOtel from './NativePulseReactNativeOtel';
2
+ import { getIsShutdown } from './config';
2
3
  import { isSupportedPlatform } from './initialization';
3
4
  import type { PulseAttributes } from './pulse.interface';
4
5
 
5
6
  export function setUserId(id: string | null): void {
6
- if (!isSupportedPlatform()) {
7
+ if (!isSupportedPlatform() || getIsShutdown()) {
7
8
  return;
8
9
  }
9
10
  PulseReactNativeOtel.setUserId(id);
10
11
  }
11
12
 
12
13
  export function setUserProperty(name: string, value: string | null): void {
13
- if (!isSupportedPlatform()) {
14
+ if (!isSupportedPlatform() || getIsShutdown()) {
14
15
  return;
15
16
  }
16
17
  PulseReactNativeOtel.setUserProperty(name, value);
17
18
  }
18
19
 
19
20
  export function setUserProperties(properties: PulseAttributes): void {
20
- if (!isSupportedPlatform()) {
21
+ if (!isSupportedPlatform() || getIsShutdown()) {
21
22
  return;
22
23
  }
23
24
  PulseReactNativeOtel.setUserProperties(properties);