@dreamhorizonorg/pulse-react-native 0.0.1

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 (118) hide show
  1. package/LICENSE +20 -0
  2. package/PulseReactNativeOtel.podspec +21 -0
  3. package/README.md +900 -0
  4. package/android/build.gradle +97 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/proguard-rules.pro +108 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/java/com/pulsereactnativeotel/PulseOtelConstants.kt +20 -0
  9. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelLogger.kt +73 -0
  10. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelModule.kt +85 -0
  11. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelPackage.kt +33 -0
  12. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelTracer.kt +127 -0
  13. package/ios/PulseReactNativeOtel.h +5 -0
  14. package/ios/PulseReactNativeOtel.mm +100 -0
  15. package/lib/module/NativePulseReactNativeOtel.js +5 -0
  16. package/lib/module/NativePulseReactNativeOtel.js.map +1 -0
  17. package/lib/module/config.js +55 -0
  18. package/lib/module/config.js.map +1 -0
  19. package/lib/module/errorBoundary.js +62 -0
  20. package/lib/module/errorBoundary.js.map +1 -0
  21. package/lib/module/errorHandler.js +112 -0
  22. package/lib/module/errorHandler.js.map +1 -0
  23. package/lib/module/events.js +14 -0
  24. package/lib/module/events.js.map +1 -0
  25. package/lib/module/global.d.js +4 -0
  26. package/lib/module/global.d.js.map +1 -0
  27. package/lib/module/globalAttributes.js +13 -0
  28. package/lib/module/globalAttributes.js.map +1 -0
  29. package/lib/module/index.js +27 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/initialization.js +18 -0
  32. package/lib/module/initialization.js.map +1 -0
  33. package/lib/module/network-interceptor/initialization.js +25 -0
  34. package/lib/module/network-interceptor/initialization.js.map +1 -0
  35. package/lib/module/network-interceptor/network.interface.js +2 -0
  36. package/lib/module/network-interceptor/network.interface.js.map +1 -0
  37. package/lib/module/network-interceptor/request-tracker-fetch.js +72 -0
  38. package/lib/module/network-interceptor/request-tracker-fetch.js.map +1 -0
  39. package/lib/module/network-interceptor/request-tracker-xhr.js +75 -0
  40. package/lib/module/network-interceptor/request-tracker-xhr.js.map +1 -0
  41. package/lib/module/network-interceptor/request-tracker.js +25 -0
  42. package/lib/module/network-interceptor/request-tracker.js.map +1 -0
  43. package/lib/module/network-interceptor/span-helpers.js +62 -0
  44. package/lib/module/network-interceptor/span-helpers.js.map +1 -0
  45. package/lib/module/network-interceptor/url-helper.js +127 -0
  46. package/lib/module/network-interceptor/url-helper.js.map +1 -0
  47. package/lib/module/pulse.interface.js +2 -0
  48. package/lib/module/pulse.interface.js.map +1 -0
  49. package/lib/module/reactNavigation.js +100 -0
  50. package/lib/module/reactNavigation.js.map +1 -0
  51. package/lib/module/trace.js +75 -0
  52. package/lib/module/trace.js.map +1 -0
  53. package/lib/module/user.js +23 -0
  54. package/lib/module/user.js.map +1 -0
  55. package/lib/module/utility.js +34 -0
  56. package/lib/module/utility.js.map +1 -0
  57. package/lib/typescript/src/NativePulseReactNativeOtel.d.ts +31 -0
  58. package/lib/typescript/src/NativePulseReactNativeOtel.d.ts.map +1 -0
  59. package/lib/typescript/src/config.d.ts +14 -0
  60. package/lib/typescript/src/config.d.ts.map +1 -0
  61. package/lib/typescript/src/errorBoundary.d.ts +26 -0
  62. package/lib/typescript/src/errorBoundary.d.ts.map +1 -0
  63. package/lib/typescript/src/errorHandler.d.ts +4 -0
  64. package/lib/typescript/src/errorHandler.d.ts.map +1 -0
  65. package/lib/typescript/src/events.d.ts +3 -0
  66. package/lib/typescript/src/events.d.ts.map +1 -0
  67. package/lib/typescript/src/globalAttributes.d.ts +4 -0
  68. package/lib/typescript/src/globalAttributes.d.ts.map +1 -0
  69. package/lib/typescript/src/index.d.ts +30 -0
  70. package/lib/typescript/src/index.d.ts.map +1 -0
  71. package/lib/typescript/src/initialization.d.ts +3 -0
  72. package/lib/typescript/src/initialization.d.ts.map +1 -0
  73. package/lib/typescript/src/network-interceptor/initialization.d.ts +3 -0
  74. package/lib/typescript/src/network-interceptor/initialization.d.ts.map +1 -0
  75. package/lib/typescript/src/network-interceptor/network.interface.d.ts +29 -0
  76. package/lib/typescript/src/network-interceptor/network.interface.d.ts.map +1 -0
  77. package/lib/typescript/src/network-interceptor/request-tracker-fetch.d.ts +7 -0
  78. package/lib/typescript/src/network-interceptor/request-tracker-fetch.d.ts.map +1 -0
  79. package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts +4 -0
  80. package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts.map +1 -0
  81. package/lib/typescript/src/network-interceptor/request-tracker.d.ts +9 -0
  82. package/lib/typescript/src/network-interceptor/request-tracker.d.ts.map +1 -0
  83. package/lib/typescript/src/network-interceptor/span-helpers.d.ts +7 -0
  84. package/lib/typescript/src/network-interceptor/span-helpers.d.ts.map +1 -0
  85. package/lib/typescript/src/network-interceptor/url-helper.d.ts +38 -0
  86. package/lib/typescript/src/network-interceptor/url-helper.d.ts.map +1 -0
  87. package/lib/typescript/src/pulse.interface.d.ts +8 -0
  88. package/lib/typescript/src/pulse.interface.d.ts.map +1 -0
  89. package/lib/typescript/src/reactNavigation.d.ts +10 -0
  90. package/lib/typescript/src/reactNavigation.d.ts.map +1 -0
  91. package/lib/typescript/src/trace.d.ts +19 -0
  92. package/lib/typescript/src/trace.d.ts.map +1 -0
  93. package/lib/typescript/src/user.d.ts +5 -0
  94. package/lib/typescript/src/user.d.ts.map +1 -0
  95. package/lib/typescript/src/utility.d.ts +8 -0
  96. package/lib/typescript/src/utility.d.ts.map +1 -0
  97. package/package.json +165 -0
  98. package/src/NativePulseReactNativeOtel.ts +57 -0
  99. package/src/config.ts +75 -0
  100. package/src/errorBoundary.tsx +92 -0
  101. package/src/errorHandler.ts +169 -0
  102. package/src/events.ts +15 -0
  103. package/src/global.d.ts +9 -0
  104. package/src/globalAttributes.ts +16 -0
  105. package/src/index.tsx +35 -0
  106. package/src/initialization.ts +18 -0
  107. package/src/network-interceptor/initialization.ts +28 -0
  108. package/src/network-interceptor/network.interface.ts +36 -0
  109. package/src/network-interceptor/request-tracker-fetch.ts +96 -0
  110. package/src/network-interceptor/request-tracker-xhr.ts +108 -0
  111. package/src/network-interceptor/request-tracker.ts +31 -0
  112. package/src/network-interceptor/span-helpers.ts +84 -0
  113. package/src/network-interceptor/url-helper.ts +155 -0
  114. package/src/pulse.interface.ts +14 -0
  115. package/src/reactNavigation.tsx +146 -0
  116. package/src/trace.ts +108 -0
  117. package/src/user.ts +24 -0
  118. package/src/utility.ts +36 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * URL parsing helper for React Native
