@dreamhorizonorg/pulse-react-native 0.0.1 → 0.0.2

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 (135) hide show
  1. package/PulseReactNativeOtel.podspec +1 -1
  2. package/README.md +34 -879
  3. package/android/build.gradle +10 -15
  4. package/android/proguard-rules.pro +3 -99
  5. package/android/src/main/java/com/pulsereactnativeotel/Pulse.kt +87 -0
  6. package/android/src/main/java/com/pulsereactnativeotel/PulseOtelConstants.kt +1 -1
  7. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelLogger.kt +3 -1
  8. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelModule.kt +53 -3
  9. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelPackage.kt +1 -1
  10. package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelTracer.kt +24 -8
  11. package/android/src/main/java/com/pulsereactnativeotel/ReactNativeScreenAttributesLogRecordProcessor.kt +21 -0
  12. package/android/src/main/java/com/pulsereactnativeotel/ReactNativeScreenAttributesSpanProcessor.kt +30 -0
  13. package/android/src/main/java/com/pulsereactnativeotel/ReactNativeScreenNameTracker.kt +17 -0
  14. package/app.plugin.js +1 -0
  15. package/ios/PulseReactNativeOtel.mm +7 -1
  16. package/lib/module/NativePulseReactNativeOtel.js.map +1 -1
  17. package/lib/module/config.js +29 -9
  18. package/lib/module/config.js.map +1 -1
  19. package/lib/module/errorBoundary.js.map +1 -1
  20. package/lib/module/events.js +6 -0
  21. package/lib/module/events.js.map +1 -1
  22. package/lib/module/index.js +4 -2
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/navigation/index.js +172 -0
  25. package/lib/module/navigation/index.js.map +1 -0
  26. package/lib/module/navigation/navigation.interface.js +2 -0
  27. package/lib/module/navigation/navigation.interface.js.map +1 -0
  28. package/lib/module/navigation/screen-interactive.js +101 -0
  29. package/lib/module/navigation/screen-interactive.js.map +1 -0
  30. package/lib/module/navigation/screen-load.js +68 -0
  31. package/lib/module/navigation/screen-load.js.map +1 -0
  32. package/lib/module/navigation/screen-session.js +60 -0
  33. package/lib/module/navigation/screen-session.js.map +1 -0
  34. package/lib/module/navigation/useNavigationTracking.js +33 -0
  35. package/lib/module/navigation/useNavigationTracking.js.map +1 -0
  36. package/lib/module/navigation/utils.js +17 -0
  37. package/lib/module/navigation/utils.js.map +1 -0
  38. package/lib/module/network-interceptor/graphql-helper.js +92 -0
  39. package/lib/module/network-interceptor/graphql-helper.js.map +1 -0
  40. package/lib/module/network-interceptor/request-tracker-xhr.js +2 -1
  41. package/lib/module/network-interceptor/request-tracker-xhr.js.map +1 -1
  42. package/lib/module/network-interceptor/span-helpers.js +24 -16
  43. package/lib/module/network-interceptor/span-helpers.js.map +1 -1
  44. package/lib/module/network-interceptor/url-helper.js +58 -2
  45. package/lib/module/network-interceptor/url-helper.js.map +1 -1
  46. package/lib/module/pulse.constants.js +42 -0
  47. package/lib/module/pulse.constants.js.map +1 -0
  48. package/lib/module/trace.js +17 -2
  49. package/lib/module/trace.js.map +1 -1
  50. package/lib/typescript/plugin/src/index.d.ts +5 -0
  51. package/lib/typescript/plugin/src/index.d.ts.map +1 -0
  52. package/lib/typescript/plugin/src/types.d.ts +27 -0
  53. package/lib/typescript/plugin/src/types.d.ts.map +1 -0
  54. package/lib/typescript/plugin/src/utils.d.ts +10 -0
  55. package/lib/typescript/plugin/src/utils.d.ts.map +1 -0
  56. package/lib/typescript/plugin/src/withAndroidPulse.d.ts +4 -0
  57. package/lib/typescript/plugin/src/withAndroidPulse.d.ts.map +1 -0
  58. package/lib/typescript/src/NativePulseReactNativeOtel.d.ts +8 -2
  59. package/lib/typescript/src/NativePulseReactNativeOtel.d.ts.map +1 -1
  60. package/lib/typescript/src/config.d.ts +8 -2
  61. package/lib/typescript/src/config.d.ts.map +1 -1
  62. package/lib/typescript/src/errorBoundary.d.ts.map +1 -1
  63. package/lib/typescript/src/events.d.ts.map +1 -1
  64. package/lib/typescript/src/index.d.ts +5 -3
  65. package/lib/typescript/src/index.d.ts.map +1 -1
  66. package/lib/typescript/src/navigation/index.d.ts +12 -0
  67. package/lib/typescript/src/navigation/index.d.ts.map +1 -0
  68. package/lib/typescript/src/navigation/navigation.interface.d.ts +17 -0
  69. package/lib/typescript/src/navigation/navigation.interface.d.ts.map +1 -0
  70. package/lib/typescript/src/navigation/screen-interactive.d.ts +16 -0
  71. package/lib/typescript/src/navigation/screen-interactive.d.ts.map +1 -0
  72. package/lib/typescript/src/navigation/screen-load.d.ts +13 -0
  73. package/lib/typescript/src/navigation/screen-load.d.ts.map +1 -0
  74. package/lib/typescript/src/navigation/screen-session.d.ts +15 -0
  75. package/lib/typescript/src/navigation/screen-session.d.ts.map +1 -0
  76. package/lib/typescript/src/navigation/useNavigationTracking.d.ts +5 -0
  77. package/lib/typescript/src/navigation/useNavigationTracking.d.ts.map +1 -0
  78. package/lib/typescript/src/navigation/utils.d.ts +8 -0
  79. package/lib/typescript/src/navigation/utils.d.ts.map +1 -0
  80. package/lib/typescript/src/network-interceptor/graphql-helper.d.ts +8 -0
  81. package/lib/typescript/src/network-interceptor/graphql-helper.d.ts.map +1 -0
  82. package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts.map +1 -1
  83. package/lib/typescript/src/network-interceptor/span-helpers.d.ts +1 -1
  84. package/lib/typescript/src/network-interceptor/span-helpers.d.ts.map +1 -1
  85. package/lib/typescript/src/network-interceptor/url-helper.d.ts +9 -0
  86. package/lib/typescript/src/network-interceptor/url-helper.d.ts.map +1 -1
  87. package/lib/typescript/src/pulse.constants.d.ts +35 -0
  88. package/lib/typescript/src/pulse.constants.d.ts.map +1 -0
  89. package/lib/typescript/src/pulse.interface.d.ts +2 -1
  90. package/lib/typescript/src/pulse.interface.d.ts.map +1 -1
  91. package/lib/typescript/src/trace.d.ts +7 -0
  92. package/lib/typescript/src/trace.d.ts.map +1 -1
  93. package/package.json +29 -9
  94. package/plugin/build/index.d.ts +4 -0
  95. package/plugin/build/index.js +10 -0
  96. package/plugin/build/types.d.ts +26 -0
  97. package/plugin/build/types.js +2 -0
  98. package/plugin/build/utils.d.ts +9 -0
  99. package/plugin/build/utils.js +102 -0
  100. package/plugin/build/withAndroidPulse.d.ts +3 -0
  101. package/plugin/build/withAndroidPulse.js +53 -0
  102. package/scripts/pulse-cli.js +82 -0
  103. package/scripts/uploadService.js +122 -0
  104. package/scripts/utils.js +125 -0
  105. package/src/NativePulseReactNativeOtel.ts +11 -2
  106. package/src/config.ts +37 -8
  107. package/src/errorBoundary.tsx +11 -5
  108. package/src/events.ts +7 -0
  109. package/src/global.d.ts +0 -1
  110. package/src/index.tsx +6 -3
  111. package/src/navigation/index.ts +306 -0
  112. package/src/navigation/navigation.interface.ts +19 -0
  113. package/src/navigation/screen-interactive.ts +149 -0
  114. package/src/navigation/screen-load.ts +103 -0
  115. package/src/navigation/screen-session.ts +87 -0
  116. package/src/navigation/useNavigationTracking.ts +50 -0
  117. package/src/navigation/utils.ts +19 -0
  118. package/src/network-interceptor/graphql-helper.ts +110 -0
  119. package/src/network-interceptor/request-tracker-xhr.ts +3 -1
  120. package/src/network-interceptor/span-helpers.ts +27 -18
  121. package/src/network-interceptor/url-helper.ts +67 -1
  122. package/src/pulse.constants.ts +38 -0
  123. package/src/pulse.interface.ts +6 -1
  124. package/src/trace.ts +25 -2
  125. package/LICENSE +0 -20
  126. package/lib/module/network-interceptor/request-tracker-fetch.js +0 -72
  127. package/lib/module/network-interceptor/request-tracker-fetch.js.map +0 -1
  128. package/lib/module/reactNavigation.js +0 -100
  129. package/lib/module/reactNavigation.js.map +0 -1
  130. package/lib/typescript/src/network-interceptor/request-tracker-fetch.d.ts +0 -7
  131. package/lib/typescript/src/network-interceptor/request-tracker-fetch.d.ts.map +0 -1
  132. package/lib/typescript/src/reactNavigation.d.ts +0 -10
  133. package/lib/typescript/src/reactNavigation.d.ts.map +0 -1
  134. package/src/network-interceptor/request-tracker-fetch.ts +0 -96
  135. package/src/reactNavigation.tsx +0 -146
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { checkAndAssertNodeVersion } = require('./utils');
4
+ checkAndAssertNodeVersion();
5
+
6
+ const { Command } = require('commander');
7
+ const { upload } = require('./uploadService');
8
+ const TAG_OPTIONAL = '(OPTIONAL)';
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name('pulse-cli')
14
+ .description('Pulse CLI - Command-line tool for Pulse SDK')
15
+ .usage('[command] [subcommand] [options]')
16
+ .version(require('../package.json').version);
17
+
18
+ const uploadCommand = new Command('upload')
19
+ .description(
20
+ 'Upload multiple symbol files to deobfuscate stack traces and improve error debugging.'
21
+ )
22
+ .usage('[subcommand] [options]');
23
+
24
+ uploadCommand
25
+ .command('react-native-android')
26
+ .description('Upload React Native Android files')
27
+ .requiredOption(
28
+ '-u, --api-url <url>',
29
+ 'URL for uploading source maps and related build artifacts.'
30
+ )
31
+ .requiredOption(
32
+ '-v, --app-version <version>',
33
+ 'App version of the application (e.g., 1.0.0)'
34
+ )
35
+ .requiredOption('-c, --version-code <code>', 'Version code (e.g., 1)')
36
+ .requiredOption(
37
+ '-j, --js-sourcemap <path>',
38
+ 'JavaScript source map file path'
39
+ )
40
+ .option(
41
+ '-b, --bundle-id <id>',
42
+ `${TAG_OPTIONAL} CodePush bundle label for identifying the specific bundle version (e.g., v1)`
43
+ )
44
+ .option(
45
+ '-m, --mapping <path>',
46
+ `${TAG_OPTIONAL} ProGuard/R8 mapping file path`
47
+ )
48
+ .option('-d, --debug', `${TAG_OPTIONAL} Show debug information`)
49
+ .action(async (options) => {
50
+ await upload('react-native-android', options);
51
+ });
52
+
53
+ uploadCommand
54
+ .command('react-native-ios')
55
+ .description('Upload React Native iOS source maps')
56
+ .requiredOption(
57
+ '-u, --api-url <url>',
58
+ 'URL for uploading source maps and related build artifacts.'
59
+ )
60
+ .requiredOption(
61
+ '-v, --bundle-version <version>',
62
+ 'Bundle version from Info.plist CFBundleShortVersionString (e.g., 1.0.0)'
63
+ )
64
+ .requiredOption('-c, --version-code <code>', 'Version code (e.g., 1)')
65
+ .requiredOption(
66
+ '-j, --js-sourcemap <path>',
67
+ 'JavaScript source map file path'
68
+ )
69
+ .option(
70
+ '-b, --bundle-id <id>',
71
+ `${TAG_OPTIONAL} CodePush bundle label for identifying the specific bundle version (e.g., v1)`
72
+ )
73
+ .option('-d, --debug', `${TAG_OPTIONAL} Show debug information`)
74
+ .action(async (options) => {
75
+ await upload('react-native-ios', options);
76
+ });
77
+
78
+ program.addCommand(uploadCommand);
79
+
80
+ program.showHelpAfterError();
81
+
82
+ program.parse();
@@ -0,0 +1,122 @@
1
+ /* global Buffer */
2
+ const fs = require('fs');
3
+ const {
4
+ getPlatform,
5
+ validateFiles,
6
+ validateVersionVersionCodeBundleId,
7
+ } = require('./utils');
8
+
9
+ function buildMetadata(files, appVersion, versionCode, platform, bundleId) {
10
+ const metadata = files.map((file) => ({
11
+ type: file.metadataType,
12
+ appVersion: appVersion,
13
+ versionCode: versionCode,
14
+ platform: platform,
15
+ fileName: file.fileName,
16
+ bundleId: bundleId || null,
17
+ }));
18
+ return metadata;
19
+ }
20
+
21
+ async function uploadFiles(commandName, options) {
22
+ const platform = getPlatform(commandName);
23
+ const files = validateFiles(options);
24
+ const version =
25
+ platform === 'ios' ? options.bundleVersion : options.appVersion;
26
+
27
+ const metadata = buildMetadata(
28
+ files,
29
+ version,
30
+ options.versionCode,
31
+ platform,
32
+ options.bundleId
33
+ );
34
+
35
+ const formData = new FormData();
36
+ const metadataContent = JSON.stringify(metadata, null, 2);
37
+
38
+ if (options.debug) {
39
+ console.log('\n📋 Metadata content (metadata.txt):');
40
+ console.log('─'.repeat(60));
41
+ console.log(metadataContent);
42
+ console.log('─'.repeat(60));
43
+ console.log(
44
+ `Metadata size: ${Buffer.byteLength(metadataContent, 'utf-8')} bytes\n`
45
+ );
46
+ }
47
+
48
+ const metadataBlob = new Blob([metadataContent], {
49
+ type: 'application/json',
50
+ });
51
+ formData.append('metadata', metadataBlob, 'metadata.txt');
52
+
53
+ for (const file of files) {
54
+ const fileBuffer = fs.readFileSync(file.path);
55
+ const fileBlob = new Blob([fileBuffer]);
56
+ formData.append('fileContent', fileBlob, file.fileName);
57
+ }
58
+
59
+ console.log(`\n📤 Uploading ${files.length} file(s) to ${options.apiUrl}...`);
60
+ console.log(` Command: ${commandName}`);
61
+ console.log(` Platform: ${platform}`);
62
+ files.forEach((file) => {
63
+ console.log(` - ${file.fileName} (${file.metadataType})`);
64
+ });
65
+
66
+ if (options.debug) {
67
+ console.log('\n🔍 Debug Info:');
68
+ console.log(` API URL: ${options.apiUrl}`);
69
+ console.log(` App Version: ${version}`);
70
+ console.log(` Version Code: ${options.versionCode}`);
71
+ if (options.bundleId) {
72
+ console.log(` Bundle ID: ${options.bundleId}`);
73
+ }
74
+ files.forEach((file) => {
75
+ const stats = fs.statSync(file.path);
76
+ console.log(
77
+ ` File: ${file.fileName} (${(stats.size / 1024).toFixed(2)} KB)`
78
+ );
79
+ console.log(` File Path: ${file.path}`);
80
+ });
81
+ }
82
+
83
+ const response = await fetch(options.apiUrl, {
84
+ method: 'POST',
85
+ body: formData,
86
+ });
87
+
88
+ const responseData = await response.json().catch(async () => {
89
+ const text = await response.text();
90
+ return text ? { message: text } : {};
91
+ });
92
+
93
+ if (options.debug && responseData && Object.keys(responseData).length > 0) {
94
+ console.log('\n📥 Backend Response:');
95
+ console.log(` Status: ${response.status} ${response.statusText}`);
96
+ console.log(` Response: ${JSON.stringify(responseData, null, 2)}`);
97
+ }
98
+
99
+ if (!response.ok) {
100
+ const errorText =
101
+ responseData.error || responseData.message || 'Unknown error';
102
+ throw new Error(
103
+ `Upload failed: HTTP ${response.status} ${response.statusText}. ${errorText}`
104
+ );
105
+ }
106
+
107
+ console.log('\n✓ Files uploaded successfully');
108
+ }
109
+
110
+ async function upload(commandName, options) {
111
+ try {
112
+ validateVersionVersionCodeBundleId(options, commandName);
113
+ await uploadFiles(commandName, options);
114
+ } catch (error) {
115
+ console.error(`\n✗ Error: ${error.message}`);
116
+ process.exit(1);
117
+ }
118
+ }
119
+
120
+ module.exports = {
121
+ upload,
122
+ };
@@ -0,0 +1,125 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const packageJson = require('../package.json');
4
+
5
+ const FILE_TYPE_TO_BACKEND_TYPE = {
6
+ 'js-sourcemap': 'JS',
7
+ 'mapping': 'mapping',
8
+ 'android-ndk': 'ndk',
9
+ };
10
+
11
+ function checkAndAssertNodeVersion() {
12
+ const nodeVersion = process.versions.node.split('.');
13
+ const majorVersion = parseInt(nodeVersion[0], 10);
14
+
15
+ const requiredVersion = packageJson.engines?.node;
16
+ let minMajorVersion = 18; // fallback
17
+
18
+ if (requiredVersion) {
19
+ const match = requiredVersion.match(/>=(\d+)/);
20
+ if (match) {
21
+ minMajorVersion = parseInt(match[1], 10);
22
+ }
23
+ }
24
+
25
+ if (majorVersion < minMajorVersion) {
26
+ console.error(
27
+ `✗ Error: Pulse CLI requires Node.js ${minMajorVersion}.0.0 or higher.`
28
+ );
29
+ console.error(` Current version: ${process.versions.node}`);
30
+ console.error(
31
+ ` Required: ${requiredVersion || `>=${minMajorVersion}.0.0`}`
32
+ );
33
+ console.error(` Please upgrade Node.js: https://nodejs.org/`);
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ function getPlatform(commandName) {
39
+ if (commandName.includes('android')) {
40
+ return 'android';
41
+ }
42
+ if (commandName.includes('ios')) {
43
+ return 'ios';
44
+ }
45
+ return 'Unknown';
46
+ }
47
+
48
+ function validateFiles(options) {
49
+ const files = [];
50
+ const errors = [];
51
+
52
+ Object.keys(FILE_TYPE_TO_BACKEND_TYPE).forEach((fileOption) => {
53
+ const optionKey = fileOption.replace(/-([a-z])/g, (_, letter) =>
54
+ letter.toUpperCase()
55
+ );
56
+ const optionValue = options[optionKey];
57
+
58
+ if (!optionValue) {
59
+ return;
60
+ }
61
+
62
+ const filePath = path.resolve(optionValue);
63
+ if (!fs.existsSync(filePath)) {
64
+ errors.push(`File not found for filepath: ${filePath}`);
65
+ return;
66
+ }
67
+
68
+ files.push({
69
+ optionName: fileOption,
70
+ path: filePath,
71
+ fileName: path.basename(filePath),
72
+ metadataType: FILE_TYPE_TO_BACKEND_TYPE[fileOption],
73
+ });
74
+ });
75
+
76
+ if (errors.length > 0) {
77
+ throw new Error(`Validation errors:\n ${errors.join('\n ')}`);
78
+ }
79
+
80
+ if (files.length === 0) {
81
+ throw new Error('No files to upload');
82
+ }
83
+
84
+ return files;
85
+ }
86
+
87
+ function validateVersionVersionCodeBundleId(options, commandName) {
88
+ const platform = getPlatform(commandName);
89
+ const isIOS = platform === 'ios';
90
+ const version = isIOS ? options.bundleVersion : options.appVersion;
91
+
92
+ if (!options.versionCode) {
93
+ throw new Error('Version code is required');
94
+ }
95
+ const versionCodeNum = parseInt(options.versionCode, 10);
96
+ if (isNaN(versionCodeNum) || versionCodeNum <= 0) {
97
+ throw new Error(
98
+ `Invalid version code: "${options.versionCode}". Must be a positive integer.`
99
+ );
100
+ }
101
+
102
+ if (!version || typeof version !== 'string' || version.trim().length === 0) {
103
+ throw new Error(
104
+ !version
105
+ ? `Missing required option: ${isIOS ? '--bundle-version' : '--app-version'}`
106
+ : `Invalid ${isIOS ? 'bundle version' : 'app version'}: "${version}". Must be a non-empty string.`
107
+ );
108
+ }
109
+
110
+ if (options.bundleId) {
111
+ const bundleIdPattern = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i;
112
+ if (!bundleIdPattern.test(options.bundleId)) {
113
+ throw new Error(
114
+ `Invalid bundle-id: "${options.bundleId}". Must be in reverse domain notation (e.g., com.example.app).`
115
+ );
116
+ }
117
+ }
118
+ }
119
+
120
+ module.exports = {
121
+ checkAndAssertNodeVersion,
122
+ getPlatform,
123
+ validateFiles,
124
+ validateVersionVersionCodeBundleId,
125
+ };
@@ -22,8 +22,8 @@ export interface Spec extends TurboModule {
22
22
  attributes?: Object
23
23
  ): boolean;
24
24
 
25
- /** Start an active span; returns spanId (simplified). */
26
- startSpan(name: string, attributes?: Object): string;
25
+ /** Start an active span; returns spanId. */
26
+ startSpan(name: string, inheritContext: boolean, attributes?: Object): string;
27
27
 
28
28
  /** End a span with optional status code. */
29
29
  endSpan(spanId: string, statusCode?: string): boolean;
@@ -41,6 +41,9 @@ export interface Spec extends TurboModule {
41
41
  stackTrace?: string
42
42
  ): boolean;
43
43
 
44
+ /** Discard a span without sending it to backend. */
45
+ discardSpan(spanId: string): boolean;
46
+
44
47
  /** Set user id for the session. Setting null will reset the id */
45
48
  setUserId(id: string | null): void;
46
49
 
@@ -52,6 +55,12 @@ export interface Spec extends TurboModule {
52
55
 
53
56
  /** Trigger ANR test (freezes main thread for 6 seconds) */
54
57
  triggerAnr(): void;
58
+
59
+ /** Set the current React Native screen name to sync active screen name on Android/iOS */
60
+ setCurrentScreenName(screenName: string): boolean;
61
+
62
+ /** Get all SDK Remote Config features */
63
+ getAllFeatures(): Object;
55
64
  }
56
65
 
57
66
  export default TurboModuleRegistry.getEnforcing<Spec>('PulseReactNativeOtel');
package/src/config.ts CHANGED
@@ -3,8 +3,11 @@ import { isSupportedPlatform } from './initialization';
3
3
  import {
4
4
  createReactNavigationIntegration,
5
5
  type ReactNavigationIntegration,
6
- } from './reactNavigation';
6
+ type NavigationIntegrationOptions,
7
+ } from './navigation';
7
8
  import { initializeNetworkInterceptor } from './network-interceptor/initialization';
9
+ import PulseReactNativeOtel from './NativePulseReactNativeOtel';
10
+ import type { PulseFeatureConfig } from './pulse.interface';
8
11
 
9
12
  export type PulseConfig = {
10
13
  autoDetectExceptions?: boolean;
@@ -26,6 +29,23 @@ const defaultConfig: PulseConfig = {
26
29
 
27
30
  let currentConfig: PulseConfig = { ...defaultConfig };
28
31
 
32
+ // Cache for features from remote SDK config
33
+ let cachedFeatures: PulseFeatureConfig = null;
34
+
35
+ /**
36
+ * Gets all features from the remote SDK config.
37
+ * @returns Record of feature names to their enabled status
38
+ */
39
+ export function getFeaturesFromRemoteConfig(): PulseFeatureConfig {
40
+ if (cachedFeatures !== null) {
41
+ return cachedFeatures;
42
+ }
43
+
44
+ const features = PulseReactNativeOtel.getAllFeatures();
45
+ cachedFeatures = features as PulseFeatureConfig;
46
+ return cachedFeatures;
47
+ }
48
+
29
49
  function configure(config: PulseConfig): void {
30
50
  currentConfig = {
31
51
  ...currentConfig,
@@ -42,9 +62,14 @@ export function start(options?: PulseStartOptions): void {
42
62
  if (!isSupportedPlatform()) {
43
63
  return;
44
64
  }
45
- const autoDetectExceptions = options?.autoDetectExceptions ?? true;
46
- const autoDetectNavigation = options?.autoDetectNavigation ?? true;
47
- const autoDetectNetwork = options?.autoDetectNetwork ?? true;
65
+ 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
+
48
73
  configure({
49
74
  autoDetectExceptions,
50
75
  autoDetectNavigation,
@@ -52,10 +77,13 @@ export function start(options?: PulseStartOptions): void {
52
77
  });
53
78
  }
54
79
 
55
- export function createNavigationIntegrationWithConfig(): ReactNavigationIntegration {
80
+ export function createNavigationIntegrationWithConfig(
81
+ options?: NavigationIntegrationOptions
82
+ ): ReactNavigationIntegration {
56
83
  if (!isSupportedPlatform()) {
57
84
  return {
58
- registerNavigationContainer: (_: unknown) => {},
85
+ registerNavigationContainer: (_: unknown) => () => {},
86
+ markContentReady: () => {},
59
87
  };
60
88
  }
61
89
  if (!currentConfig.autoDetectNavigation) {
@@ -63,13 +91,14 @@ export function createNavigationIntegrationWithConfig(): ReactNavigationIntegrat
63
91
  '[Pulse Navigation] auto-detection disabled via Pulse.start; createNavigationIntegration() returning no-op.'
64
92
  );
65
93
  const noop: ReactNavigationIntegration = {
66
- registerNavigationContainer: (_: unknown) => {
94
+ registerNavigationContainer: (_: unknown) => () => {
67
95
  console.warn(
68
96
  '[Pulse Navigation] auto-detection disabled via Pulse.start; registerNavigationContainer() returning no-op.'
69
97
  );
70
98
  },
99
+ markContentReady: () => {},
71
100
  };
72
101
  return noop;
73
102
  }
74
- return createReactNavigationIntegration();
103
+ return createReactNavigationIntegration(options);
75
104
  }
@@ -30,17 +30,22 @@ const INITIAL_STATE: ErrorBoundaryState = {
30
30
  error: null,
31
31
  };
32
32
 
33
- export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
33
+ export class ErrorBoundary extends React.Component<
34
+ ErrorBoundaryProps,
35
+ ErrorBoundaryState
36
+ > {
34
37
  public state: ErrorBoundaryState = INITIAL_STATE;
35
38
 
36
39
  public componentDidCatch(error: unknown, errorInfo: React.ErrorInfo): void {
37
- const componentStack = errorInfo.componentStack || COMPONENT_STACK_UNAVAILABLE;
40
+ const componentStack =
41
+ errorInfo.componentStack || COMPONENT_STACK_UNAVAILABLE;
38
42
  const { onError } = this.props;
39
43
 
40
44
  // Error is handled if a fallback is provided, otherwise it's unhandled (fatal)
41
45
  const handled = !!this.props.fallback;
42
46
 
43
- const errorToReport = error instanceof Error ? error : new Error(String(error));
47
+ const errorToReport =
48
+ error instanceof Error ? error : new Error(String(error));
44
49
  Pulse.reportException(errorToReport, !handled);
45
50
 
46
51
  if (onError) {
@@ -76,9 +81,10 @@ export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoun
76
81
 
77
82
  export function withErrorBoundary<P extends Record<string, any>>(
78
83
  WrappedComponent: React.ComponentType<P>,
79
- errorBoundaryOptions: ErrorBoundaryProps,
84
+ errorBoundaryOptions: ErrorBoundaryProps
80
85
  ): React.FC<P> {
81
- const componentDisplayName = WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;
86
+ const componentDisplayName =
87
+ WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;
82
88
 
83
89
  const Wrapped = React.memo((props: P) => (
84
90
  <ErrorBoundary {...errorBoundaryOptions}>
package/src/events.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import PulseReactNativeOtel from './NativePulseReactNativeOtel';
2
+ import { getFeaturesFromRemoteConfig } from './config';
2
3
  import { mergeWithGlobalAttributes } from './globalAttributes';
3
4
  import { isSupportedPlatform } from './initialization';
4
5
  import type { PulseAttributes } from './pulse.interface';
@@ -7,6 +8,12 @@ export function trackEvent(event: string, attributes?: PulseAttributes): void {
7
8
  if (!isSupportedPlatform()) {
8
9
  return;
9
10
  }
11
+ const features = getFeaturesFromRemoteConfig();
12
+ const customEventsEnabled = features?.custom_events ?? true;
13
+
14
+ if (!customEventsEnabled) {
15
+ return;
16
+ }
10
17
 
11
18
  const observedTimeMs = Date.now();
12
19
  const mergedAttributes = mergeWithGlobalAttributes(attributes || {});
package/src/global.d.ts CHANGED
@@ -6,4 +6,3 @@ declare global {
6
6
  }
7
7
 
8
8
  export {};
9
-
package/src/index.tsx CHANGED
@@ -1,11 +1,12 @@
1
1
  import { startSpan, trackSpan } from './trace';
2
2
  import { reportException } from './errorHandler';
3
3
  import { trackEvent } from './events';
4
- import { start, createNavigationIntegrationWithConfig } from './config';
4
+ import { start } from './config';
5
5
  import { isInitialized } from './initialization';
6
6
  import { setGlobalAttribute } from './globalAttributes';
7
7
  import { setUserId, setUserProperty, setUserProperties } from './user';
8
8
  import { ErrorBoundary, withErrorBoundary } from './errorBoundary';
9
+ import { useNavigationTracking, markContentReady } from './navigation';
9
10
 
10
11
  export type { Span } from './trace';
11
12
  export type { PulseConfig, PulseStartOptions } from './config';
@@ -13,7 +14,8 @@ export type { PulseAttributes, PulseAttributeValue } from './pulse.interface';
13
14
  export type {
14
15
  ReactNavigationIntegration,
15
16
  NavigationRoute,
16
- } from './reactNavigation';
17
+ NavigationIntegrationOptions,
18
+ } from './navigation';
17
19
 
18
20
  export type { ErrorBoundaryProps, FallbackRender } from './errorBoundary';
19
21
 
@@ -21,7 +23,8 @@ export { SpanStatusCode } from './trace';
21
23
  export const Pulse = {
22
24
  start,
23
25
  isInitialized,
24
- createNavigationIntegration: createNavigationIntegrationWithConfig,
26
+ useNavigationTracking,
27
+ markContentReady,
25
28
  trackEvent,
26
29
  reportException,
27
30
  trackSpan,