@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.
- package/PulseReactNativeOtel.podspec +1 -1
- package/README.md +34 -879
- package/android/build.gradle +10 -15
- package/android/proguard-rules.pro +3 -99
- package/android/src/main/java/com/pulsereactnativeotel/Pulse.kt +87 -0
- package/android/src/main/java/com/pulsereactnativeotel/PulseOtelConstants.kt +1 -1
- package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelLogger.kt +3 -1
- package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelModule.kt +53 -3
- package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelPackage.kt +1 -1
- package/android/src/main/java/com/pulsereactnativeotel/PulseReactNativeOtelTracer.kt +24 -8
- package/android/src/main/java/com/pulsereactnativeotel/ReactNativeScreenAttributesLogRecordProcessor.kt +21 -0
- package/android/src/main/java/com/pulsereactnativeotel/ReactNativeScreenAttributesSpanProcessor.kt +30 -0
- package/android/src/main/java/com/pulsereactnativeotel/ReactNativeScreenNameTracker.kt +17 -0
- package/app.plugin.js +1 -0
- package/ios/PulseReactNativeOtel.mm +7 -1
- package/lib/module/NativePulseReactNativeOtel.js.map +1 -1
- package/lib/module/config.js +29 -9
- package/lib/module/config.js.map +1 -1
- package/lib/module/errorBoundary.js.map +1 -1
- package/lib/module/events.js +6 -0
- package/lib/module/events.js.map +1 -1
- package/lib/module/index.js +4 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigation/index.js +172 -0
- package/lib/module/navigation/index.js.map +1 -0
- package/lib/module/navigation/navigation.interface.js +2 -0
- package/lib/module/navigation/navigation.interface.js.map +1 -0
- package/lib/module/navigation/screen-interactive.js +101 -0
- package/lib/module/navigation/screen-interactive.js.map +1 -0
- package/lib/module/navigation/screen-load.js +68 -0
- package/lib/module/navigation/screen-load.js.map +1 -0
- package/lib/module/navigation/screen-session.js +60 -0
- package/lib/module/navigation/screen-session.js.map +1 -0
- package/lib/module/navigation/useNavigationTracking.js +33 -0
- package/lib/module/navigation/useNavigationTracking.js.map +1 -0
- package/lib/module/navigation/utils.js +17 -0
- package/lib/module/navigation/utils.js.map +1 -0
- package/lib/module/network-interceptor/graphql-helper.js +92 -0
- package/lib/module/network-interceptor/graphql-helper.js.map +1 -0
- package/lib/module/network-interceptor/request-tracker-xhr.js +2 -1
- package/lib/module/network-interceptor/request-tracker-xhr.js.map +1 -1
- package/lib/module/network-interceptor/span-helpers.js +24 -16
- package/lib/module/network-interceptor/span-helpers.js.map +1 -1
- package/lib/module/network-interceptor/url-helper.js +58 -2
- package/lib/module/network-interceptor/url-helper.js.map +1 -1
- package/lib/module/pulse.constants.js +42 -0
- package/lib/module/pulse.constants.js.map +1 -0
- package/lib/module/trace.js +17 -2
- package/lib/module/trace.js.map +1 -1
- package/lib/typescript/plugin/src/index.d.ts +5 -0
- package/lib/typescript/plugin/src/index.d.ts.map +1 -0
- package/lib/typescript/plugin/src/types.d.ts +27 -0
- package/lib/typescript/plugin/src/types.d.ts.map +1 -0
- package/lib/typescript/plugin/src/utils.d.ts +10 -0
- package/lib/typescript/plugin/src/utils.d.ts.map +1 -0
- package/lib/typescript/plugin/src/withAndroidPulse.d.ts +4 -0
- package/lib/typescript/plugin/src/withAndroidPulse.d.ts.map +1 -0
- package/lib/typescript/src/NativePulseReactNativeOtel.d.ts +8 -2
- package/lib/typescript/src/NativePulseReactNativeOtel.d.ts.map +1 -1
- package/lib/typescript/src/config.d.ts +8 -2
- package/lib/typescript/src/config.d.ts.map +1 -1
- package/lib/typescript/src/errorBoundary.d.ts.map +1 -1
- package/lib/typescript/src/events.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/index.d.ts +12 -0
- package/lib/typescript/src/navigation/index.d.ts.map +1 -0
- package/lib/typescript/src/navigation/navigation.interface.d.ts +17 -0
- package/lib/typescript/src/navigation/navigation.interface.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen-interactive.d.ts +16 -0
- package/lib/typescript/src/navigation/screen-interactive.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen-load.d.ts +13 -0
- package/lib/typescript/src/navigation/screen-load.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen-session.d.ts +15 -0
- package/lib/typescript/src/navigation/screen-session.d.ts.map +1 -0
- package/lib/typescript/src/navigation/useNavigationTracking.d.ts +5 -0
- package/lib/typescript/src/navigation/useNavigationTracking.d.ts.map +1 -0
- package/lib/typescript/src/navigation/utils.d.ts +8 -0
- package/lib/typescript/src/navigation/utils.d.ts.map +1 -0
- package/lib/typescript/src/network-interceptor/graphql-helper.d.ts +8 -0
- package/lib/typescript/src/network-interceptor/graphql-helper.d.ts.map +1 -0
- package/lib/typescript/src/network-interceptor/request-tracker-xhr.d.ts.map +1 -1
- package/lib/typescript/src/network-interceptor/span-helpers.d.ts +1 -1
- package/lib/typescript/src/network-interceptor/span-helpers.d.ts.map +1 -1
- package/lib/typescript/src/network-interceptor/url-helper.d.ts +9 -0
- package/lib/typescript/src/network-interceptor/url-helper.d.ts.map +1 -1
- package/lib/typescript/src/pulse.constants.d.ts +35 -0
- package/lib/typescript/src/pulse.constants.d.ts.map +1 -0
- package/lib/typescript/src/pulse.interface.d.ts +2 -1
- package/lib/typescript/src/pulse.interface.d.ts.map +1 -1
- package/lib/typescript/src/trace.d.ts +7 -0
- package/lib/typescript/src/trace.d.ts.map +1 -1
- package/package.json +29 -9
- package/plugin/build/index.d.ts +4 -0
- package/plugin/build/index.js +10 -0
- package/plugin/build/types.d.ts +26 -0
- package/plugin/build/types.js +2 -0
- package/plugin/build/utils.d.ts +9 -0
- package/plugin/build/utils.js +102 -0
- package/plugin/build/withAndroidPulse.d.ts +3 -0
- package/plugin/build/withAndroidPulse.js +53 -0
- package/scripts/pulse-cli.js +82 -0
- package/scripts/uploadService.js +122 -0
- package/scripts/utils.js +125 -0
- package/src/NativePulseReactNativeOtel.ts +11 -2
- package/src/config.ts +37 -8
- package/src/errorBoundary.tsx +11 -5
- package/src/events.ts +7 -0
- package/src/global.d.ts +0 -1
- package/src/index.tsx +6 -3
- package/src/navigation/index.ts +306 -0
- package/src/navigation/navigation.interface.ts +19 -0
- package/src/navigation/screen-interactive.ts +149 -0
- package/src/navigation/screen-load.ts +103 -0
- package/src/navigation/screen-session.ts +87 -0
- package/src/navigation/useNavigationTracking.ts +50 -0
- package/src/navigation/utils.ts +19 -0
- package/src/network-interceptor/graphql-helper.ts +110 -0
- package/src/network-interceptor/request-tracker-xhr.ts +3 -1
- package/src/network-interceptor/span-helpers.ts +27 -18
- package/src/network-interceptor/url-helper.ts +67 -1
- package/src/pulse.constants.ts +38 -0
- package/src/pulse.interface.ts +6 -1
- package/src/trace.ts +25 -2
- package/LICENSE +0 -20
- package/lib/module/network-interceptor/request-tracker-fetch.js +0 -72
- package/lib/module/network-interceptor/request-tracker-fetch.js.map +0 -1
- package/lib/module/reactNavigation.js +0 -100
- package/lib/module/reactNavigation.js.map +0 -1
- package/lib/typescript/src/network-interceptor/request-tracker-fetch.d.ts +0 -7
- package/lib/typescript/src/network-interceptor/request-tracker-fetch.d.ts.map +0 -1
- package/lib/typescript/src/reactNavigation.d.ts +0 -10
- package/lib/typescript/src/reactNavigation.d.ts.map +0 -1
- package/src/network-interceptor/request-tracker-fetch.ts +0 -96
- 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
|
+
};
|
package/scripts/utils.js
ADDED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
46
|
-
const
|
|
47
|
-
|
|
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(
|
|
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
|
}
|
package/src/errorBoundary.tsx
CHANGED
|
@@ -30,17 +30,22 @@ const INITIAL_STATE: ErrorBoundaryState = {
|
|
|
30
30
|
error: null,
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export class ErrorBoundary extends React.Component<
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
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
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
useNavigationTracking,
|
|
27
|
+
markContentReady,
|
|
25
28
|
trackEvent,
|
|
26
29
|
reportException,
|
|
27
30
|
trackSpan,
|