3
+ * Provides consistent URL parsing across all RN versions
4
+ *
5
+ * React Native < 0.80 throws errors for URL properties like hostname, protocol, etc.
6
+ * This helper uses the same regex approach as RN 0.80+ to ensure compatibility.
7
+ *
8
+ * Based on: https://github.com/facebook/react-native/blob/v0.80.0/packages/react-native/Libraries/Blob/URL.js
9
+ */
10
+
11
+ export interface ParsedUrl {
12
+ protocol: string;
13
+ hostname: string;
14
+ host: string;
15
+ port: string;
16
+ pathname: string;
17
+ search: string;
18
+ hash: string;
19
+ href: string;
20
+ }
21
+
22
+ /**
23
+ * Safely extract a regex match group
24
+ * Returns empty string if match fails or group is undefined
25
+ */
26
+ function safeMatch(
27
+ text: string,
28
+ regex: RegExp,
29
+ groupIndex: number = 1
30
+ ): string {
31
+ try {
32
+ const match = text.match(regex);
33
+ return match && match[groupIndex] ? match[groupIndex] : '';
34
+ } catch (e) {
35
+ return '';
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Parse a URL string into its components
41
+ * Returns null if URL is invalid or cannot be parsed
42
+ *
43
+ * This function is defensive and will return empty strings for any component
44
+ * that cannot be extracted, rather than throwing errors.
45
+ */
46
+ export function parseUrl(url: string): ParsedUrl | null {
47
+ // Validate input
48
+ if (!url || typeof url !== 'string' || url.trim().length === 0) {
49
+ return null;
50
+ }
51
+
52
+ // Ensure URL has a protocol (required for proper parsing)
53
+ if (!url.match(/^[a-zA-Z][a-zA-Z\d+\-.]*:/)) {
54
+ // Not a valid absolute URL
55
+ return null;
56
+ }
57
+
58
+ try {
59
+ // Protocol - e.g., "https:"
60
+ const protocol = safeMatch(url, /^([a-zA-Z][a-zA-Z\d+\-.]*):/);
61
+ const protocolWithColon = protocol ? `${protocol}:` : '';
62
+
63
+ // Hostname - e.g., "example.com"
64
+ const hostname = safeMatch(url, /^https?:\/\/(?:[^@]+@)?([^:/?#]+)/);
65
+
66
+ // Port - e.g., "8080"
67
+ const port = safeMatch(url, /:(\d+)(?=[/?#]|$)/);
68
+
69
+ // Host (hostname + port if present) - e.g., "example.com:8080"
70
+ const host = hostname ? (port ? `${hostname}:${port}` : hostname) : '';
71
+
72
+ // Pathname - e.g., "/api/users"
73
+ const pathMatch = url.match(/https?:\/\/[^/]+(\/[^?#]*)?/);
74
+ const pathname = pathMatch && pathMatch[1] ? pathMatch[1] : '/';
75
+
76
+ // Search - e.g., "?id=123"
77
+ const searchContent = safeMatch(url, /\?([^#]*)/);
78
+ const search = searchContent ? `?${searchContent}` : '';
79
+
80
+ // Hash - e.g., "#section"
81
+ const hashContent = safeMatch(url, /#([^/]*)/);
82
+ const hash = hashContent ? `#${hashContent}` : '';
83
+
84
+ return {
85
+ protocol: protocolWithColon,
86
+ hostname: hostname,
87
+ host: host,
88
+ port: port,
89
+ pathname: pathname,
90
+ search: search,
91
+ hash: hash,
92
+ href: url,
93
+ };
94
+ } catch (e) {
95
+ // Any unexpected error during parsing - return null
96
+ console.warn('[Pulse] URL parsing failed:', e);
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Extract HTTP attributes from a URL string for OpenTelemetry span attributes
103
+ */
104
+ export function extractHttpAttributes(url: string): {
105
+ 'http.scheme'?: string;
106
+ 'http.host'?: string;
107
+ 'http.target'?: string;
108
+ 'net.peer.name'?: string;
109
+ 'net.peer.port'?: number;
110
+ } {
111
+ try {
112
+ const parsed = parseUrl(url);
113
+ if (!parsed) {
114
+ return {};
115
+ }
116
+
117
+ const attributes: {
118
+ 'http.scheme'?: string;
119
+ 'http.host'?: string;
120
+ 'http.target'?: string;
121
+ 'net.peer.name'?: string;
122
+ 'net.peer.port'?: number;
123
+ } = {};
124
+
125
+ // http.scheme - protocol without colon (e.g., "https")
126
+ if (parsed.protocol && parsed.protocol.length > 0) {
127
+ attributes['http.scheme'] = parsed.protocol.replace(':', '');
128
+ }
129
+
130
+ // http.host and net.peer.name - hostname (e.g., "api.example.com")
131
+ if (parsed.hostname && parsed.hostname.length > 0) {
132
+ attributes['http.host'] = parsed.hostname;
133
+ attributes['net.peer.name'] = parsed.hostname;
134
+ }
135
+
136
+ // http.target - pathname + search (e.g., "/api/users?id=123")
137
+ if (parsed.pathname && parsed.pathname.length > 0) {
138
+ attributes['http.target'] = parsed.pathname + parsed.search;
139
+ }
140
+
141
+ // net.peer.port - port number (e.g., 8080)
142
+ if (parsed.port && parsed.port.length > 0) {
143
+ const portNum = parseInt(parsed.port, 10);
144
+ if (!isNaN(portNum) && portNum > 0 && portNum <= 65535) {
145
+ attributes['net.peer.port'] = portNum;
146
+ }
147
+ }
148
+
149
+ return attributes;
150
+ } catch (e) {
151
+ // Absolutely ensure this function never throws
152
+ console.warn('[Pulse] Failed to extract HTTP attributes from URL:', e);
153
+ return {};
154
+ }
155
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * OpenTelemetry attribute value types.
3
+ * Based on OpenTelemetry JavaScript SDK attribute types.
4
+ * @see https://github.com/open-telemetry/opentelemetry-js/blob/main/api/src/common/Attributes.ts
5
+ */
6
+ export type PulseAttributeValue =
7
+ | string
8
+ | number
9
+ | boolean
10
+ | string[]
11
+ | number[]
12
+ | boolean[];
13
+
14
+ export type PulseAttributes = Record<string, PulseAttributeValue | undefined>;
@@ -0,0 +1,146 @@
1
+ import { Pulse, type Span } from './index';
2
+
3
+ const NAVIGATION_HISTORY_MAX_SIZE = 200;
4
+
5
+ export interface NavigationRoute {
6
+ name: string;
7
+ key: string;
8
+ params?: Record<string, any>;
9
+ }
10
+
11
+ interface NavigationContainer {
12
+ addListener: (type: string, listener: (event?: unknown) => void) => void;
13
+ getCurrentRoute: () => NavigationRoute | undefined;
14
+ }
15
+
16
+ export function createReactNavigationIntegration() {
17
+ let navigationContainer: NavigationContainer | undefined;
18
+ let latestRoute: NavigationRoute | undefined;
19
+ let recentRouteKeys: string[] = [];
20
+ let isInitialized = false;
21
+ let span: Span | undefined;
22
+
23
+ const onNavigationDispatch = (): void => {
24
+ try {
25
+ span = Pulse.startSpan('Navigated', {
26
+ attributes: {
27
+ 'pulse.type': 'screen_load',
28
+ 'phase': 'start',
29
+ },
30
+ });
31
+
32
+ console.log('[Pulse Navigation] Navigation dispatch span started', span);
33
+ } catch (error) {
34
+ console.warn('[Pulse Navigation] Error in onNavigationDispatch:', error);
35
+ }
36
+ };
37
+
38
+ const onStateChange = (): void => {
39
+ try {
40
+ if (!navigationContainer) {
41
+ console.warn('[Pulse Navigation] Navigation container not registered');
42
+ return;
43
+ }
44
+
45
+ const currentRoute = navigationContainer.getCurrentRoute();
46
+ if (!currentRoute) {
47
+ return;
48
+ }
49
+
50
+ if (!span) {
51
+ latestRoute = currentRoute;
52
+ pushRecentRouteKey(currentRoute.key);
53
+ return;
54
+ }
55
+
56
+ const previousRoute = latestRoute;
57
+
58
+ if (previousRoute && previousRoute.key === currentRoute.key) {
59
+ return;
60
+ }
61
+
62
+ latestRoute = currentRoute;
63
+
64
+ const routeHasBeenSeen = recentRouteKeys.includes(currentRoute.key);
65
+ pushRecentRouteKey(currentRoute.key);
66
+
67
+ span?.setAttributes({
68
+ 'screen.name': currentRoute.name,
69
+ 'last.screen.name': previousRoute?.name || undefined,
70
+ 'routeHasBeenSeen': routeHasBeenSeen,
71
+ 'routeKey': currentRoute.key,
72
+ });
73
+
74
+ span?.end();
75
+ console.log('[Pulse Navigation] Navigation dispatch span ended', span);
76
+ span = undefined;
77
+ } catch (error) {
78
+ console.warn('[Pulse Navigation] Error in onStateChange:', error);
79
+ span = undefined;
80
+ }
81
+ };
82
+
83
+ const registerNavigationContainer = (
84
+ maybeNavigationContainer: unknown
85
+ ): void => {
86
+ try {
87
+ let container: NavigationContainer | undefined;
88
+ if (
89
+ typeof maybeNavigationContainer === 'object' &&
90
+ maybeNavigationContainer !== null &&
91
+ 'current' in maybeNavigationContainer
92
+ ) {
93
+ container = maybeNavigationContainer.current as NavigationContainer;
94
+ } else {
95
+ container = maybeNavigationContainer as NavigationContainer;
96
+ }
97
+
98
+ if (!container) {
99
+ console.warn('[Pulse Navigation] Invalid navigation container ref');
100
+ return;
101
+ }
102
+
103
+ if (isInitialized && navigationContainer === container) {
104
+ console.log('[Pulse Navigation] Container already registered');
105
+ return;
106
+ }
107
+
108
+ navigationContainer = container;
109
+
110
+ navigationContainer.addListener(
111
+ '__unsafe_action__',
112
+ onNavigationDispatch
113
+ );
114
+ navigationContainer.addListener('state', onStateChange);
115
+
116
+ const currentRoute = navigationContainer.getCurrentRoute();
117
+ if (currentRoute) {
118
+ latestRoute = currentRoute;
119
+ pushRecentRouteKey(currentRoute.key);
120
+ }
121
+
122
+ isInitialized = true;
123
+ console.log('[Pulse Navigation] Integration initialized successfully');
124
+ } catch (error) {
125
+ console.error('[Pulse Navigation] Error registering container:', error);
126
+ }
127
+ };
128
+
129
+ const pushRecentRouteKey = (key: string): void => {
130
+ recentRouteKeys.push(key);
131
+
132
+ if (recentRouteKeys.length > NAVIGATION_HISTORY_MAX_SIZE) {
133
+ recentRouteKeys = recentRouteKeys.slice(
134
+ recentRouteKeys.length - NAVIGATION_HISTORY_MAX_SIZE
135
+ );
136
+ }
137
+ };
138
+
139
+ return {
140
+ registerNavigationContainer,
141
+ };
142
+ }
143
+
144
+ export type ReactNavigationIntegration = ReturnType<
145
+ typeof createReactNavigationIntegration
146
+ >;
package/src/trace.ts ADDED
@@ -0,0 +1,108 @@
1
+ import PulseReactNativeOtel from './NativePulseReactNativeOtel';
2
+ import { isSupportedPlatform } from './initialization';
3
+ import { mergeWithGlobalAttributes } from './globalAttributes';
4
+ import { extractErrorDetails } from './utility';
5
+ import type { PulseAttributes } from './pulse.interface';
6
+
7
+ export type SpanOptions = {
8
+ attributes?: PulseAttributes;
9
+ };
10
+
11
+ export enum SpanStatusCode {
12
+ OK = 'OK',
13
+ ERROR = 'ERROR',
14
+ UNSET = 'UNSET',
15
+ }
16
+
17
+ export type Span = {
18
+ end: (statusCode?: SpanStatusCode) => void;
19
+ addEvent: (name: string, attributes?: PulseAttributes) => void;
20
+ setAttributes: (attributes?: PulseAttributes) => void;
21
+ recordException: (error: Error, attributes?: PulseAttributes) => void;
22
+ // This is the auto-generated ID for span on the native side.
23
+ spanId?: string;
24
+ };
25
+
26
+ export function startSpan(name: string, options?: SpanOptions): Span {
27
+ if (!isSupportedPlatform()) {
28
+ return {
29
+ end: (_statusCode?: SpanStatusCode) => {},
30
+ addEvent: (_eventName: string, _eventAttributes?: PulseAttributes) => {},
31
+ setAttributes: (_attributes?: PulseAttributes) => {},
32
+ recordException: (_error: Error, _attributes?: PulseAttributes) => {},
33
+ spanId: undefined,
34
+ };
35
+ }
36
+
37
+ const mergedAttributes = mergeWithGlobalAttributes(options?.attributes || {});
38
+ const spanId = PulseReactNativeOtel.startSpan(name, mergedAttributes);
39
+ return {
40
+ end: (statusCode?: SpanStatusCode) => {
41
+ return endSpan(spanId, statusCode);
42
+ },
43
+ addEvent: (eventName: string, eventAttributes?: PulseAttributes) => {
44
+ return addSpanEvent(spanId, eventName, eventAttributes);
45
+ },
46
+ setAttributes: (spanAttributes?: PulseAttributes) => {
47
+ return setSpanAttributes(spanId, spanAttributes);
48
+ },
49
+ recordException: (error: Error, attributes?: PulseAttributes) => {
50
+ return recordSpanException(error, attributes);
51
+ },
52
+ spanId: spanId,
53
+ };
54
+ }
55
+
56
+ export function trackSpan<T>(
57
+ name: string,
58
+ options: SpanOptions,
59
+ fn: () => T | Promise<T>
60
+ ): T | Promise<T> {
61
+ if (!isSupportedPlatform()) {
62
+ return fn();
63
+ }
64
+
65
+ const mergedAttributes = mergeWithGlobalAttributes(options?.attributes || {});
66
+ const spanId = PulseReactNativeOtel.startSpan(name, mergedAttributes);
67
+
68
+ const result = fn();
69
+
70
+ if (result && typeof (result as any).then === 'function') {
71
+ return (result as Promise<T>).finally(() => {
72
+ endSpan(spanId, SpanStatusCode.UNSET);
73
+ });
74
+ }
75
+
76
+ endSpan(spanId, SpanStatusCode.UNSET);
77
+ return result as T;
78
+ }
79
+
80
+ function endSpan(spanId: string, statusCode?: SpanStatusCode): void {
81
+ PulseReactNativeOtel.endSpan(spanId, statusCode);
82
+ }
83
+
84
+ function addSpanEvent(
85
+ spanId: string,
86
+ name: string,
87
+ attributes?: PulseAttributes
88
+ ): void {
89
+ PulseReactNativeOtel.addSpanEvent(spanId, name, attributes || undefined);
90
+ }
91
+
92
+ function setSpanAttributes(spanId: string, attributes?: PulseAttributes): void {
93
+ PulseReactNativeOtel.setSpanAttributes(spanId, attributes || undefined);
94
+ }
95
+
96
+ function recordSpanException(error: Error, attributes?: PulseAttributes): void {
97
+ const { message, stackTrace, errorType } = extractErrorDetails(error);
98
+ const observedTimeMs = Date.now();
99
+ const mergedAttributes = mergeWithGlobalAttributes(attributes || {});
100
+ PulseReactNativeOtel.reportException(
101
+ message,
102
+ observedTimeMs,
103
+ stackTrace || '',
104
+ false,
105
+ errorType,
106
+ mergedAttributes
107
+ );
108
+ }
package/src/user.ts ADDED
@@ -0,0 +1,24 @@
1
+ import PulseReactNativeOtel from './NativePulseReactNativeOtel';
2
+ import { isSupportedPlatform } from './initialization';
3
+ import type { PulseAttributes } from './pulse.interface';
4
+
5
+ export function setUserId(id: string | null): void {
6
+ if (!isSupportedPlatform()) {
7
+ return;
8
+ }
9
+ PulseReactNativeOtel.setUserId(id);
10
+ }
11
+
12
+ export function setUserProperty(name: string, value: string | null): void {
13
+ if (!isSupportedPlatform()) {
14
+ return;
15
+ }
16
+ PulseReactNativeOtel.setUserProperty(name, value);
17
+ }
18
+
19
+ export function setUserProperties(properties: PulseAttributes): void {
20
+ if (!isSupportedPlatform()) {
21
+ return;
22
+ }
23
+ PulseReactNativeOtel.setUserProperties(properties);
24
+ }
package/src/utility.ts ADDED
@@ -0,0 +1,36 @@
1
+ export function nowMs(): number {
2
+ const perf = global.performance;
3
+ if (perf && typeof perf.now === 'function') return perf.now();
4
+ return Date.now();
5
+ }
6
+
7
+ export function getAbsoluteUrl(url: string, baseUrl?: string): string {
8
+ // if it looks like an absolute url do nothing
9
+ if (url.indexOf('https://') === 0 || url.indexOf('http://') === 0) return url;
10
+
11
+ try {
12
+ const absoluteUrl = new URL(url, baseUrl).href;
13
+
14
+ // if a trailing slash has been added inadvertently remove it
15
+ if (!url.endsWith('/') && absoluteUrl.endsWith('/')) {
16
+ return absoluteUrl.slice(0, -1);
17
+ }
18
+
19
+ return absoluteUrl;
20
+ } catch {
21
+ // not a valid URL for some reason - simply return it
22
+ return url;
23
+ }
24
+ }
25
+
26
+ export function extractErrorDetails(error: Error): {
27
+ message: string;
28
+ stackTrace: string;
29
+ errorType: string;
30
+ } {
31
+ const message = error.message || 'Unknown error';
32
+ const stackTrace = error.stack || '';
33
+ const errorType = error.name || '';
34
+
35
+ return { message, stackTrace, errorType };
36
+ }