@arms/rum-reactnative 0.1.3-beta.1 → 0.1.3-beta.3
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/README.md +27 -0
- package/RumSdkReactnative.podspec +19 -2
- package/android/build.gradle +68 -5
- package/docs/SDK /347/211/210/346/234/254/350/257/264/346/230/216.md" +78 -6
- package/docs//346/216/245/345/205/245ReactNative/345/272/224/347/224/250.md +1 -1
- package/lib/commonjs/NativeRumSdkReactnative.js +5 -1
- package/lib/commonjs/collector/resource/api.js +17 -11
- package/lib/commonjs/reporter/index.js +1 -1
- package/lib/commonjs/utils/api.js +3 -2
- package/lib/commonjs/utils/platform.js +1 -1
- package/lib/module/NativeRumSdkReactnative.js +6 -2
- package/lib/module/collector/resource/api.js +17 -11
- package/lib/module/reporter/index.js +1 -1
- package/lib/module/utils/api.js +3 -2
- package/lib/module/utils/platform.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 功能特性
|
|
6
6
|
|
|
7
|
+
- **原生崩溃采集** — 自动捕获 iOS(KSCrash)与 Android(Java + NDK)原生崩溃,支持堆栈双侧截断与数据体积优化
|
|
7
8
|
- **API性能监控** — 自动拦截 XMLHttpRequest / fetch 请求,采集耗时、状态码等指标
|
|
8
9
|
- **JS错误捕获** — 自动捕获 JavaScript 运行时异常
|
|
9
10
|
- **控制台错误监听** — 监听 `console.error` 输出
|
|
@@ -418,6 +419,32 @@ SDK 仅持久化一个键 `_arms_uid`(匿名用户 UID),用于跨冷启动
|
|
|
418
419
|
2. **隔离命名空间,不污染宿主 App**:iOS 走独立 `NSUserDefaults` suite、Android 走独立 prefs 文件,与宿主 App 自身的偏好数据互不干扰;用户卸载 / 抹机时会一并清除。
|
|
419
420
|
3. **从旧版本(依赖 AsyncStorage)升级会重置一次 UID**:原 AsyncStorage 中的 `_arms_uid` 不会自动迁移到新存储,老用户首次升级后会被分配新 UID(仅影响一次 UV 统计的连续性,不影响功能与后续使用)。如对历史 UID 连续性有强诉求,请提 issue。
|
|
420
421
|
|
|
422
|
+
## 版本要求
|
|
423
|
+
|
|
424
|
+
SDK 的 peerDependencies 声明为 `react-native: >=0.68.0`,原生构建配置已做多版本条件适配,不同功能点的实际要求如下:
|
|
425
|
+
|
|
426
|
+
| 功能点 | 关键依赖 / API | 最低 RN 版本 | 说明 |
|
|
427
|
+
|--------|--------------|-------------|------|
|
|
428
|
+
| **原生崩溃采集** | TurboModule + codegen | 0.68 | podspec / build.gradle 按版本条件分支,自动适配 0.68-0.70 和 0.71+ |
|
|
429
|
+
| NDK 崩溃采集(C/C++) | CMake + NDK `externalNativeBuild` | 0.68 | 随原生崩溃采集一起启用 |
|
|
430
|
+
| iOS 崩溃采集(KSCrash) | KSCrash ~2.0(CocoaPods 源码依赖) | 0.68 | `pod install` 时自动编译 |
|
|
431
|
+
| **JS 异常采集** | `ErrorUtils.setGlobalHandler` | 0.68 | 全版本可用 |
|
|
432
|
+
| Hermes Promise rejection | `HermesInternal.enablePromiseRejectionTracker` | 0.70(推荐) | Hermes 为默认引擎时完整支持 |
|
|
433
|
+
| JSC Promise rejection 兜底 | `globalThis.onunhandledrejection` | 0.68 | 非 Hermes 引擎有降级方案 |
|
|
434
|
+
| console.error 监听 | `interceptFunction(console, 'error')` | 0.68 | 全版本可用 |
|
|
435
|
+
| **API 性能监控** | `XMLHttpRequest.prototype` 拦截 | 0.68 | 全版本可用 |
|
|
436
|
+
| fetch 拦截 | `globalThis.fetch` patch | 0.68 | 当前已注释,仅启用 XHR 拦截 |
|
|
437
|
+
| 分布式链路追踪 | tracecontext / b3 / jaeger / sw8 | 0.68 | 纯 JS 实现,无原生依赖 |
|
|
438
|
+
| **页面导航追踪(PV)** | React Navigation `addListener('state')` | 0.68 | 兼容 React Navigation v5 / v6 |
|
|
439
|
+
| **用户行为采集(Action)** | patch `React.createElement` | 0.68 | 经典 JSX 运行时 |
|
|
440
|
+
| Pressable 组件支持 | `RN.Pressable` | 0.68 | Pressable 在 0.63 引入,下限受 peerDependencies 约束 |
|
|
441
|
+
| automatic JSX runtime patch | `react/jsx-runtime` 的 `jsx` / `jsxs` / `jsxDEV` | 0.73(推荐) | automatic runtime 在 RN 0.73+ 为默认 |
|
|
442
|
+
| **设备信息采集** | `Platform.OS` / `Dimensions.get()` | 0.68 | 全版本可用,原生桥失败有 JS 降级 |
|
|
443
|
+
| **自监控(Self-Monitoring)** | `fetch` 上报 Protobuf 至 `/rum/monitor` | 0.68 | 纯 JS 实现 |
|
|
444
|
+
| **Expo 集成** | Expo Config Plugin | Expo SDK 49+(RN 0.72+) | 仅配置透传,零运行时依赖 |
|
|
445
|
+
|
|
446
|
+
> **总结**:SDK 声明支持 RN ≥ 0.68,原生构建配置(podspec / build.gradle)已做多版本条件适配。RN 0.71+ 使用 `install_modules_dependencies` / `react-android`;RN 0.68-0.70 使用手动依赖 / `react-native:+`。JS 侧通过 `TurboModuleRegistry.get` + `NativeModules` 双路径获取原生模块,原生模块缺失时自动降级为空实现,不影响纯 JS 能力。
|
|
447
|
+
|
|
421
448
|
## 相关文档
|
|
422
449
|
|
|
423
450
|
- [ARMS用户体验监控文档](https://help.aliyun.com/zh/arms/user-experience-monitoring/)
|
|
@@ -10,7 +10,10 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.license = package["license"]
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# RN 0.73+ 通过 min_ios_version_supported 变量提供 iOS 最低版本;
|
|
14
|
+
# RN 0.68-0.72 无此变量,回退到 iOS 13.0。
|
|
15
|
+
min_ios = defined?(min_ios_version_supported) ? min_ios_version_supported : '13.0'
|
|
16
|
+
s.platforms = { :ios => min_ios }
|
|
14
17
|
s.source = { :git => "https://code.alibaba-inc.com/aliyun-rum/rum-sdk-reactnative.git", :tag => "#{s.version}" }
|
|
15
18
|
|
|
16
19
|
s.source_files = "ios/**/*.{h,m,mm,c,cpp}"
|
|
@@ -24,5 +27,19 @@ Pod::Spec.new do |s|
|
|
|
24
27
|
# 故不自行预编译,直接由宿主工程 `pod install` 时源码编译。
|
|
25
28
|
s.dependency "KSCrash", "~> 2.0"
|
|
26
29
|
|
|
27
|
-
install_modules_dependencies
|
|
30
|
+
if defined?(install_modules_dependencies)
|
|
31
|
+
# RN 0.71+ 使用 install_modules_dependencies 自动解析 React 依赖
|
|
32
|
+
install_modules_dependencies(s)
|
|
33
|
+
else
|
|
34
|
+
# RN 0.68-0.70 手动声明 React 依赖
|
|
35
|
+
s.dependency 'React-Core'
|
|
36
|
+
if ENV["RCT_NEW_ARCH_ENABLED"] == "1"
|
|
37
|
+
s.dependency "React-RCTFabric"
|
|
38
|
+
s.dependency "React-Codegen"
|
|
39
|
+
s.dependency "RCT-Folly"
|
|
40
|
+
s.dependency "RCTRequired"
|
|
41
|
+
s.dependency "RCTTypeSafety"
|
|
42
|
+
s.dependency "ReactCommon/turbomodule/core"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
28
45
|
end
|
package/android/build.gradle
CHANGED
|
@@ -2,13 +2,71 @@ ext.getExtOrDefault = {name ->
|
|
|
2
2
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['RumSdkReactnative_' + name]
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
def getExtOrIntegerDefault(name) {
|
|
6
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["RumSdkReactnative_" + name]).toInteger()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 定位宿主工程的 react-native 安装路径。
|
|
11
|
+
* 优先使用 REACT_NATIVE_NODE_MODULES_DIR 扩展属性,
|
|
12
|
+
* 其次尝试通过 node 解析(Gradle 7.5+),
|
|
13
|
+
* 最后搜索常见 node_modules 路径。
|
|
14
|
+
*/
|
|
15
|
+
def resolveReactNativeDir() {
|
|
16
|
+
def override = getExtOrDefault("REACT_NATIVE_NODE_MODULES_DIR")
|
|
17
|
+
if (override != null) {
|
|
18
|
+
return file(override)
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
// Gradle 7.5+ (RN 0.71+)
|
|
22
|
+
def resolved = providers.exec {
|
|
23
|
+
workingDir = rootDir
|
|
24
|
+
commandLine("node", "--print", "require.resolve('react-native/package.json')")
|
|
25
|
+
}.standardOutput.asText.get().trim()
|
|
26
|
+
return file(resolved).parentFile
|
|
27
|
+
} catch (Exception ignored) {
|
|
28
|
+
// Gradle < 7.5 (RN 0.68-0.70): 搜索常见路径
|
|
29
|
+
def candidates = [
|
|
30
|
+
file("${rootDir}/../node_modules/react-native"),
|
|
31
|
+
file("${projectDir}/../../node_modules/react-native"),
|
|
32
|
+
file("${rootDir}/node_modules/react-native")
|
|
33
|
+
]
|
|
34
|
+
for (def c : candidates) {
|
|
35
|
+
if (c.exists()) return c
|
|
36
|
+
}
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 判断宿主工程的 React Native 版本是否 >= 指定的 major.minor。
|
|
43
|
+
* 无法检测时默认返回 true(假设为较新版本)。
|
|
44
|
+
*/
|
|
45
|
+
def isRnVersionAtLeast(int minMajor, int minMinor) {
|
|
46
|
+
try {
|
|
47
|
+
def rnDir = resolveReactNativeDir()
|
|
48
|
+
if (rnDir == null) return true
|
|
49
|
+
def pkgFile = new File(rnDir, "package.json")
|
|
50
|
+
if (!pkgFile.exists()) return true
|
|
51
|
+
def version = new groovy.json.JsonSlurper().parse(pkgFile).version as String
|
|
52
|
+
def parts = version.tokenize(".")
|
|
53
|
+
def major = parts[0].toInteger()
|
|
54
|
+
def minor = parts[1].toInteger()
|
|
55
|
+
return major > minMajor || (major == minMajor && minor >= minMinor)
|
|
56
|
+
} catch (Exception ignored) {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def isRn71Plus = isRnVersionAtLeast(0, 71)
|
|
62
|
+
|
|
5
63
|
apply plugin: "com.android.library"
|
|
6
64
|
apply plugin: "kotlin-android"
|
|
7
65
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
66
|
+
// RN 0.71+ 提供 com.facebook.react Gradle 插件用于 codegen,
|
|
67
|
+
// RN 0.68-0.70 无此插件,跳过即可。
|
|
68
|
+
if (isRn71Plus) {
|
|
69
|
+
apply plugin: "com.facebook.react"
|
|
12
70
|
}
|
|
13
71
|
|
|
14
72
|
android {
|
|
@@ -78,6 +136,11 @@ repositories {
|
|
|
78
136
|
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
79
137
|
|
|
80
138
|
dependencies {
|
|
81
|
-
|
|
139
|
+
// RN 0.71+ 使用 react-android 制品,RN 0.68-0.70 使用 react-native:+
|
|
140
|
+
if (isRn71Plus) {
|
|
141
|
+
implementation "com.facebook.react:react-android"
|
|
142
|
+
} else {
|
|
143
|
+
implementation "com.facebook.react:react-native:+"
|
|
144
|
+
}
|
|
82
145
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
83
146
|
}
|
|
@@ -4,9 +4,81 @@
|
|
|
4
4
|
|
|
5
5
|
## 版本列表
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
### v0.1.3(2026年07月)
|
|
8
|
+
|
|
9
|
+
原生崩溃采集安全加固与自监控系统集成。
|
|
10
|
+
|
|
11
|
+
- 修复 Android/iOS 原生崩溃采集多项安全漏洞(缓冲区溢出、路径遍历、crash_id 校验)
|
|
12
|
+
- 提升采集稳定性(栈帧上限 256、线程安全、多格式时间戳解析)
|
|
13
|
+
- 集成 rum-core Telemetry 自监控,SDK 内部异常自动上报
|
|
14
|
+
- 公开导出 logger、callMonitored 供用户扩展使用
|
|
15
|
+
|
|
16
|
+
#### v0.1.3-beta.3(2026年07月16日)
|
|
17
|
+
|
|
18
|
+
API 监控增强与代码规范化。
|
|
19
|
+
|
|
20
|
+
- 新增 XMLHttpRequest abort 事件处理,触发时标记 success 为 Failed
|
|
21
|
+
- 修正 reviseApiAttr 保留 success 三态值(-1/0/1)的行为
|
|
22
|
+
- 将 reporter sendRequest 失败日志等级由 error 降为 warn
|
|
23
|
+
|
|
24
|
+
#### v0.1.3-beta.2(2026年07月16日)
|
|
25
|
+
|
|
26
|
+
React Native 0.68-0.70 多版本兼容适配。
|
|
27
|
+
|
|
28
|
+
- react-native peerDependencies 版本下限调整为 0.68.0
|
|
29
|
+
- podspec / Android build.gradle 按 RN 版本条件适配依赖与插件配置
|
|
30
|
+
- iOS crash 处理和代码生成适配多 RN 版本环境
|
|
31
|
+
- 修复多平台崩溃采集数据不统一问题,保证字段和结构一致性
|
|
32
|
+
- 强化 NDK 崩溃处理边界检查,提升稳定性
|
|
33
|
+
- 缺少原生模块时自动降级,不影响纯 JS 功能运行
|
|
34
|
+
|
|
35
|
+
#### v0.1.3-beta.1(2026年07月16日)
|
|
36
|
+
|
|
37
|
+
崩溃堆栈截断策略与模块基址计算修复。
|
|
38
|
+
|
|
39
|
+
- 统一安卓与 iOS 端双侧截断堆栈帧策略,保留前 256 + 后 256 帧,中间插入标记帧
|
|
40
|
+
- NDK 最大帧数由 256 扩充至 512
|
|
41
|
+
- 模块基址计算改用 rum_module_base_for_seg,确保 referenced_addrs 与 binary_images 过滤一致
|
|
42
|
+
- 添加边界检查防止 ELF note section 解析时二次 SIGSEGV 崩溃
|
|
43
|
+
|
|
44
|
+
#### v0.1.3-beta.0(2026年07月15日)
|
|
45
|
+
|
|
46
|
+
iOS 与 Android NDK 崩溃采集修复及数据体积优化。
|
|
47
|
+
|
|
48
|
+
- 修复 iOS objc_exception 和 cpp_exception 被 RN 桥拦截问题,改为新 NSThread 抛出异常绕过拦截
|
|
49
|
+
- 修复 iOS KSCrash 安装失败静默吞掉、userInfo 异步更新竞态及 context 同步延迟问题
|
|
50
|
+
- 优化 binary_images 按需过滤,iOS 减少 70-85%、Android NDK 减少 70-90% 崩溃数据体积
|
|
51
|
+
- 移除 Android Java 崩溃 all_threads 字段,减少 60-80% 负载
|
|
52
|
+
- 修复 Android NDK 崩溃文件重复上报问题,统一 crash_id 和文件名前缀匹配逻辑
|
|
53
|
+
- 补充双平台缺失字段:iOS device brand/app package,Android rn_version,NDK platform/exception 结构
|
|
54
|
+
- 修复 NDK 崩溃信号处理器二次 SIGSEGV,增加地址映射检测和缓冲区中间刷新
|
|
55
|
+
|
|
56
|
+
### v0.1.2(2026年05月12日)
|
|
57
|
+
|
|
58
|
+
包名正式化与核心采集能力修复。
|
|
59
|
+
|
|
60
|
+
- rum-core 升级至 >=0.1.4,包管理迁移至 Yarn 3 workspaces
|
|
61
|
+
- 修复 PV 路由采集(首屏丢失、无 path 路由过滤、navigationRef 时序)
|
|
62
|
+
- 修复 API 监控默认配置误关闭、propagatorTypes 空值崩溃
|
|
63
|
+
- Reporter AppState 回调 this 绑定修复
|
|
64
|
+
- 完善 README、接入指南、配置参考等文档
|
|
65
|
+
|
|
66
|
+
### v0.1.1(2025年11月28日)
|
|
67
|
+
|
|
68
|
+
React Native SDK 首个正式版本,提供各种配置、API,并提供了以下采集能力:
|
|
69
|
+
|
|
70
|
+
- API 监控(XMLHttpRequest / fetch),包括链路追踪等特性的支持
|
|
71
|
+
- 异常监控(JS Error、Promise Rejection、console.error)
|
|
72
|
+
- Session 相关功能(采样、超时管理)
|
|
73
|
+
- 自定义上报(Custom、Exception、Resource、View、Event)
|
|
74
|
+
- React Navigation 集成支持
|
|
75
|
+
|
|
76
|
+
### v0.1.0(2025年08月20日)
|
|
77
|
+
|
|
78
|
+
React Native 首个版本,提供各种配置、API,并提供了以下采集能力:
|
|
79
|
+
|
|
80
|
+
- API 监控(XMLHttpRequest / fetch),包括链路追踪等特性的支持
|
|
81
|
+
- 异常监控(JS Error、Promise Rejection、console.error)
|
|
82
|
+
- Session 相关功能(采样、超时管理)
|
|
83
|
+
- 自定义上报(Custom、Exception、Resource、View、Event)
|
|
84
|
+
- React Navigation 集成支持
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
### 前提条件
|
|
20
20
|
|
|
21
|
-
- React Native版本 >= 0.
|
|
21
|
+
- React Native版本 >= 0.68(SDK 已适配 0.68-0.70 多版本环境,原生模块缺失时自动降级为纯 JS 模式)
|
|
22
22
|
- Node.js >= 18
|
|
23
23
|
- npm或yarn包管理工具
|
|
24
24
|
- Android 构建:需安装 NDK 与 CMake(由宿主工程的 `ndkVersion` 决定,SDK 不强制特定版本)
|
|
@@ -16,6 +16,10 @@ var _reactNative = require("react-native");
|
|
|
16
16
|
/**
|
|
17
17
|
* 获取原生模块。
|
|
18
18
|
*
|
|
19
|
+
* 优先使用 TurboModuleRegistry(RN 0.68+ TurboModule 架构),
|
|
20
|
+
* 若未找到则降级到 Legacy Bridge 的 NativeModules(RN 0.68-0.70 未开启 New Arch 时),
|
|
21
|
+
* 若均不可用则返回一组空实现兜底。
|
|
22
|
+
*
|
|
19
23
|
* 使用非强制的 `TurboModuleRegistry.get`(而非 `getEnforcing`),避免在原生
|
|
20
24
|
* 二进制未注册该模块时于「模块加载阶段」直接抛异常——那会连锁导致整个 JS
|
|
21
25
|
* bundle 评估失败(表现为所有路由丢失 default export、App 白屏)。
|
|
@@ -25,7 +29,7 @@ var _reactNative = require("react-native");
|
|
|
25
29
|
* JS 异常等纯 JS 能力不受影响。需重新构建原生工程(如 `expo run:ios` /
|
|
26
30
|
* `pod install` + 重新编译)后崩溃采集才会真正生效。
|
|
27
31
|
*/
|
|
28
|
-
const nativeModule = _reactNative.TurboModuleRegistry.get('RumSdkReactnative');
|
|
32
|
+
const nativeModule = _reactNative.TurboModuleRegistry.get('RumSdkReactnative') ?? _reactNative.NativeModules.RumSdkReactnative;
|
|
29
33
|
if (!nativeModule) {
|
|
30
34
|
console.warn("[rum] Native module 'RumSdkReactnative' not found in the native binary. " + 'Crash reporting and native device info are disabled. ' + 'Rebuild the native app (e.g. `expo run:ios` / `expo run:android`, ' + 'or `pod install` + rebuild) so the module gets registered.');
|
|
31
35
|
}
|
|
@@ -77,9 +77,9 @@ class ApiCollector {
|
|
|
77
77
|
}
|
|
78
78
|
hackXhr() {
|
|
79
79
|
const oldXHRProto = XMLHttpRequest.prototype;
|
|
80
|
-
const
|
|
80
|
+
const that = this;
|
|
81
81
|
(0, _rumCore.interceptFunction)(oldXHRProto, 'open', function (method, url, async, user, password) {
|
|
82
|
-
const filters =
|
|
82
|
+
const filters = that.ctx.getConfig().filters || {};
|
|
83
83
|
const urlStr = String(url);
|
|
84
84
|
const methodStr = method.toUpperCase();
|
|
85
85
|
// chrome 对 URL 的最大长度为 8182 个字符
|
|
@@ -93,7 +93,7 @@ class ApiCollector {
|
|
|
93
93
|
password,
|
|
94
94
|
headers: {}
|
|
95
95
|
};
|
|
96
|
-
const baseRumEvent =
|
|
96
|
+
const baseRumEvent = that.ctx.session.getBaseEvent();
|
|
97
97
|
this.apiAttr = {
|
|
98
98
|
...baseRumEvent,
|
|
99
99
|
url: urlStr,
|
|
@@ -111,7 +111,7 @@ class ApiCollector {
|
|
|
111
111
|
if (!this.apiAttr || !this.apiCache) return;
|
|
112
112
|
let apiAttr = this.apiAttr;
|
|
113
113
|
this.apiCache.data = body;
|
|
114
|
-
|
|
114
|
+
that.injectTracing(this.apiAttr, tracingHeaders => {
|
|
115
115
|
Object.keys(tracingHeaders).forEach(key => {
|
|
116
116
|
tracingHeaders[key] && this.setRequestHeader(key, tracingHeaders[key]);
|
|
117
117
|
});
|
|
@@ -124,7 +124,7 @@ class ApiCollector {
|
|
|
124
124
|
} = this;
|
|
125
125
|
const {
|
|
126
126
|
evaluateApi
|
|
127
|
-
} =
|
|
127
|
+
} = that.ctx.getConfig();
|
|
128
128
|
if (!this.apiAttr) return;
|
|
129
129
|
apiAttr.status_code = status;
|
|
130
130
|
apiAttr.message = statusText;
|
|
@@ -148,15 +148,21 @@ class ApiCollector {
|
|
|
148
148
|
_rumCore.logger.warn('api-collector', 'evaluateApi failed for XHR', e);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
that.sendAPI(apiAttr, entry);
|
|
152
152
|
});
|
|
153
153
|
this.addEventListener('error', () => {
|
|
154
154
|
this.apiAttr && (this.apiAttr.success = _rumCore.ResourceStatus.Failed);
|
|
155
155
|
});
|
|
156
|
+
this.addEventListener('abort', () => {
|
|
157
|
+
if (this.apiAttr) {
|
|
158
|
+
this.apiAttr.success = _rumCore.ResourceStatus.Failed;
|
|
159
|
+
this.apiAttr.message = 'abort';
|
|
160
|
+
}
|
|
161
|
+
});
|
|
156
162
|
}, true);
|
|
157
163
|
}
|
|
158
164
|
hackFetch() {
|
|
159
|
-
const
|
|
165
|
+
const that = this;
|
|
160
166
|
const originFetch = this.originFetch = globalThis.fetch;
|
|
161
167
|
const filters = this.ctx.getConfig().filters || {};
|
|
162
168
|
globalThis.fetch = function (input, init = {}) {
|
|
@@ -175,7 +181,7 @@ class ApiCollector {
|
|
|
175
181
|
if ((0, _rumCore.urlMatch)(urlStr, filters.resource) || method === 'HEAD' || init?.mode === 'no-cors' || urlStr.length > 8182) {
|
|
176
182
|
return originFetch.call(global, input, init);
|
|
177
183
|
}
|
|
178
|
-
const baseRumEvent =
|
|
184
|
+
const baseRumEvent = that.ctx.session.getBaseEvent();
|
|
179
185
|
let apiAttr = {
|
|
180
186
|
...baseRumEvent,
|
|
181
187
|
url: urlStr,
|
|
@@ -184,7 +190,7 @@ class ApiCollector {
|
|
|
184
190
|
};
|
|
185
191
|
let apiResp;
|
|
186
192
|
let apiError;
|
|
187
|
-
|
|
193
|
+
that.injectTracing(apiAttr, tracingHeaders => {
|
|
188
194
|
Object.keys(tracingHeaders).forEach(key => {
|
|
189
195
|
if (!headers) return;
|
|
190
196
|
if (headers instanceof Headers) {
|
|
@@ -198,7 +204,7 @@ class ApiCollector {
|
|
|
198
204
|
async function fetchFinally() {
|
|
199
205
|
const {
|
|
200
206
|
evaluateApi
|
|
201
|
-
} =
|
|
207
|
+
} = that.ctx.getConfig();
|
|
202
208
|
const entry = {
|
|
203
209
|
name: apiAttr.url,
|
|
204
210
|
entryType: _api.ApiType.FETCH,
|
|
@@ -218,7 +224,7 @@ class ApiCollector {
|
|
|
218
224
|
_rumCore.logger.warn('api-collector', 'evaluateApi failed for fetch', e);
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
|
-
|
|
227
|
+
that.sendAPI(apiAttr, entry);
|
|
222
228
|
}
|
|
223
229
|
return originFetch.call(globalThis, input, init).then(response => {
|
|
224
230
|
// @ts-ignore
|
|
@@ -138,7 +138,7 @@ class RNReporter extends _rumCore.Reporter {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
catchHandle = err => {
|
|
141
|
-
_rumCore.logger.
|
|
141
|
+
_rumCore.logger.warn('reporter', 'sendRequest failed', err);
|
|
142
142
|
};
|
|
143
143
|
destroy() {
|
|
144
144
|
this.appStateListener?.remove();
|
|
@@ -23,8 +23,9 @@ function reviseApiAttr(attrs) {
|
|
|
23
23
|
if ((0, _rumCore.isString)(attrs.message)) {
|
|
24
24
|
obj.message = attrs.message.substring(0, 1e3);
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// 对齐 Browser 端:保留三态 (-1/0/1),避免 Unknown(-1) 被误转为 Success(1)
|
|
27
|
+
if ((0, _rumCore.isNumber)(attrs.success) && attrs.success >= -1 && attrs.success <= 1) {
|
|
28
|
+
obj.success = attrs.success;
|
|
28
29
|
}
|
|
29
30
|
if ((0, _rumCore.isNumber)(attrs.duration) && attrs.duration >= 0) {
|
|
30
31
|
obj.duration = attrs.duration;
|
|
@@ -14,7 +14,7 @@ var _rumCore = require("@arms/rum-core");
|
|
|
14
14
|
var _reactNative = require("react-native");
|
|
15
15
|
var _NativeRumSdkReactnative = _interopRequireDefault(require("../NativeRumSdkReactnative"));
|
|
16
16
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
|
-
const _pkg = {"version":"0.1.3-beta.
|
|
17
|
+
const _pkg = {"version":"0.1.3-beta.3"};
|
|
18
18
|
const VERSION = exports.VERSION = _pkg.version;
|
|
19
19
|
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
import { NativeModules, TurboModuleRegistry } from 'react-native';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 原生设备信息,对应存储模型中的公共属性:
|
|
@@ -13,6 +13,10 @@ import { TurboModuleRegistry } from 'react-native';
|
|
|
13
13
|
/**
|
|
14
14
|
* 获取原生模块。
|
|
15
15
|
*
|
|
16
|
+
* 优先使用 TurboModuleRegistry(RN 0.68+ TurboModule 架构),
|
|
17
|
+
* 若未找到则降级到 Legacy Bridge 的 NativeModules(RN 0.68-0.70 未开启 New Arch 时),
|
|
18
|
+
* 若均不可用则返回一组空实现兜底。
|
|
19
|
+
*
|
|
16
20
|
* 使用非强制的 `TurboModuleRegistry.get`(而非 `getEnforcing`),避免在原生
|
|
17
21
|
* 二进制未注册该模块时于「模块加载阶段」直接抛异常——那会连锁导致整个 JS
|
|
18
22
|
* bundle 评估失败(表现为所有路由丢失 default export、App 白屏)。
|
|
@@ -22,7 +26,7 @@ import { TurboModuleRegistry } from 'react-native';
|
|
|
22
26
|
* JS 异常等纯 JS 能力不受影响。需重新构建原生工程(如 `expo run:ios` /
|
|
23
27
|
* `pod install` + 重新编译)后崩溃采集才会真正生效。
|
|
24
28
|
*/
|
|
25
|
-
const nativeModule = TurboModuleRegistry.get('RumSdkReactnative');
|
|
29
|
+
const nativeModule = TurboModuleRegistry.get('RumSdkReactnative') ?? NativeModules.RumSdkReactnative;
|
|
26
30
|
if (!nativeModule) {
|
|
27
31
|
console.warn("[rum] Native module 'RumSdkReactnative' not found in the native binary. " + 'Crash reporting and native device info are disabled. ' + 'Rebuild the native app (e.g. `expo run:ios` / `expo run:android`, ' + 'or `pod install` + rebuild) so the module gets registered.');
|
|
28
32
|
}
|
|
@@ -73,9 +73,9 @@ export default class ApiCollector {
|
|
|
73
73
|
}
|
|
74
74
|
hackXhr() {
|
|
75
75
|
const oldXHRProto = XMLHttpRequest.prototype;
|
|
76
|
-
const
|
|
76
|
+
const that = this;
|
|
77
77
|
interceptFunction(oldXHRProto, 'open', function (method, url, async, user, password) {
|
|
78
|
-
const filters =
|
|
78
|
+
const filters = that.ctx.getConfig().filters || {};
|
|
79
79
|
const urlStr = String(url);
|
|
80
80
|
const methodStr = method.toUpperCase();
|
|
81
81
|
// chrome 对 URL 的最大长度为 8182 个字符
|
|
@@ -89,7 +89,7 @@ export default class ApiCollector {
|
|
|
89
89
|
password,
|
|
90
90
|
headers: {}
|
|
91
91
|
};
|
|
92
|
-
const baseRumEvent =
|
|
92
|
+
const baseRumEvent = that.ctx.session.getBaseEvent();
|
|
93
93
|
this.apiAttr = {
|
|
94
94
|
...baseRumEvent,
|
|
95
95
|
url: urlStr,
|
|
@@ -107,7 +107,7 @@ export default class ApiCollector {
|
|
|
107
107
|
if (!this.apiAttr || !this.apiCache) return;
|
|
108
108
|
let apiAttr = this.apiAttr;
|
|
109
109
|
this.apiCache.data = body;
|
|
110
|
-
|
|
110
|
+
that.injectTracing(this.apiAttr, tracingHeaders => {
|
|
111
111
|
Object.keys(tracingHeaders).forEach(key => {
|
|
112
112
|
tracingHeaders[key] && this.setRequestHeader(key, tracingHeaders[key]);
|
|
113
113
|
});
|
|
@@ -120,7 +120,7 @@ export default class ApiCollector {
|
|
|
120
120
|
} = this;
|
|
121
121
|
const {
|
|
122
122
|
evaluateApi
|
|
123
|
-
} =
|
|
123
|
+
} = that.ctx.getConfig();
|
|
124
124
|
if (!this.apiAttr) return;
|
|
125
125
|
apiAttr.status_code = status;
|
|
126
126
|
apiAttr.message = statusText;
|
|
@@ -144,15 +144,21 @@ export default class ApiCollector {
|
|
|
144
144
|
logger.warn('api-collector', 'evaluateApi failed for XHR', e);
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
that.sendAPI(apiAttr, entry);
|
|
148
148
|
});
|
|
149
149
|
this.addEventListener('error', () => {
|
|
150
150
|
this.apiAttr && (this.apiAttr.success = ResourceStatus.Failed);
|
|
151
151
|
});
|
|
152
|
+
this.addEventListener('abort', () => {
|
|
153
|
+
if (this.apiAttr) {
|
|
154
|
+
this.apiAttr.success = ResourceStatus.Failed;
|
|
155
|
+
this.apiAttr.message = 'abort';
|
|
156
|
+
}
|
|
157
|
+
});
|
|
152
158
|
}, true);
|
|
153
159
|
}
|
|
154
160
|
hackFetch() {
|
|
155
|
-
const
|
|
161
|
+
const that = this;
|
|
156
162
|
const originFetch = this.originFetch = globalThis.fetch;
|
|
157
163
|
const filters = this.ctx.getConfig().filters || {};
|
|
158
164
|
globalThis.fetch = function (input, init = {}) {
|
|
@@ -171,7 +177,7 @@ export default class ApiCollector {
|
|
|
171
177
|
if (urlMatch(urlStr, filters.resource) || method === 'HEAD' || init?.mode === 'no-cors' || urlStr.length > 8182) {
|
|
172
178
|
return originFetch.call(global, input, init);
|
|
173
179
|
}
|
|
174
|
-
const baseRumEvent =
|
|
180
|
+
const baseRumEvent = that.ctx.session.getBaseEvent();
|
|
175
181
|
let apiAttr = {
|
|
176
182
|
...baseRumEvent,
|
|
177
183
|
url: urlStr,
|
|
@@ -180,7 +186,7 @@ export default class ApiCollector {
|
|
|
180
186
|
};
|
|
181
187
|
let apiResp;
|
|
182
188
|
let apiError;
|
|
183
|
-
|
|
189
|
+
that.injectTracing(apiAttr, tracingHeaders => {
|
|
184
190
|
Object.keys(tracingHeaders).forEach(key => {
|
|
185
191
|
if (!headers) return;
|
|
186
192
|
if (headers instanceof Headers) {
|
|
@@ -194,7 +200,7 @@ export default class ApiCollector {
|
|
|
194
200
|
async function fetchFinally() {
|
|
195
201
|
const {
|
|
196
202
|
evaluateApi
|
|
197
|
-
} =
|
|
203
|
+
} = that.ctx.getConfig();
|
|
198
204
|
const entry = {
|
|
199
205
|
name: apiAttr.url,
|
|
200
206
|
entryType: ApiType.FETCH,
|
|
@@ -214,7 +220,7 @@ export default class ApiCollector {
|
|
|
214
220
|
logger.warn('api-collector', 'evaluateApi failed for fetch', e);
|
|
215
221
|
}
|
|
216
222
|
}
|
|
217
|
-
|
|
223
|
+
that.sendAPI(apiAttr, entry);
|
|
218
224
|
}
|
|
219
225
|
return originFetch.call(globalThis, input, init).then(response => {
|
|
220
226
|
// @ts-ignore
|
package/lib/module/utils/api.js
CHANGED
|
@@ -15,8 +15,9 @@ export function reviseApiAttr(attrs) {
|
|
|
15
15
|
if (isString(attrs.message)) {
|
|
16
16
|
obj.message = attrs.message.substring(0, 1e3);
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// 对齐 Browser 端:保留三态 (-1/0/1),避免 Unknown(-1) 被误转为 Success(1)
|
|
19
|
+
if (isNumber(attrs.success) && attrs.success >= -1 && attrs.success <= 1) {
|
|
20
|
+
obj.success = attrs.success;
|
|
20
21
|
}
|
|
21
22
|
if (isNumber(attrs.duration) && attrs.duration >= 0) {
|
|
22
23
|
obj.duration = attrs.duration;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { AppType } from '@arms/rum-core';
|
|
4
4
|
import { Dimensions, Platform } from 'react-native';
|
|
5
5
|
import NativeRumSdkReactnative from "../NativeRumSdkReactnative.js";
|
|
6
|
-
const _pkg = {"version":"0.1.3-beta.
|
|
6
|
+
const _pkg = {"version":"0.1.3-beta.3"};
|
|
7
7
|
export const VERSION = _pkg.version;
|
|
8
8
|
|
|
9
9
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arms/rum-reactnative",
|
|
3
|
-
"version": "0.1.3-beta.
|
|
3
|
+
"version": "0.1.3-beta.3",
|
|
4
4
|
"description": "RUM SDK For ReactNative",
|
|
5
5
|
"homepage": "https://help.aliyun.com/zh/arms/user-experience-monitoring/",
|
|
6
6
|
"main": "./lib/commonjs/index.js",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"peerDependencies": {
|
|
110
110
|
"@arms/rum-core": ">=0.1.4",
|
|
111
111
|
"react": "*",
|
|
112
|
-
"react-native": "
|
|
112
|
+
"react-native": ">=0.68.0"
|
|
113
113
|
},
|
|
114
114
|
"workspaces": [
|
|
115
115
|
"example"
|